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