Skip to content

Commit

Permalink
Teads Bid Adapter: support floc and uid2 user IDs (#7116)
Browse files Browse the repository at this point in the history
* Teads adapter: support some user IDs

* review changes

* Stub access to storage functions in tests of Teads adapter

Co-authored-by: Kylian Deau <kylian.deau@teads.tv>
  • Loading branch information
ruizb and nailyk authored Aug 2, 2021
1 parent df534b5 commit 665ffba
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 23 deletions.
79 changes: 64 additions & 15 deletions modules/teadsBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
const utils = require('../src/utils.js');
import {getStorageManager} from '../src/storageManager.js';
import * as utils from '../src/utils.js';

const BIDDER_CODE = 'teads';
const GVL_ID = 132;
const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request';
Expand All @@ -8,7 +10,9 @@ const gdprStatus = {
GDPR_APPLIES_GLOBAL: 11,
GDPR_DOESNT_APPLY: 0,
CMP_NOT_FOUND_OR_ERROR: 22
}
};
const FP_TEADS_ID_COOKIE_NAME = '_tfpvi';
export const storage = getStorageManager(GVL_ID, BIDDER_CODE);

export const spec = {
code: BIDDER_CODE,
Expand Down Expand Up @@ -41,14 +45,18 @@ export const spec = {
*/
buildRequests: function(validBidRequests, bidderRequest) {
const bids = validBidRequests.map(buildRequestObject);

const payload = {
referrer: getReferrerInfo(bidderRequest),
pageReferrer: document.referrer,
networkBandwidth: getConnectionDownLink(window.navigator),
timeToFirstByte: getTimeToFirstByte(window),
data: bids,
deviceWidth: screen.width,
hb_version: '$prebid.version$'
hb_version: '$prebid.version$',
...getFLoCParameters(utils.deepAccess(validBidRequests, '0.userId.flocId')),
...getUnifiedId2Parameter(utils.deepAccess(validBidRequests, '0.userId.uid2')),
...getFirstPartyTeadsIdParameter()
};

if (validBidRequests[0].schain) {
Expand All @@ -57,11 +65,11 @@ export const spec = {

let gdpr = bidderRequest.gdprConsent;
if (bidderRequest && gdpr) {
let isCmp = (typeof gdpr.gdprApplies === 'boolean')
let isConsentString = (typeof gdpr.consentString === 'string')
let isCmp = typeof gdpr.gdprApplies === 'boolean';
let isConsentString = typeof gdpr.consentString === 'string';
let status = isCmp
? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData, gdpr.apiVersion)
: gdprStatus.CMP_NOT_FOUND_OR_ERROR
: gdprStatus.CMP_NOT_FOUND_OR_ERROR;
payload.gdpr_iab = {
consent: isConsentString ? gdpr.consentString : '',
status: status,
Expand All @@ -70,14 +78,14 @@ export const spec = {
}

if (bidderRequest && bidderRequest.uspConsent) {
payload.us_privacy = bidderRequest.uspConsent
payload.us_privacy = bidderRequest.uspConsent;
}

const payloadString = JSON.stringify(payload);
return {
method: 'POST',
url: ENDPOINT_URL,
data: payloadString,
data: payloadString
};
},
/**
Expand Down Expand Up @@ -114,7 +122,7 @@ export const spec = {
});
}
return bidResponses;
},
}
};

function getReferrerInfo(bidderRequest) {
Expand Down Expand Up @@ -159,10 +167,14 @@ function getTimeToFirstByte(win) {
}

function findGdprStatus(gdprApplies, gdprData, apiVersion) {
let status = gdprStatus.GDPR_APPLIES_PUBLISHER
let status = gdprStatus.GDPR_APPLIES_PUBLISHER;
if (gdprApplies) {
if (isGlobalConsent(gdprData, apiVersion)) status = gdprStatus.GDPR_APPLIES_GLOBAL
} else status = gdprStatus.GDPR_DOESNT_APPLY
if (isGlobalConsent(gdprData, apiVersion)) {
status = gdprStatus.GDPR_APPLIES_GLOBAL;
}
} else {
status = gdprStatus.GDPR_DOESNT_APPLY;
}
return status;
}

Expand All @@ -171,7 +183,7 @@ function isGlobalConsent(gdprData, apiVersion) {
? (gdprData.hasGlobalScope || gdprData.hasGlobalConsent)
: gdprData && apiVersion === 2
? !gdprData.isServiceSpecific
: false
: false;
}

function buildRequestObject(bid) {
Expand Down Expand Up @@ -205,13 +217,15 @@ function concatSizes(bid) {
.reduce(function(acc, currSize) {
if (utils.isArray(currSize)) {
if (utils.isArray(currSize[0])) {
currSize.forEach(function (childSize) { acc.push(childSize) })
currSize.forEach(function (childSize) {
acc.push(childSize);
})
} else {
acc.push(currSize);
}
}
return acc;
}, [])
}, []);
} else {
return bid.sizes;
}
Expand All @@ -221,4 +235,39 @@ function _validateId(id) {
return (parseInt(id) > 0);
}

/**
* Get FLoC parameters to be sent in the bid request.
* @param `{id: string, version: string} | undefined` optionalFlocId FLoC user ID object available if "flocIdSystem" module is enabled.
* @returns `{} | {cohortId: string} | {cohortVersion: string} | {cohortId: string, cohortVersion: string}`
*/
function getFLoCParameters(optionalFlocId) {
if (!optionalFlocId) {
return {};
}
const cohortId = optionalFlocId.id ? { cohortId: optionalFlocId.id } : {};
const cohortVersion = optionalFlocId.version ? { cohortVersion: optionalFlocId.version } : {};
return { ...cohortId, ...cohortVersion };
}

/**
* Get unified ID v2 parameter to be sent in bid request.
* @param `{id: string} | undefined` optionalUid2 uid2 user ID object available if "uid2IdSystem" module is enabled.
* @returns `{} | {unifiedId2: string}`
*/
function getUnifiedId2Parameter(optionalUid2) {
return optionalUid2 ? { unifiedId2: optionalUid2.id } : {};
}

/**
* Get the first-party cookie Teads ID parameter to be sent in bid request.
* @returns `{} | {firstPartyCookieTeadsId: string}`
*/
function getFirstPartyTeadsIdParameter() {
if (!storage.cookiesAreEnabled()) {
return {};
}
const firstPartyTeadsId = storage.getCookie(FP_TEADS_ID_COOKIE_NAME);
return firstPartyTeadsId ? { firstPartyCookieTeadsId: firstPartyTeadsId } : {};
}

registerBidder(spec);
154 changes: 146 additions & 8 deletions test/spec/modules/teadsBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import {expect} from 'chai';
import {spec} from 'modules/teadsBidAdapter.js';
import {spec, storage} from 'modules/teadsBidAdapter.js';
import {newBidder} from 'src/adapters/bidderFactory.js';
import {getStorageManager} from 'src/storageManager';

const ENDPOINT = 'https://a.teads.tv/hb/bid-request';
const AD_SCRIPT = '<script type="text/javascript" class="teads" async="true" src="https://a.teads.tv/hb/getAdSettings"></script>"';

describe('teadsBidAdapter', () => {
const adapter = newBidder(spec);
let cookiesAreEnabledStub, getCookieStub;

beforeEach(function () {
cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled');
getCookieStub = sinon.stub(storage, 'getCookie');
});

afterEach(function () {
cookiesAreEnabledStub.restore();
getCookieStub.restore();
});

describe('inherited functions', () => {
it('exists and is a function', () => {
Expand Down Expand Up @@ -102,7 +114,7 @@ describe('teadsBidAdapter', () => {
'timeout': 3000
};

it('sends bid request to ENDPOINT via POST', function() {
it('should send bid request to ENDPOINT via POST', function() {
const request = spec.buildRequests(bidRequests, bidderResquestDefault);

expect(request.url).to.equal(ENDPOINT);
Expand Down Expand Up @@ -274,7 +286,6 @@ describe('teadsBidAdapter', () => {
});

it('should send GDPR to endpoint with 22 status', function() {
let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A==';
let bidderRequest = {
'auctionId': '1d1a030790a475',
'bidderRequestId': '22edbae2733bf6',
Expand Down Expand Up @@ -322,7 +333,6 @@ describe('teadsBidAdapter', () => {
});

it('should send GDPR to endpoint with 0 status when gdprApplies = false (vendorData = undefined)', function() {
let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A==';
let bidderRequest = {
'auctionId': '1d1a030790a475',
'bidderRequestId': '22edbae2733bf6',
Expand Down Expand Up @@ -377,7 +387,7 @@ describe('teadsBidAdapter', () => {
}
}
};
checkMediaTypesSizes(mediaTypesPlayerSize, '32x34')
checkMediaTypesSizes(mediaTypesPlayerSize, '32x34');
});

it('should add schain info to payload if available', function () {
Expand Down Expand Up @@ -416,7 +426,7 @@ describe('teadsBidAdapter', () => {
}
}
};
checkMediaTypesSizes(mediaTypesVideoSizes, '12x14')
checkMediaTypesSizes(mediaTypesVideoSizes, '12x14');
});

it('should use good mediaTypes banner sizes', function() {
Expand All @@ -427,7 +437,7 @@ describe('teadsBidAdapter', () => {
}
}
};
checkMediaTypesSizes(mediaTypesBannerSize, '46x48')
checkMediaTypesSizes(mediaTypesBannerSize, '46x48');
});

it('should use good mediaTypes for both video and banner sizes', function() {
Expand All @@ -441,7 +451,135 @@ describe('teadsBidAdapter', () => {
}
}
};
checkMediaTypesSizes(hybridMediaTypes, ['46x48', '50x34', '45x45'])
checkMediaTypesSizes(hybridMediaTypes, ['46x48', '50x34', '45x45']);
});

describe('User IDs', function () {
const baseBidRequest = {
'bidder': 'teads',
'params': {
'placementId': 10433394,
'pageId': 1234
},
'adUnitCode': 'adunit-code',
'sizes': [[300, 250], [300, 600]],
'bidId': '30b31c1838de1e',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
'creativeId': 'er2ee',
'deviceWidth': 1680
};

describe('FLoC ID', function () {
it('should not add cohortId and cohortVersion params to payload if FLoC ID system is not enabled', function () {
const bidRequest = {
...baseBidRequest,
userId: {} // no "flocId" property -> assumption that the FLoC ID system is disabled
};

const request = spec.buildRequests([bidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload).not.to.have.property('cohortId');
expect(payload).not.to.have.property('cohortVersion');
});

it('should add cohortId param to payload if FLoC ID system is enabled and ID available, but not version', function () {
const bidRequest = {
...baseBidRequest,
userId: {
flocId: {
id: 'my-floc-id'
}
}
};

const request = spec.buildRequests([bidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload.cohortId).to.equal('my-floc-id');
expect(payload).not.to.have.property('cohortVersion');
});

it('should add cohortId and cohortVersion params to payload if FLoC ID system is enabled', function () {
const bidRequest = {
...baseBidRequest,
userId: {
flocId: {
id: 'my-floc-id',
version: 'chrome.1.1'
}
}
};

const request = spec.buildRequests([bidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload.cohortId).to.equal('my-floc-id');
expect(payload.cohortVersion).to.equal('chrome.1.1');
});
});

describe('Unified ID v2', function () {
it('should not add unifiedId2 param to payload if uid2 system is not enabled', function () {
const bidRequest = {
...baseBidRequest,
userId: {} // no "uid2" property -> assumption that the Unified ID v2 system is disabled
};

const request = spec.buildRequests([bidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload).not.to.have.property('unifiedId2');
});

it('should add unifiedId2 param to payload if uid2 system is enabled', function () {
const bidRequest = {
...baseBidRequest,
userId: {
uid2: {
id: 'my-unified-id-2'
}
}
};

const request = spec.buildRequests([bidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload.unifiedId2).to.equal('my-unified-id-2');
})
});

describe('First-party cookie Teads ID', function () {
it('should not add firstPartyCookieTeadsId param to payload if cookies are not enabled', function () {
cookiesAreEnabledStub.returns(false);

const request = spec.buildRequests([baseBidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload).not.to.have.property('firstPartyCookieTeadsId');
});

it('should not add firstPartyCookieTeadsId param to payload if first-party cookie is not available', function () {
cookiesAreEnabledStub.returns(true);
getCookieStub.withArgs('_tfpvi').returns(undefined);

const request = spec.buildRequests([baseBidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload).not.to.have.property('firstPartyCookieTeadsId');
});

it('should add firstPartyCookieTeadsId param to payload if first-party cookie is available', function () {
cookiesAreEnabledStub.returns(true);
getCookieStub.withArgs('_tfpvi').returns('my-teads-id');

const request = spec.buildRequests([baseBidRequest], bidderResquestDefault);
const payload = JSON.parse(request.data);

expect(payload.firstPartyCookieTeadsId).to.equal('my-teads-id');
});
});
});

function checkMediaTypesSizes(mediaTypes, expectedSizes) {
Expand Down

0 comments on commit 665ffba

Please sign in to comment.