iidrak-analytics-react 1.5.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.
@@ -52,53 +52,35 @@ class MetaStreamIONetwork {
52
52
  }
53
53
  });
54
54
 
55
- // Background heartbeat to auto-flush stuck events gracefully
56
55
  this._flushInterval = setInterval(() => {
57
56
  this.flushEvents();
58
- }, 3000); // Poll every 3 seconds for perfect sync
57
+ }, 3000); // Reverted back to 3 seconds. (Locking guarantees safety)
59
58
  }
60
59
 
61
60
  reset() {
62
61
  this.endpoint = null;
62
+ this._testingPromise = null;
63
63
  }
64
64
 
65
65
  async testConnection() {
66
- if (!this.endpoint || this.endpoint === null) {
66
+ if (this.endpoint) return Promise.resolve(this.endpoint);
67
+ if (this._testingPromise) return this._testingPromise;
68
+
69
+ this._testingPromise = (async () => {
67
70
  for (let _e in this.endpoints) {
68
- this.endpoint = await this.getData({ endpoint: this.endpoints[_e] })
69
- .then((data) => {
70
- try {
71
- if (data && data.status > 0) {
72
- return this.endpoints[_e];
73
- }
74
- return false;
75
- } catch (err) {
76
- return false;
77
- }
78
- })
79
- .catch(() => { return false; });
80
- if (this.endpoint) {
81
- this.logger.log(
82
- "network",
83
- this.constant.MetaStreamIO_Network_EndpointSet.format(this.endpoint)
84
- );
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];
85
75
  break;
86
76
  } else {
87
- this.logger.log(
88
- "network",
89
- this.constant.MetaStreamIO_Network_EndpointTestFailed.format(
90
- this.endpoint
91
- )
92
- );
93
- this.logger.log(
94
- "network",
95
- this.constant.MetaStreamIO_Network_SilentModeEnabled
96
- );
77
+ this.logger.log("network", this.constant.MetaStreamIO_Network_EndpointTestFailed.format(this.endpoints[_e]));
97
78
  }
98
79
  }
99
- }
100
-
101
- return Promise.resolve(this.endpoint);
80
+ this._testingPromise = null;
81
+ return this.endpoint;
82
+ })();
83
+ return this._testingPromise;
102
84
  }
103
85
 
104
86
  async getData({ endpoint = this.endpoint } = {}) {
@@ -115,14 +97,16 @@ class MetaStreamIONetwork {
115
97
  get_config.headers = Object.assign(get_config.headers, this.headers);
116
98
  }
117
99
 
100
+ const controller = new AbortController();
101
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
102
+ get_config.signal = controller.signal;
103
+
118
104
  try {
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
- ]);
105
+ const response = await fetch(endpoint, get_config);
106
+ clearTimeout(timeoutId);
124
107
  return { status: response.status, body: "OK" };
125
108
  } catch (err) {
109
+ clearTimeout(timeoutId);
126
110
  return { status: 0, body: String(err) };
127
111
  }
128
112
  }
@@ -229,13 +213,13 @@ class MetaStreamIONetwork {
229
213
  return { status: 200, body: "Stored offline" };
230
214
  }
231
215
 
232
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
216
+ const controller = new AbortController();
217
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
218
+ post_config.signal = controller.signal;
233
219
 
234
- await Promise.race([
235
- fetch(endpoint, post_config),
236
- timeoutPromise
237
- ])
220
+ await fetch(endpoint, post_config)
238
221
  .then((response) => {
222
+ clearTimeout(timeoutId);
239
223
  rx.status = response.status;
240
224
  let _contentType = response.headers.get("content-type");
241
225
  if (!_contentType || !_contentType.includes("application/json")) {
@@ -252,6 +236,7 @@ class MetaStreamIONetwork {
252
236
  }
253
237
  })
254
238
  .catch(async (err) => {
239
+ clearTimeout(timeoutId);
255
240
  this.logger.warn("network", "fetch() error in post()", err.message || err);
256
241
  rx.status = 500;
257
242
  rx.body = "fetch error";
@@ -303,11 +288,18 @@ class MetaStreamIONetwork {
303
288
  // Pre-flight check: Do not spam the Native Bridge with 20 offline events if the server route is down
304
289
  const pingEndpoint = this.endpoint || (this.endpoints.length > 0 ? this.endpoints[0] : null);
305
290
  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);
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
+ }
311
303
 
312
304
  if (!routeValid) {
313
305
  this.logger.log("network", "flushEvents aborted: Node route not fully established natively yet.");
@@ -322,17 +314,17 @@ class MetaStreamIONetwork {
322
314
  if (this.headers) {
323
315
  reqHeaders = Object.assign(reqHeaders, this.headers);
324
316
  }
325
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
317
+ const controller = new AbortController();
318
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
326
319
 
327
320
  try {
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
- ]);
321
+ const response = await fetch(e.endpoint, {
322
+ method: "POST",
323
+ headers: reqHeaders,
324
+ body: JSON.stringify(e.payload),
325
+ signal: controller.signal
326
+ });
327
+ clearTimeout(timeoutId);
336
328
 
337
329
  if (response.ok) {
338
330
  this.logger.log("network", "Retried & sent event successfully.");
@@ -340,6 +332,7 @@ class MetaStreamIONetwork {
340
332
  this.logger.warn("network", `Retry got non-OK status (${response.status}), dropping event.`);
341
333
  }
342
334
  } catch (err) {
335
+ clearTimeout(timeoutId);
343
336
  this.logger.log("network", "Retry failed (still offline or bad host), keeping event.", err.message);
344
337
  remainingEvents.push(e);
345
338
  }
@@ -57,12 +57,19 @@ Queue.prototype.peek = function () {
57
57
  * The MetaStreamIO SAUCE
58
58
  *
59
59
  */
60
- Queue.prototype.run = function (callback) {
61
- while (this.size() > 0) {
62
- callback(this.dequeue()).catch(err => {
63
- console.error('<queue callback>', err);
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
- }).catch(() => { });
330
+ body: JSON.stringify([event]),
331
+ signal: controller.signal
332
+ }).then(() => clearTimeout(timeoutId)).catch(() => clearTimeout(timeoutId));
312
333
  } catch (e) { }
313
334
  }
314
335
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iidrak-analytics-react",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "react native client for metastreamio",
5
5
  "peerDependencies": {
6
6
  "react-native-mmkv": ">=4.0.0",