@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.
- package/CHANGELOG.md +15 -0
- package/android/native.gradle +1 -1
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqAPMModule.java +0 -9
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqNetworkLoggerModule.java +7 -29
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +52 -31
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativePackage.java +2 -0
- package/android/src/main/java/ai/luciq/reactlibrary/utils/EventEmitterModule.java +0 -7
- package/android/src/main/java/ai/luciq/reactlibrary/utils/ReportUtil.java +0 -7
- package/dist/modules/Luciq.d.ts +15 -0
- package/dist/modules/Luciq.js +22 -2
- package/dist/modules/NetworkLogger.d.ts +5 -0
- package/dist/modules/NetworkLogger.js +1 -9
- package/dist/native/NativeLuciq.d.ts +3 -0
- package/dist/utils/FeatureFlags.d.ts +0 -6
- package/dist/utils/FeatureFlags.js +0 -35
- package/dist/utils/LuciqUtils.js +0 -6
- package/dist/utils/XhrNetworkInterceptor.js +53 -85
- package/ios/RNLuciq/LuciqReactBridge.m +12 -0
- package/ios/native.rb +1 -1
- package/package.json +2 -1
- package/src/modules/Luciq.ts +25 -3
- package/src/modules/NetworkLogger.ts +1 -26
- package/src/native/NativeLuciq.ts +5 -0
- package/src/utils/FeatureFlags.ts +0 -44
- package/src/utils/LuciqUtils.ts +0 -15
- package/src/utils/XhrNetworkInterceptor.ts +55 -128
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import LuciqConstants from './LuciqConstants';
|
|
2
2
|
import { stringifyIfNotString, generateW3CHeader } from './LuciqUtils';
|
|
3
3
|
|
|
4
|
-
import {
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|