2025-03-22 00:49:46 +01:00

690 lines
21 KiB
JavaScript

//sovrn beacon standalone .js
window.sovrn = window.sovrn || {};
sovrn.auction = sovrn.auction || {};
let beaconFlag = false;
let cmpVersion = 0;
sovrn.auction = {
doNotTrack: function (nav, win) {
nav = nav || navigator;
win = win || window;
var optOutCookie = sovrn.auction.readCookie('tracking_optout');
return nav.doNotTrack === 'yes'
|| nav.doNotTrack === '1'
|| nav.msDoNotTrack === '1'
|| win.doNotTrack === '1'
|| optOutCookie === '1';
},
readCookie: function (name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
},
sendBeacon: function () {
sovrn.auction.beaconConfig = sovrn.auction.getParams(sovrn.auction.getScriptTag());
try {
var id, beacon_frame, config;
//Don't fire the beacon if it has already been fired
if (beaconFlag) {
return false;
}
id = 'sovrn_beacon';//this ends up being the iframe id for the beacon so could be anything not necessarily tied to a zone id
beacon_frame = sovrn.auction.createiFrame(id, 1, 1);
beacon_frame.src = sovrn.auction.getBeaconURL();
document.body.appendChild(beacon_frame);
beaconFlag = true;
} catch (e) {
return false;
}
return true;
},
getParams: function (currentScript) {
var currentTagID = currentScript.getAttribute("id");
var currentTag = document.getElementById(currentTagID);
if (currentTag == null) {
return false;
}
currentTagSRC = currentTag.src;
//snag params from script_tags
var query_string, qs_obj;
qs_obj = {};
query_string = currentTagSRC.split('?')[1] || '';
query_string = query_string.split('#')[0] || ''; //Deal with hashes
if (!query_string) return {};
query_string.replace(
new RegExp("([^?=&]+)(=([^&]*))?", "g"),
function ($0, $1, $2, $3) {
try {
qs_obj[$1] = decodeURIComponent($3);
} catch (e) {
sovrn.ads.dbg(e);
}
}
);
qs_obj.currentTag = currentTagID;
qs_obj.location = currentTag.parentNode.nodeName;
return qs_obj;
//
},
getScriptTag: function () {
var tag_regex = /^(https?:)?\/\/.*\.lijit\.(com|dev)\/www\/sovrn_beacon_standalone\/sovrn_standalone_beacon(\.min)?\.js/i;
//var tag_regex = /^(https?:)?\/\/.*\.lijit\.(com|dev)\/www\/delivery\/sovrn_stndalne_beacon(\.min)?\.js/i;
var script_tags, i, cur_script,
script_pattern = tag_regex;
if ('currentScript' in document) {
cur_script = document.currentScript;
if (cur_script && script_pattern.test(cur_script.src)) {
return cur_script;
}
}
//Start from the last tag on the page and work backwards
script_tags = document.getElementsByTagName('script');
for (i = script_tags.length - 1; i >= 0; i--) {
if (script_pattern.test(script_tags[i].src)) {
return script_tags[i];
}
}
return null;
},
//
createiFrame: function (id, w, h) {
var ifr, iframe_style, i, j, attr, styles;
ifr = document.createElement('iframe');
iframe_style = ifr.style;
attr = {
id: id,
margin: '0',
padding: '0',
frameborder: '0',
width: w + '',
height: h + '',
scrolling: 'no',
src: 'about:blank'
};
styles = {
margin: '0px',
padding: '0px',
border: '0px none',
width: w + 'px',
height: h + 'px',
overflow: 'hidden'
};
//Loop through the iframe attributes and set them
for (i in attr) {
if (attr.hasOwnProperty(i)) {
ifr.setAttribute(i, attr[i]);
}
}
//Loop through all the styles and apply them
for (j in styles) {
if (styles.hasOwnProperty(j)) {
try {
iframe_style[j] = styles[j];
} catch (e) {
}
}
}
return ifr;
},//end create Iframe
getBeaconURL: function () {
var informerId = (sovrn.auction.beaconConfig.hasOwnProperty("iid")) ? sovrn.auction.beaconConfig.iid : "",
gdprConsentString = sovrn.auction.gdprConsent || "",
ccpaConsentString = sovrn.auction.ccpaConsent || "",
gppConsentString = sovrn.auction.gppConsent || "",
gppSidString = sovrn.auction.gppSid || "";
return 'https://ce.lijit.com/beacon?informer=' + informerId + '&gdpr_consent=' + gdprConsentString + '&us_privacy=' + ccpaConsentString
+ '&gpp=' + gppConsentString + '&gpp_sid=' + gppSidString;
},
sovrnReady: function (f) {
/in/.test(document.readyState) ? setTimeout(function () {sovrn.auction.sovrnReady(f)}, 50) : f()
},
configureConsents: function() {
const _this = this;
_this.lookupIabConsent(
function(cmpResponse) {
if (cmpVersion === 1) {
sovrn.auction.gdprConsent = cmpResponse.getConsentData.consentData;
} else {
sovrn.auction.gdprConsent = cmpResponse.tcString;
}
_this.configureCcpa();
},
function(e) {
_this.configureCcpa();
});
},
configureCcpa: function() {
const _this = this;
this.lookupCcpaConsent(
function(apiResponse) {
sovrn.auction.ccpaConsent = apiResponse.uspData;
_this.configureGpp();
},
// ignoring errors for now - may want them to be tracked later
function(e) {
_this.configureGpp();
})
},
configureGpp: function () {
const _this = this;
this.lookupIabConsentForGpp(
function (apiResponse) {
sovrn.auction.gppConsent = apiResponse.gppString;
sovrn.auction.gppSid = apiResponse.applicableSections;
_this.sendBeacon();
},
function (e) {
_this.sendBeacon();
})
},
callCmpFromSafeframe: function(commandName, dataName, callback, errorCallback) {
const sfCallback = function(msgName, data) {
if (msgName === 'cmpReturn') {
callback(data[dataName]);
} else {
errorCallback()
}
};
window.$sf.ext.register(1, 1, sfCallback);
window.$sf.ext.cmp(commandName);
},
cmpCallbacks: {},
// Taken mostly from example from
// Quantcast's CMP JS API Spec pdf
// And from Prebid consent management module
callCmpWhileInIframe: function(commandName, cmpFrame, callbackFunc, errorCallback) {
const apiName = (cmpVersion === 2) ? '__tcfapi' : '__cmp';
const callId = Math.random().toString();
const callName = `${apiName}Call`;
const msg = {
[callName]: {
command: commandName,
parameter: null,
callId: callId
}
};
if (cmpVersion !== 1) {
msg[callName].version = cmpVersion;
}
sovrn.auction.cmpCallbacks[callId] = callbackFunc;
cmpFrame.postMessage(msg, '*');
/** when we get the return message, call the stashed callback */
window.addEventListener('message', readPostMessageResponse, false);
function readPostMessageResponse(event) {
const cmpDataPkgName = `${apiName}Return`;
let json = {};
try {
json = typeof event.data === 'string' ? JSON.parse(event.data) :
event.data;
} catch (ignore) {}
if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) {
const payload = json[cmpDataPkgName];
if (typeof sovrn.auction.cmpCallbacks[payload.callId] === 'function') {
sovrn.auction.cmpCallbacks[payload.callId](payload.returnValue,
payload.success);
} else {
errorCallback('Callback must be function type.')
}
}
}
},
/**
* Find CMP if exists & read from it.
*
* @param cmpSuccess acts as a success callback when CMP returns a value
* @param cmpError acts as an error callback, passes an error message
*/
lookupIabConsent: function(cmpSuccess, cmpError) {
function findCMP() {
let f = window;
let cmpFrame;
let cmpFunction;
while (!cmpFrame) {
try {
if (typeof f.__tcfapi === 'function' || typeof f.__cmp ===
'function') {
if (typeof f.__tcfapi === 'function') {
cmpVersion = 2;
cmpFunction = f.__tcfapi;
} else {
cmpVersion = 1;
cmpFunction = f.__cmp;
}
cmpFrame = f;
break;
}
} catch (e) {}
// need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env
try {
if (f.frames['__tcfapiLocator']) {
cmpVersion = 2;
cmpFrame = f;
break;
}
} catch (e) {}
try {
if (f.frames['__cmpLocator']) {
cmpVersion = 1;
cmpFrame = f;
break;
}
} catch (e) {}
if (f === window.top) {
break;
}
f = f.parent;
}
return {
cmpFrame,
cmpFunction
};
}
function v2CmpResponseCallback(tcfData, success) {
if (success && typeof tcfData.tcString === "string") {
if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus ===
'useractioncomplete') {
cmpSuccess(tcfData);
} else if (tcfData.eventStatus === 'cmpuishown' &&
tcfData.tcString.length > 0 && tcfData.purposeOneTreatment ===
true) {
cmpSuccess(tcfData);
} else {
cmpError('Consent string is not available.');
}
} else {
cmpError(
'CMP unable to register callback function. Please check CMP setup.');
}
}
function handleV1CmpResponseCallbacks() {
const cmpResponse = {};
function afterEach() {
if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) {
cmpSuccess(cmpResponse);
} else if (cmpResponse.hasOwnProperty('getConsentData') && cmpResponse.hasOwnProperty('getVendorConsents') &&
(!cmpResponse.getConsentData || !cmpResponse.getVendorConsents)) {
cmpError('Consent string is blank.');
}
}
return {
consentDataCallback: function(consentResponse) {
cmpResponse.getConsentData = consentResponse;
afterEach();
},
vendorConsentsCallback: function(consentResponse) {
cmpResponse.getVendorConsents = consentResponse;
afterEach();
}
}
}
const v1CallbackHandler = handleV1CmpResponseCallbacks();
const {
cmpFrame,
cmpFunction
} = findCMP();
if (!cmpFrame) {
return cmpError('CMP not found.');
}
if (typeof cmpFunction === 'function') {
if (cmpVersion === 1) {
cmpFunction('getConsentData', null,
v1CallbackHandler.consentDataCallback);
cmpFunction('getVendorConsents', null,
v1CallbackHandler.vendorConsentsCallback);
} else if (cmpVersion === 2) {
cmpFunction('addEventListener', cmpVersion, v2CmpResponseCallback);
}
} else if (cmpVersion === 1 && !!(window.$sf && window.$sf.ext) &&
typeof window.$sf.ext.cmp ===
'function') {
// this safeframe workflow is only supported with TCF v1 spec; the v2 recommends to use the iframe postMessage route instead
// (even if you are in a safeframe).
this.callCmpFromSafeframe('getConsentData',
v1CallbackHandler.consentDataCallback, cmpError);
this.callCmpFromSafeframe('getVendorConsents',
v1CallbackHandler.vendorConsentsCallback, cmpError);
} else {
if (cmpVersion === 1) {
this.callCmpWhileInIframe('getConsentData', cmpFrame,
v1CallbackHandler.consentDataCallback, cmpError);
this.callCmpWhileInIframe('getVendorConsents', cmpFrame,
v1CallbackHandler.vendorConsentsCallback, cmpError);
} else if (cmpVersion === 2) {
this.callCmpWhileInIframe('addEventListener', cmpFrame,
v2CmpResponseCallback, cmpError);
}
}
},
/**
* Find CCPA API if it exists and query it
*
* @param apiSuccess acts as a success callback when CMP returns a value
* @param apiError acts as an error callback, passes an error message
*/
lookupCcpaConsent: function(apiSuccess, apiError) {
var _this = this,
apiFunction,
apiCommand = 'getUSPData',
apiVersion = 1,
apiResponse = {
uspData: '',
success: false
},
apiCallback = function(uspData, success) {
apiResponse.uspData = uspData && uspData.uspString || "";
apiResponse.success = success;
if (success) {
apiSuccess(apiResponse);
} else {
apiError();
}
};
try {
apiFunction = window.__uspapi || window.top.__uspapi;
} catch (e) {
apiError(e)
}
if (typeof apiFunction === 'function') {
apiFunction(apiCommand, apiVersion, apiCallback);
} else {
// Assuming safeframes with no api proxy, or child iframe
var f = window, apiFrame, uniqueId = (new Date()).getTime();
while (!apiFrame) {
try {
if (f.frames['__uspapiLocator']) {
apiFrame = f;
}
} catch (e) {
apiError(e);
return;
}
if (f === window.top) {
apiError();
return;
}
f = f.parent;
}
// Found frame to post a message to
var messageToApiWindow = {
__uspapiCall: {
command: apiCommand,
version: apiVersion,
parameter: null,
callId: uniqueId
}
};
apiFrame.postMessage(messageToApiWindow, '*');
// Event handler for response
window.addEventListener("message", function(event) {
if (event.data && event.data.__uspapiReturn && (event.data.__uspapiReturn.callId === uniqueId)) {
if (event.data.__uspapiReturn.success) {
apiSuccess(event.data.__uspapiReturn.returnValue)
} else {
apiError()
}
}
}, false);
}
},
cmpClient: function (
{
apiName,
apiVersion,
apiArgs = ['command', 'callback', 'parameter', 'version'],
callbackArgs = ['returnValue', 'success'],
},
win = window
){
const cmpCallbacks = {};
const callName = `${apiName}Call`;
const cmpDataPkgName = `${apiName}Return`;
function handleMessage(event) {
const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data;
if (json?.[cmpDataPkgName]?.callId) {
const payload = json[cmpDataPkgName];
if (cmpCallbacks.hasOwnProperty(payload.callId)) {
cmpCallbacks[payload.callId](...callbackArgs.map(name => payload[name]));
}
}
}
function findCMP() {
let f = win;
let cmpFrame;
let isDirect = false;
while (f != null) {
try {
if (typeof f[apiName] === 'function') {
cmpFrame = f;
isDirect = true;
break;
}
} catch (e) {}
// need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env
try {
if (f.frames[`${apiName}Locator`]) {
cmpFrame = f;
break;
}
} catch (e) {}
if (f === win.top) break;
f = f.parent;
}
return [
cmpFrame,
isDirect
];
}
const [cmpFrame, isDirect] = findCMP();
if (!cmpFrame) {
return;
}
function resolveParams(params) {
params = Object.assign({version: apiVersion}, params);
return apiArgs.map(arg => [arg, params[arg]])
}
function wrapCallback(callback, resolve, reject, preamble) {
return function (result, success) {
preamble && preamble();
const resolver = success == null || success ? resolve : reject;
if (typeof callback === 'function') {
resolver();
return callback.apply(this, arguments);
} else {
resolver(result);
}
}
}
let client;
if (isDirect) {
client = function invokeCMPDirect(params = {}) {
return new Promise((resolve, reject) => {
const ret = cmpFrame[apiName](...resolveParams({
...params,
callback: params.callback && wrapCallback(params.callback, resolve, reject)
}).map(([_, val]) => val));
if (params.callback == null) {
resolve(ret);
}
});
};
} else {
win.addEventListener('message', handleMessage, false);
client = function invokeCMPFrame(params) {
return new Promise((resolve, reject) => {
// call CMP via postMessage
const callId = Math.random().toString();
const msg = {
[callName]: {
...Object.fromEntries(resolveParams(params).filter(([param]) => param !== 'callback')),
callId: callId
}
};
cmpCallbacks[callId] = wrapCallback(params?.callback, resolve, reject, params?.callback == null && (() => {
delete cmpCallbacks[callId]
}));
cmpFrame.postMessage(msg, '*');
});
};
}
client.isDirect = isDirect;
return client;
},
lookupIabConsentForGpp: function (onSuccess, onError) {
const _this = this;
const cmp = _this.cmpClient({
apiName: '__gpp',
apiVersion: 1,
});
if (!cmp) {
return onError('GPP CMP not found.');
}
cmp({
command: 'addEventListener',
callback: function (evt) {
if (evt) {
if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') {
cmp({command: 'getGPPData'}).then((gppData) => {
return Promise.all(
(gppData?.pingData?.supportedAPIs || [])
.map((name) => cmp({command: 'getSection', parameter: name})
.catch(() => {})
.then((res) => [name, res]))
).then((sections) => {
const sectionData = Object.fromEntries(sections.filter(([_, val]) => val != null));
processCmpData({gppData, sectionData}, {onSuccess, onError});
})
});
} else if (evt.pingData.cmpStatus === 'error') {
onError('CMP returned with a cmpStatus:error response. Check CMP setup.');
}
}
}
});
function processCmpData(consentData, {onSuccess, onError}) {
function checkData() {
const gppString = consentData?.gppData?.gppString;
const gppSection = consentData?.gppData?.applicableSections;
return !!(
(!Array.isArray(gppSection)) ||
(Array.isArray(gppSection) && (!gppString || !isStr(gppString)))
);
}
if (checkData()) {
onError(`CMP returned unexpected value during lookup process.`, consentData);
} else {
onSuccess(storeConsentData(consentData));
}
}
function storeConsentData({gppData, sectionData} = {}) {
let consentData = {
gppString: (gppData) ? gppData.gppString : undefined,
gppData: (gppData) || undefined,
};
consentData.applicableSections = applicableSections(gppData);
consentData.apiVersion = 1;
consentData.sectionData = sectionData;
return consentData;
}
function applicableSections(gppData) {
return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0
? gppData.applicableSections
: [];
}
function isStr(object){
return Object.prototype.toString.call(object) === '[object ' + 'String' + ']';
}
}
};
sovrn.auction.sovrnReady(function () {
let dnt = sovrn.auction.doNotTrack();
if (dnt === false) {
sovrn.auction.configureConsents();
}
});