@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.
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 -15
  14. package/lib/commonjs/common/network-service.js.map +1 -1
  15. package/lib/commonjs/common/reporter.js +75 -14
  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 +62 -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 +301 -54
  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 -15
  43. package/lib/module/common/network-service.js.map +1 -1
  44. package/lib/module/common/reporter.js +76 -14
  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 +63 -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 +290 -53
  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 +30 -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/{common → src/common}/reporter.d.ts +2 -1
  68. package/lib/typescript/src/common/storage-helper.d.ts +47 -0
  69. package/lib/typescript/{common → src/common}/utils.d.ts +25 -0
  70. package/lib/typescript/{eedl → src/eedl}/eedl.d.ts +43 -1
  71. package/lib/typescript/{index.d.ts → src/index.d.ts} +39 -5
  72. package/lib/typescript/{models → src/models}/mp-client-sdk.d.ts +7 -0
  73. package/lib/typescript/src/processors/visit-id.processor.d.ts +23 -0
  74. package/package.json +25 -36
  75. package/src/common/app-types.ts +33 -10
  76. package/src/common/constants.ts +0 -6
  77. package/src/common/data-store.ts +8 -30
  78. package/src/common/deeplink-helper.ts +181 -0
  79. package/src/common/device-info-helper.ts +190 -0
  80. package/src/common/event-bus.ts +39 -0
  81. package/src/common/network-service.ts +154 -21
  82. package/src/common/reporter.ts +97 -16
  83. package/src/common/storage-helper.ts +260 -0
  84. package/src/common/utils.ts +63 -2
  85. package/src/eedl/eedl.ts +225 -51
  86. package/src/index.tsx +346 -73
  87. package/src/models/mp-client-sdk.ts +8 -0
  88. package/src/processors/data-element.processor.ts +85 -7
  89. package/src/processors/visit-id.processor.ts +92 -22
  90. package/lib/commonjs/processors/trans-function.processor.js +0 -73
  91. package/lib/commonjs/processors/trans-function.processor.js.map +0 -1
  92. package/lib/module/processors/trans-function.processor.js +0 -66
  93. package/lib/module/processors/trans-function.processor.js.map +0 -1
  94. package/lib/typescript/common/event-bus.d.ts +0 -6
  95. package/lib/typescript/common/network-service.d.ts +0 -8
  96. package/lib/typescript/processors/trans-function.processor.d.ts +0 -12
  97. package/lib/typescript/processors/visit-id.processor.d.ts +0 -9
  98. package/src/processors/trans-function.processor.ts +0 -85
  99. /package/lib/typescript/{common → src/common}/logger.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
@@ -16,6 +16,45 @@ export class EventBus {
16
16
  }
17
17
  }
18
18
 
