Skip to content

Commit f3fadda

Browse files
committed
Merge branch 'master' into prebid-8
2 parents 4320362 + 27458d1 commit f3fadda

29 files changed

+1257
-409
lines changed

integrationExamples/gpt/userId_example.html

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,17 +251,31 @@
251251
{
252252
"name": "uid2",
253253
"params": {
254+
"uid2ApiBase": "https://operator-integ.uidapi.com", // Omit this setting for production
254255
"uid2Token": {
255-
"advertising_token": "example token",
256-
"refresh_token": "aslkdjaslkjdaslkhj",
257-
"identity_expires": Date.now() + 60*1000,
256+
"advertising_token": "advertising token goes here",
257+
"refresh_token": "refresh token goes here",
258+
"identity_expires": Date.now() + 60*1000, // These timestamps should be from the token generate response
258259
"refresh_from": Date.now() - 10*1000,
259260
"refresh_expires": Date.now() + 12*60*60*1000,
260-
"refresh_response_key": null
261+
"refresh_response_key": "refresh key goes here"
261262
}
262263
}
263-
}
264-
,
264+
},
265+
{
266+
"name": "euid",
267+
"params": {
268+
"euidApiBase": "https://integ.euid.eu", // Omit this setting for production
269+
"euidToken": {
270+
"advertising_token": "advertising token goes here",
271+
"refresh_token": "refresh token goes here",
272+
"identity_expires": Date.now() + 60*1000, // These timestamps should be from the token generate response
273+
"refresh_from": Date.now() - 10*1000,
274+
"refresh_expires": Date.now() + 12*60*60*1000,
275+
"refresh_response_key": "refresh key goes here"
276+
}
277+
}
278+
},
265279
{
266280
"name": "imuid",
267281
"params": {

modules/.submodules.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"tncIdSystem",
4141
"utiqSystem",
4242
"uid2IdSystem",
43+
"euidIdSystem",
4344
"unifiedIdSystem",
4445
"verizonMediaIdSystem",
4546
"zeotapIdPlusIdSystem",

modules/adagioBidAdapter.js

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js';
4444
const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';
4545
const GVLID = 617;
4646
export const storage = getStorageManager({bidderCode: BIDDER_CODE});
47-
export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js';
47+
48+
const BB_PUBLICATION = 'adagio';
49+
const BB_RENDERER_DEFAULT = 'renderer';
50+
export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.js`;
51+
4852
const MAX_SESS_DURATION = 30 * 60 * 1000;
4953
const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp';
5054
const ADAGIO_PUBKEY_E = 65537;
@@ -62,7 +66,7 @@ export const ORTB_VIDEO_PARAMS = {
6266
'startdelay': (value) => isInteger(value),
6367
'placement': (value) => isInteger(value),
6468
'linearity': (value) => isInteger(value),
65-
'skip': (value) => isInteger(value),
69+
'skip': (value) => [1, 0].includes(value),
6670
'skipmin': (value) => isInteger(value),
6771
'skipafter': (value) => isInteger(value),
6872
'sequence': (value) => isInteger(value),
@@ -443,16 +447,6 @@ function _buildVideoBidRequest(bidRequest) {
443447
});
444448
}
445449

446-
function _renderer(bid) {
447-
bid.renderer.push(() => {
448-
if (typeof window.ADAGIO.outstreamPlayer === 'function') {
449-
window.ADAGIO.outstreamPlayer(bid);
450-
} else {
451-
logError(`${LOG_PREFIX} Adagio outstream player is not defined`);
452-
}
453-
});
454-
}
455-
456450
function _parseNativeBidResponse(bid) {
457451
if (!bid.admNative || !Array.isArray(bid.admNative.assets)) {
458452
logError(`${LOG_PREFIX} Invalid native response`);
@@ -882,6 +876,77 @@ function storeRequestInAdagioNS(bidRequest) {
882876
};
883877
}
884878

879+
// See https://support.bluebillywig.com/developers/vast-renderer/
880+
const OUTSTREAM_RENDERER = {
881+
bootstrapPlayer: function(bid) {
882+
const rendererCode = bid.outstreamRendererCode;
883+
884+
const config = {
885+
code: bid.adUnitCode,
886+
};
887+
888+
if (bid.vastXml) {
889+
config.vastXml = bid.vastXml;
890+
} else if (bid.vastUrl) {
891+
config.vastUrl = bid.vastUrl;
892+
}
893+
894+
if (!bid.vastXml && !bid.vastUrl) {
895+
logError(`${LOG_PREFIX} no vastXml or vastUrl on bid`);
896+
return;
897+
}
898+
899+
if (!window.bluebillywig || !window.bluebillywig.renderers || !window.bluebillywig.renderers.length) {
900+
logError(`${LOG_PREFIX} no BlueBillywig renderers found!`);
901+
return;
902+
}
903+
904+
const rendererId = this.getRendererId(BB_PUBLICATION, rendererCode);
905+
906+
const override = {}
907+
if (bid.skipOffset) {
908+
override.skipOffset = bid.skipOffset.toString()
909+
}
910+
911+
const renderer = window.bluebillywig.renderers.find(bbr => bbr._id === rendererId);
912+
if (!renderer) {
913+
logError(`${LOG_PREFIX} couldn't find a renderer with ID ${rendererId}`);
914+
return;
915+
}
916+
917+
const el = document.getElementById(bid.adUnitCode);
918+
919+
renderer.bootstrap(config, el, override);
920+
},
921+
newRenderer: function(adUnitCode, rendererCode) {
922+
const rendererUrl = BB_RENDERER_URL.replace('$RENDERER', rendererCode);
923+
924+
const renderer = Renderer.install({
925+
url: rendererUrl,
926+
loaded: false,
927+
adUnitCode
928+
});
929+
930+
try {
931+
renderer.setRender(this.outstreamRender);
932+
} catch (err) {
933+
logError(`${LOG_PREFIX} error trying to setRender`, err);
934+
}
935+
936+
return renderer;
937+
},
938+
outstreamRender: function(bid) {
939+
bid.renderer.push(() => {
940+
OUTSTREAM_RENDERER.bootstrapPlayer(bid)
941+
});
942+
},
943+
getRendererId: function(publication, renderer) {
944+
// By convention, the RENDERER_ID is always the publication name (adagio) and the ad unit code (eg. renderer)
945+
// joined together by a dash. It's used to identify the correct renderer instance on the page in case there's multiple.
946+
return `${publication}-${renderer}`;
947+
}
948+
};
949+
885950
export const spec = {
886951
code: BIDDER_CODE,
887952
gvlid: GVLID,
@@ -1111,21 +1176,18 @@ export const spec = {
11111176
const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context');
11121177
// Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`.
11131178
if (!bidObj.vastUrl && bidObj.vastXml) {
1114-
bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + btoa(bidObj.vastXml.replace(/\\"/g, '"'));
1179+
bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidObj.vastXml.replace(/\\"/g, '"'));
11151180
}
11161181

11171182
if (mediaTypeContext === OUTSTREAM) {
1118-
bidObj.renderer = Renderer.install({
1119-
id: bidObj.requestId,
1120-
adUnitCode: bidObj.adUnitCode,
1121-
url: bidObj.urlRenderer || RENDERER_URL,
1122-
config: {
1123-
...deepAccess(bidReq, 'mediaTypes.video'),
1124-
...deepAccess(bidObj, 'outstream', {})
1125-
}
1126-
});
1127-
1128-
bidObj.renderer.setRender(_renderer);
1183+
bidObj.outstreamRendererCode = deepAccess(bidReq, 'params.rendererCode', BB_RENDERER_DEFAULT)
1184+
1185+
if (deepAccess(bidReq, 'mediaTypes.video.skip')) {
1186+
const skipOffset = deepAccess(bidReq, 'mediaTypes.video.skipafter', 5) // default 5s.
1187+
bidObj.skipOffset = skipOffset
1188+
}
1189+
1190+
bidObj.renderer = OUTSTREAM_RENDERER.newRenderer(bidObj.adUnitCode, bidObj.outstreamRendererCode);
11291191
}
11301192
}
11311193

modules/airgridRtdProvider.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import {
1212
deepAccess,
1313
} from '../src/utils.js';
1414
import { getGlobal } from '../src/prebidGlobal.js';
15-
import {getStorageManager} from '../src/storageManager.js';
16-
import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
15+
import { getStorageManager } from '../src/storageManager.js';
16+
import { loadExternalScript } from '../src/adloader.js';
17+
import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
1718

1819
const MODULE_NAME = 'realTimeData';
1920
const SUBMODULE_NAME = 'airgrid';
@@ -25,6 +26,11 @@ export const storage = getStorageManager({
2526
moduleName: SUBMODULE_NAME,
2627
});
2728

29+
function getModuleUrl(accountId) {
30+
const path = accountId ?? 'sdk';
31+
return `https://cdn.edkt.io/${path}/edgekit.min.js`;
32+
}
33+
2834
/**
2935
* Attach script tag to DOM
3036
* @param {Object} rtdConfig
@@ -33,19 +39,12 @@ export const storage = getStorageManager({
3339
export function attachScriptTagToDOM(rtdConfig) {
3440
var edktInitializor = (window.edktInitializor = window.edktInitializor || {});
3541
if (!edktInitializor.invoked) {
36-
edktInitializor.invoked = true;
3742
edktInitializor.accountId = rtdConfig.params.accountId;
3843
edktInitializor.publisherId = rtdConfig.params.publisherId;
3944
edktInitializor.apiKey = rtdConfig.params.apiKey;
40-
edktInitializor.load = function (e) {
41-
var p = e || 'sdk';
42-
var n = document.createElement('script');
43-
n.type = 'module';
44-
n.async = true;
45-
n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js';
46-
document.getElementsByTagName('head')[0].appendChild(n);
47-
};
48-
edktInitializor.load(edktInitializor.accountId);
45+
edktInitializor.invoked = true;
46+
const moduleSrc = getModuleUrl(rtdConfig.params.accountId);
47+
loadExternalScript(moduleSrc, SUBMODULE_NAME);
4948
}
5049
}
5150

modules/criteoBidAdapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const LOG_PREFIX = 'Criteo: ';
2828
Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js
2929
*/
3030
const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%';
31-
export const FAST_BID_VERSION_CURRENT = 135;
31+
export const FAST_BID_VERSION_CURRENT = 136;
3232
const FAST_BID_VERSION_LATEST = 'latest';
3333
const FAST_BID_VERSION_NONE = 'none';
3434
const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js';

modules/euidIdSystem.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* This module adds EUID ID support to the User ID module. It shares significant functionality with the UID2 module.
3+
* The {@link module:modules/userId} module is required.
4+
* @module modules/euidIdSystem
5+
* @requires module:modules/userId
6+
*/
7+
8+
import { logInfo, logWarn, deepAccess } from '../src/utils.js';
9+
import {submodule} from '../src/hook.js';
10+
import {getStorageManager} from '../src/storageManager.js';
11+
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
12+
13+
// RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here.
14+
// eslint-disable-next-line prebid/validate-imports
15+
import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js';
16+
17+
const MODULE_NAME = 'euid';
18+
const MODULE_REVISION = Uid2CodeVersion;
19+
const PREBID_VERSION = '$prebid.version$';
20+
const EUID_CLIENT_ID = `PrebidJS-${PREBID_VERSION}-EUIDModule-${MODULE_REVISION}`;
21+
const GVLID_TTD = 21; // The Trade Desk
22+
const LOG_PRE_FIX = 'EUID: ';
23+
const ADVERTISING_COOKIE = '__euid_advertising_token';
24+
25+
// eslint-disable-next-line no-unused-vars
26+
const EUID_TEST_URL = 'https://integ.euid.eu';
27+
const EUID_PROD_URL = 'https://prod.euid.eu';
28+
const EUID_BASE_URL = EUID_PROD_URL;
29+
30+
function createLogger(logger, prefix) {
31+
return function (...strings) {
32+
logger(prefix + ' ', ...strings);
33+
}
34+
}
35+
const _logInfo = createLogger(logInfo, LOG_PRE_FIX);
36+
const _logWarn = createLogger(logWarn, LOG_PRE_FIX);
37+
38+
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
39+
40+
function hasWriteToDeviceConsent(consentData) {
41+
const gdprApplies = consentData?.gdprApplies === true;
42+
const localStorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`)
43+
const prebidVendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID_TTD.toString()}`)
44+
if (gdprApplies && (!localStorageConsent || !prebidVendorConsent)) {
45+
return false;
46+
}
47+
return true;
48+
}
49+
50+
/** @type {Submodule} */
51+
export const euidIdSubmodule = {
52+
/**
53+
* used to link submodule with config
54+
* @type {string}
55+
*/
56+
name: MODULE_NAME,
57+
58+
/**
59+
* Vendor id of The Trade Desk
60+
* @type {Number}
61+
*/
62+
gvlid: GVLID_TTD,
63+
/**
64+
* decode the stored id value for passing to bid requests
65+
* @function
66+
* @param {string} value
67+
* @returns {{euid:{ id: string } }} or undefined if value doesn't exists
68+
*/
69+
decode(value) {
70+
const result = decodeImpl(value);
71+
_logInfo('EUID decode returned', result);
72+
return result;
73+
},
74+
75+
/**
76+
* performs action to obtain id and return a value.
77+
* @function
78+
* @param {SubmoduleConfig} [configparams]
79+
* @param {ConsentData|undefined} consentData
80+
* @returns {euidId}
81+
*/
82+
getId(config, consentData) {
83+
if (consentData?.gdprApplies !== true) {
84+
logWarn('EUID is intended for use within the EU. The module will not run when GDPR does not apply.');
85+
return;
86+
}
87+
if (!hasWriteToDeviceConsent(consentData)) {
88+
// The module cannot operate without this permission.
89+
_logWarn(`Unable to use EUID module due to insufficient consent. The EUID module requires storage permission.`)
90+
return;
91+
}
92+
93+
const mappedConfig = {
94+
apiBaseUrl: config?.params?.euidApiBase ?? EUID_BASE_URL,
95+
paramToken: config?.params?.euidToken,
96+
serverCookieName: config?.params?.euidCookie,
97+
storage: config?.params?.storage ?? 'localStorage',
98+
clientId: EUID_CLIENT_ID,
99+
internalStorage: ADVERTISING_COOKIE
100+
};
101+
102+
const result = Uid2GetId(mappedConfig, storage, _logInfo, _logWarn);
103+
_logInfo(`EUID getId returned`, result);
104+
return result;
105+
},
106+
};
107+
108+
function decodeImpl(value) {
109+
if (typeof value === 'string') {
110+
_logInfo('Found server-only token. Refresh is unavailable for this token.');
111+
const result = { euid: { id: value } };
112+
return result;
113+
}
114+
if (Date.now() < value.latestToken.identity_expires) {
115+
return { euid: { id: value.latestToken.advertising_token } };
116+
}
117+
return null;
118+
}
119+
120+
// Register submodule for userId
121+
submodule('userId', euidIdSubmodule);

0 commit comments

Comments
 (0)