diff --git a/babelConfig.js b/babelConfig.js
index aa4670969ca..80e0222038c 100644
--- a/babelConfig.js
+++ b/babelConfig.js
@@ -26,7 +26,7 @@ module.exports = function (options = {}) {
const plugins = [
[path.resolve(__dirname, './plugins/pbjsGlobals.js'), options],
[useLocal('@babel/plugin-transform-runtime')],
- [useLocal('@babel/plugin-proposal-private-methods')],
+ [useLocal('@babel/plugin-proposal-private-methods')],
];
if (options.codeCoverage) {
plugins.push([useLocal('babel-plugin-istanbul')])
diff --git a/modules.json b/modules.json
index 27840c64ecd..e49ed3252da 100644
--- a/modules.json
+++ b/modules.json
@@ -46,6 +46,7 @@
"sharedIdSystem",
"sharethroughBidAdapter",
"sizeMappingV2",
+ "symitriDapRtdProvider",
"sonobiBidAdapter",
"teadsBidAdapter",
"topicsFpdModule",
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 9dfeaf910f8..1eb5253ac12 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -97,6 +97,7 @@
"reconciliationRtdProvider",
"relevadRtdProvider",
"sirdataRtdProvider",
+ "symitriDapRtdProvider",
"timeoutRtdProvider",
"weboramaRtdProvider"
],
diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js
index 0bd53b2a91f..e5a647a90ef 100644
--- a/modules/akamaiDapRtdProvider.js
+++ b/modules/akamaiDapRtdProvider.js
@@ -5,802 +5,23 @@
* @module modules/akamaiDapRtdProvider
* @requires module:modules/realTimeData
*/
-import {ajax} from '../src/ajax.js';
-import {getStorageManager} from '../src/storageManager.js';
-import {submodule} from '../src/hook.js';
-import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js';
-import { loadExternalScript } from '../src/adloader.js';
-import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
-/**
- * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
- */
-
-const MODULE_NAME = 'realTimeData';
-const SUBMODULE_NAME = 'dap';
-const MODULE_CODE = 'akamaidap';
-
-export const DAP_TOKEN = 'async_dap_token';
-export const DAP_MEMBERSHIP = 'async_dap_membership';
-export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership';
-export const DAP_SS_ID = 'dap_ss_id';
-export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds
-export const DAP_MAX_RETRY_TOKENIZE = 1;
-export const DAP_CLIENT_ENTROPY = 'dap_client_entropy'
-
-export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME});
-let dapRetryTokenize = 0;
-
-/**
- * Lazy merge objects.
- * @param {String} target
- * @param {String} source
- */
-function mergeLazy(target, source) {
- if (!isPlainObject(target)) {
- target = {};
- }
- if (!isPlainObject(source)) {
- source = {};
- }
- return mergeDeep(target, source);
-}
-
-/**
- * Add real-time data & merge segments.
- * @param {Object} ortb2 destination object to merge RTD into
- * @param {Object} rtd
- */
-export function addRealTimeData(ortb2, rtd) {
- logInfo('DEBUG(addRealTimeData) - ENTER');
- if (isPlainObject(rtd.ortb2)) {
- logMessage('DEBUG(addRealTimeData): merging original: ', ortb2);
- logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2);
- mergeLazy(ortb2, rtd.ortb2);
- }
- logInfo('DEBUG(addRealTimeData) - EXIT');
-}
-
-/**
- * Real-time data retrieval from Audigent
- * @param {Object} bidConfig
- * @param {function} onDone
- * @param {Object} rtdConfig
- * @param {Object} userConsent
- */
-export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
- let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY));
- let loadScriptPromise = new Promise((resolve, reject) => {
- if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) {
- setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded'));
- }
- if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) {
- logMessage('Using cached entropy');
- resolve();
- } else {
- if (typeof window.dapCalculateEntropy === 'function') {
- window.dapCalculateEntropy(resolve, reject);
- } else {
- if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) {
- loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) });
- } else {
- reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params'));
- }
- }
- }
- });
- loadScriptPromise
- .catch((error) => {
- logError('Entropy could not be calculated due to: ', error.message);
- })
- .finally(() => {
- generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent);
- });
-}
-
-export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
- logInfo('DEBUG(generateRealTimeData) - ENTER');
- logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname);
- logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion);
- dapRetryTokenize = 0;
- var jsonData = null;
- if (rtdConfig && isPlainObject(rtdConfig.params)) {
- if (rtdConfig.params.segtax == 504) {
- let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage();
- if (encMembership) {
- jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax)
- }
- } else {
- let membership = dapUtils.dapGetMembershipFromLocalStorage();
- if (membership) {
- jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax)
- }
- }
- }
- if (jsonData) {
- if (jsonData.rtd) {
- addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd);
- onDone();
- logInfo('DEBUG(generateRealTimeData) - 1');
- // Don't return - ensure the data is always fresh.
- }
- }
- // Calling setTimeout to release the main thread so that the bid request could be sent.
- setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent);
-}
-
-/**
- * Module init
- * @param {Object} provider
- * @param {Object} userConsent
- * @return {boolean}
- */
-function init(provider, userConsent) {
- if (dapUtils.checkConsent(userConsent) === false) {
- return false;
- }
- return true;
-}
-
-/** @type {RtdSubmodule} */
-export const akamaiDapRtdSubmodule = {
- name: SUBMODULE_NAME,
- getBidRequestData: getRealTimeData,
- init: init
-};
-
-submodule(MODULE_NAME, akamaiDapRtdSubmodule);
-export const dapUtils = {
-
- callDapAPIs: function(bidConfig, onDone, rtdConfig, userConsent) {
- if (rtdConfig && isPlainObject(rtdConfig.params)) {
- let config = {
- api_hostname: rtdConfig.params.apiHostname,
- api_version: rtdConfig.params.apiVersion,
- domain: rtdConfig.params.domain,
- segtax: rtdConfig.params.segtax,
- identity: {type: rtdConfig.params.identityType}
- };
- let refreshMembership = true;
- let token = dapUtils.dapGetTokenFromLocalStorage();
- const ortb2 = bidConfig.ortb2Fragments.global;
- logMessage('token is: ', token);
- if (token !== null) { // If token is not null then check the membership in storage and add the RTD object
- if (config.segtax == 504) { // Follow the encrypted membership path
- dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone) // Get the encrypted membership from server
- refreshMembership = false;
- } else {
- dapUtils.dapRefreshMembership(ortb2, config, token, onDone) // Get the membership from server
- refreshMembership = false;
- }
- }
- dapUtils.dapRefreshToken(ortb2, config, refreshMembership, onDone) // Refresh Token and membership in all the cases
- }
- },
- dapGetEntropy: function(resolve, reject) {
- if (typeof window.dapCalculateEntropy === 'function') {
- window.dapCalculateEntropy(resolve, reject);
- } else {
- reject(Error('window.dapCalculateEntropy function is not defined'))
- }
- },
-
- dapGetTokenFromLocalStorage: function(ttl) {
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let token = null;
- let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN));
- if (item) {
- if (now < item.expires_at) {
- token = item.token;
- }
- }
- return token;
- },
-
- dapRefreshToken: function(ortb2, config, refreshMembership, onDone) {
- dapUtils.dapLog('Token missing or expired, fetching a new one...');
- // Trigger a refresh
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let item = {}
- let configAsync = {...config};
- dapUtils.dapTokenize(configAsync, config.identity, onDone,
- function(token, status, xhr, onDone) {
- item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
- let exp = dapUtils.dapExtractExpiryFromToken(token);
- if (typeof exp == 'number') {
- item.expires_at = exp - 10;
- }
- item.token = token;
- storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item));
- dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at);
- let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID');
- if (dapSSID) {
- storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID));
- }
- let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100');
- if (deviceId100 != null) {
- storage.setDataInLocalStorage('dap_deviceId100', deviceId100);
- dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100);
- }
- if (refreshMembership) {
- if (config.segtax == 504) {
- dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone);
- } else {
- dapUtils.dapRefreshMembership(ortb2, config, token, onDone);
- }
- }
- },
- function(xhr, status, error, onDone) {
- logError('ERROR(' + error + '): failed to retrieve token! ' + status);
- onDone()
- }
- );
- },
-
- dapGetMembershipFromLocalStorage: function() {
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let membership = null;
- let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP));
- if (item) {
- if (now < item.expires_at) {
- membership = {
- said: item.said,
- cohorts: item.cohorts,
- attributes: null
- };
- }
- }
- return membership;
- },
-
- dapRefreshMembership: function(ortb2, config, token, onDone) {
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let item = {}
- let configAsync = {...config};
- dapUtils.dapMembership(configAsync, token, onDone,
- function(membership, status, xhr, onDone) {
- item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
- let exp = dapUtils.dapExtractExpiryFromToken(membership.said)
- if (typeof exp == 'number') {
- item.expires_at = exp - 10;
- }
- item.said = membership.said;
- item.cohorts = membership.cohorts;
- storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item));
- dapUtils.dapLog('Successfully updated and stored membership:');
- dapUtils.dapLog(item);
-
- let data = dapUtils.dapGetRtdObj(item, config.segtax)
- dapUtils.checkAndAddRealtimeData(ortb2, data, config.segtax);
- onDone();
- },
- function(xhr, status, error, onDone) {
- logError('ERROR(' + error + '): failed to retrieve membership! ' + status);
- if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) {
- dapRetryTokenize++;
- dapUtils.dapRefreshToken(ortb2, config, true, onDone);
- } else {
- onDone();
- }
- }
- );
- },
-
- dapGetEncryptedMembershipFromLocalStorage: function() {
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let encMembership = null;
- let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP));
- if (item) {
- if (now < item.expires_at) {
- encMembership = {
- encryptedSegments: item.encryptedSegments
- };
- }
- }
- return encMembership;
- },
-
- dapRefreshEncryptedMembership: function(ortb2, config, token, onDone) {
- let now = Math.round(Date.now() / 1000.0); // in seconds
- let item = {};
- let configAsync = {...config};
- dapUtils.dapEncryptedMembership(configAsync, token, onDone,
- function(encToken, status, xhr, onDone) {
- item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
- let exp = dapUtils.dapExtractExpiryFromToken(encToken);
- if (typeof exp == 'number') {
- item.expires_at = exp - 10;
- }
- item.encryptedSegments = encToken;
- storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item));
- dapUtils.dapLog('Successfully updated and stored encrypted membership:');
- dapUtils.dapLog(item);
-
- let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax);
- dapUtils.checkAndAddRealtimeData(ortb2, encData, config.segtax);
- onDone();
- },
- function(xhr, status, error, onDone) {
- logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status);
- if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) {
- dapRetryTokenize++;
- dapUtils.dapRefreshToken(ortb2, config, true, onDone);
- } else {
- onDone();
- }
- }
- );
- },
-
- /**
- * DESCRIPTION
- * Extract expiry value from a token
- */
- dapExtractExpiryFromToken: function(token) {
- let exp = null;
- if (token) {
- const tokenArray = token.split('..');
- if (tokenArray && tokenArray.length > 0) {
- let decode = atob(tokenArray[0])
- let header = JSON.parse(decode.replace(/"/g, '"'));
- exp = header.exp;
- }
- }
- return exp
- },
-
- /**
- * DESCRIPTION
- *
- * Convert a DAP membership response to an OpenRTB2 segment object suitable
- * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
- */
- dapGetRtdObj: function(membership, segtax) {
- let segment = {
- name: 'dap.akamai.com',
- ext: {
- 'segtax': segtax
- },
- segment: []
- };
- if (membership != null) {
- for (const i of membership.cohorts) {
- segment.segment.push({ id: i });
- }
- }
- let data = {
- rtd: {
- ortb2: {
- user: {
- data: [
- segment
- ]
- },
- site: {
- ext: {
- data: {
- dapSAID: membership.said
- }
- }
- }
- }
- }
- };
- return data;
- },
-
- /**
- * DESCRIPTION
- *
- * Convert a DAP membership response to an OpenRTB2 segment object suitable
- * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
- */
- dapGetEncryptedRtdObj: function(encToken, segtax) {
- let segment = {
- name: 'dap.akamai.com',
- ext: {
- 'segtax': segtax
- },
- segment: []
- };
- if (encToken != null) {
- segment.segment.push({ id: encToken.encryptedSegments });
- }
- let encData = {
- rtd: {
- ortb2: {
- user: {
- data: [
- segment
- ]
- }
- }
- }
- };
- return encData;
- },
-
- checkAndAddRealtimeData: function(ortb2, data, segtax) {
- if (data.rtd) {
- if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)) {
- logMessage('DEBUG(handleInit): rtb Object already added');
- } else {
- addRealTimeData(ortb2, data.rtd);
- }
- logInfo('DEBUG(checkAndAddRealtimeData) - 1');
- }
- },
-
- checkIfSegmentsAlreadyExist: function(ortb2, rtd, segtax) {
- let segmentsExist = false
- if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) {
- for (let i = 0; i < ortb2.user.data.length; i++) {
- let element = ortb2.user.data[i]
- if (element.ext && element.ext.segtax == segtax) {
- segmentsExist = true
- logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data);
- break;
- }
- }
- }
- return segmentsExist
- },
-
- dapLog: function(args) {
- let css = '';
- css += 'display: inline-block;';
- css += 'color: #fff;';
- css += 'background: #F28B20;';
- css += 'padding: 1px 4px;';
- css += 'border-radius: 3px';
-
- logInfo('%cDAP Client', css, args);
- },
-
- isValidHttpsUrl: function(urlString) {
- let url;
- try {
- url = new URL(urlString);
- } catch (_) {
- return false;
- }
- return url.protocol === 'https:';
- },
-
- checkConsent: function(userConsent) {
- let consent = true;
-
- if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) {
- const gdpr = userConsent.gdpr;
- const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0;
- const gdprConsentString = hasGdpr ? gdpr.consentString : '';
- if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) {
- logError('akamaiDapRtd submodule requires consent string to call API');
- consent = false;
- }
- } else if (userConsent && userConsent.usp) {
- const usp = userConsent.usp;
- consent = usp[1] !== 'N' && usp[2] !== 'Y';
- }
-
- return consent;
- },
-
- /*******************************************************************************
- *
- * V2 (And Beyond) API
- *
- ******************************************************************************/
-
- /**
- * SYNOPSIS
- *
- * dapTokenize( config, identity );
- *
- * DESCRIPTION
- *
- * Tokenize an identity into a secure, privacy safe pseudonymiziation.
- *
- * PARAMETERS
- *
- * config: an array of system configuration parameters
- *
- * identity: an array of identity parameters passed to the tokenizing system
- *
- * EXAMPLE
- *
- * config = {
- * api_hostname: "prebid.dap.akadns.net", // required
- * domain: "prebid.org", // required
- * api_version: "x1", // optional, default "x1"
- * };
- *
- * token = null;
- * identity_email = {
- * type: "email",
- * identity: "obiwan@jedi.com"
- * attributes: { cohorts: [ "100:1641013200", "101:1641013200", "102":3:1641013200" ] },
- * };
- * dap_x1_tokenize( config, identity_email,
- * function( response, status, xhr ) { token = response; },
- * function( xhr, status, error ) { ; } // handle error
- *
- * token = null;
- * identity_signature = { type: "signature:1.0.0" };
- * dap_x1_tokenize( config, identity_signature,
- * function( response, status, xhr } { token = response; },
- * function( xhr, status, error ) { ; } // handle error
- */
- dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) {
- if (onError == null) {
- onError = function(xhr, status, error, onDone) {};
- }
-
- if (config == null || typeof (config) == typeof (undefined)) {
- onError(null, 'Invalid config object', 'ClientError', onDone);
- return;
- }
-
- if (typeof (config.domain) != 'string') {
- onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone);
- return;
- }
-
- if (config.domain.length <= 0) {
- onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone);
- return;
- }
-
- if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
- config.api_version = 'x1';
- }
-
- if (typeof (config.api_version) != 'string') {
- onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
- return;
- }
-
- if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
- onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
- return;
- }
-
- if (identity == null || typeof (identity) == typeof (undefined)) {
- onError(null, 'Invalid identity object', 'ClientError', onDone);
- return;
- }
-
- if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) {
- onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone);
- return;
- }
-
- let apiParams = {
- 'type': identity.type,
- };
-
- if (typeof (identity.identity) != typeof (undefined)) {
- apiParams.identity = identity.identity;
- }
- if (typeof (identity.attributes) != typeof (undefined)) {
- apiParams.attributes = identity.attributes;
- }
-
- let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY));
- if (entropyDict && entropyDict.entropy) {
- apiParams.entropy = entropyDict.entropy;
- }
-
- let method;
- let body;
- let path;
- switch (config.api_version) {
- case 'x1':
- case 'x1-dev':
- method = 'POST';
- path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize';
- body = JSON.stringify(apiParams);
- break;
- default:
- onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone);
- return;
- }
-
- let customHeaders = {'Content-Type': 'application/json'};
- let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID));
- if (dapSSID) {
- customHeaders['Akamai-DAP-SS-ID'] = dapSSID;
- }
-
- let url = 'https://' + config.api_hostname + path;
- let cb = {
- success: (response, request) => {
- let token = null;
- switch (config.api_version) {
- case 'x1':
- case 'x1-dev':
- token = request.getResponseHeader('Akamai-DAP-Token');
- break;
- }
- onSuccess(token, request.status, request, onDone);
- },
- error: (request, error) => {
- onError(request, request.statusText, error, onDone);
- }
- };
-
- ajax(url, cb, body, {
- method: method,
- customHeaders: customHeaders
- });
- },
-
- /**
- * SYNOPSIS
- *
- * dapMembership( config, token, onSuccess, onError );
- *
- * DESCRIPTION
- *
- * Return the audience segment membership along with a new Secure Advertising
- * ID for this token.
- *
- * PARAMETERS
- *
- * config: an array of system configuration parameters
- *
- * token: the token previously returned from the tokenize API
- *
- * EXAMPLE
- *
- * config = {
- * api_hostname: 'api.dap.akadns.net',
- * };
- *
- * // token from dap_tokenize
- *
- * dapMembership( config, token,
- * function( membership, status, xhr ) {
- * // Run auction with membership.segments and membership.said
- * },
- * function( xhr, status, error ) {
- * // error
- * } );
- *
- */
- dapMembership: function(config, token, onDone, onSuccess = null, onError = null) {
- if (onError == null) {
- onError = function(xhr, status, error, onDone) {};
- }
-
- if (config == null || typeof (config) == typeof (undefined)) {
- onError(null, 'Invalid config object', 'ClientError', onDone);
- return;
- }
-
- if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
- config.api_version = 'x1';
- }
-
- if (typeof (config.api_version) != 'string') {
- onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
- return;
- }
-
- if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
- onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
- return;
- }
-
- if (token == null || typeof (token) != 'string') {
- onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone);
- return;
- }
- let path = '/data-activation/' +
- config.api_version +
- '/token/' + token +
- '/membership';
-
- let url = 'https://' + config.api_hostname + path;
-
- let cb = {
- success: (response, request) => {
- onSuccess(JSON.parse(response), request.status, request, onDone);
- },
- error: (error, request) => {
- onError(request, request.status, error, onDone);
- }
- };
-
- ajax(url, cb, undefined, {
- method: 'GET',
- customHeaders: {}
- });
- },
-
- /**
- * SYNOPSIS
- *
- * dapEncryptedMembership( config, token, onSuccess, onError );
- *
- * DESCRIPTION
- *
- * Return the audience segment membership along with a new Secure Advertising
- * ID for this token in encrypted format.
- *
- * PARAMETERS
- *
- * config: an array of system configuration parameters
- *
- * token: the token previously returned from the tokenize API
- *
- * EXAMPLE
- *
- * config = {
- * api_hostname: 'api.dap.akadns.net',
- * };
- *
- * // token from dap_tokenize
- *
- * dapEncryptedMembership( config, token,
- * function( membership, status, xhr ) {
- * // Run auction with membership.segments and membership.said after decryption
- * },
- * function( xhr, status, error ) {
- * // error
- * } );
- *
- */
- dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) {
- if (onError == null) {
- onError = function(xhr, status, error, onDone) {};
- }
-
- if (config == null || typeof (config) == typeof (undefined)) {
- onError(null, 'Invalid config object', 'ClientError', onDone);
- return;
- }
-
- if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
- config.api_version = 'x1';
- }
-
- if (typeof (config.api_version) != 'string') {
- onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
- return;
- }
-
- if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
- onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
- return;
- }
-
- if (token == null || typeof (token) != 'string') {
- onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone);
- return;
- }
- let path = '/data-activation/' +
- config.api_version +
- '/token/' + token +
- '/membership/encrypt';
-
- let url = 'https://' + config.api_hostname + path;
-
- let cb = {
- success: (response, request) => {
- let encToken = request.getResponseHeader('Akamai-DAP-Token');
- onSuccess(encToken, request.status, request, onDone);
- },
- error: (error, request) => {
- onError(request, request.status, error, onDone);
- }
- };
- ajax(url, cb, undefined, {
- method: 'GET',
- customHeaders: {
- 'Content-Type': 'application/json',
- 'Pragma': 'akamai-x-get-extracted-values'
- }
- });
- }
-}
+import {
+ createRtdProvider
+} from './symitriDapRtdProvider.js'/* eslint prebid/validate-imports: "off" */
+
+export const {
+ addRealTimeData,
+ getRealTimeData,
+ generateRealTimeData,
+ rtdSubmodule: akamaiDapRtdSubmodule,
+ storage,
+ dapUtils,
+ DAP_TOKEN,
+ DAP_MEMBERSHIP,
+ DAP_ENCRYPTED_MEMBERSHIP,
+ DAP_SS_ID,
+ DAP_DEFAULT_TOKEN_TTL,
+ DAP_MAX_RETRY_TOKENIZE,
+ DAP_CLIENT_ENTROPY
+} = createRtdProvider('dap', 'akamaidap', 'Akamai');
diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js
new file mode 100644
index 00000000000..70e27441989
--- /dev/null
+++ b/modules/symitriDapRtdProvider.js
@@ -0,0 +1,1226 @@
+/**
+ * This module adds the Symitri DAP RTD provider to the real time data module
+ * The {@link module:modules/realTimeData} module is required
+ * The module will fetch real-time data from DAP
+ * @module modules/symitriDapRtdProvider
+ * @requires module:modules/realTimeData
+ */
+import { ajax } from '../src/ajax.js';
+import { getStorageManager } from '../src/storageManager.js';
+import { submodule } from '../src/hook.js';
+import {
+ isPlainObject,
+ mergeDeep,
+ logMessage,
+ logInfo,
+ logError,
+} from '../src/utils.js';
+import { loadExternalScript } from '../src/adloader.js';
+import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
+
+/**
+ * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
+ */
+export function createRtdProvider(moduleName, moduleCode, headerPrefix) {
+ const MODULE_NAME = 'realTimeData';
+ const SUBMODULE_NAME = moduleName;
+ const MODULE_CODE = moduleCode;
+
+ const DAP_TOKEN = 'async_dap_token';
+ const DAP_MEMBERSHIP = 'async_dap_membership';
+ const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership';
+ const DAP_SS_ID = 'dap_ss_id';
+ const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds
+ const DAP_MAX_RETRY_TOKENIZE = 1;
+ const DAP_CLIENT_ENTROPY = 'dap_client_entropy';
+
+ const storage = getStorageManager({
+ moduleType: MODULE_TYPE_RTD,
+ moduleName: SUBMODULE_NAME,
+ });
+ let dapRetryTokenize = 0;
+
+ /**
+ * Lazy merge objects.
+ * @param {String} target
+ * @param {String} source
+ */
+ function mergeLazy(target, source) {
+ if (!isPlainObject(target)) {
+ target = {};
+ }
+ if (!isPlainObject(source)) {
+ source = {};
+ }
+ return mergeDeep(target, source);
+ }
+
+ /**
+ * Add real-time data & merge segments.
+ * @param {Object} ortb2 destination object to merge RTD into
+ * @param {Object} rtd
+ */
+ function addRealTimeData(ortb2, rtd) {
+ logInfo('DEBUG(addRealTimeData) - ENTER');
+ if (isPlainObject(rtd.ortb2)) {
+ logMessage('DEBUG(addRealTimeData): merging original: ', ortb2);
+ logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2);
+ mergeLazy(ortb2, rtd.ortb2);
+ }
+ logInfo('DEBUG(addRealTimeData) - EXIT');
+ }
+
+ /**
+ * Real-time data retrieval from Audigent
+ * @param {Object} bidConfig
+ * @param {function} onDone
+ * @param {Object} rtdConfig
+ * @param {Object} userConsent
+ */
+ function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
+ let entropyDict = JSON.parse(
+ storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)
+ );
+
+ // Attempt to load entroy script if no entropy object exist and entropy config settings are present.
+ // Else
+ if (
+ !entropyDict &&
+ rtdConfig &&
+ rtdConfig.params &&
+ dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)
+ ) {
+ let loadScriptPromise = new Promise((resolve, reject) => {
+ if (
+ rtdConfig &&
+ rtdConfig.params &&
+ rtdConfig.params.dapEntropyTimeout &&
+ Number.isInteger(rtdConfig.params.dapEntropyTimeout)
+ ) {
+ setTimeout(
+ reject,
+ rtdConfig.params.dapEntropyTimeout,
+ Error('DapEntropy script could not be loaded')
+ );
+ }
+ if (
+ entropyDict &&
+ entropyDict.expires_at > Math.round(Date.now() / 1000.0)
+ ) {
+ logMessage('Using cached entropy');
+ resolve();
+ } else {
+ if (typeof window.dapCalculateEntropy === 'function') {
+ window.dapCalculateEntropy(resolve, reject);
+ } else {
+ if (
+ rtdConfig &&
+ rtdConfig.params &&
+ dapUtils.isValidHttpsUrl(
+ rtdConfig.params.dapEntropyUrl
+ )
+ ) {
+ loadExternalScript(
+ rtdConfig.params.dapEntropyUrl,
+ MODULE_CODE,
+ () => {
+ dapUtils.dapGetEntropy(resolve, reject);
+ }
+ );
+ } else {
+ reject(
+ Error(
+ 'Please check if dapEntropyUrl is specified and is valid under config.params'
+ )
+ );
+ }
+ }
+ }
+ });
+
+ loadScriptPromise
+ .catch((error) => {
+ logError(
+ 'Entropy could not be calculated due to: ',
+ error.message
+ );
+ })
+ .finally(() => {
+ generateRealTimeData(
+ bidConfig,
+ onDone,
+ rtdConfig,
+ userConsent
+ );
+ });
+ } else {
+ logMessage('No dapEntropyUrl is specified.');
+ generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent);
+ }
+ }
+
+ function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
+ logInfo('DEBUG(generateRealTimeData) - ENTER');
+ logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname);
+ logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion);
+ dapRetryTokenize = 0;
+ var jsonData = null;
+ if (rtdConfig && isPlainObject(rtdConfig.params)) {
+ if (rtdConfig.params.segtax == 504) {
+ let encMembership =
+ dapUtils.dapGetEncryptedMembershipFromLocalStorage();
+ if (encMembership) {
+ jsonData = dapUtils.dapGetEncryptedRtdObj(
+ encMembership,
+ rtdConfig.params.segtax
+ );
+ }
+ } else {
+ let membership = dapUtils.dapGetMembershipFromLocalStorage();
+ if (membership) {
+ jsonData = dapUtils.dapGetRtdObj(
+ membership,
+ rtdConfig.params.segtax
+ );
+ }
+ }
+ }
+ if (jsonData) {
+ if (jsonData.rtd) {
+ addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd);
+ onDone();
+ logInfo('DEBUG(generateRealTimeData) - 1');
+ // Don't return - ensure the data is always fresh.
+ }
+ }
+ // Calling setTimeout to release the main thread so that the bid request could be sent.
+ setTimeout(
+ dapUtils.callDapAPIs,
+ 0,
+ bidConfig,
+ onDone,
+ rtdConfig,
+ userConsent
+ );
+ }
+
+ /**
+ * Module init
+ * @param {Object} config
+ * @param {Object} userConsent
+ * @return {boolean}
+ */
+ function init(config, userConsent) {
+ if (dapUtils.checkConsent(userConsent) === false) {
+ return false;
+ }
+ return true;
+ }
+
+ function onBidResponse(bidResponse, config, userConsent) {
+ if (
+ bidResponse.dealId &&
+ typeof bidResponse.dealId != typeof undefined
+ ) {
+ let membership = dapUtils.dapGetMembershipFromLocalStorage(); // Get Membership details from Local Storage
+ let deals = membership.deals; // Get list of Deals the user is mapped to
+ deals.forEach((deal) => {
+ deal = JSON.parse(deal);
+ if (bidResponse.dealId == deal.id) {
+ // Check if the bid response deal Id matches to the deals mapped to the user
+ let token = dapUtils.dapGetTokenFromLocalStorage();
+ let url =
+ config.params.pixelUrl +
+ '?token=' +
+ token +
+ '&ad_id=' +
+ bidResponse.adId +
+ '&bidder=' +
+ bidResponse.bidder +
+ '&bidder_code=' +
+ bidResponse.bidderCode +
+ '&cpm=' +
+ bidResponse.cpm +
+ '&creative_id=' +
+ bidResponse.creativeId +
+ '&deal_id=' +
+ bidResponse.dealId +
+ '&media_type=' +
+ bidResponse.mediaType +
+ '&response_timestamp=' +
+ bidResponse.responseTimestamp;
+ bidResponse.ad = `${bidResponse.ad}`;
+ }
+ });
+ }
+ }
+
+ const rtdSubmodule = {
+ name: SUBMODULE_NAME,
+ getBidRequestData: getRealTimeData,
+ onBidResponseEvent: onBidResponse,
+ init: init,
+ };
+
+ submodule(MODULE_NAME, rtdSubmodule);
+
+ const dapUtils = {
+ callDapAPIs: function (bidConfig, onDone, rtdConfig, userConsent) {
+ if (rtdConfig && isPlainObject(rtdConfig.params)) {
+ let config = {
+ api_hostname: rtdConfig.params.apiHostname,
+ api_version: rtdConfig.params.apiVersion,
+ domain: rtdConfig.params.domain,
+ segtax: rtdConfig.params.segtax,
+ identity: {
+ type: rtdConfig.params.identityType,
+ value: rtdConfig.params.identityValue,
+ },
+ };
+ let refreshMembership = true;
+ let token = dapUtils.dapGetTokenFromLocalStorage();
+ const ortb2 = bidConfig.ortb2Fragments.global;
+ logMessage('token is: ', token);
+ if (token !== null) {
+ // If token is not null then check the membership in storage and add the RTD object
+ if (config.segtax == 504) {
+ // Follow the encrypted membership path
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ config,
+ token,
+ onDone
+ ); // Get the encrypted membership from server
+ refreshMembership = false;
+ } else {
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ config,
+ token,
+ onDone
+ ); // Get the membership from server
+ refreshMembership = false;
+ }
+ }
+ dapUtils.dapRefreshToken(
+ ortb2,
+ config,
+ refreshMembership,
+ onDone
+ ); // Refresh Token and membership in all the cases
+ }
+ },
+ dapGetEntropy: function (resolve, reject) {
+ if (typeof window.dapCalculateEntropy === 'function') {
+ window.dapCalculateEntropy(resolve, reject);
+ } else {
+ reject(
+ Error('window.dapCalculateEntropy function is not defined')
+ );
+ }
+ },
+
+ dapGetTokenFromLocalStorage: function (ttl) {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let token = null;
+ let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN));
+ if (item) {
+ if (now < item.expires_at) {
+ token = item.token;
+ }
+ }
+ return token;
+ },
+
+ dapRefreshToken: function (ortb2, config, refreshMembership, onDone) {
+ dapUtils.dapLog('Token missing or expired, fetching a new one...');
+ // Trigger a refresh
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let item = {};
+ let configAsync = { ...config };
+ dapUtils.dapTokenize(
+ configAsync,
+ config.identity,
+ onDone,
+ function (token, status, xhr, onDone) {
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
+ let exp = dapUtils.dapExtractExpiryFromToken(token);
+ if (typeof exp == 'number') {
+ item.expires_at = exp - 10;
+ }
+ item.token = token;
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(item)
+ );
+ dapUtils.dapLog(
+ 'Successfully updated and stored token; expires at ' +
+ item.expires_at
+ );
+ let dapSSID = xhr.getResponseHeader(
+ headerPrefix + '-DAP-SS-ID'
+ );
+ if (dapSSID) {
+ storage.setDataInLocalStorage(
+ DAP_SS_ID,
+ JSON.stringify(dapSSID)
+ );
+ }
+ let deviceId100 = xhr.getResponseHeader(
+ headerPrefix + '-DAP-100'
+ );
+ if (deviceId100 != null) {
+ storage.setDataInLocalStorage(
+ 'dap_deviceId100',
+ deviceId100
+ );
+ dapUtils.dapLog(
+ 'Successfully stored DAP 100 Device ID: ' +
+ deviceId100
+ );
+ }
+ if (refreshMembership) {
+ if (config.segtax == 504) {
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ config,
+ token,
+ onDone
+ );
+ } else {
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ config,
+ token,
+ onDone
+ );
+ }
+ }
+ },
+ function (xhr, status, error, onDone) {
+ logError(
+ 'ERROR(' +
+ error +
+ '): failed to retrieve token! ' +
+ status
+ );
+ onDone();
+ }
+ );
+ },
+
+ dapGetMembershipFromLocalStorage: function () {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let membership = null;
+ let item = JSON.parse(
+ storage.getDataFromLocalStorage(DAP_MEMBERSHIP)
+ );
+ if (item) {
+ if (now < item.expires_at) {
+ membership = {
+ said: item.said,
+ cohorts: item.cohorts,
+ deals: item.deals,
+ attributes: null,
+ };
+ }
+ }
+ return membership;
+ },
+
+ dapRefreshMembership: function (ortb2, config, token, onDone) {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let item = {};
+ let configAsync = { ...config };
+ dapUtils.dapMembership(
+ configAsync,
+ token,
+ onDone,
+ function (membership, status, xhr, onDone) {
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
+ let exp = dapUtils.dapExtractExpiryFromToken(
+ membership.said
+ );
+ if (typeof exp == 'number') {
+ item.expires_at = exp - 10;
+ }
+ item.said = membership.said;
+ item.cohorts = membership.cohorts;
+ item.deals = membership.deals ? membership.deals : [];
+ storage.setDataInLocalStorage(
+ DAP_MEMBERSHIP,
+ JSON.stringify(item)
+ );
+ dapUtils.dapLog(
+ 'Successfully updated and stored membership:'
+ );
+ dapUtils.dapLog(item);
+
+ let data = dapUtils.dapGetRtdObj(item, config.segtax);
+ dapUtils.checkAndAddRealtimeData(
+ ortb2,
+ data,
+ config.segtax
+ );
+ onDone();
+ },
+ function (xhr, status, error, onDone) {
+ logError(
+ 'ERROR(' +
+ error +
+ '): failed to retrieve membership! ' +
+ status
+ );
+ if (
+ status == 403 &&
+ dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE
+ ) {
+ dapRetryTokenize++;
+ dapUtils.dapRefreshToken(ortb2, config, true, onDone);
+ } else {
+ onDone();
+ }
+ }
+ );
+ },
+
+ dapGetEncryptedMembershipFromLocalStorage: function () {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let encMembership = null;
+ let item = JSON.parse(
+ storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)
+ );
+ if (item) {
+ if (now < item.expires_at) {
+ encMembership = {
+ encryptedSegments: item.encryptedSegments,
+ };
+ }
+ }
+ return encMembership;
+ },
+
+ dapRefreshEncryptedMembership: function (ortb2, config, token, onDone) {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let item = {};
+ let configAsync = { ...config };
+ dapUtils.dapEncryptedMembership(
+ configAsync,
+ token,
+ onDone,
+ function (encToken, status, xhr, onDone) {
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
+ let exp = dapUtils.dapExtractExpiryFromToken(encToken);
+ if (typeof exp == 'number') {
+ item.expires_at = exp - 10;
+ }
+ item.encryptedSegments = encToken;
+ storage.setDataInLocalStorage(
+ DAP_ENCRYPTED_MEMBERSHIP,
+ JSON.stringify(item)
+ );
+ dapUtils.dapLog(
+ 'Successfully updated and stored encrypted membership:'
+ );
+ dapUtils.dapLog(item);
+
+ let encData = dapUtils.dapGetEncryptedRtdObj(
+ item,
+ config.segtax
+ );
+ dapUtils.checkAndAddRealtimeData(
+ ortb2,
+ encData,
+ config.segtax
+ );
+ onDone();
+ },
+ function (xhr, status, error, onDone) {
+ logError(
+ 'ERROR(' +
+ error +
+ '): failed to retrieve encrypted membership! ' +
+ status
+ );
+ if (
+ status == 403 &&
+ dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE
+ ) {
+ dapRetryTokenize++;
+ dapUtils.dapRefreshToken(ortb2, config, true, onDone);
+ } else {
+ onDone();
+ }
+ }
+ );
+ },
+
+ /**
+ * DESCRIPTION
+ * Extract expiry value from a token
+ */
+ dapExtractExpiryFromToken: function (token) {
+ let exp = null;
+ if (token) {
+ const tokenArray = token.split('..');
+ if (tokenArray && tokenArray.length > 0) {
+ let decode = atob(tokenArray[0]);
+ let header = JSON.parse(decode.replace(/"/g, '"'));
+ exp = header.exp;
+ }
+ }
+ return exp;
+ },
+
+ /**
+ * DESCRIPTION
+ *
+ * Convert a DAP membership response to an OpenRTB2 segment object suitable
+ * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
+ */
+ dapGetRtdObj: function (membership, segtax) {
+ let segment = {
+ name: 'dap.symitri.com',
+ ext: {
+ segtax: segtax,
+ },
+ segment: [],
+ };
+ if (membership != null) {
+ for (const i of membership.cohorts) {
+ segment.segment.push({ id: i });
+ }
+ }
+ let data = {
+ rtd: {
+ ortb2: {
+ user: {
+ data: [segment],
+ },
+ site: {
+ ext: {
+ data: {
+ dapSAID: membership.said,
+ },
+ },
+ },
+ },
+ },
+ };
+ return data;
+ },
+
+ /**
+ * DESCRIPTION
+ *
+ * Convert a DAP membership response to an OpenRTB2 segment object suitable
+ * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
+ */
+ dapGetEncryptedRtdObj: function (encToken, segtax) {
+ let segment = {
+ name: 'dap.symitri.com',
+ ext: {
+ segtax: segtax,
+ },
+ segment: [],
+ };
+ if (encToken != null) {
+ segment.segment.push({ id: encToken.encryptedSegments });
+ }
+ let encData = {
+ rtd: {
+ ortb2: {
+ user: {
+ data: [segment],
+ },
+ },
+ },
+ };
+ return encData;
+ },
+
+ checkAndAddRealtimeData: function (ortb2, data, segtax) {
+ if (data.rtd) {
+ if (
+ segtax == 504 &&
+ dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)
+ ) {
+ logMessage('DEBUG(handleInit): rtb Object already added');
+ } else {
+ addRealTimeData(ortb2, data.rtd);
+ }
+ logInfo('DEBUG(checkAndAddRealtimeData) - 1');
+ }
+ },
+
+ checkIfSegmentsAlreadyExist: function (ortb2, rtd, segtax) {
+ let segmentsExist = false;
+ if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) {
+ for (let i = 0; i < ortb2.user.data.length; i++) {
+ let element = ortb2.user.data[i];
+ if (element.ext && element.ext.segtax == segtax) {
+ segmentsExist = true;
+ logMessage(
+ 'DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ',
+ ortb2.user.data
+ );
+ break;
+ }
+ }
+ }
+ return segmentsExist;
+ },
+
+ dapLog: function (args) {
+ let css = '';
+ css += 'display: inline-block;';
+ css += 'color: #fff;';
+ css += 'background: #F28B20;';
+ css += 'padding: 1px 4px;';
+ css += 'border-radius: 3px';
+
+ logInfo('%cDAP Client', css, args);
+ },
+
+ isValidHttpsUrl: function (urlString) {
+ let url;
+ try {
+ url = new URL(urlString);
+ } catch (_) {
+ return false;
+ }
+ return url.protocol === 'https:';
+ },
+
+ checkConsent: function (userConsent) {
+ let consent = true;
+
+ if (
+ userConsent &&
+ userConsent.gdpr &&
+ userConsent.gdpr.gdprApplies
+ ) {
+ const gdpr = userConsent.gdpr;
+ const hasGdpr =
+ gdpr &&
+ typeof gdpr.gdprApplies === 'boolean' &&
+ gdpr.gdprApplies
+ ? 1
+ : 0;
+ const gdprConsentString = hasGdpr ? gdpr.consentString : '';
+ if (
+ hasGdpr &&
+ (!gdprConsentString || gdprConsentString === '')
+ ) {
+ logError(
+ 'symitriDapRtd submodule requires consent string to call API'
+ );
+ consent = false;
+ }
+ } else if (userConsent && userConsent.usp) {
+ const usp = userConsent.usp;
+ consent = usp[1] !== 'N' && usp[2] !== 'Y';
+ }
+
+ return consent;
+ },
+
+ /*******************************************************************************
+ *
+ * V2 (And Beyond) API
+ *
+ ******************************************************************************/
+
+ dapValidationHelper: function (config, onDone, token, onError) {
+ if (onError == null) {
+ onError = function (xhr, status, error, onDone) {};
+ }
+
+ if (config == null || typeof config == typeof undefined) {
+ onError(null, 'Invalid config object', 'ClientError', onDone);
+ return [config, true];
+ }
+
+ if (
+ !('api_version' in config) ||
+ (typeof config.api_version == 'string' &&
+ config.api_version.length == 0)
+ ) {
+ config.api_version = 'x1';
+ }
+
+ if (typeof config.api_version != 'string') {
+ onError(
+ null,
+ "Invalid api_version: must be a string like 'x1', etc.",
+ 'ClientError',
+ onDone
+ );
+ return [config, true];
+ }
+
+ if (
+ !('api_hostname' in config) ||
+ typeof config.api_hostname != 'string' ||
+ config.api_hostname.length == 0
+ ) {
+ onError(
+ null,
+ 'Invalid api_hostname: must be a non-empty string',
+ 'ClientError',
+ onDone
+ );
+ return [config, true];
+ }
+
+ if (token) {
+ if (typeof token != 'string') {
+ onError(
+ null,
+ 'Invalid token: must be a non-null string',
+ 'ClientError',
+ onDone
+ );
+ return [config, true];
+ }
+ }
+
+ return [config, false];
+ },
+
+ addIdentifier: async function (identity, apiParams) {
+ if (
+ window.crypto &&
+ window.crypto.subtle &&
+ typeof identity.value != typeof undefined &&
+ identity.value.trim() !== ''
+ ) {
+ const hashBuffer = await window.crypto.subtle.digest(
+ 'SHA-256',
+ new TextEncoder().encode(identity.value)
+ );
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const hashHex = hashArray
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('');
+ apiParams.identity = hashHex.toUpperCase();
+ }
+ return apiParams;
+ },
+
+ /**
+ * SYNOPSIS
+ *
+ * dapTokenize( config, identity );
+ *
+ * DESCRIPTION
+ *
+ * Tokenize an identity into a secure, privacy safe pseudonymiziation.
+ *
+ * PARAMETERS
+ *
+ * config: an array of system configuration parameters
+ *
+ * identity: an array of identity parameters passed to the tokenizing system
+ *
+ * EXAMPLE
+ *
+ * config = {
+ * api_hostname: "prebid.dap.akadns.net", // required
+ * domain: "prebid.org", // required
+ * api_version: "x1", // optional, default "x1"
+ * };
+ *
+ * token = null;
+ * identity_email = {
+ * type: "email",
+ * identity: "obiwan@jedi.com"
+ * attributes: { cohorts: [ "100:1641013200", "101:1641013200", "102":3:1641013200" ] },
+ * };
+ * dap_x1_tokenize( config, identity_email,
+ * function( response, status, xhr ) { token = response; },
+ * function( xhr, status, error ) { ; } // handle error
+ *
+ * token = null;
+ * identity_signature = { type: "signature:1.0.0" };
+ * dap_x1_tokenize( config, identity_signature,
+ * function( response, status, xhr } { token = response; },
+ * function( xhr, status, error ) { ; } // handle error
+ */
+ dapTokenize: function (
+ config,
+ identity,
+ onDone,
+ onSuccess = null,
+ onError = null
+ ) {
+ let hasTokenizeError;
+ [config, hasTokenizeError] = this.dapValidationHelper(
+ config,
+ onDone,
+ null,
+ onError
+ );
+ if (hasTokenizeError) {
+ return;
+ }
+
+ if (typeof config.domain != 'string') {
+ onError(
+ null,
+ 'Invalid config.domain: must be a string',
+ 'ClientError',
+ onDone
+ );
+ return;
+ }
+
+ if (config.domain.length <= 0) {
+ onError(
+ null,
+ 'Invalid config.domain: must have non-zero length',
+ 'ClientError',
+ onDone
+ );
+ return;
+ }
+
+ if (identity == null || typeof identity == typeof undefined) {
+ onError(null, 'Invalid identity object', 'ClientError', onDone);
+ return;
+ }
+
+ if (
+ !('type' in identity) ||
+ typeof identity.type != 'string' ||
+ identity.type.length <= 0
+ ) {
+ onError(
+ null,
+ "Identity must contain a valid 'type' field",
+ 'ClientError',
+ onDone
+ );
+ return;
+ }
+
+ let apiParams = {
+ type: identity.type.toLowerCase(),
+ identity: identity.value,
+ };
+ if (identity.type === 'simpleid') {
+ this.addIdentifier(identity, apiParams).then((apiParams) => {
+ this.callTokenize(
+ config,
+ identity,
+ apiParams,
+ onDone,
+ onSuccess,
+ onError
+ );
+ });
+ } else if (identity.type === 'compositeid') {
+ identity = JSON.stringify(identity);
+ this.callTokenize(
+ config,
+ identity,
+ apiParams,
+ onDone,
+ onSuccess,
+ onError
+ );
+ } else if (identity.type === 'hashedid') {
+ this.callTokenize(
+ config,
+ identity,
+ apiParams,
+ onDone,
+ onSuccess,
+ onError
+ );
+ } else {
+ this.callTokenize(
+ config,
+ identity,
+ apiParams,
+ onDone,
+ onSuccess,
+ onError
+ );
+ }
+ },
+
+ callTokenize(config, identity, apiParams, onDone, onSuccess, onError) {
+ if (typeof identity.attributes != typeof undefined) {
+ apiParams.attributes = identity.attributes;
+ }
+
+ let entropyDict = JSON.parse(
+ storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)
+ );
+ if (entropyDict && entropyDict.entropy) {
+ apiParams.entropy = entropyDict.entropy;
+ }
+
+ let method;
+ let body;
+ let path;
+ switch (config.api_version) {
+ case 'x1':
+ case 'x1-dev':
+ method = 'POST';
+ path =
+ '/data-activation/' +
+ config.api_version +
+ '/domain/' +
+ config.domain +
+ '/identity/tokenize';
+ body = JSON.stringify(apiParams);
+ break;
+ default:
+ onError(
+ null,
+ 'Invalid api_version: ' + config.api_version,
+ 'ClientError',
+ onDone
+ );
+ return;
+ }
+
+ let customHeaders = { 'Content-Type': 'application/json' };
+ let dapSSID = JSON.parse(
+ storage.getDataFromLocalStorage(DAP_SS_ID)
+ );
+ if (dapSSID) {
+ customHeaders[headerPrefix + '-DAP-SS-ID'] = dapSSID;
+ }
+
+ let url = 'https://' + config.api_hostname + path;
+ let cb = {
+ success: (response, request) => {
+ let token = null;
+ switch (config.api_version) {
+ case 'x1':
+ case 'x1-dev':
+ token = request.getResponseHeader(
+ headerPrefix + '-DAP-Token'
+ );
+ break;
+ }
+ onSuccess(token, request.status, request, onDone);
+ },
+ error: (request, error) => {
+ onError(request, request.statusText, error, onDone);
+ },
+ };
+
+ ajax(url, cb, body, {
+ method: method,
+ customHeaders: customHeaders,
+ });
+ },
+
+ /**
+ * SYNOPSIS
+ *
+ * dapMembership( config, token, onSuccess, onError );
+ *
+ * DESCRIPTION
+ *
+ * Return the audience segment membership along with a new Secure Advertising
+ * ID for this token.
+ *
+ * PARAMETERS
+ *
+ * config: an array of system configuration parameters
+ *
+ * token: the token previously returned from the tokenize API
+ *
+ * EXAMPLE
+ *
+ * config = {
+ * api_hostname: 'api.dap.akadns.net',
+ * };
+ *
+ * // token from dap_tokenize
+ *
+ * dapMembership( config, token,
+ * function( membership, status, xhr ) {
+ * // Run auction with membership.segments and membership.said
+ * },
+ * function( xhr, status, error ) {
+ * // error
+ * } );
+ *
+ */
+ dapMembership: function (
+ config,
+ token,
+ onDone,
+ onSuccess = null,
+ onError = null
+ ) {
+ let hasMembershipError;
+ [config, hasMembershipError] = this.dapValidationHelper(
+ config,
+ onDone,
+ token,
+ onError
+ );
+ if (hasMembershipError) {
+ return;
+ }
+
+ if (typeof config.domain != 'string') {
+ onError(
+ null,
+ 'Invalid config.domain: must be a string',
+ 'ClientError',
+ onDone
+ );
+ return;
+ }
+
+ let path =
+ '/data-activation/' +
+ config.api_version +
+ '/token/' +
+ token +
+ '/membership';
+
+ let url = 'https://' + config.api_hostname + path;
+
+ let cb = {
+ success: (response, request) => {
+ onSuccess(
+ JSON.parse(response),
+ request.status,
+ request,
+ onDone
+ );
+ },
+ error: (error, request) => {
+ onError(request, request.status, error, onDone);
+ },
+ };
+
+ ajax(url, cb, undefined, {
+ method: 'GET',
+ customHeaders: {},
+ });
+ },
+
+ /**
+ * SYNOPSIS
+ *
+ * dapEncryptedMembership( config, token, onSuccess, onError );
+ *
+ * DESCRIPTION
+ *
+ * Return the audience segment membership along with a new Secure Advertising
+ * ID for this token in encrypted format.
+ *
+ * PARAMETERS
+ *
+ * config: an array of system configuration parameters
+ *
+ * token: the token previously returned from the tokenize API
+ *
+ * EXAMPLE
+ *
+ * config = {
+ * api_hostname: 'api.dap.akadns.net',
+ * };
+ *
+ * // token from dap_tokenize
+ *
+ * dapEncryptedMembership( config, token,
+ * function( membership, status, xhr ) {
+ * // Run auction with membership.segments and membership.said after decryption
+ * },
+ * function( xhr, status, error ) {
+ * // error
+ * } );
+ *
+ */
+ dapEncryptedMembership: function (
+ config,
+ token,
+ onDone,
+ onSuccess = null,
+ onError = null
+ ) {
+ let hasEncryptedMembershipError;
+ [config, hasEncryptedMembershipError] = this.dapValidationHelper(
+ config,
+ onDone,
+ token,
+ onError
+ );
+ if (hasEncryptedMembershipError) {
+ return;
+ }
+
+ let cb = {
+ success: (response, request) => {
+ let encToken = request.getResponseHeader(
+ headerPrefix + '-DAP-Token'
+ );
+ onSuccess(encToken, request.status, request, onDone);
+ },
+ error: (error, request) => {
+ onError(request, request.status, error, onDone);
+ },
+ };
+
+ let path =
+ '/data-activation/' +
+ config.api_version +
+ '/token/' +
+ token +
+ '/membership/encrypt';
+
+ let url = 'https://' + config.api_hostname + path;
+
+ ajax(url, cb, undefined, {
+ method: 'GET',
+ customHeaders: {
+ 'Content-Type': 'application/json',
+ Pragma: 'akamai-x-get-extracted-values',
+ },
+ });
+ },
+ };
+
+ return {
+ addRealTimeData,
+ getRealTimeData,
+ generateRealTimeData,
+ rtdSubmodule,
+ storage,
+ dapUtils,
+ DAP_TOKEN,
+ DAP_MEMBERSHIP,
+ DAP_ENCRYPTED_MEMBERSHIP,
+ DAP_SS_ID,
+ DAP_DEFAULT_TOKEN_TTL,
+ DAP_MAX_RETRY_TOKENIZE,
+ DAP_CLIENT_ENTROPY,
+ };
+}
+
+export const {
+ addRealTimeData,
+ getRealTimeData,
+ generateRealTimeData,
+ rtdSubmodule: symitriDapRtdSubmodule,
+ storage,
+ dapUtils,
+ DAP_TOKEN,
+ DAP_MEMBERSHIP,
+ DAP_ENCRYPTED_MEMBERSHIP,
+ DAP_SS_ID,
+ DAP_DEFAULT_TOKEN_TTL,
+ DAP_MAX_RETRY_TOKENIZE,
+ DAP_CLIENT_ENTROPY,
+} = createRtdProvider('symitriDap', 'symitridap', 'Symitri');
diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md
new file mode 100644
index 00000000000..28f6265b761
--- /dev/null
+++ b/modules/symitriDapRtdProvider.md
@@ -0,0 +1,97 @@
+---
+layout: page_v2
+title: Symitri DAP Real Time Data Provider Module
+display_name: Symitri DAP Real Time Data Provider Module
+description: Symitri DAP Real Time Data Provider Module
+page_type: module
+module_type: rtd
+module_code : symitriDapRtdProvider
+enable_download : true
+vendor_specific: true
+sidebarType : 1
+---
+
+# Symitri DAP Real Time Data Provider Module
+
+{:.no_toc}
+
+* TOC
+{:toc}
+
+The Symitri Data Activation Platform (DAP) is a privacy-first system that protects end-user privacy by only allowing them to be targeted as part of a larger cohort. Symitri DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the Secure Ad ID(SAID) to the bid-stream. SAID is a JWT/JWE which carries with it the cohorts and only a side-car or trusted server in the demand-side platform is allowed to see its contents.
+
+## Publisher Usage
+
+1. Build the symitriDapRTD module into the Prebid.js package with:
+
+ ```bash
+ gulp build --modules=symitriDapRtdProvider,...
+ ```
+
+2. Use `setConfig` to instruct Prebid.js to initilaize the symitriDapRtdProvider module, as specified below.
+
+### Configuration
+
+```javascript
+pbjs.setConfig({
+ realTimeData: {
+ auctionDelay: 2000,
+ dataProviders: [
+ {
+ name: "symitriDap",
+ waitForIt: true,
+ params: {
+ apiHostname: '',
+ apiVersion: "x1",
+ apiAuthToken: '',
+ domain: 'your-domain.com',
+ identityType: 'simpleid'|'compositeid'|'hashedid'|'dap-signature:1.0.0',
+ identityValue: '',
+ segtax: 501,
+ dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js',
+ dapEntropyTimeout: 1500,
+ pixelUrl: '',
+ }
+ }
+ ]
+ }
+});
+```
+
+Please reach out to your Symitri account representative() to get provisioned on the DAP platform.
+
+**Config Syntax details:**
+
+{: .table .table-bordered .table-striped }
+| Name |Type | Description | Notes |
+| :------------ | :------------ | :------------ |:------------ |
+| name | String | Symitri Dap Rtd module name | 'symitriDap' always|
+| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false |
+| apiHostname | String | Hostname provided by Symitri | Please reach out to your Symitri account representative() for this value|
+| apiVersion | String | This holds the API version | It should be "x1" always |
+| apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value |
+| domain | String | The domain name of your webpage | |
+| identityType | String | 'simpleid' or 'compositeid' or 'hashedid' or 'dap-signature:1.0.0' | Use 'simpleid' to pass email or other plain text ids and SymitriRTD Module will hash it.
+Use 'hashedid' to pass in single already hashed id. Use 'compositeid' to pass in multiple identifiers as key-value pairs as shown below:
+{
+ "identityType1": "identityValue1",
+ "identityType2": "identityValue2",
+ ...
+}
+|
+| identityValue | String | This is optional field to pass user hid. Will be used only if identityType is hid | |
+| segtax | Integer | The taxonomy for Symitri | The value should be 501 |
+| dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details |
+| dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | |
+| pixelUrl | String | Pixel URL provided by Symitri which will be triggered when bid matching with Symitri dealid wins and creative gets rendered | |
+
+### Testing
+
+To view an example of available segments returned by dap:
+
+```bash
+gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter
+```
+
+and then point your browser at:
+""
\ No newline at end of file
diff --git a/package.json b/package.json
index 12a2f1296e5..c8f12145b78 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prebid.js",
- "version": "8.49.84",
+ "version": "8.50.0",
"description": "Header Bidding Management Library",
"main": "src/prebid.js",
"scripts": {
@@ -67,7 +67,7 @@
"execa": "^1.0.0",
"faker": "^5.5.3",
"fs.extra": "^1.3.2",
- "gulp": "^4.0.0",
+ "gulp": "^4.0.2",
"gulp-clean": "^0.4.0",
"gulp-concat": "^2.6.0",
"gulp-connect": "^5.7.0",
@@ -107,7 +107,7 @@
"node-html-parser": "^6.1.5",
"opn": "^5.4.0",
"resolve-from": "^5.0.0",
- "sinon": "^4.1.3",
+ "sinon": "^4.5.0",
"through2": "^4.0.2",
"url": "^0.11.0",
"url-parse": "^1.0.5",
diff --git a/src/adloader.js b/src/adloader.js
index c2da2646320..9c1c6b720d1 100644
--- a/src/adloader.js
+++ b/src/adloader.js
@@ -34,6 +34,7 @@ const _approvedLoadExternalJSList = [
'contxtful',
'id5',
'lucead',
+ 'symitridap',
];
/**
diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js
new file mode 100644
index 00000000000..ac19099b3c2
--- /dev/null
+++ b/test/spec/modules/symitriDapRtdProvider_spec.js
@@ -0,0 +1,978 @@
+import { config } from 'src/config.js';
+import {
+ dapUtils,
+ generateRealTimeData,
+ symitriDapRtdSubmodule,
+ onBidWonListener,
+ storage,
+ DAP_MAX_RETRY_TOKENIZE,
+ DAP_SS_ID,
+ DAP_TOKEN,
+ DAP_MEMBERSHIP,
+ DAP_ENCRYPTED_MEMBERSHIP,
+} from 'modules/symitriDapRtdProvider.js';
+import { server } from 'test/mocks/xhr.js';
+import { hook } from '../../../src/hook.js';
+import { EVENTS } from 'src/constants.js';
+const responseHeader = { 'Content-Type': 'application/json' };
+
+let events = require('src/events');
+
+describe('symitriDapRtdProvider', function () {
+ const testReqBidsConfigObj = {
+ adUnits: [
+ {
+ bids: ['bid1', 'bid2'],
+ },
+ ],
+ };
+
+ const onDone = function () {
+ return true;
+ };
+
+ const sampleGdprConsentConfig = {
+ gdpr: {
+ consentString: null,
+ vendorData: {},
+ gdprApplies: true,
+ },
+ };
+
+ const sampleUspConsentConfig = {
+ usp: '1YYY',
+ };
+
+ const sampleIdentity = {
+ type: 'dap-signature:1.0.0',
+ };
+
+ const cmoduleConfig = {
+ name: 'symitriDap',
+ waitForIt: true,
+ params: {
+ apiHostname: 'prebid.dap.akadns.net',
+ apiVersion: 'x1',
+ apiAuthToken: 'Token 1234',
+ domain: 'prebid.org',
+ identityType: 'dap-signature:1.0.0',
+ segtax: 503,
+ },
+ };
+
+ const emoduleConfig = {
+ name: 'symitriDap',
+ waitForIt: true,
+ params: {
+ apiHostname: 'prebid.dap.akadns.net',
+ apiVersion: 'x1',
+ domain: 'prebid.org',
+ identityType: 'dap-signature:1.0.0',
+ segtax: 504,
+ pixelUrl: 'https://www.test.com/pixel',
+ },
+ };
+
+ const sampleConfig = {
+ api_hostname: 'prebid.dap.akadns.net',
+ api_version: 'x1',
+ domain: 'prebid.org',
+ segtax: 503,
+ identity: sampleIdentity,
+ };
+
+ const esampleConfig = {
+ api_hostname: 'prebid.dap.akadns.net',
+ api_version: 'x1',
+ domain: 'prebid.org',
+ segtax: 504,
+ identity: sampleIdentity,
+ };
+ let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds
+ const sampleCachedToken = {
+ expires_at: cacheExpiry,
+ token: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM',
+ };
+ const cachedEncryptedMembership = {
+ expires_at: cacheExpiry,
+ encryptedSegments:
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI',
+ };
+ const cachedMembership = {
+ expires_at: cacheExpiry,
+ said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk',
+ cohorts: ['9', '11', '13'],
+ };
+ const cachedMembershipWithDeals = {
+ expires_at: cacheExpiry,
+ said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk',
+ cohorts: ['9', '11', '13'],
+ deals: [
+ '{"id":"DEMODEAL555","bidfloor":5.0,"at":1,"guar":0}',
+ '{"id":"DEMODEAL111","bidfloor":5.0,"at":1,"guar":0}',
+ '{"id":"DEMODEAL123","bidfloor":5.0,"at":1,"guar":0}',
+ ],
+ };
+ const rtdUserObj = {
+ name: 'www.dataprovider3.com',
+ ext: {
+ taxonomyname: 'iab_audience_taxonomy',
+ },
+ segment: [
+ {
+ id: '1918',
+ },
+ {
+ id: '1939',
+ },
+ ],
+ };
+
+ const encRtdUserObj = {
+ name: 'www.dataprovider3.com',
+ ext: {
+ segtax: 504,
+ taxonomyname: 'iab_audience_taxonomy',
+ },
+ segment: [],
+ };
+
+ const cachedRtd = {
+ rtd: {
+ ortb2: {
+ user: {
+ data: [rtdUserObj],
+ },
+ },
+ },
+ };
+
+ let membership = {
+ said: cachedMembership.said,
+ cohorts: cachedMembership.cohorts,
+ attributes: null,
+ };
+ let encMembership = {
+ encryptedSegments: cachedEncryptedMembership.encryptedSegments,
+ };
+ encRtdUserObj.segment.push({ id: encMembership.encryptedSegments });
+ const cachedEncRtd = {
+ rtd: {
+ ortb2: {
+ user: {
+ data: [encRtdUserObj],
+ },
+ },
+ },
+ };
+
+ before(() => {
+ hook.ready();
+ });
+
+ let ortb2, bidConfig;
+
+ beforeEach(function () {
+ bidConfig = { ortb2Fragments: {} };
+ ortb2 = bidConfig.ortb2Fragments.global = {};
+ config.resetConfig();
+ storage.removeDataFromLocalStorage(DAP_TOKEN);
+ storage.removeDataFromLocalStorage(DAP_MEMBERSHIP);
+ storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP);
+ storage.removeDataFromLocalStorage(DAP_SS_ID);
+ });
+
+ afterEach(function () {});
+
+ describe('symitriDapRtdSubmodule', function () {
+ it('successfully instantiates', function () {
+ expect(symitriDapRtdSubmodule.init()).to.equal(true);
+ });
+ });
+
+ describe('Get Real-Time Data', function () {
+ it('gets rtd from local storage cache', function () {
+ let dapGetMembershipFromLocalStorageStub = sinon
+ .stub(dapUtils, 'dapGetMembershipFromLocalStorage')
+ .returns(membership);
+ let dapGetRtdObjStub = sinon
+ .stub(dapUtils, 'dapGetRtdObj')
+ .returns(cachedRtd);
+ let dapGetEncryptedMembershipFromLocalStorageStub = sinon
+ .stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage')
+ .returns(encMembership);
+ let dapGetEncryptedRtdObjStub = sinon
+ .stub(dapUtils, 'dapGetEncryptedRtdObj')
+ .returns(cachedEncRtd);
+ let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs');
+ try {
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ expect(ortb2).to.eql({});
+ generateRealTimeData(bidConfig, () => {}, emoduleConfig, {});
+
+ expect(ortb2.user.data).to.deep.include.members([
+ encRtdUserObj,
+ ]);
+ generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {});
+ expect(ortb2.user.data).to.deep.include.members([rtdUserObj]);
+ } finally {
+ dapGetRtdObjStub.restore();
+ dapGetMembershipFromLocalStorageStub.restore();
+ dapGetEncryptedRtdObjStub.restore();
+ dapGetEncryptedMembershipFromLocalStorageStub.restore();
+ callDapApisStub.restore();
+ }
+ });
+ });
+
+ describe('calling DAP APIs', function () {
+ it('Calls callDapAPIs for unencrypted segments flow', function () {
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ let dapExtractExpiryFromTokenStub = sinon
+ .stub(dapUtils, 'dapExtractExpiryFromToken')
+ .returns(cacheExpiry);
+ try {
+ expect(ortb2).to.eql({});
+ dapUtils.callDapAPIs(bidConfig, () => {}, cmoduleConfig, {});
+ let membership = {
+ cohorts: ['9', '11', '13'],
+ said: 'sample-said',
+ };
+ let membershipRequest = server.requests[0];
+ membershipRequest.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(membership)
+ );
+ let tokenWithExpiry = 'Sample-token-with-exp';
+ let tokenizeRequest = server.requests[1];
+ responseHeader['Symitri-DAP-Token'] = tokenWithExpiry;
+ tokenizeRequest.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(tokenWithExpiry)
+ );
+ let data = dapUtils.dapGetRtdObj(
+ membership,
+ cmoduleConfig.params.segtax
+ );
+ expect(ortb2.user.data).to.deep.include.members(
+ data.rtd.ortb2.user.data
+ );
+ } finally {
+ dapExtractExpiryFromTokenStub.restore();
+ }
+ });
+
+ it('Calls callDapAPIs for encrypted segments flow', function () {
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ let dapExtractExpiryFromTokenStub = sinon
+ .stub(dapUtils, 'dapExtractExpiryFromToken')
+ .returns(cacheExpiry);
+ try {
+ expect(ortb2).to.eql({});
+ dapUtils.callDapAPIs(bidConfig, () => {}, emoduleConfig, {});
+ let encMembership = 'Sample-enc-token';
+ let membershipRequest = server.requests[0];
+ responseHeader['Symitri-DAP-Token'] = encMembership;
+ membershipRequest.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(encMembership)
+ );
+ let tokenWithExpiry = 'Sample-token-with-exp';
+ let tokenizeRequest = server.requests[1];
+ responseHeader['Symitri-DAP-Token'] = tokenWithExpiry;
+ tokenizeRequest.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(tokenWithExpiry)
+ );
+ let data = dapUtils.dapGetEncryptedRtdObj(
+ { encryptedSegments: encMembership },
+ emoduleConfig.params.segtax
+ );
+ expect(ortb2.user.data).to.deep.include.members(
+ data.rtd.ortb2.user.data
+ );
+ } finally {
+ dapExtractExpiryFromTokenStub.restore();
+ }
+ });
+ });
+
+ describe('dapTokenize', function () {
+ it('dapTokenize error callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(sampleConfig));
+ let submoduleCallback = dapUtils.dapTokenize(
+ configAsync,
+ sampleIdentity,
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(400, responseHeader, JSON.stringify('error'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+
+ it('dapTokenize success callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(sampleConfig));
+ let submoduleCallback = dapUtils.dapTokenize(
+ configAsync,
+ sampleIdentity,
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify('success'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+ });
+
+ describe('dapTokenize and dapMembership incorrect params', function () {
+ it('Onerror and config are null', function () {
+ expect(
+ dapUtils.dapTokenize(null, 'identity', onDone, null, null)
+ ).to.be.equal(undefined);
+ expect(
+ dapUtils.dapMembership(null, 'identity', onDone, null, null)
+ ).to.be.equal(undefined);
+ expect(
+ dapUtils.dapEncryptedMembership(
+ null,
+ 'identity',
+ onDone,
+ null,
+ null
+ )
+ ).to.be.equal(undefined);
+ const config = {
+ api_hostname: 'prebid.dap.akadns.net',
+ api_version: 1,
+ domain: '',
+ segtax: 503,
+ };
+ const encConfig = {
+ api_hostname: 'prebid.dap.akadns.net',
+ api_version: 1,
+ domain: '',
+ segtax: 504,
+ };
+ let identity = {
+ type: 'dap-signature:1.0.0',
+ };
+ expect(
+ dapUtils.dapTokenize(config, identity, onDone, null, null)
+ ).to.be.equal(undefined);
+ expect(
+ dapUtils.dapMembership(config, 'token', onDone, null, null)
+ ).to.be.equal(undefined);
+ expect(
+ dapUtils.dapEncryptedMembership(
+ encConfig,
+ 'token',
+ onDone,
+ null,
+ null
+ )
+ ).to.be.equal(undefined);
+ });
+ });
+
+ describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () {
+ it('dapGetTokenFromLocalStorage success', function () {
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(
+ sampleCachedToken.token
+ );
+ });
+
+ it('dapGetMembershipFromLocalStorage success', function () {
+ storage.setDataInLocalStorage(
+ DAP_MEMBERSHIP,
+ JSON.stringify(cachedMembership)
+ );
+ expect(
+ JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())
+ ).to.be.equal(JSON.stringify(membership));
+ });
+
+ it('dapGetEncryptedMembershipFromLocalStorage success', function () {
+ storage.setDataInLocalStorage(
+ DAP_ENCRYPTED_MEMBERSHIP,
+ JSON.stringify(cachedEncryptedMembership)
+ );
+ expect(
+ JSON.stringify(
+ dapUtils.dapGetEncryptedMembershipFromLocalStorage()
+ )
+ ).to.be.equal(JSON.stringify(encMembership));
+ });
+ });
+
+ describe('dapMembership', function () {
+ it('dapMembership success callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(sampleConfig));
+ let submoduleCallback = dapUtils.dapMembership(
+ configAsync,
+ 'token',
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify('success'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+
+ it('dapMembership error callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(sampleConfig));
+ let submoduleCallback = dapUtils.dapMembership(
+ configAsync,
+ 'token',
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(400, responseHeader, JSON.stringify('error'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+ });
+
+ describe('dapEncMembership', function () {
+ it('dapEncMembership success callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(esampleConfig));
+ let submoduleCallback = dapUtils.dapEncryptedMembership(
+ configAsync,
+ 'token',
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify('success'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+
+ it('dapEncMembership error callback', function () {
+ let configAsync = JSON.parse(JSON.stringify(esampleConfig));
+ let submoduleCallback = dapUtils.dapEncryptedMembership(
+ configAsync,
+ 'token',
+ onDone,
+ function (token, status, xhr, onDone) {},
+ function (xhr, status, error, onDone) {}
+ );
+ let request = server.requests[0];
+ request.respond(400, responseHeader, JSON.stringify('error'));
+ expect(submoduleCallback).to.equal(undefined);
+ });
+ });
+
+ describe('dapMembership', function () {
+ it('should invoke the getDapToken and getDapMembership', function () {
+ let membership = {
+ said: 'item.said1',
+ cohorts: 'item.cohorts',
+ attributes: null,
+ };
+
+ let getDapMembershipStub = sinon
+ .stub(dapUtils, 'dapGetMembershipFromLocalStorage')
+ .returns(membership);
+ let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs');
+ try {
+ generateRealTimeData(
+ testReqBidsConfigObj,
+ onDone,
+ cmoduleConfig
+ );
+ expect(getDapMembershipStub.calledOnce).to.be.equal(true);
+ } finally {
+ getDapMembershipStub.restore();
+ callDapApisStub.restore();
+ }
+ });
+ });
+
+ describe('dapEncMembership test', function () {
+ it('should invoke the getDapToken and getEncDapMembership', function () {
+ let encMembership = {
+ encryptedSegments: 'enc.seg',
+ };
+
+ let getDapEncMembershipStub = sinon
+ .stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage')
+ .returns(encMembership);
+ let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs');
+ try {
+ generateRealTimeData(
+ testReqBidsConfigObj,
+ onDone,
+ emoduleConfig
+ );
+ expect(getDapEncMembershipStub.calledOnce).to.be.equal(true);
+ } finally {
+ getDapEncMembershipStub.restore();
+ callDapApisStub.restore();
+ }
+ });
+ });
+
+ describe('dapGetRtdObj test', function () {
+ it('dapGetRtdObj', function () {
+ const config = {
+ apiHostname: 'prebid.dap.akadns.net',
+ apiVersion: 'x1',
+ domain: 'prebid.org',
+ segtax: 503,
+ };
+ expect(
+ dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)
+ ).to.equal(undefined);
+ const membership = { cohorts: ['1', '5', '7'] };
+ expect(
+ dapUtils.dapGetRtdObj(membership, config.segtax)
+ ).to.not.equal(undefined);
+ });
+ });
+
+ describe('checkAndAddRealtimeData test', function () {
+ it('add realtime data for segtax 503 and 504', function () {
+ dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504);
+ dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504);
+ expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]);
+ dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503);
+ expect(ortb2.user.data).to.deep.include.members([rtdUserObj]);
+ });
+ });
+
+ describe('dapExtractExpiryFromToken test', function () {
+ it('test dapExtractExpiryFromToken function', function () {
+ let tokenWithoutExpiry =
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM';
+ expect(
+ dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)
+ ).to.equal(undefined);
+ let tokenWithExpiry =
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw';
+ expect(
+ dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)
+ ).to.equal(1643830369);
+ });
+ });
+
+ describe('dapRefreshToken test', function () {
+ it('test dapRefreshToken success response', function () {
+ dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone);
+ let request = server.requests[0];
+ responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token;
+ request.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(sampleCachedToken.token)
+ );
+ expect(
+ JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token
+ ).to.be.equal(sampleCachedToken.token);
+ });
+
+ it('test dapRefreshToken success response with deviceid 100', function () {
+ dapUtils.dapRefreshToken(ortb2, esampleConfig, true, onDone);
+ let request = server.requests[0];
+ responseHeader['Symitri-DAP-100'] = sampleCachedToken.token;
+ request.respond(200, responseHeader, '');
+ expect(
+ storage.getDataFromLocalStorage('dap_deviceId100')
+ ).to.be.equal(sampleCachedToken.token);
+ });
+
+ it('test dapRefreshToken success response with exp claim', function () {
+ dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone);
+ let request = server.requests[0];
+ let tokenWithExpiry =
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw';
+ responseHeader['Symitri-DAP-Token'] = tokenWithExpiry;
+ request.respond(
+ 200,
+ responseHeader,
+ JSON.stringify(tokenWithExpiry)
+ );
+ expect(
+ JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN))
+ .expires_at
+ ).to.be.equal(1643830359);
+ });
+
+ it('test dapRefreshToken error response', function () {
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone);
+ let request = server.requests[0];
+ request.respond(400, responseHeader, 'error');
+ expect(
+ JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN))
+ .expires_at
+ ).to.be.equal(cacheExpiry); // Since the expiry is same, the token is not updated in the cache
+ });
+ });
+
+ describe('dapRefreshEncryptedMembership test', function () {
+ it('test dapRefreshEncryptedMembership success response', function () {
+ let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds
+ let encMembership =
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw';
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ esampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ responseHeader['Symitri-DAP-Token'] = encMembership;
+ request.respond(200, responseHeader, encMembership);
+ let rtdObj = dapUtils.dapGetEncryptedRtdObj(
+ { encryptedSegments: encMembership },
+ 504
+ );
+ expect(ortb2.user.data).to.deep.include.members(
+ rtdObj.rtd.ortb2.user.data
+ );
+ expect(
+ JSON.parse(
+ storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)
+ ).expires_at
+ ).to.equal(expiry);
+ });
+
+ it('test dapRefreshEncryptedMembership success response with exp claim', function () {
+ let encMembership =
+ 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY';
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ esampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ responseHeader['Symitri-DAP-Token'] = encMembership;
+ request.respond(200, responseHeader, encMembership);
+ let rtdObj = dapUtils.dapGetEncryptedRtdObj(
+ { encryptedSegments: encMembership },
+ 504
+ );
+ expect(ortb2.user.data).to.deep.include.members(
+ rtdObj.rtd.ortb2.user.data
+ );
+ expect(
+ JSON.parse(
+ storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)
+ ).expires_at
+ ).to.equal(1643830630);
+ });
+
+ it('test dapRefreshEncryptedMembership error response', function () {
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ esampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(400, responseHeader, 'error');
+ expect(ortb2).to.eql({});
+ });
+
+ it('test dapRefreshEncryptedMembership 403 error response', function () {
+ dapUtils.dapRefreshEncryptedMembership(
+ ortb2,
+ esampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(403, responseHeader, 'error');
+ let requestTokenize = server.requests[1];
+ responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token;
+ requestTokenize.respond(200, responseHeader, '');
+ let requestMembership = server.requests[2];
+ requestMembership.respond(403, responseHeader, 'error');
+ expect(server.requests.length).to.be.equal(
+ DAP_MAX_RETRY_TOKENIZE + 2
+ );
+ });
+ });
+
+ describe('dapRefreshMembership test', function () {
+ it('test dapRefreshMembership success response', function () {
+ let membership = {
+ cohorts: ['9', '11', '13'],
+ said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU',
+ };
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ sampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify(membership));
+ let rtdObj = dapUtils.dapGetRtdObj(membership, 503);
+ expect(ortb2.user.data).to.deep.include.members(
+ rtdObj.rtd.ortb2.user.data
+ );
+ });
+
+ it('test dapRefreshMembership success response with exp claim', function () {
+ let membership = {
+ cohorts: ['9', '11', '13'],
+ said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE',
+ };
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ sampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify(membership));
+ let rtdObj = dapUtils.dapGetRtdObj(membership, 503);
+ expect(ortb2.user.data).to.deep.include.members(
+ rtdObj.rtd.ortb2.user.data
+ );
+ expect(
+ JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP))
+ .expires_at
+ ).to.be.equal(1647971548);
+ });
+
+ it('test dapRefreshMembership 400 error response', function () {
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ sampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(400, responseHeader, 'error');
+ expect(ortb2).to.eql({});
+ });
+
+ it('test dapRefreshMembership 403 error response', function () {
+ dapUtils.dapRefreshMembership(
+ ortb2,
+ sampleConfig,
+ sampleCachedToken.token,
+ onDone
+ );
+ let request = server.requests[0];
+ request.respond(403, responseHeader, 'error');
+ expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE);
+ });
+ });
+
+ describe('dapGetEncryptedMembershipFromLocalStorage test', function () {
+ it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () {
+ storage.setDataInLocalStorage(
+ DAP_ENCRYPTED_MEMBERSHIP,
+ JSON.stringify(cachedEncryptedMembership)
+ );
+ expect(
+ JSON.stringify(
+ dapUtils.dapGetEncryptedMembershipFromLocalStorage()
+ )
+ ).to.equal(JSON.stringify(encMembership));
+ });
+
+ it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () {
+ let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds
+ let encMembership = {
+ expiry: expiry,
+ encryptedSegments: cachedEncryptedMembership.encryptedSegments,
+ };
+ storage.setDataInLocalStorage(
+ DAP_ENCRYPTED_MEMBERSHIP,
+ JSON.stringify(encMembership)
+ );
+ expect(
+ dapUtils.dapGetEncryptedMembershipFromLocalStorage()
+ ).to.equal(null);
+ });
+ });
+
+ describe('Symitri-DAP-SS-ID test', function () {
+ it('Symitri-DAP-SS-ID present in response header', function () {
+ let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds
+ dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone);
+ let request = server.requests[0];
+ let sampleSSID = 'Test_SSID_Spec';
+ responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token;
+ responseHeader['Symitri-DAP-SS-ID'] = sampleSSID;
+ request.respond(200, responseHeader, '');
+ expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(
+ JSON.stringify(sampleSSID)
+ );
+ });
+
+ it('Test if Symitri-DAP-SS-ID is present in request header', function () {
+ let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds
+ storage.setDataInLocalStorage(
+ DAP_SS_ID,
+ JSON.stringify('Test_SSID_Spec')
+ );
+ dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone);
+ let request = server.requests[0];
+ let ssidHeader = request.requestHeaders['Symitri-DAP-SS-ID'];
+ responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token;
+ request.respond(200, responseHeader, '');
+ expect(ssidHeader).to.be.equal('Test_SSID_Spec');
+ });
+ });
+
+ describe('Test gdpr and usp consent handling', function () {
+ it('Gdpr applies and gdpr consent string not present', function () {
+ expect(
+ symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig)
+ ).to.equal(false);
+ });
+
+ it('Gdpr applies and gdpr consent string is present', function () {
+ sampleGdprConsentConfig.gdpr.consentString =
+ 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==';
+ expect(
+ symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig)
+ ).to.equal(true);
+ });
+
+ it('USP consent present and user have opted out', function () {
+ expect(
+ symitriDapRtdSubmodule.init(null, sampleUspConsentConfig)
+ ).to.equal(false);
+ });
+
+ it('USP consent present and user have not been provided with option to opt out', function () {
+ expect(symitriDapRtdSubmodule.init(null, { usp: '1NYY' })).to.equal(
+ false
+ );
+ });
+
+ it('USP consent present and user have not opted out', function () {
+ expect(symitriDapRtdSubmodule.init(null, { usp: '1YNY' })).to.equal(
+ true
+ );
+ });
+ });
+
+ describe('Test identifier is added properly to apiParams', function () {
+ let sandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.sandbox.create();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ it('passed identifier is handled', async function () {
+ const test_identity = 'test_identity_1234';
+ let identity = {
+ value: test_identity,
+ };
+ let apiParams = {
+ type: identity.type,
+ };
+
+ if (window.crypto && window.crypto.subtle) {
+ let hid = await dapUtils
+ .addIdentifier(identity, apiParams)
+ .then();
+ expect(hid['identity']).is.equal(
+ '843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'
+ );
+ } else {
+ expect(window.crypto.subtle).is.undefined;
+ }
+ });
+
+ it('passed undefined identifier is handled', async function () {
+ const test_identity = undefined;
+ let identity = {
+ identity: test_identity,
+ };
+ let apiParams = {
+ type: identity.type,
+ };
+
+ let hid = await dapUtils.addIdentifier(identity, apiParams);
+ expect(hid.identity).is.undefined;
+ });
+ });
+
+ describe('onBidResponseEvent', function () {
+ const bidResponse = {
+ adId: 'ad_123',
+ bidder: 'test_bidder',
+ bidderCode: 'test_bidder_code',
+ cpm: '1.5',
+ creativeId: 'creative_123',
+ dealId: 'DEMODEAL555',
+ mediaType: 'banner',
+ responseTimestamp: '1725892736147',
+ ad: '',
+ };
+ let url =
+ emoduleConfig.params.pixelUrl +
+ '?token=' +
+ sampleCachedToken.token +
+ '&ad_id=' +
+ bidResponse.adId +
+ '&bidder=' +
+ bidResponse.bidder +
+ '&bidder_code=' +
+ bidResponse.bidderCode +
+ '&cpm=' +
+ bidResponse.cpm +
+ '&creative_id=' +
+ bidResponse.creativeId +
+ '&deal_id=' +
+ bidResponse.dealId +
+ '&media_type=' +
+ bidResponse.mediaType +
+ '&response_timestamp=' +
+ bidResponse.responseTimestamp;
+ let adPixel = `${bidResponse.ad}`;
+ it('should add pixel to "BidResponse" ad', function () {
+ storage.setDataInLocalStorage(
+ DAP_MEMBERSHIP,
+ JSON.stringify(cachedMembershipWithDeals)
+ );
+ storage.setDataInLocalStorage(
+ DAP_TOKEN,
+ JSON.stringify(sampleCachedToken)
+ );
+ symitriDapRtdSubmodule.onBidResponseEvent(
+ bidResponse,
+ emoduleConfig
+ );
+ expect(bidResponse.ad).to.equal(adPixel);
+ });
+ });
+});