19
+ /**
20
+ * Remove a specific callback for an event
21
+ * @param eventName Event name to remove callback from
22
+ * @param callback The callback function to remove
23
+ */
24
+ static off(eventName: string, callback: EventBusCallback): void {
25
+ if (this.eventRegister.has(eventName)) {
26
+ const callbacks = this.eventRegister.get(eventName);
27
+ const index = callbacks.indexOf(callback);
28
+ if (index > -1) {
29
+ callbacks.splice(index, 1);
30
+ Logger.logDbg(`Removed listener for event: ${eventName}`);
31
+ }
32
+ // Clean up empty arrays
33
+ if (callbacks.length === 0) {
34
+ this.eventRegister.delete(eventName);
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Remove all callbacks for a specific event
41
+ * @param eventName Event name to clear all listeners for
42
+ */
43
+ static clearEvent(eventName: string): void {
44
+ if (this.eventRegister.has(eventName)) {
45
+ this.eventRegister.delete(eventName);
46
+ Logger.logDbg(`Cleared all listeners for event: ${eventName}`);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Remove all event listeners (used during SDK shutdown)
52
+ */
53
+ static clearAll(): void {
54
+ this.eventRegister.clear();
55
+ Logger.logDbg('Cleared all event listeners');
56
+ }
57
+
19
58
  static triggerEvent(eventName: string, payload: any): void {
20
59
  if (this.eventRegister.has(eventName)) {
21
60
  for (const cb of this.eventRegister.get(eventName)) {
@@ -1,40 +1,173 @@
1
1
  import { Logger } from './logger';
2
2
  import type { MapLike, SdkInitOptions, VisitorInfo } from './app-types';
3
3
  import { DataStore } from './data-store';
4
- import { Reporter } from './reporter';
5
4
  import type { MpClientSdk } from '../models/mp-client-sdk';
6
5
 
6
+ const DEFAULT_RETRY_COUNT = 3;
7
+ const DEFAULT_RETRY_DELAY_MS = 1000;
8
+ const DEFAULT_TIMEOUT_MS = 10000;
9
+
7
10
  export class NetworkService {
8
- public static async fetchIdlInfo(url: string): Promise<VisitorInfo> {
9
- const response = await NetworkService.sendNetworkRequest('get', url);
10
- if (response) {
11
- return response.json();
12
- } else {
11
+ /**
12
+ * Sleep for a specified duration
13
+ */
14
+ private static sleep(ms: number): Promise<void> {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
17
+
18
+ /**
19
+ * Fetch with timeout wrapper
20
+ */
21
+ private static async fetchWithTimeout(
22
+ url: string,
23
+ options: RequestInit,
24
+ timeoutMs: number = DEFAULT_TIMEOUT_MS
25
+ ): Promise<Response> {
26
+ const controller = new AbortController();
27
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
28
+
29
+ try {
30
+ const response = await fetch(url, {
31
+ ...options,
32
+ signal: controller.signal,
33
+ });
34
+ return response;
35
+ } finally {
36
+ clearTimeout(timeoutId);
37
+ }
38
+ }
39
+ /**
40
+ * Fetch visitor/identity info from server
41
+ * Returns null if fetch fails (caller should handle retry logic)
42
+ */
43
+ public static async fetchIdlInfo(url: string): Promise<VisitorInfo | null> {
44
+ try {
45
+ const response = await this.fetchWithTimeout(
46
+ url,
47
+ {
48
+ method: 'GET',
49
+ headers: {
50
+ 'Content-Type': 'text/plain',
51
+ 'cache-control': 'no-store',
52
+ ...DataStore.getCommonCookies(),
53
+ },
54
+ },
55
+ DEFAULT_TIMEOUT_MS
56
+ );
57
+
58
+ if (response && response.ok) {
59
+ return response.json();
60
+ } else {
61
+ Logger.logError(
62
+ `Failed to fetch IDL info: ${response?.status} ${response?.statusText}`
63
+ );
64
+ return null;
65
+ }
66
+ } catch (err) {
67
+ Logger.logError('Error fetching IDL info:', err);
13
68
  return null;
14
69
  }
15
70
  }
16
71
 
72
+ /**
73
+ * Fetch visitor/identity info with retry logic
74
+ * @param url The IDL URL
75
+ * @param retries Number of retries (default: 3)
76
+ * @returns VisitorInfo or null if all retries fail
77
+ */
78
+ public static async fetchIdlInfoWithRetry(
79
+ url: string,
80
+ retries: number = DEFAULT_RETRY_COUNT
81
+ ): Promise<VisitorInfo | null> {
82
+ let lastError: Error | null = null;
83
+
84
+ for (let attempt = 1; attempt <= retries; attempt++) {
85
+ try {
86
+ Logger.logDbg(`Fetching IDL info (attempt ${attempt}/${retries})`);
87
+ const result = await this.fetchIdlInfo(url);
88
+
89
+ if (result) {
90
+ Logger.logDbg('IDL info fetched successfully');
91
+ return result;
92
+ }
93
+ } catch (err) {
94
+ lastError = err instanceof Error ? err : new Error(String(err));
95
+ Logger.logError(`IDL fetch attempt ${attempt} failed:`, err);
96
+ }
97
+
98
+ // Wait before retry (exponential backoff)
99
+ if (attempt < retries) {
100
+ const delay = DEFAULT_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
101
+ Logger.logDbg(`Retrying IDL fetch in ${delay}ms...`);
102
+ await this.sleep(delay);
103
+ }
104
+ }
105
+
106
+ Logger.logError(`All ${retries} IDL fetch attempts failed`, lastError);
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * Refresh client SDK JSON configuration with retry logic
112
+ * @param sdkInitOptions SDK initialization options
113
+ * @returns true if successful, throws Error if all retries fail
114
+ */
17
115
  public static async refreshClientSdkJson(
18
116
  sdkInitOptions: SdkInitOptions
19
117
  ): Promise<void> {
20
- try {
21
- const response = await this.sendNetworkRequest(
22
- 'get',
23
- `${sdkInitOptions.baseUrl}/${sdkInitOptions.projectId}${
24
- sdkInitOptions.env === 'staging' ? '-staging' : ''
25
- }.json`
26
- );
118
+ const url = `${sdkInitOptions.baseUrl}/${sdkInitOptions.projectId}${
119
+ sdkInitOptions.env === 'staging' ? '-staging' : ''
120
+ }.json`;
121
+
122
+ let lastError: Error | null = null;
27
123
 
28
- if (response) {
29
- const sdkJson: MpClientSdk = await response.json();
30
- Logger.logDbg('Client Sdk:: ', sdkJson);
31
- // sdkJson.s.c_url = 'http://localhost:3003';
32
- await DataStore.init(sdkJson);
124
+ for (let attempt = 1; attempt <= DEFAULT_RETRY_COUNT; attempt++) {
125
+ try {
126
+ Logger.logDbg(
127
+ `Fetching SDK config (attempt ${attempt}/${DEFAULT_RETRY_COUNT}): ${url}`
128
+ );
129
+
130
+ const response = await this.fetchWithTimeout(
131
+ url,
132
+ {
133
+ method: 'GET',
134
+ headers: {
135
+ 'Content-Type': 'text/plain',
136
+ 'cache-control': 'no-store',
137
+ },
138
+ },
139
+ DEFAULT_TIMEOUT_MS
140
+ );
141
+
142
+ if (response && response.ok) {
143
+ const sdkJson: MpClientSdk = await response.json();
144
+ Logger.logDbg('Client SDK config fetched successfully');
145
+ await DataStore.init(sdkJson);
146
+ return; // Success - exit the retry loop
147
+ } else {
148
+ throw new Error(
149
+ `HTTP ${response?.status}: ${
150
+ response?.statusText || 'Unknown error'
151
+ }`
152
+ );
153
+ }
154
+ } catch (err) {
155
+ lastError = err instanceof Error ? err : new Error(String(err));
156
+ Logger.logError(`SDK config fetch attempt ${attempt} failed:`, err);
157
+
158
+ // Wait before retry (exponential backoff)
159
+ if (attempt < DEFAULT_RETRY_COUNT) {
160
+ const delay = DEFAULT_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
161
+ Logger.logDbg(`Retrying SDK config fetch in ${delay}ms...`);
162
+ await this.sleep(delay);
163
+ }
33
164
  }
34
- } catch (err) {
35
- Reporter.reportError('refreshClientSdk', err);
36
- // throw new Error('Unable to fetch MagicPixel SDK at this time.');
37
165
  }
166
+
167
+ // All retries failed - throw error so caller can handle it
168
+ throw new Error(
169
+ `Failed to fetch SDK configuration after ${DEFAULT_RETRY_COUNT} attempts: ${lastError?.message}`
170
+ );
38
171
  }
39
172
 
40
173
  public static sendPostRequest(url: string, body: MapLike): void {
@@ -4,6 +4,9 @@ import { DataStore } from './data-store';
4
4
  import { Utils } from './utils';
5
5
  import { NetworkService } from './network-service';
6
6
 
7
+ // Maximum number of reports to keep in memory
8
+ const MAX_REPORTS = 500;
9
+
7
10
  export class Reporter {
8
11
  static reportMap: MapLike<Report> = {};
9
12
 
@@ -20,6 +23,20 @@ export class Reporter {
20
23
  clientSdkVersion: string,
21
24
  initialItems: ReportTagItem[] = []
22
25
  ): void {
26
+ // Evict oldest reports if at capacity
27
+ const reportKeys = Object.keys(this.reportMap);
28
+ if (reportKeys.length >= MAX_REPORTS) {
29
+ // Remove oldest report (first key in object)
30
+ const oldestKey = reportKeys[0];
31
+ if (oldestKey) {
32
+ delete this.reportMap[oldestKey];
33
+ Logger.logDbg(
34
+ `Report map at capacity (${MAX_REPORTS}), evicted oldest report:`,
35
+ oldestKey
36
+ );
37
+ }
38
+ }
39
+
23
40
  this.reportMap[evtId] = {
24
41
  envName,
25
42
  envId,
@@ -44,6 +61,16 @@ export class Reporter {
44
61
  }
45
62
  }
46
63
 
64
+ static reportDevPayload(
65
+ devPayload: Record<string, any>,
66
+ evtId: string
67
+ ): void {
68
+ const reportParent = this.reportMap[evtId];
69
+ if (reportParent) {
70
+ reportParent.devPayload = Utils.encodeToBase64(devPayload);
71
+ }
72
+ }
73
+
47
74
  static reportItem(item: ReportTagItem, evtId: string): void {
48
75
  const reportParent = this.reportMap[evtId];
49
76
  if (reportParent) {
@@ -59,13 +86,44 @@ export class Reporter {
59
86
  }
60
87
 
61
88
  static publishReport(evtId: string): void {
62
- const data = (this.reportMap[evtId] || {}) as MapLike;
89
+ const report = this.reportMap[evtId];
90
+ if (!report) {
91
+ Logger.logError(`No report found for evtId: ${evtId}`);
92
+ return;
93
+ }
94
+
95
+ const data = report as MapLike;
96
+ data.debugId = DataStore.getDebugId();
97
+
98
+ // Include base64 encoded developer payload
99
+ if (report.devPayload) {
100
+ data.devPayload = report.devPayload;
101
+ }
102
+
103
+ // Check payload size to prevent DoS attacks (max 250KB)
104
+ if (Utils.isPayloadOversized(data)) {
105
+ Logger.logError('Report payload exceeds 250KB limit, dropping event');
106
+ this.reportError(
107
+ 'm::publishReport',
108
+ new Error('Payload size exceeds 250KB limit')
109
+ );
110
+ delete this.reportMap[evtId];
111
+ return;
112
+ }
113
+
114
+ // Build URL with event tracking params
115
+ const eventParams = `eid=${encodeURIComponent(
116
+ evtId
117
+ )}&enm=${encodeURIComponent(data.evt ?? '')}`;
63
118
  const postUrl = `${DataStore.getUrlInfo().rptUrl}/${
64
119
  data.dws ?? 'n'
65
- }/${Utils.getUniqueID()}`;
120
+ }/${Utils.getUniqueID()}?${eventParams}`;
66
121
  Logger.logDbg('Posting Info: ', postUrl);
67
- data.debugId = DataStore.getDebugId();
122
+
68
123
  NetworkService.sendPostRequest(postUrl, data);
124
+
125
+ // Clean up report from memory after publishing
126
+ delete this.reportMap[evtId];
69
127
  }
70
128
 
71
129
  static reportError(methodMetaData: string, err: any): void {
@@ -88,20 +146,43 @@ export class Reporter {
88
146
  envId: string,
89
147
  serverTagDownStream: 'j' | 'n',
90
148
  eventName: string,
91
- evtId: string
149
+ evtId: string,
150
+ eventData?: Record<string, any>
92
151
  ): Promise<void> {
93
- data.env = envName;
94
- data.envId = envId;
95
- data.evtName = eventName;
96
- data.dws = serverTagDownStream;
97
- data.debugId = DataStore.getDebugId();
98
- data.dm = 'n/a';
99
- data.v = DataStore.getCoreVersion();
100
- // return postHttp(`${getUrlInfo().tfUrl}/${getUniqueID()}`, 'POST', data);
101
- NetworkService.sendPostRequest(
102
- `${DataStore.getSSTUrl()}/${data.dws ?? 'n'}/${evtId}`,
103
- data
104
- );
152
+ // Clone data to avoid mutating the shared dataElements object
153
+ const payload: MapLike = { ...data };
154
+ payload.env = envName;
155
+ payload.envId = envId;
156
+ payload.evtName = eventName;
157
+ payload.dws = serverTagDownStream;
158
+ payload.debugId = DataStore.getDebugId();
159
+ payload.dm = 'n/a';
160
+ payload.v = DataStore.getCoreVersion();
161
+
162
+ // Add base64 encoded developer payload
163
+ if (eventData) {
164
+ payload.devPayload = Utils.encodeToBase64(eventData);
165
+ }
166
+
167
+ // Check payload size to prevent DoS attacks (max 250KB)
168
+ if (Utils.isPayloadOversized(payload)) {
169
+ Logger.logError('SST payload exceeds 250KB limit, dropping event');
170
+ this.reportError(
171
+ 'm::postSST',
172
+ new Error('Payload size exceeds 250KB limit')
173
+ );
174
+ return;
175
+ }
176
+
177
+ // Build URL with event tracking params
178
+ const eventParams = `eid=${encodeURIComponent(
179
+ evtId
180
+ )}&enm=${encodeURIComponent(eventName)}`;
181
+ const postPath = `${DataStore.getSSTUrl()}/${
182
+ payload.dws ?? 'n'
183
+ }/${Utils.getUniqueID()}?${eventParams}`;
184
+
185
+ NetworkService.sendPostRequest(postPath, payload);
105
186
  }
106
187
 
107
188
  static postAttrInfo(payload: MapLike): void {
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Storage helper for data element storage duration
3
+ * Handles SESSION (in-memory) and VISITOR (AsyncStorage with 30-day expiry) storage
4
+ */
5
+ import AsyncStorage from '@react-native-async-storage/async-storage';
6
+ import { DeStorageDuration } from '../models/mp-client-sdk';
7
+ import { Logger } from './logger';
8
+
9
+ // AsyncStorage key for visitor-scoped data elements
10
+ const VISITOR_STORE_KEY = '_mp_de_visitor_store';
11
+
12
+ // 30 days in milliseconds
13
+ const VISITOR_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000;
14
+
15
+ // Interface for stored visitor values (includes timestamp for expiry check)
16
+ interface StoredVisitorValue {
17
+ value: any;
18
+ timestamp: number;
19
+ }
20
+
21
+ export class StorageHelper {
22
+ // In-memory store for SESSION-scoped values (cleared on app restart)
23
+ private static sessionStore: Record<string, any> = {};
24
+
25
+ // In-memory cache of visitor store (loaded from AsyncStorage on init)
26
+ private static visitorStoreCache: Record<string, StoredVisitorValue> = {};
27
+
28
+ // Flag to track if initialized
29
+ private static isInitialized = false;
30
+
31
+ /**
32
+ * Initialize the storage helper
33
+ * Loads visitor store from AsyncStorage and cleans expired values
34
+ * Should be called once during SDK initialization
35
+ */
36
+ static async initialize(): Promise<void> {
37
+ if (this.isInitialized) {
38
+ return;
39
+ }
40
+
41
+ try {
42
+ // Load visitor store from AsyncStorage
43
+ const storedData = await AsyncStorage.getItem(VISITOR_STORE_KEY);
44
+ if (storedData) {
45
+ this.visitorStoreCache = JSON.parse(storedData);
46
+ Logger.logDbg(
47
+ 'StorageHelper: Loaded visitor store with',
48
+ Object.keys(this.visitorStoreCache).length,
49
+ 'items'
50
+ );
51
+ }
52
+
53
+ // Clean expired visitor data
54
+ await this.clearExpiredVisitorData();
55
+
56
+ this.isInitialized = true;
57
+ Logger.logDbg('StorageHelper: Initialized successfully');
58
+ } catch (err) {
59
+ Logger.logError('StorageHelper: Error initializing', err);
60
+ // Reset to empty state on error
61
+ this.visitorStoreCache = {};
62
+ this.isInitialized = true;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Clear visitor-scoped values older than 30 days
68
+ */
69
+ static async clearExpiredVisitorData(): Promise<void> {
70
+ try {
71
+ const now = Date.now();
72
+ const keysToRemove: string[] = [];
73
+
74
+ for (const key of Object.keys(this.visitorStoreCache)) {
75
+ const entry = this.visitorStoreCache[key];
76
+ if (entry && now - entry.timestamp > VISITOR_EXPIRY_MS) {
77
+ keysToRemove.push(key);
78
+ }
79
+ }
80
+
81
+ if (keysToRemove.length > 0) {
82
+ for (const key of keysToRemove) {
83
+ delete this.visitorStoreCache[key];
84
+ }
85
+
86
+ // Persist the cleaned store
87
+ await this.persistVisitorStore();
88
+
89
+ Logger.logDbg(
90
+ 'StorageHelper: Cleared',
91
+ keysToRemove.length,
92
+ 'expired visitor values'
93
+ );
94
+ }
95
+ } catch (err) {
96
+ Logger.logError('StorageHelper: Error clearing expired data', err);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Persist the visitor store cache to AsyncStorage
102
+ */
103
+ private static async persistVisitorStore(): Promise<void> {
104
+ try {
105
+ await AsyncStorage.setItem(
106
+ VISITOR_STORE_KEY,
107
+ JSON.stringify(this.visitorStoreCache)
108
+ );
109
+ } catch (err) {
110
+ Logger.logError('StorageHelper: Error persisting visitor store', err);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Check if a value is defined (not null, undefined, or empty string)
116
+ */
117
+ private static isDefined(value: any): boolean {
118
+ return value !== null && value !== undefined && value !== '';
119
+ }
120
+
121
+ /**
122
+ * Store a data element value based on storage duration
123
+ * @param deKey - The data element key
124
+ * @param value - The value to store
125
+ * @param storageDuration - The storage duration type
126
+ */
127
+ static deStorageDurationSet(
128
+ deKey: string,
129
+ value: any,
130
+ storageDuration: DeStorageDuration
131
+ ): void {
132
+ try {
133
+ if (!this.isDefined(deKey) || !this.isDefined(value)) {
134
+ // Key and value are required
135
+ return;
136
+ }
137
+
138
+ if (!storageDuration) {
139
+ storageDuration = DeStorageDuration.EVENT;
140
+ }
141
+
142
+ switch (storageDuration) {
143
+ case DeStorageDuration.SESSION:
144
+ // Store in memory (cleared on app restart)
145
+ this.sessionStore[deKey] = value;
146
+ Logger.logDbg('StorageHelper: Stored SESSION value for', deKey);
147
+ break;
148
+
149
+ case DeStorageDuration.VISITOR:
150
+ // Store in AsyncStorage cache with timestamp
151
+ this.visitorStoreCache[deKey] = {
152
+ value: value,
153
+ timestamp: Date.now(),
154
+ };
155
+ // Persist to AsyncStorage in background
156
+ this.persistVisitorStore().catch((err) => {
157
+ Logger.logError(
158
+ 'StorageHelper: Error persisting visitor value',
159
+ err
160
+ );
161
+ });
162
+ Logger.logDbg('StorageHelper: Stored VISITOR value for', deKey);
163
+ break;
164
+
165
+ case DeStorageDuration.EVENT:
166
+ case DeStorageDuration.NONE:
167
+ default:
168
+ // No persistence needed for EVENT or NONE
169
+ break;
170
+ }
171
+ } catch (err) {
172
+ Logger.logError('StorageHelper: Error in deStorageDurationSet', err);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Retrieve a data element value based on storage duration
178
+ * @param deKey - The data element key
179
+ * @param defaultValue - Default value if not found
180
+ * @param storageDuration - The storage duration type
181
+ * @returns The stored value or default value
182
+ */
183
+ static deStorageDurationGet(
184
+ deKey: string,
185
+ defaultValue: any,
186
+ storageDuration: DeStorageDuration
187
+ ): any {
188
+ try {
189
+ if (!this.isDefined(deKey)) {
190
+ return defaultValue;
191
+ }
192
+
193
+ if (!storageDuration) {
194
+ storageDuration = DeStorageDuration.EVENT;
195
+ }
196
+
197
+ switch (storageDuration) {
198
+ case DeStorageDuration.SESSION: {
199
+ // Get from in-memory session store
200
+ const sessionValue = this.sessionStore[deKey];
201
+ if (this.isDefined(sessionValue)) {
202
+ return sessionValue;
203
+ }
204
+ return defaultValue;
205
+ }
206
+
207
+ case DeStorageDuration.VISITOR: {
208
+ // Get from visitor store cache
209
+ const visitorEntry = this.visitorStoreCache[deKey];
210
+ if (visitorEntry && this.isDefined(visitorEntry.value)) {
211
+ // Check if not expired
212
+ if (Date.now() - visitorEntry.timestamp <= VISITOR_EXPIRY_MS) {
213
+ return visitorEntry.value;
214
+ } else {
215
+ // Value is expired, remove it
216
+ delete this.visitorStoreCache[deKey];
217
+ this.persistVisitorStore().catch((err) => {
218
+ Logger.logError(
219
+ 'StorageHelper: Error removing expired value',
220
+ err
221
+ );
222
+ });
223
+ }
224
+ }
225
+ return defaultValue;
226
+ }
227
+
228
+ case DeStorageDuration.EVENT:
229
+ case DeStorageDuration.NONE:
230
+ default:
231
+ // No retrieval for EVENT or NONE
232
+ return defaultValue;
233
+ }
234
+ } catch (err) {
235
+ Logger.logError('StorageHelper: Error in deStorageDurationGet', err);
236
+ return defaultValue;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Clear all session-scoped values (called on new session if needed)
242
+ */
243
+ static clearSessionStore(): void {
244
+ this.sessionStore = {};
245
+ Logger.logDbg('StorageHelper: Cleared session store');
246
+ }
247
+
248
+ /**
249
+ * Clear all visitor-scoped values
250
+ */
251
+ static async clearVisitorStore(): Promise<void> {
252
+ try {
253
+ this.visitorStoreCache = {};
254
+ await AsyncStorage.removeItem(VISITOR_STORE_KEY);
255
+ Logger.logDbg('StorageHelper: Cleared visitor store');
256
+ } catch (err) {
257
+ Logger.logError('StorageHelper: Error clearing visitor store', err);
258
+ }
259
+ }
260
+ }