Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module: New Teads User ID Module #9048

Merged
merged 1 commit into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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