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

Mass Module: support multiple custom configs for dealId and rendering #6500

Merged
merged 5 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 113 additions & 60 deletions modules/mass.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,84 +6,128 @@ 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);
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
};

bid.ad = '<script>window.parent.postMessage({massBidId: "' + bid.requestId + '"}, "*");\x3c/script>';
// add any custom renderer defined in the config:
(cfg.custom || []).forEach(renderer => {
if (!renderer.match && renderer.dealIdPattern) {
renderer.match = useDefaultMatch(renderer.dealIdPattern);
}

addListenerOnce();
if (!renderer.render && renderer.renderUrl && renderer.namespace) {
renderer.render = useDefaultRender(renderer.renderUrl, renderer.namespace);
}

next(adUnitCode, bid);
if (renderer.match && renderer.render) {
renderers.push(renderer);
}
});
}

/**
* 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 +136,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