diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index ac2e578de5c9..e3b257730615 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,5 +1,6 @@ import {find} from '../src/polyfill.js'; import { + canAccessWindowTop, cleanObj, deepAccess, deepClone, @@ -8,17 +9,18 @@ import { getUniqueIdentifierStr, getWindowSelf, getWindowTop, - inIframe, isArray, + isArrayOfNums, isFn, + inIframe, isInteger, isNumber, - isArrayOfNums, + isSafeFrameWindow, + isStr, logError, logInfo, logWarn, mergeDeep, - isStr, } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -104,13 +106,15 @@ export const GlobalExchange = (function() { getOrSetGlobalFeatures: function () { if (!features) { features = { + type: 'bidAdapter', page_dimensions: getPageDimensions().toString(), viewport_dimensions: getViewPortDimensions().toString(), user_timestamp: getTimestampUTC().toString(), dom_loading: getDomLoadingDuration().toString(), } } - return features; + + return { ...features }; }, prepareExchangeData(storageValue) { @@ -130,7 +134,7 @@ export const GlobalExchange = (function() { const data = { session: { new: newSession, - rnd: random + rnd: random, } } @@ -149,6 +153,9 @@ export const GlobalExchange = (function() { }; })(); +/** + * @deprecated will be removed in Prebid.js 9. + */ export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { @@ -179,6 +186,9 @@ export function adagioScriptFromLocalStorageCb(ls) { } } +/** + * @deprecated will be removed in Prebid.js 9. + */ export function getAdagioScript() { storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { internal.adagioScriptFromLocalStorageCb(ls); @@ -204,31 +214,14 @@ export function getAdagioScript() { }); } -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - function getCurrentWindow() { return currentWindow || getWindowSelf(); } -function isSafeFrameWindow() { - const ws = getWindowSelf(); - return !!(ws.$sf && ws.$sf.ext); -} - function initAdagio() { - if (canAccessTopWindow()) { - currentWindow = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf(); - } + currentWindow = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - const w = internal.getCurrentWindow(); + const w = currentWindow; w.ADAGIO = w.ADAGIO || {}; w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; @@ -240,13 +233,16 @@ function initAdagio() { storage.getDataFromLocalStorage('adagio', (storageData) => { try { - GlobalExchange.prepareExchangeData(storageData); + if (w.ADAGIO.hasRtd !== true) { + logInfo(`${LOG_PREFIX} RTD module not found. Loading external script from adagioBidAdapter is deprecated and will be removed in Prebid.js 9.`); + + GlobalExchange.prepareExchangeData(storageData); + getAdagioScript(); + } } catch (e) { logError(LOG_PREFIX, e); } }); - - getAdagioScript(); } function enqueue(ob) { @@ -359,6 +355,12 @@ function setPlayerName(bidRequest) { return playerName; } +function hasRtd() { + const w = internal.getCurrentWindow(); + + return !!(w.ADAGIO && w.ADAGIO.hasRtd); +}; + export const internal = { enqueue, getPageviewId, @@ -368,9 +370,10 @@ export const internal = { getRefererInfo, adagioScriptFromLocalStorageCb, getCurrentWindow, - canAccessTopWindow, + canAccessWindowTop, isRendererPreferredFromPublisher, - isNewSession + isNewSession, + hasRtd }; function _getGdprConsent(bidderRequest) { @@ -685,7 +688,7 @@ function autoFillParams(bid) { } function getPageDimensions() { - if (isSafeFrameWindow() || !canAccessTopWindow()) { + if (isSafeFrameWindow() || !canAccessWindowTop()) { return ''; } @@ -708,7 +711,7 @@ function getPageDimensions() { * @returns */ function getViewPortDimensions() { - if (!isSafeFrameWindow() && !canAccessTopWindow()) { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { return ''; } @@ -746,7 +749,7 @@ function getSlotPosition(adUnitElementId) { return ''; } - if (!isSafeFrameWindow() && !canAccessTopWindow()) { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { return ''; } @@ -769,7 +772,7 @@ function getSlotPosition(adUnitElementId) { position.x = Math.round(sfGeom.t); position.y = Math.round(sfGeom.l); - } else if (canAccessTopWindow()) { + } else if (canAccessWindowTop()) { try { // window.top based computing const wt = getWindowTop(); @@ -823,14 +826,6 @@ function getTimestampUTC() { return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; } -function getPrintNumber(adUnitCode, bidderRequest) { - if (!bidderRequest.bids || !bidderRequest.bids.length) { - return 1; - } - const adagioBid = find(bidderRequest.bids, bid => bid.adUnitCode === adUnitCode); - return adagioBid.bidderRequestsCount || 1; -} - /** * domLoading feature is computed on window.top if reachable. */ @@ -838,7 +833,7 @@ function getDomLoadingDuration() { let domLoadingDuration = -1; let performance; - performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; + performance = (canAccessWindowTop()) ? getWindowTop().performance : getWindowSelf().performance; if (performance && performance.timing && performance.timing.navigationStart > 0) { const val = performance.timing.domLoading - performance.timing.navigationStart; @@ -958,6 +953,31 @@ const OUTSTREAM_RENDERER = { } }; +/** + * + * @param {*} bidRequest + * @returns + */ +const _getFeatures = (bidRequest) => { + const f = { ...deepAccess(bidRequest, 'ortb2.ext.features', GlobalExchange.getOrSetGlobalFeatures()) } || {}; + + f.print_number = deepAccess(bidRequest, 'bidderRequestsCount', 1).toString(); + + if (f.type === 'bidAdapter') { + f.adunit_position = getSlotPosition(bidRequest.params.adUnitElementId) + } else { + f.adunit_position = deepAccess(bidRequest, 'ortb2Imp.ext.data.adunit_position'); + } + + Object.keys(f).forEach((prop) => { + if (f[prop] === '') { + delete f[prop]; + } + }); + + return f; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -986,6 +1006,7 @@ export const spec = { const device = internal.getDevice(); const site = internal.getSite(bidderRequest); const pageviewId = internal.getPageviewId(); + const hasRtd = internal.hasRtd(); const gdprConsent = _getGdprConsent(bidderRequest) || {}; const uspConsent = _getUspConsent(bidderRequest) || {}; const coppa = _getCoppa(); @@ -998,6 +1019,9 @@ export const spec = { // We don't validate the dsa object in adapter and let our server do it. const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + let rtdSamplingSession = deepAccess(bidderRequest, 'ortb2.ext.session'); + const dataExchange = (rtdSamplingSession) ? { session: rtdSamplingSession } : GlobalExchange.getExchangeData(); + const aucId = generateUUID() const adUnits = validBidRequests.map(rawBidRequest => { @@ -1006,13 +1030,6 @@ export const spec = { // Fix https://github.com/prebid/Prebid.js/issues/9781 bidRequest.auctionId = aucId - const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); - const features = { - ...globalFeatures, - print_number: getPrintNumber(bidRequest.adUnitCode, bidderRequest).toString(), - adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? - }; - // Force the Split Keyword to be a String if (bidRequest.params.splitKeyword) { if (isStr(bidRequest.params.splitKeyword) || isNumber(bidRequest.params.splitKeyword)) { @@ -1056,23 +1073,20 @@ export const spec = { } } - Object.keys(features).forEach((prop) => { - if (features[prop] === '') { - delete features[prop]; - } - }); - + const features = _getFeatures(bidRequest); bidRequest.features = features; - internal.enqueue({ - action: 'features', - ts: Date.now(), - data: { - features: bidRequest.features, - params: bidRequest.params, - adUnitCode: bidRequest.adUnitCode - } - }); + if (!hasRtd) { + internal.enqueue({ + action: 'features', + ts: Date.now(), + data: { + features, + params: { ...bidRequest.params }, + adUnitCode: bidRequest.adUnitCode + } + }); + } // Handle priceFloors module // We need to use `rawBidRequest` as param because: @@ -1127,8 +1141,10 @@ export const spec = { bidRequest.gpid = gpid; } - // store the whole bidRequest (adUnit) object in the ADAGIO namespace. - storeRequestInAdagioNS(bidRequest); + if (!hasRtd) { + // store the whole bidRequest (adUnit) object in the ADAGIO namespace. + storeRequestInAdagioNS(bidRequest); + } // Remove some params that are not needed on the server side. delete bidRequest.params.siteId; @@ -1176,12 +1192,13 @@ export const spec = { url: ENDPOINT, data: { organizationId: organizationId, + hasRtd: hasRtd ? 1 : 0, secure: secure, device: device, site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - data: GlobalExchange.getExchangeData(), + data: dataExchange, regs: { gdpr: gdprConsent, coppa: coppa, diff --git a/src/utils.js b/src/utils.js index 3225eac162ee..46dd06a6a417 100644 --- a/src/utils.js +++ b/src/utils.js @@ -41,6 +41,7 @@ export const internal = { createTrackPixelIframeHtml, getWindowSelf, getWindowTop, + canAccessWindowTop, getWindowLocation, insertUserSyncIframe, insertElement, @@ -180,6 +181,16 @@ export function getWindowLocation() { return window.location; } +export function canAccessWindowTop() { + try { + if (internal.getWindowTop().location.href) { + return true; + } + } catch (e) { + return false; + } +} + /** * Wrappers to console.(log | info | warn | error). Takes N arguments, the same as the native methods */ @@ -620,6 +631,18 @@ export function inIframe() { } } +/** + * https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf + */ +export function isSafeFrameWindow() { + if (!inIframe()) { + return false; + } + + const ws = internal.getWindowSelf(); + return !!(ws.$sf && ws.$sf.ext); +} + export function isSafariBrowser() { return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index b11c0ab4a8e6..1371c97dddfc 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -122,9 +122,6 @@ describe('Adagio bid adapter', () => { GlobalExchange.clearFeatures(); GlobalExchange.clearExchangeData(); - adagioMock = sinon.mock(adagio); - utilsMock = sinon.mock(utils); - $$PREBID_GLOBAL$$.bidderSettings = { adagio: { storageAllowed: true @@ -132,15 +129,14 @@ describe('Adagio bid adapter', () => { }; sandbox = sinon.createSandbox(); + adagioMock = sandbox.mock(adagio); + utilsMock = sandbox.mock(utils); }); afterEach(() => { window.ADAGIO = undefined; $$PREBID_GLOBAL$$.bidderSettings = {}; - adagioMock.restore(); - utilsMock.restore(); - sandbox.restore(); }); @@ -267,6 +263,7 @@ describe('Adagio bid adapter', () => { 'schain', 'prebidVersion', 'featuresVersion', + 'hasRtd', 'data', 'usIfr', 'adgjs', @@ -1625,7 +1622,7 @@ describe('Adagio bid adapter', () => { describe('Adagio features when prebid in crossdomain iframe', function() { it('should return all expected features', function() { - sandbox.stub(utils, 'getWindowTop').throws(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { @@ -1669,7 +1666,7 @@ describe('Adagio bid adapter', () => { }); it('should returns domain and page in a cross-domain w/ top domain reached context', function() { - sandbox.stub(utils, 'getWindowTop').throws(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); sandbox.stub(utils, 'getWindowSelf').returns({ document: { referrer: 'https://google.com' @@ -1704,7 +1701,7 @@ describe('Adagio bid adapter', () => { }); it('should return info in a cross-domain w/o top domain reached and w/o ancestor context', function() { - sandbox.stub(utils, 'getWindowTop').throws(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); const info = { numIframes: 2, diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 8ea68aadecc3..fb85b410bd94 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -21,6 +21,54 @@ describe('Utils', function () { type_array = 'Array', type_function = 'Function'; + describe('canAccessWindowTop', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + it('should return true if window.top is accessible', function () { + assert.equal(utils.canAccessWindowTop(), true); + }); + + it('should return false if window.top is not accessible', function () { + sandbox.stub(utils.internal, 'getWindowTop').returns(false); + assert.equal(utils.canAccessWindowTop(), false); + }); + }); + + describe('isSafeFrameWindow', function () { + // SafeFrames implementation + // https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf + const $sf = { + ext: { + geom: function() {} + } + }; + + afterEach(function() { + delete window.$sf; + }) + + it('should return true if window.$sf is accessible', function () { + window.$sf = $sf; + assert.equal(utils.isSafeFrameWindow(), true); + }); + + it('should return false if window.$sf is missimplemented', function () { + window.$sf = {}; + assert.equal(utils.isSafeFrameWindow(), false); + }); + + it('should return false if window.$sf is missing', function () { + assert.equal(utils.isSafeFrameWindow(), false); + }); + }); + describe('getBidIdParameter', function () { it('should return value of the key in input object', function () { var obj = {