Skip to content

Commit 9512cea

Browse files
committed
Allow for usage of snowplow callback without this (close #1085)
1 parent 0776cc9 commit 9512cea

File tree

3 files changed

+80
-1
lines changed

3 files changed

+80
-1
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/javascript-tracker",
5+
"comment": "Allow usage of Snowplow callback without 'this' keyword",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/javascript-tracker"
10+
}

trackers/javascript-tracker/src/in_queue.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,15 @@ export function InQueueManager(functionName: string, asyncQueue: Array<unknown>)
265265
// Strip GlobalSnowplowNamespace from ID
266266
fnTrackers[tracker.id.replace(`${functionName}_`, '')] = tracker;
267267
}
268-
input.apply(fnTrackers, parameterArray);
268+
269+
// Create a new array from `parameterArray` to avoid mutating the original
270+
const parameterArrayCopy = Array.prototype.slice.call(parameterArray);
271+
272+
// Add the `fnTrackers` object as the last element of the new array to allow it to be accessed in the callback
273+
// as the final argument, useful in environments that don't support `this` (GTM)
274+
const args = Array.prototype.concat.apply(parameterArrayCopy, [fnTrackers]);
275+
276+
input.apply(fnTrackers, args);
269277
} catch (ex) {
270278
LOG.error('Tracker callback failed', ex);
271279
} finally {

trackers/javascript-tracker/test/unit/in_queue.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,34 @@ describe('InQueueManager', () => {
163163
asyncQueue.push(['updatePageActivity:firstTracker;secondTracker']);
164164
expect(output).toEqual(29);
165165
});
166+
});
167+
168+
describe('Snowplow callback', () => {
169+
const asyncQueue = InQueueManager('callback', []);
170+
const mockTrackers: Record<string, any> = {};
171+
172+
let userId: string | null | undefined;
173+
174+
const newTracker = (trackerId: string): any => {
175+
return {
176+
id: trackerId,
177+
setUserId: function (s?: string | null) {
178+
userId = s;
179+
},
180+
getUserId: function () {
181+
return userId;
182+
},
183+
};
184+
};
185+
186+
beforeEach(() => {
187+
const tracker = newTracker('sp1');
188+
mockTrackers.sp1 = tracker;
189+
});
190+
191+
afterEach(() => {
192+
delete mockTrackers.sp1;
193+
});
166194

167195
it('Execute a user-defined custom callback', () => {
168196
let callbackExecuted = false;
@@ -183,4 +211,37 @@ describe('InQueueManager', () => {
183211
]);
184212
}).not.toThrow();
185213
});
214+
215+
it('A custom callback with arguments provided will pass those arguments into the callback parameters', () => {
216+
asyncQueue.push([
217+
function (a: number, b: number) {
218+
expect(a).toEqual(1);
219+
expect(b).toEqual(2);
220+
},
221+
1,
222+
2,
223+
]);
224+
});
225+
226+
it('The callback will be passed the tracker dictionary as the argument if there is only one parameter', () => {
227+
asyncQueue.push([
228+
function (trackers: Record<string, any>) {
229+
const tracker = trackers.sp1;
230+
expect(tracker).toEqual(mockTrackers.callback_sp1);
231+
},
232+
]);
233+
});
234+
235+
it('The callback can access the tracker dictionary using both `this` and the last argument, along with arguments', () => {
236+
asyncQueue.push([
237+
function (this: any, a: number, b: number, trackers: Record<string, any>) {
238+
expect(this.sp1).toEqual(mockTrackers.callback_sp1);
239+
expect(a).toEqual(1);
240+
expect(b).toEqual(2);
241+
expect(trackers.sp1).toEqual(mockTrackers.callback_sp1);
242+
},
243+
1,
244+
2,
245+
]);
246+
});
186247
});

0 commit comments

Comments
 (0)