@logbrew/react-native 0.1.11 → 0.1.13

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 (61) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +106 -3
  3. package/apple-native-diagnostics.d.ts +52 -0
  4. package/apple-native-diagnostics.js +286 -0
  5. package/expo.cjs +46 -0
  6. package/expo.d.ts +16 -0
  7. package/expo.js +5 -0
  8. package/fatal-replay.cjs +12 -1
  9. package/global-errors.cjs +3 -1
  10. package/index.cjs +84 -2
  11. package/index.d.cts +4 -0
  12. package/index.d.ts +4 -0
  13. package/index.js +84 -2
  14. package/index.native.d.ts +4 -0
  15. package/index.native.js +14 -1
  16. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  17. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  18. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  19. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  20. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  21. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  22. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  57. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  58. package/package.json +43 -5
  59. package/promise-rejections.cjs +3 -1
  60. package/scripts/sync-apple-native-sources.mjs +93 -0
  61. 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
- purgeLogBrewReactNativePersistentQueue
146
+ getLogBrewAppleNativeDiagnosticsStatus,
147
+ installLogBrewAppleNativeDiagnostics,
148
+ purgeLogBrewReactNativePersistentQueue,
149
+ replayLogBrewAppleNativeDiagnostics
137
150
  };
138
151
 
139
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