@logbrew/react-native 0.1.0 → 0.1.2
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 +431 -18
- package/apollo.cjs +301 -0
- package/apollo.d.cts +70 -0
- package/apollo.d.ts +70 -0
- package/apollo.js +294 -0
- package/examples/apollo-link-spans.mjs +149 -0
- package/examples/index.mjs +29 -1
- package/examples/instrumentation-kit.mjs +304 -0
- package/examples/lifecycle-spans.mjs +131 -0
- package/examples/native-bridge-scope.mjs +107 -0
- package/examples/navigation-resource-spans.mjs +156 -0
- package/examples/package.json +8 -1
- package/examples/real-user-smoke.mjs +106 -9
- package/examples/resource-fetch-spans.mjs +133 -0
- package/examples/trace-correlation.mjs +135 -0
- package/global-errors.cjs +366 -0
- package/global-errors.d.cts +63 -0
- package/global-errors.d.ts +63 -0
- package/global-errors.js +7 -0
- package/index.cjs +625 -111
- package/index.d.cts +236 -0
- package/index.d.ts +236 -0
- package/index.js +613 -95
- package/index.native.js +18 -0
- package/instrumentation.cjs +639 -0
- package/instrumentation.d.cts +84 -0
- package/instrumentation.d.ts +84 -0
- package/instrumentation.js +634 -0
- package/lifecycle.cjs +129 -0
- package/lifecycle.d.cts +50 -0
- package/lifecycle.d.ts +50 -0
- package/lifecycle.js +121 -0
- package/metadata.cjs +175 -0
- package/metadata.js +165 -0
- package/metro.cjs +310 -0
- package/metro.d.cts +37 -0
- package/metro.d.ts +37 -0
- package/metro.js +6 -0
- package/native-bridge.cjs +127 -0
- package/native-bridge.d.cts +60 -0
- package/native-bridge.d.ts +60 -0
- package/native-bridge.js +125 -0
- package/package.json +128 -4
- package/release-artifacts.cjs +344 -0
- package/release-artifacts.d.cts +55 -0
- package/release-artifacts.d.ts +53 -0
- package/release-artifacts.js +8 -0
- package/resource-fetch.cjs +469 -0
- package/resource-fetch.d.cts +60 -0
- package/resource-fetch.d.ts +60 -0
- package/resource-fetch.js +464 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
import { SdkError } from "@logbrew/sdk";
|
|
2
|
+
import {
|
|
3
|
+
captureReactNativeResourceSpan,
|
|
4
|
+
createReactNavigationSpanListener,
|
|
5
|
+
createReactNativeTraceContext,
|
|
6
|
+
createReactNativeTraceHeaders,
|
|
7
|
+
getActiveLogBrewTrace,
|
|
8
|
+
shouldPropagateTraceparent
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
import { createAppStateLifecycleSpanListener } from "./lifecycle.js";
|
|
11
|
+
import { createSafeReactNativeMetadata } from "./metadata.js";
|
|
12
|
+
import {
|
|
13
|
+
clearLogBrewNativeBridgeScope,
|
|
14
|
+
syncLogBrewNativeBridgeScope,
|
|
15
|
+
withLogBrewNativeBridgeScope
|
|
16
|
+
} from "./native-bridge.js";
|
|
17
|
+
import { createReactNativeResourceFetch } from "./resource-fetch.js";
|
|
18
|
+
|
|
19
|
+
export function createLogBrewReactNativeInstrumentation(client, {
|
|
20
|
+
appState,
|
|
21
|
+
captureInitialLifecycleState = false,
|
|
22
|
+
captureInitialNavigationRoute = false,
|
|
23
|
+
fetchImpl,
|
|
24
|
+
globalObject = globalThis,
|
|
25
|
+
includeRouteKey = false,
|
|
26
|
+
instrumentGlobalFetch = false,
|
|
27
|
+
instrumentGlobalXMLHttpRequest = false,
|
|
28
|
+
logger,
|
|
29
|
+
measureFetchResponseBodySize = false,
|
|
30
|
+
measureXhrResponseBodySize = false,
|
|
31
|
+
metadata = {},
|
|
32
|
+
metadataFactory,
|
|
33
|
+
nativeBridge,
|
|
34
|
+
navigation,
|
|
35
|
+
navigationContainer,
|
|
36
|
+
now = () => new Date().toISOString(),
|
|
37
|
+
nowMs = () => Date.now(),
|
|
38
|
+
onError,
|
|
39
|
+
platform,
|
|
40
|
+
randomValues,
|
|
41
|
+
routeTemplate,
|
|
42
|
+
routeTemplateFactory,
|
|
43
|
+
screen,
|
|
44
|
+
sessionId,
|
|
45
|
+
trace,
|
|
46
|
+
traceFlags = "01",
|
|
47
|
+
tracePropagationTargets = []
|
|
48
|
+
} = {}) {
|
|
49
|
+
requireClient(client);
|
|
50
|
+
const activeTrace = resolveInstrumentationTrace({ randomValues, trace, traceFlags });
|
|
51
|
+
const removers = [];
|
|
52
|
+
const resolvedNavigation = navigationContainer ?? navigation;
|
|
53
|
+
|
|
54
|
+
if (appState?.addEventListener) {
|
|
55
|
+
removers.push(createAppStateLifecycleSpanListener(client, appState, {
|
|
56
|
+
captureInitialState: captureInitialLifecycleState,
|
|
57
|
+
metadata,
|
|
58
|
+
now,
|
|
59
|
+
nowMs,
|
|
60
|
+
onError,
|
|
61
|
+
platform,
|
|
62
|
+
screen,
|
|
63
|
+
sessionId,
|
|
64
|
+
trace: activeTrace
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (resolvedNavigation) {
|
|
69
|
+
removers.push(createReactNavigationSpanListener(client, resolvedNavigation, {
|
|
70
|
+
appState,
|
|
71
|
+
captureInitialRoute: captureInitialNavigationRoute,
|
|
72
|
+
includeRouteKey,
|
|
73
|
+
metadata,
|
|
74
|
+
now,
|
|
75
|
+
nowMs,
|
|
76
|
+
onError,
|
|
77
|
+
platform,
|
|
78
|
+
trace: activeTrace
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (nativeBridge) {
|
|
83
|
+
syncLogBrewNativeBridgeScope(nativeBridge, {
|
|
84
|
+
logger,
|
|
85
|
+
metadata,
|
|
86
|
+
screen,
|
|
87
|
+
sessionId,
|
|
88
|
+
source: "react-native.instrumentation",
|
|
89
|
+
trace: activeTrace
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resourceFetch = createReactNativeResourceFetch(client, {
|
|
94
|
+
appState,
|
|
95
|
+
fetchImpl,
|
|
96
|
+
measureResponseBodySize: measureFetchResponseBodySize,
|
|
97
|
+
metadata,
|
|
98
|
+
metadataFactory,
|
|
99
|
+
now,
|
|
100
|
+
nowMs,
|
|
101
|
+
platform,
|
|
102
|
+
randomValues,
|
|
103
|
+
routeTemplate,
|
|
104
|
+
routeTemplateFactory,
|
|
105
|
+
screen,
|
|
106
|
+
sessionId,
|
|
107
|
+
trace: activeTrace,
|
|
108
|
+
traceFlags,
|
|
109
|
+
tracePropagationTargets
|
|
110
|
+
});
|
|
111
|
+
let globalFetch;
|
|
112
|
+
let globalXMLHttpRequest;
|
|
113
|
+
try {
|
|
114
|
+
globalFetch = instrumentGlobalFetch ? installGlobalFetchInstrumentation(client, {
|
|
115
|
+
appState,
|
|
116
|
+
globalObject,
|
|
117
|
+
measureFetchResponseBodySize,
|
|
118
|
+
metadata,
|
|
119
|
+
metadataFactory,
|
|
120
|
+
now,
|
|
121
|
+
nowMs,
|
|
122
|
+
platform,
|
|
123
|
+
randomValues,
|
|
124
|
+
routeTemplate,
|
|
125
|
+
routeTemplateFactory,
|
|
126
|
+
screen,
|
|
127
|
+
sessionId,
|
|
128
|
+
trace: activeTrace,
|
|
129
|
+
traceFlags,
|
|
130
|
+
tracePropagationTargets
|
|
131
|
+
}) : undefined;
|
|
132
|
+
if (globalFetch) {
|
|
133
|
+
removers.push(globalFetch.remove);
|
|
134
|
+
}
|
|
135
|
+
globalXMLHttpRequest = instrumentGlobalXMLHttpRequest ? installGlobalXMLHttpRequestInstrumentation(client, {
|
|
136
|
+
appState,
|
|
137
|
+
globalObject,
|
|
138
|
+
measureXhrResponseBodySize,
|
|
139
|
+
metadata,
|
|
140
|
+
metadataFactory,
|
|
141
|
+
now,
|
|
142
|
+
nowMs,
|
|
143
|
+
platform,
|
|
144
|
+
routeTemplate,
|
|
145
|
+
routeTemplateFactory,
|
|
146
|
+
screen,
|
|
147
|
+
sessionId,
|
|
148
|
+
trace: activeTrace,
|
|
149
|
+
tracePropagationTargets
|
|
150
|
+
}) : undefined;
|
|
151
|
+
if (globalXMLHttpRequest) {
|
|
152
|
+
removers.push(globalXMLHttpRequest.remove);
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
removeConfiguredInstrumentation({ nativeBridge, removers });
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let removed = false;
|
|
160
|
+
const remove = () => {
|
|
161
|
+
if (removed) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
removed = true;
|
|
165
|
+
removeConfiguredInstrumentation({ nativeBridge, removers });
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handle = {
|
|
169
|
+
globalFetch,
|
|
170
|
+
globalXMLHttpRequest,
|
|
171
|
+
remove,
|
|
172
|
+
resourceFetch,
|
|
173
|
+
stop: remove,
|
|
174
|
+
syncNativeBridgeScope(options = {}) {
|
|
175
|
+
if (!nativeBridge) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return syncLogBrewNativeBridgeScope(nativeBridge, {
|
|
179
|
+
logger,
|
|
180
|
+
metadata,
|
|
181
|
+
screen,
|
|
182
|
+
sessionId,
|
|
183
|
+
source: "react-native.instrumentation",
|
|
184
|
+
trace: activeTrace,
|
|
185
|
+
...options
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
trace: activeTrace,
|
|
189
|
+
withNativeBridgeScope(callbackOrOptions, maybeCallback) {
|
|
190
|
+
if (!nativeBridge) {
|
|
191
|
+
throw new SdkError("configuration_error", "withNativeBridgeScope requires nativeBridge");
|
|
192
|
+
}
|
|
193
|
+
if (typeof callbackOrOptions === "function") {
|
|
194
|
+
return withLogBrewNativeBridgeScope(nativeBridge, {
|
|
195
|
+
logger,
|
|
196
|
+
metadata,
|
|
197
|
+
screen,
|
|
198
|
+
sessionId,
|
|
199
|
+
source: "react-native.instrumentation",
|
|
200
|
+
trace: activeTrace
|
|
201
|
+
}, callbackOrOptions);
|
|
202
|
+
}
|
|
203
|
+
return withLogBrewNativeBridgeScope(nativeBridge, {
|
|
204
|
+
logger,
|
|
205
|
+
metadata,
|
|
206
|
+
screen,
|
|
207
|
+
sessionId,
|
|
208
|
+
source: "react-native.instrumentation",
|
|
209
|
+
trace: activeTrace,
|
|
210
|
+
...(callbackOrOptions ?? {})
|
|
211
|
+
}, maybeCallback);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
return Object.freeze(handle);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveInstrumentationTrace({ randomValues, trace, traceFlags }) {
|
|
218
|
+
if (typeof trace === "string") {
|
|
219
|
+
return createReactNativeTraceContext({ randomValues, traceFlags, traceparent: trace });
|
|
220
|
+
}
|
|
221
|
+
return trace ?? getActiveLogBrewTrace() ?? createReactNativeTraceContext({ randomValues, traceFlags });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function requireClient(client) {
|
|
225
|
+
if (!client) {
|
|
226
|
+
throw new SdkError("configuration_error", "createLogBrewReactNativeInstrumentation requires a client");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function removeConfiguredInstrumentation({ nativeBridge, removers }) {
|
|
231
|
+
if (nativeBridge) {
|
|
232
|
+
clearLogBrewNativeBridgeScope(nativeBridge);
|
|
233
|
+
}
|
|
234
|
+
for (const removeListener of removers.splice(0).reverse()) {
|
|
235
|
+
removeListener();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function installGlobalFetchInstrumentation(client, {
|
|
240
|
+
appState,
|
|
241
|
+
globalObject,
|
|
242
|
+
measureFetchResponseBodySize,
|
|
243
|
+
metadata,
|
|
244
|
+
metadataFactory,
|
|
245
|
+
now,
|
|
246
|
+
nowMs,
|
|
247
|
+
platform,
|
|
248
|
+
randomValues,
|
|
249
|
+
routeTemplate,
|
|
250
|
+
routeTemplateFactory,
|
|
251
|
+
screen,
|
|
252
|
+
sessionId,
|
|
253
|
+
trace,
|
|
254
|
+
traceFlags,
|
|
255
|
+
tracePropagationTargets
|
|
256
|
+
}) {
|
|
257
|
+
if ((typeof globalObject !== "object" && typeof globalObject !== "function") || globalObject === null) {
|
|
258
|
+
throw new SdkError("configuration_error", "instrumentGlobalFetch requires a globalObject");
|
|
259
|
+
}
|
|
260
|
+
const originalFetch = globalObject.fetch;
|
|
261
|
+
if (typeof originalFetch !== "function") {
|
|
262
|
+
throw new SdkError("configuration_error", "instrumentGlobalFetch requires globalObject.fetch");
|
|
263
|
+
}
|
|
264
|
+
const globalResourceFetch = createReactNativeResourceFetch(client, {
|
|
265
|
+
appState,
|
|
266
|
+
fetchImpl: (input, init) => originalFetch.call(globalObject, input, init),
|
|
267
|
+
measureResponseBodySize: measureFetchResponseBodySize,
|
|
268
|
+
metadata,
|
|
269
|
+
metadataFactory,
|
|
270
|
+
now,
|
|
271
|
+
nowMs,
|
|
272
|
+
platform,
|
|
273
|
+
randomValues,
|
|
274
|
+
routeTemplate,
|
|
275
|
+
routeTemplateFactory,
|
|
276
|
+
screen,
|
|
277
|
+
sessionId,
|
|
278
|
+
trace,
|
|
279
|
+
traceFlags,
|
|
280
|
+
tracePropagationTargets
|
|
281
|
+
});
|
|
282
|
+
let removed = false;
|
|
283
|
+
const wrappedFetch = (input, init) => globalResourceFetch(input, init);
|
|
284
|
+
try {
|
|
285
|
+
globalObject.fetch = wrappedFetch;
|
|
286
|
+
} catch {
|
|
287
|
+
throw new SdkError("configuration_error", "instrumentGlobalFetch could not patch globalObject.fetch");
|
|
288
|
+
}
|
|
289
|
+
if (globalObject.fetch !== wrappedFetch) {
|
|
290
|
+
throw new SdkError("configuration_error", "instrumentGlobalFetch could not patch globalObject.fetch");
|
|
291
|
+
}
|
|
292
|
+
const remove = () => {
|
|
293
|
+
if (removed) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
removed = true;
|
|
297
|
+
if (globalObject.fetch === wrappedFetch) {
|
|
298
|
+
globalObject.fetch = originalFetch;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
return Object.freeze({
|
|
302
|
+
fetch: wrappedFetch,
|
|
303
|
+
remove,
|
|
304
|
+
stop: remove
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* eslint-disable no-invalid-this */
|
|
309
|
+
function installGlobalXMLHttpRequestInstrumentation(client, {
|
|
310
|
+
appState,
|
|
311
|
+
globalObject,
|
|
312
|
+
measureXhrResponseBodySize,
|
|
313
|
+
metadata,
|
|
314
|
+
metadataFactory,
|
|
315
|
+
now,
|
|
316
|
+
nowMs,
|
|
317
|
+
platform,
|
|
318
|
+
routeTemplate,
|
|
319
|
+
routeTemplateFactory,
|
|
320
|
+
screen,
|
|
321
|
+
sessionId,
|
|
322
|
+
trace,
|
|
323
|
+
tracePropagationTargets
|
|
324
|
+
}) {
|
|
325
|
+
if ((typeof globalObject !== "object" && typeof globalObject !== "function") || globalObject === null) {
|
|
326
|
+
throw new SdkError("configuration_error", "instrumentGlobalXMLHttpRequest requires a globalObject");
|
|
327
|
+
}
|
|
328
|
+
const xhrType = globalObject.XMLHttpRequest;
|
|
329
|
+
if (typeof xhrType !== "function" || !xhrType.prototype) {
|
|
330
|
+
throw new SdkError("configuration_error", "instrumentGlobalXMLHttpRequest requires globalObject.XMLHttpRequest");
|
|
331
|
+
}
|
|
332
|
+
const prototype = xhrType.prototype;
|
|
333
|
+
const originalOpen = prototype.open;
|
|
334
|
+
const originalSend = prototype.send;
|
|
335
|
+
const originalSetRequestHeader = prototype.setRequestHeader;
|
|
336
|
+
if (typeof originalOpen !== "function" || typeof originalSend !== "function" || typeof originalSetRequestHeader !== "function") {
|
|
337
|
+
throw new SdkError("configuration_error", "instrumentGlobalXMLHttpRequest requires open, send, and setRequestHeader");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const contextKey = Symbol("logbrew.xhr");
|
|
341
|
+
const headersReceivedState = typeof xhrType.HEADERS_RECEIVED === "number" ? xhrType.HEADERS_RECEIVED : 2;
|
|
342
|
+
const doneState = typeof xhrType.DONE === "number" ? xhrType.DONE : 4;
|
|
343
|
+
const safeRouteTemplateFactory = routeTemplateFactory ?? defaultXhrRouteTemplateFactory;
|
|
344
|
+
|
|
345
|
+
function wrappedOpen(method, url) {
|
|
346
|
+
this[contextKey] = {
|
|
347
|
+
method: normalizeXhrMethod(method),
|
|
348
|
+
reported: false,
|
|
349
|
+
url: xhrUrl(url)
|
|
350
|
+
};
|
|
351
|
+
return originalOpen.apply(this, arguments);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function wrappedSend(body) {
|
|
355
|
+
const context = this[contextKey];
|
|
356
|
+
if (!context) {
|
|
357
|
+
return originalSend.apply(this, arguments);
|
|
358
|
+
}
|
|
359
|
+
context.body = xhrRequestBody(body);
|
|
360
|
+
context.startedAtMs = nowMs();
|
|
361
|
+
context.timestamp = now();
|
|
362
|
+
installXhrReadyStateTracker(this, context, {
|
|
363
|
+
appState,
|
|
364
|
+
client,
|
|
365
|
+
doneState,
|
|
366
|
+
headersReceivedState,
|
|
367
|
+
measureXhrResponseBodySize,
|
|
368
|
+
metadata,
|
|
369
|
+
metadataFactory,
|
|
370
|
+
nowMs,
|
|
371
|
+
platform,
|
|
372
|
+
routeTemplate,
|
|
373
|
+
routeTemplateFactory: safeRouteTemplateFactory,
|
|
374
|
+
screen,
|
|
375
|
+
sessionId,
|
|
376
|
+
trace
|
|
377
|
+
});
|
|
378
|
+
if (shouldPropagateTraceparent(context.url, tracePropagationTargets)) {
|
|
379
|
+
originalSetRequestHeader.call(this, "traceparent", createReactNativeTraceHeaders(trace).traceparent);
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
return originalSend.apply(this, arguments);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
captureXhrResourceSpan(this, context, {
|
|
385
|
+
appState,
|
|
386
|
+
client,
|
|
387
|
+
metadata: {
|
|
388
|
+
...metadata,
|
|
389
|
+
xhrErrorName: errorName(error),
|
|
390
|
+
xhrErrorValueType: typeof error
|
|
391
|
+
},
|
|
392
|
+
metadataFactory,
|
|
393
|
+
nowMs,
|
|
394
|
+
platform,
|
|
395
|
+
routeTemplate,
|
|
396
|
+
routeTemplateFactory: safeRouteTemplateFactory,
|
|
397
|
+
screen,
|
|
398
|
+
sessionId,
|
|
399
|
+
status: "error",
|
|
400
|
+
trace
|
|
401
|
+
});
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
prototype.open = wrappedOpen;
|
|
407
|
+
prototype.send = wrappedSend;
|
|
408
|
+
if (prototype.open !== wrappedOpen || prototype.send !== wrappedSend) {
|
|
409
|
+
throw new SdkError("configuration_error", "instrumentGlobalXMLHttpRequest could not patch XMLHttpRequest");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let removed = false;
|
|
413
|
+
const remove = () => {
|
|
414
|
+
if (removed) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
removed = true;
|
|
418
|
+
if (prototype.open === wrappedOpen) {
|
|
419
|
+
prototype.open = originalOpen;
|
|
420
|
+
}
|
|
421
|
+
if (prototype.send === wrappedSend) {
|
|
422
|
+
prototype.send = originalSend;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
return Object.freeze({
|
|
427
|
+
remove,
|
|
428
|
+
stop: remove
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function installXhrReadyStateTracker(xhr, context, options) {
|
|
433
|
+
const onReadyStateChange = () => {
|
|
434
|
+
if (xhr.readyState === options.headersReceivedState && context.responseStartAtMs === undefined) {
|
|
435
|
+
context.responseStartAtMs = options.nowMs();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (xhr.readyState === options.doneState && !context.reported) {
|
|
439
|
+
captureXhrResourceSpan(xhr, context, options);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
if (typeof xhr.addEventListener === "function") {
|
|
443
|
+
xhr.addEventListener("readystatechange", onReadyStateChange);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const originalOnReadyStateChange = xhr.onreadystatechange;
|
|
447
|
+
xhr.onreadystatechange = function logBrewOnReadyStateChange() {
|
|
448
|
+
onReadyStateChange();
|
|
449
|
+
if (typeof originalOnReadyStateChange === "function") {
|
|
450
|
+
return originalOnReadyStateChange.apply(this, arguments);
|
|
451
|
+
}
|
|
452
|
+
return undefined;
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function captureXhrResourceSpan(xhr, context, {
|
|
457
|
+
appState,
|
|
458
|
+
client,
|
|
459
|
+
measureXhrResponseBodySize,
|
|
460
|
+
metadata,
|
|
461
|
+
metadataFactory,
|
|
462
|
+
nowMs,
|
|
463
|
+
platform,
|
|
464
|
+
routeTemplate,
|
|
465
|
+
routeTemplateFactory,
|
|
466
|
+
screen,
|
|
467
|
+
sessionId,
|
|
468
|
+
status,
|
|
469
|
+
trace
|
|
470
|
+
}) {
|
|
471
|
+
if (context.reported) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
context.reported = true;
|
|
475
|
+
const durationMs = elapsedMs(context.startedAtMs, nowMs);
|
|
476
|
+
const responseStartDurationMs = context.responseStartAtMs === undefined
|
|
477
|
+
? undefined
|
|
478
|
+
: elapsedMs(context.startedAtMs, () => context.responseStartAtMs);
|
|
479
|
+
const responseSizeBytes = xhrResponseSizeBytes(xhr, { measureXhrResponseBodySize });
|
|
480
|
+
const safeRouteTemplate = routeTemplate ?? routeTemplateFactory({ url: context.url });
|
|
481
|
+
const statusCode = xhrStatusCode(xhr);
|
|
482
|
+
captureReactNativeResourceSpan(client, {
|
|
483
|
+
appState,
|
|
484
|
+
durationMs,
|
|
485
|
+
metadata: {
|
|
486
|
+
...createSafeReactNativeMetadata(metadata, metadataFactory, {
|
|
487
|
+
durationMs,
|
|
488
|
+
init: context.body === undefined ? undefined : { body: context.body },
|
|
489
|
+
input: context.url,
|
|
490
|
+
method: context.method,
|
|
491
|
+
routeTemplate: safeRouteTemplate,
|
|
492
|
+
status: status ?? undefined,
|
|
493
|
+
statusCode,
|
|
494
|
+
url: context.url
|
|
495
|
+
}),
|
|
496
|
+
responseStartDurationMs,
|
|
497
|
+
transport: "xhr"
|
|
498
|
+
},
|
|
499
|
+
method: context.method,
|
|
500
|
+
platform,
|
|
501
|
+
routeTemplate: safeRouteTemplate,
|
|
502
|
+
screen,
|
|
503
|
+
responseSizeBytes,
|
|
504
|
+
sessionId,
|
|
505
|
+
status: status ?? undefined,
|
|
506
|
+
statusCode,
|
|
507
|
+
timestamp: context.timestamp,
|
|
508
|
+
trace
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function normalizeXhrMethod(method) {
|
|
513
|
+
return String(method ?? "GET").toUpperCase();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function xhrUrl(url) {
|
|
517
|
+
if (typeof url === "string") {
|
|
518
|
+
return url;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
return url.toString();
|
|
522
|
+
} catch {
|
|
523
|
+
return String(url);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function xhrRequestBody(body) {
|
|
528
|
+
if (typeof body === "string") {
|
|
529
|
+
return body;
|
|
530
|
+
}
|
|
531
|
+
if (body instanceof String) {
|
|
532
|
+
return body.toString();
|
|
533
|
+
}
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function defaultXhrRouteTemplateFactory({ url }) {
|
|
538
|
+
const URLConstructor = globalThis.URL;
|
|
539
|
+
if (typeof URLConstructor === "function") {
|
|
540
|
+
try {
|
|
541
|
+
const parsedUrl = new URLConstructor(url, "https://logbrew.local");
|
|
542
|
+
return parsedUrl.pathname;
|
|
543
|
+
} catch {
|
|
544
|
+
// Fall back to query/hash stripping below for non-standard request keys.
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return String(url).split(/[?#]/u, 1)[0];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function elapsedMs(startedAtMs, nowMs) {
|
|
551
|
+
const durationMs = nowMs() - startedAtMs;
|
|
552
|
+
return Number.isFinite(durationMs) ? Math.max(0, durationMs) : undefined;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function xhrStatusCode(xhr) {
|
|
556
|
+
return typeof xhr?.status === "number" && Number.isFinite(xhr.status) ? xhr.status : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function xhrResponseSizeBytes(xhr, { measureXhrResponseBodySize = false } = {}) {
|
|
560
|
+
if (typeof xhr?.getResponseHeader !== "function") {
|
|
561
|
+
return measureXhrResponseBodySize ? xhrResponseBodySizeBytes(xhr) : undefined;
|
|
562
|
+
}
|
|
563
|
+
const contentLength = Number.parseInt(String(xhr.getResponseHeader("Content-Length") ?? ""), 10);
|
|
564
|
+
if (Number.isFinite(contentLength) && contentLength >= 0) {
|
|
565
|
+
return contentLength;
|
|
566
|
+
}
|
|
567
|
+
return measureXhrResponseBodySize ? xhrResponseBodySizeBytes(xhr) : undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function xhrResponseBodySizeBytes(xhr) {
|
|
571
|
+
const responseType = readXhrResponseValue(xhr, "responseType");
|
|
572
|
+
if (responseType === undefined || responseType === "" || responseType === "text") {
|
|
573
|
+
const responseText = readXhrResponseValue(xhr, "responseText");
|
|
574
|
+
if (typeof responseText === "string" || responseText instanceof String) {
|
|
575
|
+
return utf8ByteLength(responseText.toString());
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const response = readXhrResponseValue(xhr, "response");
|
|
579
|
+
if (typeof response === "string" || response instanceof String) {
|
|
580
|
+
return utf8ByteLength(response.toString());
|
|
581
|
+
}
|
|
582
|
+
if (typeof ArrayBuffer === "function") {
|
|
583
|
+
if (response instanceof ArrayBuffer) {
|
|
584
|
+
return response.byteLength;
|
|
585
|
+
}
|
|
586
|
+
if (typeof ArrayBuffer.isView === "function" && ArrayBuffer.isView(response)) {
|
|
587
|
+
return response.byteLength;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (typeof response?.size === "number" && Number.isFinite(response.size) && response.size >= 0) {
|
|
591
|
+
return response.size;
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function readXhrResponseValue(xhr, property) {
|
|
597
|
+
try {
|
|
598
|
+
return xhr?.[property];
|
|
599
|
+
} catch {
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function utf8ByteLength(value) {
|
|
605
|
+
let bytes = 0;
|
|
606
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
607
|
+
const code = value.charCodeAt(index);
|
|
608
|
+
if (code <= 0x7f) {
|
|
609
|
+
bytes += 1;
|
|
610
|
+
} else if (code <= 0x7ff) {
|
|
611
|
+
bytes += 2;
|
|
612
|
+
} else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
|
|
613
|
+
const next = value.charCodeAt(index + 1);
|
|
614
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
615
|
+
bytes += 4;
|
|
616
|
+
index += 1;
|
|
617
|
+
} else {
|
|
618
|
+
bytes += 3;
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
bytes += 3;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return bytes;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function errorName(error) {
|
|
628
|
+
return typeof error?.name === "string" && error.name.trim() !== "" ? error.name : "Error";
|
|
629
|
+
}
|
|
630
|
+
/* eslint-enable no-invalid-this */
|
|
631
|
+
|
|
632
|
+
export default {
|
|
633
|
+
createLogBrewReactNativeInstrumentation
|
|
634
|
+
};
|