@logbrew/react-native 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,6 +86,8 @@ batches, and pauses repeated automatic sends after authentication, rate-limit,
86
86
  or non-retryable failures. Do not add a second app-owned flush interval.
87
87
  `flushOnBackground: true` requests one final flush when AppState becomes
88
88
  `inactive` or `background`; a failure never escapes the AppState callback.
89
+ Helpers generate a fresh opaque event ID by default. Explicit `id` and
90
+ `idFactory` values remain available for app-owned idempotency.
89
91
 
90
92
  ## Confirm Hosted Delivery And Event Visibility
91
93
 
@@ -24,7 +24,8 @@ final class AndroidDiagnosticsRuntime implements Thread.UncaughtExceptionHandler
24
24
  Context context,
25
25
  File storageRoot,
26
26
  EventRecordStore eventStore,
27
- AndroidNativeDiagnostics.Configuration configuration) {
27
+ AndroidNativeDiagnostics.Configuration configuration,
28
+ Runnable afterPersist) {
28
29
  this.configuration = configuration;
29
30
  previousHandler = Thread.getDefaultUncaughtExceptionHandler();
30
31
  MainThreadScheduler scheduler = new MainThreadScheduler(context);
@@ -36,7 +37,7 @@ final class AndroidDiagnosticsRuntime implements Thread.UncaughtExceptionHandler
36
37
  signalStore,
37
38
  configuration,
38
39
  scheduler,
39
- () -> {},
40
+ afterPersist,
40
41
  this::chain,
41
42
  scheduler.mainThread::getStackTrace);
42
43
  }
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
7
7
  import com.facebook.react.bridge.ReadableType;
8
8
  import com.facebook.react.bridge.WritableArray;
9
9
  import com.facebook.react.bridge.WritableMap;
10
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
10
11
  import java.io.File;
11
12
  import java.nio.charset.StandardCharsets;
12
13
  import java.security.MessageDigest;
@@ -137,7 +138,8 @@ final class FatalStoreModuleImpl {
137
138
  try {
138
139
  androidDiagnostics =
139
140
  new AndroidDiagnosticsRuntime(
140
- context, storageRoot, eventStore, configuration.configuration);
141
+ context, storageRoot, eventStore, configuration.configuration,
142
+ this::notifyDiagnosticPersisted);
141
143
  String statusValue = androidDiagnostics.install();
142
144
  return androidDiagnosticsResult(statusValue);
143
145
  } catch (RuntimeException error) {
@@ -146,6 +148,13 @@ final class FatalStoreModuleImpl {
146
148
  }
147
149
  }
148
150
 
