From bfb3d4558f12fb0835b6e29592a1aeece1b14d57 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Mon, 22 Jun 2026 12:27:08 +0200 Subject: [PATCH 01/12] [CDX-470] Add `additionalTrackingKeys` option to duplicate tracking events Adds a new `additionalTrackingKeys` client option that duplicates tracking events to additional API keys. When configured, each tracking event queued via RequestQueue is also sent to each additional key with the `key` parameter swapped in both the URL and POST body. --- spec/src/modules/tracker.js | 35 ++++++++++ spec/src/utils/request-queue.js | 115 ++++++++++++++++++++++++++++++++ src/constructorio.js | 2 + src/types/index.d.ts | 1 + src/utils/request-queue.js | 22 ++++++ 5 files changed, 175 insertions(+) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index 6147c992..94d545cf 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -16345,4 +16345,39 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { ).to.equal(true); }); }); + + describe('additionalTrackingKeys', () => { + it('Should send tracking events to both primary and additional keys', (done) => { + const additionalKey = 'extra-test-key'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + additionalTrackingKeys: [additionalKey], + }); + + let callCount = 0; + + const checkComplete = () => { + callCount += 1; + + if (callCount === 2) { + expect(fetchSpy).to.have.been.calledTwice; + + const firstCallUrl = fetchSpy.getCall(0).args[0]; + const secondCallUrl = fetchSpy.getCall(1).args[0]; + + expect(firstCallUrl).to.contain(`key=${testApiKey}`); + expect(secondCallUrl).to.contain(`key=${additionalKey}`); + + done(); + } + }; + + tracker.on('success', checkComplete); + tracker.on('error', checkComplete); + + expect(tracker.trackSessionStartV2()).to.equal(true); + }); + }); }); diff --git a/spec/src/utils/request-queue.js b/spec/src/utils/request-queue.js index 767e5bed..d05446d2 100644 --- a/spec/src/utils/request-queue.js +++ b/spec/src/utils/request-queue.js @@ -318,6 +318,121 @@ describe('ConstructorIO - Utils - Request Queue', function utilsRequestQueue() { }); }); + describe('additionalTrackingKeys', () => { + let defaultAgent; + let cleanup; + + before(() => { + helpers.clearStorage(); + }); + + beforeEach(() => { + global.CLIENT_VERSION = 'cio-mocha'; + cleanup = jsdom(); + defaultAgent = window.navigator.userAgent; + }); + + afterEach(() => { + window.navigator.__defineGetter__('userAgent', () => defaultAgent); + delete global.CLIENT_VERSION; + cleanup(); + helpers.clearStorage(); + }); + + it('Should add duplicate requests for each additional tracking key', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + additionalTrackingKeys: ['extra-key-1', 'extra-key-2'], + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123', 'POST', { action: 'session_start', key: 'primary-key' }); + + const queue = RequestQueue.get(); + expect(queue).to.be.an('array').length(3); + + // Primary request + expect(queue[0].url).to.contain('key=primary-key'); + expect(queue[0].body.key).to.equal('primary-key'); + + // First additional key + expect(queue[1].url).to.contain('key=extra-key-1'); + expect(queue[1].url).to.not.contain('key=primary-key'); + expect(queue[1].body.key).to.equal('extra-key-1'); + + // Second additional key + expect(queue[2].url).to.contain('key=extra-key-2'); + expect(queue[2].url).to.not.contain('key=primary-key'); + expect(queue[2].body.key).to.equal('extra-key-2'); + }); + + it('Should not add duplicates when additionalTrackingKeys is an empty array', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + additionalTrackingKeys: [], + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123'); + + expect(RequestQueue.get()).to.be.an('array').length(1); + }); + + it('Should not add duplicates when additionalTrackingKeys is not provided', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123'); + + expect(RequestQueue.get()).to.be.an('array').length(1); + }); + + it('Should add duplicate GET requests without a body', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + additionalTrackingKeys: ['extra-key-1'], + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123'); + + const queue = RequestQueue.get(); + expect(queue).to.be.an('array').length(2); + + expect(queue[0].url).to.contain('key=primary-key'); + expect(queue[1].url).to.contain('key=extra-key-1'); + expect(queue[1].url).to.contain('action=session_start'); + expect(queue[1].body.key).to.equal('extra-key-1'); + }); + + it('Should skip invalid entries in additionalTrackingKeys', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + additionalTrackingKeys: ['valid-key', '', null, 123, 'another-valid-key'], + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123', 'POST', { action: 'session_start', key: 'primary-key' }); + + const queue = RequestQueue.get(); + expect(queue).to.be.an('array').length(3); + expect(queue[1].url).to.contain('key=valid-key'); + expect(queue[2].url).to.contain('key=another-valid-key'); + }); + }); + describe('send', () => { let fetchSpy = null; let cleanup; diff --git a/src/constructorio.js b/src/constructorio.js index b0691dd3..907685ae 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -93,6 +93,7 @@ class ConstructorIO { beaconMode, networkParameters, humanityCheckLocation, + additionalTrackingKeys, } = options; if (!apiKey || typeof apiKey !== 'string') { @@ -141,6 +142,7 @@ class ConstructorIO { beaconMode: (beaconMode === false) ? false : true, // Defaults to 'true', networkParameters: networkParameters || {}, humanityCheckLocation: humanityCheckLocation || 'session', + additionalTrackingKeys, }; // Expose global modules diff --git a/src/types/index.d.ts b/src/types/index.d.ts index bd093e43..15b3ba1e 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -71,6 +71,7 @@ export interface ConstructorClientOptions { beaconMode?: boolean; networkParameters?: NetworkParameters; humanityCheckLocation?: 'session' | 'local'; + additionalTrackingKeys?: string[]; } export interface RequestFeature extends Record { diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index 291d0724..d79180a0 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -53,6 +53,28 @@ class RequestQueue { body, networkParameters, }); + + // Duplicate request for each additional tracking key + const additionalKeys = this.options?.additionalTrackingKeys; + + if (Array.isArray(additionalKeys) && additionalKeys.length) { + const encodedOriginalKey = encodeURIComponent(this.options.apiKey); + + const validKeys = additionalKeys.filter((key) => key && typeof key === 'string'); + + validKeys.forEach((additionalKey) => { + const encodedKey = encodeURIComponent(additionalKey); + const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedKey}`); + + queue.push({ + url: obfuscatePiiRequest(swappedUrl), + method, + body: { ...body, key: additionalKey }, + networkParameters, + }); + }); + } + RequestQueue.set(queue); } } From ad68a1864e0699efd2f1e4785d72e4297512a284 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Mon, 22 Jun 2026 12:36:51 +0200 Subject: [PATCH 02/12] Change encodeURIComponent to encodeURIComponentRFC3986 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/utils/request-queue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index d79180a0..d398c85b 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -58,12 +58,12 @@ class RequestQueue { const additionalKeys = this.options?.additionalTrackingKeys; if (Array.isArray(additionalKeys) && additionalKeys.length) { - const encodedOriginalKey = encodeURIComponent(this.options.apiKey); + const encodedOriginalKey = helpers.encodeURIComponentRFC3986(this.options.apiKey); const validKeys = additionalKeys.filter((key) => key && typeof key === 'string'); validKeys.forEach((additionalKey) => { - const encodedKey = encodeURIComponent(additionalKey); + const encodedKey = helpers.encodeURIComponentRFC3986(additionalKey); const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedKey}`); queue.push({ From 35750b3e0e21e3ffe27e410977f90107f303d54c Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Mon, 22 Jun 2026 12:38:14 +0200 Subject: [PATCH 03/12] Add additionalTrackingKeys parameter to ConstructorIO client --- src/constructorio.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constructorio.js b/src/constructorio.js index 907685ae..ea8c2ec9 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -59,6 +59,7 @@ class ConstructorIO { * @param {object} [parameters.networkParameters] - Parameters relevant to network requests * @param {number} [parameters.networkParameters.timeout] - Request timeout (in milliseconds) - may be overridden within individual method calls * @param {string} [parameters.humanityCheckLocation='session'] - Storage location for the humanity check flag ('session' for sessionStorage, 'local' for localStorage) + * @param {string[]} [parameters.additionalTrackingKeys] - Additional API keys to duplicate tracking events to * @property {object} search - Interface to {@link module:search} * @property {object} browse - Interface to {@link module:browse} * @property {object} autocomplete - Interface to {@link module:autocomplete} From b2b471bd9f378e127c330c74b279579c9cee3c85 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Mon, 22 Jun 2026 12:43:29 +0200 Subject: [PATCH 04/12] Address PR feedback: add JSDoc for additionalTrackingKeys, rename test --- spec/src/utils/request-queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/src/utils/request-queue.js b/spec/src/utils/request-queue.js index d05446d2..60824589 100644 --- a/spec/src/utils/request-queue.js +++ b/spec/src/utils/request-queue.js @@ -395,7 +395,7 @@ describe('ConstructorIO - Utils - Request Queue', function utilsRequestQueue() { expect(RequestQueue.get()).to.be.an('array').length(1); }); - it('Should add duplicate GET requests without a body', () => { + it('Should add duplicate requests for GET method', () => { store.session.set(humanityStorageKey, true); const requests = new RequestQueue({ sendTrackingEvents: true, From 2947a6e1d65d3bc14eaebf3b4d5df02022a75308 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Fri, 3 Jul 2026 12:46:59 +0200 Subject: [PATCH 05/12] Address PR review: deduplicate keys with Set, strengthen tests - Use Set to remove duplicate additionalTrackingKeys and exclude the primary apiKey - Assert duplicate request URL does not contain the original key - Add test verifying request bodies are identical except for the key field - Improve JSDoc description for additionalTrackingKeys parameter Co-Authored-By: Claude Opus 4.6 --- spec/src/modules/tracker.js | 45 +++++++++++++++++++++++++++++++++++++ src/constructorio.js | 2 +- src/utils/request-queue.js | 6 +++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index 94d545cf..3e0e3b14 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -16369,6 +16369,7 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(firstCallUrl).to.contain(`key=${testApiKey}`); expect(secondCallUrl).to.contain(`key=${additionalKey}`); + expect(secondCallUrl).to.not.contain(`key=${testApiKey}`); done(); } @@ -16379,5 +16380,49 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackSessionStartV2()).to.equal(true); }); + + it('Should send identical request bodies for primary and additional keys', (done) => { + const additionalKey = 'extra-test-key'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + additionalTrackingKeys: [additionalKey], + }); + + let callCount = 0; + + const checkComplete = () => { + callCount += 1; + + if (callCount === 2) { + expect(fetchSpy).to.have.been.calledTwice; + + const firstCallOptions = fetchSpy.getCall(0).args[1]; + const secondCallOptions = fetchSpy.getCall(1).args[1]; + + const firstBody = JSON.parse(firstCallOptions.body); + const secondBody = JSON.parse(secondCallOptions.body); + + expect(firstBody.key).to.equal(testApiKey); + expect(secondBody.key).to.equal(additionalKey); + + // Verify bodies are the same except for the key + const firstBodyWithoutKey = { ...firstBody }; + const secondBodyWithoutKey = { ...secondBody }; + delete firstBodyWithoutKey.key; + delete secondBodyWithoutKey.key; + + expect(firstBodyWithoutKey).to.deep.equal(secondBodyWithoutKey); + + done(); + } + }; + + tracker.on('success', checkComplete); + tracker.on('error', checkComplete); + + expect(tracker.trackInputFocusV2('test query')).to.equal(true); + }); }); }); diff --git a/src/constructorio.js b/src/constructorio.js index ea8c2ec9..e8c35cce 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -59,7 +59,7 @@ class ConstructorIO { * @param {object} [parameters.networkParameters] - Parameters relevant to network requests * @param {number} [parameters.networkParameters.timeout] - Request timeout (in milliseconds) - may be overridden within individual method calls * @param {string} [parameters.humanityCheckLocation='session'] - Storage location for the humanity check flag ('session' for sessionStorage, 'local' for localStorage) - * @param {string[]} [parameters.additionalTrackingKeys] - Additional API keys to duplicate tracking events to + * @param {string[]} [parameters.additionalTrackingKeys] - Additional API keys that each receive a duplicate of every tracking event. Events are always sent to the primary `apiKey` and, in addition, to every key in this array. * @property {object} search - Interface to {@link module:search} * @property {object} browse - Interface to {@link module:browse} * @property {object} autocomplete - Interface to {@link module:autocomplete} diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index d398c85b..c13e62c2 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -60,9 +60,11 @@ class RequestQueue { if (Array.isArray(additionalKeys) && additionalKeys.length) { const encodedOriginalKey = helpers.encodeURIComponentRFC3986(this.options.apiKey); - const validKeys = additionalKeys.filter((key) => key && typeof key === 'string'); + const uniqueKeys = new Set( + additionalKeys.filter((key) => key && typeof key === 'string' && key !== this.options.apiKey), + ); - validKeys.forEach((additionalKey) => { + uniqueKeys.forEach((additionalKey) => { const encodedKey = helpers.encodeURIComponentRFC3986(additionalKey); const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedKey}`); From ad7223a12e4cbe3986c86e97a35316cce0fa9cad Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Wed, 15 Jul 2026 11:28:53 +0200 Subject: [PATCH 06/12] add additionalTrackingKeys option to setClientOptions --- src/constructorio.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/constructorio.js b/src/constructorio.js index ef5c5247..483b6f4e 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -185,7 +185,7 @@ class ConstructorIO { */ setClientOptions(options) { if (Object.keys(options).length) { - const { apiKey, segments, testCells, sessionId, userId, sendTrackingEvents, serviceUrl } = options; + const { apiKey, segments, testCells, sessionId, userId, sendTrackingEvents, serviceUrl, additionalTrackingKeys } = options; if (apiKey) { this.options.apiKey = apiKey; @@ -217,6 +217,10 @@ class ConstructorIO { const formattedServiceUrl = helpers.addHTTPSToString(normalizedServiceUrl, this.options.allowHttpServiceUrl); this.options.serviceUrl = formattedServiceUrl || this.options.serviceUrl; } + + if (Array.isArray(additionalTrackingKeys)) { + this.options.additionalTrackingKeys = additionalTrackingKeys; + } } } } From 5ba822ed37c5bf4ac09dbc4895eb4bceb1e2b6a3 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Wed, 15 Jul 2026 11:29:26 +0200 Subject: [PATCH 07/12] rename encodedKey to encodedAdditionalKey --- src/utils/request-queue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index c13e62c2..18c9ddbb 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -65,8 +65,8 @@ class RequestQueue { ); uniqueKeys.forEach((additionalKey) => { - const encodedKey = helpers.encodeURIComponentRFC3986(additionalKey); - const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedKey}`); + const encodedAdditionalKey = helpers.encodeURIComponentRFC3986(additionalKey); + const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedAdditionalKey}`); queue.push({ url: obfuscatePiiRequest(swappedUrl), From bea7b45e3fb72801c91736321ad4f7e6da3926c6 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Wed, 15 Jul 2026 11:36:23 +0200 Subject: [PATCH 08/12] Add test for additionalTrackingKeys deduplication against primary key --- spec/src/utils/request-queue.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/src/utils/request-queue.js b/spec/src/utils/request-queue.js index 60824589..b2302bbd 100644 --- a/spec/src/utils/request-queue.js +++ b/spec/src/utils/request-queue.js @@ -431,6 +431,23 @@ describe('ConstructorIO - Utils - Request Queue', function utilsRequestQueue() { expect(queue[1].url).to.contain('key=valid-key'); expect(queue[2].url).to.contain('key=another-valid-key'); }); + + it('Should not duplicate events when additionalTrackingKeys contains the primary key or duplicates', () => { + store.session.set(humanityStorageKey, true); + const requests = new RequestQueue({ + sendTrackingEvents: true, + trackingSendDelay: 1, + apiKey: 'primary-key', + additionalTrackingKeys: ['extra-key', 'primary-key', 'extra-key'], + }); + + requests.queue('https://ac.cnstrc.com/behavior?action=session_start&key=primary-key&_dt=123', 'POST', { action: 'session_start', key: 'primary-key' }); + + const queue = RequestQueue.get(); + expect(queue).to.be.an('array').length(2); + expect(queue[0].url).to.contain('key=primary-key'); + expect(queue[1].url).to.contain('key=extra-key'); + }); }); describe('send', () => { From 70d7fa0288cac328fed66e0f20c9772a7972e531 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Wed, 15 Jul 2026 11:37:01 +0200 Subject: [PATCH 09/12] Remove body.key assertion --- spec/src/utils/request-queue.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/src/utils/request-queue.js b/spec/src/utils/request-queue.js index b2302bbd..51f612f2 100644 --- a/spec/src/utils/request-queue.js +++ b/spec/src/utils/request-queue.js @@ -412,7 +412,6 @@ describe('ConstructorIO - Utils - Request Queue', function utilsRequestQueue() { expect(queue[0].url).to.contain('key=primary-key'); expect(queue[1].url).to.contain('key=extra-key-1'); expect(queue[1].url).to.contain('action=session_start'); - expect(queue[1].body.key).to.equal('extra-key-1'); }); it('Should skip invalid entries in additionalTrackingKeys', () => { From 7dc32eec5b1ef9b1f52bcae8bd713e656361d644 Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Wed, 15 Jul 2026 11:42:55 +0200 Subject: [PATCH 10/12] Add URL key check to additionalTrackingKeys test --- spec/src/modules/tracker.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index edcc055a..fb399ec8 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -17380,6 +17380,9 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(firstBody.key).to.equal(testApiKey); expect(secondBody.key).to.equal(additionalKey); + const secondCallUrl = fetchSpy.getCall(1).args[0]; + expect(secondCallUrl).to.not.contain(`key=${testApiKey}`); + // Verify bodies are the same except for the key const firstBodyWithoutKey = { ...firstBody }; const secondBodyWithoutKey = { ...secondBody }; From 3a6875f5f7b388eff311b1d93f56e1a6ea8c7e7d Mon Sep 17 00:00:00 2001 From: Hubert Gajewski Date: Thu, 16 Jul 2026 13:17:50 +0200 Subject: [PATCH 11/12] Add additionalTrackingKeys JSDoc to setClientOptions and test for dynamic updates --- spec/src/constructorio.js | 16 ++++++++++++++++ src/constructorio.js | 1 + 2 files changed, 17 insertions(+) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index 989a794b..2fa55e91 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -918,6 +918,22 @@ if (!bundled) { expect(instance.options).to.have.property('sessionId').to.deep.equal(newSessionId); }); + it('Should use additionalTrackingKeys updated via setClientOptions for subsequent events', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + clientId, + sessionId: 1, + }); + + expect(instance.options.additionalTrackingKeys).to.be.undefined; + + instance.setClientOptions({ + additionalTrackingKeys: ['extra-key-1', 'extra-key-2'], + }); + + expect(instance.options).to.have.property('additionalTrackingKeys').to.deep.equal(['extra-key-1', 'extra-key-2']); + }); + it('Should update the options for modules with new session id in a DOM-less context', () => { const oldSessionId = 1; const newSessionId = 2; diff --git a/src/constructorio.js b/src/constructorio.js index 483b6f4e..5e215f82 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -182,6 +182,7 @@ class ConstructorIO { * @param {string} [options.userId] - User ID * @param {boolean} [options.sendTrackingEvents] - Indicates if tracking events should be dispatched * @param {string} [options.serviceUrl] - API URL endpoint (normalized to include an HTTPS protocol and strip a trailing slash) + * @param {string[]} [options.additionalTrackingKeys] - Additional API keys that each receive a duplicate of every tracking event. Events are always sent to the primary `apiKey` and, in addition, to every key in this array. */ setClientOptions(options) { if (Object.keys(options).length) { From 0892bb179ea9f052ce47df0bef929e0fd2b90eaf Mon Sep 17 00:00:00 2001 From: Evan Yan Date: Fri, 17 Jul 2026 14:30:34 +0800 Subject: [PATCH 12/12] Update test name --- spec/src/constructorio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index 2fa55e91..450ac0d5 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -918,7 +918,7 @@ if (!bundled) { expect(instance.options).to.have.property('sessionId').to.deep.equal(newSessionId); }); - it('Should use additionalTrackingKeys updated via setClientOptions for subsequent events', () => { + it('Should use additionalTrackingKeys updated via setClientOptions in a DOM-less context', () => { const instance = new ConstructorIO({ apiKey: validApiKey, clientId,