Skip to content

Commit 99c6ec3

Browse files
ConnectID Adapter: fix storage type configuration not being respected (prebid#14018)
* fix: Update ConnectID Adapter to respect storage type configuration. * refactor: Improve ConnectID storage type handling with constants and documentation.
1 parent 7ce1e1b commit 99c6ec3

2 files changed

Lines changed: 168 additions & 15 deletions

File tree

modules/connectIdSystem.js

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {ajax} from '../src/ajax.js';
99
import {submodule} from '../src/hook.js';
1010

1111
import {getRefererInfo} from '../src/refererDetection.js';
12-
import {getStorageManager} from '../src/storageManager.js';
12+
import {getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE} from '../src/storageManager.js';
1313
import {formatQS, isNumber, isPlainObject, logError, parseUrl} from '../src/utils.js';
1414
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
1515

@@ -45,15 +45,23 @@ const O_AND_O_DOMAINS = [
4545
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
4646

4747
/**
48+
* Stores the ConnectID object in browser storage according to storage configuration
4849
* @function
49-
* @param {Object} obj
50+
* @param {Object} obj - The ID object to store
51+
* @param {Object} [storageConfig={}] - Storage configuration
52+
* @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5'
5053
*/
51-
function storeObject(obj) {
54+
function storeObject(obj, storageConfig = {}) {
5255
const expires = Date.now() + STORAGE_DURATION;
53-
if (storage.cookiesAreEnabled()) {
56+
const storageType = storageConfig.type || '';
57+
58+
const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES);
59+
const useLocalStorage = !storageType || storageType.includes(STORAGE_TYPE_LOCALSTORAGE);
60+
61+
if (useCookie && storage.cookiesAreEnabled()) {
5462
setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname());
5563
}
56-
if (storage.localStorageIsEnabled()) {
64+
if (useLocalStorage && storage.localStorageIsEnabled()) {
5765
storage.setDataInLocalStorage(MODULE_NAME, JSON.stringify(obj));
5866
}
5967
}
@@ -110,8 +118,17 @@ function getIdFromLocalStorage() {
110118
return null;
111119
}
112120

113-
function syncLocalStorageToCookie() {
114-
if (!storage.cookiesAreEnabled()) {
121+
/**
122+
* Syncs ID from localStorage to cookie if storage configuration allows
123+
* @function
124+
* @param {Object} [storageConfig={}] - Storage configuration
125+
* @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5'
126+
*/
127+
function syncLocalStorageToCookie(storageConfig = {}) {
128+
const storageType = storageConfig.type || '';
129+
const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES);
130+
131+
if (!useCookie || !storage.cookiesAreEnabled()) {
115132
return;
116133
}
117134
const value = getIdFromLocalStorage();
@@ -129,12 +146,19 @@ function isStale(storedIdData) {
129146
return false;
130147
}
131148

132-
function getStoredId() {
149+
/**
150+
* Retrieves stored ConnectID from cookie or localStorage
151+
* @function
152+
* @param {Object} [storageConfig={}] - Storage configuration
153+
* @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5'
154+
* @returns {Object|null} The stored ID object or null if not found
155+
*/
156+
function getStoredId(storageConfig = {}) {
133157
let storedId = getIdFromCookie();
134158
if (!storedId) {
135159
storedId = getIdFromLocalStorage();
136160
if (storedId && !isStale(storedId)) {
137-
syncLocalStorageToCookie();
161+
syncLocalStorageToCookie(storageConfig);
138162
}
139163
}
140164
return storedId;
@@ -191,13 +215,14 @@ export const connectIdSubmodule = {
191215
return;
192216
}
193217
const params = config.params || {};
218+
const storageConfig = config.storage || {};
194219
if (!params ||
195220
(typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) {
196221
logError(`${MODULE_NAME} module: configuration requires the 'pixelId'.`);
197222
return;
198223
}
199224

200-
const storedId = getStoredId();
225+
const storedId = getStoredId(storageConfig);
201226

202227
let shouldResync = isStale(storedId);
203228

@@ -213,7 +238,7 @@ export const connectIdSubmodule = {
213238
}
214239
if (!shouldResync) {
215240
storedId.lastUsed = Date.now();
216-
storeObject(storedId);
241+
storeObject(storedId, storageConfig);
217242
return {id: storedId};
218243
}
219244
}
@@ -274,7 +299,7 @@ export const connectIdSubmodule = {
274299
}
275300
responseObj.ttl = validTTLMiliseconds;
276301
}
277-
storeObject(responseObj);
302+
storeObject(responseObj, storageConfig);
278303
} else {
279304
logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`);
280305
}

test/spec/modules/connectIdSystem_spec.js

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ describe('Yahoo ConnectID Submodule', () => {
7777
removeLocalStorageDataStub.restore();
7878
});
7979

80-
function invokeGetIdAPI(configParams, consentData) {
81-
const result = connectIdSubmodule.getId({
80+
function invokeGetIdAPI(configParams, consentData, storageConfig) {
81+
const config = {
8282
params: configParams
83-
}, consentData);
83+
};
84+
if (storageConfig) {
85+
config.storage = storageConfig;
86+
}
87+
const result = connectIdSubmodule.getId(config, consentData);
8488
if (typeof result === 'object' && result.callback) {
8589
result.callback(sinon.stub());
8690
}
@@ -803,6 +807,130 @@ describe('Yahoo ConnectID Submodule', () => {
803807
expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY);
804808
expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData));
805809
});
810+
811+
it('stores the result in localStorage only when storage type is html5', () => {
812+
getAjaxFnStub.restore();
813+
const dateNowStub = sinon.stub(Date, 'now');
814+
dateNowStub.returns(0);
815+
const upsResponse = {connectid: 'html5only'};
816+
const expectedStoredData = {
817+
connectid: 'html5only',
818+
puid: PUBLISHER_USER_ID,
819+
lastSynced: 0,
820+
lastUsed: 0
821+
};
822+
invokeGetIdAPI({
823+
puid: PUBLISHER_USER_ID,
824+
pixelId: PIXEL_ID
825+
}, consentData, {type: 'html5'});
826+
const request = server.requests[0];
827+
request.respond(
828+
200,
829+
{'Content-Type': 'application/json'},
830+
JSON.stringify(upsResponse)
831+
);
832+
dateNowStub.restore();
833+
834+
expect(setCookieStub.called).to.be.false;
835+
expect(setLocalStorageStub.calledOnce).to.be.true;
836+
expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY);
837+
expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData));
838+
});
839+
840+
it('stores the result in cookie only when storage type is cookie', () => {
841+
getAjaxFnStub.restore();
842+
const dateNowStub = sinon.stub(Date, 'now');
843+
dateNowStub.returns(0);
844+
const upsResponse = {connectid: 'cookieonly'};
845+
const expectedStoredData = {
846+
connectid: 'cookieonly',
847+
puid: PUBLISHER_USER_ID,
848+
lastSynced: 0,
849+
lastUsed: 0
850+
};
851+
const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000);
852+
invokeGetIdAPI({
853+
puid: PUBLISHER_USER_ID,
854+
pixelId: PIXEL_ID
855+
}, consentData, {type: 'cookie'});
856+
const request = server.requests[0];
857+
request.respond(
858+
200,
859+
{'Content-Type': 'application/json'},
860+
JSON.stringify(upsResponse)
861+
);
862+
dateNowStub.restore();
863+
864+
expect(setCookieStub.calledOnce).to.be.true;
865+
expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY);
866+
expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(expectedStoredData));
867+
expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString());
868+
expect(setLocalStorageStub.called).to.be.false;
869+
});
870+
871+
it('does not sync localStorage to cookie when storage type is html5', () => {
872+
const localStorageData = {connectId: 'foobarbaz'};
873+
getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData);
874+
invokeGetIdAPI({
875+
he: HASHED_EMAIL,
876+
pixelId: PIXEL_ID
877+
}, consentData, {type: 'html5'});
878+
879+
expect(setCookieStub.called).to.be.false;
880+
});
881+
882+
it('updates existing ID with html5 storage type without writing cookie', () => {
883+
const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13);
884+
const cookieData = {connectId: 'foobar', he: HASHED_EMAIL, lastSynced: last13Days};
885+
getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData));
886+
const dateNowStub = sinon.stub(Date, 'now');
887+
dateNowStub.returns(20);
888+
const newCookieData = Object.assign({}, cookieData, {lastUsed: 20})
889+
const result = invokeGetIdAPI({
890+
he: HASHED_EMAIL,
891+
pixelId: PIXEL_ID
892+
}, consentData, {type: 'html5'});
893+
dateNowStub.restore();
894+
895+
expect(result).to.be.an('object').that.has.all.keys('id');
896+
expect(setCookieStub.called).to.be.false;
897+
expect(setLocalStorageStub.calledOnce).to.be.true;
898+
expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY);
899+
expect(setLocalStorageStub.firstCall.args[1]).to.equal(JSON.stringify(newCookieData));
900+
});
901+
902+
it('stores the result in both storages when storage type is cookie&html5', () => {
903+
getAjaxFnStub.restore();
904+
const dateNowStub = sinon.stub(Date, 'now');
905+
dateNowStub.returns(0);
906+
const upsResponse = {connectid: 'both'};
907+
const expectedStoredData = {
908+
connectid: 'both',
909+
puid: PUBLISHER_USER_ID,
910+
lastSynced: 0,
911+
lastUsed: 0
912+
};
913+
const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000);
914+
invokeGetIdAPI({
915+
puid: PUBLISHER_USER_ID,
916+
pixelId: PIXEL_ID
917+
}, consentData, {type: 'cookie&html5'});
918+
const request = server.requests[0];
919+
request.respond(
920+
200,
921+
{'Content-Type': 'application/json'},
922+
JSON.stringify(upsResponse)
923+
);
924+
dateNowStub.restore();
925+
926+
expect(setCookieStub.calledOnce).to.be.true;
927+
expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY);
928+
expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(expectedStoredData));
929+
expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString());
930+
expect(setLocalStorageStub.calledOnce).to.be.true;
931+
expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY);
932+
expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData));
933+
});
806934
});
807935
});
808936
describe('userHasOptedOut()', () => {

0 commit comments

Comments
 (0)