iidrak-analytics-react 1.3.0 → 1.5.0
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
CHANGED
|
@@ -18,8 +18,13 @@ This package relies on several peer dependencies for device info, storage, and s
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install iidrak-analytics-react
|
|
20
20
|
|
|
21
|
-
# Install required peer dependencies
|
|
22
|
-
npm install react-native-
|
|
21
|
+
# Install required peer dependencies. For storage, choose ONE: MMKV or AsyncStorage
|
|
22
|
+
npm install react-native-nitro-modules react-native-mmkv
|
|
23
|
+
# OR
|
|
24
|
+
npm install @react-native-async-storage/async-storage
|
|
25
|
+
|
|
26
|
+
# Install core peer dependencies
|
|
27
|
+
npm install @react-native-community/netinfo react-native-device-info react-native-view-shot react-native-safe-area-context
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
**iOS Users**: after installing, remember to run:
|
|
@@ -44,6 +49,13 @@ The configuration object requires strict structure for `app`, `config`, and `use
|
|
|
44
49
|
```javascript
|
|
45
50
|
import { MetaStreamIO, MetaStreamProvider } from 'iidrak-analytics-react';
|
|
46
51
|
|
|
52
|
+
// Recommended: Import your preferred storage engine
|
|
53
|
+
import { createMMKV } from 'react-native-mmkv';
|
|
54
|
+
const mmkvStorage = createMMKV();
|
|
55
|
+
|
|
56
|
+
// Alternatively, import AsyncStorage
|
|
57
|
+
// import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
58
|
+
|
|
47
59
|
const iidrakConfig = {
|
|
48
60
|
// Application Details
|
|
49
61
|
app: {
|
|
@@ -70,6 +82,17 @@ const iidrakConfig = {
|
|
|
70
82
|
sessionLength: 1800, // Session timeout in seconds (e.g. 30 mins)
|
|
71
83
|
quality: 0.1, // Recording quality (0.1 - 1.0)
|
|
72
84
|
fps: 2, // Frames Per Second for recording
|
|
85
|
+
|
|
86
|
+
// Provide the storage engine to the SDK.
|
|
87
|
+
// Why wrap MMKV? The SDK strictly expects an asynchronous `getItem/setItem` Interface.
|
|
88
|
+
// MMKV v4 is completely synchronous C++. This wrapper bridges MMKV into a standard Promise architecture.
|
|
89
|
+
storageEngine: {
|
|
90
|
+
getItem: async (key) => mmkvStorage.getString(key) || null,
|
|
91
|
+
setItem: async (key, value) => { mmkvStorage.set(key, value); return Promise.resolve(); },
|
|
92
|
+
removeItem: async (key) => { mmkvStorage.remove(key); return Promise.resolve(); }
|
|
93
|
+
},
|
|
94
|
+
// For AsyncStorage, no wrapper is needed because it natively uses Promises:
|
|
95
|
+
// storageEngine: AsyncStorage,
|
|
73
96
|
},
|
|
74
97
|
|
|
75
98
|
// Initial User Info
|
|
@@ -56,82 +56,87 @@ class MetaStreamIOEnvironment {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async logAppInfo() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
if (this._appInfoCached) return this._appInfoCached;
|
|
60
|
+
if (this._appInfoPromise) return this._appInfoPromise;
|
|
61
|
+
|
|
62
|
+
this._appInfoPromise = (async () => {
|
|
63
|
+
let app_id = null;
|
|
64
|
+
let first_installation_time = null;
|
|
65
|
+
let installation_id = null;
|
|
66
|
+
let install_referrer = null;
|
|
67
|
+
let last_update_time = null;
|
|
68
|
+
let version = null;
|
|
69
|
+
|
|
70
|
+
app_id = DeviceInfo.getBundleId() ? DeviceInfo.getBundleId() : null;
|
|
71
|
+
|
|
72
|
+
first_installation_time = await DeviceInfo.getFirstInstallTime()
|
|
73
|
+
.then((result) => {
|
|
74
|
+
if (this.date.validateDatetime(result)) {
|
|
75
|
+
return result;
|
|
76
|
+
} else {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
this.logger.warn(
|
|
82
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
83
|
+
err
|
|
84
|
+
);
|
|
73
85
|
return null;
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
.catch((err) => {
|
|
77
|
-
this.logger.warn(
|
|
78
|
-
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
79
|
-
err
|
|
80
|
-
);
|
|
81
|
-
return null;
|
|
82
|
-
});
|
|
86
|
+
});
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
install_referrer = await DeviceInfo.getInstallReferrer()
|
|
87
|
-
.then((result) => {
|
|
88
|
-
return result;
|
|
89
|
-
})
|
|
90
|
-
.catch((err) => {
|
|
91
|
-
this.logger.warn(
|
|
92
|
-
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
93
|
-
err
|
|
94
|
-
);
|
|
95
|
-
return null;
|
|
96
|
-
});
|
|
88
|
+
installation_id = "deprecated"; // Constants.installationId;
|
|
97
89
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (this.date.validateDatetime(result)) {
|
|
90
|
+
install_referrer = await DeviceInfo.getInstallReferrer()
|
|
91
|
+
.then((result) => {
|
|
101
92
|
return result;
|
|
102
|
-
}
|
|
93
|
+
})
|
|
94
|
+
.catch((err) => {
|
|
95
|
+
this.logger.warn(
|
|
96
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
97
|
+
err
|
|
98
|
+
);
|
|
103
99
|
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
last_update_time: last_update_time,
|
|
122
|
-
version: version,
|
|
123
|
-
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
last_update_time = await DeviceInfo.getLastUpdateTime()
|
|
103
|
+
.then((result) => {
|
|
104
|
+
if (this.date.validateDatetime(result)) {
|
|
105
|
+
return result;
|
|
106
|
+
} else {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => {
|
|
111
|
+
this.logger.warn(
|
|
112
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
113
|
+
err
|
|
114
|
+
);
|
|
115
|
+
return null;
|
|
116
|
+
});
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
127
|
-
this.constant.MetaStreamIO_Environment_AppInfo.format(
|
|
128
|
-
JSON.stringify(this.app_info)
|
|
129
|
-
)
|
|
130
|
-
);
|
|
118
|
+
version = DeviceInfo.getVersion() ? DeviceInfo.getVersion() : null;
|
|
131
119
|
|
|
132
|
-
|
|
120
|
+
this.app_info = new AppInfoModel({
|
|
121
|
+
first_installation_time: first_installation_time,
|
|
122
|
+
id: app_id,
|
|
123
|
+
installation_id: installation_id,
|
|
124
|
+
install_referrer: install_referrer,
|
|
125
|
+
last_update_time: last_update_time,
|
|
126
|
+
version: version,
|
|
127
|
+
});
|
|
133
128
|
|
|
134
|
-
|
|
129
|
+
this.logger.log(
|
|
130
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
131
|
+
this.constant.MetaStreamIO_Environment_AppInfo.format(
|
|
132
|
+
JSON.stringify(this.app_info)
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
this._appInfoCached = this.app_info.json();
|
|
137
|
+
return this._appInfoCached;
|
|
138
|
+
})();
|
|
139
|
+
return this._appInfoPromise;
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
async getAppPerformance() {
|
|
@@ -185,146 +190,153 @@ class MetaStreamIOEnvironment {
|
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
async logDevice() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
193
|
+
if (this._deviceCached) return this._deviceCached;
|
|
194
|
+
if (this._devicePromise) return this._devicePromise;
|
|
195
|
+
|
|
196
|
+
this._devicePromise = (async () => {
|
|
197
|
+
let brand,
|
|
198
|
+
device_type,
|
|
199
|
+
model,
|
|
200
|
+
operating_system,
|
|
201
|
+
operating_system_version,
|
|
202
|
+
pin_or_fingerprint_set,
|
|
203
|
+
screen_width,
|
|
204
|
+
screen_height,
|
|
205
|
+
storage_total,
|
|
206
|
+
storage_free,
|
|
207
|
+
supported_architectures,
|
|
208
|
+
timezone_offset_sec,
|
|
209
|
+
total_memory,
|
|
210
|
+
unique_device_id,
|
|
211
|
+
used_memory;
|
|
212
|
+
|
|
213
|
+
brand = await DeviceInfo.getBrand();
|
|
214
|
+
|
|
215
|
+
device_type = await DeviceInfo.getDeviceType();
|
|
216
|
+
|
|
217
|
+
model = DeviceInfo.getModel();
|
|
218
|
+
|
|
219
|
+
operating_system = DeviceInfo.getSystemName();
|
|
220
|
+
|
|
221
|
+
operating_system_version = DeviceInfo.getSystemVersion();
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
screen_height = Math.round(Dimensions.get("window").height);
|
|
225
|
+
} catch {
|
|
226
|
+
screen_height = null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
screen_width = Math.round(Dimensions.get("window").width);
|
|
231
|
+
} catch {
|
|
232
|
+
screen_width = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
storage_free = await DeviceInfo.getFreeDiskStorage()
|
|
236
|
+
.then((result) => {
|
|
237
|
+
return result;
|
|
238
|
+
})
|
|
239
|
+
.catch((err) => {
|
|
240
|
+
this.logger.warn(
|
|
241
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
242
|
+
err
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
storage_total = await DeviceInfo.getTotalDiskCapacity()
|
|
247
|
+
.then((result) => {
|
|
248
|
+
return result;
|
|
249
|
+
})
|
|
250
|
+
.catch((err) => {
|
|
251
|
+
this.logger.warn(
|
|
252
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
253
|
+
err
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
supported_architectures = await DeviceInfo.supportedAbis()
|
|
258
|
+
.then((result) => {
|
|
259
|
+
return result.join(",");
|
|
260
|
+
})
|
|
261
|
+
.catch((err) => {
|
|
262
|
+
this.logger.warn(
|
|
263
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
264
|
+
err
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
total_memory = await DeviceInfo.getTotalMemory()
|
|
269
|
+
.then((result) => {
|
|
270
|
+
return result;
|
|
271
|
+
})
|
|
272
|
+
.catch((err) => {
|
|
273
|
+
this.logger.warn(
|
|
274
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
275
|
+
err
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
unique_device_id = DeviceInfo.getUniqueId()
|
|
280
|
+
? DeviceInfo.getUniqueId()
|
|
281
|
+
: null;
|
|
282
|
+
|
|
283
|
+
used_memory = await DeviceInfo.getUsedMemory()
|
|
284
|
+
.then((result) => {
|
|
285
|
+
return result;
|
|
286
|
+
})
|
|
287
|
+
.catch((err) => {
|
|
288
|
+
this.logger.warn(
|
|
289
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
290
|
+
err
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
timezone_offset_sec = this.date.getDeviceTimezoneOffset();
|
|
296
|
+
} catch {
|
|
297
|
+
timezone_offset_sec = null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
pin_or_fingerprint_set = await DeviceInfo.isPinOrFingerprintSet()
|
|
301
|
+
.then((result) => {
|
|
302
|
+
return result;
|
|
303
|
+
})
|
|
304
|
+
.catch((err) => {
|
|
305
|
+
this.logger.warn(
|
|
306
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
307
|
+
err
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
this.device = new DeviceModel({
|
|
312
|
+
brand: brand,
|
|
313
|
+
device_type: device_type,
|
|
314
|
+
model: model,
|
|
315
|
+
operating_system: operating_system,
|
|
316
|
+
operating_system_version: operating_system_version,
|
|
317
|
+
pin_or_fingerprint_set: pin_or_fingerprint_set,
|
|
318
|
+
screen_height: screen_height,
|
|
319
|
+
screen_width: screen_width,
|
|
320
|
+
storage_free: storage_free,
|
|
321
|
+
storage_total: storage_total,
|
|
322
|
+
supported_architectures: supported_architectures,
|
|
323
|
+
timezone_offset_sec: timezone_offset_sec,
|
|
324
|
+
total_memory: total_memory,
|
|
325
|
+
unique_device_id: unique_device_id,
|
|
326
|
+
used_memory: used_memory,
|
|
300
327
|
});
|
|
301
328
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
supported_architectures: supported_architectures,
|
|
314
|
-
timezone_offset_sec: timezone_offset_sec,
|
|
315
|
-
total_memory: total_memory,
|
|
316
|
-
unique_device_id: unique_device_id,
|
|
317
|
-
used_memory: used_memory,
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
this.logger.log(
|
|
321
|
-
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
322
|
-
this.constant.MetaStreamIO_Environment_Device.format(
|
|
323
|
-
JSON.stringify(this.device)
|
|
324
|
-
)
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
return Promise.resolve(this.device.json());
|
|
329
|
+
this.logger.log(
|
|
330
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
331
|
+
this.constant.MetaStreamIO_Environment_Device.format(
|
|
332
|
+
JSON.stringify(this.device)
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
this._deviceCached = this.device.json();
|
|
337
|
+
return this._deviceCached;
|
|
338
|
+
})();
|
|
339
|
+
return this._devicePromise;
|
|
328
340
|
}
|
|
329
341
|
|
|
330
342
|
async getNetworkInfo() {
|
|
@@ -338,68 +350,66 @@ class MetaStreamIOEnvironment {
|
|
|
338
350
|
}
|
|
339
351
|
|
|
340
352
|
async logNetworkInfo() {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return {
|
|
353
|
+
if (this._networkCached && (Date.now() - this._networkCachedTime < 5000)) {
|
|
354
|
+
return this._networkCached;
|
|
355
|
+
}
|
|
356
|
+
if (this._networkPromise) return this._networkPromise;
|
|
357
|
+
|
|
358
|
+
this._networkPromise = (async () => {
|
|
359
|
+
let carrier,
|
|
360
|
+
carrier_iso_country_code,
|
|
361
|
+
cellular_generation,
|
|
362
|
+
mobile_country_code,
|
|
363
|
+
mobile_network_code,
|
|
364
|
+
network_state_type,
|
|
365
|
+
net_info;
|
|
366
|
+
|
|
367
|
+
carrier = await Promise.race([
|
|
368
|
+
DeviceInfo.getCarrier().then(r => r).catch(() => null),
|
|
369
|
+
new Promise(res => setTimeout(() => res(null), 500))
|
|
370
|
+
]);
|
|
371
|
+
|
|
372
|
+
net_info = await Promise.race([
|
|
373
|
+
NetInfo.fetch().then((state) => ({
|
|
363
374
|
connection_type: state.type,
|
|
364
375
|
connection_active: state.isConnected,
|
|
365
|
-
cellular_generation:
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.catch((err) => {
|
|
370
|
-
this.logger.warn(
|
|
371
|
-
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
372
|
-
err
|
|
373
|
-
);
|
|
374
|
-
});
|
|
376
|
+
cellular_generation: state.type === "cellular" ? state.details.cellularGeneration : null,
|
|
377
|
+
})).catch(() => ({ connection_type: "unknown", connection_active: true, cellular_generation: null })),
|
|
378
|
+
new Promise(res => setTimeout(() => res({ connection_type: "unknown", connection_active: true, cellular_generation: null }), 500))
|
|
379
|
+
]);
|
|
375
380
|
|
|
376
|
-
|
|
381
|
+
carrier_iso_country_code = null;
|
|
377
382
|
|
|
378
|
-
|
|
383
|
+
cellular_generation = net_info.cellular_generation;
|
|
379
384
|
|
|
380
|
-
|
|
385
|
+
mobile_country_code = null;
|
|
381
386
|
|
|
382
|
-
|
|
387
|
+
mobile_network_code = null;
|
|
383
388
|
|
|
384
|
-
|
|
389
|
+
network_state_type = net_info.connection_type;
|
|
385
390
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
391
|
+
this.logger.log(
|
|
392
|
+
this.constant.MetaStreamIO_Logger_Category_Environment,
|
|
393
|
+
this.constant.MetaStreamIO_Environment_Network.format(
|
|
394
|
+
JSON.stringify(this.network_info)
|
|
395
|
+
)
|
|
396
|
+
);
|
|
392
397
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
398
|
+
this.network_info = new NetworkInfoModel({
|
|
399
|
+
carrier: carrier,
|
|
400
|
+
carrier_iso_country_code: carrier_iso_country_code,
|
|
401
|
+
cellular_generation: cellular_generation,
|
|
402
|
+
mobile_country_code: mobile_country_code,
|
|
403
|
+
mobile_network_code: mobile_network_code,
|
|
404
|
+
network_state_type: network_state_type,
|
|
405
|
+
});
|
|
401
406
|
|
|
402
|
-
|
|
407
|
+
this._networkCached = this.network_info.json();
|
|
408
|
+
this._networkCachedTime = Date.now();
|
|
409
|
+
this._networkPromise = null;
|
|
410
|
+
return this._networkCached;
|
|
411
|
+
})();
|
|
412
|
+
return this._networkPromise;
|
|
403
413
|
}
|
|
404
414
|
}
|
|
405
415
|
|
|
@@ -82,6 +82,7 @@ class MetaStreamIO {
|
|
|
82
82
|
silentMode: this.config.silentMode,
|
|
83
83
|
endpoints: this.appConfig.endpoints,
|
|
84
84
|
headers: this.appConfig.headers,
|
|
85
|
+
storageEngine: this.config.storageEngine,
|
|
85
86
|
});
|
|
86
87
|
this.account = new MetaStreamIOAccountData({
|
|
87
88
|
constants: this.constant,
|
|
@@ -469,7 +470,7 @@ class MetaStreamIO {
|
|
|
469
470
|
);
|
|
470
471
|
return Promise.resolve(event_data);
|
|
471
472
|
} else {
|
|
472
|
-
this.logger.
|
|
473
|
+
this.logger.warn(
|
|
473
474
|
"event",
|
|
474
475
|
this.constant.MetaStreamIO_Log_EventTrackFailed.format(_data)
|
|
475
476
|
);
|
|
@@ -480,13 +481,14 @@ class MetaStreamIO {
|
|
|
480
481
|
return Promise.reject(_data);
|
|
481
482
|
}
|
|
482
483
|
})
|
|
483
|
-
.catch((err) => {
|
|
484
|
-
this.logger.
|
|
485
|
-
|
|
484
|
+
.catch(async (err) => {
|
|
485
|
+
this.logger.warn("event", "<post_event> could not post: " + (err.message || JSON.stringify(err)));
|
|
486
|
+
await this.network.storeOfflineEvent(this.network.endpoint, event_data);
|
|
487
|
+
return Promise.resolve(event_data); // Resolve to deque, since it's now safely offline
|
|
486
488
|
});
|
|
487
489
|
return status;
|
|
488
490
|
} else {
|
|
489
|
-
this.logger.
|
|
491
|
+
this.logger.warn("event", "<post_event> event_data is undefined");
|
|
490
492
|
}
|
|
491
493
|
}
|
|
492
494
|
|
|
@@ -515,7 +517,7 @@ class MetaStreamIO {
|
|
|
515
517
|
return Promise.resolve();
|
|
516
518
|
})
|
|
517
519
|
.catch((err) => {
|
|
518
|
-
this.logger.
|
|
520
|
+
this.logger.warn(
|
|
519
521
|
"event",
|
|
520
522
|
"<runQueue> this.post_event failed: " + err
|
|
521
523
|
);
|
|
@@ -526,7 +528,7 @@ class MetaStreamIO {
|
|
|
526
528
|
try {
|
|
527
529
|
this.queue.enqueue(event);
|
|
528
530
|
} catch (err) {
|
|
529
|
-
this.logger.
|
|
531
|
+
this.logger.warn(
|
|
530
532
|
"event",
|
|
531
533
|
"<runQueue> could not enqueue event: " + err
|
|
532
534
|
);
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { strf } from "./string.format.js";
|
|
4
|
-
import { createMMKV } from "react-native-mmkv";
|
|
5
|
-
const mmkvStorage = createMMKV();
|
|
6
|
-
const AsyncStorage = {
|
|
7
|
-
getItem: async (key) => mmkvStorage.getString(key) || null,
|
|
8
|
-
setItem: async (key, value) => { mmkvStorage.set(key, value); return Promise.resolve(); },
|
|
9
|
-
removeItem: async (key) => { mmkvStorage.delete(key); return Promise.resolve(); }
|
|
10
|
-
};
|
|
11
4
|
import NetInfo from "@react-native-community/netinfo";
|
|
12
5
|
|
|
13
6
|
strf();
|
|
@@ -21,7 +14,7 @@ const EVENT_QUEUE_KEY = "metastream_offline_events";
|
|
|
21
14
|
*/
|
|
22
15
|
|
|
23
16
|
class MetaStreamIONetwork {
|
|
24
|
-
constructor({ constants, endpoints, logger, silentMode, headers } = {}) {
|
|
17
|
+
constructor({ constants, endpoints, logger, silentMode, headers, storageEngine } = {}) {
|
|
25
18
|
this.silentMode = silentMode;
|
|
26
19
|
this.constant = constants;
|
|
27
20
|
this.endpoint = null;
|
|
@@ -29,13 +22,23 @@ class MetaStreamIONetwork {
|
|
|
29
22
|
this.headers = headers;
|
|
30
23
|
this.logger = logger;
|
|
31
24
|
|
|
25
|
+
// Use provided storageEngine from config or fallback to a transient in-memory store
|
|
26
|
+
this.storageEngine = storageEngine || {
|
|
27
|
+
_memory: {},
|
|
28
|
+
getItem: async (key) => this.storageEngine._memory[key] || null,
|
|
29
|
+
setItem: async (key, value) => { this.storageEngine._memory[key] = value; return Promise.resolve(); },
|
|
30
|
+
removeItem: async (key) => { delete this.storageEngine._memory[key]; return Promise.resolve(); }
|
|
31
|
+
};
|
|
32
|
+
|
|
32
33
|
// Initialize offline handling
|
|
33
34
|
this.initOfflineHandling();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
initOfflineHandling() {
|
|
38
|
+
this.isOnline = true; // Default assume online, Listener updates it
|
|
37
39
|
// Flush on startup if connected
|
|
38
40
|
NetInfo.fetch().then(state => {
|
|
41
|
+
this.isOnline = state.isConnected;
|
|
39
42
|
if (state.isConnected) {
|
|
40
43
|
this.flushEvents();
|
|
41
44
|
}
|
|
@@ -43,10 +46,16 @@ class MetaStreamIONetwork {
|
|
|
43
46
|
|
|
44
47
|
// Flush when network comes back online
|
|
45
48
|
NetInfo.addEventListener(state => {
|
|
49
|
+
this.isOnline = state.isConnected;
|
|
46
50
|
if (state.isConnected) {
|
|
47
51
|
this.flushEvents();
|
|
48
52
|
}
|
|
49
53
|
});
|
|
54
|
+
|
|
55
|
+
// Background heartbeat to auto-flush stuck events gracefully
|
|
56
|
+
this._flushInterval = setInterval(() => {
|
|
57
|
+
this.flushEvents();
|
|
58
|
+
}, 3000); // Poll every 3 seconds for perfect sync
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
reset() {
|
|
@@ -59,14 +68,15 @@ class MetaStreamIONetwork {
|
|
|
59
68
|
this.endpoint = await this.getData({ endpoint: this.endpoints[_e] })
|
|
60
69
|
.then((data) => {
|
|
61
70
|
try {
|
|
62
|
-
if (data.status
|
|
71
|
+
if (data && data.status > 0) {
|
|
63
72
|
return this.endpoints[_e];
|
|
64
73
|
}
|
|
74
|
+
return false;
|
|
65
75
|
} catch (err) {
|
|
66
76
|
return false;
|
|
67
77
|
}
|
|
68
78
|
})
|
|
69
|
-
.catch(() => { });
|
|
79
|
+
.catch(() => { return false; });
|
|
70
80
|
if (this.endpoint) {
|
|
71
81
|
this.logger.log(
|
|
72
82
|
"network",
|
|
@@ -92,15 +102,12 @@ class MetaStreamIONetwork {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
async getData({ endpoint = this.endpoint } = {}) {
|
|
95
|
-
var rx;
|
|
96
|
-
|
|
97
105
|
let get_config = {
|
|
98
106
|
method: "GET",
|
|
99
|
-
// mode: "no-cors", // required for local testing
|
|
100
107
|
cache: "no-cache",
|
|
101
108
|
credentials: "same-origin",
|
|
102
|
-
redirect: "follow",
|
|
103
|
-
referrerPolicy: "no-referrer",
|
|
109
|
+
redirect: "follow",
|
|
110
|
+
referrerPolicy: "no-referrer",
|
|
104
111
|
headers: {},
|
|
105
112
|
};
|
|
106
113
|
|
|
@@ -109,37 +116,14 @@ class MetaStreamIONetwork {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return response;
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.catch((err) => {
|
|
122
|
-
return Promise.resolve(err);
|
|
123
|
-
})
|
|
124
|
-
.then(async (response) => {
|
|
125
|
-
rx = {
|
|
126
|
-
body: await response.json(),
|
|
127
|
-
status: response.status,
|
|
128
|
-
};
|
|
129
|
-
})
|
|
130
|
-
.catch((err) => {
|
|
131
|
-
return {
|
|
132
|
-
body: err,
|
|
133
|
-
status: 400,
|
|
134
|
-
};
|
|
135
|
-
});
|
|
136
|
-
return rx;
|
|
119
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
|
|
120
|
+
const response = await Promise.race([
|
|
121
|
+
fetch(endpoint, get_config),
|
|
122
|
+
timeoutPromise
|
|
123
|
+
]);
|
|
124
|
+
return { status: response.status, body: "OK" };
|
|
137
125
|
} catch (err) {
|
|
138
|
-
return
|
|
139
|
-
throw this.constant.MetaStreamIO_Network_GetDataIssue.format(
|
|
140
|
-
err,
|
|
141
|
-
endpoint
|
|
142
|
-
);
|
|
126
|
+
return { status: 0, body: String(err) };
|
|
143
127
|
}
|
|
144
128
|
}
|
|
145
129
|
|
|
@@ -161,12 +145,25 @@ class MetaStreamIONetwork {
|
|
|
161
145
|
};
|
|
162
146
|
}
|
|
163
147
|
|
|
148
|
+
if (!this.isOnline) {
|
|
149
|
+
this.logger.log("network", "Offline detected in postData. Storing event.");
|
|
150
|
+
const fallbackEndpoint = this.endpoints.length > 0 ? this.endpoints[0] : null;
|
|
151
|
+
if (fallbackEndpoint) {
|
|
152
|
+
await this.storeOfflineEvent(fallbackEndpoint + (endpoint ? endpoint : ""), data);
|
|
153
|
+
return {
|
|
154
|
+
body: "Stored offline",
|
|
155
|
+
status: 200
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
164
160
|
if (!this.endpoint) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
const rx_endpoint = await this.testConnection().catch(() => null);
|
|
162
|
+
if (rx_endpoint) {
|
|
163
|
+
_endpoint = rx_endpoint + (endpoint ? endpoint : "");
|
|
164
|
+
} else {
|
|
165
|
+
_endpoint = null;
|
|
166
|
+
}
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
if (_endpoint) {
|
|
@@ -204,6 +201,9 @@ class MetaStreamIONetwork {
|
|
|
204
201
|
}
|
|
205
202
|
|
|
206
203
|
async post({ data = {}, endpoint = this.endpoint } = {}) {
|
|
204
|
+
// Non-blocking trigger to flush stuck offline events (if any)
|
|
205
|
+
this.flushEvents();
|
|
206
|
+
|
|
207
207
|
let rx = {};
|
|
208
208
|
|
|
209
209
|
let post_config = {
|
|
@@ -223,95 +223,151 @@ class MetaStreamIONetwork {
|
|
|
223
223
|
post_config.headers = Object.assign(post_config.headers, this.headers);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
await NetInfo.fetch().then(state => {
|
|
228
|
-
if (!state.isConnected) {
|
|
229
|
-
isOffline = true;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
if (isOffline) {
|
|
226
|
+
if (!this.isOnline) {
|
|
234
227
|
this.logger.log("network", "Offline detected. Storing event.");
|
|
235
228
|
await this.storeOfflineEvent(endpoint, data);
|
|
236
229
|
return { status: 200, body: "Stored offline" };
|
|
237
230
|
}
|
|
238
231
|
|
|
239
|
-
|
|
232
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
|
|
233
|
+
|
|
234
|
+
await Promise.race([
|
|
235
|
+
fetch(endpoint, post_config),
|
|
236
|
+
timeoutPromise
|
|
237
|
+
])
|
|
240
238
|
.then((response) => {
|
|
239
|
+
rx.status = response.status;
|
|
241
240
|
let _contentType = response.headers.get("content-type");
|
|
242
241
|
if (!_contentType || !_contentType.includes("application/json")) {
|
|
243
242
|
return null;
|
|
244
243
|
} else {
|
|
245
|
-
rx.status = response.status;
|
|
246
244
|
return response;
|
|
247
245
|
}
|
|
248
246
|
})
|
|
249
247
|
.then(async (response) => {
|
|
250
|
-
|
|
248
|
+
if (response) {
|
|
249
|
+
rx.body = await response.json();
|
|
250
|
+
} else {
|
|
251
|
+
rx.body = "OK";
|
|
252
|
+
}
|
|
251
253
|
})
|
|
252
254
|
.catch(async (err) => {
|
|
253
|
-
this.logger.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
rx.body = "Stored offline on error";
|
|
255
|
+
this.logger.warn("network", "fetch() error in post()", err.message || err);
|
|
256
|
+
rx.status = 500;
|
|
257
|
+
rx.body = "fetch error";
|
|
258
|
+
throw err;
|
|
258
259
|
});
|
|
259
260
|
|
|
260
261
|
return rx;
|
|
261
262
|
}
|
|
262
263
|
|
|
264
|
+
async _getQueue() {
|
|
265
|
+
try {
|
|
266
|
+
const raw = await this.storageEngine.getItem(EVENT_QUEUE_KEY);
|
|
267
|
+
if (!raw) return [];
|
|
268
|
+
const parsed = JSON.parse(raw);
|
|
269
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
270
|
+
} catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
263
275
|
async storeOfflineEvent(endpoint, payload) {
|
|
264
276
|
try {
|
|
265
|
-
const existing =
|
|
277
|
+
const existing = await this._getQueue();
|
|
266
278
|
const newEvent = {
|
|
267
279
|
endpoint,
|
|
268
280
|
payload,
|
|
269
281
|
failedAt: new Date().toISOString(),
|
|
270
282
|
};
|
|
271
283
|
existing.push(newEvent);
|
|
272
|
-
await
|
|
284
|
+
await this.storageEngine.setItem(EVENT_QUEUE_KEY, JSON.stringify(existing));
|
|
273
285
|
this.logger.log("network", `Stored event locally. Queue size: ${existing.length}`);
|
|
274
286
|
} catch (error) {
|
|
275
|
-
this.logger.error("network", "Failed to store event:", error);
|
|
287
|
+
this.logger.error("network", "Failed to store event:", error.message || error);
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
async flushEvents() {
|
|
292
|
+
if (this._isFlushing) return;
|
|
293
|
+
this._isFlushing = true;
|
|
294
|
+
|
|
280
295
|
try {
|
|
281
|
-
const stored =
|
|
296
|
+
const stored = await this._getQueue();
|
|
282
297
|
if (stored.length === 0) {
|
|
283
298
|
return;
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
this.logger.log("network", `Flushing ${stored.length} stored event(s)...`);
|
|
302
|
+
|
|
303
|
+
// Pre-flight check: Do not spam the Native Bridge with 20 offline events if the server route is down
|
|
304
|
+
const pingEndpoint = this.endpoint || (this.endpoints.length > 0 ? this.endpoints[0] : null);
|
|
305
|
+
if (pingEndpoint) {
|
|
306
|
+
const pingTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1500));
|
|
307
|
+
const routeValid = await Promise.race([
|
|
308
|
+
fetch(pingEndpoint, { method: "OPTIONS" }),
|
|
309
|
+
pingTimeout
|
|
310
|
+
]).then(() => true).catch(() => false);
|
|
311
|
+
|
|
312
|
+
if (!routeValid) {
|
|
313
|
+
this.logger.log("network", "flushEvents aborted: Node route not fully established natively yet.");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
287
318
|
const remainingEvents = [];
|
|
288
319
|
|
|
289
320
|
for (const e of stored) {
|
|
321
|
+
let reqHeaders = { "Content-Type": "application/json" };
|
|
322
|
+
if (this.headers) {
|
|
323
|
+
reqHeaders = Object.assign(reqHeaders, this.headers);
|
|
324
|
+
}
|
|
325
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
|
|
326
|
+
|
|
290
327
|
try {
|
|
291
|
-
const response = await
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
328
|
+
const response = await Promise.race([
|
|
329
|
+
fetch(e.endpoint, {
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers: reqHeaders,
|
|
332
|
+
body: JSON.stringify(e.payload),
|
|
333
|
+
}),
|
|
334
|
+
timeoutPromise
|
|
335
|
+
]);
|
|
296
336
|
|
|
297
337
|
if (response.ok) {
|
|
298
|
-
this.logger.log("network", "Retried & sent event");
|
|
338
|
+
this.logger.log("network", "Retried & sent event successfully.");
|
|
299
339
|
} else {
|
|
300
|
-
|
|
301
|
-
this.logger.warn("network", "Retry failed with non-OK status, dropping event");
|
|
340
|
+
this.logger.warn("network", `Retry got non-OK status (${response.status}), dropping event.`);
|
|
302
341
|
}
|
|
303
342
|
} catch (err) {
|
|
304
|
-
|
|
343
|
+
this.logger.log("network", "Retry failed (still offline or bad host), keeping event.", err.message);
|
|
305
344
|
remainingEvents.push(e);
|
|
306
345
|
}
|
|
307
346
|
}
|
|
308
347
|
|
|
309
|
-
await
|
|
348
|
+
const currentQueue = await this._getQueue();
|
|
349
|
+
// Only keep events that were just inserted (after we started flushing)
|
|
350
|
+
// We assume new events were appended to the end of the array.
|
|
351
|
+
let newAdditions = [];
|
|
352
|
+
if (currentQueue.length > stored.length) {
|
|
353
|
+
newAdditions = currentQueue.slice(stored.length);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const updatedQueue = [...remainingEvents, ...newAdditions];
|
|
357
|
+
|
|
358
|
+
if (updatedQueue.length > 0) {
|
|
359
|
+
await this.storageEngine.setItem(EVENT_QUEUE_KEY, JSON.stringify(updatedQueue));
|
|
360
|
+
} else {
|
|
361
|
+
await this.storageEngine.removeItem(EVENT_QUEUE_KEY);
|
|
362
|
+
}
|
|
363
|
+
|
|
310
364
|
if (remainingEvents.length < stored.length) {
|
|
311
|
-
this.logger.log("network", `Flush complete. Remaining: ${
|
|
365
|
+
this.logger.log("network", `Flush complete. Remaining: ${updatedQueue.length}`);
|
|
312
366
|
}
|
|
313
367
|
} catch (error) {
|
|
314
|
-
this.logger.
|
|
368
|
+
this.logger.warn("network", "Failed to flush events:", error.message || error);
|
|
369
|
+
} finally {
|
|
370
|
+
this._isFlushing = false;
|
|
315
371
|
}
|
|
316
372
|
}
|
|
317
373
|
|
|
@@ -12,6 +12,7 @@ export class ConfigConstructorModel {
|
|
|
12
12
|
quality = null,
|
|
13
13
|
fps = null,
|
|
14
14
|
recordingEndpoint = null,
|
|
15
|
+
storageEngine = null,
|
|
15
16
|
} = {}) {
|
|
16
17
|
this.logging = logging;
|
|
17
18
|
this.loggingLevel = loggingLevel;
|
|
@@ -26,6 +27,7 @@ export class ConfigConstructorModel {
|
|
|
26
27
|
this.quality = quality;
|
|
27
28
|
this.fps = fps;
|
|
28
29
|
this.recordingEndpoint = recordingEndpoint;
|
|
30
|
+
this.storageEngine = storageEngine;
|
|
29
31
|
}
|
|
30
32
|
json() {
|
|
31
33
|
return {
|
|
@@ -41,6 +43,7 @@ export class ConfigConstructorModel {
|
|
|
41
43
|
quality: this.quality,
|
|
42
44
|
fps: this.fps,
|
|
43
45
|
recordingEndpoint: this.recordingEndpoint,
|
|
46
|
+
storageEngine: this.storageEngine,
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iidrak-analytics-react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "react native client for metastreamio",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"react-native-mmkv": ">=4.0.0",
|
|
7
7
|
"react-native-nitro-modules": ">=0.30.0",
|
|
8
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
8
9
|
"@react-native-community/netinfo": ">=11.5.2",
|
|
9
10
|
"react": ">=18.0.0",
|
|
10
11
|
"react-native": ">=0.70.0",
|
|
@@ -12,6 +13,17 @@
|
|
|
12
13
|
"react-native-view-shot": ">=3.0.0",
|
|
13
14
|
"react-native-safe-area-context": ">=4.0.0"
|
|
14
15
|
},
|
|
16
|
+
"peerDependenciesMeta": {
|
|
17
|
+
"react-native-mmkv": {
|
|
18
|
+
"optional": true
|
|
19
|
+
},
|
|
20
|
+
"react-native-nitro-modules": {
|
|
21
|
+
"optional": true
|
|
22
|
+
},
|
|
23
|
+
"@react-native-async-storage/async-storage": {
|
|
24
|
+
"optional": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
15
27
|
"dependencies": {
|
|
16
28
|
"string-format": "^0.5.0"
|
|
17
29
|
},
|