@logbrew/react-native 0.1.11 → 0.1.12
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/LogBrewReactNative.podspec +25 -3
- package/README.md +106 -3
- package/apple-native-diagnostics.d.ts +52 -0
- package/apple-native-diagnostics.js +286 -0
- package/expo.cjs +46 -0
- package/expo.d.ts +16 -0
- package/expo.js +5 -0
- package/fatal-replay.cjs +12 -1
- package/global-errors.cjs +3 -1
- package/index.cjs +84 -2
- package/index.d.cts +4 -0
- package/index.d.ts +4 -0
- package/index.js +84 -2
- package/index.native.d.ts +4 -0
- package/index.native.js +14 -1
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
- package/package.json +42 -4
- package/promise-rejections.cjs +3 -1
- package/scripts/sync-apple-native-sources.mjs +93 -0
- package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
package/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
createTraceparent,
|
|
5
5
|
LogBrewClient,
|
|
6
6
|
parseTraceparent,
|
|
7
|
+
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
7
8
|
SdkError,
|
|
8
9
|
TransportError
|
|
9
10
|
} = require("@logbrew/sdk");
|
|
@@ -17,6 +18,7 @@ const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
|
17
18
|
const DEFAULT_SDK_VERSION = "0.1.0";
|
|
18
19
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
19
20
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
21
|
+
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
20
22
|
const SCREEN_ACTION_PREFIX = "screen:";
|
|
21
23
|
const BACKGROUND_APP_STATES = new Set(["background", "inactive"]);
|
|
22
24
|
const LogBrewNativeContext = React.createContext(null);
|
|
@@ -27,6 +29,7 @@ function createLogBrewReactNativeClient({
|
|
|
27
29
|
automaticDelivery,
|
|
28
30
|
apiKey,
|
|
29
31
|
clientKey,
|
|
32
|
+
context,
|
|
30
33
|
deliveryIntervalMs,
|
|
31
34
|
deliveryQueueThreshold,
|
|
32
35
|
eventStore,
|
|
@@ -47,6 +50,7 @@ function createLogBrewReactNativeClient({
|
|
|
47
50
|
return LogBrewClient.create({
|
|
48
51
|
apiKey: authKey,
|
|
49
52
|
automaticDelivery,
|
|
53
|
+
context,
|
|
50
54
|
deliveryIntervalMs,
|
|
51
55
|
deliveryQueueThreshold,
|
|
52
56
|
eventStore,
|
|
@@ -304,9 +308,14 @@ function captureScreenView(client, screenName, {
|
|
|
304
308
|
...getReactNativeContext({ platform, appState }),
|
|
305
309
|
screen: screenName,
|
|
306
310
|
...metadata,
|
|
307
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
311
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
312
|
+
...productAnalyticsMetadata("screen_view", screenName)
|
|
308
313
|
}
|
|
309
314
|
});
|
|
315
|
+
recordIssueBreadcrumb(client, {
|
|
316
|
+
timestamp,
|
|
317
|
+
attributes: { name: screenActionName(screenName), status }
|
|
318
|
+
}, "navigation", "navigation");
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
function captureAppStateChange(client, state, {
|
|
@@ -329,6 +338,10 @@ function captureAppStateChange(client, state, {
|
|
|
329
338
|
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
330
339
|
}
|
|
331
340
|
});
|
|
341
|
+
recordIssueBreadcrumb(client, {
|
|
342
|
+
timestamp,
|
|
343
|
+
attributes: { name: "app_state_change", status: "success" }
|
|
344
|
+
}, "app.state", "state");
|
|
332
345
|
}
|
|
333
346
|
|
|
334
347
|
function createReactNativeActionEvent({
|
|
@@ -359,7 +372,8 @@ function createReactNativeActionEvent({
|
|
|
359
372
|
sessionId,
|
|
360
373
|
traceId,
|
|
361
374
|
...metadata,
|
|
362
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
375
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
376
|
+
...productAnalyticsMetadata("interaction", screen)
|
|
363
377
|
})
|
|
364
378
|
}
|
|
365
379
|
};
|
|
@@ -369,6 +383,7 @@ function captureReactNativeAction(client, input = {}) {
|
|
|
369
383
|
requireClient(client);
|
|
370
384
|
const event = createReactNativeActionEvent(input);
|
|
371
385
|
client.action(event.id, event.timestamp, event.attributes);
|
|
386
|
+
recordIssueBreadcrumb(client, event, "ui.action", "user");
|
|
372
387
|
return event;
|
|
373
388
|
}
|
|
374
389
|
|
|
@@ -421,6 +436,7 @@ function captureReactNativeNetwork(client, input = {}) {
|
|
|
421
436
|
requireClient(client);
|
|
422
437
|
const event = createReactNativeNetworkEvent(input);
|
|
423
438
|
client.action(event.id, event.timestamp, event.attributes);
|
|
439
|
+
recordIssueBreadcrumb(client, event, "http", "http");
|
|
424
440
|
return event;
|
|
425
441
|
}
|
|
426
442
|
|
|
@@ -459,6 +475,7 @@ function captureReactNativeNavigationSpan(client, input = {}) {
|
|
|
459
475
|
requireClient(client);
|
|
460
476
|
const event = createReactNativeNavigationSpanEvent(input);
|
|
461
477
|
client.span(event.id, event.timestamp, event.attributes);
|
|
478
|
+
recordIssueBreadcrumb(client, event, "navigation", "navigation");
|
|
462
479
|
return event;
|
|
463
480
|
}
|
|
464
481
|
|
|
@@ -579,10 +596,12 @@ function createReactNativeErrorEvent(error, {
|
|
|
579
596
|
debugIdMap,
|
|
580
597
|
environment,
|
|
581
598
|
fingerprint,
|
|
599
|
+
handled = true,
|
|
582
600
|
id,
|
|
583
601
|
idFactory = defaultErrorEventId,
|
|
584
602
|
includeStack = false,
|
|
585
603
|
level = "error",
|
|
604
|
+
mechanism = "react-native.error",
|
|
586
605
|
metadata = {},
|
|
587
606
|
now = () => new Date().toISOString(),
|
|
588
607
|
platform,
|
|
@@ -600,8 +619,10 @@ function createReactNativeErrorEvent(error, {
|
|
|
600
619
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
601
620
|
environment,
|
|
602
621
|
fingerprint,
|
|
622
|
+
handled,
|
|
603
623
|
includeErrorStack: includeStack,
|
|
604
624
|
level,
|
|
625
|
+
mechanism,
|
|
605
626
|
metadata: {
|
|
606
627
|
...getReactNativeContext({ platform, appState }),
|
|
607
628
|
errorValueType: details.valueType,
|
|
@@ -852,6 +873,40 @@ function defaultFetch() {
|
|
|
852
873
|
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
853
874
|
}
|
|
854
875
|
|
|
876
|
+
function recordIssueBreadcrumb(client, event, category, type) {
|
|
877
|
+
if (typeof client?.addBreadcrumb !== "function") {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const status = typeof event?.attributes?.status === "string"
|
|
881
|
+
? event.attributes.status
|
|
882
|
+
: undefined;
|
|
883
|
+
const message = boundedBreadcrumbMessage(event?.attributes?.name);
|
|
884
|
+
client.addBreadcrumb({
|
|
885
|
+
category,
|
|
886
|
+
type,
|
|
887
|
+
level: status === "failure" || status === "error" ? "error" : "info",
|
|
888
|
+
...(message === undefined ? {} : { message }),
|
|
889
|
+
...(status === undefined ? {} : { data: { status } })
|
|
890
|
+
}, event.timestamp);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function boundedBreadcrumbMessage(value) {
|
|
894
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
895
|
+
return undefined;
|
|
896
|
+
}
|
|
897
|
+
const characters = Array.from(value);
|
|
898
|
+
if (
|
|
899
|
+
characters.length > 512
|
|
900
|
+
|| characters.some((character) => {
|
|
901
|
+
const code = character.codePointAt(0);
|
|
902
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
903
|
+
})
|
|
904
|
+
) {
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
return value;
|
|
908
|
+
}
|
|
909
|
+
|
|
855
910
|
function defaultRandomValues(length) {
|
|
856
911
|
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
857
912
|
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
@@ -1043,6 +1098,33 @@ function screenActionName(screenName) {
|
|
|
1043
1098
|
return `${SCREEN_ACTION_PREFIX}${screenSegment}`;
|
|
1044
1099
|
}
|
|
1045
1100
|
|
|
1101
|
+
function productAnalyticsMetadata(kind, surface) {
|
|
1102
|
+
return {
|
|
1103
|
+
analyticsSchemaVersion: PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
1104
|
+
analyticsKind: kind,
|
|
1105
|
+
analyticsSurface: boundedProductAnalyticsSurface(surface)
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function boundedProductAnalyticsSurface(surface) {
|
|
1110
|
+
if (typeof surface !== "string") {
|
|
1111
|
+
return undefined;
|
|
1112
|
+
}
|
|
1113
|
+
const normalized = surface.trim();
|
|
1114
|
+
const characters = Array.from(normalized);
|
|
1115
|
+
if (
|
|
1116
|
+
normalized === ""
|
|
1117
|
+
|| characters.length > MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH
|
|
1118
|
+
|| characters.some((character) => {
|
|
1119
|
+
const code = character.codePointAt(0);
|
|
1120
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
1121
|
+
})
|
|
1122
|
+
) {
|
|
1123
|
+
return undefined;
|
|
1124
|
+
}
|
|
1125
|
+
return normalized;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1046
1128
|
function statusFromStatusCode(statusCode) {
|
|
1047
1129
|
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1048
1130
|
return "failure";
|
package/index.d.cts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
Metadata,
|
|
12
12
|
ReleaseAttributes,
|
|
13
13
|
SpanAttributes,
|
|
14
|
+
TelemetryContext,
|
|
14
15
|
Transport,
|
|
15
16
|
TransportResponse
|
|
16
17
|
} from "@logbrew/sdk";
|
|
@@ -36,6 +37,7 @@ export type CreateLogBrewReactNativeClientConfig = {
|
|
|
36
37
|
automaticDelivery?: boolean;
|
|
37
38
|
apiKey?: string;
|
|
38
39
|
clientKey?: string;
|
|
40
|
+
context?: TelemetryContext;
|
|
39
41
|
deliveryIntervalMs?: number;
|
|
40
42
|
deliveryQueueThreshold?: number;
|
|
41
43
|
/** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
|
|
@@ -287,10 +289,12 @@ export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
|
287
289
|
debugIdMap?: Record<string, string>;
|
|
288
290
|
environment?: string;
|
|
289
291
|
fingerprint?: string;
|
|
292
|
+
handled?: boolean;
|
|
290
293
|
id?: string;
|
|
291
294
|
idFactory?: (context: ReactNativeErrorIdFactoryContext) => string;
|
|
292
295
|
includeStack?: boolean;
|
|
293
296
|
level?: IssueAttributes["level"];
|
|
297
|
+
mechanism?: string;
|
|
294
298
|
now?: () => string;
|
|
295
299
|
release?: string;
|
|
296
300
|
runtime?: string;
|
package/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
Metadata,
|
|
12
12
|
ReleaseAttributes,
|
|
13
13
|
SpanAttributes,
|
|
14
|
+
TelemetryContext,
|
|
14
15
|
Transport,
|
|
15
16
|
TransportResponse
|
|
16
17
|
} from "@logbrew/sdk";
|
|
@@ -36,6 +37,7 @@ export type CreateLogBrewReactNativeClientConfig = {
|
|
|
36
37
|
automaticDelivery?: boolean;
|
|
37
38
|
apiKey?: string;
|
|
38
39
|
clientKey?: string;
|
|
40
|
+
context?: TelemetryContext;
|
|
39
41
|
deliveryIntervalMs?: number;
|
|
40
42
|
deliveryQueueThreshold?: number;
|
|
41
43
|
/** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
|
|
@@ -287,10 +289,12 @@ export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
|
287
289
|
debugIdMap?: Record<string, string>;
|
|
288
290
|
environment?: string;
|
|
289
291
|
fingerprint?: string;
|
|
292
|
+
handled?: boolean;
|
|
290
293
|
id?: string;
|
|
291
294
|
idFactory?: (context: ReactNativeErrorIdFactoryContext) => string;
|
|
292
295
|
includeStack?: boolean;
|
|
293
296
|
level?: IssueAttributes["level"];
|
|
297
|
+
mechanism?: string;
|
|
294
298
|
now?: () => string;
|
|
295
299
|
release?: string;
|
|
296
300
|
runtime?: string;
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createTraceparent,
|
|
5
5
|
LogBrewClient,
|
|
6
6
|
parseTraceparent,
|
|
7
|
+
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
7
8
|
SdkError,
|
|
8
9
|
TransportError
|
|
9
10
|
} from "@logbrew/sdk";
|
|
@@ -17,6 +18,7 @@ const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
|
17
18
|
const DEFAULT_SDK_VERSION = "0.1.0";
|
|
18
19
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
19
20
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
21
|
+
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
20
22
|
const SCREEN_ACTION_PREFIX = "screen:";
|
|
21
23
|
const BACKGROUND_APP_STATES = new Set(["background", "inactive"]);
|
|
22
24
|
const LogBrewNativeContext = React.createContext(null);
|
|
@@ -27,6 +29,7 @@ export function createLogBrewReactNativeClient({
|
|
|
27
29
|
automaticDelivery,
|
|
28
30
|
apiKey,
|
|
29
31
|
clientKey,
|
|
32
|
+
context,
|
|
30
33
|
deliveryIntervalMs,
|
|
31
34
|
deliveryQueueThreshold,
|
|
32
35
|
eventStore,
|
|
@@ -47,6 +50,7 @@ export function createLogBrewReactNativeClient({
|
|
|
47
50
|
return LogBrewClient.create({
|
|
48
51
|
apiKey: authKey,
|
|
49
52
|
automaticDelivery,
|
|
53
|
+
context,
|
|
50
54
|
deliveryIntervalMs,
|
|
51
55
|
deliveryQueueThreshold,
|
|
52
56
|
eventStore,
|
|
@@ -304,9 +308,14 @@ export function captureScreenView(client, screenName, {
|
|
|
304
308
|
...getReactNativeContext({ platform, appState }),
|
|
305
309
|
screen: screenName,
|
|
306
310
|
...metadata,
|
|
307
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
311
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
312
|
+
...productAnalyticsMetadata("screen_view", screenName)
|
|
308
313
|
}
|
|
309
314
|
});
|
|
315
|
+
recordIssueBreadcrumb(client, {
|
|
316
|
+
timestamp,
|
|
317
|
+
attributes: { name: screenActionName(screenName), status }
|
|
318
|
+
}, "navigation", "navigation");
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
export function captureAppStateChange(client, state, {
|
|
@@ -329,6 +338,10 @@ export function captureAppStateChange(client, state, {
|
|
|
329
338
|
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
330
339
|
}
|
|
331
340
|
});
|
|
341
|
+
recordIssueBreadcrumb(client, {
|
|
342
|
+
timestamp,
|
|
343
|
+
attributes: { name: "app_state_change", status: "success" }
|
|
344
|
+
}, "app.state", "state");
|
|
332
345
|
}
|
|
333
346
|
|
|
334
347
|
export function createReactNativeActionEvent({
|
|
@@ -359,7 +372,8 @@ export function createReactNativeActionEvent({
|
|
|
359
372
|
sessionId,
|
|
360
373
|
traceId,
|
|
361
374
|
...metadata,
|
|
362
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
375
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
376
|
+
...productAnalyticsMetadata("interaction", screen)
|
|
363
377
|
})
|
|
364
378
|
}
|
|
365
379
|
};
|
|
@@ -369,6 +383,7 @@ export function captureReactNativeAction(client, input = {}) {
|
|
|
369
383
|
requireClient(client);
|
|
370
384
|
const event = createReactNativeActionEvent(input);
|
|
371
385
|
client.action(event.id, event.timestamp, event.attributes);
|
|
386
|
+
recordIssueBreadcrumb(client, event, "ui.action", "user");
|
|
372
387
|
return event;
|
|
373
388
|
}
|
|
374
389
|
|
|
@@ -421,6 +436,7 @@ export function captureReactNativeNetwork(client, input = {}) {
|
|
|
421
436
|
requireClient(client);
|
|
422
437
|
const event = createReactNativeNetworkEvent(input);
|
|
423
438
|
client.action(event.id, event.timestamp, event.attributes);
|
|
439
|
+
recordIssueBreadcrumb(client, event, "http", "http");
|
|
424
440
|
return event;
|
|
425
441
|
}
|
|
426
442
|
|
|
@@ -459,6 +475,7 @@ export function captureReactNativeNavigationSpan(client, input = {}) {
|
|
|
459
475
|
requireClient(client);
|
|
460
476
|
const event = createReactNativeNavigationSpanEvent(input);
|
|
461
477
|
client.span(event.id, event.timestamp, event.attributes);
|
|
478
|
+
recordIssueBreadcrumb(client, event, "navigation", "navigation");
|
|
462
479
|
return event;
|
|
463
480
|
}
|
|
464
481
|
|
|
@@ -568,10 +585,12 @@ export function createReactNativeErrorEvent(error, {
|
|
|
568
585
|
debugIdMap,
|
|
569
586
|
environment,
|
|
570
587
|
fingerprint,
|
|
588
|
+
handled = true,
|
|
571
589
|
id,
|
|
572
590
|
idFactory = defaultErrorEventId,
|
|
573
591
|
includeStack = false,
|
|
574
592
|
level = "error",
|
|
593
|
+
mechanism = "react-native.error",
|
|
575
594
|
metadata = {},
|
|
576
595
|
now = () => new Date().toISOString(),
|
|
577
596
|
platform,
|
|
@@ -589,8 +608,10 @@ export function createReactNativeErrorEvent(error, {
|
|
|
589
608
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
590
609
|
environment,
|
|
591
610
|
fingerprint,
|
|
611
|
+
handled,
|
|
592
612
|
includeErrorStack: includeStack,
|
|
593
613
|
level,
|
|
614
|
+
mechanism,
|
|
594
615
|
metadata: {
|
|
595
616
|
...getReactNativeContext({ platform, appState }),
|
|
596
617
|
errorValueType: details.valueType,
|
|
@@ -841,6 +862,40 @@ function defaultFetch() {
|
|
|
841
862
|
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
842
863
|
}
|
|
843
864
|
|
|
865
|
+
function recordIssueBreadcrumb(client, event, category, type) {
|
|
866
|
+
if (typeof client?.addBreadcrumb !== "function") {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const status = typeof event?.attributes?.status === "string"
|
|
870
|
+
? event.attributes.status
|
|
871
|
+
: undefined;
|
|
872
|
+
const message = boundedBreadcrumbMessage(event?.attributes?.name);
|
|
873
|
+
client.addBreadcrumb({
|
|
874
|
+
category,
|
|
875
|
+
type,
|
|
876
|
+
level: status === "failure" || status === "error" ? "error" : "info",
|
|
877
|
+
...(message === undefined ? {} : { message }),
|
|
878
|
+
...(status === undefined ? {} : { data: { status } })
|
|
879
|
+
}, event.timestamp);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function boundedBreadcrumbMessage(value) {
|
|
883
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
884
|
+
return undefined;
|
|
885
|
+
}
|
|
886
|
+
const characters = Array.from(value);
|
|
887
|
+
if (
|
|
888
|
+
characters.length > 512
|
|
889
|
+
|| characters.some((character) => {
|
|
890
|
+
const code = character.codePointAt(0);
|
|
891
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
892
|
+
})
|
|
893
|
+
) {
|
|
894
|
+
return undefined;
|
|
895
|
+
}
|
|
896
|
+
return value;
|
|
897
|
+
}
|
|
898
|
+
|
|
844
899
|
function defaultRandomValues(length) {
|
|
845
900
|
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
846
901
|
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
@@ -1032,6 +1087,33 @@ function screenActionName(screenName) {
|
|
|
1032
1087
|
return `${SCREEN_ACTION_PREFIX}${screenSegment}`;
|
|
1033
1088
|
}
|
|
1034
1089
|
|
|
1090
|
+
function productAnalyticsMetadata(kind, surface) {
|
|
1091
|
+
return {
|
|
1092
|
+
analyticsSchemaVersion: PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
1093
|
+
analyticsKind: kind,
|
|
1094
|
+
analyticsSurface: boundedProductAnalyticsSurface(surface)
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function boundedProductAnalyticsSurface(surface) {
|
|
1099
|
+
if (typeof surface !== "string") {
|
|
1100
|
+
return undefined;
|
|
1101
|
+
}
|
|
1102
|
+
const normalized = surface.trim();
|
|
1103
|
+
const characters = Array.from(normalized);
|
|
1104
|
+
if (
|
|
1105
|
+
normalized === ""
|
|
1106
|
+
|| characters.length > MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH
|
|
1107
|
+
|| characters.some((character) => {
|
|
1108
|
+
const code = character.codePointAt(0);
|
|
1109
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
1110
|
+
})
|
|
1111
|
+
) {
|
|
1112
|
+
return undefined;
|
|
1113
|
+
}
|
|
1114
|
+
return normalized;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1035
1117
|
function statusFromStatusCode(statusCode) {
|
|
1036
1118
|
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1037
1119
|
return "failure";
|
package/index.native.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { EventStore, LogBrewClient } from "@logbrew/sdk";
|
|
|
2
2
|
import type { CreateLogBrewReactNativeClientConfig } from "./index.js";
|
|
3
3
|
|
|
4
4
|
export * from "./index.js";
|
|
5
|
+
export * from "./apple-native-diagnostics.js";
|
|
5
6
|
export {
|
|
6
7
|
installLogBrewReactNativeGlobalErrorHandler,
|
|
7
8
|
installLogBrewReactNativePromiseRejectionTracker
|
|
@@ -41,7 +42,10 @@ declare const defaultExport: Omit<
|
|
|
41
42
|
> & {
|
|
42
43
|
createLogBrewReactNativeClient: typeof createLogBrewReactNativeClient;
|
|
43
44
|
createDefaultLogBrewReactNativeClient: typeof createDefaultLogBrewReactNativeClient;
|
|
45
|
+
getLogBrewAppleNativeDiagnosticsStatus: typeof import("./apple-native-diagnostics.js").getLogBrewAppleNativeDiagnosticsStatus;
|
|
46
|
+
installLogBrewAppleNativeDiagnostics: typeof import("./apple-native-diagnostics.js").installLogBrewAppleNativeDiagnostics;
|
|
44
47
|
purgeLogBrewReactNativePersistentQueue: typeof purgeLogBrewReactNativePersistentQueue;
|
|
48
|
+
replayLogBrewAppleNativeDiagnostics: typeof import("./apple-native-diagnostics.js").replayLogBrewAppleNativeDiagnostics;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
export default defaultExport;
|
package/index.native.js
CHANGED
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
getReactNativeContext
|
|
11
11
|
} from "./index.js";
|
|
12
12
|
import baseDefault from "./index.js";
|
|
13
|
+
import {
|
|
14
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
15
|
+
installLogBrewAppleNativeDiagnostics,
|
|
16
|
+
replayLogBrewAppleNativeDiagnostics
|
|
17
|
+
} from "./apple-native-diagnostics.js";
|
|
13
18
|
import {
|
|
14
19
|
purgeReactNativePersistentQueue,
|
|
15
20
|
resolveReactNativePersistentEventStore
|
|
@@ -18,6 +23,11 @@ export {
|
|
|
18
23
|
installLogBrewReactNativeGlobalErrorHandler,
|
|
19
24
|
installLogBrewReactNativePromiseRejectionTracker
|
|
20
25
|
} from "./global-errors.native.js";
|
|
26
|
+
export {
|
|
27
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
28
|
+
installLogBrewAppleNativeDiagnostics,
|
|
29
|
+
replayLogBrewAppleNativeDiagnostics
|
|
30
|
+
};
|
|
21
31
|
|
|
22
32
|
export * from "./index.js";
|
|
23
33
|
|
|
@@ -133,7 +143,10 @@ const defaultExport = {
|
|
|
133
143
|
createDefaultLogBrewReactNativeClient,
|
|
134
144
|
createLogBrewReactNativeClient,
|
|
135
145
|
getDefaultReactNativeContext,
|
|
136
|
-
|
|
146
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
147
|
+
installLogBrewAppleNativeDiagnostics,
|
|
148
|
+
purgeLogBrewReactNativePersistentQueue,
|
|
149
|
+
replayLogBrewAppleNativeDiagnostics
|
|
137
150
|
};
|
|
138
151
|
|
|
139
152
|
export default defaultExport;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#import "LBRNAppleDiagnosticsModule.h"
|
|
2
|
+
|
|
3
|
+
#if __has_include(<LogBrewReactNative/LogBrewReactNative-Swift.h>)
|
|
4
|
+
#import <LogBrewReactNative/LogBrewReactNative-Swift.h>
|
|
5
|
+
#elif __has_include("LogBrewReactNative-Swift.h")
|
|
6
|
+
#import "LogBrewReactNative-Swift.h"
|
|
7
|
+
#else
|
|
8
|
+
#error "LogBrew React Native Apple diagnostics requires its generated Swift interface"
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
12
|
+
#import <LogBrewReactNativeSpec/LogBrewReactNativeSpec.h>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
@interface LBRNAppleDiagnosticsModule ()
|
|
16
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
17
|
+
<NativeLogBrewAppleDiagnosticsSpec>
|
|
18
|
+
#endif
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@implementation LBRNAppleDiagnosticsModule
|
|
22
|
+
|
|
23
|
+
RCT_EXPORT_MODULE(LogBrewAppleDiagnostics)
|
|
24
|
+
|
|
25
|
+
+ (BOOL)requiresMainQueueSetup
|
|
26
|
+
{
|
|
27
|
+
return YES;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(installNativeDiagnostics:(NSDictionary *)configuration)
|
|
31
|
+
{
|
|
32
|
+
return [LBRNAppleNativeDiagnostics installWithConfiguration:configuration];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
RCT_EXPORT_METHOD(replayNativeDiagnostics:(RCTPromiseResolveBlock)resolve
|
|
36
|
+
reject:(__unused RCTPromiseRejectBlock)reject)
|
|
37
|
+
{
|
|
38
|
+
[LBRNAppleNativeDiagnostics replayWithCompletion:^(NSDictionary *result) {
|
|
39
|
+
resolve(result);
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(nativeDiagnosticsStatus)
|
|
44
|
+
{
|
|
45
|
+
return [LBRNAppleNativeDiagnostics status];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
49
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
50
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
51
|
+
{
|
|
52
|
+
return std::make_shared<facebook::react::NativeLogBrewAppleDiagnosticsSpecJSI>(params);
|
|
53
|
+
}
|
|
54
|
+
#endif
|
|
55
|
+
|
|
56
|
+
@end
|