151
+ private void notifyDiagnosticPersisted() {
152
+ try {
153
+ context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
154
+ .emit("logbrewAndroidDiagnosticPersisted", null);
155
+ } catch (RuntimeException ignored) {}
156
+ }
157
+
149
158
  synchronized WritableMap androidDiagnosticsStatus() {
150
159
  if (androidDiagnostics == null || !androidDiagnostics.installed()) {
151
160
  return androidDiagnosticsReceipt("not_installed", 0);
package/apollo.cjs CHANGED
@@ -176,7 +176,6 @@ function captureApolloSpan(client, {
176
176
  captureReactNativeResourceSpan(client, {
177
177
  appState,
178
178
  durationMs,
179
- id: defaultApolloSpanId({ operationName: details.operationName, operationType: details.operationType, screen }),
180
179
  kind: "graphql",
181
180
  metadata: {
182
181
  ...metadata,
@@ -280,19 +279,6 @@ function errorName(error) {
280
279
  return undefined;
281
280
  }
282
281
 
283
- function defaultApolloSpanId({ operationName, operationType, screen }) {
284
- return `evt_native_apollo_${slugify([screen, operationType, operationName].filter(Boolean).join("_") || "operation")}`;
285
- }
286
-
287
- function slugify(value) {
288
- return String(value)
289
- .trim()
290
- .toLowerCase()
291
- .replace(/[^a-z0-9]+/gu, "_")
292
- .replace(/^_+|_+$/gu, "")
293
- .slice(0, 96) || "event";
294
- }
295
-
296
282
  module.exports = {
297
283
  createReactNativeApolloLink,
298
284
  default: {
package/apollo.js CHANGED
@@ -172,7 +172,6 @@ function captureApolloSpan(client, {
172
172
  captureReactNativeResourceSpan(client, {
173
173
  appState,
174
174
  durationMs,
175
- id: defaultApolloSpanId({ operationName: details.operationName, operationType: details.operationType, screen }),
176
175
  kind: "graphql",
177
176
  metadata: {
178
177
  ...metadata,
@@ -276,19 +275,6 @@ function errorName(error) {
276
275
  return undefined;
277
276
  }
278
277
 
279
- function defaultApolloSpanId({ operationName, operationType, screen }) {
280
- return `evt_native_apollo_${slugify([screen, operationType, operationName].filter(Boolean).join("_") || "operation")}`;
281
- }
282
-
283
- function slugify(value) {
284
- return String(value)
285
- .trim()
286
- .toLowerCase()
287
- .replace(/[^a-z0-9]+/gu, "_")
288
- .replace(/^_+|_+$/gu, "")
289
- .slice(0, 96) || "event";
290
- }
291
-
292
278
  export default {
293
279
  createReactNativeApolloLink
294
280
  };
package/index.cjs CHANGED
@@ -16,7 +16,7 @@ const {
16
16
  } = require("./metadata.cjs");
17
17
 
18
18
  const DEFAULT_SDK_NAME = "logbrew-react-native";
19
- const DEFAULT_SDK_VERSION = "0.1.27";
19
+ const DEFAULT_SDK_VERSION = "0.1.29";
20
20
  const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
21
21
  const NATIVE_RANDOM_HEX = Symbol.for("co.logbrew.react-native.secure-random-hex");
22
22
  const MAX_ACTION_NAME_LENGTH = 64;
@@ -293,7 +293,7 @@ function getReactNativeContext({ platform, appState, metadata = {} } = {}) {
293
293
  }
294
294
 
295
295
  function captureScreenView(client, screenName, {
296
- id = `evt_screen_${slugify(screenName)}`,
296
+ id,
297
297
  timestamp = new Date().toISOString(),
298
298
  status = "success",
299
299
  platform,
@@ -303,7 +303,7 @@ function captureScreenView(client, screenName, {
303
303
  } = {}) {
304
304
  requireClient(client);
305
305
  requireNonEmpty("screen name", screenName);
306
- client.action(id, timestamp, {
306
+ client.action(id ?? defaultEventId("screen"), timestamp, {
307
307
  name: screenActionName(screenName),
308
308
  status,
309
309
  metadata: {
@@ -321,7 +321,7 @@ function captureScreenView(client, screenName, {
321
321
  }
322
322
 
323
323
  function captureAppStateChange(client, state, {
324
- id = `evt_app_state_${slugify(state)}`,
324
+ id,
325
325
  timestamp = new Date().toISOString(),
326
326
  platform,
327
327
  appState,
@@ -330,7 +330,7 @@ function captureAppStateChange(client, state, {
330
330
  } = {}) {
331
331
  requireClient(client);
332
332
  requireNonEmpty("app state", state);
333
- client.action(id, timestamp, {
333
+ client.action(id ?? defaultEventId("app_state"), timestamp, {
334
334
  name: "app_state_change",
335
335
  status: "success",
336
336
  metadata: {
@@ -855,25 +855,15 @@ function retryAfterMsFromHeader(value, now = Date.now()) {
855
855
  return Number.isFinite(timestamp) ? Math.max(0, timestamp - now) : undefined;
856
856
  }
857
857
 
858
- function defaultErrorEventId({ message, screen }) {
859
- return `evt_native_error_${slugify(`${screen ?? "app"}_${message}`)}`;
858
+ function defaultEventId(kind) {
859
+ return `evt_native_${kind}_${randomHex(16, defaultRandomValues)}`;
860
860
  }
861
861
 
862
- function defaultActionEventId({ name, screen }) {
863
- return `evt_native_action_${slugify(`${screen ?? "app"}_${name ?? "event"}`)}`;
864
- }
865
-
866
- function defaultNetworkEventId({ method, routeTemplate, screen }) {
867
- return `evt_native_network_${slugify([screen, method, routeTemplate].filter(Boolean).join("_") || "request")}`;
868
- }
869
-
870
- function defaultNavigationSpanEventId({ routeName, routePath, screen }) {
871
- return `evt_native_navigation_${slugify([screen, routeName, routePath].filter(Boolean).join("_") || "route")}`;
872
- }
873
-
874
- function defaultResourceSpanEventId({ method, routeTemplate, screen }) {
875
- return `evt_native_resource_${slugify([screen, method, routeTemplate].filter(Boolean).join("_") || "request")}`;
876
- }
862
+ const defaultErrorEventId = () => defaultEventId("error");
863
+ const defaultActionEventId = () => defaultEventId("action");
864
+ const defaultNetworkEventId = () => defaultEventId("network");
865
+ const defaultNavigationSpanEventId = () => defaultEventId("navigation");
866
+ const defaultResourceSpanEventId = () => defaultEventId("resource");
877
867
 
878
868
  function defaultFetch() {
879
869
  return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
@@ -939,7 +929,7 @@ function defaultRandomValues(length) {
939
929
  }
940
930
 
941
931
  function secureRandomError() {
942
- return new SdkError("configuration_error", "createReactNativeTraceparent requires secure random values");
932
+ return new SdkError("configuration_error", "React Native secure random values are unavailable");
943
933
  }
944
934
 
945
935
  function headersWithTraceparent(headers, traceparent) {
package/index.native.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AppState, NativeModules, Platform, TurboModuleRegistry } from "react-native";
1
+ import { AppState, DeviceEventEmitter, NativeModules, Platform, TurboModuleRegistry } from "react-native";
2
2
  import {
3
3
  captureAppStateChange,
4
4
  captureReactNativeAction,
@@ -91,13 +91,28 @@ export function createLogBrewReactNativeClient(config = {}) {
91
91
  if (resolved.durable) {
92
92
  Object.defineProperty(client, DURABLE_QUEUE, { value: true });
93
93
  }
94
- return bindAppleNativeCrashBreadcrumbs(client);
94
+ return bindAndroidNativeDelivery(bindAppleNativeCrashBreadcrumbs(client), resolved.durable);
95
95
  } catch (error) {
96
96
  resolved.abort();
97
97
  throw error;
98
98
  }
99
99
  }
100
100
 
101
+ function bindAndroidNativeDelivery(client, durable) {
102
+ if (!durable || Platform?.OS !== "android" || typeof DeviceEventEmitter?.addListener !== "function") {
103
+ return client;
104
+ }
105
+ const subscription = DeviceEventEmitter.addListener(
106
+ "logbrewAndroidDiagnosticPersisted", () => void client.flush().catch(() => {})
107
+ );
108
+ const shutdown = client.shutdown.bind(client);
109
+ client.shutdown = (...args) => shutdown(...args).then((result) => {
110
+ subscription?.remove?.();
111
+ return result;
112
+ });
113
+ return client;
114
+ }
115
+
101
116
  function bindAppleNativeCrashBreadcrumbs(client) {
102
117
  if (Platform?.OS !== "ios") {
103
118
  return client;
@@ -3,7 +3,7 @@ import Foundation
3
3
  @objc(LBRNAppleNativeDiagnostics)
4
4
  public final class LBRNAppleNativeDiagnostics: NSObject, @unchecked Sendable {
5
5
  private static let shared = LBRNAppleNativeDiagnostics()
6
- private static let sdkVersion = "0.1.27"
6
+ private static let sdkVersion = "0.1.29"
7
7
 
8
8
  private let lock = NSLock()
9
9
  private let replayQueue = DispatchQueue(label: "co.logbrew.react-native.apple-diagnostics-replay")
package/lifecycle.cjs CHANGED
@@ -97,8 +97,8 @@ function requireClient(client) {
97
97
  }
98
98
  }
99
99
 
100
- function defaultLifecycleSpanEventId({ fromState, screen, toState }) {
101
- return `evt_native_lifecycle_${slugify([screen, fromState, toState].filter(Boolean).join("_") || "app_state")}`;
100
+ function defaultLifecycleSpanEventId() {
101
+ return `evt_native_lifecycle_${createReactNativeTraceContext().traceId}`;
102
102
  }
103
103
 
104
104
  function normalizeLifecycleState(state) {
@@ -115,13 +115,6 @@ function subscriptionRemover(subscription) {
115
115
  return () => {};
116
116
  }
117
117
 
118
- function slugify(value) {
119
- return String(value)
120
- .toLowerCase()
121
- .replace(/[^a-z0-9]+/g, "_")
122
- .replace(/^_+|_+$/g, "") || "event";
123
- }
124
-
125
118
  module.exports = {
126
119
  captureReactNativeLifecycleSpan,
127
120
  createAppStateLifecycleSpanListener,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/react-native",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "React Native offline delivery, native diagnostics, screen, error, trace, action, and network timeline helpers for LogBrew.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
@@ -252,7 +252,7 @@
252
252
  "url": "git+https://github.com/LogBrewCo/sdk.git"
253
253
  },
254
254
  "peerDependencies": {
255
- "@logbrew/sdk": "^0.1.20",
255
+ "@logbrew/sdk": "^0.1.24",
256
256
  "expo": ">=49",
257
257
  "react": ">=18",
258
258
  "react-native": ">=0.72"