From a39375d4f52b31aac3687991b9dee55e5fb8041b Mon Sep 17 00:00:00 2001 From: Hrechko Dmytro Date: Thu, 11 Jul 2024 05:08:50 +0200 Subject: [PATCH] EightPod Bid Adapter + EightPod Analytic Adapter - Support multiple adUnit, updated event tracking, added UserId support (#11944) * Support multiple adUnit, updated event tracking, added UserId support * remove unused params * fix unit tests * change MODULE_NAME * added keepalive --- modules/eightPodAnalyticsAdapter.js | 62 +++---- modules/eightPodBidAdapter.js | 170 ++++++++++++------ .../modules/eightPodAnalyticsAdapter_spec.js | 35 ++-- 3 files changed, 163 insertions(+), 104 deletions(-) diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js index a1ea76daed1d..559a8107ba2e 100644 --- a/modules/eightPodAnalyticsAdapter.js +++ b/modules/eightPodAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {logError, logInfo} from '../src/utils.js'; +import {logError, logInfo, logMessage} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; @@ -9,13 +9,11 @@ import {getStorageManager} from '../src/storageManager.js'; const analyticsType = 'endpoint'; const MODULE_NAME = `eightPod`; const MODULE = `${MODULE_NAME}AnalyticProvider`; -let interval; /** * Custom tracking server that gets internal events from EightPod's ad unit */ const trackerUrl = 'https://demo.8pod.com/tracker/track'; - export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) const { @@ -23,12 +21,12 @@ const { } = EVENTS; export let queue = []; -export let context; +let context = {}; /** * Create eightPod Analytic adapter */ -let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { +let eightPodAnalytics = Object.assign(adapter({url: trackerUrl, analyticsType}), { /** * Execute on bid won - setup basic settings, save context about EightPod's bid. We will send it with our events later */ @@ -36,45 +34,47 @@ let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { switch (eventType) { case BID_WON: if (args.bidder === 'eightPod') { + context[args.adUnitCode] = makeContext(args); + eightPodAnalytics.setupPage(args); - context = makeContext(args); break; } } }, /** - * Execute on bid won - subscribe on events from adUnit + * Execute on bid won upload events from local storage */ setupPage() { - eightPodAnalytics.eventSubscribe(); - queue = getEventFromLocalStorage(); + queue = this.getEventFromLocalStorage(); }, + /** * Subscribe on internal ad unit tracking events */ eventSubscribe() { window.addEventListener('message', async (event) => { - const data = event?.data; + const data = event.data; - if (!data?.detail?.name) { - return; - } + const frameElement = event.source.frameElement; + const parentElement = frameElement?.parentElement; + const adUnitCode = parentElement?.id; - trackEvent(data); + trackEvent(data, adUnitCode); }); - // Send queue of event every 10 seconds - if (!interval) { - interval = setInterval(sendEvents, 10_000); - } + setInterval(sendEvents, 10_000); }, resetQueue() { queue = []; }, getContext() { return context; - } + }, + resetContext() { + context = {}; + }, + getEventFromLocalStorage, }); /** @@ -84,8 +84,8 @@ function makeContext(args) { const params = args?.params?.[0]; return { bidId: args.seatBidId, - variantId: args.creativeId || 'variantId', - campaignId: 'campaignId', + variantId: args.creativeId || '', + campaignId: args.cid || '', publisherId: params.publisherId, placementId: params.placementId, }; @@ -94,12 +94,13 @@ function makeContext(args) { /** * Create event, add context and push it to queue */ -export function trackEvent(event) { +export function trackEvent(event, adUnitCode) { if (!event.detail) { return; } + const fullEvent = { - context: eightPodAnalytics.getContext(), + context: eightPodAnalytics.getContext()[adUnitCode], eventType: event.detail.type, eventClass: 'adunit', timestamp: new Date().getTime(), @@ -107,6 +108,7 @@ export function trackEvent(event) { payload: event.detail.payload }; + logMessage(fullEvent); addEvent(fullEvent); } @@ -158,7 +160,7 @@ function sendEvents() { * Send event to our custom tracking server */ function sendEventsApi(eventList, callbacks) { - ajax(trackerUrl, callbacks, JSON.stringify(eventList)); + ajax(trackerUrl, callbacks, JSON.stringify(eventList), {keepalive: true}); } /** @@ -173,21 +175,13 @@ eightPodAnalytics.originEnableAnalytics = eightPodAnalytics.enableAnalytics; eightPodAnalytics.eventsStorage = []; // override enableAnalytics so we can get access to the config passed in from the page +// Subscribe on events from adUnit eightPodAnalytics.enableAnalytics = function (config) { eightPodAnalytics.originEnableAnalytics(config); logInfo(MODULE, 'init', config); + eightPodAnalytics.eventSubscribe(); }; -eightPodAnalytics.disableAnalytics = (function (orig) { - return function (...args) { - if (interval) { - clearInterval(interval); - interval = null; - } - return orig.apply(this, args); - }; -})(eightPodAnalytics.disableAnalytics); - /** * Register Analytics Adapter */ diff --git a/modules/eightPodBidAdapter.js b/modules/eightPodBidAdapter.js index 02f0954af07b..536bc4b4036b 100644 --- a/modules/eightPodBidAdapter.js +++ b/modules/eightPodBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import * as utils from '../src/utils.js' export const BIDDER_CODE = 'eightPod' -const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid?trace=true'; +const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid'; export const spec = { code: BIDDER_CODE, @@ -49,8 +49,9 @@ function isBidRequestValid(bidRequest) { function buildRequests(bids, bidderRequest) { let bannerBids = bids.filter((bid) => isBannerBid(bid)) let requests = bannerBids.length - ? [createRequest(bannerBids, bidderRequest, BANNER)] + ? createRequest(bannerBids, bidderRequest, BANNER) : [] + return requests } @@ -61,8 +62,10 @@ function bidResponse(buildBidResponse, bid, context) { bidResponse.height = context?.imp?.banner?.format?.[0].h; bidResponse.width = context?.imp?.banner?.format?.[0].w; + bidResponse.cid = bid.cid; bidResponse.burl = replacePriceInUrl(bid.burl, bidResponse.originalCpm || bidResponse.cpm); + return bidResponse; } @@ -119,64 +122,75 @@ export function getPageKeywords(win = window) { } function createRequest(bidRequests, bidderRequest, mediaType) { - const data = converter.toORTB({ - bidRequests, - bidderRequest, - context: { mediaType }, - }); - - data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; - data.at = 1; - - const params = getBidderParams(bidRequests); - - data.device = { - ...data.device, - model: parseUserAgent().device, - os: parseUserAgent().platform, - osv: parseUserAgent().version, - geo: { - country: params.country || 'GRB' - }, - language: params.language || data.device.language, - } - data.site = { - ...data.site, - keywords: getPageKeywords(window), - } - data.imp = [ - { - ...data.imp?.[0], - pmp: params.dealId - ? { - ...data.pmp, - deals: [ - { - bidfloor: 0.5, - at: 2, - id: params.dealId, - }, - ], - private_auction: 1, - } - : data.pmp, + const requests = bidRequests.map((bidRequest) => { + const data = converter.toORTB({ + bidRequests: [bidRequest], + bidderRequest, + context: { mediaType }, + }); + + data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; + data.at = 1; + + const userId = + utils.deepAccess(bidRequest, 'userId.unifiedId.id') || + utils.deepAccess(bidRequest, 'userId.id5id.uid') || + utils.deepAccess(bidRequest, 'userId.idl_env'); + + const params = getBidderParams(bidRequest); + data.device = { + ...data.device, + devicetype: 4, + geo: { + country: params.country || 'GRB' + }, + language: params.language || data.device.language, + } + data.site = { + ...data.site, + keywords: getPageKeywords(window), + publisher: { + id: params.publisherId + } + } + data.imp = [ + { + ...data.imp?.[0], + secure: 1, + pmp: params.dealId + ? { + ...data.pmp, + deals: [ + { + id: params.dealId, + }, + ], + private_auction: 1, + } + : data.pmp, + } + ] + data.adSlotPlacementId = params.placementId; + + if (userId) { + data.user = { + id: userId + } } - ] - data.adSlotPlacementId = params.placementId; - const req = { - method: 'POST', - url, - data - } - return req -} + const req = { + method: 'POST', + url: url && params.trace ? url + '?trace=true' : url, + options: { withCredentials: false }, + data + } + return req + }) -function getBidderParams(bidRequests) { - const bid = bidRequests.find(bid => { - return bid.bidder === BIDDER_CODE - }); + return requests; +} +function getBidderParams(bid) { return bid?.params ? bid.params : undefined; } @@ -189,5 +203,47 @@ function isBannerBid(bid) { } function interpretResponse(resp, req) { - return converter.fromORTB({ request: req.data, response: resp.body }) + const impressionId = resp.body.seatbid[0].bid[0].impid; + const bidResponses = converter.fromORTB({ request: req.data, response: resp.body }); + const ad = bidResponses[0].ad; + const trackingTag = ` + + + + + ` + + bidResponses[0].ad = ad.replace('', trackingTag + ''); + return bidResponses; } diff --git a/test/spec/modules/eightPodAnalyticsAdapter_spec.js b/test/spec/modules/eightPodAnalyticsAdapter_spec.js index 930b15bda310..55a09db03b4f 100644 --- a/test/spec/modules/eightPodAnalyticsAdapter_spec.js +++ b/test/spec/modules/eightPodAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import analyticsAdapter, { storage, queue, context, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; +import analyticsAdapter, { storage, queue, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import eightPodAnalytics from 'modules/eightPodAnalyticsAdapter.js'; @@ -45,11 +45,12 @@ describe('eightPodAnalyticAdapter', function() { it('should subscribe on messageEvents', function() { getDataFromLocalStorageStub.returns(JSON.stringify([])); sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + sandbox.spy(eightPodAnalytics, 'getEventFromLocalStorage'); analyticsAdapter.setupPage(); - sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 1); - sandbox.assert.callCount(addEventListenerSpy, 1); + sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 0); + sandbox.assert.callCount(analyticsAdapter.getEventFromLocalStorage, 1); }); it('should receive saved events list', function() { @@ -67,6 +68,7 @@ describe('eightPodAnalyticAdapter', function() { beforeEach(function() { setupPageStub = sandbox.stub(eightPodAnalytics, 'setupPage'); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -79,16 +81,18 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 0); - expect(context).to.deep.equal(undefined) + expect(analyticsAdapter.getContext()).to.deep.equal({}) }); it('should call setup page and get context', function() { eightPodAnalytics.track({ eventType: BID_WON, args: { + adUnitCode: 'adUnitCode', bidder: 'eightPod', creativeId: 'creativeId', seatBidId: 'seatBidId', + cid: 'campaignId', params: [ { publisherId: 'publisherId', @@ -99,23 +103,27 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 1); - expect(context).to.deep.equal({ - bidId: 'seatBidId', - campaignId: 'campaignId', - placementId: 'placementId', - publisherId: 'publisherId', - variantId: 'creativeId' + expect(analyticsAdapter.getContext()).to.deep.equal({ + adUnitCode: { + bidId: 'seatBidId', + campaignId: 'campaignId', + placementId: 'placementId', + publisherId: 'publisherId', + variantId: 'creativeId' + } }) }); }); describe('trackEvent', function() { let getContextStub, getTimeStub; + const adUnitCode = 'adUnitCode'; beforeEach(function() { getContextStub = sandbox.stub(eightPodAnalytics, 'getContext'); getTimeStub = sandbox.stub(Date.prototype, 'getTime').returns(1234); eightPodAnalytics.resetQueue(); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -124,7 +132,7 @@ describe('eightPodAnalyticAdapter', function() { }); it('should add event to the queue', function() { - getContextStub.returns({}); + getContextStub.returns({adUnitCode: {}}); const event1 = { detail: { @@ -169,9 +177,10 @@ describe('eightPodAnalyticAdapter', function() { value: 2 } } - trackEvent(event1) + + trackEvent(event1, adUnitCode) expect(queue).to.deep.equal([result1]); - trackEvent(event2); + trackEvent(event2, adUnitCode); expect(queue).to.deep.equal([result1, result2]); }); });