From 0421bb650db8f101821ff0ef33f3ea428b56a238 Mon Sep 17 00:00:00 2001 From: Peter Perlepes Date: Fri, 25 Aug 2023 11:55:31 +0300 Subject: [PATCH] Add the idService option to the tracker configuration (close #1185) #1186 --- ...85-id-service-option_2023-05-11-20-22.json | 10 ++ ...85-id-service-option_2023-05-12-07-26.json | 10 ++ .../browser-tracker-core/src/tracker/index.ts | 3 +- .../src/tracker/out_queue.ts | 24 +++- .../browser-tracker-core/src/tracker/types.ts | 7 + .../test/out_queue.test.ts | 121 ++++++++++++++++++ .../docs/browser-tracker.api.md | 1 + 7 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 common/changes/@snowplow/browser-tracker-core/feature-1185-id-service-option_2023-05-11-20-22.json create mode 100644 common/changes/@snowplow/browser-tracker/feature-1185-id-service-option_2023-05-12-07-26.json diff --git a/common/changes/@snowplow/browser-tracker-core/feature-1185-id-service-option_2023-05-11-20-22.json b/common/changes/@snowplow/browser-tracker-core/feature-1185-id-service-option_2023-05-11-20-22.json new file mode 100644 index 000000000..de331a7c7 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker-core/feature-1185-id-service-option_2023-05-11-20-22.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker-core", + "comment": "Add idService option", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker-core" +} \ No newline at end of file diff --git a/common/changes/@snowplow/browser-tracker/feature-1185-id-service-option_2023-05-12-07-26.json b/common/changes/@snowplow/browser-tracker/feature-1185-id-service-option_2023-05-12-07-26.json new file mode 100644 index 000000000..934020399 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker/feature-1185-id-service-option_2023-05-12-07-26.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker", + "comment": "Add idService option", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker" +} \ No newline at end of file diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index 8eb055dd4..a6443a932 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -302,7 +302,8 @@ export function Tracker( trackerConfiguration.customHeaders ?? {}, trackerConfiguration.withCredentials ?? true, trackerConfiguration.retryStatusCodes ?? [], - (trackerConfiguration.dontRetryStatusCodes ?? []).concat([400, 401, 403, 410, 422]) + (trackerConfiguration.dontRetryStatusCodes ?? []).concat([400, 401, 403, 410, 422]), + trackerConfiguration.idService ), // Whether pageViewId should be regenerated after each trackPageView. Affect web_page context preservePageViewId = false, diff --git a/libraries/browser-tracker-core/src/tracker/out_queue.ts b/libraries/browser-tracker-core/src/tracker/out_queue.ts index b838ba436..92e3ad968 100644 --- a/libraries/browser-tracker-core/src/tracker/out_queue.ts +++ b/libraries/browser-tracker-core/src/tracker/out_queue.ts @@ -63,6 +63,7 @@ export interface OutQueue { * @param withCredentials - Sets the value of the withCredentials flag on XMLHttpRequest (GET and POST) requests * @param retryStatusCodes – Failure HTTP response status codes from Collector for which sending events should be retried (they can override the `dontRetryStatusCodes`) * @param dontRetryStatusCodes – Failure HTTP response status codes from Collector for which sending events should not be retried + * @param idService - Id service full URL. This URL will be added to the queue and will be called using a GET method. * @returns object OutQueueManager instance */ export function OutQueueManager( @@ -81,7 +82,8 @@ export function OutQueueManager( customHeaders: Record, withCredentials: boolean, retryStatusCodes: number[], - dontRetryStatusCodes: number[] + dontRetryStatusCodes: number[], + idService?: string ): OutQueue { type PostEvent = { evt: Record; @@ -90,7 +92,8 @@ export function OutQueueManager( let executingQueue = false, configCollectorUrl: string, - outQueue: Array | Array = []; + outQueue: Array | Array = [], + idServiceCalled = false; //Force to lower case if its a string eventMethod = typeof eventMethod === 'string' ? eventMethod.toLowerCase() : eventMethod; @@ -222,7 +225,7 @@ export function OutQueueManager( } const postable = (queue: Array | Array): queue is Array => { - return typeof queue[0] === 'object'; + return typeof queue[0] === 'object' && 'evt' in queue[0]; }; /** @@ -293,7 +296,7 @@ export function OutQueueManager( outQueue.shift(); } - if (outQueue.length < 1) { + if (!outQueue.length) { executingQueue = false; return; } @@ -305,6 +308,19 @@ export function OutQueueManager( executingQueue = true; + if (idService && !idServiceCalled) { + const xhr = initializeXMLHttpRequest(idService, false, sync); + idServiceCalled = true; + xhr.timeout = connectionTimeout; + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + executeQueue(); + } + }; + xhr.send(); + return; + } + if (useXhr) { // Keep track of number of events to delete from queue const chooseHowManyToSend = (queue: Array<{ bytes: number }>) => { diff --git a/libraries/browser-tracker-core/src/tracker/types.ts b/libraries/browser-tracker-core/src/tracker/types.ts index f986e2a2d..0f5801c9d 100755 --- a/libraries/browser-tracker-core/src/tracker/types.ts +++ b/libraries/browser-tracker-core/src/tracker/types.ts @@ -242,6 +242,13 @@ export type TrackerConfiguration = { * @param updatedSession - On session update, the new session information plus the previous session id. */ onSessionUpdateCallback?: (updatedSession: ClientSession) => void; + /** + * Id 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. + */ + idService?: string; }; /** diff --git a/libraries/browser-tracker-core/test/out_queue.test.ts b/libraries/browser-tracker-core/test/out_queue.test.ts index c51b71194..bab53486b 100644 --- a/libraries/browser-tracker-core/test/out_queue.test.ts +++ b/libraries/browser-tracker-core/test/out_queue.test.ts @@ -216,4 +216,125 @@ describe('OutQueueManager', () => { expect(xhrOpenMock).toHaveBeenCalledWith('POST', 'http://acme.com/com.snowplowanalytics.snowplow/tp2', true); // should make the POST request }); }); + + describe('idService requests', () => { + const idServiceEndpoint = 'http://example.com/id'; + const readPostQueue = () => { + return JSON.parse( + window.localStorage.getItem('snowplowOutQueue_sp_post2') ?? fail('Unable to find local storage queue') + ); + }; + + const readGetQueue = () => + JSON.parse(window.localStorage.getItem('snowplowOutQueue_sp_get') ?? fail('Unable to find local storage queue')); + + const getQuerystring = (p: object) => + '?' + + Object.entries(p) + .map(([k, v]) => k + '=' + encodeURIComponent(v)) + .join('&'); + + describe('GET requests', () => { + const createGetQueue = () => + OutQueueManager( + 'sp', + new SharedState(), + true, + 'get', + '/com.snowplowanalytics.snowplow/tp2', + 1, + 40000, + 0, + false, + maxQueueSize, + 5000, + false, + {}, + true, + [], + [], + idServiceEndpoint + ); + + it('should first execute the idService request and in the same `enqueueRequest` the tracking request', () => { + const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' }; + const getQueue = createGetQueue(); + + getQueue.enqueueRequest(request, 'http://example.com'); + + let retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(1); + /* The first XHR is for the idService */ + respondMockRequest(200); + retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(1); + expect(retrievedQueue[0]).toEqual(getQuerystring(request)); + /* The second XHR is the event request */ + respondMockRequest(200); + retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(0); + }); + + it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', () => { + const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' }; + const getQueue = createGetQueue(); + + getQueue.enqueueRequest(request, 'http://example.com'); + + let retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(1); + /* The first XHR is for the idService */ + respondMockRequest(500); + retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(1); + expect(retrievedQueue[0]).toEqual(getQuerystring(request)); + /* The second XHR is the event request */ + respondMockRequest(200); + retrievedQueue = readGetQueue(); + expect(retrievedQueue).toHaveLength(0); + }); + }); + + describe('POST requests', () => { + const createPostQueue = () => + OutQueueManager( + 'sp', + new SharedState(), + true, + 'post', + '/com.snowplowanalytics.snowplow/tp2', + 1, + 40000, + 0, + false, + maxQueueSize, + 5000, + false, + {}, + true, + [], + [], + idServiceEndpoint + ); + + it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', () => { + const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' }; + const postQueue = createPostQueue(); + + postQueue.enqueueRequest(request, 'http://example.com'); + + let retrievedQueue = readPostQueue(); + expect(retrievedQueue).toHaveLength(1); + /* The first XHR is for the idService */ + respondMockRequest(500); + retrievedQueue = readPostQueue(); + expect(retrievedQueue).toHaveLength(1); + expect(retrievedQueue[0].evt).toEqual(request); + /* The second XHR is the event request */ + respondMockRequest(200); + retrievedQueue = readPostQueue(); + expect(retrievedQueue).toHaveLength(0); + }); + }); + }); }); diff --git a/trackers/browser-tracker/docs/browser-tracker.api.md b/trackers/browser-tracker/docs/browser-tracker.api.md index 1fedc9d25..70aabae95 100644 --- a/trackers/browser-tracker/docs/browser-tracker.api.md +++ b/trackers/browser-tracker/docs/browser-tracker.api.md @@ -368,6 +368,7 @@ export type TrackerConfiguration = { retryStatusCodes?: number[]; dontRetryStatusCodes?: number[]; onSessionUpdateCallback?: (updatedSession: ClientSession) => void; + idService?: string; }; // @public