From ddb60dbe5316d745d46eb08a20cc935a7552acae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= Date: Thu, 21 Oct 2021 12:29:24 +0200 Subject: [PATCH 1/5] add extension and update configurations --- 3p/integration.js | 2 - 3p/vendors/smartadserver.js | 12 - ads/_a4a-config.js | 1 + ads/vendors/smartadserver.js | 13 - .../compile/bundles.config.extensions.json | 5 + build-system/test-configs/dep-check-config.js | 1 + .../0.1/amp-ad-network-smartadserver-impl.js | 218 +++++++++ .../test-amp-ad-network-smartadserver-impl.js | 453 ++++++++++++++++++ .../amp-ad-network-smartadserver-impl/OWNERS | 18 + ...-ad-network-smartadserver-impl-internal.md | 65 +++ .../readme.md | 55 +++ extensions/amp-ad/amp-ad.md | 2 +- 12 files changed, 817 insertions(+), 28 deletions(-) delete mode 100644 3p/vendors/smartadserver.js delete mode 100644 ads/vendors/smartadserver.js create mode 100644 extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js create mode 100644 extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js create mode 100644 extensions/amp-ad-network-smartadserver-impl/OWNERS create mode 100644 extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md create mode 100644 extensions/amp-ad-network-smartadserver-impl/readme.md diff --git a/3p/integration.js b/3p/integration.js index bbde59e38a71..f92948a525cc 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -237,7 +237,6 @@ import {sharethrough} from '#ads/vendors/sharethrough'; import {shemedia} from '#ads/vendors/shemedia'; import {sklik} from '#ads/vendors/sklik'; import {slimcutmedia} from '#ads/vendors/slimcutmedia'; -import {smartadserver} from '#ads/vendors/smartadserver'; import {smartclip} from '#ads/vendors/smartclip'; import {smi2} from '#ads/vendors/smi2'; import {smilewanted} from '#ads/vendors/smilewanted'; @@ -529,7 +528,6 @@ register('shemedia', shemedia); register('sklik', sklik); register('ssp', ssp); register('slimcutmedia', slimcutmedia); -register('smartadserver', smartadserver); register('smartclip', smartclip); register('smi2', smi2); register('smilewanted', smilewanted); diff --git a/3p/vendors/smartadserver.js b/3p/vendors/smartadserver.js deleted file mode 100644 index b93432464cc0..000000000000 --- a/3p/vendors/smartadserver.js +++ /dev/null @@ -1,12 +0,0 @@ -// src/polyfills.js must be the first import. -import '#3p/polyfills'; - -import {register} from '#3p/3p'; -import {draw3p, init} from '#3p/integration-lib'; - -import {smartadserver} from '#ads/vendors/smartadserver'; - -init(window); -register('smartadserver', smartadserver); - -window.draw3p = draw3p; diff --git a/ads/_a4a-config.js b/ads/_a4a-config.js index 9611f0d01f20..7eaa40fb5323 100644 --- a/ads/_a4a-config.js +++ b/ads/_a4a-config.js @@ -28,6 +28,7 @@ export function getA4ARegistry() { 'doubleclick': () => true, 'fake': () => true, 'nws': () => true, + 'smartadserver': () => true, 'valueimpression': () => true, // TODO: Add new ad network implementation "is enabled" functions here. // Note: if you add a function here that requires a new "import", above, diff --git a/ads/vendors/smartadserver.js b/ads/vendors/smartadserver.js deleted file mode 100644 index fc27c18b698f..000000000000 --- a/ads/vendors/smartadserver.js +++ /dev/null @@ -1,13 +0,0 @@ -import {loadScript} from '#3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function smartadserver(global, data) { - // For more flexibility, we construct the call to SmartAdServer's URL in the - // external loader, based on the data received from the AMP tag. - loadScript(global, 'https://ec-ns.sascdn.com/diff/js/amp.v0.js', () => { - global.sas.callAmpAd(data); - }); -} diff --git a/build-system/compile/bundles.config.extensions.json b/build-system/compile/bundles.config.extensions.json index c24d40329fd8..5525757e43df 100644 --- a/build-system/compile/bundles.config.extensions.json +++ b/build-system/compile/bundles.config.extensions.json @@ -112,6 +112,11 @@ "version": "0.1", "latestVersion": "0.1" }, + { + "name": "amp-ad-network-smartadserver-impl", + "version": "0.1", + "latestVersion": "0.1" + }, { "name": "amp-ad-network-valueimpression-impl", "version": "0.1", diff --git a/build-system/test-configs/dep-check-config.js b/build-system/test-configs/dep-check-config.js index 2f16f9318e2c..7ca55c33b913 100644 --- a/build-system/test-configs/dep-check-config.js +++ b/build-system/test-configs/dep-check-config.js @@ -153,6 +153,7 @@ exports.rules = [ 'extensions/amp-ad-network-oblivki-impl/0.1/amp-ad-network-oblivki-impl.js->extensions/amp-a4a/0.1/amp-a4a.js', 'extensions/amp-ad-network-valueimpression-impl/0.1/amp-ad-network-valueimpression-impl.js->extensions/amp-a4a/0.1/amp-a4a.js', 'extensions/amp-ad-network-dianomi-impl/0.1/amp-ad-network-dianomi-impl.js->extensions/amp-a4a/0.1/amp-a4a.js', + 'extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js->extensions/amp-a4a/0.1/amp-a4a.js', // A4A impls importing amp fast fetch header name 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->extensions/amp-a4a/0.1/signature-verifier.js', diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js new file mode 100644 index 000000000000..1c3139335f45 --- /dev/null +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js @@ -0,0 +1,218 @@ +/* eslint-disable local/no-forbidden-terms */ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {buildUrl} from '#ads/google/a4a/shared/url-builder'; + +import {Deferred} from '#core/data-structures/promise'; +import {getPageLayoutBoxBlocking} from '#core/dom/layout/page-layout-box'; +import {tryParseJson} from '#core/types/object/json'; +import {includes} from '#core/types/string'; + +import {Services} from '#service'; + +import {dev} from '#utils/log'; + +import {getOrCreateAdCid} from '../../../src/ad-cid'; +import {getConsentPolicyInfo} from '../../../src/consent'; +import {AmpA4A} from '../../amp-a4a/0.1/amp-a4a'; + +/** @type {string} */ +const TAG = 'amp-ad-network-smartadserver-impl'; + +/** @const {number} */ +const MAX_URL_LENGTH = 15360; + +/** @type {string} */ +const SAS_NO_AD_STR = ''; + +/** + * @const {!./shared/url-builder.QueryParameterDef} + * @visibleForTesting + */ +const TRUNCATION_PARAM = { + name: 'trunc', + value: 1, +}; + +/** @final */ +export class AmpAdNetworkSmartadserverImpl extends AmpA4A { + /** + * @param {!Element} element + */ + constructor(element) { + super(element); + + /** @protected {!Deferred} */ + this.getAdUrlDeferred = new Deferred(); + } + + /** @override */ + getAdUrl(opt_consentTuple, opt_rtcResponsesPromise) { + Promise.any([ + getConsentPolicyInfo(this.element, this.getConsentPolicy() || 'default'), + new Promise((resolve) => setTimeout(() => resolve(), 10)), + ]).then((consentString) => { + opt_rtcResponsesPromise = opt_rtcResponsesPromise || Promise.resolve(); + const checkStillCurrent = this.verifyStillCurrent(); + + opt_rtcResponsesPromise.then((result) => { + checkStillCurrent(); + const rtc = this.getBestRtcCallout_(result); + const urlParams = {}; + let tgt = ''; // usunąć + if (rtc && Object.keys(rtc).length) { + urlParams['hb_bid'] = rtc.hb_bidder || 'unknown'; + urlParams['hb_cpm'] = rtc.hb_pb || 0.0; + urlParams['hb_ccy'] = 'USD'; + urlParams['rtc_id'] = rtc.hb_cache_id || ''; + urlParams['rtc_host'] = rtc.hb_cache_host || ''; + urlParams['rtc_path'] = rtc.hb_cache_path || ''; + urlParams['rtc_width'] = this.element.getAttribute('width'); + urlParams['rtc_height'] = this.element.getAttribute('height'); + + // usunąć + tgt = + 'rtc_host=' + + (rtc.hb_cache_host || '') + + ';rtc_path=' + + (rtc.hb_cache_path || '') + + ';rtc_id=' + + (rtc.hb_cache_id || '') + + ';'; + } + console.log('RTC:', rtc); + + const formatId = this.element.getAttribute('data-format'); + const tagId = 'sas_' + formatId; + const adUrl = buildUrl( + (this.element.getAttribute('data-domain') || + 'https://www.smartadserver.com') + '/ac', + { + 'siteid': this.element.getAttribute('data-site'), + 'pgid': this.element.getAttribute('data-page'), + 'fmtid': formatId, + 'tgt': tgt + this.element.getAttribute('data-target'), // usunąć tgt + 'tag': tagId, + 'out': 'iframe', // amp2 + ...urlParams, + 'gdpr_consent': consentString, + 'pgDomain': this.win.top.location.hostname, + 'tmstp': Date.now(), + }, + MAX_URL_LENGTH, + TRUNCATION_PARAM + ); + this.getAdUrlDeferred.resolve(adUrl); + }); + }); + return this.getAdUrlDeferred.promise; + } + + /** @override */ + isValidElement() { + return this.isAmpAdElement(); + } + + /** @override */ + sendXhrRequest(adUrl) { + return super.sendXhrRequest(adUrl).then((response) => { + return response.text().then((responseText) => { + if (includes(responseText, SAS_NO_AD_STR)) { + this.collapse(); + } + return new Response(response); + }); + }); + } + + /** @override */ + getCustomRealTimeConfigMacros_() { + const allowed = { + 'width': true, + 'height': true, + 'json': true, + 'data-override-width': true, + 'data-override-height': true, + 'data-multi-size': true, + 'data-slot': true, + }; + + return { + PAGEVIEWID: () => Services.documentInfoForDoc(this.element).pageViewId, + PAGEVIEWID_64: () => + Services.documentInfoForDoc(this.element).pageViewId64, + HREF: () => this.win.location.href, + CANONICAL_URL: () => + Services.documentInfoForDoc(this.element).canonicalUrl, + TGT: () => + JSON.stringify( + (tryParseJson(this.element.getAttribute('json')) || {})['targeting'] + ), + ADCID: (opt_timeout) => + getOrCreateAdCid( + this.getAmpDoc(), + 'AMP_ECID_GOOGLE', + '_ga', + parseInt(opt_timeout, 10) + ), + ATTR: (name) => { + if (!allowed[name]) { + dev().warn(TAG, `Invalid attribute ${name}`); + return ''; + } else { + return this.element.getAttribute(name); + } + }, + ELEMENT_POS: () => getPageLayoutBoxBlocking(this.element).top, + SCROLL_TOP: () => + Services.viewportForDoc(this.getAmpDoc()).getScrollTop(), + PAGE_HEIGHT: () => + Services.viewportForDoc(this.getAmpDoc()).getScrollHeight(), + BKG_STATE: () => (this.getAmpDoc().isVisible() ? 'visible' : 'hidden'), + }; + } + + /** + * Chooses RTC callout with highest bid price + * @param {Array} rtcResponseArray + * @return {Object} + */ + getBestRtcCallout_(rtcResponseArray) { + if (!rtcResponseArray) { + return {}; + } + + let highestOffer = {}; + rtcResponseArray.forEach((item) => { + if (!item || !item.response || !item.response.targeting) { + return null; + } else if ( + (highestOffer.hb_pb && + item.response.targeting.hb_pb > highestOffer.hb_pb) || + (!highestOffer.hb_pb && item.response.targeting.hb_pb > 0.0) + ) { + highestOffer = item.response.targeting; + } + }); + + return highestOffer; + } +} + +AMP.extension(TAG, '0.1', (AMP) => + AMP.registerElement(TAG, AmpAdNetworkSmartadserverImpl) +); diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js new file mode 100644 index 000000000000..4aa52259148a --- /dev/null +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js @@ -0,0 +1,453 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../../../amp-ad/0.1/amp-ad'; +import {createElementWithAttributes} from '#core/dom'; + +import {Services} from '#service'; + +import {AmpAdNetworkSmartadserverImpl} from '../amp-ad-network-smartadserver-impl'; + +const realWinConfig = { + amp: { + extensions: ['amp-ad-network-smartadserver-impl'], + }, + ampAdCss: true, + allowExternalResources: false, +}; + +describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { + const rtcConfig = { + vendors: { + prebidappnexus: { + PLACEMENT_ID: 13133382, + ACCOUNT_ID: 101010, + }, + indexexchange: {SITE_ID: 123456}, + }, + timeoutMillis: 500, + }; + + let element, impl, win, doc; + + function jsonOk() { + return Promise.resolve( + new window.Response( + JSON.stringify({ + key: 'value', + }), + { + status: 200, + headers: { + 'Content-type': 'application/json', + }, + } + ) + ); + } + + beforeEach(() => { + win = env.win; + doc = win.document; + }); + + describe('isValidElement', () => { + it('should be valid', async () => { + element = createElementWithAttributes(doc, 'amp-ad', { + width: '300', + height: '250', + type: 'smartadserver', + }); + impl = new AmpAdNetworkSmartadserverImpl(element); + + expect(impl.isValidElement()).to.be.true; + }); + }); + + describe('getCustomRealTimeConfigMacros', () => { + it('should return correct macros', () => { + const macros = { + 'data-slot': '5678', + 'height': '50', + 'width': '200', + 'data-override-width': '310', + 'data-override-height': '260', + }; + const json = { + 'targeting': {'a': '123'}, + }; + + element = createElementWithAttributes(doc, 'amp-ad', { + width: macros['width'], + height: macros['height'], + type: 'smartadserver', + 'data-slot': 5678, + 'data-override-width': 310, + 'data-override-height': 260, + layout: 'fixed', + json: JSON.stringify(json), + 'rtc-config': JSON.stringify(rtcConfig), + }); + env.win.document.body.appendChild(element); + + impl = new AmpAdNetworkSmartadserverImpl(element, env.win.doc, win); + const docInfo = Services.documentInfoForDoc(element); + const customMacros = impl.getCustomRealTimeConfigMacros_(); + + expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); + expect(customMacros.PAGEVIEWID_64()).to.equal(docInfo.pageViewId64); + expect(customMacros.HREF()).to.equal(win.location.href); + expect(customMacros.CANONICAL_URL()).to.equal(docInfo.canonicalUrl); + expect(customMacros.TGT()).to.equal(JSON.stringify(json['targeting'])); + expect(customMacros.ELEMENT_POS()).to.equal( + element.getBoundingClientRect().top + scrollY + ); + expect(customMacros.BKG_STATE()).to.equal( + impl.getAmpDoc().isVisible() ? 'visible' : 'hidden' + ); + Object.keys(macros).forEach((macro) => { + expect(customMacros.ATTR(macro)).to.equal(macros[macro]); + }); + return Promise.all([ + customMacros.ADCID().then((adcid) => { + expect(adcid).to.not.be.null; + }), + ]); + }); + + it('should skip not allowed macros', () => { + const macros = { + 'width': '300', + 'height': '250', + 'json': '', + 'not-allowed': 'blabla', + }; + element = createElementWithAttributes(doc, 'amp-ad', macros); + impl = new AmpAdNetworkSmartadserverImpl(element); + const customMacros = impl.getCustomRealTimeConfigMacros_(); + + expect(customMacros.TGT()).to.equal(undefined); + expect(customMacros.ATTR('width')).to.equal(macros['width']); + expect(customMacros.ATTR('height')).to.equal(macros['height']); + expect(customMacros.ATTR('not-allowed')).to.not.be.equal( + macros['not-allowed'] + ); + expect(customMacros.ATTR('not-allowed')).to.equal(''); + }); + }); + + describe('getAdUrl', () => { + it('should return proper url with vendor data', async () => { + const rtcResponseArray = [ + { + response: { + 'targeting': { + 'hb_bidder': 'appnexus', + 'hb_cache_host': 'prebid.ams1.adnxs-simple.com', + 'hb_cache_id': '0cb22b3e-aa2d-4936-9039-0ec93ff67de5', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_pb': '0.4', + 'hb_size': '300x250', + }, + }, + rtcTime: 210, + }, + ]; + + element = doc.createElement('amp-ad'); + element.setAttribute('type', 'smartadserver'); + element.setAttribute('width', 300); + element.setAttribute('height', 250); + element.setAttribute('data-site', 111); + element.setAttribute('data-format', 222); + element.setAttribute('rtc-config', JSON.stringify(rtcConfig)); + doc.body.appendChild(element); + + impl = new AmpAdNetworkSmartadserverImpl(element); + + const stub = env.sandbox.stub(window, 'fetch'); + stub.onCall(0).returns(jsonOk()); + + expect(stub.notCalled).to.equal(true); + + await impl.getAdUrl( + {consentString: 'consent-string', gdprApplies: true}, + Promise.resolve(rtcResponseArray) + ); + expect(stub.calledOnce).to.equal(true); + expect(stub).to.have.been.calledWithMatch( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=111&fmtid=222&tag=sas_222&out=amp&hb_bid=appnexus&hb_cpm=0\.4&hb_ccy=USD&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, + { + credentials: 'include', + } + ); + }); + + it('should return proper url while missing some vendor data', async () => { + const rtcResponseArray = [ + { + response: { + 'targeting': { + 'hb_cache_host': 'prebid.ams1.adnxs-simple.com', + 'hb_cache_id': '0cb22b3e-aa2d-4936-9039-0ec93ff67de5', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_pb': '0.4', + 'hb_size': '300x250', + }, + }, + rtcTime: 109, + }, + ]; + + element = doc.createElement('amp-ad'); + element.setAttribute('data-site', 11); + element.setAttribute('data-format', '23'); + element.setAttribute('rtc-config', JSON.stringify(rtcConfig)); + doc.body.appendChild(element); + + impl = new AmpAdNetworkSmartadserverImpl(element); + + const stub = env.sandbox.stub(window, 'fetch'); + stub.onCall(0).returns(jsonOk()); + + expect(stub.notCalled).to.equal(true); + + await impl.getAdUrl( + {consentString: 'consent-string', gdprApplies: true}, + Promise.resolve(rtcResponseArray) + ); + expect(stub.calledOnce).to.equal(true); + expect(stub).to.have.been.calledWithMatch( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=11&fmtid=23&tag=sas_23&out=amp&hb_bid=unknown&hb_cpm=0\.4&hb_ccy=USD&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, + { + credentials: 'include', + } + ); + }); + + it('should return proper url without vendor data', async () => { + element = doc.createElement('amp-ad'); + element.setAttribute('type', 'smartadserver'); + element.setAttribute('width', 100); + element.setAttribute('height', 50); + element.setAttribute('data-site', '1'); + element.setAttribute('data-format', '33'); + element.setAttribute('data-domain', 'https://ww7.smartadserver.com'); + + doc.body.appendChild(element); + impl = new AmpAdNetworkSmartadserverImpl(element); + const stub = env.sandbox.stub(window, 'fetch'); + stub.onCall(0).returns(jsonOk()); + expect(stub.notCalled).to.equal(true); + await impl.getAdUrl( + {consentString: 'consent-string', gdprApplies: true}, + Promise.resolve([]) + ); + expect(stub.calledOnce).to.equal(true); + expect(stub).to.have.been.calledWithMatch( + /^https:\/\/ww7\.smartadserver\.com\/ac\?siteid=1&fmtid=33&tag=sas_33&out=amp&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, + { + credentials: 'include', + } + ); + }); + + it('should return proper url with falsy callout response', async () => { + element = doc.createElement('amp-ad'); + element.setAttribute('data-site', 2); + element.setAttribute('data-format', 3); + doc.body.appendChild(element); + impl = new AmpAdNetworkSmartadserverImpl(element); + + const stub = env.sandbox.stub(window, 'fetch'); + stub.onCall(0).returns(jsonOk()); + expect(stub.notCalled).to.equal(true); + await impl.getAdUrl({}, null); + expect(stub.calledOnce).to.equal(true); + expect(stub).to.have.been.calledWithMatch( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=2&fmtid=3&tag=sas_3&out=amp&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, + { + credentials: 'include', + } + ); + }); + }); + + describe('getBestRtcCallout', () => { + it('should return best callout data', async () => { + const rtcResponseArray = [ + { + 'response': { + 'targeting': { + 'hb_bidder': 'appnexus', + 'hb_bidder_appnexus': 'appnexus', + 'hb_cache_host': 'prebid.ams1.adnxs-simple.com', + 'hb_cache_host_appnex': 'prebid.ams1.adnxs-simple.com', + 'hb_cache_id': '558a891a-a532-423c-a30e-11a9caeea688', + 'hb_cache_id_appnexus': '558a891a-a532-423c-a30e-11a9caeea688', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_cache_path_appnex': '/pbc/v1/cache', + 'hb_pb': '0.10', + 'hb_pb_appnexus': '0.10', + 'hb_size': '300x250', + 'hb_size_appnexus': '300x250', + }, + }, + 'rtcTime': 134, + 'callout': 'prebidappnexus', + }, + { + 'response': {}, + 'rtcTime': 122, + 'callout': 'criteo', + }, + { + 'response': { + 'targeting': { + 'hb_bidder': 'indexexchange', + 'hb_cache_host': 'amp.casalemedia.com', + 'hb_cache_id': '558a891a-a532-423c-a30e-11a9caeea688', + 'hb_cache_path': '/amprtc/v1/cache', + 'hb_pb': '0.30', + 'hb_size': '300x250', + }, + }, + 'rtcTime': 106, + 'callout': 'indexexchange', + }, + ]; + + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + + expect(impl.getBestRtcCallout_(rtcResponseArray)).to.deep.equal( + rtcResponseArray[2].response.targeting + ); + }); + + it('should return empty object when no offers', async () => { + const rtcResponseArray = [ + { + 'response': {}, + 'rtcTime': 92, + 'callout': 'prebidappnexus', + }, + { + 'response': {}, + 'rtcTime': 117, + 'callout': 'indexexchange', + }, + { + 'response': {}, + 'rtcTime': 131, + 'callout': 'criteo', + }, + ]; + + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + + expect(impl.getBestRtcCallout_(rtcResponseArray)).to.deep.equal({}); + }); + + it('should return empty object when empty callouts array', async () => { + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + + expect(impl.getBestRtcCallout_([])).to.deep.equal({}); + }); + }); + + // describe('getRtcAd', () => { + // it('should fetch creative while proper ad data', async () => { + // const fetchStub = env.sandbox.stub(window, 'fetch'); + // element = doc.createElement('amp-ad'); + // impl = new AmpAdNetworkSmartadserverImpl(element); + + // fetchStub.onCall(0).returns(jsonOk()); + // expect(fetchStub.notCalled).to.equal(true); + + // const cache = { + // id: 'my_creative-id', + // host: 'callout.host.com', + // path: '/my/cache/path', + // }; + + // await impl.getRtcAd_(cache, element); + + // expect(fetchStub).to.have.been.calledOnceWithExactly( + // new Request( + // 'https://callout.host.com/my/cache/path?showAdm=1&uuid=my_creative-id' + // ) + // ); + // }); + // }); + + // describe('renderIframe', () => { + // beforeEach(() => { + // element = doc.createElement('amp-ad'); + // doc.body.appendChild(element); + // impl = new AmpAdNetworkSmartadserverImpl(element); + // }); + + // it('should render html ad markup', async () => { + // const html = '
'; + // impl.renderIframe_(html, element); + + // expect(element.innerHTML).to.deep.equal( + // '' + // ); + + // const iframe = element.firstChild; + // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( + // '' + html + '' + // ); + // }); + + // it('should render script ad markup', async () => { + // const script = 'const myConst = true;'; + // impl.renderIframe_(script, element, false); + + // expect(element.innerHTML).to.deep.equal( + // '' + // ); + + // const iframe = element.firstChild; + // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( + // '' + // ); + // }); + + // it('should hide fallback', async () => { + // impl.fallback_ = doc.createElement('div'); + // impl.fallback_.setAttribute('fallback', ''); + // impl.element.appendChild(impl.fallback_); + + // expect(element.innerHTML).to.deep.equal('
'); + + // const html = '

