forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add CriteoId module * Update the return type of getId in Criteo Id module Changes: - Use of url parsing function from url lib - Update the return type of getId() - Update the jsdoc to reflect the real return types * Fix failing tests for Criteo user module * Add CriteoIdSystem submodule to .submodule.json.
- Loading branch information
Showing
3 changed files
with
269 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/** | ||
* This module adds Criteo Real Time User Sync to the User ID module | ||
* The {@link module:modules/userId} module is required | ||
* @module modules/criteoIdSystem | ||
* @requires module:modules/userId | ||
*/ | ||
|
||
import * as utils from '../src/utils' | ||
import * as ajax from '../src/ajax' | ||
import * as urlLib from '../src/url' | ||
import { getRefererInfo } from '../src/refererDetection' | ||
import { submodule } from '../src/hook'; | ||
|
||
const bididStorageKey = 'cto_bidid'; | ||
const bundleStorageKey = 'cto_bundle'; | ||
const cookieWriteableKey = 'cto_test_cookie'; | ||
const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; | ||
|
||
const pastDateString = new Date(0).toString(); | ||
const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); | ||
|
||
function areCookiesWriteable() { | ||
utils.setCookie(cookieWriteableKey, '1'); | ||
const canWrite = utils.getCookie(cookieWriteableKey) === '1'; | ||
utils.setCookie(cookieWriteableKey, '', pastDateString); | ||
return canWrite; | ||
} | ||
|
||
function extractProtocolHost (url, returnOnlyHost = false) { | ||
const parsedUrl = urlLib.parse(url) | ||
return returnOnlyHost | ||
? `${parsedUrl.hostname}` | ||
: `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; | ||
} | ||
|
||
function getFromAllStorages(key) { | ||
return utils.getCookie(key) || utils.getDataFromLocalStorage(key); | ||
} | ||
|
||
function saveOnAllStorages(key, value) { | ||
if (key && value) { | ||
utils.setCookie(key, value, expirationString); | ||
utils.setDataInLocalStorage(key, value); | ||
} | ||
} | ||
|
||
function deleteFromAllStorages(key) { | ||
utils.setCookie(key, '', pastDateString); | ||
utils.removeDataFromLocalStorage(key); | ||
} | ||
|
||
function getCriteoDataFromAllStorages() { | ||
return { | ||
bundle: getFromAllStorages(bundleStorageKey), | ||
bidId: getFromAllStorages(bididStorageKey), | ||
} | ||
} | ||
|
||
function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent) { | ||
const url = 'https://gum.criteo.com/sid/json?origin=prebid' + | ||
`${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + | ||
`${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + | ||
`${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + | ||
`${areCookiesWriteable ? '&cw=1' : ''}` + | ||
`${isPublishertagPresent ? '&pbt=1' : ''}` | ||
|
||
return url; | ||
} | ||
|
||
function callCriteoUserSync(parsedCriteoData) { | ||
const cw = areCookiesWriteable(); | ||
const topUrl = extractProtocolHost(getRefererInfo().referer); | ||
const domain = extractProtocolHost(document.location.href, true); | ||
const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase | ||
|
||
const url = buildCriteoUsersyncUrl( | ||
topUrl, | ||
domain, | ||
parsedCriteoData.bundle, | ||
cw, | ||
isPublishertagPresent | ||
); | ||
|
||
ajax.ajaxBuilder()( | ||
url, | ||
response => { | ||
const jsonResponse = JSON.parse(response); | ||
if (jsonResponse.bidId) { | ||
saveOnAllStorages(bididStorageKey, jsonResponse.bidId); | ||
} else { | ||
deleteFromAllStorages(bididStorageKey); | ||
} | ||
|
||
if (jsonResponse.acwsUrl) { | ||
const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; | ||
urlsToCall.forEach(url => utils.triggerPixel(url)); | ||
} else if (jsonResponse.bundle) { | ||
saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); | ||
} | ||
} | ||
); | ||
} | ||
|
||
/** @type {Submodule} */ | ||
export const criteoIdSubmodule = { | ||
/** | ||
* used to link submodule with config | ||
* @type {string} | ||
*/ | ||
name: 'criteo', | ||
/** | ||
* decode the stored id value for passing to bid requests | ||
* @function | ||
* @returns {{criteoId: string} | undefined} | ||
*/ | ||
decode(bidId) { | ||
return bidId; | ||
}, | ||
/** | ||
* get the Criteo Id from local storages and initiate a new user sync | ||
* @function | ||
* @returns {{id: {criteoId: string} | undefined}}} | ||
*/ | ||
getId() { | ||
let localData = getCriteoDataFromAllStorages(); | ||
callCriteoUserSync(localData); | ||
|
||
return { id: localData.bidId ? { criteoId: localData.bidId } : undefined } | ||
} | ||
}; | ||
|
||
submodule('userId', criteoIdSubmodule); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { criteoIdSubmodule } from 'modules/criteoIdSystem'; | ||
import * as utils from 'src/utils'; | ||
import * as ajaxLib from 'src/ajax'; | ||
import * as urlLib from 'src/url'; | ||
|
||
const pastDateString = new Date(0).toString() | ||
|
||
function mockResponse(responseText, fakeResponse = (url, callback) => callback(responseText)) { | ||
return function() { | ||
return fakeResponse; | ||
} | ||
} | ||
|
||
describe('CriteoId module', function () { | ||
const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; | ||
|
||
const nowTimestamp = new Date().getTime(); | ||
|
||
let getCookieStub; | ||
let setCookieStub; | ||
let getLocalStorageStub; | ||
let setLocalStorageStub; | ||
let removeFromLocalStorageStub; | ||
let timeStampStub; | ||
let parseUrlStub; | ||
let ajaxBuilderStub; | ||
let triggerPixelStub; | ||
|
||
beforeEach(function (done) { | ||
getCookieStub = sinon.stub(utils, 'getCookie'); | ||
setCookieStub = sinon.stub(utils, 'setCookie'); | ||
getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); | ||
setLocalStorageStub = sinon.stub(utils, 'setDataInLocalStorage'); | ||
removeFromLocalStorageStub = sinon.stub(utils, 'removeDataFromLocalStorage'); | ||
timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); | ||
ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse('{}')); | ||
parseUrlStub = sinon.stub(urlLib, 'parse').returns({protocol: 'https', hostname: 'testdev.com'}) | ||
triggerPixelStub = sinon.stub(utils, 'triggerPixel'); | ||
done(); | ||
}); | ||
|
||
afterEach(function () { | ||
getCookieStub.restore(); | ||
setCookieStub.restore(); | ||
getLocalStorageStub.restore(); | ||
setLocalStorageStub.restore(); | ||
removeFromLocalStorageStub.restore(); | ||
timeStampStub.restore(); | ||
ajaxBuilderStub.restore(); | ||
triggerPixelStub.restore(); | ||
parseUrlStub.restore(); | ||
}); | ||
|
||
const storageTestCases = [ | ||
{ cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId' }, | ||
{ cookie: 'bidId', localStorage: undefined, expected: 'bidId' }, | ||
{ cookie: undefined, localStorage: 'bidId', expected: 'bidId' }, | ||
{ cookie: undefined, localStorage: undefined, expected: undefined }, | ||
] | ||
|
||
storageTestCases.forEach(testCase => it('getId() should return the bidId when it exists in local storages', function () { | ||
getCookieStub.withArgs('cto_bidid').returns(testCase.cookie); | ||
getLocalStorageStub.withArgs('cto_bidid').returns(testCase.localStorage); | ||
|
||
const id = criteoIdSubmodule.getId(); | ||
expect(id).to.be.deep.equal({id: testCase.expected ? { criteoId: testCase.expected } : undefined}); | ||
})) | ||
|
||
it('decode() should return the bidId when it exists in local storages', function () { | ||
const id = criteoIdSubmodule.decode('testDecode'); | ||
expect(id).to.equal('testDecode') | ||
}); | ||
|
||
it('should call user sync url with the right params', function () { | ||
getCookieStub.withArgs('cto_test_cookie').returns('1'); | ||
getCookieStub.withArgs('cto_bundle').returns('bundle'); | ||
window.criteo_pubtag = {} | ||
|
||
const emptyObj = '{}'; | ||
let ajaxStub = sinon.stub().callsFake((url, callback) => callback(emptyObj)); | ||
ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) | ||
|
||
criteoIdSubmodule.getId(); | ||
const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1`; | ||
|
||
expect(ajaxStub.calledWith(expectedUrl)).to.be.true; | ||
|
||
window.criteo_pubtag = undefined; | ||
}); | ||
|
||
const responses = [ | ||
{ bundle: 'bundle', bidId: 'bidId', acwsUrl: 'acwsUrl' }, | ||
{ bundle: 'bundle', bidId: undefined, acwsUrl: 'acwsUrl' }, | ||
{ bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, | ||
{ bundle: undefined, bidId: 'bidId', acwsUrl: 'acwsUrl' }, | ||
{ bundle: 'bundle', bidId: undefined, acwsUrl: undefined }, | ||
{ bundle: undefined, bidId: 'bidId', acwsUrl: undefined }, | ||
{ bundle: undefined, bidId: undefined, acwsUrl: 'acwsUrl' }, | ||
{ bundle: undefined, bidId: undefined, acwsUrl: ['acwsUrl', 'acwsUrl2'] }, | ||
{ bundle: undefined, bidId: undefined, acwsUrl: undefined }, | ||
] | ||
|
||
responses.forEach(response => describe('test user sync response behavior', function () { | ||
const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); | ||
|
||
beforeEach(function (done) { | ||
const fakeResponse = (url, callback) => { | ||
callback(JSON.stringify(response)); | ||
setTimeout(done, 0); | ||
} | ||
ajaxBuilderStub.callsFake(mockResponse(undefined, fakeResponse)); | ||
criteoIdSubmodule.getId(); | ||
}) | ||
|
||
it('should save bidId if it exists', function () { | ||
if (response.acwsUrl) { | ||
expect(triggerPixelStub.called).to.be.true; | ||
expect(setCookieStub.calledWith('cto_bundle')).to.be.false; | ||
expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; | ||
} else if (response.bundle) { | ||
expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs)).to.be.true; | ||
expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; | ||
expect(triggerPixelStub.called).to.be.false; | ||
} | ||
|
||
if (response.bidId) { | ||
expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs)).to.be.true; | ||
expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; | ||
} else { | ||
expect(setCookieStub.calledWith('cto_bidid', '', pastDateString)).to.be.true; | ||
expect(removeFromLocalStorageStub.calledWith('cto_bidid')).to.be.true; | ||
} | ||
}); | ||
})); | ||
}); |