@luciq/react-native 19.3.0-40271-SNAPSHOT → 19.4.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.
@@ -1,10 +1,7 @@
1
1
  import LuciqConstants from './LuciqConstants';
2
2
  import { stringifyIfNotString, generateW3CHeader } from './LuciqUtils';
3
3
 
4
- import { getCachedW3cFlags } from './FeatureFlags';
5
- import { Logger } from './logger';
6
-
7
- const TAG = 'LCQ-RN-NET:';
4
+ import { FeatureFlags } from '../utils/FeatureFlags';
8
5
 
9
6
  export type ProgressCallback = (totalBytesSent: number, totalBytesExpectedToSend: number) => void;
10
7
  export type NetworkDataCallback = (data: NetworkData) => void;
@@ -43,41 +40,45 @@ let originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
43
40
  let onProgressCallback: ProgressCallback | null;
44
41
  let onDoneCallback: NetworkDataCallback | null;
45
42
  let isInterceptorEnabled = false;
46
-
47
- const networkMap = new WeakMap<XMLHttpRequest, NetworkData>();
48
-
49
- const createNetworkData = (): NetworkData => ({
50
- id: '',
51
- url: '',
52
- method: '',
53
- requestBody: '',
54
- requestBodySize: 0,
55
- responseBody: '',
56
- responseBodySize: 0,
57
- responseCode: 0,
58
- requestHeaders: {},
59
- responseHeaders: {},
60
- contentType: '',
61
- errorDomain: '',
62
- errorCode: 0,
63
- startTime: 0,
64
- duration: 0,
65
- gqlQueryName: '',
66
- serverErrorMessage: '',
67
- requestContentType: '',
68
- isW3cHeaderFound: null,
69
- partialId: null,
70
- networkStartTimeInSeconds: null,
71
- w3cGeneratedHeader: null,
72
- w3cCaughtHeader: null,
73
- });
74
-
75
- const getTraceparentHeader = (networkData: NetworkData) => {
76
- const {
43
+ let network: NetworkData;
44
+
45
+ const _reset = () => {
46
+ network = {
47
+ id: '',
48
+ url: '',
49
+ method: '',
50
+ requestBody: '',
51
+ requestBodySize: 0,
52
+ responseBody: '',
53
+ responseBodySize: 0,
54
+ responseCode: 0,
55
+ requestHeaders: {},
56
+ responseHeaders: {},
57
+ contentType: '',
58
+ errorDomain: '',
59
+ errorCode: 0,
60
+ startTime: 0,
61
+ duration: 0,
62
+ gqlQueryName: '',
63
+ serverErrorMessage: '',
64
+ requestContentType: '',
65
+ isW3cHeaderFound: null,
66
+ partialId: null,
67
+ networkStartTimeInSeconds: null,
68
+ w3cGeneratedHeader: null,
69
+ w3cCaughtHeader: null,
70
+ };
71
+ };
72
+ const getTraceparentHeader = async (networkData: NetworkData) => {
73
+ const [
77
74
  isW3cExternalTraceIDEnabled,
78
75
  isW3cExternalGeneratedHeaderEnabled,
79
76
  isW3cCaughtHeaderEnabled,
80
- } = getCachedW3cFlags();
77
+ ] = await Promise.all([
78
+ FeatureFlags.isW3ExternalTraceID(),
79
+ FeatureFlags.isW3ExternalGeneratedHeader(),
80
+ FeatureFlags.isW3CaughtHeader(),
81
+ ]);
81
82
 
82
83
  return injectHeaders(networkData, {
83
84
  isW3cExternalTraceIDEnabled,
@@ -146,77 +147,44 @@ export default {
146
147
  onProgressCallback = callback;
147
148
  },
148
149
  enableInterception() {
150
+ // Prevents infinite calls to XMLHttpRequest.open when enabling interception multiple times
149
151
  if (isInterceptorEnabled) {
150
- Logger.debug(TAG, 'enableInterception called but already enabled, skipping');
151
152
  return;
152
153
  }
153
154
 
154
- Logger.debug(TAG, 'Enabling XHR network interception');
155
-
156
155
  originalXHROpen = XMLHttpRequest.prototype.open;
157
156
  originalXHRSend = XMLHttpRequest.prototype.send;
158
157
  originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
159
158
  // An error code that signifies an issue with the RN client.
160
159
  const clientErrorCode = 9876;
161
160
  XMLHttpRequest.prototype.open = function (method, url, ...args) {
162
- const networkData = createNetworkData();
163
- networkData.url = url;
164
- networkData.method = method;
165
- networkMap.set(this, networkData);
166
- Logger.debug(TAG, `[open] ${method} ${url}`);
161
+ _reset();
162
+ network.url = url;
163
+ network.method = method;
167
164
  originalXHROpen.apply(this, [method, url, ...args]);
168
165
  };
169
166
 
170
167
  XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
168
+ // According to the HTTP RFC, headers are case-insensitive, so we convert
169
+ // them to lower-case to make accessing headers predictable.
170
+ // This avoid issues like failing to get the Content-Type header for a request
171
+ // because the header is set as 'Content-Type' instead of 'content-type'.
171
172
  const key = header.toLowerCase();
172
- const networkData = networkMap.get(this);
173
- if (networkData) {
174
- networkData.requestHeaders[key] = stringifyIfNotString(value);
175
- } else {
176
- Logger.debug(
177
- TAG,
178
- `[setRequestHeader] No networkData found in WeakMap for header "${key}" — request may have been GC'd or open() was not called`,
179
- );
180
- }
173
+ network.requestHeaders[key] = stringifyIfNotString(value);
181
174
  originalXHRSetRequestHeader.apply(this, [header, value]);
182
175
  };
183
176
 
184
- XMLHttpRequest.prototype.send = function (data) {
185
- const networkData = networkMap.get(this);
186
- if (!networkData) {
187
- Logger.debug(
188
- TAG,
189
- '[send] No networkData found in WeakMap — falling back to original send (open() was not intercepted)',
190
- );
191
- originalXHRSend.apply(this, [data]);
192
- return;
193
- }
194
-
195
- Logger.debug(TAG, `[send] ${networkData.method} ${networkData.url}`);
196
-
197
- const cloneNetwork = JSON.parse(JSON.stringify(networkData));
177
+ XMLHttpRequest.prototype.send = async function (data) {
178
+ const cloneNetwork = JSON.parse(JSON.stringify(network));
198
179
  cloneNetwork.requestBody = data ? data : '';
199
180
 
200
181
  if (typeof cloneNetwork.requestBody !== 'string') {
201
182
  cloneNetwork.requestBody = JSON.stringify(cloneNetwork.requestBody);
202
183
  }
203
184
 
204
- let isReported = false;
205
-
206
185
  if (this.addEventListener) {
207
186
  this.addEventListener('readystatechange', async () => {
208
187
  if (!isInterceptorEnabled) {
209
- Logger.debug(
210
- TAG,
211
- `[readystatechange] Interceptor disabled, ignoring state=${this.readyState} for ${cloneNetwork.url}`,
212
- );
213
- return;
214
- }
215
- if (isReported) {
216
- Logger.debug(
217
- TAG,
218
- `[readystatechange] Already reported, ignoring state=${this.readyState} for ${cloneNetwork.url}`,
219
- );
220
188
  return;
221
189
  }
222
190
  if (this.readyState === this.HEADERS_RECEIVED) {
@@ -249,11 +217,6 @@ export default {
249
217
  cloneNetwork.requestContentType =
250
218
  cloneNetwork.requestHeaders['content-type'].split(';')[0];
251
219
  }
252
-
253
- Logger.debug(
254
- TAG,
255
- `[readystatechange] HEADERS_RECEIVED for ${cloneNetwork.url}, contentType=${cloneNetwork.contentType}`,
256
- );
257
220
  }
258
221
 
259
222
  if (this.readyState === this.DONE) {
@@ -276,15 +239,12 @@ export default {
276
239
  typeof _response === 'string' ? _response : JSON.stringify(_response);
277
240
  cloneNetwork.responseBody = '';
278
241
 
242
+ // Detect a more descriptive error message.
279
243
  if (typeof _response === 'string' && _response.length > 0) {
280
244
  cloneNetwork.errorDomain = _response;
281
245
  }
282
246
 
283
247
  cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
284
- Logger.debug(
285
- TAG,
286
- `[readystatechange] DONE with client error for ${cloneNetwork.url}, errorDomain=${cloneNetwork.errorDomain}`,
287
- );
288
248
 
289
249
  // @ts-ignore
290
250
  } else if (this._timedOut) {
@@ -293,7 +253,6 @@ export default {
293
253
  cloneNetwork.responseCode = 0;
294
254
  cloneNetwork.contentType = 'text/plain';
295
255
  cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
296
- Logger.debug(TAG, `[readystatechange] DONE with timeout for ${cloneNetwork.url}`);
297
256
  }
298
257
 
299
258
  // Only set response body if not already set by error handlers
@@ -341,18 +300,8 @@ export default {
341
300
  delete cloneNetwork.gqlQueryName;
342
301
  }
343
302
 
344
- isReported = true;
345
- Logger.debug(
346
- TAG,
347
- `[readystatechange] DONE for ${cloneNetwork.method} ${cloneNetwork.url} — status=${cloneNetwork.responseCode}, duration=${cloneNetwork.duration}ms, hasCallback=${!!onDoneCallback}`,
348
- );
349
303
  if (onDoneCallback) {
350
304
  onDoneCallback(cloneNetwork);
351
- } else {
352
- Logger.debug(
353
- TAG,
354
- `[readystatechange] WARNING: onDoneCallback is null, network log for ${cloneNetwork.url} will be LOST`,
355
- );
356
305
  }
357
306
  }
358
307
  });
@@ -361,6 +310,7 @@ export default {
361
310
  if (!isInterceptorEnabled) {
362
311
  return;
363
312
  }
313
+ // check if will be able to compute progress
364
314
  if (event.lengthComputable && onProgressCallback) {
365
315
  const totalBytesSent = event.loaded;
366
316
  const totalBytesExpectedToSend = event.total - event.loaded;
@@ -370,57 +320,34 @@ export default {
370
320
  this.addEventListener('progress', downloadUploadProgressCallback);
371
321
  this.upload.addEventListener('progress', downloadUploadProgressCallback);
372
322
 
323
+ // Handler for abort events (works with fetch, Axios, and any XHR-based requests)
373
324
  this.addEventListener('abort', () => {
374
325
  if (!isInterceptorEnabled) {
375
- Logger.debug(
376
- TAG,
377
- `[abort] Interceptor disabled, ignoring abort for ${cloneNetwork.url}`,
378
- );
379
326
  return;
380
327
  }
381
- if (isReported) {
382
- Logger.debug(
383
- TAG,
384
- `[abort] Already reported via readystatechange DONE, ignoring duplicate abort for ${cloneNetwork.url}`,
385
- );
386
- return;
387
- }
388
- isReported = true;
389
328
  cloneNetwork.duration = Date.now() - cloneNetwork.startTime;
390
329
  cloneNetwork.responseCode = 0;
391
330
  cloneNetwork.errorCode = clientErrorCode;
392
331
  cloneNetwork.errorDomain = 'cancelled';
393
332
  cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
394
- Logger.debug(
395
- TAG,
396
- `[abort] Request cancelled: ${cloneNetwork.method} ${cloneNetwork.url}, duration=${cloneNetwork.duration}ms, hasCallback=${!!onDoneCallback}`,
397
- );
398
- if (onDoneCallback) {
399
- onDoneCallback(cloneNetwork);
400
- } else {
401
- Logger.debug(
402
- TAG,
403
- `[abort] WARNING: onDoneCallback is null, cancelled log for ${cloneNetwork.url} will be LOST`,
404
- );
405
- }
406
333
  });
407
334
  }
408
335
 
409
336
  cloneNetwork.startTime = Date.now();
410
- const traceparent = getTraceparentHeader(cloneNetwork);
337
+ const traceparent = await getTraceparentHeader(cloneNetwork);
411
338
  if (traceparent) {
412
339
  this.setRequestHeader('Traceparent', traceparent);
413
- Logger.debug(TAG, `[send] Injected traceparent header for ${cloneNetwork.url}`);
340
+ }
341
+ if (this.readyState === this.UNSENT) {
342
+ return; // Prevent sending the request if not opened
414
343
  }
415
344
 
416
345
  originalXHRSend.apply(this, [data]);
417
346
  };
418
347
  isInterceptorEnabled = true;
419
- Logger.debug(TAG, 'XHR network interception enabled');
420
348
  },
421
349
 
422
350
  disableInterception() {
423
- Logger.debug(TAG, 'Disabling XHR network interception');
424
351
  isInterceptorEnabled = false;
425
352
  XMLHttpRequest.prototype.send = originalXHRSend;
426
353
  XMLHttpRequest.prototype.open = originalXHROpen;