@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.
Files changed (72) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +174 -4
  3. package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
  4. package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
  5. package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  6. package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  7. package/apple-native-diagnostics.d.ts +52 -0
  8. package/apple-native-diagnostics.js +286 -0
  9. package/expo.cjs +46 -0
  10. package/expo.d.ts +16 -0
  11. package/expo.js +5 -0
  12. package/fatal-replay.cjs +12 -1
  13. package/global-errors.cjs +3 -1
  14. package/index.cjs +86 -2
  15. package/index.d.cts +7 -0
  16. package/index.d.ts +7 -0
  17. package/index.js +86 -2
  18. package/index.native.d.ts +47 -1
  19. package/index.native.js +81 -1
  20. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  21. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  22. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  57. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  58. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  59. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  60. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  61. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  62. package/ios/LBRNEventRecordStore.h +29 -0
  63. package/ios/LBRNEventRecordStore.m +746 -0
  64. package/ios/LBRNFatalStoreModule.mm +159 -14
  65. package/ios/LBRNPrivateStorage.h +7 -0
  66. package/ios/LBRNPrivateStorage.m +38 -0
  67. package/package.json +47 -4
  68. package/persistent-delivery.native.js +282 -0
  69. package/promise-rejections.cjs +3 -1
  70. package/scripts/sync-apple-native-sources.mjs +93 -0
  71. package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
  72. 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
- export * from "./index";
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,4 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface LBRNAppleDiagnosticsModule : NSObject <RCTBridgeModule>
4
+ @end
@@ -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