OK

'; + // impl.renderIframe_(html, element); + + // expect(element.innerHTML).to.deep.equal( + // '' + // ); + + // const iframe = element.childNodes[1]; + // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( + // '' + html + '' + // ); + // }); + // }); +}); diff --git a/extensions/amp-ad-network-smartadserver-impl/OWNERS b/extensions/amp-ad-network-smartadserver-impl/OWNERS new file mode 100644 index 000000000000..eea3ddd5e644 --- /dev/null +++ b/extensions/amp-ad-network-smartadserver-impl/OWNERS @@ -0,0 +1,18 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [ + { + name: 'ampproject/wg-ads-reviewers' + }, + { + name: 'smart-adserver', + notify: true, + }, + ], + }, + ], +} diff --git a/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md b/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md new file mode 100644 index 000000000000..ca318ba9ac30 --- /dev/null +++ b/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md @@ -0,0 +1,65 @@ + + +# `amp-ad-network-smartadserver-impl` + + + + + + + + + + + + + + +
DescriptionThe Smartadserver fast fetch implementation for serving AMP ads, using ``.
AvailabilityLaunched
Required Script<script async custom-element="amp-ad" src="https://cdn.ampproject.org/v0/amp-ad-0.1.js"></script>
+ + +## Behavior + +Smartadserver supports the Real Time Config (RTC) to preload configuration settings +for ad placements. The RTC setup is optional. + + +## Supported parameters + +Smartadserver largely uses the same tags as ``. The following are +required tags for special behaviors of existing ones: + +- `data-site`: Site ID +- `data-page`: Page ID +- `data-format`: Format ID +- `data-domain`: Ad call domain + +These attributes are optional: +- `data-target`: Targeting string +- `rtc-config`: Please refer to [RTC Documentation](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/rtc-documentation.md) for details + + +### Example configuration + +```html + + diff --git a/extensions/amp-ad-network-smartadserver-impl/readme.md b/extensions/amp-ad-network-smartadserver-impl/readme.md new file mode 100644 index 000000000000..c8c871e7911b --- /dev/null +++ b/extensions/amp-ad-network-smartadserver-impl/readme.md @@ -0,0 +1,55 @@ +# SmartAdServer + +## Example + +### Basic call + +```html + + +``` + +### With targeting + +```html + + +``` + +## Configuration + +For ``, use the domain assigned to your network (e. g. www3.smartadserver.com); It can be found in Smart AdServer's config.js library (e.g., `http://www3.smartadserver.com/config.js?nwid=1234`). + +For semantics of configuration, please see [Smart AdServer help center](http://help.smartadserver.com/). + +### Supported parameters + +All of the parameters listed here should be prefixed with "data-" when used. + +| Parameter name | Description | Required | +| -------------- | ----------------------------------- | -------- | +| site | Your Smart AdServer Site ID | Yes | +| page | Your Smart AdServer Page ID | Yes | +| format | Your Smart AdServer Format ID | Yes | +| domain | Your Smart AdServer call domain | Yes | +| target | Your targeting string | No | +| tag | An ID for the tag containing the ad | No | + +Note: If any of the required parameters is missing, the ad slot won't be filled. diff --git a/extensions/amp-ad/amp-ad.md b/extensions/amp-ad/amp-ad.md index 6fa093af71b9..7f98d653cadb 100644 --- a/extensions/amp-ad/amp-ad.md +++ b/extensions/amp-ad/amp-ad.md @@ -448,7 +448,7 @@ See [amp-ad rules](validator-amp-ad.protoascii) in the AMP validator specificati - [Sklik](../../ads/vendors/sklik.md) - [SSP](../../ads/vendors/ssp.md) - [SlimCut Media](../../ads/vendors/slimcutmedia.md) -- [Smart AdServer](../../ads/vendors/smartadserver.md) +- [Smart AdServer](../amp-ad-network-smartadserver-impl/readme.md) - [smartclip](../../ads/vendors/smartclip.md) - [SmileWanted](../../ads/vendors/smilewanted.md) - [sogou Ad](../../ads/vendors/sogouad.md) From 8f2d2b600bab89e68b161978e1ae170400c71155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= Date: Tue, 9 Nov 2021 10:28:31 +0100 Subject: [PATCH 2/5] Improve unit tests --- .../0.1/amp-ad-network-smartadserver-impl.js | 34 +- .../test-amp-ad-network-smartadserver-impl.js | 374 ++++++++---------- 2 files changed, 182 insertions(+), 226 deletions(-) diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js index 1c3139335f45..b17403faae2f 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js @@ -1,4 +1,3 @@ -/* eslint-disable local/no-forbidden-terms */ /** * Copyright 2021 The AMP HTML Authors. All Rights Reserved. * @@ -73,28 +72,17 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { checkStillCurrent(); const rtc = this.getBestRtcCallout_(result); const urlParams = {}; - let tgt = ''; // usunąć + if (rtc && Object.keys(rtc).length) { - urlParams['hb_bid'] = rtc.hb_bidder || 'unknown'; - urlParams['hb_cpm'] = rtc.hb_pb || 0.0; + urlParams['hb_bid'] = rtc.hb_bidder || ''; + urlParams['hb_cpm'] = rtc.hb_pb; urlParams['hb_ccy'] = 'USD'; - urlParams['rtc_id'] = rtc.hb_cache_id || ''; - urlParams['rtc_host'] = rtc.hb_cache_host || ''; - urlParams['rtc_path'] = rtc.hb_cache_path || ''; - urlParams['rtc_width'] = this.element.getAttribute('width'); - urlParams['rtc_height'] = this.element.getAttribute('height'); - - // usunąć - tgt = - 'rtc_host=' + - (rtc.hb_cache_host || '') + - ';rtc_path=' + - (rtc.hb_cache_path || '') + - ';rtc_id=' + - (rtc.hb_cache_id || '') + - ';'; + urlParams['hb_cache_id'] = rtc.hb_cache_id || ''; + urlParams['hb_cache_host'] = rtc.hb_cache_host || ''; + urlParams['hb_cache_path'] = rtc.hb_cache_path || ''; + urlParams['hb_width'] = this.element.getAttribute('width'); + urlParams['hb_height'] = this.element.getAttribute('height'); } - console.log('RTC:', rtc); const formatId = this.element.getAttribute('data-format'); const tagId = 'sas_' + formatId; @@ -105,9 +93,9 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { 'siteid': this.element.getAttribute('data-site'), 'pgid': this.element.getAttribute('data-page'), 'fmtid': formatId, - 'tgt': tgt + this.element.getAttribute('data-target'), // usunąć tgt + 'tgt': this.element.getAttribute('data-target'), 'tag': tagId, - 'out': 'iframe', // amp2 + 'out': 'amp-hb', ...urlParams, 'gdpr_consent': consentString, 'pgDomain': this.win.top.location.hostname, @@ -132,7 +120,7 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { return super.sendXhrRequest(adUrl).then((response) => { return response.text().then((responseText) => { if (includes(responseText, SAS_NO_AD_STR)) { - this.collapse(); + this./*OK*/ collapse(); } return new Response(response); }); diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js index 4aa52259148a..a376eec91c45 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js @@ -43,22 +43,6 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { let element, impl, win, doc; - function jsonOk() { - return Promise.resolve( - new window.Response( - JSON.stringify({ - key: 'value', - }), - { - status: 200, - headers: { - 'Content-type': 'application/json', - }, - } - ) - ); - } - beforeEach(() => { win = env.win; doc = win.document; @@ -87,22 +71,31 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { 'data-override-height': '260', }; const json = { - 'targeting': {'a': '123'}, + targeting: {a: 123}, }; element = createElementWithAttributes(doc, 'amp-ad', { - width: macros['width'], - height: macros['height'], - type: 'smartadserver', + 'width': macros['width'], + 'height': macros['height'], + 'type': 'smartadserver', 'data-slot': 5678, 'data-override-width': 310, 'data-override-height': 260, - layout: 'fixed', - json: JSON.stringify(json), + 'layout': 'fixed', + 'json': JSON.stringify(json), 'rtc-config': JSON.stringify(rtcConfig), }); env.win.document.body.appendChild(element); + const scrollTopValue = 100; + const scrollHeightValue = 700; + env.sandbox.stub(Services, 'viewportForDoc').callsFake(() => { + return { + getScrollTop: () => scrollTopValue, + getScrollHeight: () => scrollHeightValue, + }; + }); + impl = new AmpAdNetworkSmartadserverImpl(element, env.win.doc, win); const docInfo = Services.documentInfoForDoc(element); const customMacros = impl.getCustomRealTimeConfigMacros_(); @@ -115,6 +108,8 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { expect(customMacros.ELEMENT_POS()).to.equal( element.getBoundingClientRect().top + scrollY ); + expect(customMacros.SCROLL_TOP()).to.equal(scrollTopValue); + expect(customMacros.PAGE_HEIGHT()).to.equal(scrollHeightValue); expect(customMacros.BKG_STATE()).to.equal( impl.getAmpDoc().isVisible() ? 'visible' : 'hidden' ); @@ -151,15 +146,29 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { describe('getAdUrl', () => { it('should return proper url with vendor data', async () => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': 300, + 'height': 250, + 'data-site': 111, + 'data-page': 121, + 'data-format': 222, + 'type': 'smartadserver', + 'rtc-config': JSON.stringify(rtcConfig), + }); + doc.body.appendChild(element); + + const viewer = Services.viewerForDoc(element); + env.sandbox.stub(viewer, 'getReferrerUrl'); + const rtcResponseArray = [ { response: { - 'targeting': { + targeting: { 'hb_bidder': 'appnexus', 'hb_cache_host': 'prebid.ams1.adnxs-simple.com', 'hb_cache_id': '0cb22b3e-aa2d-4936-9039-0ec93ff67de5', 'hb_cache_path': '/pbc/v1/cache', - 'hb_pb': '0.4', + 'hb_pb': '1.7', 'hb_size': '300x250', }, }, @@ -167,36 +176,27 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { }, ]; - element = doc.createElement('amp-ad'); - element.setAttribute('type', 'smartadserver'); - element.setAttribute('width', 300); - element.setAttribute('height', 250); - element.setAttribute('data-site', 111); - element.setAttribute('data-format', 222); - element.setAttribute('rtc-config', JSON.stringify(rtcConfig)); - doc.body.appendChild(element); - - impl = new AmpAdNetworkSmartadserverImpl(element); - - const stub = env.sandbox.stub(window, 'fetch'); - stub.onCall(0).returns(jsonOk()); - - expect(stub.notCalled).to.equal(true); - - await impl.getAdUrl( - {consentString: 'consent-string', gdprApplies: true}, - Promise.resolve(rtcResponseArray) - ); - expect(stub.calledOnce).to.equal(true); - expect(stub).to.have.been.calledWithMatch( - /^https:\/\/www\.smartadserver\.com\/ac\?siteid=111&fmtid=222&tag=sas_222&out=amp&hb_bid=appnexus&hb_cpm=0\.4&hb_ccy=USD&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, - { - credentials: 'include', - } - ); + return new AmpAdNetworkSmartadserverImpl(element) + .getAdUrl({}, Promise.resolve(rtcResponseArray)) + .then((url) => { + expect(url).to.match( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=111&pgid=121&fmtid=222&tag=sas_222&out=amp-hb&hb_bid=appnexus&hb_cpm=1.7&hb_ccy=USD&hb_cache_id=0cb22b3e-aa2d-4936-9039-0ec93ff67de5&hb_cache_host=prebid.ams1.adnxs-simple.com&hb_cache_path=%2Fpbc%2Fv1%2Fcache&hb_width=300&hb_height=250&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/ + ); + }); }); it('should return proper url while missing some vendor data', async () => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'data-site': 11, + 'data-format': 23, + 'type': 'smartadserver', + 'rtc-config': JSON.stringify(rtcConfig), + }); + doc.body.appendChild(element); + + const viewer = Services.viewerForDoc(element); + env.sandbox.stub(viewer, 'getReferrerUrl'); + const rtcResponseArray = [ { response: { @@ -212,86 +212,139 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { }, ]; - element = doc.createElement('amp-ad'); - element.setAttribute('data-site', 11); - element.setAttribute('data-format', '23'); - element.setAttribute('rtc-config', JSON.stringify(rtcConfig)); - doc.body.appendChild(element); - - impl = new AmpAdNetworkSmartadserverImpl(element); + return new AmpAdNetworkSmartadserverImpl(element) + .getAdUrl({}, Promise.resolve(rtcResponseArray)) + .then((url) => { + expect(url).to.match( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=11&fmtid=23&tag=sas_23&out=amp-hb&hb_cpm=0.4&hb_ccy=USD&hb_cache_id=0cb22b3e-aa2d-4936-9039-0ec93ff67de5&hb_cache_host=prebid.ams1.adnxs-simple.com&hb_cache_path=%2Fpbc%2Fv1%2Fcache&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/ + ); + }); + }); - const stub = env.sandbox.stub(window, 'fetch'); - stub.onCall(0).returns(jsonOk()); + it('should return proper url with default vendor data', async () => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'data-site': 11, + 'data-format': 23, + 'rtc-config': JSON.stringify(rtcConfig), + }); + doc.body.appendChild(element); - expect(stub.notCalled).to.equal(true); + const viewer = Services.viewerForDoc(element); + env.sandbox.stub(viewer, 'getReferrerUrl'); - await impl.getAdUrl( - {consentString: 'consent-string', gdprApplies: true}, - Promise.resolve(rtcResponseArray) - ); - expect(stub.calledOnce).to.equal(true); - expect(stub).to.have.been.calledWithMatch( - /^https:\/\/www\.smartadserver\.com\/ac\?siteid=11&fmtid=23&tag=sas_23&out=amp&hb_bid=unknown&hb_cpm=0\.4&hb_ccy=USD&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, + const rtcResponseArray = [ { - credentials: 'include', - } - ); + response: { + 'targeting': { + 'hb_pb': '0.8', + 'hb_size': '100x200', + }, + }, + rtcTime: 109, + }, + ]; + + return new AmpAdNetworkSmartadserverImpl(element) + .getAdUrl({}, Promise.resolve(rtcResponseArray)) + .then((url) => { + expect(url).to.match( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=11&fmtid=23&tag=sas_23&out=amp-hb&hb_cpm=0.8&hb_ccy=USD&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/ + ); + }); }); it('should return proper url without vendor data', async () => { - element = doc.createElement('amp-ad'); - element.setAttribute('type', 'smartadserver'); - element.setAttribute('width', 100); - element.setAttribute('height', 50); - element.setAttribute('data-site', '1'); - element.setAttribute('data-format', '33'); - element.setAttribute('data-domain', 'https://ww7.smartadserver.com'); - + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '100', + 'height': '50', + 'data-site': '1', + 'data-format': '33', + 'data-domain': 'https://ww7.smartadserver.com', + 'type': 'smartadserver', + }); doc.body.appendChild(element); - impl = new AmpAdNetworkSmartadserverImpl(element); - const stub = env.sandbox.stub(window, 'fetch'); - stub.onCall(0).returns(jsonOk()); - expect(stub.notCalled).to.equal(true); - await impl.getAdUrl( - {consentString: 'consent-string', gdprApplies: true}, - Promise.resolve([]) - ); - expect(stub.calledOnce).to.equal(true); - expect(stub).to.have.been.calledWithMatch( - /^https:\/\/ww7\.smartadserver\.com\/ac\?siteid=1&fmtid=33&tag=sas_33&out=amp&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, - { - credentials: 'include', - } - ); + + const viewer = Services.viewerForDoc(element); + env.sandbox.stub(viewer, 'getReferrerUrl'); + + return new AmpAdNetworkSmartadserverImpl(element) + .getAdUrl({}, Promise.resolve()) + .then((url) => { + expect(url).to.match( + /^https:\/\/ww7\.smartadserver\.com\/ac\?siteid=1&fmtid=33&tag=sas_33&out=amp-hb&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/ + ); + }); }); it('should return proper url with falsy callout response', async () => { - element = doc.createElement('amp-ad'); - element.setAttribute('data-site', 2); - element.setAttribute('data-format', 3); + element = createElementWithAttributes(doc, 'amp-ad', { + 'data-site': 2, + 'data-format': 3, + }); doc.body.appendChild(element); - impl = new AmpAdNetworkSmartadserverImpl(element); - const stub = env.sandbox.stub(window, 'fetch'); - stub.onCall(0).returns(jsonOk()); + const viewer = Services.viewerForDoc(element); + env.sandbox.stub(viewer, 'getReferrerUrl'); + + return new AmpAdNetworkSmartadserverImpl(element) + .getAdUrl({}, null) + .then((url) => { + expect(url).to.match( + /^https:\/\/www\.smartadserver\.com\/ac\?siteid=2&fmtid=3&tag=sas_3&out=amp-hb&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/ + ); + }); + }); + }); + + describe('sendXhrRequest', () => { + function mockXhrFor(response) { + return { + fetch: () => + Promise.resolve({ + text: () => Promise.resolve(response), + }), + }; + } + + it('should not collapse when ad response', async () => { + env.sandbox + .stub(Services, 'xhrFor') + .returns( + mockXhrFor('
advertisement
') + ); + + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + const stub = env.sandbox.stub(impl, 'collapse'); + + expect(stub.notCalled).to.equal(true); + await impl.sendXhrRequest(); + expect(stub.notCalled).to.equal(true); + }); + + it('should collapse when no ad response', async () => { + env.sandbox + .stub(Services, 'xhrFor') + .returns(mockXhrFor('')); + + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + const stub = env.sandbox.stub(impl, 'collapse'); + expect(stub.notCalled).to.equal(true); - await impl.getAdUrl({}, null); + await impl.sendXhrRequest(); expect(stub.calledOnce).to.equal(true); - expect(stub).to.have.been.calledWithMatch( - /^https:\/\/www\.smartadserver\.com\/ac\?siteid=2&fmtid=3&tag=sas_3&out=amp&pgDomain=[a-zA-Z0-9.-]+&tmstp=[0-9]+$/, - { - credentials: 'include', - } - ); }); }); describe('getBestRtcCallout', () => { + beforeEach(() => { + impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); + }); + it('should return best callout data', async () => { const rtcResponseArray = [ { - 'response': { - 'targeting': { + response: { + targeting: { 'hb_bidder': 'appnexus', 'hb_bidder_appnexus': 'appnexus', 'hb_cache_host': 'prebid.ams1.adnxs-simple.com', @@ -306,13 +359,13 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { 'hb_size_appnexus': '300x250', }, }, - 'rtcTime': 134, - 'callout': 'prebidappnexus', + rtcTime: 134, + callout: 'prebidappnexus', }, { - 'response': {}, - 'rtcTime': 122, - 'callout': 'criteo', + response: {}, + rtcTime: 122, + callout: 'criteo', }, { 'response': { @@ -330,8 +383,6 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { }, ]; - impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); - expect(impl.getBestRtcCallout_(rtcResponseArray)).to.deep.equal( rtcResponseArray[2].response.targeting ); @@ -356,98 +407,15 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { }, ]; - impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); - expect(impl.getBestRtcCallout_(rtcResponseArray)).to.deep.equal({}); }); it('should return empty object when empty callouts array', async () => { - impl = new AmpAdNetworkSmartadserverImpl(doc.createElement('amp-ad')); - expect(impl.getBestRtcCallout_([])).to.deep.equal({}); }); - }); - // describe('getRtcAd', () => { - // it('should fetch creative while proper ad data', async () => { - // const fetchStub = env.sandbox.stub(window, 'fetch'); - // element = doc.createElement('amp-ad'); - // impl = new AmpAdNetworkSmartadserverImpl(element); - - // fetchStub.onCall(0).returns(jsonOk()); - // expect(fetchStub.notCalled).to.equal(true); - - // const cache = { - // id: 'my_creative-id', - // host: 'callout.host.com', - // path: '/my/cache/path', - // }; - - // await impl.getRtcAd_(cache, element); - - // expect(fetchStub).to.have.been.calledOnceWithExactly( - // new Request( - // 'https://callout.host.com/my/cache/path?showAdm=1&uuid=my_creative-id' - // ) - // ); - // }); - // }); - - // describe('renderIframe', () => { - // beforeEach(() => { - // element = doc.createElement('amp-ad'); - // doc.body.appendChild(element); - // impl = new AmpAdNetworkSmartadserverImpl(element); - // }); - - // it('should render html ad markup', async () => { - // const html = '
'; - // impl.renderIframe_(html, element); - - // expect(element.innerHTML).to.deep.equal( - // '' - // ); - - // const iframe = element.firstChild; - // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( - // '' + html + '' - // ); - // }); - - // it('should render script ad markup', async () => { - // const script = 'const myConst = true;'; - // impl.renderIframe_(script, element, false); - - // expect(element.innerHTML).to.deep.equal( - // '' - // ); - - // const iframe = element.firstChild; - // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( - // '' - // ); - // }); - - // it('should hide fallback', async () => { - // impl.fallback_ = doc.createElement('div'); - // impl.fallback_.setAttribute('fallback', ''); - // impl.element.appendChild(impl.fallback_); - - // expect(element.innerHTML).to.deep.equal('
'); - - // const html = '

