@magicpixel/rn-mp-client-sdk 1.13.0 → 1.13.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -14
- package/lib/commonjs/common/app-types.js.map +1 -1
- package/lib/commonjs/common/constants.js +11 -2
- package/lib/commonjs/common/constants.js.map +1 -1
- package/lib/commonjs/common/data-store.js +13 -30
- package/lib/commonjs/common/data-store.js.map +1 -1
- package/lib/commonjs/common/deeplink-helper.js +174 -0
- package/lib/commonjs/common/deeplink-helper.js.map +1 -0
- package/lib/commonjs/common/device-info-helper.js +168 -0
- package/lib/commonjs/common/device-info-helper.js.map +1 -0
- package/lib/commonjs/common/event-bus.js +39 -0
- package/lib/commonjs/common/event-bus.js.map +1 -1
- package/lib/commonjs/common/network-service.js +119 -15
- package/lib/commonjs/common/network-service.js.map +1 -1
- package/lib/commonjs/common/reporter.js +28 -10
- package/lib/commonjs/common/reporter.js.map +1 -1
- package/lib/commonjs/common/storage-helper.js +227 -0
- package/lib/commonjs/common/storage-helper.js.map +1 -0
- package/lib/commonjs/common/utils.js +20 -2
- package/lib/commonjs/common/utils.js.map +1 -1
- package/lib/commonjs/eedl/eedl.js +198 -44
- package/lib/commonjs/eedl/eedl.js.map +1 -1
- package/lib/commonjs/index.js +290 -48
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/mp-client-sdk.js +17 -10
- package/lib/commonjs/models/mp-client-sdk.js.map +1 -1
- package/lib/commonjs/processors/data-element.processor.js +51 -7
- package/lib/commonjs/processors/data-element.processor.js.map +1 -1
- package/lib/commonjs/processors/visit-id.processor.js +78 -15
- package/lib/commonjs/processors/visit-id.processor.js.map +1 -1
- package/lib/module/common/app-types.js.map +1 -1
- package/lib/module/common/constants.js +11 -2
- package/lib/module/common/constants.js.map +1 -1
- package/lib/module/common/data-store.js +13 -30
- package/lib/module/common/data-store.js.map +1 -1
- package/lib/module/common/deeplink-helper.js +168 -0
- package/lib/module/common/deeplink-helper.js.map +1 -0
- package/lib/module/common/device-info-helper.js +161 -0
- package/lib/module/common/device-info-helper.js.map +1 -0
- package/lib/module/common/event-bus.js +39 -0
- package/lib/module/common/event-bus.js.map +1 -1
- package/lib/module/common/network-service.js +119 -15
- package/lib/module/common/network-service.js.map +1 -1
- package/lib/module/common/reporter.js +29 -10
- package/lib/module/common/reporter.js.map +1 -1
- package/lib/module/common/storage-helper.js +221 -0
- package/lib/module/common/storage-helper.js.map +1 -0
- package/lib/module/common/utils.js +20 -2
- package/lib/module/common/utils.js.map +1 -1
- package/lib/module/eedl/eedl.js +198 -44
- package/lib/module/eedl/eedl.js.map +1 -1
- package/lib/module/index.js +279 -47
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/mp-client-sdk.js +16 -9
- package/lib/module/models/mp-client-sdk.js.map +1 -1
- package/lib/module/processors/data-element.processor.js +51 -7
- package/lib/module/processors/data-element.processor.js.map +1 -1
- package/lib/module/processors/visit-id.processor.js +78 -15
- package/lib/module/processors/visit-id.processor.js.map +1 -1
- package/lib/typescript/{common → src/common}/app-types.d.ts +29 -9
- package/lib/typescript/{common → src/common}/constants.d.ts +0 -1
- package/lib/typescript/{common → src/common}/data-store.d.ts +3 -8
- package/lib/typescript/src/common/deeplink-helper.d.ts +60 -0
- package/lib/typescript/src/common/device-info-helper.d.ts +54 -0
- package/lib/typescript/src/common/event-bus.d.ts +21 -0
- package/lib/typescript/src/common/network-service.d.ts +32 -0
- package/lib/typescript/src/common/storage-helper.d.ts +47 -0
- package/lib/typescript/{common → src/common}/utils.d.ts +7 -0
- package/lib/typescript/{eedl → src/eedl}/eedl.d.ts +43 -1
- package/lib/typescript/{index.d.ts → src/index.d.ts} +39 -5
- package/lib/typescript/{models → src/models}/mp-client-sdk.d.ts +7 -0
- package/lib/typescript/src/processors/visit-id.processor.d.ts +23 -0
- package/package.json +26 -37
- package/src/common/app-types.ts +32 -10
- package/src/common/constants.ts +0 -6
- package/src/common/data-store.ts +8 -30
- package/src/common/deeplink-helper.ts +181 -0
- package/src/common/device-info-helper.ts +190 -0
- package/src/common/event-bus.ts +39 -0
- package/src/common/network-service.ts +154 -21
- package/src/common/reporter.ts +31 -10
- package/src/common/storage-helper.ts +266 -0
- package/src/common/utils.ts +20 -2
- package/src/eedl/eedl.ts +225 -51
- package/src/index.tsx +332 -67
- package/src/models/mp-client-sdk.ts +8 -0
- package/src/processors/data-element.processor.ts +85 -7
- package/src/processors/visit-id.processor.ts +92 -22
- package/lib/commonjs/processors/trans-function.processor.js +0 -73
- package/lib/commonjs/processors/trans-function.processor.js.map +0 -1
- package/lib/module/processors/trans-function.processor.js +0 -66
- package/lib/module/processors/trans-function.processor.js.map +0 -1
- package/lib/typescript/common/event-bus.d.ts +0 -6
- package/lib/typescript/common/network-service.d.ts +0 -8
- package/lib/typescript/processors/trans-function.processor.d.ts +0 -12
- package/lib/typescript/processors/visit-id.processor.d.ts +0 -9
- package/src/processors/trans-function.processor.ts +0 -85
- /package/lib/typescript/{common → src/common}/logger.d.ts +0 -0
- /package/lib/typescript/{common → src/common}/reporter.d.ts +0 -0
- /package/lib/typescript/{models → src/models}/geo-api-response.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/data-element.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/geo-location.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/qc.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/tag.processor.d.ts +0 -0
package/src/eedl/eedl.ts
CHANGED
|
@@ -2,18 +2,15 @@ import { Utils } from '../common/utils';
|
|
|
2
2
|
import type { EventProcessorFn, MapLike, TypedAny } from '../common/app-types';
|
|
3
3
|
import { Logger } from '../common/logger';
|
|
4
4
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
|
-
import {
|
|
5
|
+
import { ulid } from 'ulid';
|
|
6
6
|
|
|
7
7
|
const eventsToPersist: Record<string, string> = {
|
|
8
8
|
user_info: '_mpPendingUserInfo',
|
|
9
9
|
mp_purchase: '_mpPendingPurchase',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
'1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
15
|
-
36
|
|
16
|
-
);
|
|
12
|
+
// Maximum number of events to keep in memory for state tracking and queues
|
|
13
|
+
const MAX_TRACKED_EVENTS = 500;
|
|
17
14
|
|
|
18
15
|
export class MpDataLayerHelper {
|
|
19
16
|
isReady = false;
|
|
@@ -24,8 +21,14 @@ export class MpDataLayerHelper {
|
|
|
24
21
|
dlInitEvent: string;
|
|
25
22
|
receivedInitialEvent = false;
|
|
26
23
|
eventQueue: Array<any> = [];
|
|
24
|
+
private isProcessing = false;
|
|
27
25
|
private isEntryPoint = 0;
|
|
28
26
|
|
|
27
|
+
// Event deduplication
|
|
28
|
+
private eventDedupWindowMs = 5000; // Default 5 seconds
|
|
29
|
+
private eventDeduplicationCache: Map<string, number> = new Map();
|
|
30
|
+
private purchaseTransactionCache: Map<string, number> = new Map();
|
|
31
|
+
|
|
29
32
|
constructor(
|
|
30
33
|
private readonly dlEventName: string,
|
|
31
34
|
private readonly dlInitMode: string,
|
|
@@ -72,8 +75,135 @@ export class MpDataLayerHelper {
|
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Set the event deduplication window
|
|
80
|
+
* @param windowMs Window in milliseconds (0 to disable)
|
|
81
|
+
*/
|
|
82
|
+
setDeduplicationWindow(windowMs: number): void {
|
|
83
|
+
this.eventDedupWindowMs = windowMs;
|
|
84
|
+
Logger.logDbg(`Event deduplication window set to: ${windowMs}ms`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate a unique fingerprint for an event
|
|
89
|
+
* @param eventName Event name
|
|
90
|
+
* @param payload Event payload
|
|
91
|
+
* @returns Fingerprint string
|
|
92
|
+
*/
|
|
93
|
+
private getEventFingerprint(eventName: string, payload: MapLike): string {
|
|
94
|
+
try {
|
|
95
|
+
// Use JSON.stringify for deterministic payload representation
|
|
96
|
+
const payloadStr = JSON.stringify(payload);
|
|
97
|
+
return `${eventName}::${payloadStr}`;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
Logger.logError('Error generating event fingerprint', err);
|
|
100
|
+
return `${eventName}::${Date.now()}`; // Fallback to never match
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract transaction_id from purchase event payload
|
|
106
|
+
* @param payload Event payload
|
|
107
|
+
* @returns transaction_id or null
|
|
108
|
+
*/
|
|
109
|
+
private extractTransactionId(payload: MapLike): string | null {
|
|
110
|
+
// Check common field names for transaction_id
|
|
111
|
+
return (
|
|
112
|
+
payload.transaction_id ||
|
|
113
|
+
payload.transactionId ||
|
|
114
|
+
payload.orderId ||
|
|
115
|
+
payload.order_id ||
|
|
116
|
+
null
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if event is a duplicate within the deduplication window
|
|
122
|
+
* @param eventName Event name
|
|
123
|
+
* @param payload Event payload
|
|
124
|
+
* @returns true if duplicate, false otherwise
|
|
125
|
+
*/
|
|
126
|
+
private isDuplicateEvent(eventName: string, payload: MapLike): boolean {
|
|
127
|
+
// If deduplication is disabled (window = 0), never treat as duplicate
|
|
128
|
+
if (this.eventDedupWindowMs === 0) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
|
|
134
|
+
// Special handling for purchase events - deduplicate by transaction_id
|
|
135
|
+
if (eventName === 'purchase' || eventName === 'mp_purchase') {
|
|
136
|
+
const transactionId = this.extractTransactionId(payload);
|
|
137
|
+
|
|
138
|
+
if (transactionId) {
|
|
139
|
+
const lastSeenTs = this.purchaseTransactionCache.get(transactionId);
|
|
140
|
+
|
|
141
|
+
if (lastSeenTs && now - lastSeenTs < this.eventDedupWindowMs) {
|
|
142
|
+
Logger.logDbg(
|
|
143
|
+
`Duplicate purchase event detected (transaction_id: ${transactionId}) within ${this.eventDedupWindowMs}ms window, skipping`
|
|
144
|
+
);
|
|
145
|
+
return true; // Duplicate
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Record this transaction
|
|
149
|
+
this.purchaseTransactionCache.set(transactionId, now);
|
|
150
|
+
} else {
|
|
151
|
+
Logger.logDbg(
|
|
152
|
+
'Purchase event without transaction_id, cannot deduplicate by transaction'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// General event deduplication by full fingerprint
|
|
158
|
+
const fingerprint = this.getEventFingerprint(eventName, payload);
|
|
159
|
+
const lastSeenTs = this.eventDeduplicationCache.get(fingerprint);
|
|
160
|
+
|
|
161
|
+
if (lastSeenTs && now - lastSeenTs < this.eventDedupWindowMs) {
|
|
162
|
+
Logger.logDbg(
|
|
163
|
+
`Duplicate event detected (${eventName}) within ${this.eventDedupWindowMs}ms window, skipping`
|
|
164
|
+
);
|
|
165
|
+
return true; // Duplicate
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Record this event
|
|
169
|
+
this.eventDeduplicationCache.set(fingerprint, now);
|
|
170
|
+
|
|
171
|
+
// Cleanup old entries periodically
|
|
172
|
+
this.cleanupDeduplicationCache(now);
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Remove expired entries from deduplication caches
|
|
179
|
+
* @param now Current timestamp
|
|
180
|
+
*/
|
|
181
|
+
private cleanupDeduplicationCache(now: number): void {
|
|
182
|
+
// Cleanup general event cache
|
|
183
|
+
for (const [
|
|
184
|
+
fingerprint,
|
|
185
|
+
timestamp,
|
|
186
|
+
] of this.eventDeduplicationCache.entries()) {
|
|
187
|
+
if (now - timestamp > this.eventDedupWindowMs) {
|
|
188
|
+
this.eventDeduplicationCache.delete(fingerprint);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Cleanup purchase transaction cache
|
|
193
|
+
for (const [
|
|
194
|
+
transactionId,
|
|
195
|
+
timestamp,
|
|
196
|
+
] of this.purchaseTransactionCache.entries()) {
|
|
197
|
+
if (now - timestamp > this.eventDedupWindowMs) {
|
|
198
|
+
this.purchaseTransactionCache.delete(transactionId);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
75
203
|
pushEvent(eventName: string, payload: MapLike): void {
|
|
76
204
|
Logger.logDbg('EV Push Event:: ', JSON.stringify(payload));
|
|
205
|
+
|
|
206
|
+
// Special events bypass queue and process immediately (no deduplication)
|
|
77
207
|
if (
|
|
78
208
|
eventName === 'set' ||
|
|
79
209
|
eventName === 'persist' ||
|
|
@@ -84,22 +214,33 @@ export class MpDataLayerHelper {
|
|
|
84
214
|
this.processQItems(eventName, payload).catch((err) =>
|
|
85
215
|
Logger.logError('Error processing event', err)
|
|
86
216
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for duplicate events BEFORE queuing
|
|
221
|
+
if (this.isDuplicateEvent(eventName, payload)) {
|
|
222
|
+
// Duplicate detected, skip this event
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Track if we received the initial event
|
|
227
|
+
if (!this.receivedInitialEvent) {
|
|
228
|
+
this.receivedInitialEvent = eventName === this.dlInitEvent;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Always queue events for sequential processing (with eviction of oldest if at capacity)
|
|
232
|
+
if (this.eventQueue.length >= MAX_TRACKED_EVENTS) {
|
|
233
|
+
const evictedEvent = this.eventQueue.shift();
|
|
234
|
+
Logger.logDbg(
|
|
235
|
+
`Event queue at capacity (${MAX_TRACKED_EVENTS}), evicting oldest event:`,
|
|
236
|
+
evictedEvent?.[0]
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
this.eventQueue.push([eventName, payload]);
|
|
240
|
+
|
|
241
|
+
// Trigger processing if ready and initial event received
|
|
242
|
+
if (this.isReady && this.receivedInitialEvent) {
|
|
243
|
+
this.processNext();
|
|
103
244
|
}
|
|
104
245
|
}
|
|
105
246
|
|
|
@@ -292,8 +433,8 @@ export class MpDataLayerHelper {
|
|
|
292
433
|
if (eventName === 'reset' || eventName === this.dlInitEvent) {
|
|
293
434
|
this.reset();
|
|
294
435
|
|
|
295
|
-
// add an init event based
|
|
296
|
-
model['page_load_uid'] =
|
|
436
|
+
// add an init event based ulid to the core data model
|
|
437
|
+
model['page_load_uid'] = ulid();
|
|
297
438
|
model['is_entry_point'] = this.getIsEntryPointValue();
|
|
298
439
|
if (model['is_entry_point'] === 1) {
|
|
299
440
|
// Note: In React Native, we don't have document.referrer, so we'll use a placeholder
|
|
@@ -341,7 +482,10 @@ export class MpDataLayerHelper {
|
|
|
341
482
|
})
|
|
342
483
|
);
|
|
343
484
|
|
|
344
|
-
// add to state tracker
|
|
485
|
+
// add to state tracker (with eviction of oldest events if at capacity)
|
|
486
|
+
if (this.stateTracker.length >= MAX_TRACKED_EVENTS) {
|
|
487
|
+
this.stateTracker.shift(); // Remove oldest event
|
|
488
|
+
}
|
|
345
489
|
this.stateTracker.push(eventPayload);
|
|
346
490
|
|
|
347
491
|
// trigger an event that can be listened to by other listeners
|
|
@@ -402,16 +546,32 @@ export class MpDataLayerHelper {
|
|
|
402
546
|
return true;
|
|
403
547
|
}
|
|
404
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Full shutdown - clears all state and allows reinitialization
|
|
551
|
+
*/
|
|
552
|
+
shutdown(): void {
|
|
553
|
+
this.isReady = false;
|
|
554
|
+
this._masterDataLayer = {};
|
|
555
|
+
this._persistedVars = {};
|
|
556
|
+
this.eventProcessors = {};
|
|
557
|
+
this.stateTracker = [];
|
|
558
|
+
this.receivedInitialEvent = false;
|
|
559
|
+
this.eventQueue = [];
|
|
560
|
+
this.isProcessing = false;
|
|
561
|
+
this.isEntryPoint = 0;
|
|
562
|
+
this.eventDeduplicationCache.clear();
|
|
563
|
+
this.purchaseTransactionCache.clear();
|
|
564
|
+
Logger.logDbg('EEDL shutdown complete');
|
|
565
|
+
}
|
|
566
|
+
|
|
405
567
|
ready(): void {
|
|
406
568
|
this.isReady = true;
|
|
407
569
|
if (this.receivedInitialEvent) {
|
|
408
570
|
Logger.logDbg('Initial event received: ', this.dlInitEvent);
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
Logger.logError('Error draining queue', err)
|
|
414
|
-
);
|
|
571
|
+
// Start processing queue if we have events and initial event received
|
|
572
|
+
if (this.eventQueue.length > 0) {
|
|
573
|
+
this.processNext();
|
|
574
|
+
}
|
|
415
575
|
} else {
|
|
416
576
|
Logger.logDbg(
|
|
417
577
|
`Initial event (${this.dlInitEvent}) NOT received. Events will be queued`
|
|
@@ -419,27 +579,41 @@ export class MpDataLayerHelper {
|
|
|
419
579
|
}
|
|
420
580
|
}
|
|
421
581
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
(e) => [e][0]?.[0] === this.dlInitEvent
|
|
431
|
-
);
|
|
432
|
-
if (initialEventObjectIndex > -1) {
|
|
433
|
-
// splice and execute it
|
|
434
|
-
await this.processQItems(
|
|
435
|
-
_temp[initialEventObjectIndex][0],
|
|
436
|
-
_temp[initialEventObjectIndex][1]
|
|
437
|
-
);
|
|
438
|
-
_temp.splice(initialEventObjectIndex, 1);
|
|
582
|
+
/**
|
|
583
|
+
* Process next event in queue with atomic check-and-set
|
|
584
|
+
* Ensures only one event processes at a time, eliminating race conditions
|
|
585
|
+
*/
|
|
586
|
+
processNext(): void {
|
|
587
|
+
// Atomic check-and-set: if already processing or queue empty, return
|
|
588
|
+
if (this.isProcessing || this.eventQueue.length === 0) {
|
|
589
|
+
return;
|
|
439
590
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
591
|
+
|
|
592
|
+
this.isProcessing = true;
|
|
593
|
+
|
|
594
|
+
// Dequeue from front for FIFO ordering
|
|
595
|
+
const item = this.eventQueue.shift();
|
|
596
|
+
|
|
597
|
+
if (item) {
|
|
598
|
+
const [eventName, payload] = item;
|
|
599
|
+
|
|
600
|
+
// Process the event
|
|
601
|
+
this.processQItems(eventName, payload)
|
|
602
|
+
.catch((err) => {
|
|
603
|
+
Logger.logError('Error processing event in queue', err);
|
|
604
|
+
})
|
|
605
|
+
.finally(() => {
|
|
606
|
+
// Reset processing flag
|
|
607
|
+
this.isProcessing = false;
|
|
608
|
+
|
|
609
|
+
// Process next item if queue not empty
|
|
610
|
+
// Use setImmediate for non-blocking event loop
|
|
611
|
+
if (this.eventQueue.length > 0) {
|
|
612
|
+
setImmediate(() => this.processNext());
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
} else {
|
|
616
|
+
this.isProcessing = false;
|
|
443
617
|
}
|
|
444
618
|
}
|
|
445
619
|
|