Skip to content

[CDX-470] Add additionalTrackingKeys option#472

Open
t3t3c wants to merge 15 commits into
masterfrom
cdx-470-add-additionaltrackingkeys-option-to-client-js
Open

[CDX-470] Add additionalTrackingKeys option#472
t3t3c wants to merge 15 commits into
masterfrom
cdx-470-add-additionaltrackingkeys-option-to-client-js

Conversation

@t3t3c

@t3t3c t3t3c commented Jun 22, 2026

Copy link
Copy Markdown

Summary

  • Adds additionalTrackingKeys option to ConstructorClientOptions that accepts an array of API key strings
  • When configured, every tracking event queued via RequestQueue is duplicated for each additional key, with the key swapped in both the URL query string and POST body
  • Invalid entries (non-strings, empty strings) are filtered out silently

Usage

const cio = new ConstructorIO({
  apiKey: 'primary-key',
  sendTrackingEvents: true,
  additionalTrackingKeys: ['secondary-key', 'tertiary-key'],
});

// All tracker methods now send events to all 3 keys
cio.tracker.trackPurchase({ items: [...], revenue: 12.00 });

Changes

  • src/types/index.d.ts — Added additionalTrackingKeys?: string[] to options interface
  • src/constructorio.js — Pass through the new option
  • src/utils/request-queue.js — Duplicate queued requests for each additional key
  • spec/src/utils/request-queue.js — Unit tests (5 cases)
  • spec/src/modules/tracker.js — Integration test

Test plan

  • Unit tests: duplication works, empty array, missing option, invalid entries, GET requests
  • Integration test: fetch called with both primary and additional key URLs
  • Existing test suite: no regressions
  • Lint passes

Resolves CDX-470

…vents

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.
Copilot AI review requested due to automatic review settings June 22, 2026 10:28
@t3t3c
t3t3c requested a review from a team as a code owner June 22, 2026 10:28
constructor-claude-bedrock[bot]

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an additionalTrackingKeys client option to support duplicating tracking events across multiple Constructor.io API keys, primarily by cloning queued RequestQueue entries with the key swapped in the URL/body.

Changes:

  • Introduces additionalTrackingKeys?: string[] on ConstructorClientOptions and wires it through ConstructorIO options.
  • Updates RequestQueue to enqueue duplicate tracking requests per additional key.
  • Adds unit + integration tests validating duplication behavior and invalid-key filtering.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/utils/request-queue.js Duplicates queued tracking requests for each configured additional key
src/types/index.d.ts Adds additionalTrackingKeys?: string[] to the public options type
src/constructorio.js Plumbs additionalTrackingKeys through normalized client options
spec/src/utils/request-queue.js Unit tests for duplication, empty/missing option, invalid entries, GET behavior
spec/src/modules/tracker.js Integration test asserting fetch is called for both primary + additional keys

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/request-queue.js Outdated
Comment thread src/constructorio.js
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
constructor-claude-bedrock[bot]

This comment was marked as outdated.

constructor-claude-bedrock[bot]

This comment was marked as outdated.

@esezen esezen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is looking great. I left some comments

Comment thread src/utils/request-queue.js Outdated
Comment thread spec/src/modules/tracker.js
Comment thread spec/src/modules/tracker.js
Comment thread src/constructorio.js Outdated
t3t3c and others added 2 commits July 3, 2026 12:46
- 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 <noreply@anthropic.com>
…-client-js

Resolve conflicts: keep both useWindowParameters (from master) and
additionalTrackingKeys (from this branch) in constructorio.js and
index.d.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
constructor-claude-bedrock[bot]

This comment was marked as outdated.

@evanyan13 evanyan13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @t3t3c
Thank you so much for making the changes! The PR is looking great in general. 🔥
Just left some comments for your consideration please

Comment thread src/constructorio.js
Comment thread src/utils/request-queue.js Outdated
Comment thread spec/src/utils/request-queue.js
Comment thread spec/src/utils/request-queue.js Outdated
Comment thread spec/src/modules/tracker.js
constructor-claude-bedrock[bot]

This comment was marked as outdated.

@evanyan13 evanyan13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @t3t3c
Thanks for making the changes so promptly!
Have left some comment regarding the new update to setClientOptions
For your consideration please

Comment thread src/constructorio.js
Comment thread src/constructorio.js Outdated
constructor-claude-bedrock[bot]

This comment was marked as outdated.

evanyan13
evanyan13 previously approved these changes Jul 17, 2026

@evanyan13 evanyan13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
Thanks @t3t3c for the PR and the changes
@jjl014 Could we get your review & approval to push this change please
Thank you!

constructor-claude-bedrock[bot]

This comment was marked as outdated.

