From 9a4aebcec9403a51b6ac8fed4059c2180decd666 Mon Sep 17 00:00:00 2001
From: igneel64
Date: Fri, 18 Apr 2025 09:52:18 +0300
Subject: [PATCH] Add cookieExtensionService argument as replacement for
idService, deprecate idService (close #1425)
---
.../browser-tracker/browser-tracker.api.md | 2 +
.../docs/node-tracker/node-tracker.api.md | 2 +
.../react-native-tracker.api.md | 2 +
...06-rename-id-service_2025-04-18-06-53.json | 10 ++
...06-rename-id-service_2025-04-18-06-53.json | 10 ++
.../test/out_queue.test.ts | 147 +++++++++---------
libraries/tracker-core/src/emitter/index.ts | 22 ++-
.../tracker-core/test/emitter/index.test.ts | 10 +-
8 files changed, 121 insertions(+), 84 deletions(-)
create mode 100644 common/changes/@snowplow/browser-tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
create mode 100644 common/changes/@snowplow/tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
diff --git a/api-docs/docs/browser-tracker/browser-tracker.api.md b/api-docs/docs/browser-tracker/browser-tracker.api.md
index 022373eb4..c69366c54 100644
--- a/api-docs/docs/browser-tracker/browser-tracker.api.md
+++ b/api-docs/docs/browser-tracker/browser-tracker.api.md
@@ -219,12 +219,14 @@ export function discardHashTag(enable: boolean, trackers?: Array): void;
export interface EmitterConfigurationBase {
bufferSize?: number;
connectionTimeout?: number;
+ cookieExtensionService?: string;
credentials?: "omit" | "same-origin" | "include";
customFetch?: (input: Request, options?: RequestInit) => Promise;
customHeaders?: Record;
dontRetryStatusCodes?: number[];
eventMethod?: EventMethod;
eventStore?: EventStore;
+ // @deprecated (undocumented)
idService?: string;
keepalive?: boolean;
maxGetBytes?: number;
diff --git a/api-docs/docs/node-tracker/node-tracker.api.md b/api-docs/docs/node-tracker/node-tracker.api.md
index 5a9b6fd16..40280aaea 100644
--- a/api-docs/docs/node-tracker/node-tracker.api.md
+++ b/api-docs/docs/node-tracker/node-tracker.api.md
@@ -244,12 +244,14 @@ export interface EmitterConfiguration extends EmitterConfigurationBase {
export interface EmitterConfigurationBase {
bufferSize?: number;
connectionTimeout?: number;
+ cookieExtensionService?: string;
credentials?: "omit" | "same-origin" | "include";
customFetch?: (input: Request, options?: RequestInit) => Promise;
customHeaders?: Record;
dontRetryStatusCodes?: number[];
eventMethod?: EventMethod;
eventStore?: EventStore;
+ // @deprecated (undocumented)
idService?: string;
keepalive?: boolean;
maxGetBytes?: number;
diff --git a/api-docs/docs/react-native-tracker/react-native-tracker.api.md b/api-docs/docs/react-native-tracker/react-native-tracker.api.md
index 305ca7f86..7c9b0e843 100644
--- a/api-docs/docs/react-native-tracker/react-native-tracker.api.md
+++ b/api-docs/docs/react-native-tracker/react-native-tracker.api.md
@@ -120,6 +120,7 @@ export interface EmitterConfiguration extends EmitterConfigurationBase {
export interface EmitterConfigurationBase {
bufferSize?: number;
connectionTimeout?: number;
+ cookieExtensionService?: string;
credentials?: "omit" | "same-origin" | "include";
customFetch?: (input: Request, options?: RequestInit) => Promise;
customHeaders?: Record;
@@ -127,6 +128,7 @@ export interface EmitterConfigurationBase {
eventMethod?: EventMethod;
// Warning: (ae-forgotten-export) The symbol "EventStore" needs to be exported by the entry point index.d.ts
eventStore?: EventStore;
+ // @deprecated (undocumented)
idService?: string;
keepalive?: boolean;
maxGetBytes?: number;
diff --git a/common/changes/@snowplow/browser-tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json b/common/changes/@snowplow/browser-tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
new file mode 100644
index 000000000..f9ea830e2
--- /dev/null
+++ b/common/changes/@snowplow/browser-tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@snowplow/browser-tracker-core",
+ "comment": "Add `cookieExtensionService` argument as replacement for `idService`, deprecated `idService`",
+ "type": "none"
+ }
+ ],
+ "packageName": "@snowplow/browser-tracker-core"
+}
\ No newline at end of file
diff --git a/common/changes/@snowplow/tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json b/common/changes/@snowplow/tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
new file mode 100644
index 000000000..c269aabb4
--- /dev/null
+++ b/common/changes/@snowplow/tracker-core/feature-AISP-306-rename-id-service_2025-04-18-06-53.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@snowplow/tracker-core",
+ "comment": "Add `cookieExtensionService` argument as replacement for `idService`, deprecate `idService`",
+ "type": "none"
+ }
+ ],
+ "packageName": "@snowplow/tracker-core"
+}
\ No newline at end of file
diff --git a/libraries/browser-tracker-core/test/out_queue.test.ts b/libraries/browser-tracker-core/test/out_queue.test.ts
index aae8e4667..dfc2081ae 100644
--- a/libraries/browser-tracker-core/test/out_queue.test.ts
+++ b/libraries/browser-tracker-core/test/out_queue.test.ts
@@ -193,8 +193,8 @@ describe('OutQueueManager', () => {
});
});
- describe('idService requests', () => {
- const idServiceEndpoint = 'http://example.com/id';
+ describe('cookieExtensionService requests', () => {
+ const cookieExtensionServiceEndpoint = 'http://example.com/id';
const getQuerystring = (p: object) =>
'?' +
@@ -203,33 +203,33 @@ describe('OutQueueManager', () => {
.join('&');
describe('GET requests', () => {
- let createGetQueue = () => newOutQueue(
- {
- endpoint: 'http://example.com',
- trackerId: 'sp',
- eventMethod: 'get',
- useStm: false,
- maxLocalStorageQueueSize: maxQueueSize,
- eventStore,
- customFetch,
- idService: idServiceEndpoint,
- },
- new SharedState()
- );
-
+ let createGetQueue = () =>
+ newOutQueue(
+ {
+ endpoint: 'http://example.com',
+ trackerId: 'sp',
+ eventMethod: 'get',
+ useStm: false,
+ maxLocalStorageQueueSize: maxQueueSize,
+ eventStore,
+ customFetch,
+ cookieExtensionService: cookieExtensionServiceEndpoint,
+ },
+ new SharedState()
+ );
- it('should first execute the idService request and in the same `enqueueRequest` the tracking request', async () => {
+ it('should first execute the cookieExtensionService request and in the same `enqueueRequest` the tracking request', async () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
const getQueue = createGetQueue();
await getQueue.enqueueRequest(request);
expect(requests).toHaveLength(2);
- expect(requests[0].url).toEqual(idServiceEndpoint);
+ expect(requests[0].url).toEqual(cookieExtensionServiceEndpoint);
expect(requests[1].url).toEqual('http://example.com/i' + getQuerystring(request));
});
- it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', async () => {
+ it('should first execute the cookieExtensionService request and in the same `enqueueRequest` the tracking request irregardless of failure of the cookieExtensionService endpoint', async () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
const getQueue = createGetQueue();
@@ -237,33 +237,34 @@ describe('OutQueueManager', () => {
await getQueue.enqueueRequest(request);
expect(requests).toHaveLength(2);
- expect(requests[0].url).toEqual(idServiceEndpoint);
+ expect(requests[0].url).toEqual(cookieExtensionServiceEndpoint);
expect(requests[1].url).toEqual('http://example.com/i' + getQuerystring(request));
expect(await eventStore.count()).toEqual(1);
});
});
describe('POST requests', () => {
- let createPostQueue = () => newOutQueue(
- {
- endpoint: 'http://example.com',
- trackerId: 'sp',
- eventMethod: 'post',
- eventStore,
- customFetch,
- idService: idServiceEndpoint,
- },
- new SharedState()
- );
+ let createPostQueue = () =>
+ newOutQueue(
+ {
+ endpoint: 'http://example.com',
+ trackerId: 'sp',
+ eventMethod: 'post',
+ eventStore,
+ customFetch,
+ cookieExtensionService: cookieExtensionServiceEndpoint,
+ },
+ new SharedState()
+ );
- it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', async () => {
+ it('should first execute the cookieExtensionService request and in the same `enqueueRequest` the tracking request irregardless of failure of the cookieExtensionService endpoint', async () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
const postQueue = createPostQueue();
await postQueue.enqueueRequest(request);
expect(requests).toHaveLength(2);
- expect(requests[0].url).toEqual(idServiceEndpoint);
+ expect(requests[0].url).toEqual(cookieExtensionServiceEndpoint);
expect(requests[1].url).toEqual('http://example.com/com.snowplowanalytics.snowplow/tp2');
});
});
@@ -271,17 +272,18 @@ describe('OutQueueManager', () => {
describe('retryFailures = true', () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
- let createOutQueue = () => newOutQueue(
- {
- endpoint: 'http://example.com',
- trackerId: 'sp',
- eventMethod: 'post',
- retryFailedRequests: true,
- eventStore,
- customFetch,
- },
- new SharedState()
- );
+ let createOutQueue = () =>
+ newOutQueue(
+ {
+ endpoint: 'http://example.com',
+ trackerId: 'sp',
+ eventMethod: 'post',
+ retryFailedRequests: true,
+ eventStore,
+ customFetch,
+ },
+ new SharedState()
+ );
it('should remain in queue on failure', async () => {
let outQueue = createOutQueue();
@@ -295,17 +297,18 @@ describe('OutQueueManager', () => {
describe('retryFailures = false', () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
- let createOutQueue = () => newOutQueue(
- {
- endpoint: 'http://example.com',
- trackerId: 'sp',
- eventMethod: 'post',
- retryFailedRequests: false,
- eventStore,
- customFetch,
- },
- new SharedState()
- );
+ let createOutQueue = () =>
+ newOutQueue(
+ {
+ endpoint: 'http://example.com',
+ trackerId: 'sp',
+ eventMethod: 'post',
+ retryFailedRequests: false,
+ eventStore,
+ customFetch,
+ },
+ new SharedState()
+ );
it('should remove from queue on failure', async () => {
let outQueue = createOutQueue();
@@ -441,23 +444,23 @@ describe('OutQueueManager', () => {
describe('onRequestFailure', () => {
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
- const createQueue = (args: createQueueArgs) =>
- newOutQueue(
- {
- endpoint: 'http://example.com',
- trackerId: 'sp',
- eventMethod: args.method,
- maxPostBytes: args.maxPostBytes,
- maxGetBytes: args.maxGetBytes,
- maxLocalStorageQueueSize: maxQueueSize,
- onRequestSuccess: args.onSuccess,
- onRequestFailure: args.onFailure,
- dontRetryStatusCodes: [500],
- customFetch,
- eventStore,
- },
- new SharedState()
- );
+ const createQueue = (args: createQueueArgs) =>
+ newOutQueue(
+ {
+ endpoint: 'http://example.com',
+ trackerId: 'sp',
+ eventMethod: args.method,
+ maxPostBytes: args.maxPostBytes,
+ maxGetBytes: args.maxGetBytes,
+ maxLocalStorageQueueSize: maxQueueSize,
+ onRequestSuccess: args.onSuccess,
+ onRequestFailure: args.onFailure,
+ dontRetryStatusCodes: [500],
+ customFetch,
+ eventStore,
+ },
+ new SharedState()
+ );
describe('POST requests', () => {
const method = 'post';
diff --git a/libraries/tracker-core/src/emitter/index.ts b/libraries/tracker-core/src/emitter/index.ts
index 0b7bfc783..35e3649e8 100644
--- a/libraries/tracker-core/src/emitter/index.ts
+++ b/libraries/tracker-core/src/emitter/index.ts
@@ -107,11 +107,15 @@ export interface EmitterConfigurationBase {
*/
dontRetryStatusCodes?: number[];
/**
- * Id service full URL. This URL will be added to the queue and will be called using a GET method.
+ * Cookie extension service full URL. This URL will be added to the queue and will be called using a GET method.
* This option is there to allow the service URL to be called in order to set any required identifiers e.g. extra cookies.
*
* The request respects the `anonymousTracking` option, including the SP-Anonymous header if needed, and any additional custom headers from the customHeaders option.
*/
+ cookieExtensionService?: string;
+ /**
+ * @deprecated Use `cookieExtensionService` instead.
+ */
idService?: string;
/**
* Indicates that the request should be allowed to outlive the webpage that initiated it.
@@ -208,6 +212,7 @@ export function newEmitter({
serverAnonymization,
connectionTimeout,
keepalive,
+ cookieExtensionService,
idService,
dontRetryStatusCodes = [],
retryStatusCodes = [],
@@ -219,7 +224,10 @@ export function newEmitter({
eventStore = newInMemoryEventStore({}),
credentials,
}: EmitterConfiguration): Emitter {
- let idServiceCalled = false;
+ /* `cookieExtensionService` gets priority until `idService` is completely removed. */
+ cookieExtensionService = cookieExtensionService || idService;
+
+ let cookieExtensionServiceCalled = false;
let flushInProgress = false;
const usePost = eventMethod.toLowerCase() === 'post';
dontRetryStatusCodes = dontRetryStatusCodes.concat([400, 401, 403, 410, 422]);
@@ -349,10 +357,10 @@ export function newEmitter({
}
}
- async function callIdService() {
- if (idService && !idServiceCalled) {
- idServiceCalled = true;
- const request = new Request(idService, { method: 'GET' });
+ async function callCookieExtensionService() {
+ if (cookieExtensionService && !cookieExtensionServiceCalled) {
+ cookieExtensionServiceCalled = true;
+ const request = new Request(cookieExtensionService, { method: 'GET' });
await customFetch(request);
}
}
@@ -372,7 +380,7 @@ export function newEmitter({
}
async function continueFlush() {
- await callIdService();
+ await callCookieExtensionService();
const request = newEmitterRequestWithConfig();
const eventStoreIterator = eventStore.iterator();
diff --git a/libraries/tracker-core/test/emitter/index.test.ts b/libraries/tracker-core/test/emitter/index.test.ts
index 22c10ee23..0371224ef 100644
--- a/libraries/tracker-core/test/emitter/index.test.ts
+++ b/libraries/tracker-core/test/emitter/index.test.ts
@@ -1,7 +1,7 @@
import test from 'ava';
import { newEmitter, Emitter } from '../../src/emitter';
-import { newInMemoryEventStore } from "../../src/event_store";
+import { newInMemoryEventStore } from '../../src/event_store';
function createMockFetch(status: number, requests: Request[]) {
return async (input: Request) => {
@@ -15,7 +15,7 @@ test.before(() => {
console.error = () => {}; // Silence console.error globally
});
-test("input adds an event to the event store", async (t) => {
+test('input adds an event to the event store', async (t) => {
const requests: Request[] = [];
const mockFetch = createMockFetch(200, requests);
const eventStore = newInMemoryEventStore({});
@@ -26,7 +26,7 @@ test("input adds an event to the event store", async (t) => {
eventStore,
});
- await emitter.input({ e: "pv" });
+ await emitter.input({ e: 'pv' });
t.is(await eventStore.count(), 1);
t.is(requests.length, 0);
@@ -288,7 +288,7 @@ test('makes a request to the id service only once', async (t) => {
const emitter: Emitter = newEmitter({
endpoint: 'https://example.com',
customFetch: mockFetch,
- idService: 'https://id-example.com',
+ cookieExtensionService: 'https://id-example.com',
});
await emitter.input({ e: 'pv' });
@@ -325,7 +325,7 @@ test('adds a timeout to the request', async (t) => {
endpoint: 'https://example.com',
customFetch: mockFetch,
connectionTimeout: 100,
- eventStore
+ eventStore,
});
await emitter.input({ e: 'pv' });