Skip to content

Commit fcd64ea

Browse files
authored
BeOpBidAdapter: Refacto beopid cookie to caudid (#14584)
* Change beopid cookie to caudid * Add caudid_date cookie * Post review commit
1 parent a7c82c8 commit fcd64ea

2 files changed

Lines changed: 141 additions & 17 deletions

File tree

modules/beopBidAdapter.js

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js';
44
import { getRefererInfo } from '../src/refererDetection.js';
55
import {
66
buildUrl,
7-
deepAccess, generateUUID, getBidIdParameter,
7+
deepAccess,
8+
getBidIdParameter,
89
getValue,
910
isArray,
1011
isPlainObject,
@@ -23,12 +24,33 @@ import { getStorageManager } from '../src/storageManager.js';
2324

2425
const BIDDER_CODE = 'beop';
2526
const ENDPOINT_URL = 'https://hb.collectiveaudience.co/bid';
26-
const COOKIE_NAME = 'beopid';
27+
const COOKIE_NAME = 'caudid';
28+
const COOKIE_DATE_NAME = 'caudid_date';
2729
const TCF_VENDOR_ID = 666;
30+
const COOKIE_MAX_AGE_MS = 86400 * 365 * 1000; // 1 year
2831

29-
const validIdRegExp = /^[0-9a-fA-F]{24}$/
32+
const validIdRegExp = /^[0-9a-fA-F]{24}$/;
33+
34+
/**
35+
* Generates a 24-char hex string compatible with MongoDB ObjectId semantics
36+
* (4-byte timestamp + 16 random hex chars). Used for first-party user id (caudid).
37+
* Timestamp is padded to 8 hex chars so that a client clock in the past (or mocked Date)
38+
* cannot produce a shorter string that would fail the 24-char validation on later requests.
39+
* @see https://www.mongodb.com/docs/manual/reference/method/objectid/
40+
* @return {string}
41+
*/
42+
function generateObjectId() {
43+
const timestamp = (Math.floor(Date.now() / 1000)).toString(16).padStart(8, '0');
44+
const randomPart = Array.from({ length: 16 }, () =>
45+
(Math.floor(Math.random() * 16)).toString(16)
46+
).join('');
47+
return (timestamp + randomPart).toLowerCase();
48+
}
3049
const storage = getStorageManager({ bidderCode: BIDDER_CODE });
3150

51+
/** Exported for unit tests (caudid / caudid_date cookie behavior). */
52+
export const __storage = storage;
53+
3254
export const spec = {
3355
code: BIDDER_CODE,
3456
gvlid: TCF_VENDOR_ID,
@@ -68,17 +90,20 @@ export const spec = {
6890
const kwdsFromRequest = firstSlot.kwds;
6991
const keywords = getAllOrtbKeywords(bidderRequest.ortb2, kwdsFromRequest);
7092

71-
let beopid = '';
72-
if (storage.cookiesAreEnabled) {
73-
beopid = storage.getCookie(COOKIE_NAME, undefined);
74-
if (!beopid) {
75-
beopid = generateUUID();
93+
let caudid = '';
94+
if (storage.cookiesAreEnabled()) {
95+
caudid = storage.getCookie(COOKIE_NAME, undefined);
96+
if (!caudid || !validIdRegExp.test(caudid)) {
97+
caudid = generateObjectId();
7698
const expirationDate = new Date();
77-
expirationDate.setTime(expirationDate.getTime() + 86400 * 183 * 1000);
78-
storage.setCookie(COOKIE_NAME, beopid, expirationDate.toUTCString());
99+
expirationDate.setTime(expirationDate.getTime() + COOKIE_MAX_AGE_MS);
100+
storage.setCookie(COOKIE_NAME, caudid, expirationDate.toUTCString());
101+
const dateValue = String(Date.now());
102+
storage.setCookie(COOKIE_DATE_NAME, dateValue, expirationDate.toUTCString());
79103
}
80104
} else {
81105
storage.setCookie(COOKIE_NAME, '', 0);
106+
storage.setCookie(COOKIE_DATE_NAME, '', 0);
82107
}
83108

84109
const payloadObject = {
@@ -91,7 +116,7 @@ export const spec = {
91116
lang: (window.navigator.language || window.navigator.languages[0]),
92117
kwds: keywords,
93118
dbg: false,
94-
fg: beopid,
119+
fg: caudid,
95120
slts: slots,
96121
is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'),
97122
gdpr_applies: gdpr ? gdpr.gdprApplies : false,

test/spec/modules/beopBidAdapter_spec.js

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'chai';
2-
import { spec } from 'modules/beopBidAdapter.js';
2+
import { spec, __storage } from 'modules/beopBidAdapter.js';
33
import { newBidder } from 'src/adapters/bidderFactory.js';
44
import { config } from 'src/config.js';
55
import { setConfig as setCurrencyConfig } from '../../../modules/currency.js';
@@ -380,15 +380,114 @@ describe('BeOp Bid Adapter tests', () => {
380380

381381
describe('Ensure first party cookie is well managed', function () {
382382
const bidRequests = [];
383+
let sandbox;
383384

384-
it(`should generate a new uuid`, function () {
385+
beforeEach(function () {
386+
sandbox = sinon.createSandbox();
387+
});
388+
389+
afterEach(function () {
390+
sandbox.restore();
391+
});
392+
393+
it('should set fg to a 24-char hex ObjectID (caudid) when no cookie present', function () {
394+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(true);
395+
sandbox.stub(__storage, 'getCookie').returns(undefined);
385396
const bid = Object.assign({}, validBid);
386-
bidRequests.push(bid);
387-
const request = spec.buildRequests(bidRequests, {});
397+
const request = spec.buildRequests([bid], {});
388398
const payload = JSON.parse(request.data);
389399
expect(payload.fg).to.exist;
390-
})
391-
})
400+
expect(payload.fg).to.match(/^[0-9a-f]{24}$/);
401+
});
402+
403+
it('should set both caudid and caudid_date cookies when generating a new id', function () {
404+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(true);
405+
sandbox.stub(__storage, 'getCookie').returns(undefined);
406+
const setCookieSpy = sandbox.stub(__storage, 'setCookie');
407+
408+
const bid = Object.assign({}, validBid);
409+
const request = spec.buildRequests([bid], {});
410+
const payload = JSON.parse(request.data);
411+
412+
expect(setCookieSpy.calledTwice).to.be.true;
413+
expect(setCookieSpy.firstCall.args[0]).to.equal('caudid');
414+
expect(setCookieSpy.firstCall.args[1]).to.match(/^[0-9a-f]{24}$/);
415+
expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date');
416+
expect(setCookieSpy.secondCall.args[1]).to.match(/^\d+$/);
417+
expect(Number(setCookieSpy.secondCall.args[1])).to.be.closeTo(Date.now(), 5000);
418+
expect(payload.fg).to.equal(setCookieSpy.firstCall.args[1]);
419+
});
420+
421+
it('should always produce 24-char caudid when clock is in the past (timestamp padding)', function () {
422+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(true);
423+
sandbox.stub(__storage, 'getCookie').returns(undefined);
424+
const setCookieSpy = sandbox.stub(__storage, 'setCookie');
425+
// Unix epoch 0 → hex "0"; without padding that would yield 1 + 16 = 17 chars and fail validIdRegExp
426+
sandbox.stub(Date, 'now').returns(0);
427+
428+
const bid = Object.assign({}, validBid);
429+
const request = spec.buildRequests([bid], {});
430+
const payload = JSON.parse(request.data);
431+
432+
const caudid = setCookieSpy.firstCall.args[1];
433+
expect(caudid).to.have.lengthOf(24);
434+
expect(caudid).to.match(/^[0-9a-f]{24}$/);
435+
expect(caudid.substring(0, 8)).to.equal('00000000');
436+
expect(payload.fg).to.equal(caudid);
437+
});
438+
439+
it('should not set cookies when a valid caudid cookie already exists', function () {
440+
const existingCaudid = '674a1b2c3d4e5f6789abcdef';
441+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(true);
442+
sandbox.stub(__storage, 'getCookie').callsFake((name) =>
443+
name === 'caudid' ? existingCaudid : undefined
444+
);
445+
const setCookieSpy = sandbox.stub(__storage, 'setCookie');
446+
447+
const bid = Object.assign({}, validBid);
448+
const request = spec.buildRequests([bid], {});
449+
const payload = JSON.parse(request.data);
450+
451+
expect(setCookieSpy.called).to.be.false;
452+
expect(payload.fg).to.equal(existingCaudid);
453+
});
454+
455+
it('should regenerate caudid and set both cookies when existing caudid is invalid format', function () {
456+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(true);
457+
sandbox.stub(__storage, 'getCookie').callsFake((name) =>
458+
name === 'caudid' ? 'invalid-uuid-format' : undefined
459+
);
460+
const setCookieSpy = sandbox.stub(__storage, 'setCookie');
461+
462+
const bid = Object.assign({}, validBid);
463+
const request = spec.buildRequests([bid], {});
464+
const payload = JSON.parse(request.data);
465+
466+
expect(setCookieSpy.calledTwice).to.be.true;
467+
expect(setCookieSpy.firstCall.args[0]).to.equal('caudid');
468+
expect(setCookieSpy.firstCall.args[1]).to.match(/^[0-9a-f]{24}$/);
469+
expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date');
470+
expect(payload.fg).to.equal(setCookieSpy.firstCall.args[1]);
471+
});
472+
473+
it('should clear both caudid and caudid_date when cookies are disabled', function () {
474+
sandbox.stub(__storage, 'cookiesAreEnabled').returns(false);
475+
const setCookieSpy = sandbox.stub(__storage, 'setCookie');
476+
477+
const bid = Object.assign({}, validBid);
478+
const request = spec.buildRequests([bid], {});
479+
const payload = JSON.parse(request.data);
480+
481+
expect(setCookieSpy.calledTwice).to.be.true;
482+
expect(setCookieSpy.firstCall.args[0]).to.equal('caudid');
483+
expect(setCookieSpy.firstCall.args[1]).to.equal('');
484+
expect(setCookieSpy.firstCall.args[2]).to.equal(0);
485+
expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date');
486+
expect(setCookieSpy.secondCall.args[1]).to.equal('');
487+
expect(setCookieSpy.secondCall.args[2]).to.equal(0);
488+
expect(payload.fg).to.equal('');
489+
});
490+
});
392491
describe('slot name normalization', function () {
393492
it('should preserve non-GPT adUnitCode unchanged (case-sensitive)', function () {
394493
const bid = Object.assign({}, validBid);

0 commit comments

Comments
 (0)