Skip to content

[CDX-470] Add additionalTrackingKeys option - #472

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

[CDX-470] Add additionalTrackingKeys option#472
t3t3c wants to merge 17 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[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.

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. 🤔

@stanlp1
stanlp1 requested a review from esezen August 14, 2026 06:56

@Mudaafi Mudaafi 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.

Looks good, just a couple of comments

Comment thread spec/src/constructorio.js Outdated

// 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
Contributor

Choose a reason for hiding this comment

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

Suggested change
const expectSharedOption = (instance, option, expected) => {
const expectSharedOption = (instance, key, expectedValue) => {

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.

Good refactor otherwise

Comment thread spec/src/modules/tracker.js
- Rename expectSharedOption params for clarity (option -> key, expected -> expectedValue)
- Add guard in tracker tests to fail if fetch is called more than expected

@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, with solid input sanitization and good test coverage overall.

Inline comments: 6 discussions added

Overall Assessment: ⚠️ Needs Work

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, key, expectedValue) => {

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: The new expectSharedOption helper always uses .deep.equal, which silently changed the assertion semantics for the refactored tests that previously used .equal (strict reference equality) for scalar options such as apiKey, userId, sessionId, and serviceUrl. For primitives, .deep.equal and .equal behave identically, so there is no functional regression. However, the serviceUrl test (line ~698) previously only asserted on the sub-module options (not instance.options) before setClientOptions, and expectSharedOption now also asserts instance.options. Verify that instance.options.serviceUrl is intentionally set to 'https://ac.cnstrc.com' at construction time before asserting — if it is, the change is fine. If not, the test now passes for an unintended reason.

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.

Suggestion: Both integration tests in the additionalTrackingKeys describe block use a manual callCount counter with a checkComplete callback to detect the 2nd call. Since done(new Error(...)) is called when callCount > 2 but the test doesn't explicitly stop further success/error events from firing, the done callback could theoretically be called multiple times if the event emitter fires more events, which Mocha treats as an error. Consider using a stub or once listener instead:

tracker.once('success', () => {
  // first call — no assertions yet
  tracker.once('success', () => {
    expect(fetchSpy).to.have.been.calledTwice;
    // ...assertions...
    done();
  });
});

Alternatively, wrapping in Promise.all with two sinon stubs would be cleaner and remove the timing fragility.

@Mudaafi Mudaafi 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 changes

@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.

LGTM! 🚀

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.

6 participants