Skip to content
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

Add the idService option to the tracker configuration (close #1185) #1186

Merged
merged 1 commit into from
Aug 25, 2023
Merged
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
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 @@ -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,
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];
matus-tomlein marked this conversation as resolved.
Show resolved Hide resolved
};

/**
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);
matus-tomlein marked this conversation as resolved.
Show resolved Hide resolved
idServiceCalled = true;
xhr.timeout = connectionTimeout;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
executeQueue();
}
};
igneel64 marked this conversation as resolved.
Show resolved Hide resolved
xhr.send();
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 @@ -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;
};

/**
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 @@ -360,6 +360,7 @@ export type TrackerConfiguration = {
retryStatusCodes?: number[];
dontRetryStatusCodes?: number[];
onSessionUpdateCallback?: (updatedSession: ClientSession) => void;
idService?: string;
};

// @public
Expand Down
Loading