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