Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core & PBS Adapter: support eventtrackers, and normalize burl / ext.prebid.events.win into it #12711

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions libraries/ortbConverter/processors/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export const DEFAULT_PROCESSORS = {
if (bid.attr) {
bidResponse.meta.attr = bid.attr;
}
if (bid.ext?.eventtrackers) {
bidResponse.eventtrackers = (bidResponse.eventtrackers ?? []).concat(bid.ext.eventtrackers);
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions libraries/pbsExtensions/processors/eventTrackers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {EVENT_TYPE_IMPRESSION, EVENT_TYPE_WIN, TRACKER_METHOD_IMG} from '../../../src/eventTrackers.js';

export function addEventTrackers(bidResponse, bid) {
bidResponse.eventtrackers = bidResponse.eventtrackers || [];
[
[bid.burl, EVENT_TYPE_IMPRESSION], // core used to fire burl directly, but only for bids coming from PBS
[bid?.ext?.prebid?.events?.win, EVENT_TYPE_WIN]
].filter(([winUrl, type]) => winUrl && bidResponse.eventtrackers.find(
({method, event, url}) => event === type && method === TRACKER_METHOD_IMG && url === winUrl
) == null)
.forEach(([url, event]) => {
bidResponse.eventtrackers.push({
method: TRACKER_METHOD_IMG,
event,
url
})
})
}
12 changes: 4 additions & 8 deletions libraries/pbsExtensions/processors/pbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {setImpBidParams} from './params.js';
import {setImpAdUnitCode} from './adUnitCode.js';
import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js';
import {setBidResponseVideoCache} from './video.js';
import {addEventTrackers} from './eventTrackers.js';

export const PBS_PROCESSORS = {
[REQUEST]: {
Expand Down Expand Up @@ -74,14 +75,9 @@ export const PBS_PROCESSORS = {
bidResponse.meta = mergeDeep({}, deepAccess(bid, 'ext.prebid.meta'), bidResponse.meta);
}
},
pbsWurl: {
// sets bidResponse.pbsWurl from ext.prebid.events.win
fn(bidResponse, bid) {
const wurl = deepAccess(bid, 'ext.prebid.events.win');
if (isStr(wurl)) {
bidResponse.pbsWurl = wurl;
}
}
pbsWinTrackers: {
// converts "legacy" burl and ext.prebid.events.win into eventtrackers
fn: addEventTrackers
},
},
[RESPONSE]: {
Expand Down
64 changes: 0 additions & 64 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,64 +364,6 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) {
});
}

/**
* map wurl to auction id and adId for use in the BID_WON event
*/
let wurlMap = {};

/**
* @param {string} auctionId
* @param {string} adId generated value set to bidObject.adId by bidderFactory Bid()
* @param {string} wurl events.winurl passed from prebidServer as wurl
*/
function addWurl(auctionId, adId, wurl) {
if ([auctionId, adId].every(isStr)) {
wurlMap[`${auctionId}${adId}`] = wurl;
}
}

/**
* @param {string} auctionId
* @param {string} adId generated value set to bidObject.adId by bidderFactory Bid()
*/
function removeWurl(auctionId, adId) {
if ([auctionId, adId].every(isStr)) {
wurlMap[`${auctionId}${adId}`] = undefined;
}
}
/**
* @param {string} auctionId
* @param {string} adId generated value set to bidObject.adId by bidderFactory Bid()
* @return {(string|undefined)} events.winurl which was passed as wurl
*/
function getWurl(auctionId, adId) {
if ([auctionId, adId].every(isStr)) {
return wurlMap[`${auctionId}${adId}`];
}
}

/**
* remove all cached wurls
*/
export function resetWurlMap() {
wurlMap = {};
}

/**
* BID_WON event to request the wurl
* @param {Bid} bid the winning bid object
*/
function bidWonHandler(bid) {
const wurl = getWurl(bid.auctionId, bid.adId);
if (isStr(wurl)) {
logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`);
triggerPixel(wurl);

// remove from wurl cache, since the wurl url was called
removeWurl(bid.auctionId, bid.adId);
}
}

function getMatchingConsentUrl(urlProp, gdprConsent) {
const hasPurpose = hasPurpose1Consent(gdprConsent);
const url = hasPurpose ? urlProp.p1Consent : urlProp.noP1Consent
Expand Down Expand Up @@ -516,9 +458,6 @@ export function PrebidServer() {
} else {
if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) {
addBidResponse(adUnit, bid);
if (bid.pbsWurl) {
addWurl(bid.auctionId, bid.adId, bid.pbsWurl);
}
} else {
addBidResponse.reject(adUnit, bid, REJECTION_REASON.INVALID);
}
Expand All @@ -533,9 +472,6 @@ export function PrebidServer() {
}
};

// Listen for bid won to call wurl
events.on(EVENTS.BID_WON, bidWonHandler);

return Object.assign(this, {
callBids: baseAdapter.callBids,
setBidderCode: baseAdapter.setBidderCode,
Expand Down
5 changes: 4 additions & 1 deletion src/adRendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
insertElement,
logError,
logWarn,
replaceMacros
replaceMacros, triggerPixel
} from './utils.js';
import * as events from './events.js';
import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS, MESSAGES, PB_LOCATOR} from './constants.js';
Expand All @@ -20,6 +20,7 @@ import {GreedyPromise} from './utils/promise.js';
import adapterManager from './adapterManager.js';
import {useMetrics} from './utils/perfMetrics.js';
import {filters} from './targeting.js';
import {EVENT_TYPE_WIN, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js';

const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS;
const { EXCEPTION } = AD_RENDER_FAILED_REASON;
Expand All @@ -31,6 +32,8 @@ export const getBidToRender = hook('sync', function (adId, forRender = true, ove
})

export const markWinningBid = hook('sync', function (bid) {
(parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_WIN]?.[TRACKER_METHOD_IMG] || [])
.forEach(url => triggerPixel(url));
events.emit(BID_WON, bid);
auctionManager.addWinningBid(bid);
})
Expand Down
6 changes: 3 additions & 3 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {isActivityAllowed} from './activities/rules.js';
import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from './activities/activities.js';
import {ACTIVITY_PARAM_ANL_CONFIG, ACTIVITY_PARAM_S2S_NAME, activityParamsBuilder} from './activities/params.js';
import {redactor} from './activities/redactor.js';
import {EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js';

export {gdprDataHandler, gppDataHandler, uspDataHandler, coppaDataHandler} from './consentHandler.js';

Expand Down Expand Up @@ -689,9 +690,8 @@ adapterManager.triggerBilling = (() => {
return (bid) => {
if (!BILLED.has(bid)) {
BILLED.add(bid);
if (bid.source === S2S.SRC && bid.burl) {
internal.triggerPixel(bid.burl);
}
(parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_IMPRESSION]?.[TRACKER_METHOD_IMG] || [])
.forEach((url) => internal.triggerPixel(url));
tryCallBidderMethod(bid.bidder, 'onBidBillable', bid);
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/eventTrackers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const TRACKER_METHOD_IMG = 1;
export const TRACKER_METHOD_JS = 2;
export const EVENT_TYPE_IMPRESSION = 1;
export const EVENT_TYPE_WIN = 500;

/**
* Returns a map from event type (EVENT_TYPE_*)
* to a map from tracker method (TRACKER_METHOD_*)
* to an array of tracking URLs
*
* @param {{}[]} eventTrackers an array of "Event Tracker Response Object" as defined
* in the ORTB native 1.2 spec (https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf, section 5.8)
* @returns {{[type: string]: {[method: string]: string[]}}}
*/
export function parseEventTrackers(eventTrackers) {
return (eventTrackers ?? []).reduce((tally, {event, method, url}) => {
const trackersForType = tally[event] = tally[event] ?? {};
const trackersForMethod = trackersForType[method] = trackersForType[method] ?? [];
trackersForMethod.push(url);
return tally;
}, {})
}
35 changes: 8 additions & 27 deletions src/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB,
import {NATIVE} from './mediaTypes.js';
import {getRenderingData} from './adRendering.js';
import {getCreativeRendererSource} from './creativeRenderers.js';
import {EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG, TRACKER_METHOD_JS} from './eventTrackers.js';

/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
Expand Down Expand Up @@ -89,20 +90,6 @@ const SUPPORTED_TYPES = {
const PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE = inverse(PREBID_NATIVE_DATA_KEYS_TO_ORTB);
const NATIVE_ASSET_TYPES_INVERSE = inverse(NATIVE_ASSET_TYPES);

const TRACKER_METHODS = {
img: 1,
js: 2,
1: 'img',
2: 'js'
}

const TRACKER_EVENTS = {
impression: 1,
'viewable-mrc50': 2,
'viewable-mrc100': 3,
'viewable-video50': 4,
}

export function isNativeResponse(bidResponse) {
// check for native data and not mediaType; it's possible
// to treat banner responses as native
Expand Down Expand Up @@ -289,15 +276,9 @@ export function fireNativeTrackers(message, bidResponse) {
}

export function fireImpressionTrackers(nativeResponse, {runMarkup = (mkup) => insertHtmlIntoIframe(mkup), fetchURL = triggerPixel} = {}) {
const impTrackers = (nativeResponse.eventtrackers || [])
.filter(tracker => tracker.event === TRACKER_EVENTS.impression);

let {img, js} = impTrackers.reduce((tally, tracker) => {
if (TRACKER_METHODS.hasOwnProperty(tracker.method)) {
tally[TRACKER_METHODS[tracker.method]].push(tracker.url)
}
return tally;
}, {img: [], js: []});
let {[TRACKER_METHOD_IMG]: img = [], [TRACKER_METHOD_JS]: js = []} = parseEventTrackers(
nativeResponse.eventtrackers || []
)[EVENT_TYPE_IMPRESSION] || {};

if (nativeResponse.imptrackers) {
img = img.concat(nativeResponse.imptrackers);
Expand Down Expand Up @@ -726,8 +707,8 @@ export function legacyPropertiesToOrtbNative(legacyNative) {
case 'impressionTrackers':
(Array.isArray(value) ? value : [value]).forEach(url => {
response.eventtrackers.push({
event: TRACKER_EVENTS.impression,
method: TRACKER_METHODS.img,
event: EVENT_TYPE_IMPRESSION,
method: TRACKER_METHOD_IMG,
url
});
});
Expand Down Expand Up @@ -830,10 +811,10 @@ export function toLegacyResponse(ortbResponse, ortbRequest) {
legacyResponse.impressionTrackers.push(...ortbResponse.imptrackers);
}
for (const eventTracker of ortbResponse?.eventtrackers || []) {
if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.img) {
if (eventTracker.event === EVENT_TYPE_IMPRESSION && eventTracker.method === TRACKER_METHOD_IMG) {
legacyResponse.impressionTrackers.push(eventTracker.url);
}
if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.js) {
if (eventTracker.event === EVENT_TYPE_IMPRESSION && eventTracker.method === TRACKER_METHOD_JS) {
jsTrackers.push(eventTracker.url);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -929,10 +929,12 @@ if (FEATURES.VIDEO) {
* @typedef {Object} MarkBidRequest
* @property {string} adUnitCode The ad unit code
* @property {string} adId The id representing the ad we want to mark
* @property {boolean} events If true, fires tracking pixels and BID_WON handlers
* @property {boolean} analytics alias of `events` (for backwards compat)
*
* @alias module:pbjs.markWinningBidAsUsed
*/
pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false}) {
pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false, events = false}) {
let bids;
if (adUnitCode && adId == null) {
bids = targeting.getWinningBids(adUnitCode);
Expand All @@ -942,7 +944,7 @@ if (FEATURES.VIDEO) {
logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.');
}
if (bids.length > 0) {
if (analytics) {
if (analytics || events) {
markWinningBid(bids[0]);
} else {
auctionManager.addWinningBid(bids[0]);
Expand Down
Loading