@magicpixel/rn-mp-client-sdk 1.13.0 → 1.13.21
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 +75 -14
- 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 +62 -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 +301 -54
- 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 +76 -14
- 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 +63 -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 +290 -53
- 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 +30 -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/{common → src/common}/reporter.d.ts +2 -1
- package/lib/typescript/src/common/storage-helper.d.ts +47 -0
- package/lib/typescript/{common → src/common}/utils.d.ts +25 -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 +25 -36
- package/src/common/app-types.ts +33 -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 +97 -16
- package/src/common/storage-helper.ts +260 -0
- package/src/common/utils.ts +63 -2
- package/src/eedl/eedl.ts +225 -51
- package/src/index.tsx +346 -73
- 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/{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/common/utils.ts
CHANGED
|
@@ -11,6 +11,10 @@ import { EventBus } from './event-bus';
|
|
|
11
11
|
import { Dimensions, ScaledSize } from 'react-native';
|
|
12
12
|
import { URL } from 'react-native-url-polyfill';
|
|
13
13
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
14
|
+
import { ulid } from 'ulid';
|
|
15
|
+
|
|
16
|
+
/** Maximum payload size in bytes (250KB) to prevent DoS attacks */
|
|
17
|
+
const MAX_PAYLOAD_SIZE_BYTES = 250 * 1024;
|
|
14
18
|
|
|
15
19
|
export class Utils {
|
|
16
20
|
static triggerEvent(eventName: string, payload: any): void {
|
|
@@ -136,7 +140,7 @@ export class Utils {
|
|
|
136
140
|
if (tfItem.eTyp === 'URL_ENC') {
|
|
137
141
|
paramValue = encodeURIComponent(paramValue);
|
|
138
142
|
} else if (tfItem.eTyp === 'BASE64') {
|
|
139
|
-
paramValue =
|
|
143
|
+
paramValue = this.toBase64(paramValue);
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
}
|
|
@@ -204,8 +208,65 @@ export class Utils {
|
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Encode string to Base64 - React Native compatible
|
|
213
|
+
* Works with both ASCII and UTF-8 strings
|
|
214
|
+
* @param str String to encode
|
|
215
|
+
* @returns Base64 encoded string
|
|
216
|
+
*/
|
|
217
|
+
static toBase64(str: string): string {
|
|
218
|
+
try {
|
|
219
|
+
// Handle UTF-8 strings properly using encodeURIComponent + unescape trick
|
|
220
|
+
// This works in all JavaScript engines including React Native's Hermes/JSC
|
|
221
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
222
|
+
} catch (err) {
|
|
223
|
+
Logger.logError('Error encoding to Base64:', err);
|
|
224
|
+
return str; // Return original value if encoding fails
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Encode an object payload to Base64 string
|
|
230
|
+
* @param payload Object to encode
|
|
231
|
+
* @returns Base64 encoded string, or empty string on error
|
|
232
|
+
*/
|
|
233
|
+
static encodeToBase64(payload: Record<string, any>): string {
|
|
234
|
+
try {
|
|
235
|
+
const jsonStr = JSON.stringify(payload);
|
|
236
|
+
return this.toBase64(jsonStr);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
Logger.logError('Error encoding payload to Base64:', err);
|
|
239
|
+
return '';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get the size of a payload in bytes
|
|
245
|
+
* @param payload Object to measure
|
|
246
|
+
* @returns Size in bytes
|
|
247
|
+
*/
|
|
248
|
+
static getPayloadSizeBytes(payload: Record<string, any>): number {
|
|
249
|
+
try {
|
|
250
|
+
const jsonStr = JSON.stringify(payload);
|
|
251
|
+
// Use TextEncoder for accurate byte count (handles UTF-8)
|
|
252
|
+
return new TextEncoder().encode(jsonStr).length;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
Logger.logError('Error calculating payload size:', err);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check if payload exceeds the maximum allowed size (250KB)
|
|
261
|
+
* @param payload Object to check
|
|
262
|
+
* @returns True if payload is oversized
|
|
263
|
+
*/
|
|
264
|
+
static isPayloadOversized(payload: Record<string, any>): boolean {
|
|
265
|
+
return this.getPayloadSizeBytes(payload) > MAX_PAYLOAD_SIZE_BYTES;
|
|
266
|
+
}
|
|
267
|
+
|
|
207
268
|
static getUniqueID(): string {
|
|
208
|
-
return
|
|
269
|
+
return ulid();
|
|
209
270
|
}
|
|
210
271
|
|
|
211
272
|
/**
|
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
|
|