@jjl014 jjl014 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty solid to me. I did leave a couple comments that I'd like to get your thoughts on. Thanks!

Comment on lines +61 to +69
const encodedOriginalKey = helpers.encodeURIComponentRFC3986(this.options.apiKey);

const uniqueKeys = new Set(
additionalKeys.filter((key) => key && typeof key === 'string' && key !== this.options.apiKey),
);

uniqueKeys.forEach((additionalKey) => {
const encodedAdditionalKey = helpers.encodeURIComponentRFC3986(additionalKey);
const swappedUrl = url.replace(`key=${encodedOriginalKey}`, `key=${encodedAdditionalKey}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we're doing this validation and uniqueness check every time we queue an event?

It feels like an additional step that could be handled earlier (e.g. during client instantiation in the construtor or when setClientOptions is called)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jjl014 This is a good consideration!
We can definitely handle the sanitization check earlier in the system and save the time to conduct the check every time we queue an event 👍

Comment thread spec/src/constructorio.js Outdated
Comment thread src/constructorio.js Outdated
this.options.serviceUrl = formattedServiceUrl || this.options.serviceUrl;
}

if (Array.isArray(additionalTrackingKeys)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal, but if we wanted to stop sending events to multiple keys, we would have to send [] at the moment. Should also support null here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think this is a good point!
We should allow users to stop sending events to additional keys by passing null to additionalTrackingKeys , instead of forcing them to send an empty array []

constructor-claude-bedrock[bot]

This comment was marked as outdated.

@evanyan13

evanyan13 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Hi @jjl014
Thank you for the feedback! @t3t3c I have updated the PR based on Jimmy's review.
For your review and approval please.
This is a summary of the changes from my end:

  • Moved sanitization logic to higher level (at ConstructorIO.init and setClientOptions) with helpers.toValidAdditionalTrackingKeys
  • Support empty array and null on additionalTrackingKeys param as an action to stop sending events to multiple keys
  • setClientOptions re-validates on apiKey change: updating the primary key re-runs validation so a now-duplicate additional key is dropped.
  • Tests restructured: normalization/clear/re-validation coverage moved up to the ConstructorIO level. Refactor the assertion with expectSharedOption helper (asserts the value propagates across all sub-modules), and the request-queue spec keeps only pre-normalized duplication cases.
    cc - @t3t3c

@constructor-claude-bedrock constructor-claude-bedrock Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR adds an additionalTrackingKeys option that duplicates every tracking event to multiple API keys — a well-scoped feature with solid test coverage and clean sanitization logic.

Inline comments: 7 discussions added

Overall Assessment: ⚠️ Needs Work

Comment thread src/utils/helpers.js
return null;
}

const uniqueKeys = [...new Set(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: [...new Set(array)] de-duplicates after filtering. This is fine, but it only removes exact-string duplicates. Keys that differ only in casing (e.g., 'Key-A' vs 'key-a') will both pass through. API keys are normally case-sensitive so this is likely intentional — a short comment would make that explicit and prevent future "bug" reports.

additionalTrackingKeys: [additionalKey],
});

let callCount = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important Issue: Both tracker integration tests use a manual callCount pattern combined with a shared fetchSpy — but neither test resets fetchSpy between the two tests. Because fetchSpy is presumably defined at a higher describe scope (not shown in the diff), if any earlier test in the suite called fetch, fetchSpy.getCall(0) may not point to the call made by this test. Also, the assertion expect(fetchSpy).to.have.been.calledTwice is global state — if the spy is not reset in a beforeEach, the assertion will fail if more than two prior calls exist.

Ensure fetchSpy.resetHistory() (or fetchSpy.reset() depending on sinon version) is called in a beforeEach within the additionalTrackingKeys describe block, or use a locally-scoped spy.

Comment thread spec/src/constructorio.js

// Assert an option on the client and every sub-module that shares its `options` reference.
// Uses `deep.equal` to enforce strict structural equality for objects and arrays.
const expectSharedOption = (instance, option, expected) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: expectSharedOption uses .to.deep.equal for all options, but the original tests used .to.equal (strict reference equality) for primitive options like apiKey, userId, sessionId, and .to.deep.equal only for objects like testCells. The new helper applies deep.equal uniformly, which is slightly less precise for primitives (it won't catch the case where an option is accidentally cloned to a different reference for objects). For this codebase, where all modules share the same options reference, this distinction doesn't matter in practice — but it's worth a note since it silently changes assertion semantics for the refactored tests.

@jjl014 jjl014 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the updates! The changes are looking good from what I can tell, but I noticed that some of the newly added tests and a variety of tests from other parts of the spec are failing.

I'm guessing the ones failing on master might be expected. I think @Mudaafi mentioned he was working on fixing them. However, I wouldn't expect the tests added from this PR to fail as well. 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants