@logbrew/react-native 0.1.0 → 0.1.1

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 (47) hide show
  1. package/README.md +407 -18
  2. package/apollo.cjs +301 -0
  3. package/apollo.d.cts +70 -0
  4. package/apollo.d.ts +70 -0
  5. package/apollo.js +294 -0
  6. package/examples/apollo-link-spans.mjs +149 -0
  7. package/examples/index.mjs +29 -1
  8. package/examples/instrumentation-kit.mjs +304 -0
  9. package/examples/lifecycle-spans.mjs +131 -0
  10. package/examples/native-bridge-scope.mjs +107 -0
  11. package/examples/navigation-resource-spans.mjs +156 -0
  12. package/examples/package.json +8 -1
  13. package/examples/real-user-smoke.mjs +106 -9
  14. package/examples/resource-fetch-spans.mjs +133 -0
  15. package/examples/trace-correlation.mjs +135 -0
  16. package/index.cjs +625 -111
  17. package/index.d.cts +236 -0
  18. package/index.d.ts +236 -0
  19. package/index.js +613 -95
  20. package/index.native.js +18 -0
  21. package/instrumentation.cjs +639 -0
  22. package/instrumentation.d.cts +84 -0
  23. package/instrumentation.d.ts +84 -0
  24. package/instrumentation.js +634 -0
  25. package/lifecycle.cjs +129 -0
  26. package/lifecycle.d.cts +50 -0
  27. package/lifecycle.d.ts +50 -0
  28. package/lifecycle.js +121 -0
  29. package/metadata.cjs +175 -0
  30. package/metadata.js +165 -0
  31. package/metro.cjs +310 -0
  32. package/metro.d.cts +37 -0
  33. package/metro.d.ts +37 -0
  34. package/metro.js +6 -0
  35. package/native-bridge.cjs +127 -0
  36. package/native-bridge.d.cts +60 -0
  37. package/native-bridge.d.ts +60 -0
  38. package/native-bridge.js +125 -0
  39. package/package.json +113 -4
  40. package/release-artifacts.cjs +344 -0
  41. package/release-artifacts.d.cts +55 -0
  42. package/release-artifacts.d.ts +53 -0
  43. package/release-artifacts.js +8 -0
  44. package/resource-fetch.cjs +469 -0
  45. package/resource-fetch.d.cts +60 -0
  46. package/resource-fetch.d.ts +60 -0
  47. package/resource-fetch.js +464 -0
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # @logbrew/react-native
2
2
 
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/LogBrewCo/sdk/main/assets/brand/logbrew-logo-transparent-512.png" alt="LogBrew logo" width="96" height="96">
5
+ </p>
6
+
3
7
  React Native helpers for the public LogBrew JavaScript SDK.
4
8
 
5
- This package is intentionally thin. It keeps all event validation, retry, flush, and shutdown behavior in `@logbrew/sdk`, while adding mobile-friendly helpers for screen views, app-state changes, handled JavaScript errors, provider/hook usage, and explicit W3C trace propagation for mobile fetch calls.
9
+ This package is intentionally thin. It keeps all event validation, retry, flush, and shutdown behavior in `@logbrew/sdk`, while adding mobile-friendly helpers for screen views, app-state changes, product actions, API milestones, handled JavaScript errors, provider/hook usage, active W3C trace correlation, explicit W3C trace propagation, opt-in lifecycle spans, opt-in resource fetch spans, opt-in reversible global fetch spans, app-owned native bridge scope sync, and reversible instrumentation setup.
6
10
 
7
11
  ## Install
8
12
 
@@ -38,7 +42,47 @@ const stopListening = createAppStateListener(client, AppState, {
38
42
  });
39
43
  ```
40
44
 
41
- For mobile apps, prefer an app-scoped public key through `clientKey`. `apiKey` is still accepted for compatibility with lower-level SDK examples and tests.
45
+ For mobile apps, prefer an app-scoped public key through `clientKey`. `apiKey` is still accepted for compatibility with lower-level SDK examples.
46
+
47
+ ## Product Actions And API Milestones
48
+
49
+ Use explicit action and network helpers for important mobile funnel steps your app already understands. These events are designed for timelines and agent analysis without enabling broad automatic replay:
50
+
51
+ ```js
52
+ import {
53
+ captureReactNativeAction,
54
+ captureReactNativeNetwork,
55
+ createReactNativeTraceContext,
56
+ withLogBrewTrace
57
+ } from "@logbrew/react-native";
58
+
59
+ const trace = createReactNativeTraceContext({
60
+ traceparent: incomingTraceparent
61
+ });
62
+
63
+ withLogBrewTrace(trace, () => {
64
+ captureReactNativeAction(client, {
65
+ name: "checkout.submit",
66
+ screen: "Checkout",
67
+ sessionId: "session_123",
68
+ metadata: {
69
+ funnel: "checkout",
70
+ step: "submit"
71
+ }
72
+ });
73
+
74
+ captureReactNativeNetwork(client, {
75
+ method: "POST",
76
+ routeTemplate: "/api/checkout",
77
+ statusCode: 202,
78
+ durationMs: 128,
79
+ screen: "Checkout",
80
+ sessionId: "session_123"
81
+ });
82
+ });
83
+ ```
84
+
85
+ `routeTemplate` is stripped of query strings and hashes before capture. Keep metadata low-cardinality and primitive-only, such as screen names, route templates, funnel names, step names, status codes, durations, session IDs, or trace IDs. Active trace metadata overwrites caller-supplied trace keys so accidental spoofed IDs do not break correlation. Do not send request bodies, response bodies, authorization headers, user-entered form values, or full URLs with private query text. LogBrew does not patch global `fetch` or record visual replay from this package.
42
86
 
43
87
  ## Error Capture
44
88
 
@@ -62,43 +106,136 @@ try {
62
106
 
63
107
  Set `includeStack: true` only when your app has decided stack text is safe to send. Non-`Error` thrown values are accepted and converted into issue messages so app error handlers do not need custom guards.
64
108
 
109
+ When you prepare React Native release artifacts, wrap the app-owned Metro config once. Production bundles and source maps receive one matching Debug ID, while development and hot-reload serialization remain unchanged:
110
+
111
+ ```js
112
+ // metro.config.js
113
+ const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
114
+ const { withLogBrewMetroConfig } = require("@logbrew/react-native/metro");
115
+
116
+ module.exports = withLogBrewMetroConfig(
117
+ mergeConfig(getDefaultConfig(__dirname), {})
118
+ );
119
+ ```
120
+
121
+ Then use the same release identity when capturing the error. The Metro-injected runtime registry connects each matching parsed JavaScript frame to its Debug ID without another app option:
122
+
123
+ ```js
124
+ captureReactNativeError(client, error, {
125
+ platform: Platform,
126
+ appState: AppState,
127
+ screen: "Checkout",
128
+ release: "2026.06.18",
129
+ environment: "production",
130
+ service: "checkout-mobile",
131
+ runtime: "react-native"
132
+ });
133
+ ```
134
+
135
+ The wrapper composes an existing custom serializer, is idempotent, and adds no network behavior. A string-returning custom serializer may preserve Metro's default bundle code; a serializer that changes code must return `{ code, map }` so LogBrew cannot attach a mismatched source map. If an advanced build pipeline cannot use the wrapper, `debugIdMap` remains an explicit override and takes precedence over runtime discovery. LogBrew records up to 32 ordered path-only generated frames with matching Debug IDs, release/environment/service/runtime, and active trace IDs when available. It strips query strings, hashes, hosts, and local absolute paths from React Native frame data; raw stack text is still opt-in with `includeStack: true`. Hosted source-map lookup remains backend-owned and requires the matching uploaded release artifact.
136
+
65
137
  ## Provider And Hooks
66
138
 
67
139
  ```js
68
140
  import { AppState, Platform } from "react-native";
69
141
  import {
142
+ createReactNativeTraceContext,
70
143
  LogBrewNativeProvider,
71
144
  useLogBrewNativeActions
72
145
  } from "@logbrew/react-native";
73
146
 
74
147
  function CheckoutScreen() {
75
- const { captureScreenView } = useLogBrewNativeActions();
148
+ const {
149
+ captureReactNativeAction,
150
+ captureReactNativeNetwork,
151
+ captureScreenView
152
+ } = useLogBrewNativeActions();
76
153
  captureScreenView("Checkout");
154
+ captureReactNativeAction({
155
+ name: "checkout.view",
156
+ screen: "Checkout",
157
+ metadata: { funnel: "checkout", step: "view" }
158
+ });
159
+ captureReactNativeNetwork({
160
+ method: "GET",
161
+ routeTemplate: "/api/cart",
162
+ statusCode: 200,
163
+ durationMs: 42,
164
+ screen: "Checkout"
165
+ });
77
166
  return null;
78
167
  }
79
168
 
80
169
  export function App({ client }) {
170
+ const trace = createReactNativeTraceContext({
171
+ traceparent: incomingTraceparent
172
+ });
81
173
  return (
82
- <LogBrewNativeProvider client={client} platform={Platform} appState={AppState}>
174
+ <LogBrewNativeProvider client={client} platform={Platform} appState={AppState} trace={trace}>
83
175
  <CheckoutScreen />
84
176
  </LogBrewNativeProvider>
85
177
  );
86
178
  }
87
179
  ```
88
180
 
89
- The package ships a `react-native` entry that imports `AppState` and `Platform` for Metro, while the default Node entry accepts those dependencies explicitly. That keeps packaged examples and CI smoke tests runnable without pretending a Node process is a native runtime.
181
+ The package ships a `react-native` entry that imports `AppState` and `Platform` for Metro, while the default Node entry accepts those dependencies explicitly. That keeps mobile setup explicit instead of pretending a Node process is a native runtime.
90
182
 
91
183
  ## Trace Propagation
92
184
 
185
+ Use an active trace when one product operation should connect screen views, logs, handled errors, actions, network milestones, explicit spans, and outbound request headers. `createReactNativeTraceContext()` continues a valid W3C `traceparent` with a fresh local span ID and falls back to a local root when the incoming value is missing or malformed:
186
+
187
+ ```js
188
+ import {
189
+ createReactNativeSpanAttributes,
190
+ createReactNativeTraceContext,
191
+ createReactNativeTraceHeaders,
192
+ getReactNativeTraceMetadata,
193
+ getActiveLogBrewTrace,
194
+ withLogBrewTrace
195
+ } from "@logbrew/react-native";
196
+
197
+ const trace = createReactNativeTraceContext({
198
+ traceparent: incomingTraceparent
199
+ });
200
+
201
+ withLogBrewTrace(trace, activeTrace => {
202
+ client.log("evt_log_checkout", new Date().toISOString(), {
203
+ message: "checkout started",
204
+ level: "info",
205
+ metadata: {
206
+ screen: "Checkout",
207
+ ...getReactNativeTraceMetadata(activeTrace)
208
+ }
209
+ });
210
+ client.span("evt_span_checkout", new Date().toISOString(), createReactNativeSpanAttributes({
211
+ name: "mobile.checkout",
212
+ status: "ok",
213
+ durationMs: 132,
214
+ trace: activeTrace
215
+ }));
216
+ console.log(getActiveLogBrewTrace()?.traceId);
217
+ });
218
+
219
+ const headers = createReactNativeTraceHeaders(trace);
220
+ ```
221
+
222
+ For async handlers, keep the returned `trace` object and pass it explicitly after `await` boundaries, or use provider `trace` so hook helpers receive it directly. This avoids pretending React Native has a universal async context manager while still making event-handler correlation simple and predictable.
223
+
93
224
  Use `createTraceparentFetch()` when a React Native app should connect mobile fetch work to backend traces. Propagation is target-scoped by default: no `traceparent` header is attached unless the request URL matches `tracePropagationTargets`.
94
225
 
95
226
  ```js
