@logbrew/react-native 0.1.10 → 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 +174 -4
- package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
- package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
- package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- 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 +86 -2
- package/index.d.cts +7 -0
- package/index.d.ts +7 -0
- package/index.js +86 -2
- package/index.native.d.ts +47 -1
- package/index.native.js +81 -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/ios/LBRNEventRecordStore.h +29 -0
- package/ios/LBRNEventRecordStore.m +746 -0
- package/ios/LBRNFatalStoreModule.mm +159 -14
- package/ios/LBRNPrivateStorage.h +7 -0
- package/ios/LBRNPrivateStorage.m +38 -0
- package/package.json +47 -4
- package/persistent-delivery.native.js +282 -0
- package/promise-rejections.cjs +3 -1
- package/scripts/sync-apple-native-sources.mjs +93 -0
- package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
- package/src/NativeLogBrewFatalStore.ts +9 -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,8 +29,10 @@ function createLogBrewReactNativeClient({
|
|
|
27
29
|
automaticDelivery,
|
|
28
30
|
apiKey,
|
|
29
31
|
clientKey,
|
|
32
|
+
context,
|
|
30
33
|
deliveryIntervalMs,
|
|
31
34
|
deliveryQueueThreshold,
|
|
35
|
+
eventStore,
|
|
32
36
|
maxBatchBytes,
|
|
33
37
|
maxBatchEvents,
|
|
34
38
|
maxQueueBytes,
|
|
@@ -46,8 +50,10 @@ function createLogBrewReactNativeClient({
|
|
|
46
50
|
return LogBrewClient.create({
|
|
47
51
|
apiKey: authKey,
|
|
48
52
|
automaticDelivery,
|
|
53
|
+
context,
|
|
49
54
|
deliveryIntervalMs,
|
|
50
55
|
deliveryQueueThreshold,
|
|
56
|
+
eventStore,
|
|
51
57
|
maxBatchBytes,
|
|
52
58
|
maxBatchEvents,
|
|
53
59
|
maxQueueBytes,
|
|
@@ -302,9 +308,14 @@ function captureScreenView(client, screenName, {
|
|
|
302
308
|
...getReactNativeContext({ platform, appState }),
|
|
303
309
|
screen: screenName,
|
|
304
310
|
...metadata,
|
|
305
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
311
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
312
|
+
...productAnalyticsMetadata("screen_view", screenName)
|
|
306
313
|
}
|
|
307
314
|
});
|
|
315
|
+
recordIssueBreadcrumb(client, {
|
|
316
|
+
timestamp,
|
|
317
|
+
attributes: { name: screenActionName(screenName), status }
|
|
318
|
+
}, "navigation", "navigation");
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
function captureAppStateChange(client, state, {
|
|
@@ -327,6 +338,10 @@ function captureAppStateChange(client, state, {
|
|
|
327
338
|
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
328
339
|
}
|
|
329
340
|
});
|
|
341
|
+
recordIssueBreadcrumb(client, {
|
|
342
|
+
timestamp,
|
|
343
|
+
attributes: { name: "app_state_change", status: "success" }
|
|
344
|
+
}, "app.state", "state");
|
|
330
345
|
}
|
|
331
346
|
|
|
332
347
|
function createReactNativeActionEvent({
|
|
@@ -357,7 +372,8 @@ function createReactNativeActionEvent({
|
|
|
357
372
|
sessionId,
|
|
358
373
|
traceId,
|
|
359
374
|
...metadata,
|
|
360
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
375
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
376
|
+
...productAnalyticsMetadata("interaction", screen)
|
|
361
377
|
})
|
|
362
378
|
}
|
|
363
379
|
};
|
|
@@ -367,6 +383,7 @@ function captureReactNativeAction(client, input = {}) {
|
|
|
367
383
|
requireClient(client);
|
|
368
384
|
const event = createReactNativeActionEvent(input);
|
|
369
385
|
client.action(event.id, event.timestamp, event.attributes);
|
|
386
|
+
recordIssueBreadcrumb(client, event, "ui.action", "user");
|
|
370
387
|
return event;
|
|
371
388
|
}
|
|
372
389
|
|
|
@@ -419,6 +436,7 @@ function captureReactNativeNetwork(client, input = {}) {
|
|
|
419
436
|
requireClient(client);
|
|
420
437
|
const event = createReactNativeNetworkEvent(input);
|
|
421
438
|
client.action(event.id, event.timestamp, event.attributes);
|
|
439
|
+
recordIssueBreadcrumb(client, event, "http", "http");
|
|
422
440
|
return event;
|
|
423
441
|
}
|
|
424
442
|
|
|
@@ -457,6 +475,7 @@ function captureReactNativeNavigationSpan(client, input = {}) {
|
|
|
457
475
|
requireClient(client);
|
|
458
476
|
const event = createReactNativeNavigationSpanEvent(input);
|
|
459
477
|
client.span(event.id, event.timestamp, event.attributes);
|
|
478
|
+
recordIssueBreadcrumb(client, event, "navigation", "navigation");
|
|
460
479
|
return event;
|
|
461
480
|
}
|
|
462
481
|
|
|
@@ -577,10 +596,12 @@ function createReactNativeErrorEvent(error, {
|
|
|
577
596
|
debugIdMap,
|
|
578
597
|
environment,
|
|
579
598
|
fingerprint,
|
|
599
|
+
handled = true,
|
|
580
600
|
id,
|
|
581
601
|
idFactory = defaultErrorEventId,
|
|
582
602
|
includeStack = false,
|
|
583
603
|
level = "error",
|
|
604
|
+
mechanism = "react-native.error",
|
|
584
605
|
metadata = {},
|
|
585
606
|
now = () => new Date().toISOString(),
|
|
586
607
|
platform,
|
|
@@ -598,8 +619,10 @@ function createReactNativeErrorEvent(error, {
|
|
|
598
619
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
599
620
|
environment,
|
|
600
621
|
fingerprint,
|
|
622
|
+
handled,
|
|
601
623
|
includeErrorStack: includeStack,
|
|
602
624
|
level,
|
|
625
|
+
mechanism,
|
|
603
626
|
metadata: {
|
|
604
627
|
...getReactNativeContext({ platform, appState }),
|
|
605
628
|
errorValueType: details.valueType,
|
|
@@ -850,6 +873,40 @@ function defaultFetch() {
|
|
|
850
873
|
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
851
874
|
}
|
|
852
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
|
+
|
|
853
910
|
function defaultRandomValues(length) {
|
|
854
911
|
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
855
912
|
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
@@ -1041,6 +1098,33 @@ function screenActionName(screenName) {
|
|
|
1041
1098
|
return `${SCREEN_ACTION_PREFIX}${screenSegment}`;
|
|
1042
1099
|
}
|
|
1043
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
|
+
|
|
1044
1128
|
function statusFromStatusCode(statusCode) {
|
|
1045
1129
|
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1046
1130
|
return "failure";
|
package/index.d.cts
CHANGED
|
@@ -4,12 +4,14 @@ import type {
|
|
|
4
4
|
DeliveryHealthSnapshot,
|
|
5
5
|
DroppedEvent,
|
|
6
6
|
EnvironmentAttributes,
|
|
7
|
+
EventStore,
|
|
7
8
|
IssueAttributes,
|
|
8
9
|
LogAttributes,
|
|
9
10
|
LogBrewClient,
|
|
10
11
|
Metadata,
|
|
11
12
|
ReleaseAttributes,
|
|
12
13
|
SpanAttributes,
|
|
14
|
+
TelemetryContext,
|
|
13
15
|
Transport,
|
|
14
16
|
TransportResponse
|
|
15
17
|
} from "@logbrew/sdk";
|
|
@@ -35,8 +37,11 @@ export type CreateLogBrewReactNativeClientConfig = {
|
|
|
35
37
|
automaticDelivery?: boolean;
|
|
36
38
|
apiKey?: string;
|
|
37
39
|
clientKey?: string;
|
|
40
|
+
context?: TelemetryContext;
|
|
38
41
|
deliveryIntervalMs?: number;
|
|
39
42
|
deliveryQueueThreshold?: number;
|
|
43
|
+
/** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
|
|
44
|
+
eventStore?: EventStore;
|
|
40
45
|
maxBatchBytes?: number;
|
|
41
46
|
maxBatchEvents?: number;
|
|
42
47
|
maxQueueBytes?: number;
|
|
@@ -284,10 +289,12 @@ export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
|
284
289
|
debugIdMap?: Record<string, string>;
|
|
285
290
|
environment?: string;
|
|
286
291
|
fingerprint?: string;
|
|
292
|
+
handled?: boolean;
|
|
287
293
|
id?: string;
|
|
288
294
|
idFactory?: (context: ReactNativeErrorIdFactoryContext) => string;
|
|
289
295
|
includeStack?: boolean;
|
|
290
296
|
level?: IssueAttributes["level"];
|
|
297
|
+
mechanism?: string;
|
|
291
298
|
now?: () => string;
|
|
292
299
|
release?: string;
|
|
293
300
|
runtime?: string;
|
package/index.d.ts
CHANGED
|
@@ -4,12 +4,14 @@ import type {
|
|
|
4
4
|
DeliveryHealthSnapshot,
|
|
5
5
|
DroppedEvent,
|
|
6
6
|
EnvironmentAttributes,
|
|
7
|
+
EventStore,
|
|
7
8
|
IssueAttributes,
|
|
8
9
|
LogAttributes,
|
|
9
10
|
LogBrewClient,
|
|
10
11
|
Metadata,
|
|
11
12
|
ReleaseAttributes,
|
|
12
13
|
SpanAttributes,
|
|
14
|
+
TelemetryContext,
|
|
13
15
|
Transport,
|
|
14
16
|
TransportResponse
|
|
15
17
|
} from "@logbrew/sdk";
|
|
@@ -35,8 +37,11 @@ export type CreateLogBrewReactNativeClientConfig = {
|
|
|
35
37
|
automaticDelivery?: boolean;
|
|
36
38
|
apiKey?: string;
|
|
37
39
|
clientKey?: string;
|
|
40
|
+
context?: TelemetryContext;
|
|
38
41
|
deliveryIntervalMs?: number;
|
|
39
42
|
deliveryQueueThreshold?: number;
|
|
43
|
+
/** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
|
|
44
|
+
eventStore?: EventStore;
|
|
40
45
|
maxBatchBytes?: number;
|
|
41
46
|
maxBatchEvents?: number;
|
|
42
47
|
maxQueueBytes?: number;
|
|
@@ -284,10 +289,12 @@ export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
|
284
289
|
debugIdMap?: Record<string, string>;
|
|
285
290
|
environment?: string;
|
|
286
291
|
fingerprint?: string;
|
|
292
|
+
handled?: boolean;
|
|
287
293
|
id?: string;
|
|
288
294
|
idFactory?: (context: ReactNativeErrorIdFactoryContext) => string;
|
|
289
295
|
includeStack?: boolean;
|
|
290
296
|
level?: IssueAttributes["level"];
|
|
297
|
+
mechanism?: string;
|
|
291
298
|
now?: () => string;
|
|
292
299
|
release?: string;
|
|
293
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,8 +29,10 @@ export function createLogBrewReactNativeClient({
|
|
|
27
29
|
automaticDelivery,
|
|
28
30
|
apiKey,
|
|
29
31
|
clientKey,
|
|
32
|
+
context,
|
|
30
33
|
deliveryIntervalMs,
|
|
31
34
|
deliveryQueueThreshold,
|
|
35
|
+
eventStore,
|
|
32
36
|
maxBatchBytes,
|
|
33
37
|
maxBatchEvents,
|
|
34
38
|
maxQueueBytes,
|
|
@@ -46,8 +50,10 @@ export function createLogBrewReactNativeClient({
|
|
|
46
50
|
return LogBrewClient.create({
|
|
47
51
|
apiKey: authKey,
|
|
48
52
|
automaticDelivery,
|
|
53
|
+
context,
|
|
49
54
|
deliveryIntervalMs,
|
|
50
55
|
deliveryQueueThreshold,
|
|
56
|
+
eventStore,
|
|
51
57
|
maxBatchBytes,
|
|
52
58
|
maxBatchEvents,
|
|
53
59
|
maxQueueBytes,
|
|
@@ -302,9 +308,14 @@ export function captureScreenView(client, screenName, {
|
|
|
302
308
|
...getReactNativeContext({ platform, appState }),
|
|
303
309
|
screen: screenName,
|
|
304
310
|
...metadata,
|
|
305
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
311
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
312
|
+
...productAnalyticsMetadata("screen_view", screenName)
|
|
306
313
|
}
|
|
307
314
|
});
|
|
315
|
+
recordIssueBreadcrumb(client, {
|
|
316
|
+
timestamp,
|
|
317
|
+
attributes: { name: screenActionName(screenName), status }
|
|
318
|
+
}, "navigation", "navigation");
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
export function captureAppStateChange(client, state, {
|
|
@@ -327,6 +338,10 @@ export function captureAppStateChange(client, state, {
|
|
|
327
338
|
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
328
339
|
}
|
|
329
340
|
});
|
|
341
|
+
recordIssueBreadcrumb(client, {
|
|
342
|
+
timestamp,
|
|
343
|
+
attributes: { name: "app_state_change", status: "success" }
|
|
344
|
+
}, "app.state", "state");
|
|
330
345
|
}
|
|
331
346
|
|
|
332
347
|
export function createReactNativeActionEvent({
|
|
@@ -357,7 +372,8 @@ export function createReactNativeActionEvent({
|
|
|
357
372
|
sessionId,
|
|
358
373
|
traceId,
|
|
359
374
|
...metadata,
|
|
360
|
-
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace())
|
|
375
|
+
...getReactNativeTraceMetadata(trace ?? getActiveLogBrewTrace()),
|
|
376
|
+
...productAnalyticsMetadata("interaction", screen)
|
|
361
377
|
})
|
|
362
378
|
}
|
|
363
379
|
};
|
|
@@ -367,6 +383,7 @@ export function captureReactNativeAction(client, input = {}) {
|
|
|
367
383
|
requireClient(client);
|
|
368
384
|
const event = createReactNativeActionEvent(input);
|
|
369
385
|
client.action(event.id, event.timestamp, event.attributes);
|
|
386
|
+
recordIssueBreadcrumb(client, event, "ui.action", "user");
|
|
370
387
|
return event;
|
|
371
388
|
}
|
|
372
389
|
|
|
@@ -419,6 +436,7 @@ export function captureReactNativeNetwork(client, input = {}) {
|
|
|
419
436
|
requireClient(client);
|
|
420
437
|
const event = createReactNativeNetworkEvent(input);
|
|
421
438
|
client.action(event.id, event.timestamp, event.attributes);
|
|
439
|
+
recordIssueBreadcrumb(client, event, "http", "http");
|
|
422
440
|
return event;
|
|
423
441
|
}
|
|
424
442
|
|
|
@@ -457,6 +475,7 @@ export function captureReactNativeNavigationSpan(client, input = {}) {
|
|
|
457
475
|
requireClient(client);
|
|
458
476
|
const event = createReactNativeNavigationSpanEvent(input);
|
|
459
477
|
client.span(event.id, event.timestamp, event.attributes);
|
|
478
|
+
recordIssueBreadcrumb(client, event, "navigation", "navigation");
|
|
460
479
|
return event;
|
|
461
480
|
}
|
|
462
481
|
|
|
@@ -566,10 +585,12 @@ export function createReactNativeErrorEvent(error, {
|
|
|
566
585
|
debugIdMap,
|
|
567
586
|
environment,
|
|
568
587
|
fingerprint,
|
|
588
|
+
handled = true,
|
|
569
589
|
id,
|
|
570
590
|
idFactory = defaultErrorEventId,
|
|
571
591
|
includeStack = false,
|
|
572
592
|
level = "error",
|
|
593
|
+
mechanism = "react-native.error",
|
|
573
594
|
metadata = {},
|
|
574
595
|
now = () => new Date().toISOString(),
|
|
575
596
|
platform,
|
|
@@ -587,8 +608,10 @@ export function createReactNativeErrorEvent(error, {
|
|
|
587
608
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
588
609
|
environment,
|
|
589
610
|
fingerprint,
|
|
611
|
+
handled,
|
|
590
612
|
includeErrorStack: includeStack,
|
|
591
613
|
level,
|
|
614
|
+
mechanism,
|
|
592
615
|
metadata: {
|
|
593
616
|
...getReactNativeContext({ platform, appState }),
|
|
594
617
|
errorValueType: details.valueType,
|
|
@@ -839,6 +862,40 @@ function defaultFetch() {
|
|
|
839
862
|
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
840
863
|
}
|
|
841
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
|
+
|
|
842
899
|
function defaultRandomValues(length) {
|
|
843
900
|
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
844
901
|
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
@@ -1030,6 +1087,33 @@ function screenActionName(screenName) {
|
|
|
1030
1087
|
return `${SCREEN_ACTION_PREFIX}${screenSegment}`;
|
|
1031
1088
|
}
|
|
1032
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
|
+
|
|
1033
1117
|
function statusFromStatusCode(statusCode) {
|
|
1034
1118
|
if (typeof statusCode === "number" && Number.isFinite(statusCode) && statusCode >= 400) {
|
|
1035
1119
|
return "failure";
|
package/index.native.d.ts
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
import type { EventStore, LogBrewClient } from "@logbrew/sdk";
|
|
2
|
+
import type { CreateLogBrewReactNativeClientConfig } from "./index.js";
|
|
3
|
+
|
|
4
|
+
export * from "./index.js";
|
|
5
|
+
export * from "./apple-native-diagnostics.js";
|
|
2
6
|
export {
|
|
3
7
|
installLogBrewReactNativeGlobalErrorHandler,
|
|
4
8
|
installLogBrewReactNativePromiseRejectionTracker
|
|
5
9
|
} from "./global-errors";
|
|
10
|
+
|
|
11
|
+
export type ReactNativePersistentQueueMode = "auto" | "required" | "disabled";
|
|
12
|
+
|
|
13
|
+
export type CreateNativeLogBrewReactNativeClientConfig =
|
|
14
|
+
Omit<CreateLogBrewReactNativeClientConfig, "eventStore"> & ({
|
|
15
|
+
/**
|
|
16
|
+
* `auto` uses the linked app-private native queue when available and otherwise uses memory.
|
|
17
|
+
* `required` fails client creation when the native queue is unavailable. `disabled` uses memory.
|
|
18
|
+
*/
|
|
19
|
+
persistentQueue?: ReactNativePersistentQueueMode;
|
|
20
|
+
eventStore?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
/** Advanced app-owned synchronous persistence adapter. Mutually exclusive with persistentQueue. */
|
|
23
|
+
eventStore: EventStore;
|
|
24
|
+
persistentQueue?: never;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export declare function createLogBrewReactNativeClient(
|
|
28
|
+
config: CreateNativeLogBrewReactNativeClientConfig
|
|
29
|
+
): LogBrewClient;
|
|
30
|
+
|
|
31
|
+
export declare function createDefaultLogBrewReactNativeClient(
|
|
32
|
+
config: CreateNativeLogBrewReactNativeClientConfig
|
|
33
|
+
): LogBrewClient;
|
|
34
|
+
|
|
35
|
+
export declare function purgeLogBrewReactNativePersistentQueue(
|
|
36
|
+
config: { apiKey?: string; clientKey?: string }
|
|
37
|
+
): void;
|
|
38
|
+
|
|
39
|
+
declare const defaultExport: Omit<
|
|
40
|
+
typeof import("./index.js").default,
|
|
41
|
+
"createLogBrewReactNativeClient" | "createDefaultLogBrewReactNativeClient"
|
|
42
|
+
> & {
|
|
43
|
+
createLogBrewReactNativeClient: typeof createLogBrewReactNativeClient;
|
|
44
|
+
createDefaultLogBrewReactNativeClient: typeof createDefaultLogBrewReactNativeClient;
|
|
45
|
+
getLogBrewAppleNativeDiagnosticsStatus: typeof import("./apple-native-diagnostics.js").getLogBrewAppleNativeDiagnosticsStatus;
|
|
46
|
+
installLogBrewAppleNativeDiagnostics: typeof import("./apple-native-diagnostics.js").installLogBrewAppleNativeDiagnostics;
|
|
47
|
+
purgeLogBrewReactNativePersistentQueue: typeof purgeLogBrewReactNativePersistentQueue;
|
|
48
|
+
replayLogBrewAppleNativeDiagnostics: typeof import("./apple-native-diagnostics.js").replayLogBrewAppleNativeDiagnostics;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default defaultExport;
|
package/index.native.js
CHANGED
|
@@ -6,16 +6,77 @@ import {
|
|
|
6
6
|
captureReactNativeNetwork,
|
|
7
7
|
captureScreenView,
|
|
8
8
|
createAppStateListener,
|
|
9
|
-
createLogBrewReactNativeClient,
|
|
9
|
+
createLogBrewReactNativeClient as createPlatformNeutralClient,
|
|
10
10
|
getReactNativeContext
|
|
11
11
|
} from "./index.js";
|
|
12
|
+
import baseDefault from "./index.js";
|
|
13
|
+
import {
|
|
14
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
15
|
+
installLogBrewAppleNativeDiagnostics,
|
|
16
|
+
replayLogBrewAppleNativeDiagnostics
|
|
17
|
+
} from "./apple-native-diagnostics.js";
|
|
18
|
+
import {
|
|
19
|
+
purgeReactNativePersistentQueue,
|
|
20
|
+
resolveReactNativePersistentEventStore
|
|
21
|
+
} from "./persistent-delivery.native.js";
|
|
12
22
|
export {
|
|
13
23
|
installLogBrewReactNativeGlobalErrorHandler,
|
|
14
24
|
installLogBrewReactNativePromiseRejectionTracker
|
|
15
25
|
} from "./global-errors.native.js";
|
|
26
|
+
export {
|
|
27
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
28
|
+
installLogBrewAppleNativeDiagnostics,
|
|
29
|
+
replayLogBrewAppleNativeDiagnostics
|
|
30
|
+
};
|
|
16
31
|
|
|
17
32
|
export * from "./index.js";
|
|
18
33
|
|
|
34
|
+
export function createLogBrewReactNativeClient(config = {}) {
|
|
35
|
+
const input = config !== null && typeof config === "object" ? config : {};
|
|
36
|
+
const {
|
|
37
|
+
apiKey,
|
|
38
|
+
clientKey,
|
|
39
|
+
eventStore,
|
|
40
|
+
maxQueueBytes,
|
|
41
|
+
maxQueueSize,
|
|
42
|
+
persistentQueue = "auto",
|
|
43
|
+
...forwarded
|
|
44
|
+
} = input;
|
|
45
|
+
const hasExplicitPersistentQueue = Object.prototype.hasOwnProperty.call(
|
|
46
|
+
input,
|
|
47
|
+
"persistentQueue"
|
|
48
|
+
) && input.persistentQueue !== undefined;
|
|
49
|
+
const authKey = clientKey ?? apiKey;
|
|
50
|
+
if (typeof authKey !== "string" || authKey.trim() === "") {
|
|
51
|
+
return createPlatformNeutralClient(input);
|
|
52
|
+
}
|
|
53
|
+
const resolved = resolveReactNativePersistentEventStore({
|
|
54
|
+
authKey,
|
|
55
|
+
eventStore,
|
|
56
|
+
maxQueueBytes,
|
|
57
|
+
maxQueueSize,
|
|
58
|
+
persistentQueue,
|
|
59
|
+
hasExplicitPersistentQueue
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
return createPlatformNeutralClient({
|
|
63
|
+
...forwarded,
|
|
64
|
+
apiKey,
|
|
65
|
+
clientKey,
|
|
66
|
+
eventStore: resolved.eventStore,
|
|
67
|
+
maxQueueBytes,
|
|
68
|
+
maxQueueSize
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
resolved.abort();
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function purgeLogBrewReactNativePersistentQueue(config = {}) {
|
|
77
|
+
purgeReactNativePersistentQueue(config);
|
|
78
|
+
}
|
|
79
|
+
|
|
19
80
|
export function createDefaultLogBrewReactNativeClient(config = {}) {
|
|
20
81
|
return createLogBrewReactNativeClient(config);
|
|
21
82
|
}
|
|
@@ -70,3 +131,22 @@ export function createDefaultAppStateListener(client, options = {}) {
|
|
|
70
131
|
...options
|
|
71
132
|
});
|
|
72
133
|
}
|
|
134
|
+
|
|
135
|
+
const defaultExport = {
|
|
136
|
+
...baseDefault,
|
|
137
|
+
captureDefaultAppStateChange,
|
|
138
|
+
captureDefaultReactNativeAction,
|
|
139
|
+
captureDefaultReactNativeError,
|
|
140
|
+
captureDefaultReactNativeNetwork,
|
|
141
|
+
captureDefaultScreenView,
|
|
142
|
+
createDefaultAppStateListener,
|
|
143
|
+
createDefaultLogBrewReactNativeClient,
|
|
144
|
+
createLogBrewReactNativeClient,
|
|
145
|
+
getDefaultReactNativeContext,
|
|
146
|
+
getLogBrewAppleNativeDiagnosticsStatus,
|
|
147
|
+
installLogBrewAppleNativeDiagnostics,
|
|
148
|
+
purgeLogBrewReactNativePersistentQueue,
|
|
149
|
+
replayLogBrewAppleNativeDiagnostics
|
|
150
|
+
};
|
|
151
|
+
|
|
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
|