From 66101f387390a823484b686a7652d873e2d2a9ae Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 3 Feb 2026 15:52:27 +0200 Subject: [PATCH 01/17] Add Start.io User ID submodule with tests and documentation --- modules/startioSystem.js | 81 +++++++++ modules/startioSystem.md | 63 +++++++ test/spec/modules/startioSystem_spec.js | 217 ++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 modules/startioSystem.js create mode 100644 modules/startioSystem.md create mode 100644 test/spec/modules/startioSystem_spec.js diff --git a/modules/startioSystem.js b/modules/startioSystem.js new file mode 100644 index 0000000000..896d045e78 --- /dev/null +++ b/modules/startioSystem.js @@ -0,0 +1,81 @@ +import { logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; + +const MODULE_NAME = 'startioId'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +function storeId(id, storageConfig = {}) { + const expires = storageConfig.expires || 365; + const expirationDate = new Date(Date.now() + (expires * 24 * 60 * 60 * 1000)); + const storageType = storageConfig.type || ''; + const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES); + const useLocalStorage = !storageType || storageType.includes(STORAGE_TYPE_LOCALSTORAGE); + + if (useCookie && storage.cookiesAreEnabled()) { + storage.setCookie(MODULE_NAME, id, expirationDate.toUTCString()); + } + + if (useLocalStorage && storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(MODULE_NAME, id); + } +} + +export const startioIdSubmodule = { + name: MODULE_NAME, + decode(value) { + return value && typeof value === 'string' + ? { 'startioId': value } + : undefined; + }, + getId(config, consentData, storedId) { + const configParams = (config && config.params) || {}; + const storageConfig = (config && config.storage) || {}; + + if (storedId) { + return { id: storedId }; + } + + if (!configParams.endpoint || typeof configParams.endpoint !== 'string') { + logError(`${MODULE_NAME} module requires an endpoint parameter.`); + return; + } + + const resp = function (callback) { + const callbacks = { + success: response => { + let responseId; + try { + const responseObj = JSON.parse(response); + if (responseObj && responseObj.id) { + responseId = responseObj.id; + storeId(responseId, storageConfig); + } else { + logError(`${MODULE_NAME}: Server response missing 'id' field`); + } + } catch (error) { + logError(`${MODULE_NAME}: Error parsing server response`, error); + } + callback(responseId); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(configParams.endpoint, callbacks, undefined, { method: 'GET' }); + }; + return { callback: resp }; + }, + + eids: { + 'startioId': { + source: 'start.io', + atype: 3 + }, + } +}; + +submodule('userId', startioIdSubmodule); diff --git a/modules/startioSystem.md b/modules/startioSystem.md new file mode 100644 index 0000000000..44173e78f9 --- /dev/null +++ b/modules/startioSystem.md @@ -0,0 +1,63 @@ +## Start.io User ID Submodule + +The Start.io User ID submodule generates and persists a unique user identifier by fetching it from a publisher-supplied endpoint. The ID is stored in both cookies and local storage for subsequent page loads and is made available to other Prebid.js modules via the standard `eids` interface. + +For integration support, contact prebid@start.io. + +### Prebid Params + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'startioId', + params: { + endpoint: 'https://your-startio-endpoint.example.com/id' + }, + storage: { + type: 'html5', //or 'cookie', or 'cookie&html5', + name: 'startioId', + expires: 365 + } + }] + } +}); +``` + +## Parameter Descriptions for the `userSync` Configuration Section + +The below parameters apply only to the Start.io User ID integration. + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"startioId"` | +| params | Required | Object | Container of all module params. | | +| params.endpoint | Required | String | The URL of the Start.io ID endpoint. Must return a JSON object with an `id` field. | `"https://id.startio.example.com/uid"` | +| storage | Optional | Object | Controls how the ID is persisted. Managed by Prebid.js core; see notes below. | | +| storage.type | Optional | String | Storage mechanism. Accepts `cookie`, `html5`, or `cookie&html5`. Defaults to both when omitted. | `"cookie&html5"` | +| storage.expires | Optional | Number | Cookie / storage TTL in days. Defaults to `365`. | `365` | + +## Server Response Format + +The endpoint specified in `params.endpoint` must return a JSON response containing an `id` field: + +``` +{ + "id": "unique-user-identifier-string" +} +``` + +If the `id` field is missing or the response cannot be parsed, the module logs an error and does not store a value. + +## How It Works + +1. On the first page load (no stored ID exists), the module sends a `GET` request to the configured `endpoint`. +2. The returned `id` is written to both cookies and local storage (respecting the `storage` configuration). +3. On subsequent loads the stored ID is returned directly — no network request is made. +4. The ID is exposed to other modules via the extended ID (`eids`) framework with source `start.io` and `atype: 3`. + +## Notes + +- The `endpoint` parameter is required. The module will log an error and return no ID if it is missing or not a string. +- Storage defaults to both cookies and local storage when no explicit `storage.type` is provided. The module checks whether each mechanism is available before writing. +- Cookie expiration is set to `storage.expires` days from the time the ID is first fetched (default 365 days). diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js new file mode 100644 index 0000000000..c891a06a1c --- /dev/null +++ b/test/spec/modules/startioSystem_spec.js @@ -0,0 +1,217 @@ +import * as utils from '../../../src/utils.js'; +import { server } from 'test/mocks/xhr.js'; +import { startioIdSubmodule, storage } from 'modules/startioSystem.js'; + +describe('StartIO ID System', function () { + let sandbox; + + const validConfig = { + params: { + endpoint: 'https://test-endpoint.start.io/getId' + }, + storage: { + expires: 365 + } + }; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logError'); + sandbox.stub(storage, 'getCookie').returns(null); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('module registration', function () { + it('should register the submodule', function () { + expect(startioIdSubmodule.name).to.equal('startioId'); + }); + + it('should have eids configuration', function () { + expect(startioIdSubmodule.eids).to.deep.equal({ + 'startioId': { + source: 'start.io', + atype: 3 + } + }); + }); + }); + + describe('decode', function () { + it('should return undefined if no value passed', function () { + expect(startioIdSubmodule.decode()).to.be.undefined; + }); + + it('should return undefined if invalid value passed', function () { + expect(startioIdSubmodule.decode(123)).to.be.undefined; + expect(startioIdSubmodule.decode(null)).to.be.undefined; + expect(startioIdSubmodule.decode({})).to.be.undefined; + expect(startioIdSubmodule.decode('')).to.be.undefined; + }); + + it('should return startioId object if valid string passed', function () { + const id = 'test-uuid-12345'; + const result = startioIdSubmodule.decode(id); + expect(result).to.deep.equal({ 'startioId': id }); + }); + }); + + describe('getId', function () { + it('should log an error if no endpoint configured', function () { + const config = { params: {} }; + startioIdSubmodule.getId(config); + expect(utils.logError.calledOnce).to.be.true; + expect(utils.logError.args[0][0]).to.include('requires an endpoint'); + }); + + it('should log an error if endpoint is not a string', function () { + const config = { params: { endpoint: 123 } }; + startioIdSubmodule.getId(config); + expect(utils.logError.calledOnce).to.be.true; + }); + + it('should return existing storedId immediately if provided', function () { + const storedId = 'existing-id-12345'; + const result = startioIdSubmodule.getId(validConfig, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(server.requests.length).to.eq(0); + }); + + it('should fetch new ID from server if no storedId provided', function () { + const result = startioIdSubmodule.getId(validConfig); + expect(result).to.have.property('callback'); + expect(typeof result.callback).to.equal('function'); + }); + + it('should invoke callback with ID from server response', function () { + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + expect(request.method).to.eq('GET'); + expect(request.url).to.eq(validConfig.params.endpoint); + + const serverId = 'new-server-id-12345'; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(serverId); + }); + + it('should store new ID in both cookie and localStorage by default', function () { + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const serverId = 'new-server-id-12345'; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(storage.setCookie.calledOnce).to.be.true; + expect(storage.setCookie.args[0][0]).to.equal('startioId'); + expect(storage.setCookie.args[0][1]).to.equal(serverId); + expect(storage.setDataInLocalStorage.calledOnce).to.be.true; + expect(storage.setDataInLocalStorage.args[0][0]).to.equal('startioId'); + expect(storage.setDataInLocalStorage.args[0][1]).to.equal(serverId); + }); + + it('should store only in cookie when storage type is cookie', function () { + const config = { ...validConfig, storage: { ...validConfig.storage, type: 'cookie' } }; + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const serverId = 'new-server-id-12345'; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(storage.setCookie.calledOnce).to.be.true; + expect(storage.setDataInLocalStorage.called).to.be.false; + }); + + it('should store only in localStorage when storage type is html5', function () { + const config = { ...validConfig, storage: { ...validConfig.storage, type: 'html5' } }; + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const serverId = 'new-server-id-12345'; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(storage.setCookie.called).to.be.false; + expect(storage.setDataInLocalStorage.calledOnce).to.be.true; + }); + + it('should log error if server response is missing id field', function () { + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ wrongField: 'value' })); + + expect(utils.logError.calledOnce).to.be.true; + expect(utils.logError.args[0][0]).to.include('missing \'id\' field'); + }); + + it('should log error if server response is invalid JSON', function () { + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + request.respond(200, { 'Content-Type': 'application/json' }, 'invalid-json{'); + + expect(utils.logError.calledOnce).to.be.true; + expect(utils.logError.args[0][0]).to.include('Error parsing'); + }); + + it('should log error if server request fails', function () { + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + request.error(); + + expect(utils.logError.calledOnce).to.be.true; + expect(utils.logError.args[0][0]).to.include('encountered an error'); + }); + + it('should not store in cookie if cookies are disabled', function () { + storage.cookiesAreEnabled.returns(false); + + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + const serverId = 'new-server-id-12345'; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(storage.setCookie.called).to.be.false; + expect(storage.setDataInLocalStorage.calledOnce).to.be.true; + }); + + it('should not store in localStorage if localStorage is disabled', function () { + storage.localStorageIsEnabled.returns(false); + + const callbackSpy = sinon.spy(); + const callback = startioIdSubmodule.getId(validConfig).callback; + callback(callbackSpy); + + const request = server.requests[0]; + const serverId = 'new-server-id-12345'; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + + expect(storage.setCookie.calledOnce).to.be.true; + expect(storage.setDataInLocalStorage.called).to.be.false; + }); + }); +}); From 7db347257f74be96e3fc5014a6ff18d1a41643cb Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 3 Feb 2026 16:43:57 +0200 Subject: [PATCH 02/17] Update Start.io User ID module to ensure callbacks and AJAX requests fire regardless of endpoint validity --- modules/startioSystem.js | 9 +++------ test/spec/modules/startioSystem_spec.js | 23 ++++++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 896d045e78..0a0073212b 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -5,6 +5,7 @@ import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } fr import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const MODULE_NAME = 'startioId'; +const DEFAULT_ENDPOINT = ''; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); function storeId(id, storageConfig = {}) { @@ -33,16 +34,12 @@ export const startioIdSubmodule = { getId(config, consentData, storedId) { const configParams = (config && config.params) || {}; const storageConfig = (config && config.storage) || {}; + const endpoint = configParams.endpoint || DEFAULT_ENDPOINT; if (storedId) { return { id: storedId }; } - if (!configParams.endpoint || typeof configParams.endpoint !== 'string') { - logError(`${MODULE_NAME} module requires an endpoint parameter.`); - return; - } - const resp = function (callback) { const callbacks = { success: response => { @@ -65,7 +62,7 @@ export const startioIdSubmodule = { callback(); } }; - ajax(configParams.endpoint, callbacks, undefined, { method: 'GET' }); + ajax(endpoint, callbacks, undefined, { method: 'GET' }); }; return { callback: resp }; }, diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js index c891a06a1c..2fc6bf7966 100644 --- a/test/spec/modules/startioSystem_spec.js +++ b/test/spec/modules/startioSystem_spec.js @@ -64,17 +64,26 @@ describe('StartIO ID System', function () { }); describe('getId', function () { - it('should log an error if no endpoint configured', function () { + it('should return callback and fire ajax even if no endpoint configured', function () { const config = { params: {} }; - startioIdSubmodule.getId(config); - expect(utils.logError.calledOnce).to.be.true; - expect(utils.logError.args[0][0]).to.include('requires an endpoint'); + const result = startioIdSubmodule.getId(config); + expect(result).to.have.property('callback'); + expect(typeof result.callback).to.equal('function'); + + const callbackSpy = sinon.spy(); + result.callback(callbackSpy); + expect(server.requests.length).to.equal(1); }); - it('should log an error if endpoint is not a string', function () { + it('should return callback and fire ajax even if endpoint is not a string', function () { const config = { params: { endpoint: 123 } }; - startioIdSubmodule.getId(config); - expect(utils.logError.calledOnce).to.be.true; + const result = startioIdSubmodule.getId(config); + expect(result).to.have.property('callback'); + expect(typeof result.callback).to.equal('function'); + + const callbackSpy = sinon.spy(); + result.callback(callbackSpy); + expect(server.requests.length).to.equal(1); }); it('should return existing storedId immediately if provided', function () { From 8bbf781f310ecf0251fd3c6ae8105af635217e7f Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 3 Feb 2026 18:06:19 +0200 Subject: [PATCH 03/17] Remove storage-related functionality from Start.io ID submodule and add support for EIDs. --- modules/startioSystem.js | 27 ++----- modules/startioSystem.md | 3 +- test/spec/modules/startioSystem_spec.js | 99 +++++-------------------- 3 files changed, 28 insertions(+), 101 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 0a0073212b..9d2ecfd812 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -1,28 +1,15 @@ +/** + * This module adds startio ID support to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/startioSystem + * @requires module:modules/userId + */ import { logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; -import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const MODULE_NAME = 'startioId'; const DEFAULT_ENDPOINT = ''; -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); - -function storeId(id, storageConfig = {}) { - const expires = storageConfig.expires || 365; - const expirationDate = new Date(Date.now() + (expires * 24 * 60 * 60 * 1000)); - const storageType = storageConfig.type || ''; - const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES); - const useLocalStorage = !storageType || storageType.includes(STORAGE_TYPE_LOCALSTORAGE); - - if (useCookie && storage.cookiesAreEnabled()) { - storage.setCookie(MODULE_NAME, id, expirationDate.toUTCString()); - } - - if (useLocalStorage && storage.localStorageIsEnabled()) { - storage.setDataInLocalStorage(MODULE_NAME, id); - } -} export const startioIdSubmodule = { name: MODULE_NAME, @@ -33,7 +20,6 @@ export const startioIdSubmodule = { }, getId(config, consentData, storedId) { const configParams = (config && config.params) || {}; - const storageConfig = (config && config.storage) || {}; const endpoint = configParams.endpoint || DEFAULT_ENDPOINT; if (storedId) { @@ -48,7 +34,6 @@ export const startioIdSubmodule = { const responseObj = JSON.parse(response); if (responseObj && responseObj.id) { responseId = responseObj.id; - storeId(responseId, storageConfig); } else { logError(`${MODULE_NAME}: Server response missing 'id' field`); } diff --git a/modules/startioSystem.md b/modules/startioSystem.md index 44173e78f9..74eba142e7 100644 --- a/modules/startioSystem.md +++ b/modules/startioSystem.md @@ -34,7 +34,8 @@ The below parameters apply only to the Start.io User ID integration. | params | Required | Object | Container of all module params. | | | params.endpoint | Required | String | The URL of the Start.io ID endpoint. Must return a JSON object with an `id` field. | `"https://id.startio.example.com/uid"` | | storage | Optional | Object | Controls how the ID is persisted. Managed by Prebid.js core; see notes below. | | -| storage.type | Optional | String | Storage mechanism. Accepts `cookie`, `html5`, or `cookie&html5`. Defaults to both when omitted. | `"cookie&html5"` | +| storage.name | Required | String | The cookie or local-storage key used to persist the ID. Must match the module name. | `"startioId"` | +| storage.type | Optional | String | Storage mechanism. `"cookie"` for cookies only, `"html5"` for localStorage only. Omit to use both. | `"html5"` | | storage.expires | Optional | Number | Cookie / storage TTL in days. Defaults to `365`. | `365` | ## Server Response Format diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js index 2fc6bf7966..ac20ef18b9 100644 --- a/test/spec/modules/startioSystem_spec.js +++ b/test/spec/modules/startioSystem_spec.js @@ -1,6 +1,7 @@ import * as utils from '../../../src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { startioIdSubmodule, storage } from 'modules/startioSystem.js'; +import { startioIdSubmodule } from 'modules/startioSystem.js'; +import { createEidsArray } from '../../../modules/userId/eids.js'; describe('StartIO ID System', function () { let sandbox; @@ -17,12 +18,6 @@ describe('StartIO ID System', function () { beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(utils, 'logError'); - sandbox.stub(storage, 'getCookie').returns(null); - sandbox.stub(storage, 'setCookie'); - sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); - sandbox.stub(storage, 'setDataInLocalStorage'); - sandbox.stub(storage, 'cookiesAreEnabled').returns(true); - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); }); afterEach(function () { @@ -63,6 +58,24 @@ describe('StartIO ID System', function () { }); }); + describe('eid', function () { + it('should generate correct EID', function () { + const TEST_UID = 'test-uid-value'; + const eids = createEidsArray(startioIdSubmodule.decode(TEST_UID), new Map(Object.entries(startioIdSubmodule.eids))); + expect(eids).to.eql([ + { + source: 'start.io', + uids: [ + { + atype: 3, + id: TEST_UID + } + ] + } + ]); + }); + }); + describe('getId', function () { it('should return callback and fire ajax even if no endpoint configured', function () { const config = { params: {} }; @@ -115,48 +128,6 @@ describe('StartIO ID System', function () { expect(callbackSpy.lastCall.lastArg).to.equal(serverId); }); - it('should store new ID in both cookie and localStorage by default', function () { - const callbackSpy = sinon.spy(); - const callback = startioIdSubmodule.getId(validConfig).callback; - callback(callbackSpy); - - const serverId = 'new-server-id-12345'; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); - - expect(storage.setCookie.calledOnce).to.be.true; - expect(storage.setCookie.args[0][0]).to.equal('startioId'); - expect(storage.setCookie.args[0][1]).to.equal(serverId); - expect(storage.setDataInLocalStorage.calledOnce).to.be.true; - expect(storage.setDataInLocalStorage.args[0][0]).to.equal('startioId'); - expect(storage.setDataInLocalStorage.args[0][1]).to.equal(serverId); - }); - - it('should store only in cookie when storage type is cookie', function () { - const config = { ...validConfig, storage: { ...validConfig.storage, type: 'cookie' } }; - const callbackSpy = sinon.spy(); - const callback = startioIdSubmodule.getId(config).callback; - callback(callbackSpy); - - const serverId = 'new-server-id-12345'; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); - - expect(storage.setCookie.calledOnce).to.be.true; - expect(storage.setDataInLocalStorage.called).to.be.false; - }); - - it('should store only in localStorage when storage type is html5', function () { - const config = { ...validConfig, storage: { ...validConfig.storage, type: 'html5' } }; - const callbackSpy = sinon.spy(); - const callback = startioIdSubmodule.getId(config).callback; - callback(callbackSpy); - - const serverId = 'new-server-id-12345'; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); - - expect(storage.setCookie.called).to.be.false; - expect(storage.setDataInLocalStorage.calledOnce).to.be.true; - }); - it('should log error if server response is missing id field', function () { const callbackSpy = sinon.spy(); const callback = startioIdSubmodule.getId(validConfig).callback; @@ -192,35 +163,5 @@ describe('StartIO ID System', function () { expect(utils.logError.calledOnce).to.be.true; expect(utils.logError.args[0][0]).to.include('encountered an error'); }); - - it('should not store in cookie if cookies are disabled', function () { - storage.cookiesAreEnabled.returns(false); - - const callbackSpy = sinon.spy(); - const callback = startioIdSubmodule.getId(validConfig).callback; - callback(callbackSpy); - - const request = server.requests[0]; - const serverId = 'new-server-id-12345'; - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); - - expect(storage.setCookie.called).to.be.false; - expect(storage.setDataInLocalStorage.calledOnce).to.be.true; - }); - - it('should not store in localStorage if localStorage is disabled', function () { - storage.localStorageIsEnabled.returns(false); - - const callbackSpy = sinon.spy(); - const callback = startioIdSubmodule.getId(validConfig).callback; - callback(callbackSpy); - - const request = server.requests[0]; - const serverId = 'new-server-id-12345'; - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); - - expect(storage.setCookie.calledOnce).to.be.true; - expect(storage.setDataInLocalStorage.called).to.be.false; - }); }); }); From 4f2aee9b154f4d427a79fe7ce5906152ee930aa4 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Thu, 5 Feb 2026 14:49:41 +0200 Subject: [PATCH 04/17] Add iframe-based user syncing to Start.io Bid Adapter with consent parameter support --- modules/startioBidAdapter.js | 21 ++++- modules/startioBidAdapter.md | 19 +++++ test/spec/modules/startioBidAdapter_spec.js | 94 +++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/modules/startioBidAdapter.js b/modules/startioBidAdapter.js index 74629f2cc9..c165b30cdc 100644 --- a/modules/startioBidAdapter.js +++ b/modules/startioBidAdapter.js @@ -1,13 +1,15 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import { logError, isFn, isPlainObject } from '../src/utils.js'; +import { logError, isFn, isPlainObject, formatQS } from '../src/utils.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; +import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js'; const BIDDER_CODE = 'startio'; const METHOD = 'POST'; const GVLID = 1216; const ENDPOINT_URL = `https://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; +const IFRAME_URL = 'test'; const converter = ortbConverter({ imp(buildImp, bidRequest, context) { @@ -151,6 +153,23 @@ export const spec = { }, onSetTargeting: (bid) => { }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + if (syncOptions.iframeEnabled) { + const consentParams = getUserSyncParams(gdprConsent, uspConsent, gppConsent); + const queryString = formatQS(consentParams); + const queryParam = queryString ? `?${queryString}` : ''; + + syncs.push({ + type: 'iframe', + url: `${IFRAME_URL}${queryParam}` + }); + } + + return syncs; + } }; registerBidder(spec); diff --git a/modules/startioBidAdapter.md b/modules/startioBidAdapter.md index 172af1aeb4..6404b77ba2 100644 --- a/modules/startioBidAdapter.md +++ b/modules/startioBidAdapter.md @@ -94,6 +94,25 @@ var nativeAdUnits = [ ]; ``` +# User Syncs + +The adapter supports iframe-based user syncing. When `iframeEnabled` is set to `true` in the sync options, the adapter returns a single iframe sync URL. GDPR, USP (CCPA), and GPP consent strings are automatically appended as query parameters when present. + +``` +pbjs.setConfig({ + userSync: { + iframeEnabled: true + } +}); +``` + +**Consent parameters included in the sync URL (when available):** +- `gdpr` – `1` if GDPR applies, `0` otherwise +- `gdpr_consent` – the TCF consent string +- `us_privacy` – the USP/CCPA consent string (e.g. `1YNN`) +- `gpp` – the GPP consent string +- `gpp_sections` – applicable GPP section IDs + # Additional Notes - The adapter processes requests via OpenRTB 2.5 standards. - Ensure that the `accountId` parameter is set correctly for your integration. diff --git a/test/spec/modules/startioBidAdapter_spec.js b/test/spec/modules/startioBidAdapter_spec.js index 021c11e80d..4abb0f3bd2 100644 --- a/test/spec/modules/startioBidAdapter_spec.js +++ b/test/spec/modules/startioBidAdapter_spec.js @@ -370,4 +370,98 @@ describe('Prebid Adapter: Startio', function () { }); } }); + + describe('getUserSyncs', function () { + it('should return an iframe sync when iframeEnabled is true', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.be.a('string'); + }); + + it('should return an empty array when iframeEnabled is false', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: false }, []); + + expect(syncs).to.have.lengthOf(0); + }); + + it('should return an empty array when syncOptions is empty', function () { + const syncs = spec.getUserSyncs({}, []); + + expect(syncs).to.have.lengthOf(0); + }); + + it('should append GDPR consent params to the sync URL', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + }); + + it('should append gdpr=0 when gdprApplies is false', function () { + const gdprConsent = { + gdprApplies: false, + consentString: '' + }; + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + + expect(syncs[0].url).to.include('gdpr=0'); + }); + + it('should append USP consent param to the sync URL', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, '1YNN'); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.include('us_privacy=1YNN'); + }); + + it('should append GPP consent params to the sync URL', function () { + const gppConsent = { + gppString: 'DBABMA~BAAAAAAAAgA.QA', + applicableSections: [7, 8] + }; + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined, gppConsent); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.include('gpp=DBABMA~BAAAAAAAAgA.QA'); + expect(syncs[0].url).to.include('gpp_sid=7,8'); + }); + + it('should append all consent params together when all are provided', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'testConsent' + }; + const uspConsent = '1YNN'; + const gppConsent = { + gppString: 'testGpp', + applicableSections: [2] + }; + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=testConsent'); + expect(syncs[0].url).to.include('us_privacy=1YNN'); + expect(syncs[0].url).to.include('gpp=testGpp'); + expect(syncs[0].url).to.include('gpp_sid=2'); + }); + + it('should not append query string when no consent params are provided', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.not.include('?'); + }); + }); }); From d6e0c985683a574e63685bf0cc9710115de82f1c Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Thu, 5 Feb 2026 14:51:17 +0200 Subject: [PATCH 05/17] Simplify Start.io ID module by removing storage-related parameters and endpoint configuration. --- modules/startioSystem.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/modules/startioSystem.md b/modules/startioSystem.md index 74eba142e7..53b6e03531 100644 --- a/modules/startioSystem.md +++ b/modules/startioSystem.md @@ -10,15 +10,7 @@ For integration support, contact prebid@start.io. pbjs.setConfig({ userSync: { userIds: [{ - name: 'startioId', - params: { - endpoint: 'https://your-startio-endpoint.example.com/id' - }, - storage: { - type: 'html5', //or 'cookie', or 'cookie&html5', - name: 'startioId', - expires: 365 - } + name: 'startioId' }] } }); @@ -31,12 +23,6 @@ The below parameters apply only to the Start.io User ID integration. | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | The name of this module. | `"startioId"` | -| params | Required | Object | Container of all module params. | | -| params.endpoint | Required | String | The URL of the Start.io ID endpoint. Must return a JSON object with an `id` field. | `"https://id.startio.example.com/uid"` | -| storage | Optional | Object | Controls how the ID is persisted. Managed by Prebid.js core; see notes below. | | -| storage.name | Required | String | The cookie or local-storage key used to persist the ID. Must match the module name. | `"startioId"` | -| storage.type | Optional | String | Storage mechanism. `"cookie"` for cookies only, `"html5"` for localStorage only. Omit to use both. | `"html5"` | -| storage.expires | Optional | Number | Cookie / storage TTL in days. Defaults to `365`. | `365` | ## Server Response Format From e7774d77468b08f02748d4bb9f93ee7406d1e04d Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Mon, 9 Feb 2026 17:08:11 +0200 Subject: [PATCH 06/17] Update Start.io modules to use new endpoint URL and improve user sync logic --- modules/startioBidAdapter.js | 9 +++++++-- modules/startioSystem.js | 7 ++----- test/spec/modules/startioBidAdapter_spec.js | 2 +- test/spec/modules/startioSystem_spec.js | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/startioBidAdapter.js b/modules/startioBidAdapter.js index c165b30cdc..5a087680db 100644 --- a/modules/startioBidAdapter.js +++ b/modules/startioBidAdapter.js @@ -9,7 +9,7 @@ const BIDDER_CODE = 'startio'; const METHOD = 'POST'; const GVLID = 1216; const ENDPOINT_URL = `https://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; -const IFRAME_URL = 'test'; +const IFRAME_URL = 'https://cs.startappnetwork.com/sync?p=1002'; const converter = ortbConverter({ imp(buildImp, bidRequest, context) { @@ -160,7 +160,7 @@ export const spec = { if (syncOptions.iframeEnabled) { const consentParams = getUserSyncParams(gdprConsent, uspConsent, gppConsent); const queryString = formatQS(consentParams); - const queryParam = queryString ? `?${queryString}` : ''; + const queryParam = queryString ? `&${queryString}` : ''; syncs.push({ type: 'iframe', @@ -173,3 +173,8 @@ export const spec = { }; registerBidder(spec); + +// Expose spec for testing purposes in browser environment +if (typeof window !== 'undefined') { + window.startioAdapterSpec = spec; +} diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 9d2ecfd812..e84932c933 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; const MODULE_NAME = 'startioId'; -const DEFAULT_ENDPOINT = ''; +const DEFAULT_ENDPOINT = 'https://cs.startappnetwork.com/get-uid-obj?p=1002'; export const startioIdSubmodule = { name: MODULE_NAME, @@ -19,9 +19,6 @@ export const startioIdSubmodule = { : undefined; }, getId(config, consentData, storedId) { - const configParams = (config && config.params) || {}; - const endpoint = configParams.endpoint || DEFAULT_ENDPOINT; - if (storedId) { return { id: storedId }; } @@ -47,7 +44,7 @@ export const startioIdSubmodule = { callback(); } }; - ajax(endpoint, callbacks, undefined, { method: 'GET' }); + ajax(DEFAULT_ENDPOINT, callbacks, undefined, { method: 'GET' }); }; return { callback: resp }; }, diff --git a/test/spec/modules/startioBidAdapter_spec.js b/test/spec/modules/startioBidAdapter_spec.js index 4abb0f3bd2..0b21e23bf8 100644 --- a/test/spec/modules/startioBidAdapter_spec.js +++ b/test/spec/modules/startioBidAdapter_spec.js @@ -461,7 +461,7 @@ describe('Prebid Adapter: Startio', function () { const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); expect(syncs).to.have.lengthOf(1); - expect(syncs[0].url).to.not.include('?'); + expect(syncs[0].url).to.equal('https://cs.startappnetwork.com/sync?p=1002'); }); }); }); diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js index ac20ef18b9..d2c1ebf312 100644 --- a/test/spec/modules/startioSystem_spec.js +++ b/test/spec/modules/startioSystem_spec.js @@ -8,7 +8,7 @@ describe('StartIO ID System', function () { const validConfig = { params: { - endpoint: 'https://test-endpoint.start.io/getId' + endpoint: 'https://cs.startappnetwork.com/get-uid-obj?p=1002' }, storage: { expires: 365 From 8e9a6bdd0669a8c38c6fe4a68411943f095ccd94 Mon Sep 17 00:00:00 2001 From: MatanArbel Date: Tue, 10 Feb 2026 13:45:39 +0200 Subject: [PATCH 07/17] Fix documentation --- modules/startioBidAdapter.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/startioBidAdapter.md b/modules/startioBidAdapter.md index 6404b77ba2..6e6d91f20d 100644 --- a/modules/startioBidAdapter.md +++ b/modules/startioBidAdapter.md @@ -25,7 +25,7 @@ var adUnits = [ bidder: 'startio', params: { // REQUIRED - Publisher Account ID - accountId: 'your-account-id', + publisherId: 'your-account-id', // OPTIONAL - Enable test ads testAdsEnabled: true @@ -58,7 +58,7 @@ var videoAdUnits = [ { bidder: 'startio', params: { - accountId: 'your-account-id', + publisherId: 'your-account-id', testAdsEnabled: true } } @@ -85,7 +85,7 @@ var nativeAdUnits = [ { bidder: 'startio', params: { - accountId: 'your-account-id', + publisherId: 'your-account-id', testAdsEnabled: true } } @@ -115,6 +115,6 @@ pbjs.setConfig({ # Additional Notes - The adapter processes requests via OpenRTB 2.5 standards. -- Ensure that the `accountId` parameter is set correctly for your integration. +- Ensure that the `publisherId` parameter is set correctly for your integration. - Test ads can be enabled using `testAdsEnabled: true` during development. - The adapter supports multiple ad formats, allowing publishers to serve banners, native ads and instream video ads seamlessly. From 90386aaa44eec2b00394b4a57fb225ee8805020d Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 14:18:22 +0200 Subject: [PATCH 08/17] Enhance Start.io ID module with storage management, caching, and improved server response handling --- modules/startioSystem.js | 94 ++++++++++++++++++------- modules/startioSystem.md | 16 ++++- test/spec/modules/startioSystem_spec.js | 25 +++++-- 3 files changed, 101 insertions(+), 34 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index e84932c933..4f65bfac05 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -7,10 +7,74 @@ import { logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const MODULE_NAME = 'startioId'; const DEFAULT_ENDPOINT = 'https://cs.startappnetwork.com/get-uid-obj?p=1002'; +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function getCachedId() { + let cachedId; + + if (storage.cookiesAreEnabled()) { + cachedId = storage.getCookie(MODULE_NAME); + } + + if (!cachedId && storage.hasLocalStorage()) { + const expirationStr = storage.getDataFromLocalStorage(`${MODULE_NAME}_exp`); + if (expirationStr) { + const expirationDate = new Date(expirationStr); + if (expirationDate > new Date()) { + cachedId = storage.getDataFromLocalStorage(MODULE_NAME); + } + } + } + + return cachedId || null; +} + +function storeId(id) { + const expiresInDays = 90; + const expirationDate = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000).toUTCString(); + + if (storage.cookiesAreEnabled()) { + storage.setCookie(MODULE_NAME, id, expirationDate, 'None'); + } + + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(`${MODULE_NAME}_exp`, expirationDate); + storage.setDataInLocalStorage(`${MODULE_NAME}_last`, new Date().toUTCString()); + storage.setDataInLocalStorage(MODULE_NAME, id); + } +} + +function fetchIdFromServer(callback) { + const callbacks = { + success: response => { + let responseId; + try { + const responseObj = JSON.parse(response); + if (responseObj && responseObj.uid) { + responseId = responseObj.uid; + storeId(responseId); + } else { + logError(`${MODULE_NAME}: Server response missing 'uid' field`); + } + } catch (error) { + logError(`${MODULE_NAME}: Error parsing server response`, error); + } + callback(responseId); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(DEFAULT_ENDPOINT, callbacks, undefined, { method: 'GET' }); +} + export const startioIdSubmodule = { name: MODULE_NAME, decode(value) { @@ -23,30 +87,12 @@ export const startioIdSubmodule = { return { id: storedId }; } - const resp = function (callback) { - const callbacks = { - success: response => { - let responseId; - try { - const responseObj = JSON.parse(response); - if (responseObj && responseObj.id) { - responseId = responseObj.id; - } else { - logError(`${MODULE_NAME}: Server response missing 'id' field`); - } - } catch (error) { - logError(`${MODULE_NAME}: Error parsing server response`, error); - } - callback(responseId); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); - } - }; - ajax(DEFAULT_ENDPOINT, callbacks, undefined, { method: 'GET' }); - }; - return { callback: resp }; + const cachedId = getCachedId(); + if (cachedId) { + return { id: cachedId }; + } + + return { callback: fetchIdFromServer }; }, eids: { diff --git a/modules/startioSystem.md b/modules/startioSystem.md index 53b6e03531..c96a2c4b59 100644 --- a/modules/startioSystem.md +++ b/modules/startioSystem.md @@ -4,18 +4,28 @@ The Start.io User ID submodule generates and persists a unique user identifier b For integration support, contact prebid@start.io. -### Prebid Params +### Prebid Params Enabling User Sync -``` +To enable iframe-based user syncing for Start.io, include the `filterSettings` configuration in your `userSync` setup: + +```javascript pbjs.setConfig({ userSync: { userIds: [{ name: 'startioId' - }] + }], + filterSettings: { + iframe: { + bidders: ['startio'], + filter: 'include' + } + } } }); ``` +This configuration allows Start.io to sync user data via iframe, which is necessary for cross-domain user identification. + ## Parameter Descriptions for the `userSync` Configuration Section The below parameters apply only to the Start.io User ID integration. diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js index d2c1ebf312..a1ff46dbd5 100644 --- a/test/spec/modules/startioSystem_spec.js +++ b/test/spec/modules/startioSystem_spec.js @@ -2,14 +2,14 @@ import * as utils from '../../../src/utils.js'; import { server } from 'test/mocks/xhr.js'; import { startioIdSubmodule } from 'modules/startioSystem.js'; import { createEidsArray } from '../../../modules/userId/eids.js'; +import { getStorageManager } from '../../../src/storageManager.js'; describe('StartIO ID System', function () { let sandbox; + let storage; const validConfig = { - params: { - endpoint: 'https://cs.startappnetwork.com/get-uid-obj?p=1002' - }, + params: {}, storage: { expires: 365 } @@ -18,6 +18,17 @@ describe('StartIO ID System', function () { beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(utils, 'logError'); + storage = getStorageManager({ moduleType: 'userId', moduleName: 'startioId' }); + + // Clear any cached storage + if (storage.cookiesAreEnabled()) { + storage.setCookie('startioId', '', new Date(0).toUTCString()); + } + if (storage.hasLocalStorage()) { + storage.removeDataFromLocalStorage('startioId'); + storage.removeDataFromLocalStorage('startioId_exp'); + storage.removeDataFromLocalStorage('startioId_last'); + } }); afterEach(function () { @@ -119,16 +130,16 @@ describe('StartIO ID System', function () { const request = server.requests[0]; expect(request.method).to.eq('GET'); - expect(request.url).to.eq(validConfig.params.endpoint); + expect(request.url).to.eq('https://cs.startappnetwork.com/get-uid-obj?p=1002'); const serverId = 'new-server-id-12345'; - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ id: serverId })); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ uid: serverId })); expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.equal(serverId); }); - it('should log error if server response is missing id field', function () { + it('should log error if server response is missing uid field', function () { const callbackSpy = sinon.spy(); const callback = startioIdSubmodule.getId(validConfig).callback; callback(callbackSpy); @@ -137,7 +148,7 @@ describe('StartIO ID System', function () { request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ wrongField: 'value' })); expect(utils.logError.calledOnce).to.be.true; - expect(utils.logError.args[0][0]).to.include('missing \'id\' field'); + expect(utils.logError.args[0][0]).to.include('missing \'uid\' field'); }); it('should log error if server response is invalid JSON', function () { From 9adb313d347cb4e45eafaaa7077b05f82a5ee410 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 14:21:31 +0200 Subject: [PATCH 09/17] Remove window exposure of startioAdapterSpec for browser testing purposes --- modules/startioBidAdapter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/startioBidAdapter.js b/modules/startioBidAdapter.js index 5a087680db..6637dd219b 100644 --- a/modules/startioBidAdapter.js +++ b/modules/startioBidAdapter.js @@ -173,8 +173,3 @@ export const spec = { }; registerBidder(spec); - -// Expose spec for testing purposes in browser environment -if (typeof window !== 'undefined') { - window.startioAdapterSpec = spec; -} From 4b004ba1a049076deea7f8988429f2112a90be49 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 14:23:59 +0200 Subject: [PATCH 10/17] Update Start.io Bid Adapter to clarify prebid params for iframe-based user sync configuration --- modules/startioBidAdapter.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/startioBidAdapter.md b/modules/startioBidAdapter.md index 6e6d91f20d..e36569bde2 100644 --- a/modules/startioBidAdapter.md +++ b/modules/startioBidAdapter.md @@ -94,15 +94,23 @@ var nativeAdUnits = [ ]; ``` -# User Syncs +### Prebid Params Enabling User Sync -The adapter supports iframe-based user syncing. When `iframeEnabled` is set to `true` in the sync options, the adapter returns a single iframe sync URL. GDPR, USP (CCPA), and GPP consent strings are automatically appended as query parameters when present. +To enable iframe-based user syncing for Start.io, include the `filterSettings` configuration in your `userSync` setup: -``` +```javascript pbjs.setConfig({ - userSync: { - iframeEnabled: true - } + userSync: { + userIds: [{ + name: 'startioId' + }], + filterSettings: { + iframe: { + bidders: ['startio'], + filter: 'include' + } + } + } }); ``` From 41e3fff4671e2c66f70d68d308e554b4889728b4 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 14:37:56 +0200 Subject: [PATCH 11/17] Update Start.io Bid Adapter to clarify prebid params for iframe-based user sync configuration --- modules/startioSystem.js | 1 - test/spec/modules/startioSystem_spec.js | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 4f65bfac05..6df2dec998 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -45,7 +45,6 @@ function storeId(id) { if (storage.hasLocalStorage()) { storage.setDataInLocalStorage(`${MODULE_NAME}_exp`, expirationDate); - storage.setDataInLocalStorage(`${MODULE_NAME}_last`, new Date().toUTCString()); storage.setDataInLocalStorage(MODULE_NAME, id); } } diff --git a/test/spec/modules/startioSystem_spec.js b/test/spec/modules/startioSystem_spec.js index a1ff46dbd5..eff2cc431e 100644 --- a/test/spec/modules/startioSystem_spec.js +++ b/test/spec/modules/startioSystem_spec.js @@ -27,7 +27,6 @@ describe('StartIO ID System', function () { if (storage.hasLocalStorage()) { storage.removeDataFromLocalStorage('startioId'); storage.removeDataFromLocalStorage('startioId_exp'); - storage.removeDataFromLocalStorage('startioId_last'); } }); From 127201c8af121639ce4c80fc244c9776df258343 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 14:39:50 +0200 Subject: [PATCH 12/17] id fixed to uid and updated --- modules/startioSystem.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/startioSystem.md b/modules/startioSystem.md index c96a2c4b59..9c68983a4e 100644 --- a/modules/startioSystem.md +++ b/modules/startioSystem.md @@ -36,15 +36,15 @@ The below parameters apply only to the Start.io User ID integration. ## Server Response Format -The endpoint specified in `params.endpoint` must return a JSON response containing an `id` field: +The endpoint specified in `params.endpoint` must return a JSON response containing an `uid` field: ``` { - "id": "unique-user-identifier-string" + "uid": "unique-user-identifier-string" } ``` -If the `id` field is missing or the response cannot be parsed, the module logs an error and does not store a value. +If the `uid` field is missing or the response cannot be parsed, the module logs an error and does not store a value. ## How It Works From b9babdfa89dc5dc4f77b21ef6abf074a429b7faa Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 15:21:37 +0200 Subject: [PATCH 13/17] docs updated --- modules/startioBidAdapter.md | 7 ------- modules/startioSystem.md | 25 ------------------------- 2 files changed, 32 deletions(-) diff --git a/modules/startioBidAdapter.md b/modules/startioBidAdapter.md index e36569bde2..d74e0e64d3 100644 --- a/modules/startioBidAdapter.md +++ b/modules/startioBidAdapter.md @@ -114,13 +114,6 @@ pbjs.setConfig({ }); ``` -**Consent parameters included in the sync URL (when available):** -- `gdpr` – `1` if GDPR applies, `0` otherwise -- `gdpr_consent` – the TCF consent string -- `us_privacy` – the USP/CCPA consent string (e.g. `1YNN`) -- `gpp` – the GPP consent string -- `gpp_sections` – applicable GPP section IDs - # Additional Notes - The adapter processes requests via OpenRTB 2.5 standards. - Ensure that the `publisherId` parameter is set correctly for your integration. diff --git a/modules/startioSystem.md b/modules/startioSystem.md index 9c68983a4e..84bee91fa8 100644 --- a/modules/startioSystem.md +++ b/modules/startioSystem.md @@ -33,28 +33,3 @@ The below parameters apply only to the Start.io User ID integration. | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | The name of this module. | `"startioId"` | - -## Server Response Format - -The endpoint specified in `params.endpoint` must return a JSON response containing an `uid` field: - -``` -{ - "uid": "unique-user-identifier-string" -} -``` - -If the `uid` field is missing or the response cannot be parsed, the module logs an error and does not store a value. - -## How It Works - -1. On the first page load (no stored ID exists), the module sends a `GET` request to the configured `endpoint`. -2. The returned `id` is written to both cookies and local storage (respecting the `storage` configuration). -3. On subsequent loads the stored ID is returned directly — no network request is made. -4. The ID is exposed to other modules via the extended ID (`eids`) framework with source `start.io` and `atype: 3`. - -## Notes - -- The `endpoint` parameter is required. The module will log an error and return no ID if it is missing or not a string. -- Storage defaults to both cookies and local storage when no explicit `storage.type` is provided. The module checks whether each mechanism is available before writing. -- Cookie expiration is set to `storage.expires` days from the time the ID is first fetched (default 365 days). From 93317677e31eed7ac4a68197331f3915b3ee7ea2 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 15:33:14 +0200 Subject: [PATCH 14/17] Make `storeId` and `fetchIdFromServer` functions configurable with `expiresInDays`. --- modules/startioSystem.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 6df2dec998..c37d8e6ac0 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -35,8 +35,8 @@ function getCachedId() { return cachedId || null; } -function storeId(id) { - const expiresInDays = 90; +function storeId(id, expiresInDays) { + expiresInDays = expiresInDays || 90; const expirationDate = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000).toUTCString(); if (storage.cookiesAreEnabled()) { @@ -49,7 +49,7 @@ function storeId(id) { } } -function fetchIdFromServer(callback) { +function fetchIdFromServer(callback, expiresInDays) { const callbacks = { success: response => { let responseId; @@ -57,7 +57,7 @@ function fetchIdFromServer(callback) { const responseObj = JSON.parse(response); if (responseObj && responseObj.uid) { responseId = responseObj.uid; - storeId(responseId); + storeId(responseId, expiresInDays); } else { logError(`${MODULE_NAME}: Server response missing 'uid' field`); } @@ -90,8 +90,11 @@ export const startioIdSubmodule = { if (cachedId) { return { id: cachedId }; } - - return { callback: fetchIdFromServer }; + // eslint-disable-next-line no-debugger + debugger + const storageConfig = config && config.storage; + const expiresInDays = storageConfig && storageConfig.expires; + return { callback: (cb) => fetchIdFromServer(cb, expiresInDays) }; }, eids: { From 3c22f00657d01e1f90123b0629e894434fc94443 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 15:34:32 +0200 Subject: [PATCH 15/17] debugger removed --- modules/startioSystem.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index c37d8e6ac0..87dd9354ac 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -90,8 +90,6 @@ export const startioIdSubmodule = { if (cachedId) { return { id: cachedId }; } - // eslint-disable-next-line no-debugger - debugger const storageConfig = config && config.storage; const expiresInDays = storageConfig && storageConfig.expires; return { callback: (cb) => fetchIdFromServer(cb, expiresInDays) }; From a8fcb9536f06c0da8e2679087c72a39489e46d33 Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 16:12:52 +0200 Subject: [PATCH 16/17] test --- modules/startioSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 87dd9354ac..79b4287e4e 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -36,7 +36,7 @@ function getCachedId() { } function storeId(id, expiresInDays) { - expiresInDays = expiresInDays || 90; + expiresInDays = expiresInDays || 9; const expirationDate = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000).toUTCString(); if (storage.cookiesAreEnabled()) { From f7248ee622042d4256c41bbdb08762e878bb760f Mon Sep 17 00:00:00 2001 From: illiamilshtein Date: Tue, 10 Feb 2026 16:13:19 +0200 Subject: [PATCH 17/17] Extend default cookie expiration for `storeId` from 9 to 90 days. --- modules/startioSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/startioSystem.js b/modules/startioSystem.js index 79b4287e4e..87dd9354ac 100644 --- a/modules/startioSystem.js +++ b/modules/startioSystem.js @@ -36,7 +36,7 @@ function getCachedId() { } function storeId(id, expiresInDays) { - expiresInDays = expiresInDays || 9; + expiresInDays = expiresInDays || 90; const expirationDate = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000).toUTCString(); if (storage.cookiesAreEnabled()) {