@magicpixel/rn-mp-client-sdk 1.12.1 → 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.
Files changed (104) hide show
  1. package/README.md +163 -14
  2. package/lib/commonjs/common/app-types.js.map +1 -1
  3. package/lib/commonjs/common/constants.js +11 -2
  4. package/lib/commonjs/common/constants.js.map +1 -1
  5. package/lib/commonjs/common/data-store.js +13 -30
  6. package/lib/commonjs/common/data-store.js.map +1 -1
  7. package/lib/commonjs/common/deeplink-helper.js +174 -0
  8. package/lib/commonjs/common/deeplink-helper.js.map +1 -0
  9. package/lib/commonjs/common/device-info-helper.js +168 -0
  10. package/lib/commonjs/common/device-info-helper.js.map +1 -0
  11. package/lib/commonjs/common/event-bus.js +39 -0
  12. package/lib/commonjs/common/event-bus.js.map +1 -1
  13. package/lib/commonjs/common/network-service.js +119 -17
  14. package/lib/commonjs/common/network-service.js.map +1 -1
  15. package/lib/commonjs/common/reporter.js +28 -10
  16. package/lib/commonjs/common/reporter.js.map +1 -1
  17. package/lib/commonjs/common/storage-helper.js +227 -0
  18. package/lib/commonjs/common/storage-helper.js.map +1 -0
  19. package/lib/commonjs/common/utils.js +20 -2
  20. package/lib/commonjs/common/utils.js.map +1 -1
  21. package/lib/commonjs/eedl/eedl.js +198 -44
  22. package/lib/commonjs/eedl/eedl.js.map +1 -1
  23. package/lib/commonjs/index.js +290 -48
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/models/mp-client-sdk.js +17 -10
  26. package/lib/commonjs/models/mp-client-sdk.js.map +1 -1
  27. package/lib/commonjs/processors/data-element.processor.js +51 -7
  28. package/lib/commonjs/processors/data-element.processor.js.map +1 -1
  29. package/lib/commonjs/processors/visit-id.processor.js +78 -15
  30. package/lib/commonjs/processors/visit-id.processor.js.map +1 -1
  31. package/lib/module/common/app-types.js.map +1 -1
  32. package/lib/module/common/constants.js +11 -2
  33. package/lib/module/common/constants.js.map +1 -1
  34. package/lib/module/common/data-store.js +13 -30
  35. package/lib/module/common/data-store.js.map +1 -1
  36. package/lib/module/common/deeplink-helper.js +168 -0
  37. package/lib/module/common/deeplink-helper.js.map +1 -0
  38. package/lib/module/common/device-info-helper.js +161 -0
  39. package/lib/module/common/device-info-helper.js.map +1 -0
  40. package/lib/module/common/event-bus.js +39 -0
  41. package/lib/module/common/event-bus.js.map +1 -1
  42. package/lib/module/common/network-service.js +119 -17
  43. package/lib/module/common/network-service.js.map +1 -1
  44. package/lib/module/common/reporter.js +29 -10
  45. package/lib/module/common/reporter.js.map +1 -1
  46. package/lib/module/common/storage-helper.js +221 -0
  47. package/lib/module/common/storage-helper.js.map +1 -0
  48. package/lib/module/common/utils.js +20 -2
  49. package/lib/module/common/utils.js.map +1 -1
  50. package/lib/module/eedl/eedl.js +198 -44
  51. package/lib/module/eedl/eedl.js.map +1 -1
  52. package/lib/module/index.js +279 -47
  53. package/lib/module/index.js.map +1 -1
  54. package/lib/module/models/mp-client-sdk.js +16 -9
  55. package/lib/module/models/mp-client-sdk.js.map +1 -1
  56. package/lib/module/processors/data-element.processor.js +51 -7
  57. package/lib/module/processors/data-element.processor.js.map +1 -1
  58. package/lib/module/processors/visit-id.processor.js +78 -15
  59. package/lib/module/processors/visit-id.processor.js.map +1 -1
  60. package/lib/typescript/{common → src/common}/app-types.d.ts +29 -9
  61. package/lib/typescript/{common → src/common}/constants.d.ts +0 -1
  62. package/lib/typescript/{common → src/common}/data-store.d.ts +3 -8
  63. package/lib/typescript/src/common/deeplink-helper.d.ts +60 -0
  64. package/lib/typescript/src/common/device-info-helper.d.ts +54 -0
  65. package/lib/typescript/src/common/event-bus.d.ts +21 -0
  66. package/lib/typescript/src/common/network-service.d.ts +32 -0
  67. package/lib/typescript/src/common/storage-helper.d.ts +47 -0
  68. package/lib/typescript/{common → src/common}/utils.d.ts +7 -0
  69. package/lib/typescript/{eedl → src/eedl}/eedl.d.ts +43 -1
  70. package/lib/typescript/{index.d.ts → src/index.d.ts} +39 -5
  71. package/lib/typescript/{models → src/models}/mp-client-sdk.d.ts +7 -0
  72. package/lib/typescript/src/processors/visit-id.processor.d.ts +23 -0
  73. package/package.json +26 -42
  74. package/src/common/app-types.ts +32 -10
  75. package/src/common/constants.ts +0 -6
  76. package/src/common/data-store.ts +8 -30
  77. package/src/common/deeplink-helper.ts +181 -0
  78. package/src/common/device-info-helper.ts +190 -0
  79. package/src/common/event-bus.ts +39 -0
  80. package/src/common/network-service.ts +154 -23
  81. package/src/common/reporter.ts +31 -10
  82. package/src/common/storage-helper.ts +266 -0
  83. package/src/common/utils.ts +20 -2
  84. package/src/eedl/eedl.ts +225 -51
  85. package/src/index.tsx +332 -67
  86. package/src/models/mp-client-sdk.ts +8 -0
  87. package/src/processors/data-element.processor.ts +85 -7
  88. package/src/processors/visit-id.processor.ts +92 -22
  89. package/lib/commonjs/processors/trans-function.processor.js +0 -73
  90. package/lib/commonjs/processors/trans-function.processor.js.map +0 -1
  91. package/lib/module/processors/trans-function.processor.js +0 -66
  92. package/lib/module/processors/trans-function.processor.js.map +0 -1
  93. package/lib/typescript/common/event-bus.d.ts +0 -6
  94. package/lib/typescript/common/network-service.d.ts +0 -8
  95. package/lib/typescript/processors/trans-function.processor.d.ts +0 -12
  96. package/lib/typescript/processors/visit-id.processor.d.ts +0 -9
  97. package/src/processors/trans-function.processor.ts +0 -85
  98. /package/lib/typescript/{common → src/common}/logger.d.ts +0 -0
  99. /package/lib/typescript/{common → src/common}/reporter.d.ts +0 -0
  100. /package/lib/typescript/{models → src/models}/geo-api-response.d.ts +0 -0
  101. /package/lib/typescript/{processors → src/processors}/data-element.processor.d.ts +0 -0
  102. /package/lib/typescript/{processors → src/processors}/geo-location.processor.d.ts +0 -0
  103. /package/lib/typescript/{processors → src/processors}/qc.processor.d.ts +0 -0
  104. /package/lib/typescript/{processors → src/processors}/tag.processor.d.ts +0 -0
