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' });