Skip to content

Commit

Permalink
New Teads User ID Module
Browse files Browse the repository at this point in the history
  • Loading branch information
github-tom-kuhnen committed Sep 28, 2022
1 parent f121e2a commit 848b2bb
Show file tree
Hide file tree
Showing 4 changed files with 528 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"quantcastIdSystem",
"sharedIdSystem",
"tapadIdSystem",
"teadsIdSystem",
"tncIdSystem",
"trustpidSystem",
"uid2IdSystem",
Expand Down
234 changes: 234 additions & 0 deletions modules/teadsIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/**
* This module adds TeadsId to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/teadsIdSystem
* @requires module:modules/userId
*/

import {isStr, isNumber, logError, logInfo, isEmpty, timestamp} from '../src/utils.js'
import {ajax} from '../src/ajax.js';
import {submodule} from '../src/hook.js';
import {getStorageManager} from '../src/storageManager.js';
import {uspDataHandler} from '../src/adapterManager.js';

const MODULE_NAME = 'teadsId';
const GVL_ID = 132;
const FP_TEADS_ID_COOKIE_NAME = '_tfpvi';
const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT';

export const gdprStatus = {
GDPR_DOESNT_APPLY: 0,
CMP_NOT_FOUND_OR_ERROR: 22,
GDPR_APPLIES_PUBLISHER: 12,
};

export const gdprReason = {
GDPR_DOESNT_APPLY: 0,
CMP_NOT_FOUND: 220,
GDPR_APPLIES_PUBLISHER_CLASSIC: 120,
};

export const storage = getStorageManager({gvlid: GVL_ID, moduleName: MODULE_NAME});

/** @type {Submodule} */
export const teadsIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
/**
* decode the stored id value for passing to bid requests
* @function
* @param {string} value
* @returns {{teadsId:string}}
*/
decode(value) {
return {teadsId: value}
},
/**
* performs action to obtain id and return a value in the callback's response argument
* @function
* @param {SubmoduleConfig} [submoduleConfig]
* @param {ConsentData} [consentData]
* @returns {IdResponse|undefined}
*/
getId(submoduleConfig, consentData) {
const resp = function (callback) {
const url = buildAnalyticsTagUrl(submoduleConfig, consentData);

const callbacks = {
success: (bodyResponse, responseObj) => {
if (responseObj && responseObj.status === 200) {
if (isStr(bodyResponse) && !isEmpty(bodyResponse)) {
const cookiesMaxAge = getTimestampFromDays(365); // 1 year
const expirationCookieDate = getCookieExpirationDate(cookiesMaxAge);
storage.setCookie(FP_TEADS_ID_COOKIE_NAME, bodyResponse, expirationCookieDate);
callback(bodyResponse);
} else {
storage.setCookie(FP_TEADS_ID_COOKIE_NAME, '', EXPIRED_COOKIE_DATE);
callback();
}
} else {
logInfo(`${MODULE_NAME}: Server error while fetching ID`);
callback();
}
},
error: error => {
logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
callback();
}
};

ajax(url, callbacks, undefined, {method: 'GET'});
};
return {callback: resp};
}
};

/**
* Build the full URL from the Submodule config & consentData
* @param submoduleConfig
* @param consentData
* @returns {string}
*/
export function buildAnalyticsTagUrl(submoduleConfig, consentData) {
const pubId = getPublisherId(submoduleConfig);
const teadsViewerId = getTeadsViewerId();
const status = getGdprStatus(consentData);
const gdprConsentString = getGdprConsentString(consentData);
const ccpaConsentString = getCcpaConsentString(uspDataHandler?.getConsentData());
const gdprReason = getGdprReasonFromStatus(status);
const params = {
analytics_tag_id: pubId,
tfpvi: teadsViewerId,
gdpr_consent: gdprConsentString,
gdpr_status: status,
gdpr_reason: gdprReason,
ccpa_consent: ccpaConsentString,
sv: 'prebid-v1',
};

const url = 'https://at.teads.tv/fpc';
const queryParams = new URLSearchParams();

for (const param in params) {
queryParams.append(param, params[param]);
}

return url + '?' + queryParams.toString();
}

/**
* Extract the Publisher ID from the Submodule config
* @returns {string}
* @param submoduleConfig
*/
export function getPublisherId(submoduleConfig) {
const pubId = submoduleConfig?.params?.pubId;
const prefix = 'PUB_';
if (isNumber(pubId)) {
return prefix + pubId.toString();
}
if (isStr(pubId) && parseInt(pubId)) {
return prefix + pubId;
}
return '';
}

/**
* Extract the GDPR status from the given consentData
* @param consentData
* @returns {number}
*/
export function getGdprStatus(consentData) {
const gdprApplies = consentData?.gdprApplies;
if (gdprApplies === true) {
return gdprStatus.GDPR_APPLIES_PUBLISHER;
} else if (gdprApplies === false) {
return gdprStatus.GDPR_DOESNT_APPLY;
} else {
return gdprStatus.CMP_NOT_FOUND_OR_ERROR;
}
}

/**
* Extract the GDPR consent string from the given consentData
* @param consentData
* @returns {string}
*/
export function getGdprConsentString(consentData) {
const consentString = consentData?.consentString;
if (isStr(consentString)) {
return consentString;
} else {
return '';
}
}

/**
* Map the GDPR reason from the given GDPR status
* @param status
* @returns {number}
*/
function getGdprReasonFromStatus(status) {
switch (status) {
case gdprStatus.GDPR_DOESNT_APPLY:
return gdprReason.GDPR_DOESNT_APPLY;
case gdprStatus.CMP_NOT_FOUND_OR_ERROR:
return gdprReason.CMP_NOT_FOUND;
case gdprStatus.GDPR_APPLIES_PUBLISHER:
return gdprReason.GDPR_APPLIES_PUBLISHER_CLASSIC;
default:
return -1;
}
}

/**
* Return the well formatted CCPA consent string
* @param ccpaConsentString
* @returns {string|*}
*/
export function getCcpaConsentString(ccpaConsentString) {
if (isStr(ccpaConsentString)) {
return ccpaConsentString;
} else {
return '';
}
}

/**
* Get the cookie expiration date string from a given Date and a max age
* @param {number} maxAge
* @returns {string}
*/
export function getCookieExpirationDate(maxAge) {
return new Date(timestamp() + maxAge).toUTCString()
}

/**
* Get cookie from Cookie or Local Storage
* @returns {string}
*/
function getTeadsViewerId() {
const teadsViewerId = readCookie()
if (isStr(teadsViewerId)) {
return teadsViewerId
} else {
return '';
}
}

function readCookie() {
return storage.cookiesAreEnabled(null) ? storage.getCookie(FP_TEADS_ID_COOKIE_NAME, null) : null;
}

/**
* Return a number of milliseconds from given days number
* @param days
* @returns {number}
*/
export function getTimestampFromDays(days) {
return days * 24 * 60 * 60 * 1000;
}
submodule('userId', teadsIdSubmodule);
22 changes: 22 additions & 0 deletions modules/teadsIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Overview

Module Name: Teads Id System
Module Type: User Id System
Maintainer: innov-ssp@teads.com

# Description

Teads user identification system. GDPR & CCPA compliant.

## Example configuration for publishers:

pbjs.setConfig({
userSync: {
userIds: [{
name: 'teadsId',
params: {
pubId: 1234
}
}]
}
});
Loading

0 comments on commit 848b2bb

Please sign in to comment.