Skip to content

Commit

Permalink
Criteo Id System: add support for user sync pixels (#8555)
Browse files Browse the repository at this point in the history
* Criteo Id Module - Ensure that all available bundles are sent back to backend

* Criteo Id Module - Added ability to call sync pixels

Co-authored-by: le.labat <le.labat@criteo.com>
  • Loading branch information
afewcc and leonardlabat authored Jun 14, 2022
1 parent 48ad719 commit f7dca5b
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 10 deletions.
39 changes: 35 additions & 4 deletions modules/criteoIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ import { getStorageManager } from '../src/storageManager.js';

const gvlid = 91;
const bidderCode = 'criteo';
export const storage = getStorageManager({gvlid: gvlid, moduleName: bidderCode});
export const storage = getStorageManager({ gvlid: gvlid, moduleName: bidderCode });

const bididStorageKey = 'cto_bidid';
const bundleStorageKey = 'cto_bundle';
const dnaBundleStorageKey = 'cto_dna_bundle';
const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000;

const pastDateString = new Date(0).toString();
const expirationString = new Date(timestamp() + cookiesMaxAge).toString();

function extractProtocolHost (url, returnOnlyHost = false) {
const parsedUrl = parseUrl(url, {noDecodeWholeURL: true})
function extractProtocolHost(url, returnOnlyHost = false) {
const parsedUrl = parseUrl(url, { noDecodeWholeURL: true })
return returnOnlyHost
? `${parsedUrl.hostname}`
: `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`;
Expand Down Expand Up @@ -70,15 +71,17 @@ function deleteFromAllStorages(key, hostname) {
function getCriteoDataFromAllStorages() {
return {
bundle: getFromAllStorages(bundleStorageKey),
dnaBundle: getFromAllStorages(dnaBundleStorageKey),
bidId: getFromAllStorages(bididStorageKey),
}
}

function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) {
function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) {
const url = 'https://gum.criteo.com/sid/json?origin=prebid' +
`${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` +
`${domain ? '&domain=' + encodeURIComponent(domain) : ''}` +
`${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` +
`${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` +
`${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` +
`${areCookiesWriteable ? '&cw=1' : ''}` +
`${isPublishertagPresent ? '&pbt=1' : ''}` +
Expand All @@ -87,6 +90,28 @@ function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isL
return url;
}

function callSyncPixel(domain, pixel) {
if (pixel.writeBundleInStorage && pixel.bundlePropertyName && pixel.storageKeyName) {
ajax(
pixel.pixelUrl,
{
success: response => {
if (response) {
const jsonResponse = JSON.parse(response);
if (jsonResponse && jsonResponse[pixel.bundlePropertyName]) {
saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain);
}
}
}
},
undefined,
{ method: 'GET', withCredentials: true }
);
} else {
triggerPixel(pixel.pixelUrl);
}
}

function callCriteoUserSync(parsedCriteoData, gdprString, callback) {
const cw = storage.cookiesAreEnabled();
const lsw = storage.localStorageIsEnabled();
Expand All @@ -99,6 +124,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) {
topUrl,
domain,
parsedCriteoData.bundle,
parsedCriteoData.dnaBundle,
cw,
lsw,
isPublishertagPresent,
Expand All @@ -108,6 +134,11 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) {
const callbacks = {
success: response => {
const jsonResponse = JSON.parse(response);

if (jsonResponse.pixels) {
jsonResponse.pixels.forEach(pixel => callSyncPixel(domain, pixel));
}

if (jsonResponse.acwsUrl) {
const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl;
urlsToCall.forEach(url => triggerPixel(url));
Expand Down
55 changes: 49 additions & 6 deletions test/spec/modules/criteoIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js';
import * as utils from 'src/utils.js';
import {server} from '../../mocks/xhr';
import { server } from '../../mocks/xhr';

const pastDateString = new Date(0).toString()

Expand All @@ -25,7 +25,7 @@ describe('CriteoId module', function () {
setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage');
removeFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage');
timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp);
parseUrlStub = sinon.stub(utils, 'parseUrl').returns({protocol: 'https', hostname: 'testdev.com'})
parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' })
triggerPixelStub = sinon.stub(utils, 'triggerPixel');
done();
});
Expand Down Expand Up @@ -64,20 +64,21 @@ describe('CriteoId module', function () {

it('should call user sync url with the right params', function () {
getCookieStub.withArgs('cto_bundle').returns('bundle');
getCookieStub.withArgs('cto_dna_bundle').returns('info');
window.criteo_pubtag = {}

let callBackSpy = sinon.spy();
let result = criteoIdSubmodule.getId();
result.callback(callBackSpy);

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&lsw=1`;
const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&info=info&cw=1&pbt=1&lsw=1`;

let request = server.requests[0];
expect(request.url).to.be.eq(expectedUrl);

request.respond(
200,
{'Content-Type': 'application/json'},
{ 'Content-Type': 'application/json' },
JSON.stringify({})
);
expect(callBackSpy.calledOnce).to.be.true;
Expand Down Expand Up @@ -107,7 +108,7 @@ describe('CriteoId module', function () {
let request = server.requests[0];
request.respond(
200,
{'Content-Type': 'application/json'},
{ 'Content-Type': 'application/json' },
JSON.stringify(response)
);

Expand Down Expand Up @@ -142,6 +143,48 @@ describe('CriteoId module', function () {
{ consentData: undefined, expected: undefined }
];

it('should call sync pixels if request by backend', function () {
const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString();

const result = criteoIdSubmodule.getId();
result.callback((id) => {

});

const response = {
pixels: [
{
pixelUrl: 'pixelUrlWithBundle',
writeBundleInStorage: true,
bundlePropertyName: 'abc',
storageKeyName: 'cto_pixel_test'
},
{
pixelUrl: 'pixelUrl'
}
]
};

server.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response)
);

server.requests[1].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
abc: 'ok'
})
);

expect(triggerPixelStub.called).to.be.true;
expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.com')).to.be.true;
expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.testdev.com')).to.be.true;
expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.true;
});

gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () {
let callBackSpy = sinon.spy();
let result = criteoIdSubmodule.getId(undefined, testCase.consentData);
Expand All @@ -156,7 +199,7 @@ describe('CriteoId module', function () {

request.respond(
200,
{'Content-Type': 'application/json'},
{ 'Content-Type': 'application/json' },
JSON.stringify({})
);

Expand Down

0 comments on commit f7dca5b

Please sign in to comment.