diff --git a/gulpfile.js b/gulpfile.js
index d7b91db4ade..ac8b8c2dcd5 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -286,7 +286,7 @@ function test(done) {
} else {
var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file);
- var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase);
+ var browserOverride = helpers.parseBrowserArgs(argv);
if (browserOverride.length > 0) {
karmaConf.browsers = browserOverride;
}
diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html
new file mode 100644
index 00000000000..33f8b9be6a2
--- /dev/null
+++ b/integrationExamples/gpt/adloox.html
@@ -0,0 +1,211 @@
+
+
+
+ Prebid Display/Video Merged Auction with Adloox Integration
+
+
+
+
+
+
+
+
+ Prebid Display/Video Merged Auction with Adloox Integration
+
+ div-1
+
+
+
+
+ div-2
+
+
+
+
+ video-1
+
+
+
+
+
diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html
index fae5ca2b539..973c295325a 100644
--- a/integrationExamples/gpt/userId_example.html
+++ b/integrationExamples/gpt/userId_example.html
@@ -197,13 +197,16 @@
}, {
name: "merkleId",
params: {
- ptk: '12345678-aaaa-bbbb-cccc-123456789abc', //Set your real merkle partner key here
- pubid: 'EXAMPLE' //Set your real merkle publisher id here
- },
+ vendor:'sdfg',
+ sv_cid:'dfg',
+ sv_pubid:'xcv',
+ sv_domain:'zxv'
+ }
+ ,
storage: {
- type: "html5",
- name: "merkleId",
- expires: 30
+ type: "html5",
+ name: "merkleId",
+ expires: 30
},
},{
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 0f62627822a..f71325d38a9 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -16,13 +16,16 @@
"zeotapIdPlusIdSystem",
"haloIdSystem",
"quantcastIdSystem",
+ "nextrollIdSystem",
"idxIdSystem",
"fabrickIdSystem",
"verizonMediaIdSystem",
"pubProvidedIdSystem",
"mwOpenLinkIdSystem",
"tapadIdSystem",
- "novatiqIdSystem"
+ "novatiqIdSystem",
+ "uid2IdSystem",
+ "admixerIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js
index 4fae85e82ad..b9dbae529ba 100644
--- a/modules/adheseBidAdapter.js
+++ b/modules/adheseBidAdapter.js
@@ -34,6 +34,7 @@ export const spec = {
const payload = {
slots: slots,
parameters: commonParams,
+ vastContentAsUrl: true,
user: {
ext: {
eids: getEids(validBidRequests),
@@ -95,7 +96,7 @@ function adResponse(bid, ad) {
const bidResponse = getbaseAdResponse({
requestId: bid.bidId,
- mediaType: getMediaType(markup),
+ mediaType: ad.extension.mediaType,
cpm: Number(price.amount),
currency: price.currency,
width: Number(ad.width),
@@ -110,7 +111,11 @@ function adResponse(bid, ad) {
});
if (bidResponse.mediaType === VIDEO) {
- bidResponse.vastXml = markup;
+ if (ad.cachedBodyUrl) {
+ bidResponse.vastUrl = ad.cachedBodyUrl
+ } else {
+ bidResponse.vastXml = markup;
+ }
} else {
const counter = ad.impressionCounter ? "" : '';
bidResponse.ad = markup + counter;
@@ -172,11 +177,6 @@ function isAdheseAd(ad) {
return !ad.origin || ad.origin === 'JERLICIA';
}
-function getMediaType(markup) {
- const isVideo = markup.trim().toLowerCase().match(/<\?xml| {
adapterManager.registerAnalyticsAdapter({
adapter: analyticsAdapter,
- code: 'adkernelAdn'
+ code: 'adkernelAdn',
+ gvlid: GVLID
});
export default analyticsAdapter;
@@ -390,3 +395,19 @@ function getLocationAndReferrer(win) {
loc: win.location
};
}
+
+function initPrivacy(template, requests) {
+ let consent = requests[0].gdprConsent;
+ if (consent && consent.gdprApplies) {
+ template.user.gdpr = ~~consent.gdprApplies;
+ }
+ if (consent && consent.consentString) {
+ template.user.gdpr_consent = consent.consentString;
+ }
+ if (requests[0].uspConsent) {
+ template.user.us_privacy = requests[0].uspConsent;
+ }
+ if (config.getConfig('coppa')) {
+ template.user.coppa = 1;
+ }
+}
diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js
index 0a0317e1f59..c838d93b8f1 100644
--- a/modules/adkernelAdnBidAdapter.js
+++ b/modules/adkernelAdnBidAdapter.js
@@ -1,11 +1,13 @@
import * as utils from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
const DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com';
const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript'];
const DEFAULT_PROTOCOLS = [2, 3, 5, 6];
const DEFAULT_APIS = [1, 2];
+const GVLID = 14;
function isRtbDebugEnabled(refInfo) {
return refInfo.referer.indexOf('adk_debug=true') !== -1;
@@ -67,6 +69,9 @@ function buildRequestParams(tags, bidderRequest) {
if (uspConsent) {
utils.deepSetValue(req, 'user.us_privacy', uspConsent);
}
+ if (config.getConfig('coppa')) {
+ utils.deepSetValue(req, 'user.coppa', 1);
+ }
return req;
}
@@ -110,6 +115,7 @@ function buildBid(tag) {
export const spec = {
code: 'adkernelAdn',
+ gvlid: GVLID,
supportedMediaTypes: [BANNER, VIDEO],
aliases: ['engagesimply'],
diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 03fa5c2b2d9..b6d82aec976 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -22,6 +22,7 @@ const SYNC_TYPES = Object.freeze({
1: 'iframe',
2: 'image'
});
+const GVLID = 14;
const NATIVE_MODEL = [
{name: 'title', assetType: 'title'},
@@ -50,8 +51,8 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => {
* Adapter for requesting bids from AdKernel white-label display platform
*/
export const spec = {
-
code: 'adkernel',
+ gvlid: GVLID,
aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm'],
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
@@ -320,7 +321,7 @@ function getAllowedSyncMethod(bidderCode) {
*/
function buildRtbRequest(imps, bidderRequest, schain) {
let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest;
-
+ let coppa = config.getConfig('coppa');
let req = {
'id': auctionId,
'imp': imps,
@@ -349,6 +350,9 @@ function buildRtbRequest(imps, bidderRequest, schain) {
if (uspConsent) {
utils.deepSetValue(req, 'regs.ext.us_privacy', uspConsent);
}
+ if (coppa) {
+ utils.deepSetValue(req, 'regs.coppa', 1);
+ }
let syncMethod = getAllowedSyncMethod(bidderCode);
if (syncMethod) {
utils.deepSetValue(req, 'ext.adk_usersync', syncMethod);
diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js
new file mode 100644
index 00000000000..3e92ae34004
--- /dev/null
+++ b/modules/adlooxAnalyticsAdapter.js
@@ -0,0 +1,288 @@
+/**
+ * This module provides [Adloox]{@link https://www.adloox.com/} Analytics
+ * The module will inject Adloox's verification JS tag alongside slot at bidWin
+ * @module modules/adlooxAnalyticsAdapter
+ */
+
+import adapterManager from '../src/adapterManager.js';
+import adapter from '../src/AnalyticsAdapter.js';
+import { loadExternalScript } from '../src/adloader.js';
+import { auctionManager } from '../src/auctionManager.js';
+import { AUCTION_COMPLETED } from '../src/auction.js';
+import { EVENTS } from '../src/constants.json';
+import find from 'core-js-pure/features/array/find.js';
+import * as utils from '../src/utils.js';
+
+const MODULE = 'adlooxAnalyticsAdapter';
+
+const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js';
+
+const ADLOOX_VENDOR_ID = 93;
+
+const ADLOOX_MEDIATYPE = {
+ DISPLAY: 2,
+ VIDEO: 6
+};
+
+const MACRO = {};
+MACRO['client'] = function(b, c) {
+ return c.client;
+};
+MACRO['clientid'] = function(b, c) {
+ return c.clientid;
+};
+MACRO['tagid'] = function(b, c) {
+ return c.tagid;
+};
+MACRO['platformid'] = function(b, c) {
+ return c.platformid;
+};
+MACRO['targetelt'] = function(b, c) {
+ return c.toselector(b);
+};
+MACRO['creatype'] = function(b, c) {
+ return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY;
+};
+MACRO['pbAdSlot'] = function(b, c) {
+ const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code);
+ return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode;
+};
+
+const PARAMS_DEFAULT = {
+ 'id1': function(b) { return b.adUnitCode },
+ 'id2': '%%pbAdSlot%%',
+ 'id3': function(b) { return b.bidder },
+ 'id4': function(b) { return b.adId },
+ 'id5': function(b) { return b.dealId },
+ 'id6': function(b) { return b.creativeId },
+ 'id7': function(b) { return b.size },
+ 'id11': '$ADLOOX_WEBSITE'
+};
+
+const NOOP = function() {};
+
+let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), {
+ track({ eventType, args }) {
+ if (!analyticsAdapter[`handle_${eventType}`]) return;
+
+ utils.logInfo(MODULE, 'track', eventType, args);
+
+ analyticsAdapter[`handle_${eventType}`](args);
+ }
+});
+
+analyticsAdapter.context = null;
+
+analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics;
+analyticsAdapter.enableAnalytics = function(config) {
+ analyticsAdapter.context = null;
+
+ utils.logInfo(MODULE, 'config', config);
+
+ if (!utils.isPlainObject(config.options)) {
+ utils.logError(MODULE, 'missing options');
+ return;
+ }
+ if (!(config.options.js === undefined || utils.isStr(config.options.js))) {
+ utils.logError(MODULE, 'invalid js options value');
+ return;
+ }
+ if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) {
+ utils.logError(MODULE, 'invalid toselector options value');
+ return;
+ }
+ if (!utils.isStr(config.options.client)) {
+ utils.logError(MODULE, 'invalid client options value');
+ return;
+ }
+ if (!utils.isNumber(config.options.clientid)) {
+ utils.logError(MODULE, 'invalid clientid options value');
+ return;
+ }
+ if (!utils.isNumber(config.options.tagid)) {
+ utils.logError(MODULE, 'invalid tagid options value');
+ return;
+ }
+ if (!utils.isNumber(config.options.platformid)) {
+ utils.logError(MODULE, 'invalid platformid options value');
+ return;
+ }
+ if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) {
+ utils.logError(MODULE, 'invalid params options value');
+ return;
+ }
+
+ analyticsAdapter.context = {
+ js: config.options.js || URL_JS,
+ toselector: config.options.toselector || function(bid) {
+ let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode;
+ // https://mathiasbynens.be/notes/css-escapes
+ code = code.replace(/^\d/, '\\3$& ');
+ return `#${code}`
+ },
+ client: config.options.client,
+ clientid: config.options.clientid,
+ tagid: config.options.tagid,
+ platformid: config.options.platformid,
+ params: []
+ };
+
+ config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {});
+ Object
+ .keys(config.options.params)
+ .forEach(k => {
+ if (!Array.isArray(config.options.params[k])) {
+ config.options.params[k] = [ config.options.params[k] ];
+ }
+ config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ]));
+ });
+
+ Object.keys(COMMAND_QUEUE).forEach(commandProcess);
+
+ analyticsAdapter.originEnableAnalytics(config);
+}
+
+analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics;
+analyticsAdapter.disableAnalytics = function() {
+ analyticsAdapter.context = null;
+
+ analyticsAdapter.originDisableAnalytics();
+}
+
+analyticsAdapter.url = function(url, args, bid) {
+ // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻
+ function a2qs(a) {
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
+ function fixedEncodeURIComponent(str) {
+ return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
+ return '%' + c.charCodeAt(0).toString(16);
+ });
+ }
+
+ const args = [];
+ let n = a.length;
+ while (n-- > 0) {
+ if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) {
+ args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : ''));
+ }
+ }
+
+ return args.join('&');
+ }
+
+ const macros = (str) => {
+ return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match);
+ };
+
+ url = macros(url);
+ args = args || [];
+
+ let n = args.length;
+ while (n-- > 0) {
+ if (utils.isFn(args[n][1])) {
+ try {
+ args[n][1] = args[n][1](bid);
+ } catch (_) {
+ utils.logError(MODULE, 'macro', args[n][0], _.message);
+ args[n][1] = `ERROR: ${_.message}`;
+ }
+ }
+ if (utils.isStr(args[n][1])) {
+ args[n][1] = macros(args[n][1]);
+ }
+ }
+
+ return url + a2qs(args);
+}
+
+analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) {
+ if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return;
+ analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP;
+
+ utils.logMessage(MODULE, 'preloading verification JS');
+
+ const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`));
+
+ const link = document.createElement('link');
+ link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`);
+ link.setAttribute('rel', 'preload');
+ link.setAttribute('as', 'script');
+ utils.insertElement(link);
+}
+
+analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) {
+ const sl = analyticsAdapter.context.toselector(bid);
+ let el;
+ try {
+ el = document.querySelector(sl);
+ } catch (_) { }
+ if (!el) {
+ utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`);
+ return;
+ }
+
+ utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`);
+
+ const params = analyticsAdapter.context.params.concat([
+ [ 'tagid', '%%tagid%%' ],
+ [ 'platform', '%%platformid%%' ],
+ [ 'fwtype', 4 ],
+ [ 'targetelt', '%%targetelt%%' ],
+ [ 'creatype', '%%creatype%%' ]
+ ]);
+
+ loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox');
+}
+
+adapterManager.registerAnalyticsAdapter({
+ adapter: analyticsAdapter,
+ code: 'adloox',
+ gvlid: ADLOOX_VENDOR_ID
+});
+
+export default analyticsAdapter;
+
+// src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻
+const COMMAND_QUEUE = {};
+export const COMMAND = {
+ CONFIG: 'config',
+ URL: 'url',
+ TRACK: 'track'
+};
+export function command(cmd, data, callback0) {
+ const cid = utils.getUniqueIdentifierStr();
+ const callback = function() {
+ delete COMMAND_QUEUE[cid];
+ if (callback0) callback0.apply(null, arguments);
+ };
+ COMMAND_QUEUE[cid] = { cmd, data, callback };
+ if (analyticsAdapter.context) commandProcess(cid);
+}
+function commandProcess(cid) {
+ const { cmd, data, callback } = COMMAND_QUEUE[cid];
+
+ utils.logInfo(MODULE, 'command', cmd, data);
+
+ switch (cmd) {
+ case COMMAND.CONFIG:
+ const response = {
+ client: analyticsAdapter.context.client,
+ clientid: analyticsAdapter.context.clientid,
+ tagid: analyticsAdapter.context.tagid,
+ platformid: analyticsAdapter.context.platformid
+ };
+ callback(response);
+ break;
+ case COMMAND.URL:
+ if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10
+ callback(analyticsAdapter.url(data.url, data.args, data.bid));
+ break;
+ case COMMAND.TRACK:
+ analyticsAdapter.track(data);
+ callback(); // drain queue
+ break;
+ default:
+ utils.logWarn(MODULE, 'command unknown', cmd);
+ // do not callback as arguments are unknown and to aid debugging
+ }
+}
diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md
new file mode 100644
index 00000000000..0ca67f937f6
--- /dev/null
+++ b/modules/adlooxAnalyticsAdapter.md
@@ -0,0 +1,146 @@
+# Overview
+
+ Module Name: Adloox Analytics Adapter
+ Module Type: Analytics Adapter
+ Maintainer: technique@adloox.com
+
+# Description
+
+Analytics adapter for adloox.com. Contact adops@adloox.com for information.
+
+This module can be used to track:
+
+ * Display
+ * Native
+ * Video (see below for further instructions)
+
+The adapter adds an HTML `