Skip to content

Add cookieExtensionService argument as replacement for idService, deprecate idService #1426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: release/4.5.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api-docs/docs/browser-tracker/browser-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,14 @@ export function discardHashTag(enable: boolean, trackers?: Array<string>): void;
export interface EmitterConfigurationBase {
bufferSize?: number;
connectionTimeout?: number;
cookieExtensionService?: string;
credentials?: "omit" | "same-origin" | "include";
customFetch?: (input: Request, options?: RequestInit) => Promise<Response>;
customHeaders?: Record<string, string>;
dontRetryStatusCodes?: number[];
eventMethod?: EventMethod;
eventStore?: EventStore;
// @deprecated (undocumented)
idService?: string;
keepalive?: boolean;
maxGetBytes?: number;
Expand Down
2 changes: 2 additions & 0 deletions api-docs/docs/node-tracker/node-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response>;
customHeaders?: Record<string, string>;
dontRetryStatusCodes?: number[];
eventMethod?: EventMethod;
eventStore?: EventStore;
// @deprecated (undocumented)
idService?: string;
keepalive?: boolean;
maxGetBytes?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,15 @@ 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<Response>;
customHeaders?: Record<string, string>;
dontRetryStatusCodes?: number[];
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
147 changes: 75 additions & 72 deletions libraries/browser-tracker-core/test/out_queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
'?' +
Expand All @@ -203,85 +203,87 @@ 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();

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

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();
Expand All @@ -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();
Expand Down Expand Up @@ -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';
Expand Down
22 changes: 15 additions & 7 deletions libraries/tracker-core/src/emitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -208,6 +212,7 @@ export function newEmitter({
serverAnonymization,
connectionTimeout,
keepalive,
cookieExtensionService,
idService,
dontRetryStatusCodes = [],
retryStatusCodes = [],
Expand All @@ -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]);
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -372,7 +380,7 @@ export function newEmitter({
}

async function continueFlush() {
await callIdService();
await callCookieExtensionService();

const request = newEmitterRequestWithConfig();
const eventStoreIterator = eventStore.iterator();
Expand Down
10 changes: 5 additions & 5 deletions libraries/tracker-core/test/emitter/index.test.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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({});
Expand All @@ -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);
Expand Down Expand Up @@ -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' });
Expand Down Expand Up @@ -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' });
Expand Down
Loading