Skip to content

Commit 0126522

Browse files
authored
feat: support batch forwarding in kits (#1244)
1 parent fd62adb commit 0126522

4 files changed

Lines changed: 458 additions & 6 deletions

File tree

src/batchUploader.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,19 @@ export class BatchUploader {
333333
entry[1],
334334
mpInstance
335335
);
336+
337+
if (uploadBatchObject) {
338+
try {
339+
mpInstance._Forwarders.sendBatchToForwarders(
340+
uploadBatchObject
341+
);
342+
} catch (e) {
343+
mpInstance.Logger.error(
344+
`Error forwarding batch to kits. ${e}`
345+
);
346+
}
347+
}
348+
336349
const onCreateBatchCallback =
337350
mpInstance._Store.SDKConfig.onCreateBatch;
338351

src/forwarders.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,34 @@ export default function Forwarders(mpInstance, kitBlocker) {
405405
}
406406
};
407407

408+
this.sendBatchToForwarders = function(batch) {
409+
if (
410+
mpInstance._Store.webviewBridgeEnabled ||
411+
!mpInstance._Store.activeForwarders
412+
) {
413+
return;
414+
}
415+
416+
for (const forwarder of mpInstance._Store.activeForwarders) {
417+
if (forwarder.processBatch) {
418+
try {
419+
const batchCopy = mpInstance._Helpers.extend(
420+
true,
421+
{},
422+
batch
423+
);
424+
const result = forwarder.processBatch(batchCopy);
425+
426+
if (result) {
427+
mpInstance.Logger.verbose(result);
428+
}
429+
} catch (e) {
430+
mpInstance.Logger.verbose(e);
431+
}
432+
}
433+
}
434+
};
435+
408436
this.handleForwarderUserAttributes = function(functionNameKey, key, value) {
409437
if (
410438
(kitBlocker && kitBlocker.isAttributeKeyBlocked(key)) ||
@@ -747,7 +775,6 @@ export default function Forwarders(mpInstance, kitBlocker) {
747775
config.filteringConsentRuleValues || {};
748776
newForwarder.excludeAnonymousUser =
749777
config.excludeAnonymousUser || false;
750-
751778
return newForwarder;
752779
};
753780

test/jest/batchUploader.spec.ts

Lines changed: 242 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ describe('BatchUploader', () => {
55
let batchUploader: BatchUploader;
66
let mockMPInstance: IMParticleWebSDKInstance;
77
let originalFetch: typeof global.fetch;
8+
let mockSendBatchToForwarders: jest.Mock;
89

910
beforeEach(() => {
1011
const now = Date.now();
@@ -16,21 +17,36 @@ describe('BatchUploader', () => {
1617
global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200 });
1718

1819
// Create a mock mParticle instance with mocked methods for instantiating a BatchUploader
20+
mockSendBatchToForwarders = jest.fn();
1921
mockMPInstance = {
2022
_Store: {
2123
SDKConfig: {
22-
flags: {}
23-
}
24+
flags: {},
25+
},
26+
sideloadedKitsCount: 0,
2427
},
2528
_Helpers: {
2629
getFeatureFlag: jest.fn().mockReturnValue(false),
2730
createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com'),
31+
generateUniqueId: jest.fn().mockReturnValue('mock-uuid'),
32+
},
33+
_Forwarders: {
34+
sendBatchToForwarders: mockSendBatchToForwarders,
2835
},
2936
Identity: {
3037
getCurrentUser: jest.fn().mockReturnValue({
31-
getMPID: () => 'test-mpid'
32-
})
33-
}
38+
getMPID: () => 'test-mpid',
39+
getConsentState: () => null,
40+
getUserIdentities: () => ({ userIdentities: {} }),
41+
getAllUserAttributes: () => ({}),
42+
}),
43+
},
44+
Logger: {
45+
error: jest.fn(),
46+
warning: jest.fn(),
47+
verbose: jest.fn(),
48+
isVerbose: jest.fn().mockReturnValue(false),
49+
},
3450
} as unknown as IMParticleWebSDKInstance;
3551

3652
batchUploader = new BatchUploader(mockMPInstance, 1000);
@@ -41,6 +57,227 @@ describe('BatchUploader', () => {
4157
global.fetch = originalFetch;
4258
});
4359

60+
describe('batch forwarding to kits', () => {
61+
it('should call sendBatchToForwarders for each new batch in createNewBatches', () => {
62+
const mockEvents = [
63+
{
64+
EventName: 'Test Event',
65+
EventDataType: 4,
66+
MPID: 'test-mpid',
67+
SessionId: 'session-1',
68+
EventCategory: 1,
69+
Timestamp: Date.now(),
70+
},
71+
];
72+
73+
const mockUser = {
74+
getMPID: () => 'test-mpid',
75+
getConsentState: () => null,
76+
getUserIdentities: () => ({ userIdentities: {} }),
77+
getAllUserAttributes: () => ({}),
78+
};
79+
80+
BatchUploader['createNewBatches'](
81+
mockEvents as any,
82+
mockUser as any,
83+
mockMPInstance
84+
);
85+
86+
expect(mockSendBatchToForwarders).toHaveBeenCalledTimes(1);
87+
expect(mockSendBatchToForwarders).toHaveBeenCalledWith(
88+
expect.objectContaining({
89+
mpid: 'test-mpid',
90+
})
91+
);
92+
});
93+
94+
it('should call sendBatchToForwarders before onCreateBatch', () => {
95+
const callOrder: string[] = [];
96+
97+
mockSendBatchToForwarders.mockImplementation(() => {
98+
callOrder.push('sendBatchToForwarders');
99+
});
100+
101+
mockMPInstance._Store.SDKConfig.onCreateBatch = (batch) => {
102+
callOrder.push('onCreateBatch');
103+
return batch;
104+
};
105+
106+
const mockEvents = [
107+
{
108+
EventName: 'Test Event',
109+
EventDataType: 4,
110+
MPID: 'test-mpid',
111+
SessionId: 'session-1',
112+
EventCategory: 1,
113+
Timestamp: Date.now(),
114+
},
115+
];
116+
117+
const mockUser = {
118+
getMPID: () => 'test-mpid',
119+
getConsentState: () => null,
120+
getUserIdentities: () => ({ userIdentities: {} }),
121+
getAllUserAttributes: () => ({}),
122+
};
123+
124+
BatchUploader['createNewBatches'](
125+
mockEvents as any,
126+
mockUser as any,
127+
mockMPInstance
128+
);
129+
130+
expect(callOrder).toEqual([
131+
'sendBatchToForwarders',
132+
'onCreateBatch',
133+
]);
134+
});
135+
136+
it('should forward batch before onCreateBatch can modify it', () => {
137+
let forwardedEventCount = 0;
138+
139+
mockSendBatchToForwarders.mockImplementation((batch) => {
140+
forwardedEventCount = batch.events.length;
141+
});
142+
143+
mockMPInstance._Store.SDKConfig.onCreateBatch = (batch) => {
144+
batch.modified = true;
145+
batch.events = [];
146+
return batch;
147+
};
148+
149+
const mockEvents = [
150+
{
151+
EventName: 'Test Event',
152+
EventDataType: 4,
153+
MPID: 'test-mpid',
154+
SessionId: 'session-1',
155+
EventCategory: 1,
156+
Timestamp: Date.now(),
157+
},
158+
];
159+
160+
const mockUser = {
161+
getMPID: () => 'test-mpid',
162+
getConsentState: () => null,
163+
getUserIdentities: () => ({ userIdentities: {} }),
164+
getAllUserAttributes: () => ({}),
165+
};
166+
167+
BatchUploader['createNewBatches'](
168+
mockEvents as any,
169+
mockUser as any,
170+
mockMPInstance
171+
);
172+
173+
expect(forwardedEventCount).toBe(1);
174+
});
175+
176+
it('should forward batch even when onCreateBatch drops it', () => {
177+
mockMPInstance._Store.SDKConfig.onCreateBatch = () => {
178+
return null;
179+
};
180+
181+
(mockMPInstance.Logger as any).warning = jest.fn();
182+
183+
const mockEvents = [
184+
{
185+
EventName: 'Test Event',
186+
EventDataType: 4,
187+
MPID: 'test-mpid',
188+
SessionId: 'session-1',
189+
EventCategory: 1,
190+
Timestamp: Date.now(),
191+
},
192+
];
193+
194+
const mockUser = {
195+
getMPID: () => 'test-mpid',
196+
getConsentState: () => null,
197+
getUserIdentities: () => ({ userIdentities: {} }),
198+
getAllUserAttributes: () => ({}),
199+
};
200+
201+
BatchUploader['createNewBatches'](
202+
mockEvents as any,
203+
mockUser as any,
204+
mockMPInstance
205+
);
206+
207+
expect(mockSendBatchToForwarders).toHaveBeenCalledTimes(1);
208+
});
209+
210+
it('should not throw if sendBatchToForwarders errors', () => {
211+
mockSendBatchToForwarders.mockImplementation(() => {
212+
throw new Error('Kit failure');
213+
});
214+
215+
const mockEvents = [
216+
{
217+
EventName: 'Test Event',
218+
EventDataType: 4,
219+
MPID: 'test-mpid',
220+
SessionId: 'session-1',
221+
EventCategory: 1,
222+
Timestamp: Date.now(),
223+
},
224+
];
225+
226+
const mockUser = {
227+
getMPID: () => 'test-mpid',
228+
getConsentState: () => null,
229+
getUserIdentities: () => ({ userIdentities: {} }),
230+
getAllUserAttributes: () => ({}),
231+
};
232+
233+
expect(() => {
234+
BatchUploader['createNewBatches'](
235+
mockEvents as any,
236+
mockUser as any,
237+
mockMPInstance
238+
);
239+
}).not.toThrow();
240+
241+
expect(mockMPInstance.Logger.error).toHaveBeenCalled();
242+
});
243+
244+
it('should create separate batches per MPID and forward each', () => {
245+
const mockEvents = [
246+
{
247+
EventName: 'Event User A',
248+
EventDataType: 4,
249+
MPID: 'mpid-a',
250+
SessionId: 'session-1',
251+
EventCategory: 1,
252+
Timestamp: Date.now(),
253+
},
254+
{
255+
EventName: 'Event User B',
256+
EventDataType: 4,
257+
MPID: 'mpid-b',
258+
SessionId: 'session-1',
259+
EventCategory: 1,
260+
Timestamp: Date.now(),
261+
},
262+
];
263+
264+
const mockUser = {
265+
getMPID: () => 'mpid-a',
266+
getConsentState: () => null,
267+
getUserIdentities: () => ({ userIdentities: {} }),
268+
getAllUserAttributes: () => ({}),
269+
};
270+
271+
BatchUploader['createNewBatches'](
272+
mockEvents as any,
273+
mockUser as any,
274+
mockMPInstance
275+
);
276+
277+
expect(mockSendBatchToForwarders).toHaveBeenCalledTimes(2);
278+
});
279+
});
280+
44281
describe('shouldDebounceAST', () => {
45282
it('should return false for first call', () => {
46283
// First call should not be debounced

0 commit comments

Comments
 (0)