package/src/index.tsx CHANGED
@@ -1,3 +1,6 @@
1
+ // Polyfill for crypto.getRandomValues() required by ULID
2
+ import 'react-native-get-random-values';
3
+
1
4
  import { MpDataLayerHelper } from './eedl/eedl';
2
5
  import type {
3
6
  AppCustomerInfo,
@@ -17,9 +20,22 @@ import { NetworkService } from './common/network-service';
17
20
  import { EventBus } from './common/event-bus';
18
21
  import { VisitIdProcessor } from './processors/visit-id.processor';
19
22
  import { GeoLocationProcessor } from './processors/geo-location.processor';
23
+ import { DeviceInfoHelper } from './common/device-info-helper';
24
+ import { DeepLinkHelper } from './common/deeplink-helper';
25
+ import { StorageHelper } from './common/storage-helper';
20
26
 
21
27
  const DL_INIT_EVENT = 'page_load';
22
28
 
29
+ // Maximum number of events to buffer before SDK is ready
30
+ const MAX_BUFFERED_EVENTS = 500;
31
+
32
+ // Type for buffered events
33
+ interface BufferedEvent {
34
+ type: 'pageLoad' | 'event';
35
+ name?: string; // event name for recordEvent
36
+ data: any;
37
+ }
38
+
23
39
  class MagicPixelImpl {
24
40
  private static dl: MpDataLayerHelper = new MpDataLayerHelper(
25
41
  'mpDlEvent',
@@ -31,26 +47,131 @@ class MagicPixelImpl {
31
47
  private static customerIdentifiers: MapLike = {};
32
48
 
33
49
  private static deepLinkUrl: string | undefined = undefined;
50
+ private static orgId: string | undefined = undefined;
34
51
 
35
52
  private static firstAppLaunch = true;
36
53
 
37
- static async init(options: SdkInitOptions): Promise<void> {
54
+ // SDK readiness state and event buffer
55
+ private static isReady = false;
56
+ private static isInitializing = false;
57
+ private static eventBuffer: BufferedEvent[] = [];
58
+ private static onInitFailureCallback: ((error: Error) => void) | undefined;
59
+
60
+ /**
61
+ * Initialize the MagicPixel SDK
62
+ * This method is synchronous - it stores config immediately and runs async setup in background
63
+ * Events called before async setup completes are buffered and processed once ready
64
+ * @param options SDK initialization options
65
+ */
66
+ static init(options: SdkInitOptions): void {
67
+ // Guard against multiple init() calls
68
+ if (this.isReady) {
69
+ Logger.logDbg(
70
+ 'MagicPixel SDK already initialized. Ignoring duplicate init() call.'
71
+ );
72
+ return;
73
+ }
74
+
75
+ if (this.isInitializing) {
76
+ Logger.logDbg(
77
+ 'MagicPixel SDK initialization in progress. Ignoring duplicate init() call.'
78
+ );
79
+ return;
80
+ }
81
+
82
+ this.isInitializing = true;
83
+
84
+ // Sync setup - happens immediately
38
85
  Logger.setLogLevel(options.logLevel ?? 'none');
86
+ this.orgId = options.orgId;
87
+ this.onInitFailureCallback = options.onInitFailure;
88
+
89
+ Logger.logDbg('MagicPixel SDK init started');
90
+
91
+ // Async setup - runs in background, events are buffered until complete
92
+ this.initAsync(options).catch((err) => {
93
+ Logger.logError('MagicPixel SDK initialization failed:', err);
94
+ Reporter.reportError('sdk_init', err);
95
+
96
+ // Reset initializing flag so developer can retry if needed
97
+ this.isInitializing = false;
98
+
99
+ // Call failure callback if provided
100
+ if (this.onInitFailureCallback) {
101
+ const error = err instanceof Error ? err : new Error(String(err));
102
+ this.onInitFailureCallback(error);
103
+ }
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Shutdown the SDK and release all resources
109
+ * Call this when the app is closing or when you need to reinitialize the SDK
110
+ * After shutdown, you must call init() again to use the SDK
111
+ */
112
+ static shutdown(): void {
113
+ Logger.logDbg('MagicPixel SDK shutting down...');
114
+
115
+ // Clean up deeplink listener and callback
116
+ DeepLinkHelper.cleanup();
117
+
118
+ // Clear all EventBus listeners to prevent duplicates on reinit
119
+ EventBus.clearAll();
120
+
121
+ // Clear session-scoped data element values (in-memory store)
122
+ StorageHelper.clearSessionStore();
123
+
124
+ // Clear event buffer
125
+ this.eventBuffer = [];
126
+
127
+ // Reset state flags
128
+ this.isReady = false;
129
+ this.isInitializing = false;
130
+ this.onInitFailureCallback = undefined;
131
+
132
+ // Reset customer data
133
+ this.customerInfo = undefined as any;
134
+ this.customerIdentifiers = {};
135
+ this.deepLinkUrl = undefined;
136
+ this.firstAppLaunch = true;
137
+
138
+ // Shutdown EEDL
139
+ this.dl.shutdown();
140
+
141
+ Logger.logDbg('MagicPixel SDK shutdown complete');
142
+ }
143
+
144
+ /**
145
+ * Async initialization - runs in background
146
+ */
147
+ private static async initAsync(options: SdkInitOptions): Promise<void> {
39
148
  await NetworkService.refreshClientSdkJson(options);
40
149
 
41
150
  if (!DataStore.isDataStoreReady()) {
42
- throw new Error('MagicPixel SDK is not ready. No tags will be processed');
151
+ throw new Error(
152
+ 'MagicPixel SDK: DataStore not ready after config fetch. Initialization failed.'
153
+ );
43
154
  }
44
155
 
45
- if (
46
- options &&
47
- options.device_type &&
48
- options.device_type?.trim().length > 0
49
- ) {
50
- DataStore.overrideDeviceType(options.device_type);
51
- }
156
+ // Initialize StorageHelper for data element storage duration
157
+ // This loads visitor-scoped values from AsyncStorage and clears expired ones (>30 days)
158
+ await StorageHelper.initialize();
159
+
160
+ // Auto-detect device info - always use detected values to prevent hardcoded values
161
+ const autoDetectedInfo = await DeviceInfoHelper.getAppInfo();
162
+ Logger.logDbg('Auto-detected device info:', autoDetectedInfo);
163
+
164
+ // Set device type from auto-detection (Expo/react-native-device-info)
165
+ // This is more reliable than dimension-based detection which fails on high-res phones
166
+ const detectedDeviceType = autoDetectedInfo.is_tablet ? 'tablet' : 'mobile';
167
+ DataStore.overrideDeviceType(detectedDeviceType);
168
+ Logger.logDbg('Device type set from auto-detection:', detectedDeviceType);
52
169
 
53
- this.setAppVersion(options.app_version);
170
+ // Always use auto-detected app_version (ignore options.app_version)
171
+ this.setAppVersion(autoDetectedInfo.app_version);
172
+
173
+ // Set device info with auto-detected values (cannot be overridden)
174
+ await this.setDeviceInfo();
54
175
 
55
176
  await VisitIdProcessor.init(options?.orgId);
56
177
 
@@ -62,9 +183,39 @@ class MagicPixelImpl {
62
183
  Logger.logDbg('No facebook client id found. not setting');
63
184
  }
64
185
 
186
+ // Auto-detect deeplink if app was opened via deeplink (e.g., ad click)
187
+ // Supports: custom schemes (myapp://), universal links (https://), app links, HTTP links
188
+ const initialDeepLink = await DeepLinkHelper.initialize((url, linkType) => {
189
+ try {
190
+ Logger.logDbg(`Deeplink detected (${linkType}):`, url);
191
+
192
+ // Store deeplink URL - will be attached to next page_load event
193
+ this.setDeepLinkUrl(url);
194
+
195
+ // Fire deeplink opened event
196
+ // URL params will be parsed and attached to page_load event automatically
197
+ this.recordEvent('deeplink_opened', {
198
+ deep_link_url: url,
199
+ link_type: linkType,
200
+ });
201
+ } catch (err) {
202
+ Logger.logError('Error processing deeplink:', err);
203
+ Reporter.reportError('deeplink_callback', err);
204
+ }
205
+ });
206
+
207
+ if (initialDeepLink) {
208
+ Logger.logDbg('App opened with initial deeplink:', initialDeepLink);
209
+ }
210
+
65
211
  // Initialize EEDL with any global event listeners
66
212
  await this.dl.init({});
67
213
 
214
+ // Set event deduplication window if specified (default is 5000ms)
215
+ if (typeof options.eventDeduplicationWindowMs !== 'undefined') {
216
+ this.dl.setDeduplicationWindow(options.eventDeduplicationWindowMs);
217
+ }
218
+
68
219
  // Make geo location API call after initialization
69
220
  try {
70
221
  await GeoLocationProcessor.makeGeoLocationApiCall();
@@ -81,15 +232,14 @@ class MagicPixelImpl {
81
232
  Logger.logDbg('Tracking Event:: ', eventName, 'with id:: ', eventId);
82
233
 
83
234
  if (DataStore.shouldExecuteTMForEvent(eventName)) {
84
- // if this is something the tag manager is interested in - fire tag manager
85
- DataStore.enQueueTMFire(
235
+ // Call runTM directly - no queuing, EEDL manages sequential processing
236
+ await this.runTM(
86
237
  false,
87
238
  `custom_event_${eventName}`,
88
239
  eventName,
89
240
  eventId,
90
241
  eventDataModel
91
242
  );
92
- await this.checkAndFireTM();
93
243
  }
94
244
  });
95
245
 
@@ -100,9 +250,38 @@ class MagicPixelImpl {
100
250
  }
101
251
 
102
252
  this.ready();
253
+
254
+ // Mark SDK as ready and flush any buffered events
255
+ this.isReady = true;
256
+ this.isInitializing = false;
257
+ Logger.logDbg('SDK is ready, flushing event buffer');
258
+ this.flushEventBuffer();
103
259
  }
104
260
 
105
261
  static recordEvent(eventName: string, payload: MapLike): void {
262
+ // Buffer event if SDK is not ready yet
263
+ if (!this.isReady) {
264
+ Logger.logDbg(`SDK not ready, buffering event: ${eventName}`);
265
+ // Evict oldest event if buffer at capacity
266
+ if (this.eventBuffer.length >= MAX_BUFFERED_EVENTS) {
267
+ const evicted = this.eventBuffer.shift();
268
+ Logger.logDbg(
269
+ `Event buffer at capacity (${MAX_BUFFERED_EVENTS}), evicted oldest:`,
270
+ evicted?.type === 'event' ? evicted.name : 'pageLoad'
271
+ );
272
+ }
273
+ this.eventBuffer.push({
274
+ type: 'event',
275
+ name: eventName,
276
+ data: payload,
277
+ });
278
+ return;
279
+ }
280
+
281
+ this.processRecordEvent(eventName, payload);
282
+ }
283
+
284
+ private static processRecordEvent(eventName: string, payload: MapLike): void {
106
285
  if (this.customerInfo) {
107
286
  const newPayload: MapLike = {
108
287
  ...payload,
@@ -123,42 +302,36 @@ class MagicPixelImpl {
123
302
  this.dl.ready();
124
303
  }
125
304
 
126
- static getDebugId(): string {
127
- return DataStore.getDebugId();
128
- }
305
+ /**
306
+ * Flush buffered events after SDK is ready
307
+ * Events are processed in the order they were received
308
+ */
309
+ private static flushEventBuffer(): void {
310
+ if (this.eventBuffer.length === 0) {
311
+ Logger.logDbg('No buffered events to flush');
312
+ return;
313
+ }
129
314
 
130
- private static async checkAndFireTM(): Promise<void> {
131
- try {
132
- if (!DataStore.isTagManagerProcessing()) {
133
- const item = DataStore.deQueueTMFire();
134
- if (item) {
135
- Logger.logDbg(
136
- 'Executing TM For: ',
137
- item.name,
138
- ', Event:: ',
139
- item.dcrName,
140
- ', Event id:: ',
141
- item.eventId,
142
- 'Payload:: ',
143
- item.dcrPayload
144
- );
145
- await this.runTM(
146
- item.sseOnly,
147
- item.name,
148
- item.dcrName,
149
- item.eventId,
150
- item.dcrPayload
151
- );
152
- }
153
- } else {
154
- Logger.logDbg(
155
- 'Tag Manager is processing or is not ready. This event will start after that'
156
- );
315
+ Logger.logDbg(`Flushing ${this.eventBuffer.length} buffered event(s)`);
316
+
317
+ // Process all buffered events in order
318
+ while (this.eventBuffer.length > 0) {
319
+ const event = this.eventBuffer.shift()!;
320
+
321
+ if (event.type === 'pageLoad') {
322
+ Logger.logDbg(`Processing buffered page load: ${event.data.page_name}`);
323
+ this.processRecordPageLoad(event.data);
324
+ } else if (event.type === 'event') {
325
+ Logger.logDbg(`Processing buffered event: ${event.name}`);
326
+ this.processRecordEvent(event.name!, event.data);
157
327
  }
158
- } catch (err) {
159
- Logger.logError('Error check and process tm', err);
160
- Reporter.reportError('m:checkAndFireTM', err);
161
328
  }
329
+
330
+ Logger.logDbg('Event buffer flushed');
331
+ }
332
+
333
+ static getDebugId(): string {
334
+ return DataStore.getDebugId();
162
335
  }
163
336
 
164
337
  private static async runTM(
@@ -178,9 +351,6 @@ class MagicPixelImpl {
178
351
  eventData
179
352
  );
180
353
 
181
- // set tag manager in process status
182
- DataStore.setTagManagerProcessing(true);
183
-
184
354
  // process all the data elements and cache them for this run
185
355
  await DataElementProcessor.processDataElements(
186
356
  DataStore.getSdkDataElements(),
@@ -200,7 +370,12 @@ class MagicPixelImpl {
200
370
  DataStore.setPrivacyCompliance(false);
201
371
  }
202
372
  } else {
203
- Logger.logDbg('Set: PR Comp: ', false);
373
+ // No privacy manager configured - default to true (tracking allowed)
374
+ Logger.logDbg(
375
+ 'Set: PR Comp: ',
376
+ true,
377
+ '(no privacy manager configured)'
378
+ );
204
379
  DataStore.setPrivacyCompliance(true);
205
380
  }
206
381
 
@@ -271,12 +446,12 @@ class MagicPixelImpl {
271
446
  ): void {
272
447
  // increment visit_depth if event name is page_load because that's the only way we can track page views in an app for now
273
448
  if (evtName === DL_INIT_EVENT) {
274
- VisitIdProcessor.incrementVisitDepth().finally(() => {
275
- this._fireTMPrivate(envName, envId, evtName, evtId);
276
- });
277
- } else {
278
- this._fireTMPrivate(envName, envId, evtName, evtId);
449
+ // Synchronous increment - no race condition possible
450
+ VisitIdProcessor.incrementVisitDepth();
279
451
  }
452
+
453
+ // Continue immediately after sync increment
454
+ this._fireTMPrivate(envName, envId, evtName, evtId);
280
455
  }
281
456
 
282
457
  private static _fireTMPrivate(
@@ -297,15 +472,8 @@ class MagicPixelImpl {
297
472
  Reporter.reportError('l::postSST', err);
298
473
  });
299
474
  }
300
-
301
- DataStore.setTagManagerProcessing(false);
302
- Utils.sleep(250).then(async () => {
303
- try {
304
- await this.checkAndFireTM();
305
- } catch (err) {
306
- Reporter.reportError('i::runTM', err);
307
- }
308
- });
475
+ // Note: EEDL handles queue progression via its own .finally() block
476
+ // No need to call processNext() here - it would be redundant
309
477
  }
310
478
 
311
479
  static setCustomerInfo(customerInfo: AppCustomerInfo): void {
@@ -321,7 +489,46 @@ class MagicPixelImpl {
321
489
  this.deepLinkUrl = deepLinkUrl;
322
490
  }
323
491
 
324
- static setDeviceInfo(deviceInfo: MpDeviceInfo): void {
492
+ /**
493
+ * Set device info with auto-detected values
494
+ * Standard fields (os_version, device_model_name, package_name) are always auto-detected
495
+ * Optional customFields parameter allows adding custom tracking fields only
496
+ * @param customFields Optional custom fields to add (cannot override standard fields)
497
+ */
498
+ static async setDeviceInfo(
499
+ customFields?: Record<string, any>
500
+ ): Promise<void> {
501
+ // Always auto-detect standard device info (cannot be overridden)
502
+ const autoDetectedInfo = await DeviceInfoHelper.getAppInfo();
503
+
504
+ // Standard fields are always auto-detected to prevent hardcoded values
505
+ const deviceInfo: MpDeviceInfo = {
506
+ os_version: autoDetectedInfo.os_version,
507
+ device_model_name: autoDetectedInfo.device_model_name,
508
+ package_name: autoDetectedInfo.package_name,
509
+ };
510
+
511
+ // Add any custom fields (if provided)
512
+ if (customFields) {
513
+ // Filter out attempts to override standard fields
514
+ const allowedCustomFields = { ...customFields };
515
+ delete allowedCustomFields.os_version;
516
+ delete allowedCustomFields.device_model_name;
517
+ delete allowedCustomFields.package_name;
518
+
519
+ Object.assign(deviceInfo, allowedCustomFields);
520
+
521
+ if (
522
+ Object.keys(allowedCustomFields).length !==
523
+ Object.keys(customFields).length
524
+ ) {
525
+ Logger.logDbg(
526
+ 'Warning: Attempted to override standard device fields. Using auto-detected values instead.'
527
+ );
528
+ }
529
+ }
530
+
531
+ Logger.logDbg('Setting device info:', deviceInfo);
325
532
  DataStore.setDeviceInfo(deviceInfo);
326
533
  }
327
534
 
@@ -336,6 +543,35 @@ class MagicPixelImpl {
336
543
  }
337
544
 
338
545
  static recordPageLoad(pageLoadInfo: AppPageLoad): void {
546
+ // Buffer page load if SDK is not ready yet
547
+ if (!this.isReady) {
548
+ Logger.logDbg(
549
+ `SDK not ready, buffering page load: ${pageLoadInfo.page_name}`
550
+ );
551
+ // Evict oldest event if buffer at capacity
552
+ if (this.eventBuffer.length >= MAX_BUFFERED_EVENTS) {
553
+ const evicted = this.eventBuffer.shift();
554
+ Logger.logDbg(
555
+ `Event buffer at capacity (${MAX_BUFFERED_EVENTS}), evicted oldest:`,
556
+ evicted?.type === 'event' ? evicted.name : 'pageLoad'
557
+ );
558
+ }
559
+ this.eventBuffer.push({
560
+ type: 'pageLoad',
561
+ data: { ...pageLoadInfo }, // Clone to preserve original data
562
+ });
563
+ return;
564
+ }
565
+
566
+ this.processRecordPageLoad(pageLoadInfo);
567
+ }
568
+
569
+ private static processRecordPageLoad(pageLoadInfo: AppPageLoad): void {
570
+ // Retry visitor ID fetch if it failed during init (fire and forget)
571
+ VisitIdProcessor.retryVisitorIdIfNeeded().catch((err) => {
572
+ Logger.logError('Error retrying visitor ID fetch:', err);
573
+ });
574
+
339
575
  pageLoadInfo.is_entry = this.firstAppLaunch ? 1 : 0;
340
576
 
341
577
  // Use stored deepLinkUrl if it exists AND either:
@@ -345,6 +581,30 @@ class MagicPixelImpl {
345
581
  pageLoadInfo.deep_link_url = this.deepLinkUrl;
346
582
  }
347
583
 
584
+ // Set page_url for attribution framework
585
+ // Priority: deep_link_url (if present) > constructed URL (v_<orgId>://<screen_name>)
586
+ // Using deep_link_url allows urlp data elements to extract attribution params (gclid, fbclid, etc.)
587
+ if (pageLoadInfo.deep_link_url) {
588
+ pageLoadInfo.page_url = pageLoadInfo.deep_link_url;
589
+ Logger.logDbg(
590
+ 'Setting page_url from deep_link_url:',
591
+ pageLoadInfo.page_url
592
+ );
593
+ } else if (this.orgId && pageLoadInfo.page_name) {
594
+ pageLoadInfo.page_url = `v_${this.orgId}://${pageLoadInfo.page_name}`;
595
+ Logger.logDbg(
596
+ 'Setting page_url from orgId/page_name:',
597
+ pageLoadInfo.page_url
598
+ );
599
+ } else {
600
+ Logger.logDbg(
601
+ 'Not setting page_url. No deep_link_url and orgId or page_name is missing. Org Id is: ',
602
+ this.orgId,
603
+ 'page_name is: ',
604
+ pageLoadInfo.page_name
605
+ );
606
+ }
607
+
348
608
  const deviceInfo = DataStore.getDeviceInfo();
349
609
 
350
610
  if (this.customerInfo) {
@@ -352,7 +612,9 @@ class MagicPixelImpl {
352
612
  ...pageLoadInfo,
353
613
  ...this.customerInfo,
354
614
  ...(this.customerIdentifiers ?? {}),
355
- ...Utils.parseQueryParamsToObject(pageLoadInfo?.deep_link_url),
615
+ page_params: Utils.parseQueryParamsToObject(
616
+ pageLoadInfo?.deep_link_url
617
+ ),
356
618
  };
357
619
 
358
620
  if (deviceInfo) {
@@ -364,7 +626,9 @@ class MagicPixelImpl {
364
626
  const newPayload: MapLike = {
365
627
  ...pageLoadInfo,
366
628
  ...(this.customerIdentifiers ?? {}),
367
- ...Utils.parseQueryParamsToObject(pageLoadInfo?.deep_link_url),
629
+ page_params: Utils.parseQueryParamsToObject(
630
+ pageLoadInfo?.deep_link_url
631
+ ),
368
632
  };
369
633
 
370
634
  if (deviceInfo) {
@@ -461,3 +725,4 @@ class MagicPixelImpl {
461
725
 
462
726
  export const MagicPixelEventBus = EventBus;
463
727
  export const MagicPixel = MagicPixelImpl;
728
+ export { DeepLinkHelper, DeepLinkType } from './common/deeplink-helper';
@@ -10,6 +10,14 @@ export interface ClientSdkDeItem {
10
10
  fn?: (deHelper: any, input: any) => any;
11
11
  isAsync?: boolean;
12
12
  fnTOut?: number;
13
+ stDur?: DeStorageDuration; // storage duration
14
+ }
15
+
16
+ export enum DeStorageDuration {
17
+ NONE = 'NONE',
18
+ EVENT = 'EVENT',
19
+ SESSION = 'SESSION',
20
+ VISITOR = 'VISITOR',
13
21
  }
14
22
 
15
23
  export enum ParamResourceEncodingType {
@@ -4,13 +4,22 @@
4
4
  */
5
5
  import { Utils } from '../common/utils';
6
6
  import type { DeJsvHelper, MapLike } from '../common/app-types';
7
+ import { DeStorageDuration } from '../models/mp-client-sdk';
7
8
  import type { ClientSdkDeItem } from '../models/mp-client-sdk';
8
9
  import { DataStore } from '../common/data-store';
9
10
  import { Logger } from '../common/logger';
11
+ import { StorageHelper } from '../common/storage-helper';
10
12
 
11
13
  // Note: JSV (JavaScript functions) are not supported in React Native
12
14
  // This timeout function is kept for potential future use
13
15
 
16
+ /**
17
+ * Check if a value is defined (not null, undefined, or empty string)
18
+ */
19
+ function isDefined(value: any): boolean {
20
+ return value !== null && value !== undefined && value !== '';
21
+ }
22
+
14
23
  const dElemProcessors: MapLike<
15
24
  (
16
25
  helper: DeJsvHelper,
@@ -86,7 +95,10 @@ const dElemProcessors: MapLike<
86
95
  _elem: ClientSdkDeItem,
87
96
  _eventData?: Record<string, any>
88
97
  ): any => {
89
- return DataStore.getVisitInfo()?.visitDepth ?? 0;
98
+ // Visit depth should start at 1 (first page), not 0
99
+ // Return at least 1 to ensure first page view shows depth = 1
100
+ const depth = DataStore.getVisitInfo()?.visitDepth ?? 0;
101
+ return Math.max(1, depth);
90
102
  },
91
103
  /* Browser/Platform data elements */
92
104
  pg_browser: (
@@ -96,14 +108,13 @@ const dElemProcessors: MapLike<
96
108
  ): any => {
97
109
  return DataStore.getDeviceOs(); // Using device OS as browser equivalent
98
110
  },
99
- /* User status data element - TODO: Implement getMpUserStatus in DataStore */
111
+ /* User status data element - 1 = new visitor (first visit), 0 = returning visitor */
100
112
  mp_new_vis: (
101
113
  _helper: DeJsvHelper,
102
114
  _elem: ClientSdkDeItem,
103
115
  _eventData?: Record<string, any>
104
116
  ): any => {
105
- // TODO: Implement user status functionality
106
- return '1'; // Default to new visitor
117
+ return DataStore.getMpUserStatus();
107
118
  },
108
119
  pg_random: (
109
120
  _helper: DeJsvHelper,
@@ -178,6 +189,55 @@ const dElemProcessors: MapLike<
178
189
  _elem.key = 'ev._eventName';
179
190
  return processMpDl(_helper, _elem, _eventData);
180
191
  },
192
+ pg_hash: (
193
+ _helper: DeJsvHelper,
194
+ _elem: ClientSdkDeItem,
195
+ _eventData?: Record<string, any>
196
+ ): any => {
197
+ return '';
198
+ },
199
+ pg_title: (
200
+ _helper: DeJsvHelper,
201
+ _elem: ClientSdkDeItem,
202
+ _eventData?: Record<string, any>
203
+ ): any => {
204
+ return '';
205
+ },
206
+ pg_params: (
207
+ _helper: DeJsvHelper,
208
+ _elem: ClientSdkDeItem,
209
+ _eventData?: Record<string, any>
210
+ ): any => {
211
+ return '';
212
+ },
213
+ pg_domain: (
214
+ _helper: DeJsvHelper,
215
+ _elem: ClientSdkDeItem,
216
+ _eventData?: Record<string, any>
217
+ ): any => {
218
+ return '';
219
+ },
220
+ pg_srch: (
221
+ _helper: DeJsvHelper,
222
+ _elem: ClientSdkDeItem,
223
+ _eventData?: Record<string, any>
224
+ ): any => {
225
+ return '';
226
+ },
227
+ pg_referrer: (
228
+ _helper: DeJsvHelper,
229
+ _elem: ClientSdkDeItem,
230
+ _eventData?: Record<string, any>
231
+ ): any => {
232
+ return '';
233
+ },
234
+ pg_url: (
235
+ _helper: DeJsvHelper,
236
+ _elem: ClientSdkDeItem,
237
+ _eventData?: Record<string, any>
238
+ ): any => {
239
+ return '';
240
+ },
181
241
  // Note: JSV (JavaScript functions) processor removed - not supported in React Native
182
242
  };
183
243
 
@@ -238,14 +298,32 @@ export async function processOneDataElement(
238
298
  try {
239
299
  if (dElemProcessors[deItem.typ]) {
240
300
  Logger.logDbg('Processing data element: ', deItem.nm);
241
- const deValue = await dElemProcessors[deItem.typ](
301
+ let deValue = await dElemProcessors[deItem.typ](
242
302
  deHelper,
243
303
  deItem,
244
304
  eventData
245
305
  );
246
306
 
247
- // TODO: Implement storage duration functionality for React Native
248
- // For now, we'll just return the value without persistence
307
+ // Default storage duration to EVENT if not defined
308
+ if (!deItem.stDur) {
309
+ deItem.stDur = DeStorageDuration.EVENT;
310
+ }
311
+
312
+ // If data element has persistence enabled, store the value
313
+ StorageHelper.deStorageDurationSet(deItem.key, deValue, deItem.stDur);
314
+
315
+ // If value is not defined, try to retrieve from storage
316
+ // This allows values like gclid to persist across page loads
317
+ if (!isDefined(deValue)) {
318
+ const storedValue = StorageHelper.deStorageDurationGet(
319
+ deItem.key,
320
+ deItem.def,
321
+ deItem.stDur
322
+ );
323
+ if (isDefined(storedValue)) {
324
+ deValue = storedValue;
325
+ }
326
+ }
249
327
 
250
328
  return deValue;
251
329
  } else {