Skip to content

Commit

Permalink
Add the idService option to the tracker configuration (close #1185)
Browse files Browse the repository at this point in the history
  • Loading branch information
igneel64 committed May 16, 2023
1 parent 748a7cc commit 2cadf0b
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/browser-tracker-core",
"comment": "Add idService option",
"type": "none"
}
],
"packageName": "@snowplow/browser-tracker-core"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/browser-tracker",
"comment": "Add idService option",
"type": "none"
}
],
"packageName": "@snowplow/browser-tracker"
}
3 changes: 2 additions & 1 deletion libraries/browser-tracker-core/src/tracker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,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,
Expand Down
24 changes: 20 additions & 4 deletions libraries/browser-tracker-core/src/tracker/out_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -81,7 +82,8 @@ export function OutQueueManager(
customHeaders: Record<string, string>,
withCredentials: boolean,
retryStatusCodes: number[],
dontRetryStatusCodes: number[]
dontRetryStatusCodes: number[],
idService?: string
): OutQueue {
type PostEvent = {
evt: Record<string, unknown>;
Expand All @@ -90,7 +92,8 @@ export function OutQueueManager(

let executingQueue = false,
configCollectorUrl: string,
outQueue: Array<PostEvent> | Array<string> = [];
outQueue: Array<PostEvent> | Array<string> = [],
idServiceCalled = false;

//Force to lower case if its a string
eventMethod = typeof eventMethod === 'string' ? eventMethod.toLowerCase() : eventMethod;
Expand Down Expand Up @@ -222,7 +225,7 @@ export function OutQueueManager(
}

const postable = (queue: Array<PostEvent> | Array<string>): queue is Array<PostEvent> => {
return typeof queue[0] === 'object';
return typeof queue[0] === 'object' && 'evt' in queue[0];
};

/**
Expand Down Expand Up @@ -293,7 +296,7 @@ export function OutQueueManager(
outQueue.shift();
}

if (outQueue.length < 1) {
if (!outQueue.length) {
executingQueue = false;
return;
}
Expand All @@ -305,6 +308,19 @@ export function OutQueueManager(

executingQueue = true;

if (idService && !idServiceCalled) {
const xhr = initializeXMLHttpRequest(idService, false, sync);
idServiceCalled = true;
xhr.timeout = connectionTimeout;
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
executeQueue();
}
};
return;
}

if (useXhr) {
// Keep track of number of events to delete from queue
const chooseHowManyToSend = (queue: Array<{ bytes: number }>) => {
Expand Down
7 changes: 7 additions & 0 deletions libraries/browser-tracker-core/src/tracker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,13 @@ export type TrackerConfiguration = {
* By default, the tracker retries on all non-success status codes except for 400, 401, 403, 410, and 422.
*/
dontRetryStatusCodes?: number[];
/**
* 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;
};

/**
Expand Down
121 changes: 121 additions & 0 deletions libraries/browser-tracker-core/test/out_queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
});
1 change: 1 addition & 0 deletions trackers/browser-tracker/docs/browser-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ export type TrackerConfiguration = {
customHeaders?: Record<string, string>;
retryStatusCodes?: number[];
dontRetryStatusCodes?: number[];
idService?: string;
};

// @public
Expand Down

0 comments on commit 2cadf0b

Please sign in to comment.