crumbtrail-react-native 0.2.0

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/dist/index.js ADDED
@@ -0,0 +1,766 @@
1
+ // src/capabilities.ts
2
+ var REACT_NATIVE_CAPABILITY_BITS = {
3
+ asyncStorage: 1 << 0,
4
+ navigation: 1 << 1,
5
+ viewShot: 1 << 2
6
+ };
7
+ var OPTIONAL_MODULES = [
8
+ {
9
+ key: "asyncStorage",
10
+ packageName: "@react-native-async-storage/async-storage",
11
+ capability: "async-storage",
12
+ bit: REACT_NATIVE_CAPABILITY_BITS.asyncStorage
13
+ },
14
+ {
15
+ key: "navigation",
16
+ packageName: "@react-navigation/native",
17
+ capability: "navigation",
18
+ bit: REACT_NATIVE_CAPABILITY_BITS.navigation
19
+ },
20
+ {
21
+ key: "viewShot",
22
+ packageName: "react-native-view-shot",
23
+ capability: "view-snapshot",
24
+ bit: REACT_NATIVE_CAPABILITY_BITS.viewShot
25
+ }
26
+ ];
27
+ function detectReactNativeCapabilities(options = {}) {
28
+ const resolver = options.resolver ?? safeRequireOptionalModule;
29
+ const modules = {};
30
+ const capabilities = [];
31
+ let bitset = 0;
32
+ for (const optionalModule of OPTIONAL_MODULES) {
33
+ const present = isModulePresent(optionalModule.packageName, resolver);
34
+ modules[optionalModule.key] = {
35
+ packageName: optionalModule.packageName,
36
+ present,
37
+ status: present ? "present" : "absent"
38
+ };
39
+ if (present) {
40
+ bitset |= optionalModule.bit;
41
+ capabilities.push(optionalModule.capability);
42
+ }
43
+ }
44
+ return { bitset, capabilities, modules };
45
+ }
46
+ function isModulePresent(packageName, resolver) {
47
+ try {
48
+ return resolver(packageName) !== void 0;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function safeRequireOptionalModule(packageName) {
54
+ try {
55
+ const requireFn = getRequire();
56
+ return requireFn ? requireFn(packageName) : void 0;
57
+ } catch {
58
+ return void 0;
59
+ }
60
+ }
61
+ function getRequire() {
62
+ try {
63
+ const maybeRequire = Function(
64
+ 'return typeof require === "function" ? require : undefined'
65
+ )();
66
+ return typeof maybeRequire === "function" ? maybeRequire : void 0;
67
+ } catch {
68
+ return void 0;
69
+ }
70
+ }
71
+
72
+ // src/session-store.ts
73
+ import { DEFAULT_SESSION_STORAGE_KEY } from "crumbtrail-core";
74
+ function createReactNativeSessionStore(storage, key = DEFAULT_SESSION_STORAGE_KEY) {
75
+ if (!storage) return void 0;
76
+ let cached;
77
+ return {
78
+ read() {
79
+ return cached;
80
+ },
81
+ write(session) {
82
+ cached = session;
83
+ try {
84
+ const result = storage.setItem(key, JSON.stringify(session));
85
+ if (result && typeof result.catch === "function") {
86
+ void result.catch(() => {
87
+ });
88
+ }
89
+ } catch {
90
+ }
91
+ },
92
+ async hydrate() {
93
+ try {
94
+ const raw = await storage.getItem(key);
95
+ cached = parsePersistedSession(raw);
96
+ return cached;
97
+ } catch {
98
+ return void 0;
99
+ }
100
+ }
101
+ };
102
+ }
103
+ function parsePersistedSession(raw) {
104
+ if (!raw) return void 0;
105
+ try {
106
+ const parsed = JSON.parse(raw);
107
+ if (typeof parsed.id !== "string" || parsed.id.length === 0)
108
+ return void 0;
109
+ return {
110
+ id: parsed.id,
111
+ lastActivity: typeof parsed.lastActivity === "number" ? parsed.lastActivity : 0
112
+ };
113
+ } catch {
114
+ return void 0;
115
+ }
116
+ }
117
+
118
+ // src/logger.ts
119
+ import { Crumbtrail } from "crumbtrail-core";
120
+
121
+ // src/target-descriptor.ts
122
+ function createReactNativeTargetDescriptor(input) {
123
+ const descriptor = {
124
+ role: input.role,
125
+ label: input.label ?? input.accessibilityLabel,
126
+ testID: input.testID ?? input.testId,
127
+ accessibilityId: input.accessibilityId,
128
+ componentName: input.componentName,
129
+ routePath: input.routePath,
130
+ ancestryHash: input.ancestryHash,
131
+ bounds: input.bounds,
132
+ redaction: input.redaction
133
+ };
134
+ const hasIdentity = Boolean(
135
+ descriptor.role || descriptor.label || descriptor.testID || descriptor.accessibilityId || descriptor.componentName || descriptor.routePath || descriptor.ancestryHash
136
+ );
137
+ return hasIdentity ? descriptor : void 0;
138
+ }
139
+
140
+ // src/replay-lite.ts
141
+ function createReactNativeReplayLite(options) {
142
+ const emit2 = (type, data, target) => {
143
+ options.logger.addEvent({
144
+ type,
145
+ data,
146
+ platform: "react-native",
147
+ sdk: { name: "crumbtrail-react-native" },
148
+ capabilities: options.capabilities,
149
+ ...target ? { target } : {}
150
+ });
151
+ };
152
+ return {
153
+ recordViewSnapshot(snapshot) {
154
+ emit2("view-snapshot", {
155
+ kind: "component-tree",
156
+ routePath: snapshot.routePath,
157
+ root: sanitizeNode(snapshot.root)
158
+ });
159
+ },
160
+ recordTouch(overlay) {
161
+ const target = overlay.target ? createReactNativeTargetDescriptor(
162
+ overlay.target
163
+ ) : void 0;
164
+ emit2(
165
+ "touch",
166
+ {
167
+ kind: "overlay",
168
+ x: overlay.x,
169
+ y: overlay.y,
170
+ phase: overlay.phase ?? "press"
171
+ },
172
+ target
173
+ );
174
+ },
175
+ async captureCrashScreenshot(target) {
176
+ const capture = target !== void 0 && options.viewShot?.captureRef ? options.viewShot.captureRef(target, { format: "jpg", quality: 0.7 }) : options.viewShot?.captureScreen?.({ format: "jpg", quality: 0.7 });
177
+ if (!capture) return void 0;
178
+ try {
179
+ const uri = await capture;
180
+ emit2("view-snapshot", {
181
+ kind: "crash-screenshot",
182
+ uri,
183
+ capture: "react-native-view-shot"
184
+ });
185
+ return uri;
186
+ } catch {
187
+ return void 0;
188
+ }
189
+ }
190
+ };
191
+ }
192
+ function sanitizeNode(node) {
193
+ return {
194
+ id: node.id,
195
+ componentName: node.componentName,
196
+ role: node.role,
197
+ label: node.label,
198
+ testID: node.testID,
199
+ accessibilityId: node.accessibilityId,
200
+ bounds: node.bounds,
201
+ children: node.children?.map(sanitizeNode)
202
+ };
203
+ }
204
+
205
+ // src/collectors.ts
206
+ var DEFAULT_COLLECTORS = {
207
+ console: true,
208
+ errors: true,
209
+ network: true,
210
+ appState: true,
211
+ environment: true,
212
+ navigation: true,
213
+ replayLite: true
214
+ };
215
+ function startReactNativeCollectors(logger, options) {
216
+ const enabled = resolveCollectorConfig(options.config);
217
+ const globalObject = options.globalObject ?? globalThis;
218
+ const reactNative = options.reactNative ?? resolveModule("react-native", options.resolver);
219
+ const cleanup = [];
220
+ if (enabled.console)
221
+ cleanup.push(
222
+ startConsoleCollector(logger, options.capabilities, globalObject)
223
+ );
224
+ if (enabled.errors)
225
+ cleanup.push(
226
+ startErrorCollector(
227
+ logger,
228
+ options.capabilities,
229
+ globalObject,
230
+ options.errorUtils
231
+ )
232
+ );
233
+ if (enabled.network)
234
+ cleanup.push(
235
+ startNetworkCollector(logger, options.capabilities, globalObject)
236
+ );
237
+ if (enabled.appState)
238
+ cleanup.push(
239
+ startAppStateCollector(
240
+ logger,
241
+ options.capabilities,
242
+ reactNative?.AppState
243
+ )
244
+ );
245
+ if (enabled.environment)
246
+ startEnvironmentCollector(logger, options.capabilities, reactNative);
247
+ if (enabled.navigation)
248
+ cleanup.push(
249
+ startNavigationCollector(
250
+ logger,
251
+ options.capabilities,
252
+ options.navigation
253
+ )
254
+ );
255
+ const viewShot = resolveModule(
256
+ "react-native-view-shot",
257
+ options.resolver
258
+ );
259
+ const replayLite = enabled.replayLite ? createReactNativeReplayLite({
260
+ logger,
261
+ capabilities: options.capabilities.capabilities,
262
+ viewShot
263
+ }) : void 0;
264
+ return {
265
+ cleanup() {
266
+ for (const stop of cleanup.splice(0).reverse()) stop();
267
+ },
268
+ replayLite
269
+ };
270
+ }
271
+ function resolveCollectorConfig(config) {
272
+ if (config === false) {
273
+ return Object.fromEntries(
274
+ Object.keys(DEFAULT_COLLECTORS).map((key) => [key, false])
275
+ );
276
+ }
277
+ if (config === true || config === void 0) return { ...DEFAULT_COLLECTORS };
278
+ return { ...DEFAULT_COLLECTORS, ...config };
279
+ }
280
+ function emit(logger, capabilities, type, data, target) {
281
+ logger.addEvent({
282
+ type,
283
+ data,
284
+ platform: "react-native",
285
+ sdk: { name: "crumbtrail-react-native" },
286
+ capabilities: capabilities.capabilities,
287
+ ...target ? { target } : {}
288
+ });
289
+ }
290
+ function startConsoleCollector(logger, capabilities, globalObject) {
291
+ const consoleObject = globalObject.console;
292
+ if (!consoleObject) return () => {
293
+ };
294
+ const mutableConsole = consoleObject;
295
+ const methods = ["log", "warn", "error", "debug", "info"];
296
+ const originals = /* @__PURE__ */ new Map();
297
+ const level = {
298
+ log: "log",
299
+ warn: "warn",
300
+ error: "err",
301
+ debug: "dbg",
302
+ info: "info"
303
+ };
304
+ for (const method of methods) {
305
+ if (typeof mutableConsole[method] !== "function") continue;
306
+ const original = mutableConsole[method].bind(consoleObject);
307
+ originals.set(method, original);
308
+ mutableConsole[method] = (...args) => {
309
+ emit(logger, capabilities, "con", {
310
+ lv: level[method],
311
+ args: args.map((arg) => safeStringify(arg))
312
+ });
313
+ original(...args);
314
+ };
315
+ }
316
+ return () => {
317
+ for (const [method, original] of originals) {
318
+ mutableConsole[method] = original;
319
+ }
320
+ };
321
+ }
322
+ function startErrorCollector(logger, capabilities, globalObject, suppliedErrorUtils) {
323
+ const cleanup = [];
324
+ const errorUtils = suppliedErrorUtils ?? globalObject.ErrorUtils;
325
+ if (errorUtils?.setGlobalHandler && errorUtils.getGlobalHandler) {
326
+ const previous = errorUtils.getGlobalHandler();
327
+ if (typeof previous !== "function") return () => {
328
+ };
329
+ errorUtils.setGlobalHandler((error, isFatal) => {
330
+ emit(logger, capabilities, "err", {
331
+ msg: error instanceof Error ? error.message : String(error),
332
+ stk: error instanceof Error ? error.stack : void 0,
333
+ fatal: Boolean(isFatal),
334
+ source: "react-native-global-handler"
335
+ });
336
+ previous?.(error, isFatal);
337
+ });
338
+ cleanup.push(() => {
339
+ if (previous) errorUtils.setGlobalHandler?.(previous);
340
+ });
341
+ }
342
+ const addEventListener = globalObject.addEventListener;
343
+ const removeEventListener = globalObject.removeEventListener;
344
+ if (addEventListener && removeEventListener) {
345
+ const onUnhandledRejection = (event) => {
346
+ const reason = event.reason;
347
+ emit(logger, capabilities, "rej", {
348
+ msg: reason instanceof Error ? reason.message : String(reason),
349
+ stk: reason instanceof Error ? reason.stack : void 0,
350
+ source: "react-native-unhandled-rejection"
351
+ });
352
+ };
353
+ addEventListener.call(
354
+ globalObject,
355
+ "unhandledrejection",
356
+ onUnhandledRejection
357
+ );
358
+ cleanup.push(
359
+ () => removeEventListener.call(
360
+ globalObject,
361
+ "unhandledrejection",
362
+ onUnhandledRejection
363
+ )
364
+ );
365
+ }
366
+ return () => {
367
+ for (const stop of cleanup.reverse()) stop();
368
+ };
369
+ }
370
+ function startNetworkCollector(logger, capabilities, globalObject) {
371
+ const cleanup = [];
372
+ const originalFetch = globalObject.fetch;
373
+ if (typeof originalFetch === "function") {
374
+ globalObject.fetch = (async (input, init) => {
375
+ const startedAt = Date.now();
376
+ const method = init?.method?.toUpperCase() ?? "GET";
377
+ const url = extractUrl(input);
378
+ try {
379
+ const response = await originalFetch(input, init);
380
+ emit(logger, capabilities, "net", {
381
+ url,
382
+ method,
383
+ status: response.status,
384
+ ok: response.ok,
385
+ dur: Date.now() - startedAt,
386
+ source: "fetch"
387
+ });
388
+ return response;
389
+ } catch (error) {
390
+ emit(logger, capabilities, "net", {
391
+ url,
392
+ method,
393
+ error: error instanceof Error ? error.message : String(error),
394
+ dur: Date.now() - startedAt,
395
+ source: "fetch"
396
+ });
397
+ throw error;
398
+ }
399
+ });
400
+ cleanup.push(() => {
401
+ globalObject.fetch = originalFetch;
402
+ });
403
+ }
404
+ const Xhr = globalObject.XMLHttpRequest;
405
+ if (Xhr?.prototype) {
406
+ const originalOpen = Xhr.prototype.open;
407
+ const originalSend = Xhr.prototype.send;
408
+ if (typeof originalOpen === "function" && typeof originalSend === "function") {
409
+ Xhr.prototype.open = function open(method, url, ...rest) {
410
+ this.__crumbtrailNetwork = { method, url };
411
+ return originalOpen.call(this, method, url, ...rest);
412
+ };
413
+ Xhr.prototype.send = function send(body) {
414
+ const startedAt = Date.now();
415
+ const info = this.__crumbtrailNetwork;
416
+ const previous = this.onreadystatechange;
417
+ this.onreadystatechange = () => {
418
+ if (this.readyState === 4) {
419
+ emit(logger, capabilities, "net", {
420
+ url: info?.url,
421
+ method: info?.method?.toUpperCase(),
422
+ status: this.status,
423
+ dur: Date.now() - startedAt,
424
+ source: "xmlhttprequest"
425
+ });
426
+ }
427
+ previous?.call(this);
428
+ };
429
+ return originalSend.call(this, body);
430
+ };
431
+ cleanup.push(() => {
432
+ Xhr.prototype.open = originalOpen;
433
+ Xhr.prototype.send = originalSend;
434
+ });
435
+ }
436
+ }
437
+ return () => {
438
+ for (const stop of cleanup.reverse()) stop();
439
+ };
440
+ }
441
+ function startAppStateCollector(logger, capabilities, appState) {
442
+ if (!appState?.addEventListener) return () => {
443
+ };
444
+ const subscription = appState.addEventListener("change", (state) => {
445
+ emit(logger, capabilities, "app-lifecycle", { state, source: "AppState" });
446
+ });
447
+ emit(logger, capabilities, "app-lifecycle", {
448
+ state: appState.currentState,
449
+ source: "AppState",
450
+ kind: "initial"
451
+ });
452
+ return toCleanup(subscription);
453
+ }
454
+ function startEnvironmentCollector(logger, capabilities, reactNative) {
455
+ const window = reactNative?.Dimensions?.get?.("window");
456
+ emit(logger, capabilities, "env", {
457
+ kind: "snapshot",
458
+ platform: {
459
+ os: reactNative?.Platform?.OS,
460
+ version: reactNative?.Platform?.Version,
461
+ constants: reactNative?.Platform?.constants
462
+ },
463
+ viewport: window ? {
464
+ w: window.width,
465
+ h: window.height,
466
+ scale: window.scale,
467
+ fontScale: window.fontScale
468
+ } : void 0
469
+ });
470
+ }
471
+ function startNavigationCollector(logger, capabilities, navigation) {
472
+ if (!navigation?.addListener) return () => {
473
+ };
474
+ let previousRouteKey;
475
+ const emitCurrentRoute = () => {
476
+ const route = navigation.getCurrentRoute?.();
477
+ if (!route || route.key === previousRouteKey) return;
478
+ previousRouteKey = route.key;
479
+ emit(logger, capabilities, "navigation", {
480
+ name: route.name,
481
+ path: route.path,
482
+ key: route.key
483
+ });
484
+ };
485
+ const subscription = navigation.addListener("state", emitCurrentRoute);
486
+ emitCurrentRoute();
487
+ return toCleanup(subscription);
488
+ }
489
+ function resolveModule(packageName, resolver) {
490
+ if (!resolver) return void 0;
491
+ try {
492
+ return resolver(packageName);
493
+ } catch {
494
+ return void 0;
495
+ }
496
+ }
497
+ function extractUrl(input) {
498
+ if (typeof input === "string") return input;
499
+ if (input instanceof URL) return input.toString();
500
+ if (typeof Request !== "undefined" && input instanceof Request)
501
+ return input.url;
502
+ return String(input);
503
+ }
504
+ function safeStringify(value) {
505
+ if (typeof value === "string") return value;
506
+ try {
507
+ return JSON.stringify(value);
508
+ } catch {
509
+ return String(value);
510
+ }
511
+ }
512
+ function toCleanup(subscription) {
513
+ if (typeof subscription === "function") return subscription;
514
+ if (subscription?.remove) return () => subscription.remove?.();
515
+ return () => {
516
+ };
517
+ }
518
+
519
+ // src/logger.ts
520
+ var REACT_NATIVE_DEFAULT_CONFIG = {
521
+ network: false,
522
+ interactions: false,
523
+ keystrokes: false,
524
+ scroll: false,
525
+ visibility: false,
526
+ clipboard: false,
527
+ cookies: false,
528
+ storage: false,
529
+ performance: false,
530
+ video: false,
531
+ audio: false,
532
+ widget: false,
533
+ sessionPersistence: "session"
534
+ };
535
+ function createReactNativeCrumbtrail(options = {}) {
536
+ const capabilities = detectReactNativeCapabilities({
537
+ resolver: options.resolver
538
+ });
539
+ const userConfig = options.config;
540
+ const sessionStore = options.asyncStorage ? createReactNativeSessionStore(options.asyncStorage) : userConfig?.sessionStore;
541
+ const config = {
542
+ ...REACT_NATIVE_DEFAULT_CONFIG,
543
+ ...userConfig,
544
+ ...sessionStore ? { sessionStore } : {}
545
+ };
546
+ const logger = Crumbtrail.init(config);
547
+ if (options.reportCapabilities !== false) {
548
+ logger.addEvent({
549
+ type: "rn.capabilities",
550
+ data: {
551
+ bitset: capabilities.bitset,
552
+ capabilities: capabilities.capabilities,
553
+ modules: capabilities.modules
554
+ },
555
+ platform: "react-native",
556
+ sdk: { name: "crumbtrail-react-native" },
557
+ capabilities: capabilities.capabilities
558
+ });
559
+ }
560
+ const collectors = startReactNativeCollectors(logger, {
561
+ config: options.collectors,
562
+ capabilities,
563
+ resolver: options.resolver,
564
+ globalObject: options.globalObject,
565
+ reactNative: options.reactNative,
566
+ navigation: options.navigation,
567
+ errorUtils: options.errorUtils
568
+ });
569
+ wrapStopWithCollectorCleanup(logger, collectors);
570
+ return { logger, capabilities, collectors };
571
+ }
572
+ async function createReactNativeCrumbtrailAsync(options = {}) {
573
+ const sessionStore = options.asyncStorage ? createReactNativeSessionStore(options.asyncStorage) : void 0;
574
+ await sessionStore?.hydrate();
575
+ return createReactNativeCrumbtrail({
576
+ ...options,
577
+ asyncStorage: void 0,
578
+ config: {
579
+ ...options.config,
580
+ ...sessionStore ? { sessionStore } : {}
581
+ }
582
+ });
583
+ }
584
+ function wrapStopWithCollectorCleanup(logger, collectors) {
585
+ const stop = logger.stop.bind(logger);
586
+ let cleaned = false;
587
+ logger.stop = async () => {
588
+ if (!cleaned) {
589
+ cleaned = true;
590
+ collectors.cleanup();
591
+ }
592
+ return stop();
593
+ };
594
+ }
595
+
596
+ // src/provider.ts
597
+ import {
598
+ createContext,
599
+ createElement,
600
+ useContext,
601
+ useEffect,
602
+ useState
603
+ } from "react";
604
+ var CrumbtrailReactNativeContext = createContext(void 0);
605
+ function CrumbtrailReactNativeProvider(props) {
606
+ const [value, setValue] = useState(() => {
607
+ if (!props.logger && props.asyncStorage) return void 0;
608
+ return props.logger ? {
609
+ logger: props.logger,
610
+ capabilities: detectReactNativeCapabilities({
611
+ resolver: props.resolver
612
+ })
613
+ } : createReactNativeCrumbtrail({
614
+ config: props.config,
615
+ resolver: props.resolver,
616
+ reportCapabilities: props.reportCapabilities
617
+ });
618
+ });
619
+ useEffect(() => {
620
+ let active = true;
621
+ if (props.logger) {
622
+ setValue({
623
+ logger: props.logger,
624
+ capabilities: detectReactNativeCapabilities({
625
+ resolver: props.resolver
626
+ })
627
+ });
628
+ return;
629
+ }
630
+ if (!props.asyncStorage) return;
631
+ let hydratedLogger;
632
+ createReactNativeCrumbtrailAsync({
633
+ config: props.config,
634
+ asyncStorage: props.asyncStorage,
635
+ resolver: props.resolver,
636
+ reportCapabilities: props.reportCapabilities
637
+ }).then((result) => {
638
+ if (!active) {
639
+ void result.logger.stop();
640
+ return;
641
+ }
642
+ hydratedLogger = result.logger;
643
+ setValue(result);
644
+ }).catch(() => {
645
+ });
646
+ return () => {
647
+ active = false;
648
+ if (hydratedLogger) void hydratedLogger.stop();
649
+ };
650
+ }, [
651
+ props.asyncStorage,
652
+ props.config,
653
+ props.logger,
654
+ props.reportCapabilities,
655
+ props.resolver
656
+ ]);
657
+ if (!value) return props.fallback ?? null;
658
+ return createElement(
659
+ CrumbtrailReactNativeContext.Provider,
660
+ { value },
661
+ props.children
662
+ );
663
+ }
664
+ function useCrumbtrailReactNative() {
665
+ const value = useContext(CrumbtrailReactNativeContext);
666
+ if (!value) {
667
+ throw new Error(
668
+ "useCrumbtrailReactNative must be used within CrumbtrailReactNativeProvider"
669
+ );
670
+ }
671
+ return value;
672
+ }
673
+
674
+ // src/use-bug-state.ts
675
+ import { useEffect as useEffect2, useRef } from "react";
676
+ var REDACTED_VALUE = "[REDACTED]";
677
+ var SENSITIVE_NAME_RE = /(^|[^a-z0-9])(access[-_]?token|api[-_]?key|auth|authorization|bearer|card[-_]?number|client[-_]?secret|cookie|credential(s)?|creds|csrf|cvv|cvc|id[-_]?token|jsessionid|jwt|mfa|otp|pass[-_]?phrase|pass(code|word)?|passwd|password[-_]?confirmation|pin|private[-_]?key|pwd|refresh[-_]?token|secret|security[-_]?code|session|session[-_]?id|sid|ssn|token|verification[-_]?code|xsrf)([^a-z0-9]|$)/i;
678
+ var TOKEN_RE = /\b(?:Bearer|Token|Basic)\s+[A-Za-z0-9._~+/=-]{8,}\b|\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b|(?:sk|pk|rk|ghp|gho|ghu|ghs|glpat|xox[baprs])[-_][A-Za-z0-9_.=-]{12,}|\b[A-Fa-f0-9]{32,}\b|\b[A-Za-z0-9_-]{40,}\b/gi;
679
+ function isSensitiveName(name) {
680
+ const normalized = name.replace(/([a-z])([A-Z])/g, "$1_$2");
681
+ return SENSITIVE_NAME_RE.test(name) || SENSITIVE_NAME_RE.test(normalized);
682
+ }
683
+ function redactReactNativeSnapshot(value, keyName) {
684
+ if (keyName && isSensitiveName(keyName)) return REDACTED_VALUE;
685
+ if (typeof value === "string") {
686
+ if (/^\s*[^:=\n\r]{1,120}\s*[:=]/.test(value)) {
687
+ return value.replace(
688
+ /^(\s*[^:=\n\r]{1,120}\s*[:=]).*$/s,
689
+ (_match, key) => {
690
+ return isSensitiveName(key) ? `${key}${REDACTED_VALUE}` : value.replace(TOKEN_RE, REDACTED_VALUE);
691
+ }
692
+ );
693
+ }
694
+ return value.replace(TOKEN_RE, REDACTED_VALUE);
695
+ }
696
+ if (Array.isArray(value))
697
+ return value.map((entry) => redactReactNativeSnapshot(entry));
698
+ if (value && typeof value === "object") {
699
+ const output = {};
700
+ for (const [key, entry] of Object.entries(
701
+ value
702
+ )) {
703
+ output[key] = redactReactNativeSnapshot(entry, key);
704
+ }
705
+ return output;
706
+ }
707
+ return value;
708
+ }
709
+ function useBugState(logger, name, value, options = {}) {
710
+ const valueRef = useRef(value);
711
+ valueRef.current = value;
712
+ useEffect2(() => {
713
+ if (!logger || typeof logger.registerStateProvider !== "function") return;
714
+ return logger.registerStateProvider(
715
+ name,
716
+ () => options.captureRawState ? valueRef.current : redactReactNativeSnapshot(valueRef.current)
717
+ );
718
+ }, [logger, name, options.captureRawState]);
719
+ }
720
+
721
+ // src/error-boundary.ts
722
+ import { Component } from "react";
723
+ var CrumbtrailReactNativeErrorBoundary = class extends Component {
724
+ constructor(props) {
725
+ super(props);
726
+ this.state = { hasError: false };
727
+ }
728
+ static getDerivedStateFromError(_error) {
729
+ return { hasError: true };
730
+ }
731
+ componentDidCatch(error, errorInfo) {
732
+ this.props.logger.addEvent({
733
+ type: "err",
734
+ data: {
735
+ msg: redactReactNativeSnapshot(error.message),
736
+ stack: redactReactNativeSnapshot(error.stack),
737
+ componentStack: redactReactNativeSnapshot(errorInfo.componentStack),
738
+ source: "react-native-error-boundary"
739
+ }
740
+ });
741
+ }
742
+ resetError() {
743
+ this.setState({ hasError: false });
744
+ }
745
+ render() {
746
+ if (this.state.hasError) {
747
+ return this.props.fallback ?? null;
748
+ }
749
+ return this.props.children;
750
+ }
751
+ };
752
+ export {
753
+ CrumbtrailReactNativeErrorBoundary,
754
+ CrumbtrailReactNativeProvider,
755
+ REACT_NATIVE_CAPABILITY_BITS,
756
+ createReactNativeCrumbtrail,
757
+ createReactNativeCrumbtrailAsync,
758
+ createReactNativeReplayLite,
759
+ createReactNativeSessionStore,
760
+ createReactNativeTargetDescriptor,
761
+ detectReactNativeCapabilities,
762
+ redactReactNativeSnapshot,
763
+ startReactNativeCollectors,
764
+ useBugState,
765
+ useCrumbtrailReactNative
766
+ };