iidrak-analytics-react 1.2.9 → 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
|
|
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,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { strf } from "./string.format.js";
|
|
4
|
-
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
5
4
|
import NetInfo from "@react-native-community/netinfo";
|
|
6
5
|
|
|
7
6
|
strf();
|
|
@@ -15,7 +14,7 @@ const EVENT_QUEUE_KEY = "metastream_offline_events";
|
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
class MetaStreamIONetwork {
|
|
18
|
-
constructor({ constants, endpoints, logger, silentMode, headers } = {}) {
|
|
17
|
+
constructor({ constants, endpoints, logger, silentMode, headers, storageEngine } = {}) {
|
|
19
18
|
this.silentMode = silentMode;
|
|
20
19
|
this.constant = constants;
|
|
21
20
|
this.endpoint = null;
|
|
@@ -23,13 +22,23 @@ class MetaStreamIONetwork {
|
|
|
23
22
|
this.headers = headers;
|
|
24
23
|
this.logger = logger;
|
|
25
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
|
+
|
|
26
33
|
// Initialize offline handling
|
|
27
34
|
this.initOfflineHandling();
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
initOfflineHandling() {
|
|
38
|
+
this.isOnline = true; // Default assume online, Listener updates it
|
|
31
39
|
// Flush on startup if connected
|
|
32
40
|
NetInfo.fetch().then(state => {
|
|
41
|
+
this.isOnline = state.isConnected;
|
|
33
42
|
if (state.isConnected) {
|
|
34
43
|
this.flushEvents();
|
|
35
44
|
}
|
|
@@ -37,10 +46,16 @@ class MetaStreamIONetwork {
|
|
|
37
46
|
|
|
38
47
|
// Flush when network comes back online
|
|
39
48
|
NetInfo.addEventListener(state => {
|
|
49
|
+
this.isOnline = state.isConnected;
|
|
40
50
|
if (state.isConnected) {
|
|
41
51
|
this.flushEvents();
|
|
42
52
|
}
|
|
43
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
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
reset() {
|
|
@@ -53,14 +68,15 @@ class MetaStreamIONetwork {
|
|
|
53
68
|
this.endpoint = await this.getData({ endpoint: this.endpoints[_e] })
|
|
54
69
|
.then((data) => {
|
|
55
70
|
try {
|
|
56
|
-
if (data.status
|
|
71
|
+
if (data && data.status > 0) {
|
|
57
72
|
return this.endpoints[_e];
|
|
58
73
|
}
|
|
74
|
+
return false;
|
|
59
75
|
} catch (err) {
|
|
60
76
|
return false;
|
|
61
77
|
}
|
|
62
78
|
})
|
|
63
|
-
.catch(() => { });
|
|
79
|
+
.catch(() => { return false; });
|
|
64
80
|
if (this.endpoint) {
|
|
65
81
|
this.logger.log(
|
|
66
82
|
"network",
|
|
@@ -86,15 +102,12 @@ class MetaStreamIONetwork {
|
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
async getData({ endpoint = this.endpoint } = {}) {
|
|
89
|
-
var rx;
|
|
90
|
-
|
|
91
105
|
let get_config = {
|
|
92
106
|
method: "GET",
|
|
93
|
-
// mode: "no-cors", // required for local testing
|
|
94
107
|
cache: "no-cache",
|
|
95
108
|
credentials: "same-origin",
|
|
96
|
-
redirect: "follow",
|
|
97
|
-
referrerPolicy: "no-referrer",
|
|
109
|
+
redirect: "follow",
|
|
110
|
+
referrerPolicy: "no-referrer",
|
|
98
111
|
headers: {},
|
|
99
112
|
};
|
|
100
113
|
|
|
@@ -103,37 +116,14 @@ class MetaStreamIONetwork {
|
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return response;
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
.catch((err) => {
|
|
116
|
-
return Promise.resolve(err);
|
|
117
|
-
})
|
|
118
|
-
.then(async (response) => {
|
|
119
|
-
rx = {
|
|
120
|
-
body: await response.json(),
|
|
121
|
-
status: response.status,
|
|
122
|
-
};
|
|
123
|
-
})
|
|
124
|
-
.catch((err) => {
|
|
125
|
-
return {
|
|
126
|
-
body: err,
|
|
127
|
-
status: 400,
|
|
128
|
-
};
|
|
129
|
-
});
|
|
130
|
-
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" };
|
|
131
125
|
} catch (err) {
|
|
132
|
-
return
|
|
133
|
-
throw this.constant.MetaStreamIO_Network_GetDataIssue.format(
|
|
134
|
-
err,
|
|
135
|
-
endpoint
|
|
136
|
-
);
|
|
126
|
+
return { status: 0, body: String(err) };
|
|
137
127
|
}
|
|
138
128
|
}
|
|
139
129
|
|
|
@@ -155,12 +145,25 @@ class MetaStreamIONetwork {
|
|
|
155
145
|
};
|
|
156
146
|
}
|
|
157
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
|
+
|
|
158
160
|
if (!this.endpoint) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
if (_endpoint) {
|
|
@@ -198,6 +201,9 @@ class MetaStreamIONetwork {
|
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
async post({ data = {}, endpoint = this.endpoint } = {}) {
|
|
204
|
+
// Non-blocking trigger to flush stuck offline events (if any)
|
|
205
|
+
this.flushEvents();
|
|
206
|
+
|
|
201
207
|
let rx = {};
|
|
202
208
|
|
|
203
209
|
let post_config = {
|
|
@@ -217,95 +223,151 @@ class MetaStreamIONetwork {
|
|
|
217
223
|
post_config.headers = Object.assign(post_config.headers, this.headers);
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
|
|
221
|
-
await NetInfo.fetch().then(state => {
|
|
222
|
-
if (!state.isConnected) {
|
|
223
|
-
isOffline = true;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (isOffline) {
|
|
226
|
+
if (!this.isOnline) {
|
|
228
227
|
this.logger.log("network", "Offline detected. Storing event.");
|
|
229
228
|
await this.storeOfflineEvent(endpoint, data);
|
|
230
229
|
return { status: 200, body: "Stored offline" };
|
|
231
230
|
}
|
|
232
231
|
|
|
233
|
-
|
|
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
|
+
])
|
|
234
238
|
.then((response) => {
|
|
239
|
+
rx.status = response.status;
|
|
235
240
|
let _contentType = response.headers.get("content-type");
|
|
236
241
|
if (!_contentType || !_contentType.includes("application/json")) {
|
|
237
242
|
return null;
|
|
238
243
|
} else {
|
|
239
|
-
rx.status = response.status;
|
|
240
244
|
return response;
|
|
241
245
|
}
|
|
242
246
|
})
|
|
243
247
|
.then(async (response) => {
|
|
244
|
-
|
|
248
|
+
if (response) {
|
|
249
|
+
rx.body = await response.json();
|
|
250
|
+
} else {
|
|
251
|
+
rx.body = "OK";
|
|
252
|
+
}
|
|
245
253
|
})
|
|
246
254
|
.catch(async (err) => {
|
|
247
|
-
this.logger.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
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;
|
|
252
259
|
});
|
|
253
260
|
|
|
254
261
|
return rx;
|
|
255
262
|
}
|
|
256
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
|
+
|
|
257
275
|
async storeOfflineEvent(endpoint, payload) {
|
|
258
276
|
try {
|
|
259
|
-
const existing =
|
|
277
|
+
const existing = await this._getQueue();
|
|
260
278
|
const newEvent = {
|
|
261
279
|
endpoint,
|
|
262
280
|
payload,
|
|
263
281
|
failedAt: new Date().toISOString(),
|
|
264
282
|
};
|
|
265
283
|
existing.push(newEvent);
|
|
266
|
-
await
|
|
284
|
+
await this.storageEngine.setItem(EVENT_QUEUE_KEY, JSON.stringify(existing));
|
|
267
285
|
this.logger.log("network", `Stored event locally. Queue size: ${existing.length}`);
|
|
268
286
|
} catch (error) {
|
|
269
|
-
this.logger.error("network", "Failed to store event:", error);
|
|
287
|
+
this.logger.error("network", "Failed to store event:", error.message || error);
|
|
270
288
|
}
|
|
271
289
|
}
|
|
272
290
|
|
|
273
291
|
async flushEvents() {
|
|
292
|
+
if (this._isFlushing) return;
|
|
293
|
+
this._isFlushing = true;
|
|
294
|
+
|
|
274
295
|
try {
|
|
275
|
-
const stored =
|
|
296
|
+
const stored = await this._getQueue();
|
|
276
297
|
if (stored.length === 0) {
|
|
277
298
|
return;
|
|
278
299
|
}
|
|
279
300
|
|
|
280
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
|
+
|
|
281
318
|
const remainingEvents = [];
|
|
282
319
|
|
|
283
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
|
+
|
|
284
327
|
try {
|
|
285
|
-
const response = await
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
+
]);
|
|
290
336
|
|
|
291
337
|
if (response.ok) {
|
|
292
|
-
this.logger.log("network", "Retried & sent event");
|
|
338
|
+
this.logger.log("network", "Retried & sent event successfully.");
|
|
293
339
|
} else {
|
|
294
|
-
|
|
295
|
-
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.`);
|
|
296
341
|
}
|
|
297
342
|
} catch (err) {
|
|
298
|
-
|
|
343
|
+
this.logger.log("network", "Retry failed (still offline or bad host), keeping event.", err.message);
|
|
299
344
|
remainingEvents.push(e);
|
|
300
345
|
}
|
|
301
346
|
}
|
|
302
347
|
|
|
303
|
-
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
|
+
|
|
304
364
|
if (remainingEvents.length < stored.length) {
|
|
305
|
-
this.logger.log("network", `Flush complete. Remaining: ${
|
|
365
|
+
this.logger.log("network", `Flush complete. Remaining: ${updatedQueue.length}`);
|
|
306
366
|
}
|
|
307
367
|
} catch (error) {
|
|
308
|
-
this.logger.
|
|
368
|
+
this.logger.warn("network", "Failed to flush events:", error.message || error);
|
|
369
|
+
} finally {
|
|
370
|
+
this._isFlushing = false;
|
|
309
371
|
}
|
|
310
372
|
}
|
|
311
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,9 +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
|
+
"react-native-nitro-modules": ">=0.30.0",
|
|
8
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
7
9
|
"@react-native-community/netinfo": ">=11.5.2",
|
|
8
10
|
"react": ">=18.0.0",
|
|
9
11
|
"react-native": ">=0.70.0",
|
|
@@ -11,6 +13,17 @@
|
|
|
11
13
|
"react-native-view-shot": ">=3.0.0",
|
|
12
14
|
"react-native-safe-area-context": ">=4.0.0"
|
|
13
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
|
+
},
|
|
14
27
|
"dependencies": {
|
|
15
28
|
"string-format": "^0.5.0"
|
|
16
29
|
},
|