@logbrew/react-native 0.1.18 → 0.1.20
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 +20 -2
- package/apple-native-diagnostics.d.ts +20 -0
- package/apple-native-diagnostics.js +97 -1
- package/expo.cjs +8 -14
- package/index.cjs +1 -1
- package/index.js +36 -1207
- package/index.native.d.ts +1 -0
- package/index.native.js +6 -3
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +5 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +23 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +16 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +5 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +22 -10
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +16 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +77 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +11 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +6 -5
- package/package.json +1 -1
- package/src/NativeLogBrewAppleDiagnostics.ts +3 -0
package/index.js
CHANGED
|
@@ -1,1207 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
maxBatchBytes,
|
|
38
|
-
maxBatchEvents,
|
|
39
|
-
maxQueueBytes,
|
|
40
|
-
maxQueueSize,
|
|
41
|
-
onEventDropped,
|
|
42
|
-
sdkName = DEFAULT_SDK_NAME,
|
|
43
|
-
sdkVersion = DEFAULT_SDK_VERSION,
|
|
44
|
-
maxRetries = 2,
|
|
45
|
-
transport
|
|
46
|
-
}) {
|
|
47
|
-
const authKey = clientKey ?? apiKey;
|
|
48
|
-
if (!authKey) {
|
|
49
|
-
throw new SdkError("configuration_error", "createLogBrewReactNativeClient requires clientKey or apiKey");
|
|
50
|
-
}
|
|
51
|
-
return LogBrewClient.create({
|
|
52
|
-
apiKey: authKey,
|
|
53
|
-
automaticDelivery,
|
|
54
|
-
context,
|
|
55
|
-
deliveryIntervalMs,
|
|
56
|
-
deliveryQueueThreshold,
|
|
57
|
-
eventStore,
|
|
58
|
-
maxBatchBytes,
|
|
59
|
-
maxBatchEvents,
|
|
60
|
-
maxQueueBytes,
|
|
61
|
-
maxQueueSize,
|
|
62
|
-
maxRetries,
|
|
63
|
-
onEventDropped,
|
|
64
|
-
sdkName,
|
|
65
|
-
sdkVersion,
|
|
66
|
-
transport
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function createReactNativeFetchTransport({
|
|
71
|
-
endpoint = DEFAULT_ENDPOINT,
|
|
72
|
-
fetchImpl = defaultFetch(),
|
|
73
|
-
headers = {}
|
|
74
|
-
} = {}) {
|
|
75
|
-
validateDeliveryEndpoint(endpoint);
|
|
76
|
-
if (typeof fetchImpl !== "function") {
|
|
77
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport requires fetch");
|
|
78
|
-
}
|
|
79
|
-
if (!headers || Array.isArray(headers) || typeof headers !== "object") {
|
|
80
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport headers must be an object");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return Object.freeze({
|
|
84
|
-
async send(apiKey, body) {
|
|
85
|
-
try {
|
|
86
|
-
const response = await fetchImpl(endpoint, {
|
|
87
|
-
body,
|
|
88
|
-
headers: {
|
|
89
|
-
...headers,
|
|
90
|
-
authorization: `Bearer ${apiKey}`,
|
|
91
|
-
"content-type": "application/json"
|
|
92
|
-
},
|
|
93
|
-
method: "POST"
|
|
94
|
-
});
|
|
95
|
-
const retryAfterMs = retryAfterMsFromHeaders(response?.headers);
|
|
96
|
-
return retryAfterMs === undefined
|
|
97
|
-
? { statusCode: response?.status, attempts: 1 }
|
|
98
|
-
: { statusCode: response?.status, attempts: 1, retryAfterMs };
|
|
99
|
-
} catch {
|
|
100
|
-
throw TransportError.network("LogBrew React Native delivery failed");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function createReactNativeTraceparent({ randomValues = defaultRandomValues, spanId, traceFlags = "01", traceId } = {}) {
|
|
107
|
-
return createTraceparent({
|
|
108
|
-
spanId: spanId ?? randomHex(8, randomValues),
|
|
109
|
-
traceFlags,
|
|
110
|
-
traceId: traceId ?? randomHex(16, randomValues)
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function createReactNativeTraceContext({
|
|
115
|
-
parentSpanId, randomValues = defaultRandomValues, spanId, traceFlags = "01", traceId, traceparent
|
|
116
|
-
} = {}) {
|
|
117
|
-
if (traceparent !== undefined && traceparent !== null && String(traceparent).trim() !== "") {
|
|
118
|
-
try {
|
|
119
|
-
const parsed = parseTraceparent(traceparent);
|
|
120
|
-
const localSpanId = spanId ?? randomHex(8, randomValues);
|
|
121
|
-
createTraceparent({ traceId: parsed.traceId, spanId: localSpanId, traceFlags: parsed.traceFlags });
|
|
122
|
-
return freezeTraceContext({
|
|
123
|
-
parentSpanId: parsed.parentSpanId,
|
|
124
|
-
sampled: parsed.sampled,
|
|
125
|
-
spanId: localSpanId,
|
|
126
|
-
traceFlags: parsed.traceFlags,
|
|
127
|
-
traceId: parsed.traceId
|
|
128
|
-
});
|
|
129
|
-
} catch {
|
|
130
|
-
// Bad upstream propagation should not break mobile app flows.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const localTraceId = traceId ?? randomHex(16, randomValues);
|
|
135
|
-
const localSpanId = spanId ?? randomHex(8, randomValues);
|
|
136
|
-
createTraceparent({ traceId: localTraceId, spanId: localSpanId, traceFlags });
|
|
137
|
-
if (parentSpanId !== undefined) {
|
|
138
|
-
createTraceparent({ traceId: localTraceId, spanId: parentSpanId, traceFlags });
|
|
139
|
-
}
|
|
140
|
-
return freezeTraceContext({
|
|
141
|
-
parentSpanId,
|
|
142
|
-
sampled: sampledFromTraceFlags(traceFlags),
|
|
143
|
-
spanId: localSpanId,
|
|
144
|
-
traceFlags,
|
|
145
|
-
traceId: localTraceId
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function getActiveLogBrewTrace() {
|
|
150
|
-
return activeTraceScopes.length === 0 ? undefined : activeTraceScopes[activeTraceScopes.length - 1].trace;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function withLogBrewTrace(trace, callback) {
|
|
154
|
-
if (typeof callback !== "function") {
|
|
155
|
-
throw new SdkError("configuration_error", "withLogBrewTrace requires a callback");
|
|
156
|
-
}
|
|
157
|
-
const context = resolveTraceContext(trace) ?? createReactNativeTraceContext();
|
|
158
|
-
const scope = {
|
|
159
|
-
id: ++nextTraceScopeId,
|
|
160
|
-
trace: context
|
|
161
|
-
};
|
|
162
|
-
activeTraceScopes.push(scope);
|
|
163
|
-
try {
|
|
164
|
-
return callback(context);
|
|
165
|
-
} finally {
|
|
166
|
-
removeActiveTraceScope(scope.id);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function bindLogBrewTrace(trace, callback) {
|
|
171
|
-
if (typeof callback !== "function") {
|
|
172
|
-
throw new SdkError("configuration_error", "bindLogBrewTrace requires a callback");
|
|
173
|
-
}
|
|
174
|
-
const context = resolveTraceContext(trace) ?? createReactNativeTraceContext();
|
|
175
|
-
return function logBrewTracedCallback(...args) {
|
|
176
|
-
return withLogBrewTrace(context, () => callback(...args));
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export function getReactNativeTraceMetadata(trace = getActiveLogBrewTrace()) {
|
|
181
|
-
const context = resolveTraceContext(trace);
|
|
182
|
-
if (!context) {
|
|
183
|
-
return {};
|
|
184
|
-
}
|
|
185
|
-
return compactMetadata({
|
|
186
|
-
parentSpanId: context.parentSpanId,
|
|
187
|
-
spanId: context.spanId,
|
|
188
|
-
traceFlags: context.traceFlags,
|
|
189
|
-
traceId: context.traceId,
|
|
190
|
-
traceSampled: context.sampled
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function createReactNativeSpanAttributes({
|
|
195
|
-
durationMs,
|
|
196
|
-
metadata = {},
|
|
197
|
-
name,
|
|
198
|
-
spanId,
|
|
199
|
-
status = "ok",
|
|
200
|
-
trace = getActiveLogBrewTrace()
|
|
201
|
-
} = {}) {
|
|
202
|
-
const context = resolveTraceContext(trace) ?? createReactNativeTraceContext();
|
|
203
|
-
return {
|
|
204
|
-
name,
|
|
205
|
-
traceId: context.traceId,
|
|
206
|
-
spanId: spanId ?? context.spanId,
|
|
207
|
-
status,
|
|
208
|
-
durationMs,
|
|
209
|
-
metadata: compactMetadata({
|
|
210
|
-
...metadata,
|
|
211
|
-
...getReactNativeTraceMetadata(context)
|
|
212
|
-
})
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function createReactNativeTraceHeaders(trace = getActiveLogBrewTrace()) {
|
|
217
|
-
const context = resolveTraceContext(trace) ?? createReactNativeTraceContext();
|
|
218
|
-
return {
|
|
219
|
-
traceparent: createTraceparent({
|
|
220
|
-
traceFlags: context.traceFlags,
|
|
221
|
-
traceId: context.traceId,
|
|
222
|
-
spanId: context.spanId
|
|
223
|
-
})
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function createTraceparentFetch({
|
|
228
|
-
fetchImpl = defaultFetch(),
|
|
229
|
-
randomValues = defaultRandomValues,
|
|
230
|
-
trace,
|
|
231
|
-
traceFlags = "01",
|
|
232
|
-
traceparent,
|
|
233
|
-
traceparentFactory,
|
|
234
|
-
tracePropagationTargets = []
|
|
235
|
-
} = {}) {
|
|
236
|
-
if (typeof fetchImpl !== "function") {
|
|
237
|
-
throw new SdkError("configuration_error", "createTraceparentFetch requires fetch");
|
|
238
|
-
}
|
|
239
|
-
if (!Array.isArray(tracePropagationTargets)) {
|
|
240
|
-
throw new SdkError("configuration_error", "tracePropagationTargets must be an array");
|
|
241
|
-
}
|
|
242
|
-
if (traceparentFactory !== undefined && typeof traceparentFactory !== "function") {
|
|
243
|
-
throw new SdkError("configuration_error", "traceparentFactory must be a function");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return async function tracedFetch(input, init) {
|
|
247
|
-
const url = requestUrl(input);
|
|
248
|
-
if (!shouldPropagateTraceparent(url, tracePropagationTargets)) {
|
|
249
|
-
return init === undefined ? fetchImpl(input) : fetchImpl(input, init);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const requestInit = init ?? {};
|
|
253
|
-
const nextTraceparent = traceparentForRequest({ init, input, randomValues, trace, traceFlags, traceparent, traceparentFactory, url });
|
|
254
|
-
const nextInit = {
|
|
255
|
-
...requestInit,
|
|
256
|
-
headers: headersWithTraceparent(requestHeaders(input, requestInit), nextTraceparent)
|
|
257
|
-
};
|
|
258
|
-
return fetchImpl(input, nextInit);
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function shouldPropagateTraceparent(url, tracePropagationTargets = []) {
|
|
263
|
-
if (!Array.isArray(tracePropagationTargets)) {
|
|
264
|
-
throw new SdkError("configuration_error", "tracePropagationTargets must be an array");
|
|
265
|
-
}
|
|
266
|
-
return tracePropagationTargets.some((target) => {
|
|
267
|
-
if (typeof target === "string") {
|
|
268
|
-
return shouldPropagateToStringTarget(url, target);
|
|
269
|
-
}
|
|
270
|
-
if (target instanceof RegExp) {
|
|
271
|
-
target.lastIndex = 0;
|
|
272
|
-
const matched = target.test(url);
|
|
273
|
-
target.lastIndex = 0;
|
|
274
|
-
return matched;
|
|
275
|
-
}
|
|
276
|
-
if (typeof target === "function") {
|
|
277
|
-
return target(url) === true;
|
|
278
|
-
}
|
|
279
|
-
throw new SdkError("configuration_error", "tracePropagationTargets entries must be strings, RegExp values, or functions");
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function getReactNativeContext({ platform, appState, metadata = {} } = {}) {
|
|
284
|
-
return {
|
|
285
|
-
platform: platform?.OS ?? "unknown",
|
|
286
|
-
platformVersion: normalizeMetadataValue(platform?.Version),
|
|
287
|
-
appState: normalizeMetadataValue(appState?.currentState),
|
|
288
|
-
isPad: normalizeMetadataValue(platform?.isPad),
|
|
289
|
-
isTesting: normalizeMetadataValue(platform?.constants?.isTesting),
|
|
290
|
-
...metadata
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export function captureScreenView(client, screenName, {
|
|
295
|
-
id = `evt_screen_${slugify(screenName)}`,
|
|
296
|
-
timestamp = new Date().toISOString(),
|
|
297
|
-
status = "success",
|
|
298
|
-
platform,
|
|
299
|
-
appState,
|
|
300
|
-
trace,
|
|
301
|
-
metadata = {}
|
|
302
|
-
} = {}) {
|
|
303
|
-
requireClient(client);
|
|
304
|
-
requireNonEmpty("screen name", screenName);
|
|
305
|
-
client.action(id, timestamp, {
|
|
306
|
-
name: screenActionName(screenName),
|
|
307
|
-
status,
|
|
308
|
-
metadata: {
|
|
309
|
-
...getReactNativeContext({ platform, appState }),
|
|
310
|
-
screen: screenName,
|
|
311
|
-
...metadata,
|
|
312
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
313
|
-
...productAnalyticsMetadata("screen_view", screenName)
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
recordIssueBreadcrumb(client, {
|
|
317
|
-
timestamp,
|
|
318
|
-
attributes: { name: screenActionName(screenName), status }
|
|
319
|
-
}, "navigation", "navigation");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function captureAppStateChange(client, state, {
|
|
323
|
-
id = `evt_app_state_${slugify(state)}`,
|
|
324
|
-
timestamp = new Date().toISOString(),
|
|
325
|
-
platform,
|
|
326
|
-
appState,
|
|
327
|
-
trace,
|
|
328
|
-
metadata = {}
|
|
329
|
-
} = {}) {
|
|
330
|
-
requireClient(client);
|
|
331
|
-
requireNonEmpty("app state", state);
|
|
332
|
-
client.action(id, timestamp, {
|
|
333
|
-
name: "app_state_change",
|
|
334
|
-
status: "success",
|
|
335
|
-
metadata: {
|
|
336
|
-
...getReactNativeContext({ platform, appState }),
|
|
337
|
-
appState: state,
|
|
338
|
-
...metadata,
|
|
339
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
recordIssueBreadcrumb(client, {
|
|
343
|
-
timestamp,
|
|
344
|
-
attributes: { name: "app_state_change", status: "success" }
|
|
345
|
-
}, "app.state", "state");
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export function createReactNativeActionEvent({
|
|
349
|
-
id,
|
|
350
|
-
idFactory = defaultActionEventId,
|
|
351
|
-
metadata = {},
|
|
352
|
-
name,
|
|
353
|
-
now = () => new Date().toISOString(),
|
|
354
|
-
platform,
|
|
355
|
-
appState,
|
|
356
|
-
screen,
|
|
357
|
-
sessionId,
|
|
358
|
-
status = "success",
|
|
359
|
-
timestamp,
|
|
360
|
-
trace,
|
|
361
|
-
traceId
|
|
362
|
-
} = {}) {
|
|
363
|
-
return {
|
|
364
|
-
id: id ?? idFactory({ name, screen }),
|
|
365
|
-
timestamp: timestamp ?? now(),
|
|
366
|
-
attributes: {
|
|
367
|
-
name,
|
|
368
|
-
status,
|
|
369
|
-
metadata: compactMetadata({
|
|
370
|
-
...getReactNativeContext({ platform, appState }),
|
|
371
|
-
source: "react-native.action",
|
|
372
|
-
screen,
|
|
373
|
-
sessionId,
|
|
374
|
-
traceId,
|
|
375
|
-
...metadata,
|
|
376
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
377
|
-
...productAnalyticsMetadata("interaction", screen)
|
|
378
|
-
})
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
export function captureReactNativeAction(client, input = {}) {
|
|
384
|
-
requireClient(client);
|
|
385
|
-
const event = createReactNativeActionEvent(input);
|
|
386
|
-
client.action(event.id, event.timestamp, event.attributes);
|
|
387
|
-
recordIssueBreadcrumb(client, event, "ui.action", "user");
|
|
388
|
-
return event;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export function createReactNativeNetworkEvent({
|
|
392
|
-
durationMs,
|
|
393
|
-
id,
|
|
394
|
-
idFactory = defaultNetworkEventId,
|
|
395
|
-
metadata = {},
|
|
396
|
-
method,
|
|
397
|
-
name,
|
|
398
|
-
now = () => new Date().toISOString(),
|
|
399
|
-
platform,
|
|
400
|
-
appState,
|
|
401
|
-
routeTemplate,
|
|
402
|
-
screen,
|
|
403
|
-
sessionId,
|
|
404
|
-
status,
|
|
405
|
-
statusCode,
|
|
406
|
-
timestamp,
|
|
407
|
-
trace,
|
|
408
|
-
traceId
|
|
409
|
-
} = {}) {
|
|
410
|
-
const safeRouteTemplate = stripQueryAndHash(routeTemplate);
|
|
411
|
-
const safeMethod = method === undefined ? undefined : String(method).toUpperCase();
|
|
412
|
-
const actionName = name ?? [safeMethod, safeRouteTemplate].filter(Boolean).join(" ");
|
|
413
|
-
return {
|
|
414
|
-
id: id ?? idFactory({ method: safeMethod, routeTemplate: safeRouteTemplate, screen }),
|
|
415
|
-
timestamp: timestamp ?? now(),
|
|
416
|
-
attributes: {
|
|
417
|
-
name: actionName,
|
|
418
|
-
status: status ?? statusFromStatusCode(statusCode),
|
|
419
|
-
metadata: compactMetadata({
|
|
420
|
-
...getReactNativeContext({ platform, appState }),
|
|
421
|
-
source: "react-native.network",
|
|
422
|
-
durationMs,
|
|
423
|
-
method: safeMethod,
|
|
424
|
-
routeTemplate: safeRouteTemplate,
|
|
425
|
-
screen,
|
|
426
|
-
sessionId,
|
|
427
|
-
statusCode,
|
|
428
|
-
traceId,
|
|
429
|
-
...metadata,
|
|
430
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
431
|
-
})
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export function captureReactNativeNetwork(client, input = {}) {
|
|
437
|
-
requireClient(client);
|
|
438
|
-
const event = createReactNativeNetworkEvent(input);
|
|
439
|
-
client.action(event.id, event.timestamp, event.attributes);
|
|
440
|
-
recordIssueBreadcrumb(client, event, "http", "http");
|
|
441
|
-
return event;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export function createReactNativeNavigationSpanEvent({
|
|
445
|
-
actionType, durationMs, id, idFactory = defaultNavigationSpanEventId, includeRouteKey = false, metadata = {},
|
|
446
|
-
name, now = () => new Date().toISOString(), platform, appState, previousRouteKey, previousRouteName,
|
|
447
|
-
routeKey, routeName, routePath, screen, status = "ok", timestamp, trace
|
|
448
|
-
} = {}) {
|
|
449
|
-
const safeRoutePath = stripQueryAndHash(routePath);
|
|
450
|
-
const spanName = name ?? `navigation:${routeName ?? safeRoutePath ?? screen ?? "route"}`;
|
|
451
|
-
const context = resolveTraceContext(trace) ?? getActiveLogBrewTrace() ?? createReactNativeTraceContext();
|
|
452
|
-
return {
|
|
453
|
-
id: id ?? idFactory({ routeName, routePath: safeRoutePath, screen }),
|
|
454
|
-
timestamp: timestamp ?? now(),
|
|
455
|
-
attributes: createReactNativeSpanAttributes({
|
|
456
|
-
name: spanName,
|
|
457
|
-
status,
|
|
458
|
-
durationMs,
|
|
459
|
-
trace: context,
|
|
460
|
-
metadata: {
|
|
461
|
-
...getReactNativeContext({ platform, appState }),
|
|
462
|
-
source: "react-native.navigation",
|
|
463
|
-
actionType,
|
|
464
|
-
previousRouteName,
|
|
465
|
-
routeName,
|
|
466
|
-
routePath: safeRoutePath,
|
|
467
|
-
screen: screen ?? routeName,
|
|
468
|
-
...(includeRouteKey ? { previousRouteKey, routeKey } : {}),
|
|
469
|
-
...metadata
|
|
470
|
-
}
|
|
471
|
-
})
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export function captureReactNativeNavigationSpan(client, input = {}) {
|
|
476
|
-
requireClient(client);
|
|
477
|
-
const event = createReactNativeNavigationSpanEvent(input);
|
|
478
|
-
client.span(event.id, event.timestamp, event.attributes);
|
|
479
|
-
recordIssueBreadcrumb(client, event, "navigation", "navigation");
|
|
480
|
-
return event;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
export function createReactNativeResourceSpanEvent({
|
|
484
|
-
durationMs, id, idFactory = defaultResourceSpanEventId, kind = "fetch", metadata = {}, method, name,
|
|
485
|
-
now = () => new Date().toISOString(), platform, appState, responseSizeBytes, routeTemplate, screen,
|
|
486
|
-
sessionId, status, statusCode, timestamp, trace
|
|
487
|
-
} = {}) {
|
|
488
|
-
const safeRouteTemplate = stripQueryAndHash(routeTemplate);
|
|
489
|
-
const safeMethod = method === undefined ? undefined : String(method).toUpperCase();
|
|
490
|
-
const defaultName = [safeMethod, safeRouteTemplate].filter(Boolean).join(" ");
|
|
491
|
-
const spanName = name ?? (defaultName || "mobile.resource");
|
|
492
|
-
const context = resolveTraceContext(trace) ?? getActiveLogBrewTrace() ?? createReactNativeTraceContext();
|
|
493
|
-
return {
|
|
494
|
-
id: id ?? idFactory({ method: safeMethod, routeTemplate: safeRouteTemplate, screen }),
|
|
495
|
-
timestamp: timestamp ?? now(),
|
|
496
|
-
attributes: createReactNativeSpanAttributes({
|
|
497
|
-
name: spanName,
|
|
498
|
-
status: status ?? spanStatusFromStatusCode(statusCode),
|
|
499
|
-
durationMs,
|
|
500
|
-
trace: context,
|
|
501
|
-
metadata: {
|
|
502
|
-
...getReactNativeContext({ platform, appState }),
|
|
503
|
-
source: "react-native.resource",
|
|
504
|
-
durationMs,
|
|
505
|
-
method: safeMethod,
|
|
506
|
-
resourceKind: kind,
|
|
507
|
-
responseSizeBytes,
|
|
508
|
-
routeTemplate: safeRouteTemplate,
|
|
509
|
-
screen,
|
|
510
|
-
sessionId,
|
|
511
|
-
statusCode,
|
|
512
|
-
...metadata
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export function captureReactNativeResourceSpan(client, input = {}) {
|
|
519
|
-
requireClient(client);
|
|
520
|
-
const event = createReactNativeResourceSpanEvent(input);
|
|
521
|
-
client.span(event.id, event.timestamp, event.attributes);
|
|
522
|
-
return event;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
export function createReactNavigationSpanListener(client, navigationContainer, {
|
|
526
|
-
captureInitialRoute = false, includeRouteKey = false, metadata = {}, now = () => new Date().toISOString(),
|
|
527
|
-
nowMs = () => Date.now(), onError, platform, appState, trace
|
|
528
|
-
} = {}) {
|
|
529
|
-
requireClient(client);
|
|
530
|
-
const container = resolveNavigationContainer(navigationContainer);
|
|
531
|
-
if (!container || typeof container.addListener !== "function" || typeof container.getCurrentRoute !== "function") {
|
|
532
|
-
throw new SdkError("configuration_error", "createReactNavigationSpanListener requires a React Navigation container ref with addListener and getCurrentRoute");
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
let previousRoute = captureInitialRoute ? {} : routeSnapshot(container.getCurrentRoute());
|
|
536
|
-
let pendingNavigation;
|
|
537
|
-
const removers = [];
|
|
538
|
-
const captureCurrentRoute = () => {
|
|
539
|
-
try {
|
|
540
|
-
const route = routeSnapshot(container.getCurrentRoute());
|
|
541
|
-
if (!route.name && !route.path) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const startedAtMs = pendingNavigation?.startedAtMs;
|
|
545
|
-
const durationMs = startedAtMs === undefined ? undefined : Math.max(0, nowMs() - startedAtMs);
|
|
546
|
-
captureReactNativeNavigationSpan(client, {
|
|
547
|
-
actionType: pendingNavigation?.actionType, durationMs, includeRouteKey, metadata, now, platform, appState,
|
|
548
|
-
previousRouteKey: previousRoute.key, previousRouteName: previousRoute.name, routeKey: route.key,
|
|
549
|
-
routeName: route.name, routePath: route.path, timestamp: pendingNavigation?.timestamp, trace
|
|
550
|
-
});
|
|
551
|
-
previousRoute = route;
|
|
552
|
-
pendingNavigation = undefined;
|
|
553
|
-
} catch (error) {
|
|
554
|
-
if (typeof onError === "function") {
|
|
555
|
-
onError(error);
|
|
556
|
-
} else {
|
|
557
|
-
throw error;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const actionSubscription = safeNavigationListener(container, "__unsafe_action__", (event) => {
|
|
563
|
-
pendingNavigation = {
|
|
564
|
-
actionType: navigationActionType(event),
|
|
565
|
-
startedAtMs: nowMs(),
|
|
566
|
-
timestamp: now()
|
|
567
|
-
};
|
|
568
|
-
});
|
|
569
|
-
if (actionSubscription) {
|
|
570
|
-
removers.push(actionSubscription);
|
|
571
|
-
}
|
|
572
|
-
removers.push(addNavigationListener(container, "state", captureCurrentRoute));
|
|
573
|
-
|
|
574
|
-
if (captureInitialRoute) {
|
|
575
|
-
captureCurrentRoute();
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return () => {
|
|
579
|
-
for (const remove of removers.splice(0).reverse()) {
|
|
580
|
-
remove();
|
|
581
|
-
}
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
export function createReactNativeErrorEvent(error, {
|
|
586
|
-
debugIdMap,
|
|
587
|
-
evidence,
|
|
588
|
-
environment,
|
|
589
|
-
fingerprint,
|
|
590
|
-
handled = true,
|
|
591
|
-
id,
|
|
592
|
-
idFactory = defaultErrorEventId,
|
|
593
|
-
includeStack = false,
|
|
594
|
-
level = "error",
|
|
595
|
-
mechanism = "react-native.error",
|
|
596
|
-
metadata = {},
|
|
597
|
-
now = () => new Date().toISOString(),
|
|
598
|
-
platform,
|
|
599
|
-
appState,
|
|
600
|
-
release,
|
|
601
|
-
runtime,
|
|
602
|
-
screen,
|
|
603
|
-
service,
|
|
604
|
-
timestamp,
|
|
605
|
-
trace
|
|
606
|
-
} = {}) {
|
|
607
|
-
const details = errorDetails(error);
|
|
608
|
-
const traceContext = resolveTraceContext(trace ?? getActiveLogBrewTrace());
|
|
609
|
-
const attributes = createIssueAttributesFromError(error?.reason ?? error?.error ?? error, {
|
|
610
|
-
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
611
|
-
evidence,
|
|
612
|
-
environment,
|
|
613
|
-
fingerprint,
|
|
614
|
-
handled,
|
|
615
|
-
includeErrorStack: includeStack,
|
|
616
|
-
level,
|
|
617
|
-
mechanism,
|
|
618
|
-
metadata: {
|
|
619
|
-
...getReactNativeContext({ platform, appState }),
|
|
620
|
-
errorValueType: details.valueType,
|
|
621
|
-
screen,
|
|
622
|
-
...metadata
|
|
623
|
-
},
|
|
624
|
-
release,
|
|
625
|
-
runtime,
|
|
626
|
-
service,
|
|
627
|
-
source: "react-native.error",
|
|
628
|
-
trace: traceContext
|
|
629
|
-
});
|
|
630
|
-
const stackFrames = sanitizeReactNativeIssueStackFrames(attributes.stackFrames);
|
|
631
|
-
const exceptionChain = sanitizeReactNativeIssueExceptionChain(attributes.exceptionChain);
|
|
632
|
-
return {
|
|
633
|
-
id: id ?? idFactory({ error, message: details.message, screen }),
|
|
634
|
-
timestamp: timestamp ?? now(),
|
|
635
|
-
attributes: {
|
|
636
|
-
...attributes,
|
|
637
|
-
...(exceptionChain ? { exceptionChain } : {}),
|
|
638
|
-
...(stackFrames ? { stackFrames } : {}),
|
|
639
|
-
title: `React Native error: ${details.message}`,
|
|
640
|
-
message: details.message,
|
|
641
|
-
metadata: sanitizeReactNativeIssueMetadata(attributes.metadata, compactMetadata)
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
export function captureReactNativeError(client, error, options = {}) {
|
|
647
|
-
requireClient(client);
|
|
648
|
-
const event = createReactNativeErrorEvent(error, options);
|
|
649
|
-
client.issue(event.id, event.timestamp, event.attributes);
|
|
650
|
-
return event;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
export function createAppStateListener(client, appState, options = {}) {
|
|
654
|
-
requireClient(client);
|
|
655
|
-
if (!appState || typeof appState.addEventListener !== "function") {
|
|
656
|
-
throw new SdkError("configuration_error", "createAppStateListener requires AppState.addEventListener");
|
|
657
|
-
}
|
|
658
|
-
const {
|
|
659
|
-
flushOnBackground = false,
|
|
660
|
-
onFlushError,
|
|
661
|
-
...captureOptions
|
|
662
|
-
} = options;
|
|
663
|
-
if (typeof flushOnBackground !== "boolean") {
|
|
664
|
-
throw new SdkError("configuration_error", "createAppStateListener flushOnBackground must be a boolean");
|
|
665
|
-
}
|
|
666
|
-
if (onFlushError !== undefined && typeof onFlushError !== "function") {
|
|
667
|
-
throw new SdkError("configuration_error", "createAppStateListener onFlushError must be a function");
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const subscription = appState.addEventListener("change", (nextState) => {
|
|
671
|
-
captureAppStateChange(client, nextState, {
|
|
672
|
-
...captureOptions,
|
|
673
|
-
appState
|
|
674
|
-
});
|
|
675
|
-
if (flushOnBackground && BACKGROUND_APP_STATES.has(nextState)) {
|
|
676
|
-
void client.flush().catch((error) => {
|
|
677
|
-
if (typeof onFlushError === "function") {
|
|
678
|
-
try {
|
|
679
|
-
onFlushError(error);
|
|
680
|
-
} catch {
|
|
681
|
-
// Delivery diagnostics must not interrupt the app-state callback.
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
return subscriptionRemover(subscription);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
export function LogBrewNativeProvider({ client, platform, appState, trace, children }) {
|
|
692
|
-
requireClient(client);
|
|
693
|
-
const value = React.useMemo(() => ({
|
|
694
|
-
client,
|
|
695
|
-
platform,
|
|
696
|
-
appState,
|
|
697
|
-
trace: resolveTraceContext(trace)
|
|
698
|
-
}), [appState, client, platform, trace]);
|
|
699
|
-
return React.createElement(LogBrewNativeContext.Provider, { value }, children);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
export function useLogBrewNative() {
|
|
703
|
-
const value = React.useContext(LogBrewNativeContext);
|
|
704
|
-
if (!value?.client) {
|
|
705
|
-
throw new SdkError("configuration_error", "useLogBrewNative must be used inside LogBrewNativeProvider");
|
|
706
|
-
}
|
|
707
|
-
return value;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
export function useLogBrewNativeActions() {
|
|
711
|
-
const { client, platform, appState, trace } = useLogBrewNative();
|
|
712
|
-
const scoped = (options = {}) => ({ platform, appState, trace, ...options });
|
|
713
|
-
return {
|
|
714
|
-
release: client.release.bind(client),
|
|
715
|
-
environment: client.environment.bind(client),
|
|
716
|
-
issue: (id, timestamp, attributes) => client.issue(id, timestamp, attributesWithTrace(attributes, trace)),
|
|
717
|
-
log: (id, timestamp, attributes) => client.log(id, timestamp, attributesWithTrace(attributes, trace)),
|
|
718
|
-
span: client.span.bind(client),
|
|
719
|
-
action: (id, timestamp, attributes) => client.action(id, timestamp, attributesWithTrace(attributes, trace)),
|
|
720
|
-
flush: client.flush.bind(client),
|
|
721
|
-
shutdown: client.shutdown.bind(client),
|
|
722
|
-
deliveryHealth: client.deliveryHealth.bind(client),
|
|
723
|
-
droppedEvents: client.droppedEvents.bind(client),
|
|
724
|
-
pendingBytes: client.pendingBytes.bind(client),
|
|
725
|
-
previewJson: client.previewJson.bind(client),
|
|
726
|
-
pendingEvents: client.pendingEvents.bind(client),
|
|
727
|
-
trace,
|
|
728
|
-
captureScreenView: (screenName, options = {}) => captureScreenView(client, screenName, scoped(options)),
|
|
729
|
-
captureAppStateChange: (state, options = {}) => captureAppStateChange(client, state, scoped(options)),
|
|
730
|
-
captureReactNativeAction: (input = {}) => captureReactNativeAction(client, scoped(input)),
|
|
731
|
-
captureReactNativeNetwork: (input = {}) => captureReactNativeNetwork(client, scoped(input)),
|
|
732
|
-
captureReactNativeNavigationSpan: (input = {}) => captureReactNativeNavigationSpan(client, scoped(input)),
|
|
733
|
-
captureReactNativeResourceSpan: (input = {}) => captureReactNativeResourceSpan(client, scoped(input)),
|
|
734
|
-
captureReactNativeError: (error, options = {}) => captureReactNativeError(client, error, scoped(options))
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function requireClient(client) {
|
|
739
|
-
if (!client) {
|
|
740
|
-
throw new SdkError("configuration_error", "LogBrew React Native helpers require a client");
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function requireNonEmpty(label, value) {
|
|
745
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
746
|
-
throw new SdkError("validation_error", `${label} must be non-empty`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function normalizeMetadataValue(value) {
|
|
751
|
-
if (value === undefined) {
|
|
752
|
-
return null;
|
|
753
|
-
}
|
|
754
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
755
|
-
return value;
|
|
756
|
-
}
|
|
757
|
-
return String(value);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
function compactMetadata(metadata) {
|
|
761
|
-
const compacted = {};
|
|
762
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
763
|
-
if (value === undefined) {
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
767
|
-
compacted[key] = value;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return compacted;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function attributesWithTrace(attributes, trace) {
|
|
774
|
-
const context = resolveTraceContext(trace) ?? getActiveLogBrewTrace();
|
|
775
|
-
if (!context) {
|
|
776
|
-
return attributes;
|
|
777
|
-
}
|
|
778
|
-
return {
|
|
779
|
-
...attributes,
|
|
780
|
-
metadata: compactMetadata({
|
|
781
|
-
...(attributes?.metadata ?? {}),
|
|
782
|
-
...getReactNativeTraceMetadata(context)
|
|
783
|
-
})
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
function errorDetails(error) {
|
|
788
|
-
const candidate = error?.reason ?? error?.error ?? error;
|
|
789
|
-
return {
|
|
790
|
-
message: errorMessage(candidate),
|
|
791
|
-
valueType: candidate === null ? "null" : typeof candidate
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function errorMessage(error) {
|
|
796
|
-
if (error instanceof Error && typeof error.message === "string" && error.message.trim() !== "") {
|
|
797
|
-
return error.message;
|
|
798
|
-
}
|
|
799
|
-
if (typeof error?.message === "string" && error.message.trim() !== "") {
|
|
800
|
-
return error.message;
|
|
801
|
-
}
|
|
802
|
-
if (typeof error === "string" && error.trim() !== "") {
|
|
803
|
-
return error;
|
|
804
|
-
}
|
|
805
|
-
return String(error ?? "unknown error");
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function validateDeliveryEndpoint(endpoint) {
|
|
809
|
-
if (typeof endpoint !== "string" || endpoint.trim() === "") {
|
|
810
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport requires a non-empty endpoint");
|
|
811
|
-
}
|
|
812
|
-
let parsed;
|
|
813
|
-
try {
|
|
814
|
-
parsed = new URL(endpoint);
|
|
815
|
-
} catch {
|
|
816
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport endpoint must be an absolute URL");
|
|
817
|
-
}
|
|
818
|
-
if (parsed.protocol !== "https:") {
|
|
819
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport endpoint must use HTTPS");
|
|
820
|
-
}
|
|
821
|
-
if (endpoint !== `${parsed.origin}${parsed.pathname}`) {
|
|
822
|
-
throw new SdkError("configuration_error", "createReactNativeFetchTransport endpoint must be a plain HTTPS path");
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
function retryAfterMsFromHeaders(headers) {
|
|
827
|
-
if (!headers || typeof headers.get !== "function") {
|
|
828
|
-
return undefined;
|
|
829
|
-
}
|
|
830
|
-
return retryAfterMsFromHeader(headers.get("retry-after"));
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
function retryAfterMsFromHeader(value, now = Date.now()) {
|
|
834
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
835
|
-
return undefined;
|
|
836
|
-
}
|
|
837
|
-
const trimmed = value.trim();
|
|
838
|
-
if (/^\d+$/u.test(trimmed)) {
|
|
839
|
-
const milliseconds = Number(trimmed) * 1000;
|
|
840
|
-
return Number.isSafeInteger(milliseconds) ? milliseconds : undefined;
|
|
841
|
-
}
|
|
842
|
-
const timestamp = Date.parse(trimmed);
|
|
843
|
-
return Number.isFinite(timestamp) ? Math.max(0, timestamp - now) : undefined;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function defaultErrorEventId({ message, screen }) {
|
|
847
|
-
return `evt_native_error_${slugify(`${screen ?? "app"}_${message}`)}`;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function defaultActionEventId({ name, screen }) {
|
|
851
|
-
return `evt_native_action_${slugify(`${screen ?? "app"}_${name ?? "event"}`)}`;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function defaultNetworkEventId({ method, routeTemplate, screen }) {
|
|
855
|
-
return `evt_native_network_${slugify([screen, method, routeTemplate].filter(Boolean).join("_") || "request")}`;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function defaultNavigationSpanEventId({ routeName, routePath, screen }) {
|
|
859
|
-
return `evt_native_navigation_${slugify([screen, routeName, routePath].filter(Boolean).join("_") || "route")}`;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
function defaultResourceSpanEventId({ method, routeTemplate, screen }) {
|
|
863
|
-
return `evt_native_resource_${slugify([screen, method, routeTemplate].filter(Boolean).join("_") || "request")}`;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function defaultFetch() {
|
|
867
|
-
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
function recordIssueBreadcrumb(client, event, category, type) {
|
|
871
|
-
if (typeof client?.addBreadcrumb !== "function") {
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
const status = typeof event?.attributes?.status === "string"
|
|
875
|
-
? event.attributes.status
|
|
876
|
-
: undefined;
|
|
877
|
-
const message = boundedBreadcrumbMessage(event?.attributes?.name);
|
|
878
|
-
client.addBreadcrumb({
|
|
879
|
-
category,
|
|
880
|
-
type,
|
|
881
|
-
level: status === "failure" || status === "error" ? "error" : "info",
|
|
882
|
-
...(message === undefined ? {} : { message }),
|
|
883
|
-
...(status === undefined ? {} : { data: { status } })
|
|
884
|
-
}, event.timestamp);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
function boundedBreadcrumbMessage(value) {
|
|
888
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
889
|
-
return undefined;
|
|
890
|
-
}
|
|
891
|
-
const characters = Array.from(value);
|
|
892
|
-
if (
|
|
893
|
-
characters.length > 512
|
|
894
|
-
|| characters.some((character) => {
|
|
895
|
-
const code = character.codePointAt(0);
|
|
896
|
-
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
897
|
-
})
|
|
898
|
-
) {
|
|
899
|
-
return undefined;
|
|
900
|
-
}
|
|
901
|
-
return value;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function defaultRandomValues(length) {
|
|
905
|
-
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
906
|
-
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
907
|
-
}
|
|
908
|
-
const bytes = new Uint8Array(length);
|
|
909
|
-
return globalThis.crypto.getRandomValues(bytes);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function headersWithTraceparent(headers, traceparent) {
|
|
913
|
-
const nextHeaders = {};
|
|
914
|
-
for (const [key, value] of headerEntries(headers)) {
|
|
915
|
-
if (String(key).toLowerCase() !== "traceparent") {
|
|
916
|
-
nextHeaders[key] = value;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
nextHeaders.traceparent = traceparent;
|
|
920
|
-
return nextHeaders;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function headerEntries(headers) {
|
|
924
|
-
if (headers === undefined || headers === null) {
|
|
925
|
-
return [];
|
|
926
|
-
}
|
|
927
|
-
const HeadersConstructor = globalThis.Headers;
|
|
928
|
-
if (typeof HeadersConstructor === "function" && headers instanceof HeadersConstructor) {
|
|
929
|
-
const entries = [];
|
|
930
|
-
headers.forEach((value, key) => {
|
|
931
|
-
entries.push([key, value]);
|
|
932
|
-
});
|
|
933
|
-
return entries;
|
|
934
|
-
}
|
|
935
|
-
if (Array.isArray(headers)) {
|
|
936
|
-
return headers;
|
|
937
|
-
}
|
|
938
|
-
if (typeof headers !== "string" && typeof headers[Symbol.iterator] === "function") {
|
|
939
|
-
return Array.from(headers);
|
|
940
|
-
}
|
|
941
|
-
if (typeof headers === "object") {
|
|
942
|
-
return Object.entries(headers);
|
|
943
|
-
}
|
|
944
|
-
return [];
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
function randomHex(length, randomValues) {
|
|
948
|
-
if (typeof randomValues !== "function") {
|
|
949
|
-
throw new SdkError("configuration_error", "randomValues must be a function");
|
|
950
|
-
}
|
|
951
|
-
const bytes = Array.from(randomValues(length));
|
|
952
|
-
if (bytes.length !== length || bytes.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
|
|
953
|
-
throw new SdkError("configuration_error", "randomValues must return byte values for the requested length");
|
|
954
|
-
}
|
|
955
|
-
return bytes.map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
function resolveTraceContext(trace) {
|
|
959
|
-
if (trace === undefined || trace === null) {
|
|
960
|
-
return undefined;
|
|
961
|
-
}
|
|
962
|
-
if (typeof trace === "string") {
|
|
963
|
-
return createReactNativeTraceContext({ traceparent: trace });
|
|
964
|
-
}
|
|
965
|
-
if (typeof trace === "object") {
|
|
966
|
-
const {
|
|
967
|
-
parentSpanId,
|
|
968
|
-
sampled,
|
|
969
|
-
spanId,
|
|
970
|
-
traceFlags = "01",
|
|
971
|
-
traceId
|
|
972
|
-
} = trace;
|
|
973
|
-
if (typeof traceId !== "string" || typeof spanId !== "string") {
|
|
974
|
-
throw new SdkError("validation_error", "trace context requires traceId and spanId");
|
|
975
|
-
}
|
|
976
|
-
createTraceparent({ traceId, spanId, traceFlags });
|
|
977
|
-
if (parentSpanId !== undefined) {
|
|
978
|
-
createTraceparent({ traceId, spanId: parentSpanId, traceFlags });
|
|
979
|
-
}
|
|
980
|
-
return freezeTraceContext({
|
|
981
|
-
parentSpanId,
|
|
982
|
-
sampled: sampled ?? sampledFromTraceFlags(traceFlags),
|
|
983
|
-
spanId,
|
|
984
|
-
traceFlags,
|
|
985
|
-
traceId
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
throw new SdkError("validation_error", "trace must be a trace context or traceparent string");
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function freezeTraceContext({ parentSpanId, sampled, spanId, traceFlags, traceId }) {
|
|
992
|
-
const normalized = {
|
|
993
|
-
traceId: String(traceId).toLowerCase(),
|
|
994
|
-
spanId: String(spanId).toLowerCase(),
|
|
995
|
-
parentSpanId: parentSpanId === undefined ? undefined : String(parentSpanId).toLowerCase(),
|
|
996
|
-
traceFlags: String(traceFlags).toLowerCase(),
|
|
997
|
-
sampled: Boolean(sampled)
|
|
998
|
-
};
|
|
999
|
-
return Object.freeze(normalized);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
function sampledFromTraceFlags(traceFlags) {
|
|
1003
|
-
return (Number.parseInt(traceFlags, 16) & 1) === 1;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
function removeActiveTraceScope(scopeId) {
|
|
1007
|
-
const index = activeTraceScopes.findIndex((scope) => scope.id === scopeId);
|
|
1008
|
-
if (index >= 0) {
|
|
1009
|
-
activeTraceScopes.splice(index, 1);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
function shouldPropagateToStringTarget(url, target) {
|
|
1014
|
-
const targetText = target.trim();
|
|
1015
|
-
if (targetText === "") {
|
|
1016
|
-
return false;
|
|
1017
|
-
}
|
|
1018
|
-
if (targetText.startsWith("/")) {
|
|
1019
|
-
return url.startsWith(targetText);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
const URLConstructor = globalThis.URL;
|
|
1023
|
-
if (typeof URLConstructor === "function") {
|
|
1024
|
-
try {
|
|
1025
|
-
const targetUrl = new URLConstructor(targetText);
|
|
1026
|
-
if (!hasUrlScheme(url)) {
|
|
1027
|
-
return false;
|
|
1028
|
-
}
|
|
1029
|
-
const requestUrl = new URLConstructor(url, targetUrl.origin);
|
|
1030
|
-
if (requestUrl.origin !== targetUrl.origin) {
|
|
1031
|
-
return false;
|
|
1032
|
-
}
|
|
1033
|
-
const targetPath = targetUrl.pathname || "/";
|
|
1034
|
-
return requestUrl.pathname === targetPath || requestUrl.pathname.startsWith(pathPrefix(targetPath));
|
|
1035
|
-
} catch {
|
|
1036
|
-
return url.startsWith(targetText);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
return url.startsWith(targetText);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function pathPrefix(pathname) {
|
|
1044
|
-
return pathname.endsWith("/") ? pathname : `${pathname}/`;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
function hasUrlScheme(url) {
|
|
1048
|
-
return /^[a-z][a-z0-9+.-]*:/iu.test(url);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
function requestHeaders(input, init) {
|
|
1052
|
-
if (init && init.headers !== undefined) {
|
|
1053
|
-
return init.headers;
|
|
1054
|
-
}
|
|
1055
|
-
return input?.headers;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
function requestUrl(input) {
|
|
1059
|
-
if (typeof input === "string") {
|
|
1060
|
-
return input;
|
|
1061
|
-
}
|
|
1062
|
-
const URLConstructor = globalThis.URL;
|
|
1063
|
-
if (typeof URLConstructor === "function" && input instanceof URLConstructor) {
|
|
1064
|
-
return input.toString();
|
|
1065
|
-
}
|
|
1066
|
-
if (typeof input?.url === "string") {
|
|
1067
|
-
return input.url;
|
|
1068
|
-
}
|
|
1069
|
-
return String(input);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
function slugify(value) {
|
|
1073
|
-
return String(value)
|
|
1074
|
-
.toLowerCase()
|
|
1075
|
-
.replace(/[^a-z0-9]+/g, "_")
|
|
1076
|
-
.replace(/^_+|_+$/g, "") || "event";
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
function screenActionName(screenName) {
|
|
1080
|
-
const directName = `${SCREEN_ACTION_PREFIX}${screenName}`;
|
|
1081
|
-
if (
|
|
1082
|
-
directName.length <= MAX_ACTION_NAME_LENGTH
|
|
1083
|
-
&& /^[a-z0-9_.:-]+$/i.test(directName)
|
|
1084
|
-
) {
|
|
1085
|
-
return directName;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
const maxScreenSegmentLength = MAX_ACTION_NAME_LENGTH - SCREEN_ACTION_PREFIX.length;
|
|
1089
|
-
const screenSegment = slugify(screenName)
|
|
1090
|
-
.slice(0, maxScreenSegmentLength)
|
|
1091
|
-
.replace(/_+$/g, "") || "event";
|
|
1092
|
-
return `${SCREEN_ACTION_PREFIX}${screenSegment}`;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function productAnalyticsMetadata(kind, surface) {
|
|
1096
|
-
return {
|
|
1097
|
-
analyticsSchemaVersion: PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
1098
|
-
analyticsKind: kind,
|
|
1099
|
-
analyticsSurface: boundedProductAnalyticsSurface(surface)
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
function boundedProductAnalyticsSurface(surface) {
|
|
1104
|
-
if (typeof surface !== "string") {
|
|
1105
|
-
return undefined;
|
|
1106
|
-
}
|
|
1107
|
-
const normalized = surface.trim();
|
|
1108
|
-
const characters = Array.from(normalized);
|
|
1109
|
-
if (
|
|
1110
|
-
normalized === ""
|
|
1111
|
-
|| characters.length > MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH
|
|
1112
|
-
|| characters.some((character) => {
|
|
1113
|
-
const code = character.codePointAt(0);
|
|
1114
|
-
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
1115
|
-
})
|
|
1116
|
-
) {
|
|
1117
|
-
return undefined;
|
|
1118
|
-
}
|
|
1119
|
-
return normalized;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
function statusFromStatusCode(statusCode) {
|
|
1123
|
-
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1124
|
-
return "failure";
|
|
1125
|
-
}
|
|
1126
|
-
return "success";
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
function spanStatusFromStatusCode(statusCode) {
|
|
1130
|
-
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1131
|
-
return "error";
|
|
1132
|
-
}
|
|
1133
|
-
return "ok";
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
function stripQueryAndHash(value) {
|
|
1137
|
-
if (value === undefined || value === null) {
|
|
1138
|
-
return undefined;
|
|
1139
|
-
}
|
|
1140
|
-
return String(value).split(/[?#]/u, 1)[0];
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
function resolveNavigationContainer(navigationContainer) {
|
|
1144
|
-
if (navigationContainer && typeof navigationContainer === "object" && "current" in navigationContainer) {
|
|
1145
|
-
return navigationContainer.current;
|
|
1146
|
-
}
|
|
1147
|
-
return navigationContainer;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function addNavigationListener(container, eventName, listener) {
|
|
1151
|
-
const subscription = container.addListener(eventName, listener);
|
|
1152
|
-
return subscriptionRemover(subscription);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function subscriptionRemover(subscription) {
|
|
1156
|
-
if (typeof subscription === "function") {
|
|
1157
|
-
return subscription;
|
|
1158
|
-
}
|
|
1159
|
-
if (subscription && typeof subscription.remove === "function") {
|
|
1160
|
-
return () => subscription.remove();
|
|
1161
|
-
}
|
|
1162
|
-
return () => {};
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
function safeNavigationListener(container, eventName, listener) {
|
|
1166
|
-
try {
|
|
1167
|
-
return addNavigationListener(container, eventName, listener);
|
|
1168
|
-
} catch {
|
|
1169
|
-
return undefined;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
function routeSnapshot(route) {
|
|
1174
|
-
return {
|
|
1175
|
-
key: normalizeMetadataValue(route?.key),
|
|
1176
|
-
name: typeof route?.name === "string" && route.name.trim() !== "" ? route.name : undefined,
|
|
1177
|
-
path: stripQueryAndHash(route?.path)
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
function navigationActionType(event) {
|
|
1182
|
-
const action = event?.data?.action ?? event?.action ?? event;
|
|
1183
|
-
return typeof action?.type === "string" && action.type.trim() !== "" ? action.type : undefined;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
function traceparentForRequest({
|
|
1187
|
-
init, input, randomValues, trace, traceFlags, traceparent, traceparentFactory, url
|
|
1188
|
-
}) {
|
|
1189
|
-
const context = resolveTraceContext(trace) ?? getActiveLogBrewTrace();
|
|
1190
|
-
const nextTraceparent = typeof traceparentFactory === "function"
|
|
1191
|
-
? traceparentFactory({ init, input, url })
|
|
1192
|
-
: traceparent ?? (context ? createReactNativeTraceHeaders(context).traceparent : createReactNativeTraceparent({ randomValues, traceFlags }));
|
|
1193
|
-
parseTraceparent(nextTraceparent);
|
|
1194
|
-
return nextTraceparent;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
export default {
|
|
1198
|
-
LogBrewNativeProvider, captureAppStateChange, captureReactNativeAction, captureReactNativeError,
|
|
1199
|
-
captureReactNativeNetwork, captureReactNativeNavigationSpan, captureReactNativeResourceSpan, captureScreenView,
|
|
1200
|
-
bindLogBrewTrace, createAppStateListener, createLogBrewReactNativeClient, createReactNavigationSpanListener,
|
|
1201
|
-
createReactNativeFetchTransport, createReactNativeSpanAttributes, createReactNativeTraceContext,
|
|
1202
|
-
createReactNativeTraceHeaders, createReactNativeActionEvent,
|
|
1203
|
-
createReactNativeErrorEvent, createReactNativeNetworkEvent, createReactNativeNavigationSpanEvent,
|
|
1204
|
-
createReactNativeResourceSpanEvent, createReactNativeTraceparent, createTraceparentFetch, getActiveLogBrewTrace,
|
|
1205
|
-
getReactNativeContext, getReactNativeTraceMetadata, shouldPropagateTraceparent, useLogBrewNative,
|
|
1206
|
-
useLogBrewNativeActions, withLogBrewTrace
|
|
1207
|
-
};
|
|
1
|
+
import runtime from "./index.cjs";
|
|
2
|
+
|
|
3
|
+
export const {
|
|
4
|
+
LogBrewNativeProvider,
|
|
5
|
+
bindLogBrewTrace,
|
|
6
|
+
captureAppStateChange,
|
|
7
|
+
captureReactNativeAction,
|
|
8
|
+
captureReactNativeError,
|
|
9
|
+
captureReactNativeNavigationSpan,
|
|
10
|
+
captureReactNativeNetwork,
|
|
11
|
+
captureReactNativeResourceSpan,
|
|
12
|
+
captureScreenView,
|
|
13
|
+
createAppStateListener,
|
|
14
|
+
createLogBrewReactNativeClient,
|
|
15
|
+
createReactNavigationSpanListener,
|
|
16
|
+
createReactNativeActionEvent,
|
|
17
|
+
createReactNativeErrorEvent,
|
|
18
|
+
createReactNativeFetchTransport,
|
|
19
|
+
createReactNativeNavigationSpanEvent,
|
|
20
|
+
createReactNativeNetworkEvent,
|
|
21
|
+
createReactNativeResourceSpanEvent,
|
|
22
|
+
createReactNativeSpanAttributes,
|
|
23
|
+
createReactNativeTraceContext,
|
|
24
|
+
createReactNativeTraceHeaders,
|
|
25
|
+
createReactNativeTraceparent,
|
|
26
|
+
createTraceparentFetch,
|
|
27
|
+
getActiveLogBrewTrace,
|
|
28
|
+
getReactNativeContext,
|
|
29
|
+
getReactNativeTraceMetadata,
|
|
30
|
+
shouldPropagateTraceparent,
|
|
31
|
+
useLogBrewNative,
|
|
32
|
+
useLogBrewNativeActions,
|
|
33
|
+
withLogBrewTrace
|
|
34
|
+
} = runtime;
|
|
35
|
+
|
|
36
|
+
export default runtime.default;
|