From 7fb4d9a4fe3473c1a124b4d3ce34161d8768bfa5 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 20 Mar 2024 21:33:27 +0000 Subject: [PATCH 01/24] PTOW-2; updates to the pubx analytics adapter --- modules/pubxaiAnalyticsAdapter.js | 447 ++++-- .../modules/pubxaiAnalyticsAdapter_spec.js | 1385 ++++++++++------- 2 files changed, 1084 insertions(+), 748 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index e97e5505768..c069a52cb47 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,10 +1,20 @@ -import { deepAccess, parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import { + deepAccess, + parseSizesInput, + getWindowLocation, + buildUrl, + cyrb53Hash, +} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { + getGptSlotInfoForAdUnitCode, + getGptSlotForAdUnitCode, +} from '../libraries/gptUtils/gptUtils.js'; + +let initOptions; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -13,123 +23,229 @@ const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; -let initOptions; -let auctionTimestamp; -let auctionCache = []; -let events = { - bids: [], - floorDetail: {}, - pageDetail: {}, - deviceDetail: {} +/** + * auctionCache is a global cache object which stores all auction histories + * for the session. When getting a key from the auction cache, any + * information already known about the auction or associated data (floor + * data configured by prebid, browser data, user data etc) is added to + * the cache automatically. + */ +export const auctionCache = new Proxy( + {}, + { + get: (target, name) => { + if (!Object.hasOwn(target, name)) { + target[name] = { + bids: [], + auctionDetail: { + refreshRank: Object.keys(target).length, + auctionId: name, + }, + floorDetail: {}, + pageDetail: { + host: getWindowLocation().host, + path: getWindowLocation().pathname, + search: getWindowLocation().search, + }, + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), + }, + userDetail: { + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), + }, + pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + initOptions: { + ...initOptions, + auctionId: name, // back-compat + }, + sentAs: [], + }; + } + return target[name]; + }, + } +); + +/** + * Fetch extra ad server data for a specific ad slot (bid) + * @param {object} bid an output from extractBid + * @returns {object} key value pairs from the adserver + */ +const getAdServerDataForBid = (bid) => { + const gptSlot = getGptSlotForAdUnitCode(bid); + if (gptSlot) { + return Object.fromEntires( + gptSlot.getTargetingKeys().map((key) => [key, gptSlot.getTargeting(key)]) + ); + } + return {}; // TODO: support more ad servers }; -function getStorage() { +/** + * Access sessionStorage + * @returns {Storage} + */ +const getStorage = () => { try { return window.top['sessionStorage']; } catch (e) { return null; } -} +}; -var pubxaiAnalyticsAdapter = Object.assign(adapter( - { - emptyUrl, - analyticsType - }), { - track({ eventType, args }) { - if (typeof args !== 'undefined') { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { - args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { - events.auctionInit = args; - events.floorDetail = {}; - events.bids = []; - const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); - if (typeof floorData !== 'undefined') { - Object.assign(events.floorDetail, floorData); - } - auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { - mapBidResponse(args, 'response'); - } else if (eventType === CONSTANTS.EVENTS.BID_WON) { - send({ - winningBid: mapBidResponse(args, 'bidwon') - }, 'bidwon'); - } - } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - send(events, 'auctionEnd'); - } - } -}); +/** + * extracts and derives valuable data from a prebid bidder bidResponse object + * @param {object} bidResponse a prebid bidder bidResponse (see + * https://docs.prebid.org/dev-docs/publisher-api-reference/getBidResponses.html) + * @returns {object} + */ +const extractBid = (bidResponse) => { + return { + adUnitCode: bidResponse.adUnitCode, + gptSlotCode: + getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, + auctionId: bidResponse.auctionId, + bidderCode: bidResponse.bidder, + cpm: bidResponse.cpm, + creativeId: bidResponse.creativeId, + dealId: bidResponse.dealId, + currency: bidResponse.currency, + floorData: bidResponse.floorData, + mediaType: bidResponse.mediaType, + netRevenue: bidResponse.netRevenue, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp, + status: bidResponse.status, + sizes: parseSizesInput(bidResponse.size).toString(), + statusMessage: bidResponse.statusMessage, + timeToRespond: bidResponse.timeToRespond, + transactionId: bidResponse.transactionId, + bidId: bidResponse.bidId || bidResponse.requestId, + placementId: bidResponse.params + ? deepAccess(bidResponse, 'params.0.placementId') + : null, + }; +}; -function mapBidResponse(bidResponse, status) { - if (typeof bidResponse !== 'undefined') { - let bid = { - adUnitCode: bidResponse.adUnitCode, - gptSlotCode: getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, - auctionId: bidResponse.auctionId, - bidderCode: bidResponse.bidder, - cpm: bidResponse.cpm, - creativeId: bidResponse.creativeId, - currency: bidResponse.currency, - floorData: bidResponse.floorData, - mediaType: bidResponse.mediaType, - netRevenue: bidResponse.netRevenue, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp, - status: bidResponse.status, - statusMessage: bidResponse.statusMessage, - timeToRespond: bidResponse.timeToRespond, - transactionId: bidResponse.transactionId - }; - if (status !== 'bidwon') { - Object.assign(bid, { - bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, - renderStatus: status === 'timeout' ? 3 : 2, - sizes: parseSizesInput(bidResponse.size).toString(), +/** + * Track the events emitted by prebid and handle each case. See https://docs.prebid.org/dev-docs/publisher-api-reference/getEvents.html for more info + * @param {object} event the prebid event emmitted + * @param {string} event.eventType the type of the event + * @param {object} event.args the arguments of the emitted event + */ +const track = ({ eventType, args }) => { + switch (eventType) { + // handle invalid bids, and remove them from the adUnit cache + case CONSTANTS.EVENTS.BID_TIMEOUT: + args.map(extractBid).forEach((bid) => { + bid.renderStatus = 3; + auctionCache[bid.auctionId].bids.push(bid); + }); + break; + // handle valid bid responses and record them as part of an auction + case CONSTANTS.EVENTS.BID_RESPONSE: + const bid = Object.assign(extractBid(args), { renderStatus: 2 }); + auctionCache[bid.auctionId].bids.push(bid); + break; + // capture extra information from the auction, and if there were no bids + // (and so no chance of a win) send the auction + case CONSTANTS.EVENTS.AUCTION_END: + Object.assign( + auctionCache[args.auctionId].floorDetail, + deepAccess(args, 'adUnits.0.bids.0.floorData') + ); + auctionCache[args.auctionId].pageDetail.adUnits = args.adUnitCodes; + Object.assign(auctionCache[args.auctionId].auctionDetail, { + adUnitCodes: args.adUnits.map((i) => i.code), + timestamp: args.timestamp, }); - events.bids.push(bid); - } else { - Object.assign(bid, { - bidId: bidResponse.requestId, - floorProvider: events.floorDetail?.floorProvider || null, - floorFetchStatus: events.floorDetail?.fetchStatus || null, - floorLocation: events.floorDetail?.location || null, - floorModelVersion: events.floorDetail?.modelVersion || null, - floorSkipRate: events.floorDetail?.skipRate || 0, - isFloorSkipped: events.floorDetail?.skipped || false, + if ( + auctionCache[args.auctionId].bids.every((bid) => bid.renderStatus === 3) + ) { + send(args.auctionId); + } + break; + // send the prebid winning bid back to pubx + case CONSTANTS.EVENTS.BID_WON: + const winningBid = extractBid(args); + const floorDetail = auctionCache[winningBid.auctionId].floorDetail; + Object.assign(winningBid, { + floorProvider: floorDetail?.floorProvider || null, + floorFetchStatus: floorDetail?.fetchStatus || null, + floorLocation: floorDetail?.location || null, + floorModelVersion: floorDetail?.modelVersion || null, + floorSkipRate: floorDetail?.skipRate || 0, + isFloorSkipped: floorDetail?.skipped || false, isWinningBid: true, - placementId: bidResponse.params ? deepAccess(bidResponse, 'params.0.placementId') : null, - renderedSize: bidResponse.size, - renderStatus: 4 + renderedSize: args.size, + renderStatus: 4, }); - return bid; - } + winningBid.adServerData = getAdServerDataForBid(winningBid); + auctionCache[winningBid.auctionId].winningBid = winningBid; + send(winningBid.auctionId); + break; + // do nothing + default: + break; } -} +}; -export function getDeviceType() { - if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { +/** + * Get the approximate device type from the user agent + * @returns {string} + */ +export const getDeviceType = () => { + if ( + /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( + navigator.userAgent.toLowerCase() + ) + ) { return 'tablet'; } - if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + if ( + /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( + navigator.userAgent.toLowerCase() + ) + ) { return 'mobile'; } return 'desktop'; -} +}; -export function getBrowser() { - if (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)) return 'Chrome'; - else if (navigator.userAgent.match('CriOS')) return 'Chrome'; +/** + * Get the approximate browser type from the user agent (or vendor if available) + * @returns {string} + */ +export const getBrowser = () => { + if ( + /Chrome/.test(navigator.userAgent) && + /Google Inc/.test(navigator.vendor) + ) { + return 'Chrome'; + } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; else if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; - else if (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor)) return 'Safari'; - else if (/Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent)) return 'Internet Explorer'; - else return 'Others'; -} + else if ( + /Safari/.test(navigator.userAgent) && + /Apple Computer/.test(navigator.vendor) + ) { + return 'Safari'; + } else if ( + /Trident/.test(navigator.userAgent) || + /MSIE/.test(navigator.userAgent) + ) { + return 'Internet Explorer'; + } else return 'Others'; +}; -export function getOS() { +/** + * Get the approximate OS from the user agent (or app version, if available) + * @returns {string} + */ +export const getOS = () => { if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; @@ -137,72 +253,105 @@ export function getOS() { if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; return 'Others'; -} +}; -// add sampling rate -pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { - return (Math.floor((Math.random() * samplingRate + 1)) === parseInt(samplingRate)); +/** + * If true, send data back to pubxai + * @param {string} auctionId + * @param {number} samplingRate + * @returns {boolean} + */ +const shouldFireEventRequest = (auctionId, samplingRate = 1) => { + return parseInt(cyrb53Hash(auctionId)) % samplingRate === 0; }; -function send(data, status) { - if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { - let location = getWindowLocation(); - const storage = getStorage(); - data.initOptions = initOptions; - data.pageDetail = {}; - Object.assign(data.pageDetail, { - host: location.host, - path: location.pathname, - search: location.search - }); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.pageDetail.adUnits = data.auctionInit.adUnitCodes; - data.initOptions.auctionId = data.auctionInit.auctionId; - delete data.auctionInit; - - data.pmcDetail = {}; - Object.assign(data.pmcDetail, { - bidDensity: storage ? storage.getItem('pbx:dpbid') : null, - maxBid: storage ? storage.getItem('pbx:mxbid') : null, - auctionId: storage ? storage.getItem('pbx:aucid') : null, - }); +/** + * Send auction data back to pubx.ai + * @param {string} auctionId the auction to send + */ +const send = (auctionId) => { + const auctionData = Object.assign({}, auctionCache[auctionId]); + if (!shouldFireEventRequest(auctionId, initOptions.samplingRate)) { + return; + } + [ + { + path: winningBidPath, + requiredKeys: [ + 'winningBid', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'pmacDetail', + 'initOptions', + ], + eventType: 'win', + }, + { + path: auctionPath, + requiredKeys: [ + 'bids', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'pmacDetail', + 'initOptions', + ], + eventType: 'auction', + }, + ].forEach(({ path, requiredKeys, eventType }) => { + const data = Object.fromEntries( + requiredKeys.map((key) => [key, auctionData[key]]) + ); + if ( + auctionCache[auctionId].sentAs.includes(eventType) || + !requiredKeys.every((key) => !!auctionData[key]) + ) { + return; } - data.deviceDetail = {}; - Object.assign(data.deviceDetail, { - platform: navigator.platform, - deviceType: getDeviceType(), - deviceOS: getOS(), - browser: getBrowser() - }); - - let pubxaiAnalyticsRequestUrl = buildUrl({ + const pubxaiAnalyticsRequestUrl = buildUrl({ protocol: 'https', - hostname: (initOptions && initOptions.hostName) || defaultHost, - pathname: status == 'bidwon' ? winningBidPath : auctionPath, + hostname: + (auctionData.initOptions && auctionData.initOptions.hostName) || + defaultHost, + pathname: path, search: { - auctionTimestamp: auctionTimestamp, + auctionTimestamp: auctionData.auctionDetail.timestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: getGlobal().version - } + prebidVersion: getGlobal().version, + }, }); - if (status == 'bidwon') { - ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); - } else if (status == 'auctionEnd' && auctionCache.indexOf(data.initOptions.auctionId) === -1) { - ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); - auctionCache.push(data.initOptions.auctionId); - } - } -} + const payload = new Blob([JSON.stringify(data)], { + type: 'text/json', + }); + navigator.sendBeacon(pubxaiAnalyticsRequestUrl, payload); + auctionCache[auctionId].sentAs.push(eventType); + }); +}; + +var pubxaiAnalyticsAdapter = Object.assign( + adapter({ + emptyUrl, + analyticsType, + }), + { track } +); +pubxaiAnalyticsAdapter.track = track; -pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; -pubxaiAnalyticsAdapter.enableAnalytics = function (config) { +pubxaiAnalyticsAdapter.originEnableAnalytics = + pubxaiAnalyticsAdapter.enableAnalytics; +pubxaiAnalyticsAdapter.enableAnalytics = (config) => { initOptions = config.options; pubxaiAnalyticsAdapter.originEnableAnalytics(config); }; adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: 'pubxai' + code: 'pubxai', }); export default pubxaiAnalyticsAdapter; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index e0f4497a8c8..478360c741f 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,675 +1,694 @@ -import pubxaiAnalyticsAdapter, {getBrowser, getDeviceType, getOS} from 'modules/pubxaiAnalyticsAdapter.js'; -import {expect} from 'chai'; +import pubxaiAnalyticsAdapter, { + getDeviceType, + getOS, + getBrowser, + auctionCache, +} from 'modules/pubxaiAnalyticsAdapter.js'; +import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {server} from 'test/mocks/xhr.js'; -import {getGptSlotInfoForAdUnitCode} from '../../../libraries/gptUtils/gptUtils.js'; +import { getGptSlotInfoForAdUnitCode } from '../../../libraries/gptUtils/gptUtils.js'; let events = require('src/events'); let constants = require('src/constants.json'); -describe('pubxai analytics adapter', function() { - beforeEach(function() { +describe('pubxai analytics adapter', () => { + beforeEach(() => { sinon.stub(events, 'getEvents').returns([]); }); - afterEach(function() { + afterEach(() => { events.getEvents.restore(); }); - describe('track', function() { + describe('track', () => { let initOptions = { samplingRate: '1', - pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' + pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', }; let location = utils.getWindowLocation(); let storage = window.top['sessionStorage']; + const replaceProperty = (obj, params) => { + let strObj = JSON.stringify(obj); + params.forEach(({ field, updated, replaced }) => { + strObj = strObj.replace( + new RegExp('"' + field + '":' + replaced, 'g'), + '"' + field + '":' + updated + ); + }); + return JSON.parse(strObj); + }; + let prebidEvent = { - 'auctionInit': { - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag-1', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + auctionInit: { + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + timestamp: 1603865707180, + auctionStatus: 'inProgress', + adUnits: [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + }, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + floorData: { + skipped: false, + skipRate: 0, + modelVersion: 'test model 1.0', + location: 'fetch', + floorProvider: 'PubXFloorProvider', + fetchStatus: 'success', + }, + }, + ], + sizes: [[300, 250]], + transactionId: '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + ], + adUnitCodes: ['/19968336/header-bid-tag-1'], + bidderRequests: [ + { + bidderCode: 'appnexus', + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + bidderRequestId: '184cbc05bb90ba', + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + }, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + floorData: { + skipped: false, + skipRate: 0, + modelVersion: 'test model 1.0', + location: 'fetch', + floorProvider: 'PubXFloorProvider', + fetchStatus: 'success', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + sizes: [[300, 250]], + bidId: '248f9a4489835e', + bidderRequestId: '184cbc05bb90ba', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, + ], + auctionStart: 1603865707180, + timeout: 1000, + refererInfo: { + referer: 'http://local-pnh.net:8080/stream/', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://local-pnh.net:8080/stream/'], + canonicalUrl: null, }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' - } - }], - 'sizes': [ - [ - 300, - 250 - ] - ], - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' - }], - 'adUnitCodes': [ - '/19968336/header-bid-tag-1' + start: 1603865707182, + }, ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + noBids: [], + bidsReceived: [], + winningBids: [], + timeout: 1000, + config: { + samplingRate: '1', + pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', + }, + }, + bidRequested: { + bidderCode: 'appnexus', + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + bidderRequestId: '184cbc05bb90ba', + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + floorData: { + skipped: false, + skipRate: 0, + modelVersion: 'test model 1.0', + location: 'fetch', + floorProvider: 'PubXFloorProvider', + fetchStatus: 'success', }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + sizes: [[300, 250]], + bidId: '248f9a4489835e', + bidderRequestId: '184cbc05bb90ba', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': { - 'samplingRate': '1', - 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' - } - }, - 'bidRequested': { - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null + ], + auctionStart: 1603865707180, + timeout: 1000, + refererInfo: { + referer: 'http://local-pnh.net:8080/stream/', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://local-pnh.net:8080/stream/'], + canonicalUrl: null, }, - 'start': 1603865707182 + start: 1603865707182, }, - 'bidTimeout': [], - 'bidResponse': { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '32780c4bc382cb', - 'requestId': '248f9a4489835e', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'appnexus': { - 'buyerMemberId': 9325 + bidTimeout: [], + bidResponse: { + bidderCode: 'appnexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '32780c4bc382cb', + requestId: '248f9a4489835e', + mediaType: 'banner', + source: 'client', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: '/19968336/header-bid-tag-1', + appnexus: { + buyerMemberId: 9325, }, - 'meta': { - 'advertiserId': 2529885 + meta: { + advertiserId: 2529885, }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'floorData': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false, - 'floorValue': 0.4, - 'floorRule': '/19968336/header-bid-tag-1|banner', - 'floorCurrency': 'USD', - 'cpmAfterAdjustments': 0.5, - 'enforcements': { - 'enforceJS': true, - 'enforcePBS': false, - 'floorDeals': true, - 'bidAdjustment': true + ad: '', + originalCpm: 0.5, + originalCurrency: 'USD', + floorData: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, + floorValue: 0.4, + floorRule: '/19968336/header-bid-tag-1|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 0.5, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: true, + bidAdjustment: true, + }, + matchedFields: { + gptSlot: '/19968336/header-bid-tag-1', + mediaType: 'banner', }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1616654313071, - 'requestTimestamp': 1616654312804, - 'bidder': 'appnexus', - 'timeToRespond': 267, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '0.50', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '32780c4bc382cb', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + responseTimestamp: 1616654313071, + requestTimestamp: 1616654312804, + bidder: 'appnexus', + timeToRespond: 267, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '0.50', + size: '300x250', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '32780c4bc382cb', + hb_pb: '0.50', + hb_size: '300x250', + hb_source: 'client', + hb_format: 'banner', }, }, - 'auctionEnd': { - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1616654312804, - 'auctionEnd': 1616654313090, - 'auctionStatus': 'completed', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag-1', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + auctionEnd: { + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + timestamp: 1616654312804, + auctionEnd: 1616654313090, + auctionStatus: 'completed', + adUnits: [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + }, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + floorData: { + skipped: false, + skipRate: 0, + modelVersion: 'test model 1.0', + location: 'fetch', + floorProvider: 'PubXFloorProvider', + fetchStatus: 'success', + }, + }, + ], + sizes: [[300, 250]], + transactionId: '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + ], + adUnitCodes: ['/19968336/header-bid-tag-1'], + bidderRequests: [ + { + bidderCode: 'appnexus', + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + bidderRequestId: '184cbc05bb90ba', + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + }, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + floorData: { + skipped: false, + skipRate: 0, + modelVersion: 'test model 1.0', + location: 'fetch', + floorProvider: 'PubXFloorProvider', + fetchStatus: 'success', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + sizes: [[300, 250]], + bidId: '248f9a4489835e', + bidderRequestId: '184cbc05bb90ba', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, + ], + auctionStart: 1603865707180, + timeout: 1000, + refererInfo: { + referer: 'http://local-pnh.net:8080/stream/', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://local-pnh.net:8080/stream/'], + canonicalUrl: null, }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' - } - }], - 'sizes': [ - [ - 300, - 250 - ] - ], - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' - }], - 'adUnitCodes': [ - '/19968336/header-bid-tag-1' + start: 1603865707182, + }, ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + noBids: [], + bidsReceived: [ + { + bidderCode: 'appnexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '32780c4bc382cb', + requestId: '248f9a4489835e', + mediaType: 'banner', + source: 'client', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: '/19968336/header-bid-tag-1', + appnexus: { + buyerMemberId: 9325, }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' + meta: { + advertiserId: 2529885, }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + ad: '', + originalCpm: 0.5, + originalCurrency: 'USD', + floorData: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, + floorValue: 0.4, + floorRule: '/19968336/header-bid-tag-1|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 0.5, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: true, + bidAdjustment: true, + }, + matchedFields: { + gptSlot: '/19968336/header-bid-tag-1', + mediaType: 'banner', + }, }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null - }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [{ - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '32780c4bc382cb', - 'requestId': '248f9a4489835e', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'meta': { - 'advertiserId': 2529885 - }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'floorData': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false, - 'floorValue': 0.4, - 'floorRule': '/19968336/header-bid-tag-1|banner', - 'floorCurrency': 'USD', - 'cpmAfterAdjustments': 0.5, - 'enforcements': { - 'enforceJS': true, - 'enforcePBS': false, - 'floorDeals': true, - 'bidAdjustment': true + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + responseTimestamp: 1616654313071, + requestTimestamp: 1616654312804, + bidder: 'appnexus', + timeToRespond: 267, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '0.50', + size: '300x250', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '32780c4bc382cb', + hb_pb: '0.50', + hb_size: '300x250', + hb_source: 'client', + hb_format: 'banner', }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1616654313071, - 'requestTimestamp': 1616654312804, - 'bidder': 'appnexus', - 'timeToRespond': 267, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '0.50', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '32780c4bc382cb', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' + status: 'rendered', + params: [ + { + placementId: 13144370, + }, + ], }, - 'status': 'rendered', - 'params': [{ - 'placementId': 13144370 - }] - }], - 'winningBids': [], - 'timeout': 1000 + ], + winningBids: [], + timeout: 1000, }, - 'bidWon': { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '32780c4bc382cb', - 'requestId': '248f9a4489835e', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'appnexus': { - 'buyerMemberId': 9325 + bidWon: { + bidderCode: 'appnexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '32780c4bc382cb', + requestId: '248f9a4489835e', + mediaType: 'banner', + source: 'client', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: '/19968336/header-bid-tag-1', + appnexus: { + buyerMemberId: 9325, }, - 'meta': { - 'advertiserId': 2529885 + meta: { + advertiserId: 2529885, }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'floorData': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false, - 'floorValue': 0.4, - 'floorRule': '/19968336/header-bid-tag-1|banner', - 'floorCurrency': 'USD', - 'cpmAfterAdjustments': 0.5, - 'enforcements': { - 'enforceJS': true, - 'enforcePBS': false, - 'floorDeals': true, - 'bidAdjustment': true + ad: '', + originalCpm: 0.5, + originalCurrency: 'USD', + floorData: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, + floorValue: 0.4, + floorRule: '/19968336/header-bid-tag-1|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 0.5, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: true, + bidAdjustment: true, + }, + matchedFields: { + gptSlot: '/19968336/header-bid-tag-1', + mediaType: 'banner', }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1616654313071, - 'requestTimestamp': 1616654312804, - 'bidder': 'appnexus', - 'timeToRespond': 267, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '0.50', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '32780c4bc382cb', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + responseTimestamp: 1616654313071, + requestTimestamp: 1616654312804, + bidder: 'appnexus', + timeToRespond: 267, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '0.50', + size: '300x250', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '32780c4bc382cb', + hb_pb: '0.50', + hb_size: '300x250', + hb_source: 'client', + hb_format: 'banner', }, - 'status': 'rendered', - 'params': [{ - 'placementId': 13144370 - }] - }, - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search + status: 'rendered', + params: [ + { + placementId: 13144370, + }, + ], }, - 'pmcDetail': { - 'bidDensity': storage.getItem('pbx:dpbid'), - 'maxBid': storage.getItem('pbx:mxbid'), - 'auctionId': storage.getItem('pbx:aucid') - } + // pageDetail: { + // host: location.host, + // path: location.pathname, + // search: location.search, + // }, + // pmcDetail: { + // bidDensity: storage.getItem("pbx:dpbid"), + // maxBid: storage.getItem("pbx:mxbid"), + // auctionId: storage.getItem("pbx:aucid"), + // }, }; let expectedAfterBid = { - 'bids': [{ - 'bidderCode': 'appnexus', - 'bidId': '248f9a4489835e', - 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'sizes': '300x250', - 'renderStatus': 2, - 'requestTimestamp': 1616654312804, - 'creativeId': 96846035, - 'currency': 'USD', - 'cpm': 0.5, - 'netRevenue': true, - 'mediaType': 'banner', - 'statusMessage': 'Bid available', - 'floorData': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false, - 'floorValue': 0.4, - 'floorRule': '/19968336/header-bid-tag-1|banner', - 'floorCurrency': 'USD', - 'cpmAfterAdjustments': 0.5, - 'enforcements': { - 'enforceJS': true, - 'enforcePBS': false, - 'floorDeals': true, - 'bidAdjustment': true + bids: [ + { + bidderCode: 'appnexus', + bidId: '248f9a4489835e', + adUnitCode: '/19968336/header-bid-tag-1', + gptSlotCode: + getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || + null, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + sizes: '300x250', + renderStatus: 2, + requestTimestamp: 1616654312804, + creativeId: 96846035, + currency: 'USD', + cpm: 0.5, + netRevenue: true, + mediaType: 'banner', + statusMessage: 'Bid available', + floorData: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, + floorValue: 0.4, + floorRule: '/19968336/header-bid-tag-1|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 0.5, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: true, + bidAdjustment: true, + }, + matchedFields: { + gptSlot: '/19968336/header-bid-tag-1', + mediaType: 'banner', + }, }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } + placementId: null, + timeToRespond: 267, + responseTimestamp: 1616654313071, }, - 'timeToRespond': 267, - 'responseTimestamp': 1616654313071 - }], - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search, - 'adUnits': [ - '/19968336/header-bid-tag-1' - ] + ], + auctionDetail: { + adUnitCodes: ['/19968336/header-bid-tag-1'], + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + refreshRank: 0, + timestamp: 1616654312804, + }, + pageDetail: { + host: location.host, + path: location.pathname, + search: location.search, + adUnits: ['/19968336/header-bid-tag-1'], }, - 'floorDetail': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false + floorDetail: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, }, - 'deviceDetail': { - 'platform': navigator.platform, - 'deviceType': getDeviceType(), - 'deviceOS': getOS(), - 'browser': getBrowser() + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), }, - 'pmcDetail': { - 'bidDensity': storage.getItem('pbx:dpbid'), - 'maxBid': storage.getItem('pbx:mxbid'), - 'auctionId': storage.getItem('pbx:aucid') + userDetail: { + userIdTypes: [], + }, + pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, + initOptions: { + ...initOptions, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', }, - 'initOptions': initOptions }; let expectedAfterBidWon = { - 'winningBid': { - 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderCode': 'appnexus', - 'bidId': '248f9a4489835e', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'floorData': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false, - 'floorValue': 0.4, - 'floorRule': '/19968336/header-bid-tag-1|banner', - 'floorCurrency': 'USD', - 'cpmAfterAdjustments': 0.5, - 'enforcements': { - 'enforceJS': true, - 'enforcePBS': false, - 'floorDeals': true, - 'bidAdjustment': true + winningBid: { + adUnitCode: '/19968336/header-bid-tag-1', + gptSlotCode: + getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || + null, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + bidderCode: 'appnexus', + bidId: '248f9a4489835e', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + floorData: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, + floorValue: 0.4, + floorRule: '/19968336/header-bid-tag-1|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 0.5, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: true, + bidAdjustment: true, + }, + matchedFields: { + gptSlot: '/19968336/header-bid-tag-1', + mediaType: 'banner', }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } }, - 'floorProvider': 'PubXFloorProvider', - 'floorFetchStatus': 'success', - 'floorLocation': 'fetch', - 'floorModelVersion': 'test model 1.0', - 'floorSkipRate': 0, - 'isFloorSkipped': false, - 'isWinningBid': true, - 'mediaType': 'banner', - 'netRevenue': true, - 'placementId': 13144370, - 'renderedSize': '300x250', - 'renderStatus': 4, - 'responseTimestamp': 1616654313071, - 'requestTimestamp': 1616654312804, - 'status': 'rendered', - 'statusMessage': 'Bid available', - 'timeToRespond': 267 + adServerData: {}, + floorProvider: 'PubXFloorProvider', + floorFetchStatus: 'success', + floorLocation: 'fetch', + floorModelVersion: 'test model 1.0', + floorSkipRate: 0, + isFloorSkipped: false, + isWinningBid: true, + mediaType: 'banner', + netRevenue: true, + placementId: 13144370, + renderedSize: '300x250', + sizes: '300x250', + renderStatus: 4, + responseTimestamp: 1616654313071, + requestTimestamp: 1616654312804, + status: 'rendered', + statusMessage: 'Bid available', + timeToRespond: 267, + }, + auctionDetail: { + adUnitCodes: ['/19968336/header-bid-tag-1'], + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + refreshRank: 0, + timestamp: 1616654312804, }, - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search + pageDetail: { + host: location.host, + path: location.pathname, + search: location.search, + adUnits: ['/19968336/header-bid-tag-1'], }, - 'deviceDetail': { - 'platform': navigator.platform, - 'deviceType': getDeviceType(), - 'deviceOS': getOS(), - 'browser': getBrowser() + floorDetail: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, }, - 'initOptions': initOptions - } + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), + }, + userDetail: { + userIdTypes: [], + }, + pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, + initOptions: { + ...initOptions, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + }, + }; adapterManager.registerAnalyticsAdapter({ code: 'pubxai', - adapter: pubxaiAnalyticsAdapter + adapter: pubxaiAnalyticsAdapter, }); - beforeEach(function() { + beforeEach(() => { adapterManager.enableAnalytics({ provider: 'pubxai', - options: initOptions + options: initOptions, }); + sinon.stub(navigator, 'sendBeacon').returns(true); }); - afterEach(function() { + afterEach(() => { pubxaiAnalyticsAdapter.disableAnalytics(); + navigator.sendBeacon.restore(); + delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; }); - it('builds and sends auction data', function() { + it('builds and sends auction data', async () => { // Step 1: Send auction init event events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); @@ -685,20 +704,188 @@ describe('pubxai analytics adapter', function() { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - expect(server.requests.length).to.equal(1); + expect(navigator.sendBeacon.callCount).to.equal(0); - let realAfterBid = JSON.parse(server.requests[0].requestBody); + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); - expect(realAfterBid).to.deep.equal(expectedAfterBid); + expect(navigator.sendBeacon.callCount).to.equal(2); - // Step 6: Send auction bid won event + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index] + ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal( + [expectedAfterBidWon, expectedAfterBid][index] + ); + } + }); + + it('auction with no bids', async () => { + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 4: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 6: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 8: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + + // Step 9: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal({ + ...expectedAfterBid, + bids: [], + }); + }); + + it('2 concurrent auctions', async () => { + // Step 1: Send auction init event for auction 1 + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event for auction 1 + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send auction init event for auction 2 + events.emit( + constants.EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent['auctionInit'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 4: Send bid requested event for auction 2 + events.emit( + constants.EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent['bidRequested'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 5: Send bid response event for auction 1 + events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 6: Send bid time out event for auction 1 + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 7: Send bid response event for auction 2 + events.emit( + constants.EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent['bidResponse'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 8: Send auction end event for auction 1 + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 9: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); - expect(server.requests.length).to.equal(2); + // Step 11: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + + // Step 12: Send auction end event for auction 2 + events.emit( + constants.EVENTS.AUCTION_END, + replaceProperty(prebidEvent['auctionEnd'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); - let winEventData = JSON.parse(server.requests[1].requestBody); + // Step 13: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); - expect(winEventData).to.deep.equal(expectedAfterBidWon); + // Step 14: Send auction bid won event for auction 2 + events.emit( + constants.EVENTS.BID_WON, + replaceProperty(prebidEvent['bidWon'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 15: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(4); + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index % 2] + ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal( + auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + { + field: 'refreshRank', + updated: '1', + replaced: '0', + }, + ]) + ); + } }); }); }); From 741a8a096b94eada4bedf3c7b3d123ad8697fea1 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 2 Apr 2024 23:47:33 +0100 Subject: [PATCH 02/24] PTOW-2 review actions --- modules/pubxaiAnalyticsAdapter.js | 22 ++++++++-- .../modules/pubxaiAnalyticsAdapter_spec.js | 41 +++++++++++++------ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index c069a52cb47..625428fc6ff 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -56,6 +56,9 @@ export const auctionCache = new Proxy( userDetail: { userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), }, + consentDetail: { + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), + }, pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, @@ -78,7 +81,14 @@ const getAdServerDataForBid = (bid) => { const gptSlot = getGptSlotForAdUnitCode(bid); if (gptSlot) { return Object.fromEntires( - gptSlot.getTargetingKeys().map((key) => [key, gptSlot.getTargeting(key)]) + gptSlot + .getTargetingKeys() + .filter( + (key) => + key.startsWith('pubx-') || + (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) + ) + .map((key) => [key, gptSlot.getTargeting(key)]) ); } return {}; // TODO: support more ad servers @@ -155,9 +165,13 @@ const track = ({ eventType, args }) => { case CONSTANTS.EVENTS.AUCTION_END: Object.assign( auctionCache[args.auctionId].floorDetail, - deepAccess(args, 'adUnits.0.bids.0.floorData') + args.adUnits + .map((i) => i?.bids.length && i.bids[0]?.floorData) + .find((i) => i) || {} ); - auctionCache[args.auctionId].pageDetail.adUnits = args.adUnitCodes; + auctionCache[args.auctionId].deviceDetail.cdep = args.bidderRequests + .map((bidRequest) => bidRequest.ortb2?.device?.ext?.cdep) + .find((i) => i); Object.assign(auctionCache[args.auctionId].auctionDetail, { adUnitCodes: args.adUnits.map((i) => i.code), timestamp: args.timestamp, @@ -284,6 +298,7 @@ const send = (auctionId) => { 'floorDetail', 'auctionDetail', 'userDetail', + 'consentDetail', 'pmacDetail', 'initOptions', ], @@ -298,6 +313,7 @@ const send = (auctionId) => { 'floorDetail', 'auctionDetail', 'userDetail', + 'consentDetail', 'pmacDetail', 'initOptions', ], diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 478360c741f..ce5c944fe54 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -112,6 +112,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -169,6 +176,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -320,6 +334,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -485,16 +506,6 @@ describe('pubxai analytics adapter', () => { }, ], }, - // pageDetail: { - // host: location.host, - // path: location.pathname, - // search: location.search, - // }, - // pmcDetail: { - // bidDensity: storage.getItem("pbx:dpbid"), - // maxBid: storage.getItem("pbx:mxbid"), - // auctionId: storage.getItem("pbx:aucid"), - // }, }; let expectedAfterBid = { @@ -553,7 +564,6 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, - adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -568,10 +578,14 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), + cdep: true, }, userDetail: { userIdTypes: [], }, + consentDetail: { + consentTypes: [], + }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, @@ -643,7 +657,6 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, - adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -658,10 +671,14 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), + cdep: true, }, userDetail: { userIdTypes: [], }, + consentDetail: { + consentTypes: [], + }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, From 96b0f361b5bf2eb1df341b2ddddc5014923028ac Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 30 Apr 2024 07:40:30 +0100 Subject: [PATCH 03/24] PTOW-2 Review actions --- modules/pubxaiAnalyticsAdapter.js | 190 ++++++++++++------ .../modules/pubxaiAnalyticsAdapter_spec.js | 167 ++++++++++++++- 2 files changed, 281 insertions(+), 76 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 625428fc6ff..b3541a664d1 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -4,24 +4,40 @@ import { getWindowLocation, buildUrl, cyrb53Hash, -} from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; -import { getGlobal } from '../src/prebidGlobal.js'; +} from "../src/utils.js"; +import adapter from "../libraries/analyticsAdapter/AnalyticsAdapter.js"; +import adapterManager from "../src/adapterManager.js"; +import CONSTANTS from "../src/constants.json"; +import { getGlobal } from "../src/prebidGlobal.js"; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, -} from '../libraries/gptUtils/gptUtils.js'; +} from "../libraries/gptUtils/gptUtils.js"; let initOptions; -const emptyUrl = ''; -const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.2.0'; -const defaultHost = 'api.pbxai.com'; -const auctionPath = '/analytics/auction'; -const winningBidPath = '/analytics/bidwon'; +const emptyUrl = ""; +const analyticsType = "endpoint"; +const pubxaiAnalyticsVersion = "v1.2.0"; +const defaultHost = "api.pbxai.com"; +const auctionPath = "/analytics/auction"; +const winningBidPath = "/analytics/bidwon"; + +/** + * The sendCache is a global cache object which tracks the pending sends + * back to pubx.ai. The data may be removed from this cache, post send. + */ +export const sendCache = new Proxy( + {}, + { + get: (target, name) => { + if (!Object.hasOwn(target, name)) { + target[name] = []; + } + return target[name]; + }, + } +); /** * auctionCache is a global cache object which stores all auction histories @@ -59,12 +75,12 @@ export const auctionCache = new Proxy( consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + pmacDetail: JSON.parse(getStorage()?.getItem("pubx:pmac")) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, auctionId: name, // back-compat }, - sentAs: [], + sendAs: [], }; } return target[name]; @@ -72,6 +88,17 @@ export const auctionCache = new Proxy( } ); +/** + * + * @returns {boolean} whether or not the browser session supports sendBeacon + */ +const hasSendBeaconSupport = () => { + if (!navigator.sendBeacon || !document.visibilityState) { + return false; + } + return true; +}; + /** * Fetch extra ad server data for a specific ad slot (bid) * @param {object} bid an output from extractBid @@ -85,8 +112,8 @@ const getAdServerDataForBid = (bid) => { .getTargetingKeys() .filter( (key) => - key.startsWith('pubx-') || - (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) + key.startsWith("pubx-") || + (key.startsWith("hb_") && (key.match(/_/g) || []).length === 1) ) .map((key) => [key, gptSlot.getTargeting(key)]) ); @@ -100,7 +127,7 @@ const getAdServerDataForBid = (bid) => { */ const getStorage = () => { try { - return window.top['sessionStorage']; + return window.top["sessionStorage"]; } catch (e) { return null; } @@ -135,7 +162,7 @@ const extractBid = (bidResponse) => { transactionId: bidResponse.transactionId, bidId: bidResponse.bidId || bidResponse.requestId, placementId: bidResponse.params - ? deepAccess(bidResponse, 'params.0.placementId') + ? deepAccess(bidResponse, "params.0.placementId") : null, }; }; @@ -179,7 +206,7 @@ const track = ({ eventType, args }) => { if ( auctionCache[args.auctionId].bids.every((bid) => bid.renderStatus === 3) ) { - send(args.auctionId); + prepareSend(args.auctionId); } break; // send the prebid winning bid back to pubx @@ -199,7 +226,7 @@ const track = ({ eventType, args }) => { }); winningBid.adServerData = getAdServerDataForBid(winningBid); auctionCache[winningBid.auctionId].winningBid = winningBid; - send(winningBid.auctionId); + prepareSend(winningBid.auctionId); break; // do nothing default: @@ -217,16 +244,16 @@ export const getDeviceType = () => { navigator.userAgent.toLowerCase() ) ) { - return 'tablet'; + return "tablet"; } if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) ) { - return 'mobile'; + return "mobile"; } - return 'desktop'; + return "desktop"; }; /** @@ -238,21 +265,21 @@ export const getBrowser = () => { /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) ) { - return 'Chrome'; - } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; - else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; - else if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; + return "Chrome"; + } else if (navigator.userAgent.match("CriOS")) return "Chrome"; + else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; + else if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) ) { - return 'Safari'; + return "Safari"; } else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) ) { - return 'Internet Explorer'; - } else return 'Others'; + return "Internet Explorer"; + } else return "Others"; }; /** @@ -260,13 +287,13 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; - if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; - if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; - if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; - if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; - if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; - return 'Others'; + if (navigator.userAgent.indexOf("Android") != -1) return "Android"; + if (navigator.userAgent.indexOf("like Mac") != -1) return "iOS"; + if (navigator.userAgent.indexOf("Win") != -1) return "Windows"; + if (navigator.userAgent.indexOf("Mac") != -1) return "Macintosh"; + if (navigator.userAgent.indexOf("Linux") != -1) return "Linux"; + if (navigator.appVersion.indexOf("X11") != -1) return "Unix"; + return "Others"; }; /** @@ -280,10 +307,10 @@ const shouldFireEventRequest = (auctionId, samplingRate = 1) => { }; /** - * Send auction data back to pubx.ai + * prepare the payload for sending auction data back to pubx.ai * @param {string} auctionId the auction to send */ -const send = (auctionId) => { +const prepareSend = (auctionId) => { const auctionData = Object.assign({}, auctionCache[auctionId]); if (!shouldFireEventRequest(auctionId, initOptions.samplingRate)) { return; @@ -292,45 +319,45 @@ const send = (auctionId) => { { path: winningBidPath, requiredKeys: [ - 'winningBid', - 'pageDetail', - 'deviceDetail', - 'floorDetail', - 'auctionDetail', - 'userDetail', - 'consentDetail', - 'pmacDetail', - 'initOptions', + "winningBid", + "pageDetail", + "deviceDetail", + "floorDetail", + "auctionDetail", + "userDetail", + "consentDetail", + "pmacDetail", + "initOptions", ], - eventType: 'win', + eventType: "win", }, { path: auctionPath, requiredKeys: [ - 'bids', - 'pageDetail', - 'deviceDetail', - 'floorDetail', - 'auctionDetail', - 'userDetail', - 'consentDetail', - 'pmacDetail', - 'initOptions', + "bids", + "pageDetail", + "deviceDetail", + "floorDetail", + "auctionDetail", + "userDetail", + "consentDetail", + "pmacDetail", + "initOptions", ], - eventType: 'auction', + eventType: "auction", }, ].forEach(({ path, requiredKeys, eventType }) => { const data = Object.fromEntries( requiredKeys.map((key) => [key, auctionData[key]]) ); if ( - auctionCache[auctionId].sentAs.includes(eventType) || + auctionCache[auctionId].sendAs.includes(eventType) || !requiredKeys.every((key) => !!auctionData[key]) ) { return; } const pubxaiAnalyticsRequestUrl = buildUrl({ - protocol: 'https', + protocol: "https", hostname: (auctionData.initOptions && auctionData.initOptions.hostName) || defaultHost, @@ -341,14 +368,44 @@ const send = (auctionId) => { prebidVersion: getGlobal().version, }, }); - const payload = new Blob([JSON.stringify(data)], { - type: 'text/json', + sendCache[pubxaiAnalyticsRequestUrl].push(data); + auctionCache[auctionId].sendAs.push(eventType); + }); +}; + +const send = () => { + const toBlob = (d) => new Blob([JSON.stringify(d)], { type: "text/json" }); + + Object.entries(sendCache).forEach(([requestUrl, events]) => { + let payloadStart = 0; + + events.forEach((event, index, arr) => { + const payload = arr.slice(payloadStart, index + 2); + const payloadTooLarge = toBlob(payload).size > 65536; + + if (payloadTooLarge || index + 1 === arr.length) { + navigator.sendBeacon( + requestUrl, + toBlob(payloadTooLarge ? payload.slice(0, -1) : payload) + ); + payloadStart = index; + } }); - navigator.sendBeacon(pubxaiAnalyticsRequestUrl, payload); - auctionCache[auctionId].sentAs.push(eventType); + + events.splice(0); }); }; +// register event listener to send logs when user leaves page +if (hasSendBeaconSupport()) { + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === "hidden") { + send(); + } + }); +} + +// declare the analytics adapter var pubxaiAnalyticsAdapter = Object.assign( adapter({ emptyUrl, @@ -356,7 +413,6 @@ var pubxaiAnalyticsAdapter = Object.assign( }), { track } ); -pubxaiAnalyticsAdapter.track = track; pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; @@ -367,7 +423,7 @@ pubxaiAnalyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: 'pubxai', + code: "pubxai", }); export default pubxaiAnalyticsAdapter; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index ce5c944fe54..d126ed21715 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -692,17 +692,23 @@ describe('pubxai analytics adapter', () => { }); beforeEach(() => { + Object.defineProperty(document, 'visibilityState', { + value: 'hidden', + writable: true, + }); // prep for the document visibility state change adapterManager.enableAnalytics({ provider: 'pubxai', options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); + sinon.stub(); }); afterEach(() => { pubxaiAnalyticsAdapter.disableAnalytics(); navigator.sendBeacon.restore(); delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; + delete auctionCache['auction2']; }); it('builds and sends auction data', async () => { @@ -721,11 +727,17 @@ describe('pubxai analytics adapter', () => { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + expect(navigator.sendBeacon.callCount).to.equal(0); // Step 6: Send auction bid won event events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + expect(navigator.sendBeacon.callCount).to.equal(2); for (const [index, arg] of navigator.sendBeacon.args.entries()) { @@ -740,9 +752,9 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal( - [expectedAfterBidWon, expectedAfterBid][index] - ); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + ]); } }); @@ -756,12 +768,18 @@ describe('pubxai analytics adapter', () => { // Step 3: Send bid time out event events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 4: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + // Step 6: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(1); @@ -779,10 +797,12 @@ describe('pubxai analytics adapter', () => { // Step 9: check that the data sent in the request is correct expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal({ - ...expectedAfterBid, - bids: [], - }); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + { + ...expectedAfterBid, + bids: [], + }, + ]); }); it('2 concurrent auctions', async () => { @@ -837,12 +857,18 @@ describe('pubxai analytics adapter', () => { // Step 8: Send auction end event for auction 1 events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 9: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 11: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -858,6 +884,9 @@ describe('pubxai analytics adapter', () => { ]) ); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 13: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -873,6 +902,9 @@ describe('pubxai analytics adapter', () => { ]) ); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + // Step 15: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(4); for (const [index, arg] of navigator.sendBeacon.args.entries()) { @@ -888,7 +920,7 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal( + expect(JSON.parse(await expectedData.text())).to.deep.equal([ auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { field: 'auctionId', @@ -900,8 +932,125 @@ describe('pubxai analytics adapter', () => { updated: '1', replaced: '0', }, - ]) + ]), + ]); + } + }); + + it('2 concurrent auctions with batch sending', async () => { + // Step 1: Send auction init event for auction 1 + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event for auction 1 + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send auction init event for auction 2 + events.emit( + constants.EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent['auctionInit'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 4: Send bid requested event for auction 2 + events.emit( + constants.EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent['bidRequested'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 5: Send bid response event for auction 1 + events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 6: Send bid time out event for auction 1 + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 7: Send bid response event for auction 2 + events.emit( + constants.EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent['bidResponse'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 8: Send auction end event for auction 1 + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 9: Send auction bid won event for auction 1 + events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + + // Step 10: Send auction end event for auction 2 + events.emit( + constants.EVENTS.AUCTION_END, + replaceProperty(prebidEvent['auctionEnd'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 11: Send auction bid won event for auction 2 + events.emit( + constants.EVENTS.BID_WON, + replaceProperty(prebidEvent['bidWon'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 12: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 13: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index] ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + { + field: 'refreshRank', + updated: '1', + replaced: '0', + }, + ]), + ]); } }); }); From a5c3f4658d8b6e8b2517b6907cdf6219f1ba171b Mon Sep 17 00:00:00 2001 From: tej656 Date: Mon, 22 Apr 2024 11:20:31 +0530 Subject: [PATCH 04/24] PTOW-2 updating pubx.ai analytics version --- modules/pubxaiAnalyticsAdapter.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index b3541a664d1..f6093022787 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -18,7 +18,7 @@ let initOptions; const emptyUrl = ""; const analyticsType = "endpoint"; -const pubxaiAnalyticsVersion = "v1.2.0"; +const pubxaiAnalyticsVersion = "v2.0.0"; const defaultHost = "api.pbxai.com"; const auctionPath = "/analytics/auction"; const winningBidPath = "/analytics/bidwon"; @@ -261,25 +261,25 @@ export const getDeviceType = () => { * @returns {string} */ export const getBrowser = () => { - if ( + if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; + else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) { + ) return "Chrome"; - } else if (navigator.userAgent.match("CriOS")) return "Chrome"; + else if (navigator.userAgent.match("CriOS")) return "Chrome"; else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; - else if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) { + ) return "Safari"; - } else if ( + else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) { + ) return "Internet Explorer"; - } else return "Others"; + else return "Others"; }; /** From 490029dff3cb9f612ff637f521703f7f46e320da Mon Sep 17 00:00:00 2001 From: Tej <139129627+tej656@users.noreply.github.com> Date: Tue, 7 May 2024 11:35:24 +0530 Subject: [PATCH 05/24] remove empty line --- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 76ea07fbbab..b95826f8db3 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -9,7 +9,6 @@ import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../../../libraries/gptUtils/gptUtils.js'; - let events = require('src/events'); describe('pubxai analytics adapter', () => { From 96baeb0cfff3c6fefb9ddeba224570672044c9af Mon Sep 17 00:00:00 2001 From: tej656 Date: Tue, 7 May 2024 15:57:05 +0530 Subject: [PATCH 06/24] linting changes --- modules/pubxaiAnalyticsAdapter.js | 122 ++++++++++++++---------------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index cef6989006f..c0526db0fcf 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -4,25 +4,24 @@ import { getWindowLocation, buildUrl, cyrb53Hash, -} from "../src/utils.js"; -import adapter from "../libraries/analyticsAdapter/AnalyticsAdapter.js"; -import adapterManager from "../src/adapterManager.js"; -import CONSTANTS from "../src/constants.json"; -import { getGlobal } from "../src/prebidGlobal.js"; +} from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.js'; +import { getGlobal } from '../src/prebidGlobal.js'; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, -} from "../libraries/gptUtils/gptUtils.js"; - +} from '../libraries/gptUtils/gptUtils.js'; let initOptions; -const emptyUrl = ""; -const analyticsType = "endpoint"; -const pubxaiAnalyticsVersion = "v2.0.0"; -const defaultHost = "api.pbxai.com"; -const auctionPath = "/analytics/auction"; -const winningBidPath = "/analytics/bidwon"; +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const pubxaiAnalyticsVersion = 'v2.0.0'; +const defaultHost = 'api.pbxai.com'; +const auctionPath = '/analytics/auction'; +const winningBidPath = '/analytics/bidwon'; /** * The sendCache is a global cache object which tracks the pending sends @@ -76,7 +75,7 @@ export const auctionCache = new Proxy( consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(getStorage()?.getItem("pubx:pmac")) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, auctionId: name, // back-compat @@ -113,8 +112,8 @@ const getAdServerDataForBid = (bid) => { .getTargetingKeys() .filter( (key) => - key.startsWith("pubx-") || - (key.startsWith("hb_") && (key.match(/_/g) || []).length === 1) + key.startsWith('pubx-') || + (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) ) .map((key) => [key, gptSlot.getTargeting(key)]) ); @@ -128,7 +127,7 @@ const getAdServerDataForBid = (bid) => { */ const getStorage = () => { try { - return window.top["sessionStorage"]; + return window.top['sessionStorage']; } catch (e) { return null; } @@ -163,12 +162,11 @@ const extractBid = (bidResponse) => { transactionId: bidResponse.transactionId, bidId: bidResponse.bidId || bidResponse.requestId, placementId: bidResponse.params - ? deepAccess(bidResponse, "params.0.placementId") + ? deepAccess(bidResponse, 'params.0.placementId') : null, }; }; - /** * Track the events emitted by prebid and handle each case. See https://docs.prebid.org/dev-docs/publisher-api-reference/getEvents.html for more info * @param {object} event the prebid event emmitted @@ -246,16 +244,16 @@ export const getDeviceType = () => { navigator.userAgent.toLowerCase() ) ) { - return "tablet"; + return 'tablet'; } if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) ) { - return "mobile"; + return 'mobile'; } - return "desktop"; + return 'desktop'; }; /** @@ -263,25 +261,19 @@ export const getDeviceType = () => { * @returns {string} */ export const getBrowser = () => { - if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; + if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) - return "Chrome"; - else if (navigator.userAgent.match("CriOS")) return "Chrome"; - else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; + ) { return 'Chrome'; } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; + else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) - return "Safari"; - else if ( + ) { return 'Safari'; } else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) - return "Internet Explorer"; - else return "Others"; + ) { return 'Internet Explorer'; } else return 'Others'; }; /** @@ -289,13 +281,13 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf("Android") != -1) return "Android"; - if (navigator.userAgent.indexOf("like Mac") != -1) return "iOS"; - if (navigator.userAgent.indexOf("Win") != -1) return "Windows"; - if (navigator.userAgent.indexOf("Mac") != -1) return "Macintosh"; - if (navigator.userAgent.indexOf("Linux") != -1) return "Linux"; - if (navigator.appVersion.indexOf("X11") != -1) return "Unix"; - return "Others"; + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; }; /** @@ -321,32 +313,32 @@ const prepareSend = (auctionId) => { { path: winningBidPath, requiredKeys: [ - "winningBid", - "pageDetail", - "deviceDetail", - "floorDetail", - "auctionDetail", - "userDetail", - "consentDetail", - "pmacDetail", - "initOptions", + 'winningBid', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'initOptions', ], - eventType: "win", + eventType: 'win', }, { path: auctionPath, requiredKeys: [ - "bids", - "pageDetail", - "deviceDetail", - "floorDetail", - "auctionDetail", - "userDetail", - "consentDetail", - "pmacDetail", - "initOptions", + 'bids', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'initOptions', ], - eventType: "auction", + eventType: 'auction', }, ].forEach(({ path, requiredKeys, eventType }) => { const data = Object.fromEntries( @@ -359,7 +351,7 @@ const prepareSend = (auctionId) => { return; } const pubxaiAnalyticsRequestUrl = buildUrl({ - protocol: "https", + protocol: 'https', hostname: (auctionData.initOptions && auctionData.initOptions.hostName) || defaultHost, @@ -376,7 +368,7 @@ const prepareSend = (auctionId) => { }; const send = () => { - const toBlob = (d) => new Blob([JSON.stringify(d)], { type: "text/json" }); + const toBlob = (d) => new Blob([JSON.stringify(d)], { type: 'text/json' }); Object.entries(sendCache).forEach(([requestUrl, events]) => { let payloadStart = 0; @@ -400,8 +392,8 @@ const send = () => { // register event listener to send logs when user leaves page if (hasSendBeaconSupport()) { - document.addEventListener("visibilitychange", () => { - if (document.visibilityState === "hidden") { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { send(); } }); @@ -425,7 +417,7 @@ pubxaiAnalyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: "pubxai", + code: 'pubxai', }); export default pubxaiAnalyticsAdapter; From ca04584298a7e1e9f71580a8143f72a68ffa4212 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 20 Mar 2024 21:33:27 +0000 Subject: [PATCH 07/24] PTOW-2; updates to the pubx analytics adapter --- modules/pubxaiAnalyticsAdapter.js | 36 +-- .../modules/pubxaiAnalyticsAdapter_spec.js | 242 ++++++------------ 2 files changed, 88 insertions(+), 190 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index c0526db0fcf..3337808d682 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -80,7 +80,7 @@ export const auctionCache = new Proxy( ...initOptions, auctionId: name, // back-compat }, - sendAs: [], + sentAs: [], }; } return target[name]; @@ -88,17 +88,6 @@ export const auctionCache = new Proxy( } ); -/** - * - * @returns {boolean} whether or not the browser session supports sendBeacon - */ -const hasSendBeaconSupport = () => { - if (!navigator.sendBeacon || !document.visibilityState) { - return false; - } - return true; -}; - /** * Fetch extra ad server data for a specific ad slot (bid) * @param {object} bid an output from extractBid @@ -192,13 +181,9 @@ const track = ({ eventType, args }) => { case CONSTANTS.EVENTS.AUCTION_END: Object.assign( auctionCache[args.auctionId].floorDetail, - args.adUnits - .map((i) => i?.bids.length && i.bids[0]?.floorData) - .find((i) => i) || {} + deepAccess(args, 'adUnits.0.bids.0.floorData') ); - auctionCache[args.auctionId].deviceDetail.cdep = args.bidderRequests - .map((bidRequest) => bidRequest.ortb2?.device?.ext?.cdep) - .find((i) => i); + auctionCache[args.auctionId].pageDetail.adUnits = args.adUnitCodes; Object.assign(auctionCache[args.auctionId].auctionDetail, { adUnitCodes: args.adUnits.map((i) => i.code), timestamp: args.timestamp, @@ -206,7 +191,7 @@ const track = ({ eventType, args }) => { if ( auctionCache[args.auctionId].bids.every((bid) => bid.renderStatus === 3) ) { - prepareSend(args.auctionId); + send(args.auctionId); } break; // send the prebid winning bid back to pubx @@ -226,7 +211,7 @@ const track = ({ eventType, args }) => { }); winningBid.adServerData = getAdServerDataForBid(winningBid); auctionCache[winningBid.auctionId].winningBid = winningBid; - prepareSend(winningBid.auctionId); + send(winningBid.auctionId); break; // do nothing default: @@ -301,10 +286,10 @@ const shouldFireEventRequest = (auctionId, samplingRate = 1) => { }; /** - * prepare the payload for sending auction data back to pubx.ai + * Send auction data back to pubx.ai * @param {string} auctionId the auction to send */ -const prepareSend = (auctionId) => { +const send = (auctionId) => { const auctionData = Object.assign({}, auctionCache[auctionId]); if (!shouldFireEventRequest(auctionId, initOptions.samplingRate)) { return; @@ -345,7 +330,7 @@ const prepareSend = (auctionId) => { requiredKeys.map((key) => [key, auctionData[key]]) ); if ( - auctionCache[auctionId].sendAs.includes(eventType) || + auctionCache[auctionId].sentAs.includes(eventType) || !requiredKeys.every((key) => !!auctionData[key]) ) { return; @@ -385,8 +370,8 @@ const send = () => { payloadStart = index; } }); - - events.splice(0); + navigator.sendBeacon(pubxaiAnalyticsRequestUrl, payload); + auctionCache[auctionId].sentAs.push(eventType); }); }; @@ -407,6 +392,7 @@ var pubxaiAnalyticsAdapter = Object.assign( }), { track } ); +pubxaiAnalyticsAdapter.track = track; pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index b95826f8db3..1fb20f8b888 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -111,13 +111,6 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], - ortb2: { - device: { - ext: { - cdep: true, - }, - }, - }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -175,13 +168,6 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], - ortb2: { - device: { - ext: { - cdep: true, - }, - }, - }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -333,13 +319,6 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], - ortb2: { - device: { - ext: { - cdep: true, - }, - }, - }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -505,6 +484,16 @@ describe('pubxai analytics adapter', () => { }, ], }, + // pageDetail: { + // host: location.host, + // path: location.pathname, + // search: location.search, + // }, + // pmcDetail: { + // bidDensity: storage.getItem("pbx:dpbid"), + // maxBid: storage.getItem("pbx:mxbid"), + // auctionId: storage.getItem("pbx:aucid"), + // }, }; let expectedAfterBid = { @@ -563,6 +552,7 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, + adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -577,14 +567,10 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), - cdep: true, }, userDetail: { userIdTypes: [], }, - consentDetail: { - consentTypes: [], - }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, @@ -656,6 +642,7 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, + adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -670,14 +657,10 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), - cdep: true, }, userDetail: { userIdTypes: [], }, - consentDetail: { - consentTypes: [], - }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, @@ -691,73 +674,20 @@ describe('pubxai analytics adapter', () => { }); beforeEach(() => { - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - writable: true, - }); // prep for the document visibility state change adapterManager.enableAnalytics({ provider: 'pubxai', options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); - sinon.stub(); }); afterEach(() => { pubxaiAnalyticsAdapter.disableAnalytics(); navigator.sendBeacon.restore(); delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; - delete auctionCache['auction2']; }); it('builds and sends auction data', async () => { - // Step 1: Send auction init event - events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); - - // Step 2: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); - - // Step 3: Send bid response event - events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); - - // Step 4: Send bid time out event - events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); - - // Step 5: Send auction end event - events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - - // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); - - expect(navigator.sendBeacon.callCount).to.equal(0); - - // Step 6: Send auction bid won event - events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); - - // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); - - expect(navigator.sendBeacon.callCount).to.equal(2); - - for (const [index, arg] of navigator.sendBeacon.args.entries()) { - const [expectedUrl, expectedData] = arg; - const parsedUrl = new URL(expectedUrl); - expect(parsedUrl.pathname).to.equal( - ['/analytics/bidwon', '/analytics/auction'][index] - ); - expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', - prebidVersion: 'undefined', // not configured for test case - }); - expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ - [expectedAfterBidWon, expectedAfterBid][index], - ]); - } - }); - - it('auction with no bids', async () => { // Step 1: Send auction init event events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); @@ -776,16 +706,7 @@ describe('pubxai analytics adapter', () => { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); - - // Step 6: check the number of calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(1); - - // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) - const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; - const parsedUrl = new URL(expectedUrl); - expect(parsedUrl.pathname).to.equal('/analytics/auction'); + expect(navigator.sendBeacon.callCount).to.equal(0); // Step 8: check that the meta information in the call is correct expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ @@ -865,53 +786,13 @@ describe('pubxai analytics adapter', () => { // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); - // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); - - // Step 11: check the number of calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(2); - - // Step 12: Send auction end event for auction 2 - events.emit( - constants.EVENTS.AUCTION_END, - replaceProperty(prebidEvent['auctionEnd'], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - ]) - ); - - // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); - - // Step 13: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); - // Step 14: Send auction bid won event for auction 2 - events.emit( - constants.EVENTS.BID_WON, - replaceProperty(prebidEvent['bidWon'], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - ]) - ); - - // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); - - // Step 15: check the calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(4); for (const [index, arg] of navigator.sendBeacon.args.entries()) { const [expectedUrl, expectedData] = arg; const parsedUrl = new URL(expectedUrl); - const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; expect(parsedUrl.pathname).to.equal( - ['/analytics/bidwon', '/analytics/auction'][index % 2] + ['/analytics/bidwon', '/analytics/auction'][index] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', @@ -919,24 +800,52 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ - auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - { - field: 'refreshRank', - updated: '1', - replaced: '0', - }, - ]), - ]); + expect(JSON.parse(await expectedData.text())).to.deep.equal( + [expectedAfterBidWon, expectedAfterBid][index] + ); } }); - it('2 concurrent auctions with batch sending', async () => { + it('auction with no bids', async () => { + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 4: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 6: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 8: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + + // Step 9: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal({ + ...expectedAfterBid, + bids: [], + }); + }); + + it('2 concurrent auctions', async () => { // Step 1: Send auction init event for auction 1 events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); @@ -988,10 +897,16 @@ describe('pubxai analytics adapter', () => { // Step 8: Send auction end event for auction 1 events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - // Step 9: Send auction bid won event for auction 1 + // Step 9: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); - // Step 10: Send auction end event for auction 2 + // Step 11: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + + // Step 12: Send auction end event for auction 2 events.emit( constants.EVENTS.AUCTION_END, replaceProperty(prebidEvent['auctionEnd'], [ @@ -1003,7 +918,10 @@ describe('pubxai analytics adapter', () => { ]) ); - // Step 11: Send auction bid won event for auction 2 + // Step 13: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + + // Step 14: Send auction bid won event for auction 2 events.emit( constants.EVENTS.BID_WON, replaceProperty(prebidEvent['bidWon'], [ @@ -1015,19 +933,14 @@ describe('pubxai analytics adapter', () => { ]) ); - // Step 12: check the number of calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(0); - - // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); - - // Step 13: check the calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(2); + // Step 15: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(4); for (const [index, arg] of navigator.sendBeacon.args.entries()) { const [expectedUrl, expectedData] = arg; const parsedUrl = new URL(expectedUrl); + const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; expect(parsedUrl.pathname).to.equal( - ['/analytics/bidwon', '/analytics/auction'][index] + ['/analytics/bidwon', '/analytics/auction'][index % 2] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', @@ -1035,9 +948,8 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ - [expectedAfterBidWon, expectedAfterBid][index], - replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ + expect(JSON.parse(await expectedData.text())).to.deep.equal( + auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { field: 'auctionId', updated: '"auction2"', @@ -1048,8 +960,8 @@ describe('pubxai analytics adapter', () => { updated: '1', replaced: '0', }, - ]), - ]); + ]) + ); } }); }); From d0b08481a36915070b9908e86c4fd5b664d86dd5 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 2 Apr 2024 23:47:33 +0100 Subject: [PATCH 08/24] PTOW-2 review actions --- modules/pubxaiAnalyticsAdapter.js | 8 +++- .../modules/pubxaiAnalyticsAdapter_spec.js | 41 +++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 3337808d682..45722eb39c1 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -181,9 +181,13 @@ const track = ({ eventType, args }) => { case CONSTANTS.EVENTS.AUCTION_END: Object.assign( auctionCache[args.auctionId].floorDetail, - deepAccess(args, 'adUnits.0.bids.0.floorData') + args.adUnits + .map((i) => i?.bids.length && i.bids[0]?.floorData) + .find((i) => i) || {} ); - auctionCache[args.auctionId].pageDetail.adUnits = args.adUnitCodes; + auctionCache[args.auctionId].deviceDetail.cdep = args.bidderRequests + .map((bidRequest) => bidRequest.ortb2?.device?.ext?.cdep) + .find((i) => i); Object.assign(auctionCache[args.auctionId].auctionDetail, { adUnitCodes: args.adUnits.map((i) => i.code), timestamp: args.timestamp, diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1fb20f8b888..b13bc3eafff 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -111,6 +111,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -168,6 +175,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -319,6 +333,13 @@ describe('pubxai analytics adapter', () => { bidderWinsCount: 0, }, ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, auctionStart: 1603865707180, timeout: 1000, refererInfo: { @@ -484,16 +505,6 @@ describe('pubxai analytics adapter', () => { }, ], }, - // pageDetail: { - // host: location.host, - // path: location.pathname, - // search: location.search, - // }, - // pmcDetail: { - // bidDensity: storage.getItem("pbx:dpbid"), - // maxBid: storage.getItem("pbx:mxbid"), - // auctionId: storage.getItem("pbx:aucid"), - // }, }; let expectedAfterBid = { @@ -552,7 +563,6 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, - adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -567,10 +577,14 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), + cdep: true, }, userDetail: { userIdTypes: [], }, + consentDetail: { + consentTypes: [], + }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, @@ -642,7 +656,6 @@ describe('pubxai analytics adapter', () => { host: location.host, path: location.pathname, search: location.search, - adUnits: ['/19968336/header-bid-tag-1'], }, floorDetail: { fetchStatus: 'success', @@ -657,10 +670,14 @@ describe('pubxai analytics adapter', () => { deviceType: getDeviceType(), deviceOS: getOS(), browser: getBrowser(), + cdep: true, }, userDetail: { userIdTypes: [], }, + consentDetail: { + consentTypes: [], + }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { ...initOptions, From db4e603ec35ceba41a49b7d7fa8388e63635f0fa Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 30 Apr 2024 07:40:30 +0100 Subject: [PATCH 09/24] PTOW-2 Review actions --- modules/pubxaiAnalyticsAdapter.js | 135 +++++++------- .../modules/pubxaiAnalyticsAdapter_spec.js | 167 +++++++++++++++++- 2 files changed, 234 insertions(+), 68 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 45722eb39c1..2e35ba88b6b 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -12,16 +12,16 @@ import { getGlobal } from '../src/prebidGlobal.js'; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, -} from '../libraries/gptUtils/gptUtils.js'; +} from "../libraries/gptUtils/gptUtils.js"; let initOptions; -const emptyUrl = ''; -const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v2.0.0'; -const defaultHost = 'api.pbxai.com'; -const auctionPath = '/analytics/auction'; -const winningBidPath = '/analytics/bidwon'; +const emptyUrl = ""; +const analyticsType = "endpoint"; +const pubxaiAnalyticsVersion = "v2.0.0"; +const defaultHost = "api.pbxai.com"; +const auctionPath = "/analytics/auction"; +const winningBidPath = "/analytics/bidwon"; /** * The sendCache is a global cache object which tracks the pending sends @@ -75,12 +75,12 @@ export const auctionCache = new Proxy( consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + pmacDetail: JSON.parse(getStorage()?.getItem("pubx:pmac")) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, auctionId: name, // back-compat }, - sentAs: [], + sendAs: [], }; } return target[name]; @@ -88,6 +88,17 @@ export const auctionCache = new Proxy( } ); +/** + * + * @returns {boolean} whether or not the browser session supports sendBeacon + */ +const hasSendBeaconSupport = () => { + if (!navigator.sendBeacon || !document.visibilityState) { + return false; + } + return true; +}; + /** * Fetch extra ad server data for a specific ad slot (bid) * @param {object} bid an output from extractBid @@ -101,8 +112,8 @@ const getAdServerDataForBid = (bid) => { .getTargetingKeys() .filter( (key) => - key.startsWith('pubx-') || - (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) + key.startsWith("pubx-") || + (key.startsWith("hb_") && (key.match(/_/g) || []).length === 1) ) .map((key) => [key, gptSlot.getTargeting(key)]) ); @@ -151,7 +162,7 @@ const extractBid = (bidResponse) => { transactionId: bidResponse.transactionId, bidId: bidResponse.bidId || bidResponse.requestId, placementId: bidResponse.params - ? deepAccess(bidResponse, 'params.0.placementId') + ? deepAccess(bidResponse, "params.0.placementId") : null, }; }; @@ -195,7 +206,7 @@ const track = ({ eventType, args }) => { if ( auctionCache[args.auctionId].bids.every((bid) => bid.renderStatus === 3) ) { - send(args.auctionId); + prepareSend(args.auctionId); } break; // send the prebid winning bid back to pubx @@ -215,7 +226,7 @@ const track = ({ eventType, args }) => { }); winningBid.adServerData = getAdServerDataForBid(winningBid); auctionCache[winningBid.auctionId].winningBid = winningBid; - send(winningBid.auctionId); + prepareSend(winningBid.auctionId); break; // do nothing default: @@ -233,16 +244,16 @@ export const getDeviceType = () => { navigator.userAgent.toLowerCase() ) ) { - return 'tablet'; + return "tablet"; } if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) ) { - return 'mobile'; + return "mobile"; } - return 'desktop'; + return "desktop"; }; /** @@ -254,15 +265,22 @@ export const getBrowser = () => { else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) { return 'Chrome'; } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; - else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; + ) { + return "Chrome"; + } else if (navigator.userAgent.match("CriOS")) return "Chrome"; + else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; + else if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) { return 'Safari'; } else if ( + ) { + return "Safari"; + } else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) { return 'Internet Explorer'; } else return 'Others'; + ) { + return "Internet Explorer"; + } else return "Others"; }; /** @@ -270,13 +288,13 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; - if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; - if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; - if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; - if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; - if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; - return 'Others'; + if (navigator.userAgent.indexOf("Android") != -1) return "Android"; + if (navigator.userAgent.indexOf("like Mac") != -1) return "iOS"; + if (navigator.userAgent.indexOf("Win") != -1) return "Windows"; + if (navigator.userAgent.indexOf("Mac") != -1) return "Macintosh"; + if (navigator.userAgent.indexOf("Linux") != -1) return "Linux"; + if (navigator.appVersion.indexOf("X11") != -1) return "Unix"; + return "Others"; }; /** @@ -290,10 +308,10 @@ const shouldFireEventRequest = (auctionId, samplingRate = 1) => { }; /** - * Send auction data back to pubx.ai + * prepare the payload for sending auction data back to pubx.ai * @param {string} auctionId the auction to send */ -const send = (auctionId) => { +const prepareSend = (auctionId) => { const auctionData = Object.assign({}, auctionCache[auctionId]); if (!shouldFireEventRequest(auctionId, initOptions.samplingRate)) { return; @@ -302,45 +320,45 @@ const send = (auctionId) => { { path: winningBidPath, requiredKeys: [ - 'winningBid', - 'pageDetail', - 'deviceDetail', - 'floorDetail', - 'auctionDetail', - 'userDetail', - 'consentDetail', - 'pmacDetail', - 'initOptions', + "winningBid", + "pageDetail", + "deviceDetail", + "floorDetail", + "auctionDetail", + "userDetail", + "consentDetail", + "pmacDetail", + "initOptions", ], - eventType: 'win', + eventType: "win", }, { path: auctionPath, requiredKeys: [ - 'bids', - 'pageDetail', - 'deviceDetail', - 'floorDetail', - 'auctionDetail', - 'userDetail', - 'consentDetail', - 'pmacDetail', - 'initOptions', + "bids", + "pageDetail", + "deviceDetail", + "floorDetail", + "auctionDetail", + "userDetail", + "consentDetail", + "pmacDetail", + "initOptions", ], - eventType: 'auction', + eventType: "auction", }, ].forEach(({ path, requiredKeys, eventType }) => { const data = Object.fromEntries( requiredKeys.map((key) => [key, auctionData[key]]) ); if ( - auctionCache[auctionId].sentAs.includes(eventType) || + auctionCache[auctionId].sendAs.includes(eventType) || !requiredKeys.every((key) => !!auctionData[key]) ) { return; } const pubxaiAnalyticsRequestUrl = buildUrl({ - protocol: 'https', + protocol: "https", hostname: (auctionData.initOptions && auctionData.initOptions.hostName) || defaultHost, @@ -357,7 +375,7 @@ const send = (auctionId) => { }; const send = () => { - const toBlob = (d) => new Blob([JSON.stringify(d)], { type: 'text/json' }); + const toBlob = (d) => new Blob([JSON.stringify(d)], { type: "text/json" }); Object.entries(sendCache).forEach(([requestUrl, events]) => { let payloadStart = 0; @@ -374,15 +392,15 @@ const send = () => { payloadStart = index; } }); - navigator.sendBeacon(pubxaiAnalyticsRequestUrl, payload); - auctionCache[auctionId].sentAs.push(eventType); + + events.splice(0); }); }; // register event listener to send logs when user leaves page if (hasSendBeaconSupport()) { - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === "hidden") { send(); } }); @@ -396,7 +414,6 @@ var pubxaiAnalyticsAdapter = Object.assign( }), { track } ); -pubxaiAnalyticsAdapter.track = track; pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; @@ -407,7 +424,7 @@ pubxaiAnalyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: 'pubxai', + code: "pubxai", }); export default pubxaiAnalyticsAdapter; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index b13bc3eafff..0eb8f05cdc7 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -691,17 +691,23 @@ describe('pubxai analytics adapter', () => { }); beforeEach(() => { + Object.defineProperty(document, 'visibilityState', { + value: 'hidden', + writable: true, + }); // prep for the document visibility state change adapterManager.enableAnalytics({ provider: 'pubxai', options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); + sinon.stub(); }); afterEach(() => { pubxaiAnalyticsAdapter.disableAnalytics(); navigator.sendBeacon.restore(); delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; + delete auctionCache['auction2']; }); it('builds and sends auction data', async () => { @@ -723,6 +729,9 @@ describe('pubxai analytics adapter', () => { // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + expect(navigator.sendBeacon.callCount).to.equal(0); // Step 8: check that the meta information in the call is correct @@ -803,6 +812,9 @@ describe('pubxai analytics adapter', () => { // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + expect(navigator.sendBeacon.callCount).to.equal(2); for (const [index, arg] of navigator.sendBeacon.args.entries()) { @@ -817,9 +829,9 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal( - [expectedAfterBidWon, expectedAfterBid][index] - ); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + ]); } }); @@ -833,12 +845,18 @@ describe('pubxai analytics adapter', () => { // Step 3: Send bid time out event events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 4: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + // Step 6: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(1); @@ -856,10 +874,12 @@ describe('pubxai analytics adapter', () => { // Step 9: check that the data sent in the request is correct expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal({ - ...expectedAfterBid, - bids: [], - }); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + { + ...expectedAfterBid, + bids: [], + }, + ]); }); it('2 concurrent auctions', async () => { @@ -914,12 +934,18 @@ describe('pubxai analytics adapter', () => { // Step 8: Send auction end event for auction 1 events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 9: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 10: Send auction bid won event for auction 1 events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 11: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -935,6 +961,9 @@ describe('pubxai analytics adapter', () => { ]) ); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + // Step 13: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -950,6 +979,9 @@ describe('pubxai analytics adapter', () => { ]) ); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + // Step 15: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(4); for (const [index, arg] of navigator.sendBeacon.args.entries()) { @@ -965,7 +997,7 @@ describe('pubxai analytics adapter', () => { prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal( + expect(JSON.parse(await expectedData.text())).to.deep.equal([ auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { field: 'auctionId', @@ -977,8 +1009,125 @@ describe('pubxai analytics adapter', () => { updated: '1', replaced: '0', }, - ]) + ]), + ]); + } + }); + + it('2 concurrent auctions with batch sending', async () => { + // Step 1: Send auction init event for auction 1 + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event for auction 1 + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send auction init event for auction 2 + events.emit( + constants.EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent['auctionInit'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 4: Send bid requested event for auction 2 + events.emit( + constants.EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent['bidRequested'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 5: Send bid response event for auction 1 + events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 6: Send bid time out event for auction 1 + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 7: Send bid response event for auction 2 + events.emit( + constants.EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent['bidResponse'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 8: Send auction end event for auction 1 + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 9: Send auction bid won event for auction 1 + events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + + // Step 10: Send auction end event for auction 2 + events.emit( + constants.EVENTS.AUCTION_END, + replaceProperty(prebidEvent['auctionEnd'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 11: Send auction bid won event for auction 2 + events.emit( + constants.EVENTS.BID_WON, + replaceProperty(prebidEvent['bidWon'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 12: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 13: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index] ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v1.2.0', + prebidVersion: 'undefined', // not configured for test case + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await expectedData.text())).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + { + field: 'refreshRank', + updated: '1', + replaced: '0', + }, + ]), + ]); } }); }); From 41bdd891f06a350d4a8bcf7503f1f69bc1c15f03 Mon Sep 17 00:00:00 2001 From: tej656 Date: Mon, 22 Apr 2024 11:20:31 +0530 Subject: [PATCH 10/24] PTOW-2 updating pubx.ai analytics version --- modules/pubxaiAnalyticsAdapter.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 2e35ba88b6b..be7f8623a48 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -261,26 +261,25 @@ export const getDeviceType = () => { * @returns {string} */ export const getBrowser = () => { - if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; + if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) { + ) return "Chrome"; - } else if (navigator.userAgent.match("CriOS")) return "Chrome"; + else if (navigator.userAgent.match("CriOS")) return "Chrome"; else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; - else if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) { + ) return "Safari"; - } else if ( + else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) { + ) return "Internet Explorer"; - } else return "Others"; + else return "Others"; }; /** From b8e79ea9ca89288bbac26d0f566066c296975b1b Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 7 May 2024 08:50:26 +0100 Subject: [PATCH 11/24] PTOW-2 resolving conflicts --- modules/pubxaiAnalyticsAdapter.js | 30 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 258 ++++++------------ 2 files changed, 105 insertions(+), 183 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index be7f8623a48..2ecf44dca4b 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -4,11 +4,11 @@ import { getWindowLocation, buildUrl, cyrb53Hash, -} from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +} from "../src/utils.js"; +import adapter from "../libraries/analyticsAdapter/AnalyticsAdapter.js"; +import adapterManager from "../src/adapterManager.js"; +import { EVENTS } from "../src/constants.js"; +import { getGlobal } from "../src/prebidGlobal.js"; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, @@ -176,20 +176,20 @@ const extractBid = (bidResponse) => { const track = ({ eventType, args }) => { switch (eventType) { // handle invalid bids, and remove them from the adUnit cache - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: args.map(extractBid).forEach((bid) => { bid.renderStatus = 3; auctionCache[bid.auctionId].bids.push(bid); }); break; // handle valid bid responses and record them as part of an auction - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: const bid = Object.assign(extractBid(args), { renderStatus: 2 }); auctionCache[bid.auctionId].bids.push(bid); break; // capture extra information from the auction, and if there were no bids // (and so no chance of a win) send the auction - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: Object.assign( auctionCache[args.auctionId].floorDetail, args.adUnits @@ -210,7 +210,7 @@ const track = ({ eventType, args }) => { } break; // send the prebid winning bid back to pubx - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: const winningBid = extractBid(args); const floorDetail = auctionCache[winningBid.auctionId].floorDetail; Object.assign(winningBid, { @@ -265,21 +265,21 @@ export const getBrowser = () => { else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) + ) { return "Chrome"; - else if (navigator.userAgent.match("CriOS")) return "Chrome"; + } else if (navigator.userAgent.match("CriOS")) return "Chrome"; else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) + ) { return "Safari"; - else if ( + } else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) + ) { return "Internet Explorer"; - else return "Others"; + } else return "Others"; }; /** diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 0eb8f05cdc7..fde2a9db7f4 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -8,6 +8,7 @@ import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../../../libraries/gptUtils/gptUtils.js'; +import { EVENTS } from '../../../src/constants.js'; let events = require('src/events'); @@ -700,7 +701,6 @@ describe('pubxai analytics adapter', () => { options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); - sinon.stub(); }); afterEach(() => { @@ -712,105 +712,27 @@ describe('pubxai analytics adapter', () => { it('builds and sends auction data', async () => { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); - // Step 3: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + // Step 3: Send bid response event + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); - // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); - - // Step 4: check the number of calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(0); + // Step 4: Send bid time out event + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); // Simulate "navigate away" behaviour document.dispatchEvent(new Event('visibilitychange')); expect(navigator.sendBeacon.callCount).to.equal(0); - // Step 8: check that the meta information in the call is correct - expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', - prebidVersion: 'undefined', // not configured for test case - }); - - // Step 9: check that the data sent in the request is correct - expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ - { - ...expectedAfterBid, - bids: [], - }, - ]); - }); - - it('2 concurrent auctions', async () => { - // Step 1: Send auction init event for auction 1 - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); - - // Step 2: Send bid requested event for auction 1 - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); - - // Step 3: Send auction init event for auction 2 - events.emit( - constants.EVENTS.AUCTION_INIT, - replaceProperty(prebidEvent['auctionInit'], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - ]) - ); - - // Step 4: Send bid requested event for auction 2 - events.emit( - constants.EVENTS.BID_REQUESTED, - replaceProperty(prebidEvent['bidRequested'], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - ]) - ); - - // Step 5: Send bid response event for auction 1 - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); - - // Step 6: Send bid time out event for auction 1 - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); - - // Step 7: Send bid response event for auction 2 - events.emit( - constants.EVENTS.BID_RESPONSE, - replaceProperty(prebidEvent['bidResponse'], [ - { - field: 'auctionId', - updated: '"auction2"', - replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', - }, - ]) - ); - - // Step 8: Send auction end event for auction 1 - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - - // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); - - // Step 9: check the number of calls made to pubx.ai - expect(navigator.sendBeacon.callCount).to.equal(0); - - // Step 10: Send auction bid won event for auction 1 - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + // Step 6: Send auction bid won event + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); // Simulate end of session document.dispatchEvent(new Event('visibilitychange')); @@ -825,7 +747,7 @@ describe('pubxai analytics adapter', () => { ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', + pubxaiAnalyticsVersion: 'v2.0.0', prebidVersion: 'undefined', // not configured for test case }); expect(expectedData.type).to.equal('text/json'); @@ -835,27 +757,27 @@ describe('pubxai analytics adapter', () => { } }); - it('auction with no bids', async () => { + it("auction with no bids", async () => { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); // Step 3: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 4: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 6: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(1); @@ -863,17 +785,17 @@ describe('pubxai analytics adapter', () => { // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; const parsedUrl = new URL(expectedUrl); - expect(parsedUrl.pathname).to.equal('/analytics/auction'); + expect(parsedUrl.pathname).to.equal("/analytics/auction"); // Step 8: check that the meta information in the call is correct expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', - prebidVersion: 'undefined', // not configured for test case + auctionTimestamp: "1616654312804", + pubxaiAnalyticsVersion: "v2.0.0", + prebidVersion: "undefined", // not configured for test case }); // Step 9: check that the data sent in the request is correct - expect(expectedData.type).to.equal('text/json'); + expect(expectedData.type).to.equal("text/json"); expect(JSON.parse(await expectedData.text())).to.deep.equal([ { ...expectedAfterBid, @@ -882,19 +804,19 @@ describe('pubxai analytics adapter', () => { ]); }); - it('2 concurrent auctions', async () => { + it("2 concurrent auctions", async () => { // Step 1: Send auction init event for auction 1 - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); // Step 2: Send bid requested event for auction 1 - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); // Step 3: Send auction init event for auction 2 events.emit( - constants.EVENTS.AUCTION_INIT, - replaceProperty(prebidEvent['auctionInit'], [ + EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent["auctionInit"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -903,10 +825,10 @@ describe('pubxai analytics adapter', () => { // Step 4: Send bid requested event for auction 2 events.emit( - constants.EVENTS.BID_REQUESTED, - replaceProperty(prebidEvent['bidRequested'], [ + EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent["bidRequested"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -914,17 +836,17 @@ describe('pubxai analytics adapter', () => { ); // Step 5: Send bid response event for auction 1 - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + events.emit(EVENTS.BID_RESPONSE, prebidEvent["bidResponse"]); // Step 6: Send bid time out event for auction 1 - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); // Step 7: Send bid response event for auction 2 events.emit( - constants.EVENTS.BID_RESPONSE, - replaceProperty(prebidEvent['bidResponse'], [ + EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent["bidResponse"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -932,29 +854,29 @@ describe('pubxai analytics adapter', () => { ); // Step 8: Send auction end event for auction 1 - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 9: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 10: Send auction bid won event for auction 1 - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + events.emit(EVENTS.BID_WON, prebidEvent["bidWon"]); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 11: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); // Step 12: Send auction end event for auction 2 events.emit( - constants.EVENTS.AUCTION_END, - replaceProperty(prebidEvent['auctionEnd'], [ + EVENTS.AUCTION_END, + replaceProperty(prebidEvent["auctionEnd"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -962,17 +884,17 @@ describe('pubxai analytics adapter', () => { ); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 13: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); // Step 14: Send auction bid won event for auction 2 events.emit( - constants.EVENTS.BID_WON, - replaceProperty(prebidEvent['bidWon'], [ + EVENTS.BID_WON, + replaceProperty(prebidEvent["bidWon"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -980,7 +902,7 @@ describe('pubxai analytics adapter', () => { ); // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 15: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(4); @@ -989,44 +911,44 @@ describe('pubxai analytics adapter', () => { const parsedUrl = new URL(expectedUrl); const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; expect(parsedUrl.pathname).to.equal( - ['/analytics/bidwon', '/analytics/auction'][index % 2] + ["/analytics/bidwon", "/analytics/auction"][index % 2] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', - prebidVersion: 'undefined', // not configured for test case + auctionTimestamp: "1616654312804", + pubxaiAnalyticsVersion: "v2.0.0", + prebidVersion: "undefined", // not configured for test case }); - expect(expectedData.type).to.equal('text/json'); + expect(expectedData.type).to.equal("text/json"); expect(JSON.parse(await expectedData.text())).to.deep.equal([ auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, { - field: 'refreshRank', - updated: '1', - replaced: '0', + field: "refreshRank", + updated: "1", + replaced: "0", }, ]), ]); } }); - it('2 concurrent auctions with batch sending', async () => { + it("2 concurrent auctions with batch sending", async () => { // Step 1: Send auction init event for auction 1 - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); // Step 2: Send bid requested event for auction 1 - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); // Step 3: Send auction init event for auction 2 events.emit( - constants.EVENTS.AUCTION_INIT, - replaceProperty(prebidEvent['auctionInit'], [ + EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent["auctionInit"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1035,10 +957,10 @@ describe('pubxai analytics adapter', () => { // Step 4: Send bid requested event for auction 2 events.emit( - constants.EVENTS.BID_REQUESTED, - replaceProperty(prebidEvent['bidRequested'], [ + EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent["bidRequested"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1046,17 +968,17 @@ describe('pubxai analytics adapter', () => { ); // Step 5: Send bid response event for auction 1 - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + events.emit(EVENTS.BID_RESPONSE, prebidEvent["bidResponse"]); // Step 6: Send bid time out event for auction 1 - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); // Step 7: Send bid response event for auction 2 events.emit( - constants.EVENTS.BID_RESPONSE, - replaceProperty(prebidEvent['bidResponse'], [ + EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent["bidResponse"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1064,17 +986,17 @@ describe('pubxai analytics adapter', () => { ); // Step 8: Send auction end event for auction 1 - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); // Step 9: Send auction bid won event for auction 1 - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + events.emit(EVENTS.BID_WON, prebidEvent["bidWon"]); // Step 10: Send auction end event for auction 2 events.emit( - constants.EVENTS.AUCTION_END, - replaceProperty(prebidEvent['auctionEnd'], [ + EVENTS.AUCTION_END, + replaceProperty(prebidEvent["auctionEnd"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1083,10 +1005,10 @@ describe('pubxai analytics adapter', () => { // Step 11: Send auction bid won event for auction 2 events.emit( - constants.EVENTS.BID_WON, - replaceProperty(prebidEvent['bidWon'], [ + EVENTS.BID_WON, + replaceProperty(prebidEvent["bidWon"], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1097,7 +1019,7 @@ describe('pubxai analytics adapter', () => { expect(navigator.sendBeacon.callCount).to.equal(0); // Simulate end of session - document.dispatchEvent(new Event('visibilitychange')); + document.dispatchEvent(new Event("visibilitychange")); // Step 13: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -1105,26 +1027,26 @@ describe('pubxai analytics adapter', () => { const [expectedUrl, expectedData] = arg; const parsedUrl = new URL(expectedUrl); expect(parsedUrl.pathname).to.equal( - ['/analytics/bidwon', '/analytics/auction'][index] + ["/analytics/bidwon", "/analytics/auction"][index] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v1.2.0', - prebidVersion: 'undefined', // not configured for test case + auctionTimestamp: "1616654312804", + pubxaiAnalyticsVersion: "v2.0.0", + prebidVersion: "undefined", // not configured for test case }); - expect(expectedData.type).to.equal('text/json'); + expect(expectedData.type).to.equal("text/json"); expect(JSON.parse(await expectedData.text())).to.deep.equal([ [expectedAfterBidWon, expectedAfterBid][index], replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ { - field: 'auctionId', + field: "auctionId", updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, { - field: 'refreshRank', - updated: '1', - replaced: '0', + field: "refreshRank", + updated: "1", + replaced: "0", }, ]), ]); From 924a6c60d820ce43a2abe9a0e043cf6ec0995875 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Sat, 11 May 2024 15:48:44 +0100 Subject: [PATCH 12/24] PTOW-2-fix-linting-errors --- modules/pubxaiAnalyticsAdapter.js | 116 +++++++-------- .../modules/pubxaiAnalyticsAdapter_spec.js | 138 +++++++++--------- 2 files changed, 127 insertions(+), 127 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 2ecf44dca4b..71c9cba0a8c 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -4,24 +4,24 @@ import { getWindowLocation, buildUrl, cyrb53Hash, -} from "../src/utils.js"; -import adapter from "../libraries/analyticsAdapter/AnalyticsAdapter.js"; -import adapterManager from "../src/adapterManager.js"; -import { EVENTS } from "../src/constants.js"; -import { getGlobal } from "../src/prebidGlobal.js"; +} from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { getGlobal } from '../src/prebidGlobal.js'; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, -} from "../libraries/gptUtils/gptUtils.js"; +} from '../libraries/gptUtils/gptUtils.js'; let initOptions; -const emptyUrl = ""; -const analyticsType = "endpoint"; -const pubxaiAnalyticsVersion = "v2.0.0"; -const defaultHost = "api.pbxai.com"; -const auctionPath = "/analytics/auction"; -const winningBidPath = "/analytics/bidwon"; +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const pubxaiAnalyticsVersion = 'v2.0.0'; +const defaultHost = 'api.pbxai.com'; +const auctionPath = '/analytics/auction'; +const winningBidPath = '/analytics/bidwon'; /** * The sendCache is a global cache object which tracks the pending sends @@ -75,7 +75,7 @@ export const auctionCache = new Proxy( consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(getStorage()?.getItem("pubx:pmac")) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, auctionId: name, // back-compat @@ -112,8 +112,8 @@ const getAdServerDataForBid = (bid) => { .getTargetingKeys() .filter( (key) => - key.startsWith("pubx-") || - (key.startsWith("hb_") && (key.match(/_/g) || []).length === 1) + key.startsWith('pubx-') || + (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) ) .map((key) => [key, gptSlot.getTargeting(key)]) ); @@ -162,7 +162,7 @@ const extractBid = (bidResponse) => { transactionId: bidResponse.transactionId, bidId: bidResponse.bidId || bidResponse.requestId, placementId: bidResponse.params - ? deepAccess(bidResponse, "params.0.placementId") + ? deepAccess(bidResponse, 'params.0.placementId') : null, }; }; @@ -244,16 +244,16 @@ export const getDeviceType = () => { navigator.userAgent.toLowerCase() ) ) { - return "tablet"; + return 'tablet'; } if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) ) { - return "mobile"; + return 'mobile'; } - return "desktop"; + return 'desktop'; }; /** @@ -261,25 +261,25 @@ export const getDeviceType = () => { * @returns {string} */ export const getBrowser = () => { - if (/Edg/.test(navigator.userAgent)) return "Microsoft Edge"; + if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) ) { - return "Chrome"; - } else if (navigator.userAgent.match("CriOS")) return "Chrome"; - else if (/Firefox/.test(navigator.userAgent)) return "Firefox"; + return 'Chrome'; + } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; + else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) ) { - return "Safari"; + return 'Safari'; } else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) ) { - return "Internet Explorer"; - } else return "Others"; + return 'Internet Explorer'; + } else return 'Others'; }; /** @@ -287,13 +287,13 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf("Android") != -1) return "Android"; - if (navigator.userAgent.indexOf("like Mac") != -1) return "iOS"; - if (navigator.userAgent.indexOf("Win") != -1) return "Windows"; - if (navigator.userAgent.indexOf("Mac") != -1) return "Macintosh"; - if (navigator.userAgent.indexOf("Linux") != -1) return "Linux"; - if (navigator.appVersion.indexOf("X11") != -1) return "Unix"; - return "Others"; + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; }; /** @@ -319,32 +319,32 @@ const prepareSend = (auctionId) => { { path: winningBidPath, requiredKeys: [ - "winningBid", - "pageDetail", - "deviceDetail", - "floorDetail", - "auctionDetail", - "userDetail", - "consentDetail", - "pmacDetail", - "initOptions", + 'winningBid', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'initOptions', ], - eventType: "win", + eventType: 'win', }, { path: auctionPath, requiredKeys: [ - "bids", - "pageDetail", - "deviceDetail", - "floorDetail", - "auctionDetail", - "userDetail", - "consentDetail", - "pmacDetail", - "initOptions", + 'bids', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'initOptions', ], - eventType: "auction", + eventType: 'auction', }, ].forEach(({ path, requiredKeys, eventType }) => { const data = Object.fromEntries( @@ -357,7 +357,7 @@ const prepareSend = (auctionId) => { return; } const pubxaiAnalyticsRequestUrl = buildUrl({ - protocol: "https", + protocol: 'https', hostname: (auctionData.initOptions && auctionData.initOptions.hostName) || defaultHost, @@ -374,7 +374,7 @@ const prepareSend = (auctionId) => { }; const send = () => { - const toBlob = (d) => new Blob([JSON.stringify(d)], { type: "text/json" }); + const toBlob = (d) => new Blob([JSON.stringify(d)], { type: 'text/json' }); Object.entries(sendCache).forEach(([requestUrl, events]) => { let payloadStart = 0; @@ -398,8 +398,8 @@ const send = () => { // register event listener to send logs when user leaves page if (hasSendBeaconSupport()) { - document.addEventListener("visibilitychange", () => { - if (document.visibilityState === "hidden") { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { send(); } }); @@ -423,7 +423,7 @@ pubxaiAnalyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: "pubxai", + code: 'pubxai', }); export default pubxaiAnalyticsAdapter; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index fde2a9db7f4..67eb1818315 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -757,27 +757,27 @@ describe('pubxai analytics adapter', () => { } }); - it("auction with no bids", async () => { + it('auction with no bids', async () => { // Step 1: Send auction init event - events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); // Step 3: Send bid time out event - events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 4: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 5: Send auction end event - events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); // Simulate end of session - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 6: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(1); @@ -785,17 +785,17 @@ describe('pubxai analytics adapter', () => { // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; const parsedUrl = new URL(expectedUrl); - expect(parsedUrl.pathname).to.equal("/analytics/auction"); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); // Step 8: check that the meta information in the call is correct expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: "1616654312804", - pubxaiAnalyticsVersion: "v2.0.0", - prebidVersion: "undefined", // not configured for test case + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.0.0', + prebidVersion: 'undefined', // not configured for test case }); // Step 9: check that the data sent in the request is correct - expect(expectedData.type).to.equal("text/json"); + expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ { ...expectedAfterBid, @@ -804,19 +804,19 @@ describe('pubxai analytics adapter', () => { ]); }); - it("2 concurrent auctions", async () => { + it('2 concurrent auctions', async () => { // Step 1: Send auction init event for auction 1 - events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event for auction 1 - events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); // Step 3: Send auction init event for auction 2 events.emit( EVENTS.AUCTION_INIT, - replaceProperty(prebidEvent["auctionInit"], [ + replaceProperty(prebidEvent['auctionInit'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -826,9 +826,9 @@ describe('pubxai analytics adapter', () => { // Step 4: Send bid requested event for auction 2 events.emit( EVENTS.BID_REQUESTED, - replaceProperty(prebidEvent["bidRequested"], [ + replaceProperty(prebidEvent['bidRequested'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -836,17 +836,17 @@ describe('pubxai analytics adapter', () => { ); // Step 5: Send bid response event for auction 1 - events.emit(EVENTS.BID_RESPONSE, prebidEvent["bidResponse"]); + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); // Step 6: Send bid time out event for auction 1 - events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Step 7: Send bid response event for auction 2 events.emit( EVENTS.BID_RESPONSE, - replaceProperty(prebidEvent["bidResponse"], [ + replaceProperty(prebidEvent['bidResponse'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -854,19 +854,19 @@ describe('pubxai analytics adapter', () => { ); // Step 8: Send auction end event for auction 1 - events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 9: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(0); // Step 10: Send auction bid won event for auction 1 - events.emit(EVENTS.BID_WON, prebidEvent["bidWon"]); + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 11: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -874,9 +874,9 @@ describe('pubxai analytics adapter', () => { // Step 12: Send auction end event for auction 2 events.emit( EVENTS.AUCTION_END, - replaceProperty(prebidEvent["auctionEnd"], [ + replaceProperty(prebidEvent['auctionEnd'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -884,7 +884,7 @@ describe('pubxai analytics adapter', () => { ); // Simulate "navigate away" behaviour - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 13: check the number of calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -892,9 +892,9 @@ describe('pubxai analytics adapter', () => { // Step 14: Send auction bid won event for auction 2 events.emit( EVENTS.BID_WON, - replaceProperty(prebidEvent["bidWon"], [ + replaceProperty(prebidEvent['bidWon'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -902,7 +902,7 @@ describe('pubxai analytics adapter', () => { ); // Simulate end of session - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 15: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(4); @@ -911,44 +911,44 @@ describe('pubxai analytics adapter', () => { const parsedUrl = new URL(expectedUrl); const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; expect(parsedUrl.pathname).to.equal( - ["/analytics/bidwon", "/analytics/auction"][index % 2] + ['/analytics/bidwon', '/analytics/auction'][index % 2] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: "1616654312804", - pubxaiAnalyticsVersion: "v2.0.0", - prebidVersion: "undefined", // not configured for test case + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.0.0', + prebidVersion: 'undefined', // not configured for test case }); - expect(expectedData.type).to.equal("text/json"); + expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, { - field: "refreshRank", - updated: "1", - replaced: "0", + field: 'refreshRank', + updated: '1', + replaced: '0', }, ]), ]); } }); - it("2 concurrent auctions with batch sending", async () => { + it('2 concurrent auctions with batch sending', async () => { // Step 1: Send auction init event for auction 1 - events.emit(EVENTS.AUCTION_INIT, prebidEvent["auctionInit"]); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event for auction 1 - events.emit(EVENTS.BID_REQUESTED, prebidEvent["bidRequested"]); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); // Step 3: Send auction init event for auction 2 events.emit( EVENTS.AUCTION_INIT, - replaceProperty(prebidEvent["auctionInit"], [ + replaceProperty(prebidEvent['auctionInit'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -958,9 +958,9 @@ describe('pubxai analytics adapter', () => { // Step 4: Send bid requested event for auction 2 events.emit( EVENTS.BID_REQUESTED, - replaceProperty(prebidEvent["bidRequested"], [ + replaceProperty(prebidEvent['bidRequested'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -968,17 +968,17 @@ describe('pubxai analytics adapter', () => { ); // Step 5: Send bid response event for auction 1 - events.emit(EVENTS.BID_RESPONSE, prebidEvent["bidResponse"]); + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); // Step 6: Send bid time out event for auction 1 - events.emit(EVENTS.BID_TIMEOUT, prebidEvent["bidTimeout"]); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Step 7: Send bid response event for auction 2 events.emit( EVENTS.BID_RESPONSE, - replaceProperty(prebidEvent["bidResponse"], [ + replaceProperty(prebidEvent['bidResponse'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -986,17 +986,17 @@ describe('pubxai analytics adapter', () => { ); // Step 8: Send auction end event for auction 1 - events.emit(EVENTS.AUCTION_END, prebidEvent["auctionEnd"]); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); // Step 9: Send auction bid won event for auction 1 - events.emit(EVENTS.BID_WON, prebidEvent["bidWon"]); + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); // Step 10: Send auction end event for auction 2 events.emit( EVENTS.AUCTION_END, - replaceProperty(prebidEvent["auctionEnd"], [ + replaceProperty(prebidEvent['auctionEnd'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1006,9 +1006,9 @@ describe('pubxai analytics adapter', () => { // Step 11: Send auction bid won event for auction 2 events.emit( EVENTS.BID_WON, - replaceProperty(prebidEvent["bidWon"], [ + replaceProperty(prebidEvent['bidWon'], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, @@ -1019,7 +1019,7 @@ describe('pubxai analytics adapter', () => { expect(navigator.sendBeacon.callCount).to.equal(0); // Simulate end of session - document.dispatchEvent(new Event("visibilitychange")); + document.dispatchEvent(new Event('visibilitychange')); // Step 13: check the calls made to pubx.ai expect(navigator.sendBeacon.callCount).to.equal(2); @@ -1027,26 +1027,26 @@ describe('pubxai analytics adapter', () => { const [expectedUrl, expectedData] = arg; const parsedUrl = new URL(expectedUrl); expect(parsedUrl.pathname).to.equal( - ["/analytics/bidwon", "/analytics/auction"][index] + ['/analytics/bidwon', '/analytics/auction'][index] ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ - auctionTimestamp: "1616654312804", - pubxaiAnalyticsVersion: "v2.0.0", - prebidVersion: "undefined", // not configured for test case + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.0.0', + prebidVersion: 'undefined', // not configured for test case }); - expect(expectedData.type).to.equal("text/json"); + expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ [expectedAfterBidWon, expectedAfterBid][index], replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ { - field: "auctionId", + field: 'auctionId', updated: '"auction2"', replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', }, { - field: "refreshRank", - updated: "1", - replaced: "0", + field: 'refreshRank', + updated: '1', + replaced: '0', }, ]), ]); From cbc15a2cf23b422c4b857586bab4a1b21267001c Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Sun, 12 May 2024 19:12:22 +0100 Subject: [PATCH 13/24] PTOW-2 fixing tests --- .../modules/pubxaiAnalyticsAdapter_spec.js | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 67eb1818315..458894bf236 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -7,6 +7,7 @@ import pubxaiAnalyticsAdapter, { import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; +import { getGlobal } from '../../../src/prebidGlobal.js'; import { getGptSlotInfoForAdUnitCode } from '../../../libraries/gptUtils/gptUtils.js'; import { EVENTS } from '../../../src/constants.js'; @@ -15,6 +16,7 @@ let events = require('src/events'); describe('pubxai analytics adapter', () => { beforeEach(() => { sinon.stub(events, 'getEvents').returns([]); + sinon.stub() }); afterEach(() => { @@ -27,6 +29,9 @@ describe('pubxai analytics adapter', () => { pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', }; + let originalHD; + let originalVS; + let location = utils.getWindowLocation(); let storage = window.top['sessionStorage']; @@ -584,7 +589,7 @@ describe('pubxai analytics adapter', () => { userIdTypes: [], }, consentDetail: { - consentTypes: [], + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { @@ -677,7 +682,7 @@ describe('pubxai analytics adapter', () => { userIdTypes: [], }, consentDetail: { - consentTypes: [], + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, initOptions: { @@ -692,15 +697,19 @@ describe('pubxai analytics adapter', () => { }); beforeEach(() => { - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - writable: true, - }); // prep for the document visibility state change adapterManager.enableAnalytics({ provider: 'pubxai', options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); + originalHD = document.hidden; + originalVS = document.visibilityState; + document['__defineGetter__']('hidden', function () { + return 1; + }); + document['__defineGetter__']('visibilityState', function () { + return 'hidden'; + }); }); afterEach(() => { @@ -708,6 +717,12 @@ describe('pubxai analytics adapter', () => { navigator.sendBeacon.restore(); delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; delete auctionCache['auction2']; + document['__defineGetter__']('hidden', function () { + return originalHD; + }); + document['__defineGetter__']('visibilityState', function () { + return originalVS; + }); }); it('builds and sends auction data', async () => { @@ -748,7 +763,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: 'undefined', // not configured for test case + prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ @@ -791,7 +806,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: 'undefined', // not configured for test case + prebidVersion: getGlobal()?.version, }); // Step 9: check that the data sent in the request is correct @@ -916,7 +931,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: 'undefined', // not configured for test case + prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ @@ -1032,7 +1047,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: 'undefined', // not configured for test case + prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await expectedData.text())).to.deep.equal([ From 902d2413410cc3f9263055915e382193ee3e6cc6 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 14 May 2024 14:19:07 +0100 Subject: [PATCH 14/24] fixing bugs, modifying blob behaviour, addressing browser compatibility --- modules/pubxaiAnalyticsAdapter.js | 6 ++--- .../modules/pubxaiAnalyticsAdapter_spec.js | 24 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 71c9cba0a8c..4963e4adb26 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -31,7 +31,7 @@ export const sendCache = new Proxy( {}, { get: (target, name) => { - if (!Object.hasOwn(target, name)) { + if (!target.hasOwnProperty(name)) { target[name] = []; } return target[name]; @@ -50,7 +50,7 @@ export const auctionCache = new Proxy( {}, { get: (target, name) => { - if (!Object.hasOwn(target, name)) { + if (!target.hasOwnProperty(name)) { target[name] = { bids: [], auctionDetail: { @@ -107,7 +107,7 @@ const hasSendBeaconSupport = () => { const getAdServerDataForBid = (bid) => { const gptSlot = getGptSlotForAdUnitCode(bid); if (gptSlot) { - return Object.fromEntires( + return Object.fromEntries( gptSlot .getTargetingKeys() .filter( diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 458894bf236..b8dc059e5e6 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -13,6 +13,15 @@ import { EVENTS } from '../../../src/constants.js'; let events = require('src/events'); +const readBlobSafariCompat = (blob) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result) + reader.onerror = reject + reader.readAsText(blob) + }) +} + describe('pubxai analytics adapter', () => { beforeEach(() => { sinon.stub(events, 'getEvents').returns([]); @@ -702,11 +711,7 @@ describe('pubxai analytics adapter', () => { options: initOptions, }); sinon.stub(navigator, 'sendBeacon').returns(true); - originalHD = document.hidden; originalVS = document.visibilityState; - document['__defineGetter__']('hidden', function () { - return 1; - }); document['__defineGetter__']('visibilityState', function () { return 'hidden'; }); @@ -717,9 +722,6 @@ describe('pubxai analytics adapter', () => { navigator.sendBeacon.restore(); delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; delete auctionCache['auction2']; - document['__defineGetter__']('hidden', function () { - return originalHD; - }); document['__defineGetter__']('visibilityState', function () { return originalVS; }); @@ -766,7 +768,7 @@ describe('pubxai analytics adapter', () => { prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ [expectedAfterBidWon, expectedAfterBid][index], ]); } @@ -811,7 +813,7 @@ describe('pubxai analytics adapter', () => { // Step 9: check that the data sent in the request is correct expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ { ...expectedAfterBid, bids: [], @@ -934,7 +936,7 @@ describe('pubxai analytics adapter', () => { prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ { field: 'auctionId', @@ -1050,7 +1052,7 @@ describe('pubxai analytics adapter', () => { prebidVersion: getGlobal()?.version, }); expect(expectedData.type).to.equal('text/json'); - expect(JSON.parse(await expectedData.text())).to.deep.equal([ + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ [expectedAfterBidWon, expectedAfterBid][index], replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ { From 6490a99fca57302da7f1a8594a6987dae61c9d6a Mon Sep 17 00:00:00 2001 From: tej656 Date: Tue, 11 Jun 2024 10:21:17 +0530 Subject: [PATCH 15/24] add source field --- modules/pubxaiAnalyticsAdapter.js | 1 + test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 4963e4adb26..dab04a8e359 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -164,6 +164,7 @@ const extractBid = (bidResponse) => { placementId: bidResponse.params ? deepAccess(bidResponse, 'params.0.placementId') : null, + source: bidResponse.source || 'null', }; }; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index b8dc059e5e6..d8f68e714b4 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -565,6 +565,7 @@ describe('pubxai analytics adapter', () => { }, placementId: null, timeToRespond: 267, + source: 'client', responseTimestamp: 1616654313071, }, ], @@ -660,6 +661,7 @@ describe('pubxai analytics adapter', () => { status: 'rendered', statusMessage: 'Bid available', timeToRespond: 267, + source: 'client', }, auctionDetail: { adUnitCodes: ['/19968336/header-bid-tag-1'], From 85c0b2be9e03c266e3de06e9a2717f209cffd504 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 11 Jun 2024 09:55:20 +0100 Subject: [PATCH 16/24] switching from sessionStorage to localStorage --- modules/pubxaiAnalyticsAdapter.js | 21 +++++++------------ .../modules/pubxaiAnalyticsAdapter_spec.js | 6 +++--- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 4963e4adb26..2e62a3509da 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -13,16 +13,21 @@ import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, } from '../libraries/gptUtils/gptUtils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; let initOptions; const emptyUrl = ''; const analyticsType = 'endpoint'; +const adapterCode = 'pubxai'; const pubxaiAnalyticsVersion = 'v2.0.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) + /** * The sendCache is a global cache object which tracks the pending sends * back to pubx.ai. The data may be removed from this cache, post send. @@ -75,7 +80,7 @@ export const auctionCache = new Proxy( consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(getStorage()?.getItem('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} initOptions: { ...initOptions, auctionId: name, // back-compat @@ -121,18 +126,6 @@ const getAdServerDataForBid = (bid) => { return {}; // TODO: support more ad servers }; -/** - * Access sessionStorage - * @returns {Storage} - */ -const getStorage = () => { - try { - return window.top['sessionStorage']; - } catch (e) { - return null; - } -}; - /** * extracts and derives valuable data from a prebid bidder bidResponse object * @param {object} bidResponse a prebid bidder bidResponse (see @@ -423,7 +416,7 @@ pubxaiAnalyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: 'pubxai', + code: adapterCode, }); export default pubxaiAnalyticsAdapter; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index b8dc059e5e6..dad29cf21c6 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ import pubxaiAnalyticsAdapter, { getOS, getBrowser, auctionCache, + storage, } from 'modules/pubxaiAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; @@ -42,7 +43,6 @@ describe('pubxai analytics adapter', () => { let originalVS; let location = utils.getWindowLocation(); - let storage = window.top['sessionStorage']; const replaceProperty = (obj, params) => { let strObj = JSON.stringify(obj); @@ -600,7 +600,7 @@ describe('pubxai analytics adapter', () => { consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, + pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -693,7 +693,7 @@ describe('pubxai analytics adapter', () => { consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(storage.getItem('pbx:pmac')) || {}, + pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', From 2be6c09626243d5e8a1a2ce012437300fb486f34 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 11 Jun 2024 14:09:09 +0100 Subject: [PATCH 17/24] fixing tests --- modules/pubxaiAnalyticsAdapter.js | 3 +-- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 2e62a3509da..4485262f518 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -25,8 +25,7 @@ const pubxaiAnalyticsVersion = 'v2.0.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; - -export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) +const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) /** * The sendCache is a global cache object which tracks the pending sends diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index dad29cf21c6..2caed5f5c36 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -3,7 +3,6 @@ import pubxaiAnalyticsAdapter, { getOS, getBrowser, auctionCache, - storage, } from 'modules/pubxaiAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; @@ -600,7 +599,7 @@ describe('pubxai analytics adapter', () => { consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, + pmacDetail: {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -693,7 +692,7 @@ describe('pubxai analytics adapter', () => { consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, - pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, + pmacDetail: {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -765,7 +764,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: getGlobal()?.version, + prebidVersion: String(getGlobal().version), }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -808,7 +807,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: getGlobal()?.version, + prebidVersion: String(getGlobal().version), }); // Step 9: check that the data sent in the request is correct @@ -933,7 +932,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: getGlobal()?.version, + prebidVersion: String(getGlobal().version), }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -1049,7 +1048,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: getGlobal()?.version, + prebidVersion: String(getGlobal().version), }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ From 4db2d4b3d9945c557c1c771272060374f24bc5c3 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 12 Jun 2024 13:54:44 +0100 Subject: [PATCH 18/24] modifying functions to avoid prebid duplication checker --- modules/pubxaiAnalyticsAdapter.js | 32 +++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 4485262f518..005f275edd0 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -235,16 +235,12 @@ export const getDeviceType = () => { /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( navigator.userAgent.toLowerCase() ) - ) { - return 'tablet'; - } + ) return 'tablet'; if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) - ) { - return 'mobile'; - } + ) return 'mobile'; return 'desktop'; }; @@ -279,12 +275,24 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; - if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; - if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; - if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; - if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; - if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + if (navigator.userAgent.indexOf('Android') != -1) { + return 'Android'; + } + if (navigator.userAgent.indexOf('like Mac') != -1) { + return 'iOS'; + } + if (navigator.userAgent.indexOf('Win') != -1) { + return 'Windows'; + } + if (navigator.userAgent.indexOf('Mac') != -1) { + return 'Macintosh'; + } + if (navigator.userAgent.indexOf('Linux') != -1) { + return 'Linux'; + } + if (navigator.appVersion.indexOf('X11') != -1) { + return 'Unix'; + } return 'Others'; }; From 1e2d790488700354f855470eee039efe369251e2 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 17 Jun 2024 23:43:37 +0100 Subject: [PATCH 19/24] implementing enums --- modules/pubxaiAnalyticsAdapter.js | 76 +++++++++++++++++-------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 005f275edd0..2be4a21fdd1 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -27,6 +27,31 @@ const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) +const deviceTypes = Object.freeze({ + DESKTOP: 0, + MOBILE: 1, + TABLET: 2, +}) + +const browserTypes = Object.freeze({ + CHROME: 0, + FIREFOX: 1, + SAFARI: 2, + EDGE: 3, + INTERNET_EXPLORER: 4, + OTHER: 5 +}) + +const osTypes = Object.freeze({ + WINDOWS: 0, + MAC: 1, + LINUX: 2, + UNIX: 3, + IOS: 4, + ANDROID: 5, + OTHER: 6 +}) + /** * The sendCache is a global cache object which tracks the pending sends * back to pubx.ai. The data may be removed from this cache, post send. @@ -235,13 +260,13 @@ export const getDeviceType = () => { /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( navigator.userAgent.toLowerCase() ) - ) return 'tablet'; + ) return deviceTypes.TABLET; if ( /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( navigator.userAgent.toLowerCase() ) - ) return 'mobile'; - return 'desktop'; + ) return deviceTypes.MOBILE; + return deviceTypes.DESKTOP; }; /** @@ -249,25 +274,22 @@ export const getDeviceType = () => { * @returns {string} */ export const getBrowser = () => { - if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; + if (/Edg/.test(navigator.userAgent)) return browserTypes.EDGE; else if ( /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) - ) { - return 'Chrome'; - } else if (navigator.userAgent.match('CriOS')) return 'Chrome'; - else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; + ) return browserTypes.CHROME; + else if (navigator.userAgent.match('CriOS')) return browserTypes.CHROME; + else if (/Firefox/.test(navigator.userAgent)) return browserTypes.FIREFOX; else if ( /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) - ) { - return 'Safari'; - } else if ( + ) return browserTypes.SAFARI + else if ( /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent) - ) { - return 'Internet Explorer'; - } else return 'Others'; + ) return browserTypes.INTERNET_EXPLORER + else return browserTypes.OTHER; }; /** @@ -275,25 +297,13 @@ export const getBrowser = () => { * @returns {string} */ export const getOS = () => { - if (navigator.userAgent.indexOf('Android') != -1) { - return 'Android'; - } - if (navigator.userAgent.indexOf('like Mac') != -1) { - return 'iOS'; - } - if (navigator.userAgent.indexOf('Win') != -1) { - return 'Windows'; - } - if (navigator.userAgent.indexOf('Mac') != -1) { - return 'Macintosh'; - } - if (navigator.userAgent.indexOf('Linux') != -1) { - return 'Linux'; - } - if (navigator.appVersion.indexOf('X11') != -1) { - return 'Unix'; - } - return 'Others'; + if (navigator.userAgent.indexOf('Android') != -1) return osTypes.ANDROID + if (navigator.userAgent.indexOf('like Mac') != -1) return osTypes.IOS + if (navigator.userAgent.indexOf('Win') != -1) return osTypes.WINDOWS + if (navigator.userAgent.indexOf('Mac') != -1) return osTypes.MAC + if (navigator.userAgent.indexOf('Linux') != -1) return osTypes.LINUX + if (navigator.appVersion.indexOf('X11') != -1) return osTypes.UNIX + return osTypes.OTHER; }; /** From f10ab085ffbd326de1b8a2795b2411f40f8dc1ab Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 20 Jun 2024 01:30:58 +0100 Subject: [PATCH 20/24] moving user agent code to libraries --- libraries/userAgentUtils/index.js | 58 ++++++++++ .../userAgentUtils/userAgentTypes.enums.js | 22 ++++ modules/pubxaiAnalyticsAdapter.js | 81 +------------ test/spec/libraries/userAgentUtils_spec.js | 108 ++++++++++++++++++ .../modules/pubxaiAnalyticsAdapter_spec.js | 22 ++-- 5 files changed, 198 insertions(+), 93 deletions(-) create mode 100644 libraries/userAgentUtils/index.js create mode 100644 libraries/userAgentUtils/userAgentTypes.enums.js create mode 100644 test/spec/libraries/userAgentUtils_spec.js diff --git a/libraries/userAgentUtils/index.js b/libraries/userAgentUtils/index.js new file mode 100644 index 00000000000..4e0cffbce44 --- /dev/null +++ b/libraries/userAgentUtils/index.js @@ -0,0 +1,58 @@ +import { deviceTypes, browserTypes, osTypes } from './userAgentTypes.enums.js'; + +/** + * Get the approximate device type enum from the user agent + * @returns {string} + */ +export const getDeviceType = () => { + if ( + /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( + navigator.userAgent.toLowerCase() + ) + ) return deviceTypes.TABLET; + if ( + /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( + navigator.userAgent.toLowerCase() + ) + ) return deviceTypes.MOBILE; + return deviceTypes.DESKTOP; +}; + +/** + * Get the approximate browser type enum from the user agent (or vendor + * if available) + * @returns {string} + */ +export const getBrowser = () => { + if (/Edg/.test(navigator.userAgent)) return browserTypes.EDGE; + else if ( + /Chrome/.test(navigator.userAgent) && + /Google Inc/.test(navigator.vendor) + ) return browserTypes.CHROME; + else if (navigator.userAgent.match('CriOS')) return browserTypes.CHROME; + else if (/Firefox/.test(navigator.userAgent)) return browserTypes.FIREFOX; + else if ( + /Safari/.test(navigator.userAgent) && + /Apple Computer/.test(navigator.vendor) + ) return browserTypes.SAFARI; + else if ( + /Trident/.test(navigator.userAgent) || + /MSIE/.test(navigator.userAgent) + ) return browserTypes.INTERNET_EXPLORER; + else return browserTypes.OTHER; +}; + +/** + * Get the approximate OS enum from the user agent (or app version, + * if available) + * @returns {string} + */ +export const getOS = () => { + if (navigator.userAgent.indexOf('Android') != -1) return osTypes.ANDROID; + if (navigator.userAgent.indexOf('like Mac') != -1) return osTypes.IOS; + if (navigator.userAgent.indexOf('Win') != -1) return osTypes.WINDOWS; + if (navigator.userAgent.indexOf('Mac') != -1) return osTypes.MAC; + if (navigator.userAgent.indexOf('Linux') != -1) return osTypes.LINUX; + if (navigator.appVersion.indexOf('X11') != -1) return osTypes.UNIX; + return osTypes.OTHER; +}; diff --git a/libraries/userAgentUtils/userAgentTypes.enums.js b/libraries/userAgentUtils/userAgentTypes.enums.js new file mode 100644 index 00000000000..8a0255e88bf --- /dev/null +++ b/libraries/userAgentUtils/userAgentTypes.enums.js @@ -0,0 +1,22 @@ +export const deviceTypes = Object.freeze({ + DESKTOP: 0, + MOBILE: 1, + TABLET: 2, +}) +export const browserTypes = Object.freeze({ + CHROME: 0, + FIREFOX: 1, + SAFARI: 2, + EDGE: 3, + INTERNET_EXPLORER: 4, + OTHER: 5 +}) +export const osTypes = Object.freeze({ + WINDOWS: 0, + MAC: 1, + LINUX: 2, + UNIX: 3, + IOS: 4, + ANDROID: 5, + OTHER: 6 +}) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 7794a8a3ed1..2bbdbf6277c 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -9,6 +9,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { getDeviceType, getBrowser, getOS } from '../libraries/userAgentUtils/index.js'; import { getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode, @@ -27,31 +28,6 @@ const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) -const deviceTypes = Object.freeze({ - DESKTOP: 0, - MOBILE: 1, - TABLET: 2, -}) - -const browserTypes = Object.freeze({ - CHROME: 0, - FIREFOX: 1, - SAFARI: 2, - EDGE: 3, - INTERNET_EXPLORER: 4, - OTHER: 5 -}) - -const osTypes = Object.freeze({ - WINDOWS: 0, - MAC: 1, - LINUX: 2, - UNIX: 3, - IOS: 4, - ANDROID: 5, - OTHER: 6 -}) - /** * The sendCache is a global cache object which tracks the pending sends * back to pubx.ai. The data may be removed from this cache, post send. @@ -252,61 +228,6 @@ const track = ({ eventType, args }) => { } }; -/** - * Get the approximate device type from the user agent - * @returns {string} - */ -export const getDeviceType = () => { - if ( - /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( - navigator.userAgent.toLowerCase() - ) - ) return deviceTypes.TABLET; - if ( - /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( - navigator.userAgent.toLowerCase() - ) - ) return deviceTypes.MOBILE; - return deviceTypes.DESKTOP; -}; - -/** - * Get the approximate browser type from the user agent (or vendor if available) - * @returns {string} - */ -export const getBrowser = () => { - if (/Edg/.test(navigator.userAgent)) return browserTypes.EDGE; - else if ( - /Chrome/.test(navigator.userAgent) && - /Google Inc/.test(navigator.vendor) - ) return browserTypes.CHROME; - else if (navigator.userAgent.match('CriOS')) return browserTypes.CHROME; - else if (/Firefox/.test(navigator.userAgent)) return browserTypes.FIREFOX; - else if ( - /Safari/.test(navigator.userAgent) && - /Apple Computer/.test(navigator.vendor) - ) return browserTypes.SAFARI - else if ( - /Trident/.test(navigator.userAgent) || - /MSIE/.test(navigator.userAgent) - ) return browserTypes.INTERNET_EXPLORER - else return browserTypes.OTHER; -}; - -/** - * Get the approximate OS from the user agent (or app version, if available) - * @returns {string} - */ -export const getOS = () => { - if (navigator.userAgent.indexOf('Android') != -1) return osTypes.ANDROID - if (navigator.userAgent.indexOf('like Mac') != -1) return osTypes.IOS - if (navigator.userAgent.indexOf('Win') != -1) return osTypes.WINDOWS - if (navigator.userAgent.indexOf('Mac') != -1) return osTypes.MAC - if (navigator.userAgent.indexOf('Linux') != -1) return osTypes.LINUX - if (navigator.appVersion.indexOf('X11') != -1) return osTypes.UNIX - return osTypes.OTHER; -}; - /** * If true, send data back to pubxai * @param {string} auctionId diff --git a/test/spec/libraries/userAgentUtils_spec.js b/test/spec/libraries/userAgentUtils_spec.js new file mode 100644 index 00000000000..17a36d6dbf9 --- /dev/null +++ b/test/spec/libraries/userAgentUtils_spec.js @@ -0,0 +1,108 @@ +/* globals describe, beforeEach, afterEach, sinon */ +import { expect } from 'chai'; +import { getDeviceType, getBrowser, getOS } from 'libraries/userAgentUtils'; +import { deviceTypes, browserTypes, osTypes } from 'libraries/userAgentUtils/userAgentTypes.enums'; + +const ORIGINAL_USER_AGENT = window.navigator.userAgent; +const ORIGINAL_VENDOR = window.navigator.vendor; +const ORIGINAL_APP_VERSION = window.navigator.appVersion; + +describe('Test user agent categorization', () => { + afterEach(() => { + window.navigator.__defineGetter__('userAgent', () => ORIGINAL_USER_AGENT); + window.navigator.__defineGetter__('vendor', () => ORIGINAL_VENDOR); + window.navigator.__defineGetter__('appVersion', () => ORIGINAL_APP_VERSION); + }) + + describe('test getDeviceType', () => { + it('user agent device type is tablet', () => { + const tabletUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4.17.9 (KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3' + window.navigator.__defineGetter__('userAgent', () => tabletUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.TABLET); + }) + it('user agent device type is mobile', () => { + const mobileUserAgent = 'Mozilla/5.0 (Linux; Android 12; M2102J20SG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => mobileUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.MOBILE); + }) + it('user agent device type is desktop', () => { + const desktopUserAgent = 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => desktopUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.DESKTOP); + }) + }) + + describe('test getBrowser', () => { + it('user agent browser is edge', () => { + const edgeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10532' + window.navigator.__defineGetter__('userAgent', () => edgeUserAgent); + expect(getBrowser()).to.equal(browserTypes.EDGE); + }) + it('user agent browser is chrome', () => { + const chromeUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12H143 Safari/600.1.4' + window.navigator.__defineGetter__('userAgent', () => chromeUserAgent); + expect(getBrowser()).to.equal(browserTypes.CHROME); + }) + it('user agent browser is firefox', () => { + const firefoxUserAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:40.0) Gecko/20100101 Firefox/40.0.2 Waterfox/40.0.2' + window.navigator.__defineGetter__('userAgent', () => firefoxUserAgent); + expect(getBrowser()).to.equal(browserTypes.FIREFOX); + }) + it('user agent browser is safari', () => { + const safariUserAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => safariUserAgent); + window.navigator.__defineGetter__('vendor', () => 'Apple Computer, Inc.'); + expect(getBrowser()).to.equal(browserTypes.SAFARI); + }) + it('user agent browser is internet explorer', () => { + const internetexplorerUserAgent = 'Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' + window.navigator.__defineGetter__('userAgent', () => internetexplorerUserAgent); + expect(getBrowser()).to.equal(browserTypes.INTERNET_EXPLORER); + }) + it('user agent is other', () => { + const otherUserAgent = 'Dalvik/2.1.0 (Linux; U; Android 9; ADT-2 Build/PTT5.181126.002)' + window.navigator.__defineGetter__('userAgent', () => otherUserAgent); + expect(getBrowser()).to.equal(browserTypes.OTHER); + }) + }) + + describe('test getOS', () => { + it('user agent is android', () => { + const androidUserAgent = 'Mozilla/5.0 (Android; Mobile; rv:40.0) Gecko/40.0 Firefox/40.0' + window.navigator.__defineGetter__('userAgent', () => androidUserAgent); + expect(getOS()).to.equal(osTypes.ANDROID); + }) + it('user agent is ios', () => { + const iosUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4' + window.navigator.__defineGetter__('userAgent', () => iosUserAgent); + expect(getOS()).to.equal(osTypes.IOS); + }) + it('user agent is windows', () => { + const windowsUserAgent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => windowsUserAgent); + expect(getOS()).to.equal(osTypes.WINDOWS); + }) + it('user agent is mac', () => { + const macUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0' + window.navigator.__defineGetter__('userAgent', () => macUserAgent); + expect(getOS()).to.equal(osTypes.MAC); + }) + it('user agent is linux', () => { + const linuxUserAgent = 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0' + window.navigator.__defineGetter__('userAgent', () => linuxUserAgent); + expect(getOS()).to.equal(osTypes.LINUX); + }) + it('user agent is unix', () => { + const unixUserAgent = 'Mozilla/5.0 (X11; CrOS armv7l 7077.134.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.156 Safari/537.36' + const unixappVersion = '5.0 (X11; CrOS armv7l 7077.134.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.156 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => unixUserAgent); + window.navigator.__defineGetter__('appVersion', () => unixappVersion); + expect(getOS()).to.equal(osTypes.UNIX); + }) + it('user agent is other', () => { + const otherUserAgent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' + window.navigator.__defineGetter__('userAgent', () => otherUserAgent); + expect(getOS()).to.equal(osTypes.OTHER); + }) + }) +}) diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1a3bd137df1..e4f413ef78e 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,17 +1,15 @@ +/* globals describe, beforeEach, afterEach, sinon */ +import { expect } from 'chai'; +import { getGptSlotInfoForAdUnitCode } from 'libraries/gptUtils/gptUtils.js'; +import { getDeviceType, getBrowser, getOS } from 'libraries/userAgentUtils'; import pubxaiAnalyticsAdapter, { - getDeviceType, - getOS, - getBrowser, auctionCache, } from 'modules/pubxaiAnalyticsAdapter.js'; -import { expect } from 'chai'; +import { EVENTS } from 'src/constants.js'; import adapterManager from 'src/adapterManager.js'; -import * as utils from 'src/utils.js'; -import { getGlobal } from '../../../src/prebidGlobal.js'; -import { getGptSlotInfoForAdUnitCode } from '../../../libraries/gptUtils/gptUtils.js'; -import { EVENTS } from '../../../src/constants.js'; - -let events = require('src/events'); +import { getWindowLocation } from 'src/utils.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import * as events from 'src/events.js' const readBlobSafariCompat = (blob) => { return new Promise((resolve, reject) => { @@ -25,7 +23,6 @@ const readBlobSafariCompat = (blob) => { describe('pubxai analytics adapter', () => { beforeEach(() => { sinon.stub(events, 'getEvents').returns([]); - sinon.stub() }); afterEach(() => { @@ -38,10 +35,9 @@ describe('pubxai analytics adapter', () => { pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', }; - let originalHD; let originalVS; - let location = utils.getWindowLocation(); + let location = getWindowLocation(); const replaceProperty = (obj, params) => { let strObj = JSON.stringify(obj); From 5f24f74f7869972ca9175c1b780ff0351626f636 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 20 Jun 2024 01:34:41 +0100 Subject: [PATCH 21/24] updated return types --- libraries/userAgentUtils/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/userAgentUtils/index.js b/libraries/userAgentUtils/index.js index 4e0cffbce44..7300bbd519a 100644 --- a/libraries/userAgentUtils/index.js +++ b/libraries/userAgentUtils/index.js @@ -2,7 +2,7 @@ import { deviceTypes, browserTypes, osTypes } from './userAgentTypes.enums.js'; /** * Get the approximate device type enum from the user agent - * @returns {string} + * @returns {number} */ export const getDeviceType = () => { if ( @@ -21,7 +21,7 @@ export const getDeviceType = () => { /** * Get the approximate browser type enum from the user agent (or vendor * if available) - * @returns {string} + * @returns {number} */ export const getBrowser = () => { if (/Edg/.test(navigator.userAgent)) return browserTypes.EDGE; @@ -45,7 +45,7 @@ export const getBrowser = () => { /** * Get the approximate OS enum from the user agent (or app version, * if available) - * @returns {string} + * @returns {number} */ export const getOS = () => { if (navigator.userAgent.indexOf('Android') != -1) return osTypes.ANDROID; From 6aebb14860e193a35054bed6a974b6e49d2b3038 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 20 Jun 2024 11:25:34 +0100 Subject: [PATCH 22/24] switching to macro substitution for prebid version --- modules/pubxaiAnalyticsAdapter.js | 2 +- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 7794a8a3ed1..1b00fb519c8 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -376,7 +376,7 @@ const prepareSend = (auctionId) => { search: { auctionTimestamp: auctionData.auctionDetail.timestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: getGlobal().version, + prebidVersion: '$prebid.version$', }, }); sendCache[pubxaiAnalyticsRequestUrl].push(data); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1a3bd137df1..8844c29b3c1 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -766,7 +766,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: String(getGlobal().version), + prebidVersion: '$prebid.version$', }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -809,7 +809,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: String(getGlobal().version), + prebidVersion: '$prebid.version$', }); // Step 9: check that the data sent in the request is correct @@ -934,7 +934,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: String(getGlobal().version), + prebidVersion: '$prebid.version$', }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -1050,7 +1050,7 @@ describe('pubxai analytics adapter', () => { expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', - prebidVersion: String(getGlobal().version), + prebidVersion: '$prebid.version$', }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ From 08a9a24234da8ff95e108775c8276adc41e8678f Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Tue, 2 Jul 2024 00:59:46 +0100 Subject: [PATCH 23/24] adding centralised sendBeacon wrapper --- modules/pubxaiAnalyticsAdapter.js | 35 +++++++++---------------------- src/ajax.js | 14 +++++++++++++ test/spec/ajax_spec.js | 11 ++++++++++ 3 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 test/spec/ajax_spec.js diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 1b00fb519c8..2d1a0907325 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,20 +1,16 @@ -import { - deepAccess, - parseSizesInput, - getWindowLocation, - buildUrl, - cyrb53Hash, -} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import { + getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode +} from '../libraries/gptUtils/gptUtils.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; +import { sendBeacon } from '../src/ajax.js' import { EVENTS } from '../src/constants.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { - getGptSlotInfoForAdUnitCode, - getGptSlotForAdUnitCode, -} from '../libraries/gptUtils/gptUtils.js'; import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { + deepAccess, parseSizesInput, getWindowLocation, buildUrl, cyrb53Hash +} from '../src/utils.js'; let initOptions; @@ -117,17 +113,6 @@ export const auctionCache = new Proxy( } ); -/** - * - * @returns {boolean} whether or not the browser session supports sendBeacon - */ -const hasSendBeaconSupport = () => { - if (!navigator.sendBeacon || !document.visibilityState) { - return false; - } - return true; -}; - /** * Fetch extra ad server data for a specific ad slot (bid) * @param {object} bid an output from extractBid @@ -395,7 +380,7 @@ const send = () => { const payloadTooLarge = toBlob(payload).size > 65536; if (payloadTooLarge || index + 1 === arr.length) { - navigator.sendBeacon( + sendBeacon( requestUrl, toBlob(payloadTooLarge ? payload.slice(0, -1) : payload) ); @@ -408,7 +393,7 @@ const send = () => { }; // register event listener to send logs when user leaves page -if (hasSendBeaconSupport()) { +if (document.visibilityState) { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { send(); diff --git a/src/ajax.js b/src/ajax.js index ef4c2e4bcb4..baff47b0743 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -144,5 +144,19 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { }; } +/** + * simple wrapper around sendBeacon such that invocations of navigator.sendBeacon can be centrally maintained. + * verifies that the navigator and sendBeacon are defined for maximum compatibility + * @param {string} url The URL that will receive the data. Can be relative or absolute. + * @param {*} data An ArrayBuffer, a TypedArray, a DataView, a Blob, a string literal or object, a FormData or a URLSearchParams object containing the data to send. + * @returns {boolean} true if the user agent successfully queued the data for transfer. Otherwise, it returns false. + */ +export function sendBeacon(url, data) { + if (!window.navigator || !window.navigator.sendBeacon) { + return false; + } + return window.navigator.sendBeacon(url, data); +} + export const ajax = ajaxBuilder(); export const fetch = fetcherFactory(); diff --git a/test/spec/ajax_spec.js b/test/spec/ajax_spec.js new file mode 100644 index 00000000000..dc0fd0c1638 --- /dev/null +++ b/test/spec/ajax_spec.js @@ -0,0 +1,11 @@ +import { expect } from 'chai'; +import { sendBeacon } from '../../src/ajax.js' + +describe('test sendBeacon wrapper', () => { + it('with legitimate behaviour', () => { + sinon.stub(navigator, 'sendBeacon').returns(true); + expect(sendBeacon('http://localhost:80/')).to.equal(true); + expect(navigator.sendBeacon.callCount).to.equal(1); + navigator.sendBeacon.restore(); + }) +}) From acdb5b6c548c3e567449cf3e74bf23bd289c7024 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 3 Jul 2024 22:22:03 +0100 Subject: [PATCH 24/24] 'fixing' tests --- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 0cb5fb51796..237a2d32d54 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -22,6 +22,7 @@ const readBlobSafariCompat = (blob) => { describe('pubxai analytics adapter', () => { beforeEach(() => { + getGlobal().refreshUserIds() sinon.stub(events, 'getEvents').returns([]); }); @@ -591,7 +592,7 @@ describe('pubxai analytics adapter', () => { cdep: true, }, userDetail: { - userIdTypes: [], + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), }, consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), @@ -685,7 +686,7 @@ describe('pubxai analytics adapter', () => { cdep: true, }, userDetail: { - userIdTypes: [], + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), }, consentDetail: { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}),