OK

'; - // impl.renderIframe_(html, element); - - // expect(element.innerHTML).to.deep.equal( - // '' - // ); - - // const iframe = element.childNodes[1]; - // expect(iframe.contentDocument.documentElement.innerHTML).to.deep.equal( - // '' + html + '' - // ); - // }); - // }); + it('should return empty object when falsy argument', async () => { + expect(impl.getBestRtcCallout_(null)).to.deep.equal({}); + }); + }); }); From f0f6a3933a05545846db1d38888cb95ad85d0448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= Date: Thu, 18 Nov 2021 18:40:07 +0100 Subject: [PATCH 3/5] Fixing formatting errors --- .../amp-ad-network-smartadserver-impl/OWNERS | 2 +- ...-ad-network-smartadserver-impl-internal.md | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/extensions/amp-ad-network-smartadserver-impl/OWNERS b/extensions/amp-ad-network-smartadserver-impl/OWNERS index eea3ddd5e644..d212171aeae4 100644 --- a/extensions/amp-ad-network-smartadserver-impl/OWNERS +++ b/extensions/amp-ad-network-smartadserver-impl/OWNERS @@ -6,7 +6,7 @@ { owners: [ { - name: 'ampproject/wg-ads-reviewers' + name: 'ampproject/wg-ads-reviewers', }, { name: 'smart-adserver', diff --git a/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md b/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md index ca318ba9ac30..21f44b49063e 100644 --- a/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md +++ b/extensions/amp-ad-network-smartadserver-impl/amp-ad-network-smartadserver-impl-internal.md @@ -31,27 +31,23 @@ limitations under the License. - ## Behavior -Smartadserver supports the Real Time Config (RTC) to preload configuration settings -for ad placements. The RTC setup is optional. - +Smartadserver supports the Real Time Config (RTC) to preload configuration settings for ad placements. The RTC setup is optional. ## Supported parameters -Smartadserver largely uses the same tags as ``. The following are -required tags for special behaviors of existing ones: +Smartadserver largely uses the same tags as ``. The following are required tags for special behaviors of existing ones: -- `data-site`: Site ID -- `data-page`: Page ID -- `data-format`: Format ID -- `data-domain`: Ad call domain +- `data-site`: Site ID +- `data-page`: Page ID +- `data-format`: Format ID +- `data-domain`: Ad call domain These attributes are optional: -- `data-target`: Targeting string -- `rtc-config`: Please refer to [RTC Documentation](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/rtc-documentation.md) for details +- `data-target`: Targeting string +- `rtc-config`: Please refer to [RTC Documentation](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/rtc-documentation.md) for details ### Example configuration @@ -63,3 +59,4 @@ These attributes are optional: data-target="test=amp" data-domain="https://www4.smartadserver.com"> +``` From 67ffdf2decc8b9132b4251a9a3ac9456d100b310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= Date: Thu, 25 Nov 2021 11:51:53 +0100 Subject: [PATCH 4/5] Removing Deferred --- .../0.1/amp-ad-network-smartadserver-impl.js | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js index b17403faae2f..7dd37e42ee32 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js @@ -16,7 +16,6 @@ import {buildUrl} from '#ads/google/a4a/shared/url-builder'; -import {Deferred} from '#core/data-structures/promise'; import {getPageLayoutBoxBlocking} from '#core/dom/layout/page-layout-box'; import {tryParseJson} from '#core/types/object/json'; import {includes} from '#core/types/string'; @@ -54,13 +53,12 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { */ constructor(element) { super(element); - - /** @protected {!Deferred} */ - this.getAdUrlDeferred = new Deferred(); } /** @override */ getAdUrl(opt_consentTuple, opt_rtcResponsesPromise) { + let adUrl; + Promise.any([ getConsentPolicyInfo(this.element, this.getConsentPolicy() || 'default'), new Promise((resolve) => setTimeout(() => resolve(), 10)), @@ -86,28 +84,32 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { const formatId = this.element.getAttribute('data-format'); const tagId = 'sas_' + formatId; - const adUrl = buildUrl( - (this.element.getAttribute('data-domain') || - 'https://www.smartadserver.com') + '/ac', - { - 'siteid': this.element.getAttribute('data-site'), - 'pgid': this.element.getAttribute('data-page'), - 'fmtid': formatId, - 'tgt': this.element.getAttribute('data-target'), - 'tag': tagId, - 'out': 'amp-hb', - ...urlParams, - 'gdpr_consent': consentString, - 'pgDomain': this.win.top.location.hostname, - 'tmstp': Date.now(), - }, - MAX_URL_LENGTH, - TRUNCATION_PARAM + adUrl( + buildUrl( + (this.element.getAttribute('data-domain') || + 'https://www.smartadserver.com') + '/ac', + { + 'siteid': this.element.getAttribute('data-site'), + 'pgid': this.element.getAttribute('data-page'), + 'fmtid': formatId, + 'tgt': this.element.getAttribute('data-target'), + 'tag': tagId, + 'out': 'amp-hb', + ...urlParams, + 'gdpr_consent': consentString, + 'pgDomain': this.win.top.location.hostname, + 'tmstp': Date.now(), + }, + MAX_URL_LENGTH, + TRUNCATION_PARAM + ) ); - this.getAdUrlDeferred.resolve(adUrl); }); }); - return this.getAdUrlDeferred.promise; + + return new Promise((resolve) => { + adUrl = resolve; + }); } /** @override */ From 684aff275792f1aae025ec08fb28cb2d0d40d0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= Date: Wed, 1 Dec 2021 22:16:49 +0100 Subject: [PATCH 5/5] Implementing suggested improvements --- .../0.1/amp-ad-network-smartadserver-impl.js | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js index 7dd37e42ee32..1e166ffda3e2 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js @@ -57,16 +57,14 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { /** @override */ getAdUrl(opt_consentTuple, opt_rtcResponsesPromise) { - let adUrl; - - Promise.any([ + return Promise.any([ getConsentPolicyInfo(this.element, this.getConsentPolicy() || 'default'), new Promise((resolve) => setTimeout(() => resolve(), 10)), ]).then((consentString) => { opt_rtcResponsesPromise = opt_rtcResponsesPromise || Promise.resolve(); const checkStillCurrent = this.verifyStillCurrent(); - opt_rtcResponsesPromise.then((result) => { + return opt_rtcResponsesPromise.then((result) => { checkStillCurrent(); const rtc = this.getBestRtcCallout_(result); const urlParams = {}; @@ -84,32 +82,26 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { const formatId = this.element.getAttribute('data-format'); const tagId = 'sas_' + formatId; - adUrl( - buildUrl( - (this.element.getAttribute('data-domain') || - 'https://www.smartadserver.com') + '/ac', - { - 'siteid': this.element.getAttribute('data-site'), - 'pgid': this.element.getAttribute('data-page'), - 'fmtid': formatId, - 'tgt': this.element.getAttribute('data-target'), - 'tag': tagId, - 'out': 'amp-hb', - ...urlParams, - 'gdpr_consent': consentString, - 'pgDomain': this.win.top.location.hostname, - 'tmstp': Date.now(), - }, - MAX_URL_LENGTH, - TRUNCATION_PARAM - ) + return buildUrl( + (this.element.getAttribute('data-domain') || + 'https://www.smartadserver.com') + '/ac', + { + 'siteid': this.element.getAttribute('data-site'), + 'pgid': this.element.getAttribute('data-page'), + 'fmtid': formatId, + 'tgt': this.element.getAttribute('data-target'), + 'tag': tagId, + 'out': 'amp-hb', + ...urlParams, + 'gdpr_consent': consentString, + 'pgDomain': this.win.top.location.hostname, + 'tmstp': Date.now(), + }, + MAX_URL_LENGTH, + TRUNCATION_PARAM ); }); }); - - return new Promise((resolve) => { - adUrl = resolve; - }); } /** @override */