96
227
  import {
228
+ createReactNativeTraceContext,
97
229
  createReactNativeTraceparent,
98
230
  createTraceparentFetch
99
231
  } from "@logbrew/react-native";
100
232
 
233
+ const trace = createReactNativeTraceContext({
234
+ traceparent: incomingTraceparent
235
+ });
236
+
101
237
  const tracedFetch = createTraceparentFetch({
238
+ trace,
102
239
  traceparentFactory: () => createReactNativeTraceparent(),
103
240
  tracePropagationTargets: [
104
241
  "https://api.example.com/",
@@ -112,22 +249,274 @@ await tracedFetch("https://api.example.com/checkout", {
112
249
  });
113
250
  ```
114
251
 
115
- `tracePropagationTargets` accepts strings, regular expressions, or `(url) => boolean` functions. Match narrowly so mobile requests do not send tracing headers to unrelated origins. If the API is cross-origin or behind a gateway, allow the `traceparent` request header there too.
252
+ When `traceparentFactory` is omitted, `createTraceparentFetch()` reuses the supplied or active trace context. `tracePropagationTargets` accepts strings, regular expressions, or `(url) => boolean` functions. String URL targets apply only to the same origin plus a path prefix, so `https://api.example.com/v1` covers `/v1/orders` on that origin but not `https://wrong.example.com` or `/v10`. Keep targets narrow so mobile requests do not send tracing headers to unrelated origins. If the API is cross-origin or behind a gateway, allow the `traceparent` request header there too.
253
+
254
+ ## Lifecycle, Navigation, And Resource Spans
255
+
256
+ Use explicit span helpers when you want app foreground/background transitions, route changes, and API resources to appear in the same trace as mobile actions and errors. The AppState lifecycle listener records app-owned lifecycle spans without replacing the simpler action-only `createAppStateListener()`:
257
+
258
+ ```js
259
+ import { createReactNativeTraceContext } from "@logbrew/react-native";
260
+ import { createAppStateLifecycleSpanListener } from "@logbrew/react-native/lifecycle";
261
+
262
+ const trace = createReactNativeTraceContext({
263
+ traceparent: incomingTraceparent
264
+ });
116
265
 
117
- ## Packaged Examples
266
+ const stopLifecycleTracing = createAppStateLifecycleSpanListener(client, AppState, {
267
+ trace,
268
+ platform: Platform,
269
+ screen: "Checkout",
270
+ sessionId: "session_123",
271
+ captureInitialState: true
272
+ });
273
+ ```
118
274
 
119
- After install, these commands are available from a consumer app:
275
+ `createAppStateLifecycleSpanListener()` captures the current AppState as primitive metadata, records transition names such as `app_state:active->background`, and measures duration from the previous observed state when possible. It does not patch React Native internals, derive session health, or inspect native bridge state.
276
+
277
+ The React Navigation listener accepts a navigation container ref shape without adding a React Navigation dependency:
278
+
279
+ ```js
280
+ import {
281
+ captureReactNativeResourceSpan,
282
+ createReactNavigationSpanListener,
283
+ } from "@logbrew/react-native";
284
+
285
+ const stopNavigationTracing = createReactNavigationSpanListener(client, navigationRef, {
286
+ trace,
287
+ platform: Platform,
288
+ appState: AppState,
289
+ metadata: { flow: "checkout" }
290
+ });
291
+
292
+ captureReactNativeResourceSpan(client, {
293
+ trace,
294
+ method: "POST",
295
+ routeTemplate: "/api/checkout",
296
+ statusCode: 202,
297
+ durationMs: 171,
298
+ screen: "Checkout"
299
+ });
300
+ ```
301
+
302
+ `createReactNavigationSpanListener()` listens for React Navigation `state` changes and uses `__unsafe_action__` dispatch timing when the container exposes it. Route names and query-stripped route paths are captured; route keys are omitted unless `includeRouteKey: true` is set because they can be high-cardinality. `captureReactNativeResourceSpan()` records app-owned resource spans without patching global `fetch`/XHR, reading request bodies, copying headers, or storing full URLs with query text.
303
+
304
+ For app-owned fetch calls where you want the resource span and outbound `traceparent` in one place, use the explicit resource-fetch subpath:
305
+
306
+ ```js
307
+ import {
308
+ createReactNativeGraphQLMetadataFactory,
309
+ createReactNativeResourceFetch
310
+ } from "@logbrew/react-native/resource-fetch";
311
+
312
+ const resourceFetch = createReactNativeResourceFetch(client, {
313
+ trace,
314
+ platform: Platform,
315
+ appState: AppState,
316
+ screen: "Checkout",
317
+ measureResponseBodySize: true,
318
+ metadataFactory: createReactNativeGraphQLMetadataFactory({
319
+ endpoint: "/graphql"
320
+ }),
321
+ tracePropagationTargets: ["https://api.example.com/"]
322
+ });
323
+
324
+ await resourceFetch("https://api.example.com/graphql?email=hidden", {
325
+ method: "POST",
326
+ headers: { accept: "application/json" },
327
+ body: JSON.stringify({
328
+ query: "mutation CheckoutSubmit($email: String!) { checkout(email: $email) { id } }",
329
+ variables: { email: "hidden@example.com" }
330
+ })
331
+ });
332
+ ```
333
+
334
+ `createReactNativeResourceFetch()` wraps the fetch function your app supplies, or the runtime `fetch` when available. It records status, method, duration, response-start timing, sanitized route template, screen, session, primitive metadata, response size when `Content-Length` is available, and trace correlation. `metadataFactory` is called after each fetch completes or fails so apps can add low-cardinality request metadata such as `graphqlOperationName` or `graphqlOperationType`; by default LogBrew does not parse GraphQL payloads. If you set `measureResponseBodySize: true`, LogBrew can fall back to measuring a cloned response body's byte length when the response omits `Content-Length`; it returns the original response untouched and does not store the response content. Metadata returned from the factory keeps only primitive values and drops sensitive request fields. It does not patch global `fetch` or XHR, inspect request or response bodies by default, capture arbitrary headers, or attach `traceparent` outside `tracePropagationTargets`. Pass `trace` explicitly after `await` boundaries or build the wrapper from provider/hook state so async resource spans stay correlated.
335
+ `createReactNativeGraphQLMetadataFactory()` is an explicit helper for GraphQL requests your app already owns. Pass `endpoint` as a route template, absolute URL without query/hash, `RegExp`, predicate, or an array of those when you use it with broader fetch/XHR instrumentation; LogBrew compares route templates and query-stripped URL paths before parsing. It reads only a JSON string request body to derive `graphqlOperationName` and `graphqlOperationType`, drops variables/query text/body fields, ignores large or non-JSON bodies, and can compose an existing primitive metadata factory. Do not use it on unrelated endpoints without an endpoint matcher.
336
+
337
+ If your app uses Apollo Client, use the optional Apollo subpath with the `ApolloLink` constructor your app already imports:
338
+
339
+ ```js
340
+ import { ApolloLink } from "@apollo/client";
341
+ import { createReactNativeApolloLink } from "@logbrew/react-native/apollo";
342
+
343
+ const logbrewApolloLink = createReactNativeApolloLink(client, {
344
+ ApolloLink,
345
+ trace,
346
+ screen: "Checkout",
347
+ metadata: { flow: "checkout" }
348
+ });
349
+ ```
350
+
351
+ `createReactNativeApolloLink()` returns an app-owned Apollo Link. It records one `graphql.<operationType> <operationName>` span when an operation completes or fails, writes one normalized W3C `traceparent` into the operation context by default, and keeps primitive metadata such as `graphqlOperationName`, `graphqlOperationType`, `framework`, and `source`. It does not add an Apollo dependency to default LogBrew installs, patch global fetch/XHR, capture query text, variables, payloads, response data, arbitrary headers, cookies, error messages, stacks, baggage, or tracestate. Pass `propagateTraceparent: false` if another Apollo link owns outbound propagation.
352
+
353
+ ## Native Bridge Scope Sync
354
+
355
+ Use the native bridge subpath when JavaScript needs to pass the active LogBrew trace into a native module call your app owns. The helper builds a primitive-only scope payload and sends it through a callback or adapter method such as `setLogBrewScope()`:
356
+
357
+ ```js
358
+ import { createReactNativeTraceContext } from "@logbrew/react-native";
359
+ import { withLogBrewNativeBridgeScope } from "@logbrew/react-native/native-bridge";
360
+
361
+ const trace = createReactNativeTraceContext({
362
+ traceparent: incomingTraceparent
363
+ });
364
+
365
+ await withLogBrewNativeBridgeScope(nativeCheckoutModule, {
366
+ trace,
367
+ logger: "NativeCheckout",
368
+ screen: "Checkout",
369
+ sessionId: "session_123",
370
+ metadata: {
371
+ routeTemplate: "/native/checkout"
372
+ }
373
+ }, async () => {
374
+ await nativeCheckoutModule.submitOrder();
375
+ });
376
+ ```
377
+
378
+ `withLogBrewNativeBridgeScope()` syncs the scope before the callback and clears it afterward, including async callbacks. The payload contains only trace IDs, sampled flags, and primitive metadata. It does not install a native module, inspect native bridge arguments, sync user/session identity, capture payloads or headers, derive session health, or patch React Native internals.
379
+
380
+ ## Reversible Instrumentation Setup
381
+
382
+ Use the instrumentation subpath when you want one setup call to install the app-owned pieces above and receive a resource fetch wrapper:
383
+
384
+ ```js
385
+ import { createReactNativeTraceContext } from "@logbrew/react-native";
386
+ import { createLogBrewReactNativeInstrumentation } from "@logbrew/react-native/instrumentation";
387
+
388
+ const trace = createReactNativeTraceContext({
389
+ traceparent: incomingTraceparent
390
+ });
391
+
392
+ const instrumentation = createLogBrewReactNativeInstrumentation(client, {
393
+ trace,
394
+ platform: Platform,
395
+ appState: AppState,
396
+ navigationContainer: navigationRef,
397
+ nativeBridge: nativeCheckoutModule,
398
+ screen: "Checkout",
399
+ sessionId: "session_123",
400
+ tracePropagationTargets: ["https://api.example.com/"],
401
+ captureInitialLifecycleState: true,
402
+ captureInitialNavigationRoute: true
403
+ });
404
+
405
+ await instrumentation.resourceFetch("https://api.example.com/checkout", {
406
+ method: "POST"
407
+ });
408
+
409
+ instrumentation.remove();
410
+ ```
411
+
412
+ `createLogBrewReactNativeInstrumentation()` composes existing AppState lifecycle spans, React Navigation spans, target-scoped resource fetch spans, and native bridge scope sync into a removable handle. It does not patch global `fetch`, XHR, React Navigation, AppState, or native modules by default; it only subscribes to the objects your app passes in and returns `remove()`/`stop()` so setup is reversible. Keep `tracePropagationTargets` narrow and continue to avoid request bodies, response bodies, arbitrary headers, full URLs with query text, and high-cardinality route keys.
413
+
414
+ If migrating an app with many existing `fetch(...)` calls, opt into reversible global fetch instrumentation explicitly:
415
+
416
+ ```js
417
+ const instrumentation = createLogBrewReactNativeInstrumentation(client, {
418
+ trace,
419
+ screen: "Checkout",
420
+ instrumentGlobalFetch: true,
421
+ measureFetchResponseBodySize: true,
422
+ tracePropagationTargets: ["https://api.example.com/"]
423
+ });
424
+
425
+ await fetch("https://api.example.com/checkout", { method: "POST" });
426
+ instrumentation.remove();
427
+ ```
428
+
429
+ With `instrumentGlobalFetch: true`, LogBrew wraps the current `globalThis.fetch`, records the same sanitized resource spans as `resourceFetch`, and puts the original function back only if LogBrew still owns the `fetch` slot. Response-start timing is measured when the fetch promise resolves, and total duration includes any explicit cloned-body sizing you opt into. Response size is read from `Content-Length` by default; set `measureFetchResponseBodySize: true` only when your app accepts clone-based response body sizing for responses without that header. Outbound `traceparent` remains target-scoped; LogBrew still does not patch XHR, read original request or response bodies, copy arbitrary headers, persist offline requests, capture full URLs with query/hash text, or inspect GraphQL payloads unless you explicitly pass the GraphQL metadata factory above.
430
+
431
+ Apps with older libraries that still use `XMLHttpRequest` can opt into reversible XHR instrumentation separately:
432
+
433
+ ```js
434
+ const instrumentation = createLogBrewReactNativeInstrumentation(client, {
435
+ trace,
436
+ screen: "Checkout",
437
+ instrumentGlobalXMLHttpRequest: true,
438
+ measureXhrResponseBodySize: true,
439
+ tracePropagationTargets: ["https://api.example.com/"]
440
+ });
441
+
442
+ const xhr = new XMLHttpRequest();
443
+ xhr.open("POST", "https://api.example.com/checkout?email=hidden");
444
+ xhr.send(JSON.stringify({ ignored: "body is not captured" }));
445
+ instrumentation.remove();
446
+ ```
447
+
448
+ With `instrumentGlobalXMLHttpRequest: true`, LogBrew patches only `XMLHttpRequest.prototype.open` and `send`, records sanitized XHR resource spans with status, response-start timing, and response size when `Content-Length` is available, and puts the original methods back when it is safe to do so. If you set `measureXhrResponseBodySize: true`, LogBrew can fall back to measuring the completed XHR response object's byte length without storing the response content. It writes a single `traceparent` through the app's existing `setRequestHeader` only for configured targets. It does not capture request bodies, response bodies, arbitrary request headers, arbitrary response headers, cookies, GraphQL payloads, full URLs with query/hash text, baggage, or tracestate.
449
+ If you pass `metadataFactory: createReactNativeGraphQLMetadataFactory({ endpoint: "/graphql" })`, XHR spans can derive the GraphQL operation name and type from JSON string request bodies your app already owns and skip unrelated endpoints. The helper still drops variables, query text, body fields, headers, payloads, response data, baggage, and tracestate; do not enable it broadly without endpoint matching.
450
+
451
+ ## Release Artifact Preparation
452
+
453
+ Use the release-artifacts subpath after the wrapped React Native build has emitted a Metro bundle and source map. The helper preserves the Metro-injected Debug ID, strips embedded source content by default, writes a local manifest, and can run the installed upload path. It also injects a matching ID when used without the Metro wrapper for compatibility. Hosted JavaScript bundle upload is an explicit opt-in; rendered symbolicated issues and native crash symbolication remain separate service capabilities:
454
+
455
+ ```js
456
+ import { prepareLogBrewReactNativeReleaseArtifacts } from "@logbrew/react-native/release-artifacts";
457
+
458
+ prepareLogBrewReactNativeReleaseArtifacts({
459
+ bundle: "dist/index.android.bundle",
460
+ sourcemap: "dist/index.android.bundle.map",
461
+ platform: "android",
462
+ release: "2026.06.18",
463
+ environment: "production",
464
+ service: "checkout-mobile",
465
+ root: process.cwd()
466
+ });
467
+ ```
468
+
469
+ For a local loopback upload check, use the upload helper against a `localhost` or `127.0.0.1` endpoint:
470
+
471
+ ```js
472
+ import { uploadLogBrewReactNativeReleaseArtifacts } from "@logbrew/react-native/release-artifacts";
473
+
474
+ uploadLogBrewReactNativeReleaseArtifacts({
475
+ bundle: "dist/index.android.bundle",
476
+ sourcemap: "dist/index.android.bundle.map",
477
+ platform: "android",
478
+ release: "2026.06.18",
479
+ environment: "production",
480
+ service: "checkout-mobile",
481
+ root: process.cwd(),
482
+ endpoint: "http://127.0.0.1:4319/retry-success",
483
+ maxRetries: 2,
484
+ retryDelay: 0
485
+ });
486
+ ```
487
+
488
+ For a hosted release-artifact endpoint, keep the release-artifact auth value in an environment variable and opt in explicitly:
489
+
490
+ ```js
491
+ uploadLogBrewReactNativeReleaseArtifacts({
492
+ bundle: "dist/index.android.bundle",
493
+ sourcemap: "dist/index.android.bundle.map",
494
+ projectId: "550e8400-e29b-41d4-a716-446655440000",
495
+ platform: "android",
496
+ release: "2026.06.18",
497
+ environment: "production",
498
+ service: "checkout-mobile",
499
+ root: process.cwd(),
500
+ endpoint: "https://api.logbrew.com/api/release-artifacts",
501
+ allowHostedUpload: true,
502
+ tokenEnv: "LOGBREW_RELEASE_ARTIFACT_AUTH"
503
+ });
504
+ ```
505
+
506
+ The helper requires explicit `release`, `environment`, `service`, and `platform` metadata. Hosted uploads also require a UUID `projectId`; local preparation and loopback upload remain valid without it. It defaults minified bundle URLs to `app:///react-native/<platform>/...`, removes query strings and hashes from manifest URLs, and strips source paths under `root` or `stripSourcePrefix`. Hosted endpoints must use HTTPS and must not include embedded auth values, query strings, or fragments. The helper never uses normal SDK ingest keys or account/session API auth values. When `sourcemap` points at a final Hermes-composed map, the helper makes the bundle's `sourceMappingURL` point at that explicit map, so stale packager-map comments do not block manifest generation. The explicit Metro wrapper changes only app-owned serialization and one bounded runtime Debug-ID registry; neither helper patches Gradle, Xcode, global fetch/XHR, request payloads, or transport behavior.
507
+
508
+ React Native native symbols are handled as release artifacts, not runtime telemetry. For local dry-run validation, use the repo release-artifact tooling against app-owned build outputs such as `ios/build/.../*.dSYM`, `android/app/build/outputs/mapping/release/mapping.txt`, and `android/app/build/intermediates/merged_native_libs/.../*.so`. The current public SDK validates metadata and privacy boundaries only; backend upload, storage, lookup, and native symbolication are still backend-owned future support, so do not rely on normal runtime error capture for native crash symbolication yet.
509
+
510
+ ## Example Source
511
+
512
+ The package includes example source for screen views, app-state metadata, handled JavaScript errors, provider/hooks, active trace correlation, target-scoped trace propagation, lifecycle/resource spans, native bridge scope sync, and reversible instrumentation setup. After installing, inspect the shipped examples with:
120
513
 
121
514
  ```bash
122
- node node_modules/@logbrew/react-native/examples/index.mjs --help
123
515
  node node_modules/@logbrew/react-native/examples/index.mjs --list
124
- node node_modules/@logbrew/react-native/examples/index.mjs readme-example
125
- node node_modules/@logbrew/react-native/examples/index.mjs real-user-smoke
126
- node node_modules/@logbrew/react-native/examples/index.mjs
127
- npm --prefix node_modules/@logbrew/react-native/examples run help
128
- npm --prefix node_modules/@logbrew/react-native/examples run list
129
- npm --prefix node_modules/@logbrew/react-native/examples run readme-example
130
- npm --prefix node_modules/@logbrew/react-native/examples run real-user-smoke
516
+ node node_modules/@logbrew/react-native/examples/index.mjs instrumentation-kit
517
+ node node_modules/@logbrew/react-native/examples/index.mjs lifecycle-spans
518
+ node node_modules/@logbrew/react-native/examples/index.mjs native-bridge-scope
519
+ node node_modules/@logbrew/react-native/examples/index.mjs navigation-resource-spans
520
+ node node_modules/@logbrew/react-native/examples/index.mjs resource-fetch-spans
521
+ node node_modules/@logbrew/react-native/examples/index.mjs trace-correlation
131
522
  ```
132
-
133
- The default launcher path runs `real-user-smoke`.