Skip to content

Commit 0421bb6

Browse files
committed
Add the idService option to the tracker configuration (close #1185)
#1186
1 parent 100309b commit 0421bb6

File tree

7 files changed

+171
-5
lines changed

7 files changed

+171
-5
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/browser-tracker-core",
5+
"comment": "Add idService option",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/browser-tracker-core"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/browser-tracker",
5+
"comment": "Add idService option",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/browser-tracker"
10+
}

libraries/browser-tracker-core/src/tracker/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ export function Tracker(
302302
trackerConfiguration.customHeaders ?? {},
303303
trackerConfiguration.withCredentials ?? true,
304304
trackerConfiguration.retryStatusCodes ?? [],
305-
(trackerConfiguration.dontRetryStatusCodes ?? []).concat([400, 401, 403, 410, 422])
305+
(trackerConfiguration.dontRetryStatusCodes ?? []).concat([400, 401, 403, 410, 422]),
306+
trackerConfiguration.idService
306307
),
307308
// Whether pageViewId should be regenerated after each trackPageView. Affect web_page context
308309
preservePageViewId = false,

libraries/browser-tracker-core/src/tracker/out_queue.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface OutQueue {
6363
* @param withCredentials - Sets the value of the withCredentials flag on XMLHttpRequest (GET and POST) requests
6464
* @param retryStatusCodes – Failure HTTP response status codes from Collector for which sending events should be retried (they can override the `dontRetryStatusCodes`)
6565
* @param dontRetryStatusCodes – Failure HTTP response status codes from Collector for which sending events should not be retried
66+
* @param idService - Id service full URL. This URL will be added to the queue and will be called using a GET method.
6667
* @returns object OutQueueManager instance
6768
*/
6869
export function OutQueueManager(
@@ -81,7 +82,8 @@ export function OutQueueManager(
8182
customHeaders: Record<string, string>,
8283
withCredentials: boolean,
8384
retryStatusCodes: number[],
84-
dontRetryStatusCodes: number[]
85+
dontRetryStatusCodes: number[],
86+
idService?: string
8587
): OutQueue {
8688
type PostEvent = {
8789
evt: Record<string, unknown>;
@@ -90,7 +92,8 @@ export function OutQueueManager(
9092

9193
let executingQueue = false,
9294
configCollectorUrl: string,
93-
outQueue: Array<PostEvent> | Array<string> = [];
95+
outQueue: Array<PostEvent> | Array<string> = [],
96+
idServiceCalled = false;
9497

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

224227
const postable = (queue: Array<PostEvent> | Array<string>): queue is Array<PostEvent> => {
225-
return typeof queue[0] === 'object';
228+
return typeof queue[0] === 'object' && 'evt' in queue[0];
226229
};
227230

228231
/**
@@ -293,7 +296,7 @@ export function OutQueueManager(
293296
outQueue.shift();
294297
}
295298

296-
if (outQueue.length < 1) {
299+
if (!outQueue.length) {
297300
executingQueue = false;
298301
return;
299302
}
@@ -305,6 +308,19 @@ export function OutQueueManager(
305308

306309
executingQueue = true;
307310

311+
if (idService && !idServiceCalled) {
312+
const xhr = initializeXMLHttpRequest(idService, false, sync);
313+
idServiceCalled = true;
314+
xhr.timeout = connectionTimeout;
315+
xhr.onreadystatechange = function () {
316+
if (xhr.readyState === 4) {
317+
executeQueue();
318+
}
319+
};
320+
xhr.send();
321+
return;
322+
}
323+
308324
if (useXhr) {
309325
// Keep track of number of events to delete from queue
310326
const chooseHowManyToSend = (queue: Array<{ bytes: number }>) => {

libraries/browser-tracker-core/src/tracker/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ export type TrackerConfiguration = {
242242
* @param updatedSession - On session update, the new session information plus the previous session id.
243243
*/
244244
onSessionUpdateCallback?: (updatedSession: ClientSession) => void;
245+
/**
246+
* Id service full URL. This URL will be added to the queue and will be called using a GET method.
247+
* This option is there to allow the service URL to be called in order to set any required identifiers e.g. extra cookies.
248+
*
249+
* The request respects the `anonymousTracking` option, including the SP-Anonymous header if needed, and any additional custom headers from the customHeaders option.
250+
*/
251+
idService?: string;
245252
};
246253

247254
/**

libraries/browser-tracker-core/test/out_queue.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,125 @@ describe('OutQueueManager', () => {
216216
expect(xhrOpenMock).toHaveBeenCalledWith('POST', 'http://acme.com/com.snowplowanalytics.snowplow/tp2', true); // should make the POST request
217217
});
218218
});
219+
220+
describe('idService requests', () => {
221+
const idServiceEndpoint = 'http://example.com/id';
222+
const readPostQueue = () => {
223+
return JSON.parse(
224+
window.localStorage.getItem('snowplowOutQueue_sp_post2') ?? fail('Unable to find local storage queue')
225+
);
226+
};
227+
228+
const readGetQueue = () =>
229+
JSON.parse(window.localStorage.getItem('snowplowOutQueue_sp_get') ?? fail('Unable to find local storage queue'));
230+
231+
const getQuerystring = (p: object) =>
232+
'?' +
233+
Object.entries(p)
234+
.map(([k, v]) => k + '=' + encodeURIComponent(v))
235+
.join('&');
236+
237+
describe('GET requests', () => {
238+
const createGetQueue = () =>
239+
OutQueueManager(
240+
'sp',
241+
new SharedState(),
242+
true,
243+
'get',
244+
'/com.snowplowanalytics.snowplow/tp2',
245+
1,
246+
40000,
247+
0,
248+
false,
249+
maxQueueSize,
250+
5000,
251+
false,
252+
{},
253+
true,
254+
[],
255+
[],
256+
idServiceEndpoint
257+
);
258+
259+
it('should first execute the idService request and in the same `enqueueRequest` the tracking request', () => {
260+
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
261+
const getQueue = createGetQueue();
262+
263+
getQueue.enqueueRequest(request, 'http://example.com');
264+
265+
let retrievedQueue = readGetQueue();
266+
expect(retrievedQueue).toHaveLength(1);
267+
/* The first XHR is for the idService */
268+
respondMockRequest(200);
269+
retrievedQueue = readGetQueue();
270+
expect(retrievedQueue).toHaveLength(1);
271+
expect(retrievedQueue[0]).toEqual(getQuerystring(request));
272+
/* The second XHR is the event request */
273+
respondMockRequest(200);
274+
retrievedQueue = readGetQueue();
275+
expect(retrievedQueue).toHaveLength(0);
276+
});
277+
278+
it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', () => {
279+
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
280+
const getQueue = createGetQueue();
281+
282+
getQueue.enqueueRequest(request, 'http://example.com');
283+
284+
let retrievedQueue = readGetQueue();
285+
expect(retrievedQueue).toHaveLength(1);
286+
/* The first XHR is for the idService */
287+
respondMockRequest(500);
288+
retrievedQueue = readGetQueue();
289+
expect(retrievedQueue).toHaveLength(1);
290+
expect(retrievedQueue[0]).toEqual(getQuerystring(request));
291+
/* The second XHR is the event request */
292+
respondMockRequest(200);
293+
retrievedQueue = readGetQueue();
294+
expect(retrievedQueue).toHaveLength(0);
295+
});
296+
});
297+
298+
describe('POST requests', () => {
299+
const createPostQueue = () =>
300+
OutQueueManager(
301+
'sp',
302+
new SharedState(),
303+
true,
304+
'post',
305+
'/com.snowplowanalytics.snowplow/tp2',
306+
1,
307+
40000,
308+
0,
309+
false,
310+
maxQueueSize,
311+
5000,
312+
false,
313+
{},
314+
true,
315+
[],
316+
[],
317+
idServiceEndpoint
318+
);
319+
320+
it('should first execute the idService request and in the same `enqueueRequest` the tracking request irregardless of failure of the idService endpoint', () => {
321+
const request = { e: 'pv', eid: '65cb78de-470c-4764-8c10-02bd79477a3a' };
322+
const postQueue = createPostQueue();
323+
324+
postQueue.enqueueRequest(request, 'http://example.com');
325+
326+
let retrievedQueue = readPostQueue();
327+
expect(retrievedQueue).toHaveLength(1);
328+
/* The first XHR is for the idService */
329+
respondMockRequest(500);
330+
retrievedQueue = readPostQueue();
331+
expect(retrievedQueue).toHaveLength(1);
332+
expect(retrievedQueue[0].evt).toEqual(request);
333+
/* The second XHR is the event request */
334+
respondMockRequest(200);
335+
retrievedQueue = readPostQueue();
336+
expect(retrievedQueue).toHaveLength(0);
337+
});
338+
});
339+
});
219340
});

trackers/browser-tracker/docs/browser-tracker.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ export type TrackerConfiguration = {
368368
retryStatusCodes?: number[];
369369
dontRetryStatusCodes?: number[];
370370
onSessionUpdateCallback?: (updatedSession: ClientSession) => void;
371+
idService?: string;
371372
};
372373

373374
// @public

0 commit comments

Comments
 (0)