iidrak-analytics-react 1.3.0 → 1.5.1
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 +25 -2
- package/metastreamio/metastreamio.environment.js +268 -258
- package/metastreamio/metastreamio.interface.js +9 -7
- package/metastreamio/metastreamio.network.js +154 -105
- package/metastreamio/metastreamio.queue.js +13 -6
- package/metastreamio/metastreamio.recorder.js +26 -5
- package/metastreamio/models/constructor.config.js +3 -0
- package/package.json +13 -1
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,64 +46,50 @@ 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
|
+
this._flushInterval = setInterval(() => {
|
|
56
|
+
this.flushEvents();
|
|
57
|
+
}, 3000); // Reverted back to 3 seconds. (Locking guarantees safety)
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
reset() {
|
|
53
61
|
this.endpoint = null;
|
|
62
|
+
this._testingPromise = null;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
async testConnection() {
|
|
57
|
-
if (
|
|
66
|
+
if (this.endpoint) return Promise.resolve(this.endpoint);
|
|
67
|
+
if (this._testingPromise) return this._testingPromise;
|
|
68
|
+
|
|
69
|
+
this._testingPromise = (async () => {
|
|
58
70
|
for (let _e in this.endpoints) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return this.endpoints[_e];
|
|
64
|
-
}
|
|
65
|
-
} catch (err) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
.catch(() => { });
|
|
70
|
-
if (this.endpoint) {
|
|
71
|
-
this.logger.log(
|
|
72
|
-
"network",
|
|
73
|
-
this.constant.MetaStreamIO_Network_EndpointSet.format(this.endpoint)
|
|
74
|
-
);
|
|
71
|
+
let testEp = await this.getData({ endpoint: this.endpoints[_e] });
|
|
72
|
+
if (testEp && testEp.status > 0) {
|
|
73
|
+
this.logger.log("network", this.constant.MetaStreamIO_Network_EndpointSet.format(this.endpoints[_e]));
|
|
74
|
+
this.endpoint = this.endpoints[_e];
|
|
75
75
|
break;
|
|
76
76
|
} else {
|
|
77
|
-
this.logger.log(
|
|
78
|
-
"network",
|
|
79
|
-
this.constant.MetaStreamIO_Network_EndpointTestFailed.format(
|
|
80
|
-
this.endpoint
|
|
81
|
-
)
|
|
82
|
-
);
|
|
83
|
-
this.logger.log(
|
|
84
|
-
"network",
|
|
85
|
-
this.constant.MetaStreamIO_Network_SilentModeEnabled
|
|
86
|
-
);
|
|
77
|
+
this.logger.log("network", this.constant.MetaStreamIO_Network_EndpointTestFailed.format(this.endpoints[_e]));
|
|
87
78
|
}
|
|
88
79
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
this._testingPromise = null;
|
|
81
|
+
return this.endpoint;
|
|
82
|
+
})();
|
|
83
|
+
return this._testingPromise;
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
async getData({ endpoint = this.endpoint } = {}) {
|
|
95
|
-
var rx;
|
|
96
|
-
|
|
97
87
|
let get_config = {
|
|
98
88
|
method: "GET",
|
|
99
|
-
// mode: "no-cors", // required for local testing
|
|
100
89
|
cache: "no-cache",
|
|
101
90
|
credentials: "same-origin",
|
|
102
|
-
redirect: "follow",
|
|
103
|
-
referrerPolicy: "no-referrer",
|
|
91
|
+
redirect: "follow",
|
|
92
|
+
referrerPolicy: "no-referrer",
|
|
104
93
|
headers: {},
|
|
105
94
|
};
|
|
106
95
|
|
|
@@ -108,38 +97,17 @@ class MetaStreamIONetwork {
|
|
|
108
97
|
get_config.headers = Object.assign(get_config.headers, this.headers);
|
|
109
98
|
}
|
|
110
99
|
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
102
|
+
get_config.signal = controller.signal;
|
|
103
|
+
|
|
111
104
|
try {
|
|
112
|
-
await fetch(endpoint, get_config)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!contentType || !contentType.includes("application/json")) {
|
|
116
|
-
return null;
|
|
117
|
-
} else {
|
|
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;
|
|
105
|
+
const response = await fetch(endpoint, get_config);
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
return { status: response.status, body: "OK" };
|
|
137
108
|
} catch (err) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
err,
|
|
141
|
-
endpoint
|
|
142
|
-
);
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
return { status: 0, body: String(err) };
|
|
143
111
|
}
|
|
144
112
|
}
|
|
145
113
|
|
|
@@ -161,12 +129,25 @@ class MetaStreamIONetwork {
|
|
|
161
129
|
};
|
|
162
130
|
}
|
|
163
131
|
|
|
132
|
+
if (!this.isOnline) {
|
|
133
|
+
this.logger.log("network", "Offline detected in postData. Storing event.");
|
|
134
|
+
const fallbackEndpoint = this.endpoints.length > 0 ? this.endpoints[0] : null;
|
|
135
|
+
if (fallbackEndpoint) {
|
|
136
|
+
await this.storeOfflineEvent(fallbackEndpoint + (endpoint ? endpoint : ""), data);
|
|
137
|
+
return {
|
|
138
|
+
body: "Stored offline",
|
|
139
|
+
status: 200
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
164
144
|
if (!this.endpoint) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
145
|
+
const rx_endpoint = await this.testConnection().catch(() => null);
|
|
146
|
+
if (rx_endpoint) {
|
|
147
|
+
_endpoint = rx_endpoint + (endpoint ? endpoint : "");
|
|
148
|
+
} else {
|
|
149
|
+
_endpoint = null;
|
|
150
|
+
}
|
|
170
151
|
}
|
|
171
152
|
|
|
172
153
|
if (_endpoint) {
|
|
@@ -204,6 +185,9 @@ class MetaStreamIONetwork {
|
|
|
204
185
|
}
|
|
205
186
|
|
|
206
187
|
async post({ data = {}, endpoint = this.endpoint } = {}) {
|
|
188
|
+
// Non-blocking trigger to flush stuck offline events (if any)
|
|
189
|
+
this.flushEvents();
|
|
190
|
+
|
|
207
191
|
let rx = {};
|
|
208
192
|
|
|
209
193
|
let post_config = {
|
|
@@ -223,95 +207,160 @@ class MetaStreamIONetwork {
|
|
|
223
207
|
post_config.headers = Object.assign(post_config.headers, this.headers);
|
|
224
208
|
}
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
await NetInfo.fetch().then(state => {
|
|
228
|
-
if (!state.isConnected) {
|
|
229
|
-
isOffline = true;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
if (isOffline) {
|
|
210
|
+
if (!this.isOnline) {
|
|
234
211
|
this.logger.log("network", "Offline detected. Storing event.");
|
|
235
212
|
await this.storeOfflineEvent(endpoint, data);
|
|
236
213
|
return { status: 200, body: "Stored offline" };
|
|
237
214
|
}
|
|
238
215
|
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
218
|
+
post_config.signal = controller.signal;
|
|
219
|
+
|
|
239
220
|
await fetch(endpoint, post_config)
|
|
240
221
|
.then((response) => {
|
|
222
|
+
clearTimeout(timeoutId);
|
|
223
|
+
rx.status = response.status;
|
|
241
224
|
let _contentType = response.headers.get("content-type");
|
|
242
225
|
if (!_contentType || !_contentType.includes("application/json")) {
|
|
243
226
|
return null;
|
|
244
227
|
} else {
|
|
245
|
-
rx.status = response.status;
|
|
246
228
|
return response;
|
|
247
229
|
}
|
|
248
230
|
})
|
|
249
231
|
.then(async (response) => {
|
|
250
|
-
|
|
232
|
+
if (response) {
|
|
233
|
+
rx.body = await response.json();
|
|
234
|
+
} else {
|
|
235
|
+
rx.body = "OK";
|
|
236
|
+
}
|
|
251
237
|
})
|
|
252
238
|
.catch(async (err) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
rx.
|
|
257
|
-
|
|
239
|
+
clearTimeout(timeoutId);
|
|
240
|
+
this.logger.warn("network", "fetch() error in post()", err.message || err);
|
|
241
|
+
rx.status = 500;
|
|
242
|
+
rx.body = "fetch error";
|
|
243
|
+
throw err;
|
|
258
244
|
});
|
|
259
245
|
|
|
260
246
|
return rx;
|
|
261
247
|
}
|
|
262
248
|
|
|
249
|
+
async _getQueue() {
|
|
250
|
+
try {
|
|
251
|
+
const raw = await this.storageEngine.getItem(EVENT_QUEUE_KEY);
|
|
252
|
+
if (!raw) return [];
|
|
253
|
+
const parsed = JSON.parse(raw);
|
|
254
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
255
|
+
} catch {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
263
260
|
async storeOfflineEvent(endpoint, payload) {
|
|
264
261
|
try {
|
|
265
|
-
const existing =
|
|
262
|
+
const existing = await this._getQueue();
|
|
266
263
|
const newEvent = {
|
|
267
264
|
endpoint,
|
|
268
265
|
payload,
|
|
269
266
|
failedAt: new Date().toISOString(),
|
|
270
267
|
};
|
|
271
268
|
existing.push(newEvent);
|
|
272
|
-
await
|
|
269
|
+
await this.storageEngine.setItem(EVENT_QUEUE_KEY, JSON.stringify(existing));
|
|
273
270
|
this.logger.log("network", `Stored event locally. Queue size: ${existing.length}`);
|
|
274
271
|
} catch (error) {
|
|
275
|
-
this.logger.error("network", "Failed to store event:", error);
|
|
272
|
+
this.logger.error("network", "Failed to store event:", error.message || error);
|
|
276
273
|
}
|
|
277
274
|
}
|
|
278
275
|
|
|
279
276
|
async flushEvents() {
|
|
277
|
+
if (this._isFlushing) return;
|
|
278
|
+
this._isFlushing = true;
|
|
279
|
+
|
|
280
280
|
try {
|
|
281
|
-
const stored =
|
|
281
|
+
const stored = await this._getQueue();
|
|
282
282
|
if (stored.length === 0) {
|
|
283
283
|
return;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
this.logger.log("network", `Flushing ${stored.length} stored event(s)...`);
|
|
287
|
+
|
|
288
|
+
// Pre-flight check: Do not spam the Native Bridge with 20 offline events if the server route is down
|
|
289
|
+
const pingEndpoint = this.endpoint || (this.endpoints.length > 0 ? this.endpoints[0] : null);
|
|
290
|
+
if (pingEndpoint) {
|
|
291
|
+
let routeValid = false;
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
await fetch(pingEndpoint, { method: "OPTIONS", signal: controller.signal });
|
|
297
|
+
routeValid = true;
|
|
298
|
+
} catch (err) {
|
|
299
|
+
routeValid = false;
|
|
300
|
+
} finally {
|
|
301
|
+
clearTimeout(timeoutId);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!routeValid) {
|
|
305
|
+
this.logger.log("network", "flushEvents aborted: Node route not fully established natively yet.");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
287
310
|
const remainingEvents = [];
|
|
288
311
|
|
|
289
312
|
for (const e of stored) {
|
|
313
|
+
let reqHeaders = { "Content-Type": "application/json" };
|
|
314
|
+
if (this.headers) {
|
|
315
|
+
reqHeaders = Object.assign(reqHeaders, this.headers);
|
|
316
|
+
}
|
|
317
|
+
const controller = new AbortController();
|
|
318
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
319
|
+
|
|
290
320
|
try {
|
|
291
321
|
const response = await fetch(e.endpoint, {
|
|
292
322
|
method: "POST",
|
|
293
|
-
headers:
|
|
323
|
+
headers: reqHeaders,
|
|
294
324
|
body: JSON.stringify(e.payload),
|
|
325
|
+
signal: controller.signal
|
|
295
326
|
});
|
|
327
|
+
clearTimeout(timeoutId);
|
|
296
328
|
|
|
297
329
|
if (response.ok) {
|
|
298
|
-
this.logger.log("network", "Retried & sent event");
|
|
330
|
+
this.logger.log("network", "Retried & sent event successfully.");
|
|
299
331
|
} else {
|
|
300
|
-
|
|
301
|
-
this.logger.warn("network", "Retry failed with non-OK status, dropping event");
|
|
332
|
+
this.logger.warn("network", `Retry got non-OK status (${response.status}), dropping event.`);
|
|
302
333
|
}
|
|
303
334
|
} catch (err) {
|
|
304
|
-
|
|
335
|
+
clearTimeout(timeoutId);
|
|
336
|
+
this.logger.log("network", "Retry failed (still offline or bad host), keeping event.", err.message);
|
|
305
337
|
remainingEvents.push(e);
|
|
306
338
|
}
|
|
307
339
|
}
|
|
308
340
|
|
|
309
|
-
await
|
|
341
|
+
const currentQueue = await this._getQueue();
|
|
342
|
+
// Only keep events that were just inserted (after we started flushing)
|
|
343
|
+
// We assume new events were appended to the end of the array.
|
|
344
|
+
let newAdditions = [];
|
|
345
|
+
if (currentQueue.length > stored.length) {
|
|
346
|
+
newAdditions = currentQueue.slice(stored.length);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const updatedQueue = [...remainingEvents, ...newAdditions];
|
|
350
|
+
|
|
351
|
+
if (updatedQueue.length > 0) {
|
|
352
|
+
await this.storageEngine.setItem(EVENT_QUEUE_KEY, JSON.stringify(updatedQueue));
|
|
353
|
+
} else {
|
|
354
|
+
await this.storageEngine.removeItem(EVENT_QUEUE_KEY);
|
|
355
|
+
}
|
|
356
|
+
|
|
310
357
|
if (remainingEvents.length < stored.length) {
|
|
311
|
-
this.logger.log("network", `Flush complete. Remaining: ${
|
|
358
|
+
this.logger.log("network", `Flush complete. Remaining: ${updatedQueue.length}`);
|
|
312
359
|
}
|
|
313
360
|
} catch (error) {
|
|
314
|
-
this.logger.
|
|
361
|
+
this.logger.warn("network", "Failed to flush events:", error.message || error);
|
|
362
|
+
} finally {
|
|
363
|
+
this._isFlushing = false;
|
|
315
364
|
}
|
|
316
365
|
}
|
|
317
366
|
|
|
@@ -57,12 +57,19 @@ Queue.prototype.peek = function () {
|
|
|
57
57
|
* The MetaStreamIO SAUCE
|
|
58
58
|
*
|
|
59
59
|
*/
|
|
60
|
-
Queue.prototype.run = function (callback) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
Queue.prototype.run = async function (callback) {
|
|
61
|
+
if (this._isRunning) return;
|
|
62
|
+
this._isRunning = true;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
while (this.size() > 0) {
|
|
66
|
+
await callback(this.dequeue()).catch(err => {
|
|
67
|
+
console.error('<queue callback>', err);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
this._isRunning = false;
|
|
65
72
|
}
|
|
66
73
|
};
|
|
67
74
|
|
|
68
|
-
export {Queue};
|
|
75
|
+
export { Queue };
|
|
@@ -139,9 +139,14 @@ class MetaStreamIORecorder {
|
|
|
139
139
|
// Note: We use a separate fetch here because the payload format is different from standard analytics
|
|
140
140
|
const url = `${this.endpoint}/api/apps/${this.app_id}/sessions`;
|
|
141
141
|
this.logger.log("recorder", `Sending session creation request to: ${url}`);
|
|
142
|
+
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
145
|
+
|
|
142
146
|
await fetch(url, {
|
|
143
147
|
method: 'POST',
|
|
144
148
|
headers: { 'Content-Type': 'application/json' },
|
|
149
|
+
signal: controller.signal,
|
|
145
150
|
body: JSON.stringify({
|
|
146
151
|
session_id: this.sessionId,
|
|
147
152
|
device_info: {
|
|
@@ -152,6 +157,7 @@ class MetaStreamIORecorder {
|
|
|
152
157
|
}
|
|
153
158
|
})
|
|
154
159
|
}).then(async res => {
|
|
160
|
+
clearTimeout(timeoutId);
|
|
155
161
|
this.logger.log("recorder", "Session creation response status: " + res.status);
|
|
156
162
|
if (res.ok) {
|
|
157
163
|
const data = await res.json();
|
|
@@ -164,6 +170,7 @@ class MetaStreamIORecorder {
|
|
|
164
170
|
this.logger.error("recorder", "Failed to start session on recording server: " + res.status);
|
|
165
171
|
}
|
|
166
172
|
}).catch(e => {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
167
174
|
this.logger.error("recorder", "Connection error to recording server: " + e + " to " + url);
|
|
168
175
|
});
|
|
169
176
|
|
|
@@ -200,6 +207,9 @@ class MetaStreamIORecorder {
|
|
|
200
207
|
console.log("Starting loop");
|
|
201
208
|
this.intervalId = setInterval(async () => {
|
|
202
209
|
if (!viewRef.current) return;
|
|
210
|
+
if (this._isCapturing) return;
|
|
211
|
+
|
|
212
|
+
this._isCapturing = true;
|
|
203
213
|
|
|
204
214
|
try {
|
|
205
215
|
// Capture as base64 for comparison and direct upload
|
|
@@ -241,13 +251,15 @@ class MetaStreamIORecorder {
|
|
|
241
251
|
// Measure privacy zones
|
|
242
252
|
const zones = await this.measurePrivacyZones(viewRef);
|
|
243
253
|
|
|
244
|
-
console.log("DBG:: Uploading frame with " + zones.length + " privacy zones");
|
|
245
|
-
this.uploadFrame(base64, isDuplicate, zones);
|
|
254
|
+
// console.log("DBG:: Uploading frame with " + zones.length + " privacy zones");
|
|
255
|
+
await this.uploadFrame(base64, isDuplicate, zones);
|
|
246
256
|
this.lastUploadTime = now;
|
|
247
257
|
this.lastBase64 = base64;
|
|
248
258
|
}
|
|
249
259
|
} catch (e) {
|
|
250
260
|
// Silent fail on capture error to avoid log spam
|
|
261
|
+
} finally {
|
|
262
|
+
this._isCapturing = false;
|
|
251
263
|
}
|
|
252
264
|
}, intervalMs);
|
|
253
265
|
}
|
|
@@ -277,13 +289,18 @@ class MetaStreamIORecorder {
|
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
try {
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
294
|
+
|
|
280
295
|
await fetch(`${this.endpoint}/api/apps/${this.app_id}/sessions/${this.recorderSessionId}/screenshot`, {
|
|
281
296
|
method: 'POST',
|
|
282
297
|
body: formData,
|
|
283
298
|
headers: {
|
|
284
299
|
'Content-Type': 'multipart/form-data',
|
|
285
|
-
}
|
|
300
|
+
},
|
|
301
|
+
signal: controller.signal
|
|
286
302
|
});
|
|
303
|
+
clearTimeout(timeoutId);
|
|
287
304
|
} catch (e) {
|
|
288
305
|
// Network error, drop frame
|
|
289
306
|
}
|
|
@@ -304,11 +321,15 @@ class MetaStreamIORecorder {
|
|
|
304
321
|
};
|
|
305
322
|
|
|
306
323
|
try {
|
|
324
|
+
const controller = new AbortController();
|
|
325
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
326
|
+
|
|
307
327
|
fetch(`${this.endpoint}/api/apps/${this.app_id}/sessions/${this.recorderSessionId}/events`, {
|
|
308
328
|
method: 'POST',
|
|
309
329
|
headers: { 'Content-Type': 'application/json' },
|
|
310
|
-
body: JSON.stringify([event])
|
|
311
|
-
|
|
330
|
+
body: JSON.stringify([event]),
|
|
331
|
+
signal: controller.signal
|
|
332
|
+
}).then(() => clearTimeout(timeoutId)).catch(() => clearTimeout(timeoutId));
|
|
312
333
|
} catch (e) { }
|
|
313
334
|
}
|
|
314
335
|
}
|
|
@@ -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.1",
|
|
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
|
},
|