Skip to content

Commit

Permalink
Mass Deal Rendering Module: support multiple custom configs for dealI…
Browse files Browse the repository at this point in the history
…d and rendering (#6500)
  • Loading branch information
cciocov authored Apr 9, 2021
1 parent 6af2f1e commit f48f978
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 87 deletions.
24 changes: 23 additions & 1 deletion integrationExamples/mass/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,30 @@
pbjs.setConfig({
mass: {
enabled: true,

// official MASS-supported config:
dealIdPattern: /^MASS/i,
renderUrl: 'https://cdn.massplatform.net/bootloader.js',
dealIdPattern: /^MASS/i

// custom configs:
custom: [
// simple:
{
dealIdPattern: /^abc/i,
renderUrl: 'https://my.domain.com/script.js'
},

// flexible:
{
match: function(bid) {
// return true or false, based on given bid
},

render: function(payload) {
// render the ad
}
}
]
}
});
});
Expand Down
173 changes: 114 additions & 59 deletions modules/mass.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,84 +6,130 @@ import { config } from '../src/config.js';
import { getHook } from '../src/hook.js';
import find from 'core-js-pure/features/array/find.js';

export let listenerAdded = false;
export let massEnabled = false;

const defaultCfg = {
dealIdPattern: /^MASS/i
};
let cfg;

const massBids = {};
export let listenerAdded = false;
export let isEnabled = false;

const matchedBids = {};
let renderers;

init();
config.getConfig('mass', config => init(config.mass));

/**
* Module init.
*/
export function init(customCfg) {
cfg = Object.assign({}, defaultCfg, customCfg);
export function init(userCfg) {
cfg = Object.assign({}, defaultCfg, window.massConfig && window.massConfig.mass, userCfg);

if (cfg.enabled === false) {
if (massEnabled) {
massEnabled = false;
if (isEnabled) {
getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove();
isEnabled = false;
}
} else {
if (!massEnabled) {
if (!isEnabled) {
getHook('addBidResponse').before(addBidResponseHook);
massEnabled = true;
isEnabled = true;
}
}

if (isEnabled) {
updateRenderers();
}
}

/**
* Before hook for 'addBidResponse'.
* Update the list of renderers based on current config.
*/
export function addBidResponseHook(next, adUnitCode, bid) {
if (!isMassBid(bid) || !cfg.renderUrl) {
return next(adUnitCode, bid);
export function updateRenderers() {
renderers = [];

// official MASS renderer:
if (cfg.dealIdPattern && cfg.renderUrl) {
renderers.push({
match: isMassBid,
render: useDefaultRender(cfg.renderUrl, 'mass')
});
}

const bidRequest = find(this.bidderRequest.bids, bidRequest =>
bidRequest.bidId === bid.requestId
);

massBids[bid.requestId] = {
bidRequest,
bid,
adm: bid.ad
};
// add any custom renderer defined in the config:
(cfg.custom || []).forEach(renderer => {
if (!renderer.match && renderer.dealIdPattern) {
renderer.match = useDefaultMatch(renderer.dealIdPattern);
}

bid.ad = '<script>window.parent.postMessage({massBidId: "' + bid.requestId + '"}, "*");\x3c/script>';
if (!renderer.render && renderer.renderUrl && renderer.namespace) {
renderer.render = useDefaultRender(renderer.renderUrl, renderer.namespace);
}

addListenerOnce();
if (renderer.match && renderer.render) {
renderers.push(renderer);
}
});

next(adUnitCode, bid);
return renderers;
}

/**
* Check if a bid is MASS.
* Before hook for 'addBidResponse'.
*/
export function isMassBid(bid) {
// either bid.meta.mass is set or deal ID matches a publisher specified pattern:
if (!((bid.meta && bid.meta.mass) || (cfg.dealIdPattern && cfg.dealIdPattern.test(bid.dealId)))) {
return false;
export function addBidResponseHook(next, adUnitCode, bid) {
let renderer;
for (let i = 0; i < renderers.length; i++) {
if (renderers[i].match(bid)) {
renderer = renderers[i];
break;
}
}

// there must be a 'mass://' or 'pcreative?' in the ad markup:
return /mass:\/\/|\/pcreative\?/i.test(bid.ad);
if (renderer) {
const bidRequest = find(this.bidderRequest.bids, bidRequest =>
bidRequest.bidId === bid.requestId
);

matchedBids[bid.requestId] = {
renderer,
payload: {
bidRequest,
bid,
adm: bid.ad
}
};

bid.ad = '<script>window.parent.postMessage({massBidId: "' + bid.requestId + '"}, "*");\x3c/script>';

addListenerOnce();
}

next(adUnitCode, bid);
}

/**
* Add listener to detect requests to render MASS ads.
* Add listener for the "message" event sent by the winning bid
*/
export function addListenerOnce() {
if (!listenerAdded) {
window.addEventListener('message', e => {
if (e && e.data && e.data.massBidId) {
render(getRenderPayload(e));
if (!e || !e.data || !e.data.massBidId) {
return;
}

const matchedBid = matchedBids[e.data.massBidId];
if (matchedBid) {
const payload = {
type: 'prebid',
event: e,
...matchedBid.payload
};

delete payload.bid.ad;

matchedBid.renderer.render(payload);
}
});

Expand All @@ -92,38 +138,47 @@ export function addListenerOnce() {
}

/**
* Prepare payload for render.
* Check if a bid is MASS.
*/
export function getRenderPayload(e) {
const payload = {
type: 'prebid',
e
};
export function isMassBid(bid) {
// either bid.meta.mass is set or deal ID matches a publisher specified pattern:
if (!((bid.meta && bid.meta.mass) || (cfg.dealIdPattern && cfg.dealIdPattern.test(bid.dealId)))) {
return false;
}

Object.assign(payload, massBids[e.data.massBidId]);
delete payload.bid.ad;
// there must be a 'mass://' or 'pcreative?' in the ad markup:
return /mass:\/\/|\/pcreative\?/i.test(bid.ad);
}

return payload;
/**
* Default match (factory).
*/
export function useDefaultMatch(dealIdPattern) {
return function(bid) {
return dealIdPattern.test(bid.dealId);
};
}

/**
* Render a MASS ad.
* Default render (factory).
*/
export function render(payload) {
const ns = window.mass = window.mass || {};
export function useDefaultRender(renderUrl, namespace) {
return function render(payload) {
const ns = window[namespace] = window[namespace] || {};
ns.queue = ns.queue || [];

ns.bootloader = ns.bootloader || {queue: []};
ns.bootloader.queue.push(payload);
ns.queue.push(payload);

if (!ns.bootloader.loaded) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = cfg.renderUrl;
if (!ns.loaded) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = renderUrl;

const x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
const x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);

ns.bootloader.loaded = true;
}
ns.loaded = true;
}
};
}
Loading

0 comments on commit f48f978

Please sign in to comment.