@grafana/faro-react-native 1.0.0 → 1.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/README.md +80 -3
- package/android/build.gradle +145 -0
- package/dist/cjs/config/getRNInstrumentations.js +3 -1
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/makeRNConfig.js +7 -4
- package/dist/cjs/config/makeRNConfig.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +3 -2
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/errors/stackTraceParser.js +98 -37
- package/dist/cjs/instrumentations/errors/stackTraceParser.js.map +1 -1
- package/dist/cjs/util/performanceObserverPolyfill.js +32 -3
- package/dist/cjs/util/performanceObserverPolyfill.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +3 -1
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/makeRNConfig.js +7 -4
- package/dist/esm/config/makeRNConfig.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +3 -2
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/errors/stackTraceParser.js +97 -37
- package/dist/esm/instrumentations/errors/stackTraceParser.js.map +1 -1
- package/dist/esm/util/performanceObserverPolyfill.js +32 -3
- package/dist/esm/util/performanceObserverPolyfill.js.map +1 -1
- package/dist/types/config/types.d.ts +5 -0
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/instrumentations/errors/index.d.ts +5 -0
- package/dist/types/instrumentations/errors/stackTraceParser.d.ts +19 -3
- package/dist/types/util/performanceObserverPolyfill.d.ts +5 -2
- package/ios/faro-upload-composed-source-map.sh +87 -0
- package/package.json +7 -10
- package/react-native.config.js +9 -1
- package/src/config/getRNInstrumentations.ts +5 -1
- package/src/config/makeRNConfig.test.ts +11 -0
- package/src/config/makeRNConfig.ts +7 -4
- package/src/config/types.ts +5 -0
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/initialize.test.ts +25 -0
- package/src/instrumentations/errors/index.ts +25 -8
- package/src/instrumentations/errors/stackTraceParser.test.ts +65 -0
- package/src/instrumentations/errors/stackTraceParser.ts +121 -37
- package/src/util/performanceObserverPolyfill.test.ts +48 -0
- package/src/util/performanceObserverPolyfill.ts +43 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ErrorUtils } from 'react-native';\n\nimport { BaseInstrumentation } from '@grafana/faro-core';\n\nimport { ErrorMechanism, primitiveUnhandledType } from './const';\nexport { ErrorMechanism } from './const';\nexport type { ErrorMechanismType } from './const';\nimport { enhanceErrorWithContext } from './stackTraceParser';\n\n// Access the global ErrorUtils\ndeclare const global: {\n ErrorUtils: ErrorUtils;\n addEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n removeEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n};\n\ntype ErrorHandlerCallback = (error: Error | unknown, isFatal?: boolean) => void;\n\nexport interface ErrorsInstrumentationOptions {\n /**\n * Patterns to ignore errors by message\n * @example [/network timeout/i, /cancelled/i]\n */\n ignoreErrors?: RegExp[];\n\n /**\n * Enable error deduplication (default: true)\n * Prevents sending the same error multiple times within a time window\n */\n enableDeduplication?: boolean;\n\n /**\n * Deduplication time window in milliseconds (default: 5000)\n * Errors with same message/stack within this window are considered duplicates\n */\n deduplicationWindow?: number;\n\n /**\n * Maximum number of errors to track for deduplication (default: 50)\n * Older errors are removed when this limit is reached\n */\n maxDeduplicationEntries?: number;\n}\n\ninterface ErrorFingerprint {\n message: string;\n stack: string;\n timestamp: number;\n}\n\n/**\n * Errors instrumentation for React Native\n *\n * Features:\n * - Captures unhandled errors and promise rejections using ErrorUtils\n * - Parses React Native stack traces (dev, release, Metro bundler formats)\n * - Adds platform context (OS, version, Hermes engine)\n * - Error deduplication to prevent duplicate reports\n * - Configurable error filtering\n *\n * @example\n * ```tsx\n * import { ErrorsInstrumentation, initializeFaro } from '@grafana/faro-react-native';\n *\n * await initializeFaro({\n * // ...config\n * instrumentations: [\n * new ErrorsInstrumentation({\n * ignoreErrors: [/network timeout/i, /cancelled/i],\n * enableDeduplication: true,\n * deduplicationWindow: 5000,\n * }),\n * ],\n * });\n * ```\n */\nexport class ErrorsInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native-errors';\n readonly version = '1.0.0';\n\n private originalErrorHandler?: ErrorHandlerCallback;\n private unhandledRejectionListener?: (event: PromiseRejectionEvent) => void;\n private options: Required<ErrorsInstrumentationOptions>;\n private errorFingerprints: ErrorFingerprint[] = [];\n\n constructor(options: ErrorsInstrumentationOptions = {}) {\n super();\n this.options = {\n ignoreErrors: options.ignoreErrors ?? [],\n enableDeduplication: options.enableDeduplication ?? true,\n deduplicationWindow: options.deduplicationWindow ?? 5000,\n maxDeduplicationEntries: options.maxDeduplicationEntries ?? 50,\n };\n }\n\n initialize(): void {\n // Capture unhandled JavaScript errors\n this.setupGlobalErrorHandler();\n\n // Capture unhandled promise rejections\n this.setupUnhandledRejectionHandler();\n }\n\n private setupGlobalErrorHandler(): void {\n // Store the original error handler\n this.originalErrorHandler = global.ErrorUtils.getGlobalHandler();\n\n // Set our custom handler\n global.ErrorUtils.setGlobalHandler((error: Error, isFatal?: boolean) => {\n try {\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(error, {\n mechanism: ErrorMechanism.UNCAUGHT,\n isFatal: String(isFatal ?? false),\n });\n\n // Push error to Faro with enhanced data (type from error.name, matches Web SDK)\n this.api.pushError(enhancedError, {\n type: enhancedError.name || 'Error',\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n } finally {\n // Always call the original handler to maintain normal error behavior\n if (this.originalErrorHandler) {\n this.originalErrorHandler(error, isFatal);\n }\n }\n });\n }\n\n private setupUnhandledRejectionHandler(): void {\n // React Native supports the standard unhandledrejection event\n this.unhandledRejectionListener = (event: PromiseRejectionEvent) => {\n try {\n const reason = event.reason;\n\n // Convert reason to an Error if it isn't one (matches Web SDK approach)\n const isPrimitiveRejection = !(reason instanceof Error);\n let error: Error;\n if (reason instanceof Error) {\n error = reason;\n } else {\n error = new Error(\n `Unhandled Promise Rejection: ${typeof reason === 'object' ? JSON.stringify(reason) : String(reason)}`\n );\n }\n\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(error, {\n mechanism: ErrorMechanism.UNHANDLED_REJECTION,\n });\n\n // Type: use actual error type for Error objects; 'UnhandledRejection' for primitives (Web SDK)\n const errorType = isPrimitiveRejection ? primitiveUnhandledType : enhancedError.name || 'Error';\n\n this.api.pushError(enhancedError, {\n type: errorType,\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n }\n };\n\n // Add the listener\n global.addEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n /**\n * Check if error should be ignored based on ignoreErrors patterns\n */\n private shouldIgnoreError(error: Error): boolean {\n if (!error || !error.message) {\n return false;\n }\n\n return this.options.ignoreErrors.some((pattern) => pattern.test(error.message));\n }\n\n /**\n * Check if error is a duplicate based on message and stack\n */\n private isDuplicateError(error: Error): boolean {\n const message = error.message || '';\n const stack = error.stack || '';\n const now = Date.now();\n\n // Clean up old entries first\n this.cleanupOldFingerprints(now);\n\n // Check if we've seen this error recently\n return this.errorFingerprints.some((fingerprint) => {\n return (\n fingerprint.message === message &&\n fingerprint.stack === stack &&\n now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n });\n }\n\n /**\n * Add error fingerprint for deduplication tracking\n */\n private addErrorFingerprint(error: Error): void {\n const message = error.message || '';\n const stack = error.stack || '';\n const timestamp = Date.now();\n\n this.errorFingerprints.push({ message, stack, timestamp });\n\n // Limit the number of tracked fingerprints\n if (this.errorFingerprints.length > this.options.maxDeduplicationEntries) {\n this.errorFingerprints.shift();\n }\n }\n\n /**\n * Remove error fingerprints older than the deduplication window\n */\n private cleanupOldFingerprints(now: number): void {\n this.errorFingerprints = this.errorFingerprints.filter(\n (fingerprint) => now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n }\n\n unpatch(): void {\n // Restore original error handler\n if (this.originalErrorHandler) {\n global.ErrorUtils.setGlobalHandler(this.originalErrorHandler);\n }\n\n // Remove unhandled rejection listener\n if (this.unhandledRejectionListener) {\n global.removeEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n // Clear deduplication tracking\n this.errorFingerprints = [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ErrorUtils } from 'react-native';\n\nimport { BaseInstrumentation } from '@grafana/faro-core';\n\nimport { ErrorMechanism, primitiveUnhandledType } from './const';\nexport { ErrorMechanism } from './const';\nexport type { ErrorMechanismType } from './const';\nimport { enhanceErrorWithContext } from './stackTraceParser';\n\n// Access the global ErrorUtils\ndeclare const global: {\n ErrorUtils: ErrorUtils;\n addEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n removeEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n};\n\ntype ErrorHandlerCallback = (error: Error | unknown, isFatal?: boolean) => void;\n\nexport interface ErrorsInstrumentationOptions {\n /**\n * Patterns to ignore errors by message\n * @example [/network timeout/i, /cancelled/i]\n */\n ignoreErrors?: RegExp[];\n\n /**\n * Enable error deduplication (default: true)\n * Prevents sending the same error multiple times within a time window\n */\n enableDeduplication?: boolean;\n\n /**\n * Deduplication time window in milliseconds (default: 5000)\n * Errors with same message/stack within this window are considered duplicates\n */\n deduplicationWindow?: number;\n\n /**\n * Maximum number of errors to track for deduplication (default: 50)\n * Older errors are removed when this limit is reached\n */\n maxDeduplicationEntries?: number;\n\n /**\n * Bundle filename for Hermes/minified release lines (`func@line:col`).\n * Must match the Metro plugin source map `file` field (default `bundle.js`).\n */\n releaseBundleFilename?: string;\n}\n\ninterface ErrorFingerprint {\n message: string;\n stack: string;\n timestamp: number;\n}\n\n/**\n * Errors instrumentation for React Native\n *\n * Features:\n * - Captures unhandled errors and promise rejections using ErrorUtils\n * - Parses React Native stack traces (dev, release, Metro bundler formats)\n * - Adds platform context (OS, version, Hermes engine)\n * - Error deduplication to prevent duplicate reports\n * - Configurable error filtering\n *\n * @example\n * ```tsx\n * import { ErrorsInstrumentation, initializeFaro } from '@grafana/faro-react-native';\n *\n * await initializeFaro({\n * // ...config\n * instrumentations: [\n * new ErrorsInstrumentation({\n * ignoreErrors: [/network timeout/i, /cancelled/i],\n * enableDeduplication: true,\n * deduplicationWindow: 5000,\n * }),\n * ],\n * });\n * ```\n */\nexport class ErrorsInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native-errors';\n readonly version = '1.0.0';\n\n private originalErrorHandler?: ErrorHandlerCallback;\n private unhandledRejectionListener?: (event: PromiseRejectionEvent) => void;\n private options: Required<Omit<ErrorsInstrumentationOptions, 'releaseBundleFilename'>> & {\n releaseBundleFilename?: string;\n };\n private errorFingerprints: ErrorFingerprint[] = [];\n\n constructor(options: ErrorsInstrumentationOptions = {}) {\n super();\n this.options = {\n ignoreErrors: options.ignoreErrors ?? [],\n enableDeduplication: options.enableDeduplication ?? true,\n deduplicationWindow: options.deduplicationWindow ?? 5000,\n maxDeduplicationEntries: options.maxDeduplicationEntries ?? 50,\n releaseBundleFilename: options.releaseBundleFilename,\n };\n }\n\n initialize(): void {\n // Capture unhandled JavaScript errors\n this.setupGlobalErrorHandler();\n\n // Capture unhandled promise rejections\n this.setupUnhandledRejectionHandler();\n }\n\n private setupGlobalErrorHandler(): void {\n // Store the original error handler\n this.originalErrorHandler = global.ErrorUtils.getGlobalHandler();\n\n // Set our custom handler\n global.ErrorUtils.setGlobalHandler((error: Error, isFatal?: boolean) => {\n try {\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNCAUGHT,\n isFatal: String(isFatal ?? false),\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Push error to Faro with enhanced data (type from error.name, matches Web SDK)\n this.api.pushError(enhancedError, {\n type: enhancedError.name || 'Error',\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n } finally {\n // Always call the original handler to maintain normal error behavior\n if (this.originalErrorHandler) {\n this.originalErrorHandler(error, isFatal);\n }\n }\n });\n }\n\n private setupUnhandledRejectionHandler(): void {\n // React Native supports the standard unhandledrejection event\n this.unhandledRejectionListener = (event: PromiseRejectionEvent) => {\n try {\n const reason = event.reason;\n\n // Convert reason to an Error if it isn't one (matches Web SDK approach)\n const isPrimitiveRejection = !(reason instanceof Error);\n let error: Error;\n if (reason instanceof Error) {\n error = reason;\n } else {\n error = new Error(\n `Unhandled Promise Rejection: ${typeof reason === 'object' ? JSON.stringify(reason) : String(reason)}`\n );\n }\n\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNHANDLED_REJECTION,\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Type: use actual error type for Error objects; 'UnhandledRejection' for primitives (Web SDK)\n const errorType = isPrimitiveRejection ? primitiveUnhandledType : enhancedError.name || 'Error';\n\n this.api.pushError(enhancedError, {\n type: errorType,\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n }\n };\n\n // Add the listener\n global.addEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n /**\n * Check if error should be ignored based on ignoreErrors patterns\n */\n private shouldIgnoreError(error: Error): boolean {\n if (!error || !error.message) {\n return false;\n }\n\n return this.options.ignoreErrors.some((pattern) => pattern.test(error.message));\n }\n\n /**\n * Check if error is a duplicate based on message and stack\n */\n private isDuplicateError(error: Error): boolean {\n const message = error.message || '';\n const stack = error.stack || '';\n const now = Date.now();\n\n // Clean up old entries first\n this.cleanupOldFingerprints(now);\n\n // Check if we've seen this error recently\n return this.errorFingerprints.some((fingerprint) => {\n return (\n fingerprint.message === message &&\n fingerprint.stack === stack &&\n now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n });\n }\n\n /**\n * Add error fingerprint for deduplication tracking\n */\n private addErrorFingerprint(error: Error): void {\n const message = error.message || '';\n const stack = error.stack || '';\n const timestamp = Date.now();\n\n this.errorFingerprints.push({ message, stack, timestamp });\n\n // Limit the number of tracked fingerprints\n if (this.errorFingerprints.length > this.options.maxDeduplicationEntries) {\n this.errorFingerprints.shift();\n }\n }\n\n /**\n * Remove error fingerprints older than the deduplication window\n */\n private cleanupOldFingerprints(now: number): void {\n this.errorFingerprints = this.errorFingerprints.filter(\n (fingerprint) => now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n }\n\n unpatch(): void {\n // Restore original error handler\n if (this.originalErrorHandler) {\n global.ErrorUtils.setGlobalHandler(this.originalErrorHandler);\n }\n\n // Remove unhandled rejection listener\n if (this.unhandledRejectionListener) {\n global.removeEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n // Clear deduplication tracking\n this.errorFingerprints = [];\n }\n}\n"]}
|
|
@@ -21,67 +21,113 @@ const REACT_NATIVE_RELEASE_REGEX = /^(.+?)@(\d+):(\d+)$/;
|
|
|
21
21
|
const REACT_NATIVE_ANONYMOUS_REGEX = /^\s*at\s+anonymous\s+\((.+?):(\d+):(\d+)\)$/;
|
|
22
22
|
const METRO_BUNDLER_REGEX = /^\s*at\s+(?:Object\.)?(.+?)\s+\((.+?):(\d+):(\d+)\)$/;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Hermes (Android release) often reports the bundle segment as `address at index.android.bundle`
|
|
25
|
+
* inside parentheses. The collector hashes `frame.filename` for uploaded maps; it must match the
|
|
26
|
+
* source map `file` field (e.g. `index.android.bundle`), not the verbose Hermes label.
|
|
25
27
|
*/
|
|
26
|
-
export function
|
|
28
|
+
export function normalizeHermesStackFilename(filename) {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
const trimmed = filename.trim();
|
|
31
|
+
const m = /^address\s+at\s+(.+)$/i.exec(trimmed);
|
|
32
|
+
return (_b = (_a = m === null || m === void 0 ? void 0 : m[1]) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : filename;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* iOS Simulator (and Android) stacks often carry full filesystem paths such as
|
|
36
|
+
* `…/Foo.app/main.jsbundle` while uploaded maps are keyed by the bundle basename (`main.jsbundle`)
|
|
37
|
+
* that matches Metro’s source map `file` field — same as ingest’s bundle lookup hashes.
|
|
38
|
+
*/
|
|
39
|
+
function collapseBundledArtifactPath(filename, releaseBasename) {
|
|
40
|
+
const key = releaseBasename.trim();
|
|
41
|
+
if (!key || filename === key) {
|
|
42
|
+
return filename;
|
|
43
|
+
}
|
|
44
|
+
const suffix = `/${key}`;
|
|
45
|
+
return filename.endsWith(suffix) ? key : filename;
|
|
46
|
+
}
|
|
47
|
+
const UNKNOWN_RELEASE_FILENAME = '<unknown>';
|
|
48
|
+
/**
|
|
49
|
+
* Parse a single stack trace line into a structured frame and record how it was parsed.
|
|
50
|
+
*/
|
|
51
|
+
function parseStackTraceLineDetailed(line) {
|
|
27
52
|
if (!line || typeof line !== 'string') {
|
|
28
|
-
return null;
|
|
53
|
+
return { frame: null, kind: 'unparsed' };
|
|
29
54
|
}
|
|
30
55
|
const trimmedLine = line.trim();
|
|
31
56
|
if (!trimmedLine) {
|
|
32
|
-
return null;
|
|
57
|
+
return { frame: null, kind: 'unparsed' };
|
|
33
58
|
}
|
|
34
59
|
// Try standard React Native format: at functionName (file.js:123:45)
|
|
35
60
|
let match = trimmedLine.match(REACT_NATIVE_STACK_REGEX);
|
|
36
61
|
if (match && match[2] && match[3] && match[4]) {
|
|
37
62
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
kind: 'react_native_paren',
|
|
64
|
+
frame: {
|
|
65
|
+
function: match[1] || '<anonymous>',
|
|
66
|
+
filename: normalizeHermesStackFilename(match[2]),
|
|
67
|
+
lineno: parseInt(match[3], 10),
|
|
68
|
+
colno: parseInt(match[4], 10),
|
|
69
|
+
},
|
|
42
70
|
};
|
|
43
71
|
}
|
|
44
72
|
// Try Metro bundler format: at Object.functionName (/path/to/file.js:123:456)
|
|
45
73
|
match = trimmedLine.match(METRO_BUNDLER_REGEX);
|
|
46
74
|
if (match && match[2] && match[3] && match[4]) {
|
|
47
75
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
kind: 'metro_paren',
|
|
77
|
+
frame: {
|
|
78
|
+
function: match[1] || '<anonymous>',
|
|
79
|
+
filename: normalizeHermesStackFilename(match[2]),
|
|
80
|
+
lineno: parseInt(match[3], 10),
|
|
81
|
+
colno: parseInt(match[4], 10),
|
|
82
|
+
},
|
|
52
83
|
};
|
|
53
84
|
}
|
|
54
85
|
// Try anonymous format: at anonymous (file.js:123:45)
|
|
55
86
|
match = trimmedLine.match(REACT_NATIVE_ANONYMOUS_REGEX);
|
|
56
87
|
if (match && match[1] && match[2] && match[3]) {
|
|
57
88
|
return {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
kind: 'anonymous_paren',
|
|
90
|
+
frame: {
|
|
91
|
+
function: '<anonymous>',
|
|
92
|
+
filename: normalizeHermesStackFilename(match[1]),
|
|
93
|
+
lineno: parseInt(match[2], 10),
|
|
94
|
+
colno: parseInt(match[3], 10),
|
|
95
|
+
},
|
|
62
96
|
};
|
|
63
97
|
}
|
|
64
98
|
// Try native format: at functionName (native)
|
|
65
99
|
match = trimmedLine.match(REACT_NATIVE_NATIVE_REGEX);
|
|
66
100
|
if (match) {
|
|
67
101
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
102
|
+
kind: 'native',
|
|
103
|
+
frame: {
|
|
104
|
+
function: match[1] || '<native>',
|
|
105
|
+
filename: '<native>',
|
|
106
|
+
isNative: true,
|
|
107
|
+
},
|
|
71
108
|
};
|
|
72
109
|
}
|
|
73
110
|
// Try release/minified format: functionName@123:456
|
|
74
111
|
match = trimmedLine.match(REACT_NATIVE_RELEASE_REGEX);
|
|
75
112
|
if (match && match[2] && match[3]) {
|
|
76
113
|
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
114
|
+
kind: 'release_func_at',
|
|
115
|
+
frame: {
|
|
116
|
+
function: match[1] || '<anonymous>',
|
|
117
|
+
filename: UNKNOWN_RELEASE_FILENAME,
|
|
118
|
+
lineno: parseInt(match[2], 10),
|
|
119
|
+
colno: parseInt(match[3], 10),
|
|
120
|
+
releaseLine: true,
|
|
121
|
+
},
|
|
81
122
|
};
|
|
82
123
|
}
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
return { frame: null, kind: 'unparsed' };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse a single stack trace line into a structured frame
|
|
128
|
+
*/
|
|
129
|
+
export function parseStackTraceLine(line) {
|
|
130
|
+
return parseStackTraceLineDetailed(line).frame;
|
|
85
131
|
}
|
|
86
132
|
/**
|
|
87
133
|
* Parse a full stack trace string into an array of structured frames
|
|
@@ -93,7 +139,7 @@ export function parseStackTrace(stackTrace) {
|
|
|
93
139
|
const lines = stackTrace.split('\n');
|
|
94
140
|
const frames = [];
|
|
95
141
|
for (const line of lines) {
|
|
96
|
-
const frame =
|
|
142
|
+
const { frame } = parseStackTraceLineDetailed(line);
|
|
97
143
|
if (frame) {
|
|
98
144
|
frames.push(frame);
|
|
99
145
|
}
|
|
@@ -103,24 +149,38 @@ export function parseStackTrace(stackTrace) {
|
|
|
103
149
|
/**
|
|
104
150
|
* Convert parsed stack frames to Faro's StackFrame format
|
|
105
151
|
*/
|
|
106
|
-
export function toFaroStackFrames(parsedFrames) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
152
|
+
export function toFaroStackFrames(parsedFrames, options) {
|
|
153
|
+
var _a;
|
|
154
|
+
const releaseName = (_a = options === null || options === void 0 ? void 0 : options.releaseBundleFilename) !== null && _a !== void 0 ? _a : 'bundle.js';
|
|
155
|
+
const keyedBasename = typeof (options === null || options === void 0 ? void 0 : options.releaseBundleFilename) === 'string' && options.releaseBundleFilename.trim() !== ''
|
|
156
|
+
? options.releaseBundleFilename.trim()
|
|
157
|
+
: null;
|
|
158
|
+
return parsedFrames.map((frame, _index) => {
|
|
159
|
+
let filename = normalizeHermesStackFilename(frame.filename || '<unknown>');
|
|
160
|
+
if (frame.releaseLine) {
|
|
161
|
+
filename = releaseName;
|
|
162
|
+
}
|
|
163
|
+
else if (keyedBasename) {
|
|
164
|
+
filename = collapseBundledArtifactPath(filename, keyedBasename);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
filename,
|
|
168
|
+
function: frame.function || '<anonymous>',
|
|
169
|
+
lineno: frame.lineno,
|
|
170
|
+
colno: frame.colno,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
113
173
|
}
|
|
114
174
|
/**
|
|
115
175
|
* Extract and parse stack frames from an Error object
|
|
116
176
|
*/
|
|
117
|
-
export function getStackFramesFromError(error) {
|
|
177
|
+
export function getStackFramesFromError(error, options) {
|
|
118
178
|
if (!error || !error.stack) {
|
|
119
179
|
return [];
|
|
120
180
|
}
|
|
121
181
|
try {
|
|
122
182
|
const parsedFrames = parseStackTrace(error.stack);
|
|
123
|
-
return toFaroStackFrames(parsedFrames);
|
|
183
|
+
return toFaroStackFrames(parsedFrames, options);
|
|
124
184
|
}
|
|
125
185
|
catch (_e) {
|
|
126
186
|
// If parsing fails, return empty array
|
|
@@ -141,8 +201,8 @@ export function getPlatformErrorContext() {
|
|
|
141
201
|
/**
|
|
142
202
|
* Enhance error with React Native specific information
|
|
143
203
|
*/
|
|
144
|
-
export function enhanceErrorWithContext(error, additionalContext) {
|
|
145
|
-
const stackFrames = getStackFramesFromError(error);
|
|
204
|
+
export function enhanceErrorWithContext(error, additionalContext, stackParseOptions) {
|
|
205
|
+
const stackFrames = getStackFramesFromError(error, stackParseOptions);
|
|
146
206
|
const platformContext = getPlatformErrorContext();
|
|
147
207
|
return {
|
|
148
208
|
error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stackTraceParser.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/stackTraceParser.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\n\nimport type { ExceptionStackFrame as StackFrame } from '@grafana/faro-core';\n\n// Declare global Hermes runtime\ndeclare const HermesInternal: object | undefined;\n\n/**\n * Parse React Native stack traces into structured stack frames\n *\n * React Native stack traces have different formats depending on platform and environment:\n *\n * iOS/Android (Dev):\n * at functionName (file.js:123:45)\n * at anonymous (native)\n *\n * iOS/Android (Release):\n * functionName@123:456\n * @[native code]\n *\n * Metro bundler:\n * at Object.functionName (/path/to/file.js:123:456)\n */\n\nconst REACT_NATIVE_STACK_REGEX = /^\\s*at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\nconst REACT_NATIVE_NATIVE_REGEX = /^\\s*at\\s+(.+?)\\s+\\(native\\)$/;\nconst REACT_NATIVE_RELEASE_REGEX = /^(.+?)@(\\d+):(\\d+)$/;\nconst REACT_NATIVE_ANONYMOUS_REGEX = /^\\s*at\\s+anonymous\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\nconst METRO_BUNDLER_REGEX = /^\\s*at\\s+(?:Object\\.)?(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\n\nexport interface ParsedStackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n isNative?: boolean;\n}\n\n/**\n * Parse a single stack trace line into a structured frame\n */\nexport function parseStackTraceLine(line: string): ParsedStackFrame | null {\n if (!line || typeof line !== 'string') {\n return null;\n }\n\n const trimmedLine = line.trim();\n if (!trimmedLine) {\n return null;\n }\n\n // Try standard React Native format: at functionName (file.js:123:45)\n let match = trimmedLine.match(REACT_NATIVE_STACK_REGEX);\n if (match && match[2] && match[3] && match[4]) {\n return {\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n };\n }\n\n // Try Metro bundler format: at Object.functionName (/path/to/file.js:123:456)\n match = trimmedLine.match(METRO_BUNDLER_REGEX);\n if (match && match[2] && match[3] && match[4]) {\n return {\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n };\n }\n\n // Try anonymous format: at anonymous (file.js:123:45)\n match = trimmedLine.match(REACT_NATIVE_ANONYMOUS_REGEX);\n if (match && match[1] && match[2] && match[3]) {\n return {\n function: '<anonymous>',\n filename: match[1],\n lineno: parseInt(match[2], 10),\n colno: parseInt(match[3], 10),\n };\n }\n\n // Try native format: at functionName (native)\n match = trimmedLine.match(REACT_NATIVE_NATIVE_REGEX);\n if (match) {\n return {\n function: match[1] || '<native>',\n filename: '<native>',\n isNative: true,\n };\n }\n\n // Try release/minified format: functionName@123:456\n match = trimmedLine.match(REACT_NATIVE_RELEASE_REGEX);\n if (match && match[2] && match[3]) {\n return {\n function: match[1] || '<anonymous>',\n filename: '<unknown>',\n lineno: parseInt(match[2], 10),\n colno: parseInt(match[3], 10),\n };\n }\n\n // Couldn't parse this line\n return null;\n}\n\n/**\n * Parse a full stack trace string into an array of structured frames\n */\nexport function parseStackTrace(stackTrace: string): ParsedStackFrame[] {\n if (!stackTrace || typeof stackTrace !== 'string') {\n return [];\n }\n\n const lines = stackTrace.split('\\n');\n const frames: ParsedStackFrame[] = [];\n\n for (const line of lines) {\n const frame = parseStackTraceLine(line);\n if (frame) {\n frames.push(frame);\n }\n }\n\n return frames;\n}\n\n/**\n * Convert parsed stack frames to Faro's StackFrame format\n */\nexport function toFaroStackFrames(parsedFrames: ParsedStackFrame[]): StackFrame[] {\n return parsedFrames.map((frame, _index) => ({\n filename: frame.filename || '<unknown>',\n function: frame.function || '<anonymous>',\n lineno: frame.lineno,\n colno: frame.colno,\n }));\n}\n\n/**\n * Extract and parse stack frames from an Error object\n */\nexport function getStackFramesFromError(error: Error): StackFrame[] {\n if (!error || !error.stack) {\n return [];\n }\n\n try {\n const parsedFrames = parseStackTrace(error.stack);\n return toFaroStackFrames(parsedFrames);\n } catch (_e) {\n // If parsing fails, return empty array\n return [];\n }\n}\n\n/**\n * Get platform-specific error context\n */\nexport function getPlatformErrorContext(): Record<string, string> {\n return {\n platform: Platform.OS,\n platformVersion: Platform.Version?.toString() || 'unknown',\n isHermes: typeof HermesInternal !== 'undefined' ? 'true' : 'false',\n };\n}\n\n/**\n * Enhance error with React Native specific information\n */\nexport function enhanceErrorWithContext(\n error: Error,\n additionalContext?: Record<string, string>\n): {\n error: Error;\n stackFrames: StackFrame[];\n context: Record<string, string>;\n} {\n const stackFrames = getStackFramesFromError(error);\n const platformContext = getPlatformErrorContext();\n\n return {\n error,\n stackFrames,\n context: {\n ...platformContext,\n ...additionalContext,\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stackTraceParser.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/stackTraceParser.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\n\nimport type { ExceptionStackFrame as StackFrame } from '@grafana/faro-core';\n\n// Declare global Hermes runtime\ndeclare const HermesInternal: object | undefined;\n\n/**\n * Parse React Native stack traces into structured stack frames\n *\n * React Native stack traces have different formats depending on platform and environment:\n *\n * iOS/Android (Dev):\n * at functionName (file.js:123:45)\n * at anonymous (native)\n *\n * iOS/Android (Release):\n * functionName@123:456\n * @[native code]\n *\n * Metro bundler:\n * at Object.functionName (/path/to/file.js:123:456)\n */\n\nconst REACT_NATIVE_STACK_REGEX = /^\\s*at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\nconst REACT_NATIVE_NATIVE_REGEX = /^\\s*at\\s+(.+?)\\s+\\(native\\)$/;\nconst REACT_NATIVE_RELEASE_REGEX = /^(.+?)@(\\d+):(\\d+)$/;\nconst REACT_NATIVE_ANONYMOUS_REGEX = /^\\s*at\\s+anonymous\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\nconst METRO_BUNDLER_REGEX = /^\\s*at\\s+(?:Object\\.)?(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/;\n\n/**\n * Hermes (Android release) often reports the bundle segment as `address at index.android.bundle`\n * inside parentheses. The collector hashes `frame.filename` for uploaded maps; it must match the\n * source map `file` field (e.g. `index.android.bundle`), not the verbose Hermes label.\n */\nexport function normalizeHermesStackFilename(filename: string): string {\n const trimmed = filename.trim();\n const m = /^address\\s+at\\s+(.+)$/i.exec(trimmed);\n return m?.[1]?.trim() ?? filename;\n}\n\n/**\n * iOS Simulator (and Android) stacks often carry full filesystem paths such as\n * `…/Foo.app/main.jsbundle` while uploaded maps are keyed by the bundle basename (`main.jsbundle`)\n * that matches Metro’s source map `file` field — same as ingest’s bundle lookup hashes.\n */\nfunction collapseBundledArtifactPath(filename: string, releaseBasename: string): string {\n const key = releaseBasename.trim();\n if (!key || filename === key) {\n return filename;\n }\n const suffix = `/${key}`;\n return filename.endsWith(suffix) ? key : filename;\n}\n\nexport interface ParsedStackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n isNative?: boolean;\n /** Set when the line matched Hermes/minified `func@line:col` (no path). */\n releaseLine?: boolean;\n}\n\n/** Which parser branch handled a stack line (internal). */\ntype ParsedStackLineKind =\n | 'react_native_paren'\n | 'metro_paren'\n | 'anonymous_paren'\n | 'native'\n | 'release_func_at'\n | 'unparsed';\n\nconst UNKNOWN_RELEASE_FILENAME = '<unknown>';\n\nexport interface StackFrameParseOptions {\n /**\n * Hermes / minified RN release lines use `func@line:col` with no file path. Those frames are tagged\n * with `releaseLine` and use this as `filename` (default `bundle.js`). Match the Metro plugin’s\n * source map `file` field (or `sourceMapFile` option there).\n */\n releaseBundleFilename?: string;\n}\n\n/**\n * Parse a single stack trace line into a structured frame and record how it was parsed.\n */\nfunction parseStackTraceLineDetailed(line: string): { frame: ParsedStackFrame | null; kind: ParsedStackLineKind } {\n if (!line || typeof line !== 'string') {\n return { frame: null, kind: 'unparsed' };\n }\n\n const trimmedLine = line.trim();\n if (!trimmedLine) {\n return { frame: null, kind: 'unparsed' };\n }\n\n // Try standard React Native format: at functionName (file.js:123:45)\n let match = trimmedLine.match(REACT_NATIVE_STACK_REGEX);\n if (match && match[2] && match[3] && match[4]) {\n return {\n kind: 'react_native_paren',\n frame: {\n function: match[1] || '<anonymous>',\n filename: normalizeHermesStackFilename(match[2]),\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n },\n };\n }\n\n // Try Metro bundler format: at Object.functionName (/path/to/file.js:123:456)\n match = trimmedLine.match(METRO_BUNDLER_REGEX);\n if (match && match[2] && match[3] && match[4]) {\n return {\n kind: 'metro_paren',\n frame: {\n function: match[1] || '<anonymous>',\n filename: normalizeHermesStackFilename(match[2]),\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n },\n };\n }\n\n // Try anonymous format: at anonymous (file.js:123:45)\n match = trimmedLine.match(REACT_NATIVE_ANONYMOUS_REGEX);\n if (match && match[1] && match[2] && match[3]) {\n return {\n kind: 'anonymous_paren',\n frame: {\n function: '<anonymous>',\n filename: normalizeHermesStackFilename(match[1]),\n lineno: parseInt(match[2], 10),\n colno: parseInt(match[3], 10),\n },\n };\n }\n\n // Try native format: at functionName (native)\n match = trimmedLine.match(REACT_NATIVE_NATIVE_REGEX);\n if (match) {\n return {\n kind: 'native',\n frame: {\n function: match[1] || '<native>',\n filename: '<native>',\n isNative: true,\n },\n };\n }\n\n // Try release/minified format: functionName@123:456\n match = trimmedLine.match(REACT_NATIVE_RELEASE_REGEX);\n if (match && match[2] && match[3]) {\n return {\n kind: 'release_func_at',\n frame: {\n function: match[1] || '<anonymous>',\n filename: UNKNOWN_RELEASE_FILENAME,\n lineno: parseInt(match[2], 10),\n colno: parseInt(match[3], 10),\n releaseLine: true,\n },\n };\n }\n\n return { frame: null, kind: 'unparsed' };\n}\n\n/**\n * Parse a single stack trace line into a structured frame\n */\nexport function parseStackTraceLine(line: string): ParsedStackFrame | null {\n return parseStackTraceLineDetailed(line).frame;\n}\n\n/**\n * Parse a full stack trace string into an array of structured frames\n */\nexport function parseStackTrace(stackTrace: string): ParsedStackFrame[] {\n if (!stackTrace || typeof stackTrace !== 'string') {\n return [];\n }\n\n const lines = stackTrace.split('\\n');\n const frames: ParsedStackFrame[] = [];\n\n for (const line of lines) {\n const { frame } = parseStackTraceLineDetailed(line);\n if (frame) {\n frames.push(frame);\n }\n }\n\n return frames;\n}\n\n/**\n * Convert parsed stack frames to Faro's StackFrame format\n */\nexport function toFaroStackFrames(parsedFrames: ParsedStackFrame[], options?: StackFrameParseOptions): StackFrame[] {\n const releaseName = options?.releaseBundleFilename ?? 'bundle.js';\n const keyedBasename =\n typeof options?.releaseBundleFilename === 'string' && options.releaseBundleFilename.trim() !== ''\n ? options.releaseBundleFilename.trim()\n : null;\n\n return parsedFrames.map((frame, _index) => {\n let filename = normalizeHermesStackFilename(frame.filename || '<unknown>');\n if (frame.releaseLine) {\n filename = releaseName;\n } else if (keyedBasename) {\n filename = collapseBundledArtifactPath(filename, keyedBasename);\n }\n return {\n filename,\n function: frame.function || '<anonymous>',\n lineno: frame.lineno,\n colno: frame.colno,\n };\n });\n}\n\n/**\n * Extract and parse stack frames from an Error object\n */\nexport function getStackFramesFromError(error: Error, options?: StackFrameParseOptions): StackFrame[] {\n if (!error || !error.stack) {\n return [];\n }\n\n try {\n const parsedFrames = parseStackTrace(error.stack);\n return toFaroStackFrames(parsedFrames, options);\n } catch (_e) {\n // If parsing fails, return empty array\n return [];\n }\n}\n\n/**\n * Get platform-specific error context\n */\nexport function getPlatformErrorContext(): Record<string, string> {\n return {\n platform: Platform.OS,\n platformVersion: Platform.Version?.toString() || 'unknown',\n isHermes: typeof HermesInternal !== 'undefined' ? 'true' : 'false',\n };\n}\n\n/**\n * Enhance error with React Native specific information\n */\nexport function enhanceErrorWithContext(\n error: Error,\n additionalContext?: Record<string, string>,\n stackParseOptions?: StackFrameParseOptions\n): {\n error: Error;\n stackFrames: StackFrame[];\n context: Record<string, string>;\n} {\n const stackFrames = getStackFramesFromError(error, stackParseOptions);\n const platformContext = getPlatformErrorContext();\n\n return {\n error,\n stackFrames,\n context: {\n ...platformContext,\n ...additionalContext,\n },\n };\n}\n"]}
|
|
@@ -17,6 +17,8 @@ const globalObj = (typeof globalThis !== 'undefined' && globalThis) ||
|
|
|
17
17
|
(typeof global !== 'undefined' && global) ||
|
|
18
18
|
(typeof window !== 'undefined' && window) ||
|
|
19
19
|
{};
|
|
20
|
+
const RESOURCE_ENTRY_TYPE = 'resource';
|
|
21
|
+
const FARO_RESOURCE_TIMING_PATCH = '__faroResourceTimingPatch';
|
|
20
22
|
/**
|
|
21
23
|
* No-op PerformanceObserver that matches the Web API interface.
|
|
22
24
|
* Prevents crashes when React Native's native implementation throws.
|
|
@@ -37,14 +39,18 @@ class NoopPerformanceObserver {
|
|
|
37
39
|
}
|
|
38
40
|
NoopPerformanceObserver.supportedEntryTypes = [];
|
|
39
41
|
/**
|
|
40
|
-
* Apply
|
|
41
|
-
*
|
|
42
|
+
* Apply React Native performance API compatibility patches.
|
|
43
|
+
*
|
|
44
|
+
* Resource Timing lookup is patched on all platforms because OTel web
|
|
45
|
+
* instrumentation can probe it. The PerformanceObserver replacement is
|
|
46
|
+
* iOS-only to avoid the native bad_variant_access crash.
|
|
42
47
|
*/
|
|
43
48
|
export function applyPerformanceObserverPolyfill() {
|
|
49
|
+
const record = globalObj;
|
|
50
|
+
patchUnsupportedResourceTimingLookup(record);
|
|
44
51
|
if (Platform.OS !== 'ios') {
|
|
45
52
|
return;
|
|
46
53
|
}
|
|
47
|
-
const record = globalObj;
|
|
48
54
|
const existing = record['PerformanceObserver'];
|
|
49
55
|
if (existing && existing.name === 'NoopPerformanceObserver') {
|
|
50
56
|
return; // Already applied
|
|
@@ -56,4 +62,27 @@ export function applyPerformanceObserverPolyfill() {
|
|
|
56
62
|
// Ignore if global is frozen (e.g. in some test environments)
|
|
57
63
|
}
|
|
58
64
|
}
|
|
65
|
+
function patchUnsupportedResourceTimingLookup(record) {
|
|
66
|
+
const performance = record['performance'];
|
|
67
|
+
if (!(performance === null || performance === void 0 ? void 0 : performance.getEntriesByType) || performance.getEntriesByType[FARO_RESOURCE_TIMING_PATCH]) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const originalGetEntriesByType = performance.getEntriesByType.bind(performance);
|
|
71
|
+
const patchedGetEntriesByType = ((entryType) => {
|
|
72
|
+
if (entryType === RESOURCE_ENTRY_TYPE) {
|
|
73
|
+
// OTel web fetch instrumentation probes browser Resource Timing APIs.
|
|
74
|
+
// React Native does not expose resource entries, and calling through emits
|
|
75
|
+
// "Deprecated API for given entry type." warnings in development.
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return originalGetEntriesByType(entryType);
|
|
79
|
+
});
|
|
80
|
+
patchedGetEntriesByType[FARO_RESOURCE_TIMING_PATCH] = true;
|
|
81
|
+
try {
|
|
82
|
+
performance.getEntriesByType = patchedGetEntriesByType;
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
// Ignore if performance is read-only in a test or host environment.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
59
88
|
//# sourceMappingURL=performanceObserverPolyfill.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"performanceObserverPolyfill.js","sourceRoot":"","sources":["../../../src/util/performanceObserverPolyfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,SAAS,GACb,CAAC,OAAO,UAAU,KAAK,WAAW,IAAI,UAAU,CAAC;IACjD,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC;IACzC,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC;IACzC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"performanceObserverPolyfill.js","sourceRoot":"","sources":["../../../src/util/performanceObserverPolyfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,SAAS,GACb,CAAC,OAAO,UAAU,KAAK,WAAW,IAAI,UAAU,CAAC;IACjD,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC;IACzC,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC;IACzC,EAAE,CAAC;AAEL,MAAM,mBAAmB,GAAG,UAAU,CAAC;AACvC,MAAM,0BAA0B,GAAG,2BAA2B,CAAC;AAqB/D;;;GAGG;AACH,MAAM,uBAAuB;IAC3B,YACE,SAIS;QAET,+DAA+D;IACjE,CAAC;IAED,OAAO,CAAC,QAAkC;QACxC,QAAQ;IACV,CAAC;IAED,UAAU;QACR,QAAQ;IACV,CAAC;IAED,WAAW;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;;AAEe,2CAAmB,GAAa,EAAE,CAAC;AAGrD;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC;IAC9C,MAAM,MAAM,GAAG,SAAoC,CAAC;IACpD,oCAAoC,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAC/C,IAAI,QAAQ,IAAK,QAAyC,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;QAC9F,OAAO,CAAC,kBAAkB;IAC5B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,GAAG,uBAAuB,CAAC;IAC1D,CAAC;IAAC,WAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC;AAED,SAAS,oCAAoC,CAAC,MAA+B;IAC3E,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAI3B,CAAC;IAEd,IAAI,CAAC,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,gBAAgB,CAAA,IAAI,WAAW,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC/F,OAAO;IACT,CAAC;IAED,MAAM,wBAAwB,GAAG,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChF,MAAM,uBAAuB,GAAG,CAAC,CAAC,SAAiB,EAAsB,EAAE;QACzE,IAAI,SAAS,KAAK,mBAAmB,EAAE,CAAC;YACtC,sEAAsE;YACtE,2EAA2E;YAC3E,kEAAkE;YAClE,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,wBAAwB,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC,CAAwC,CAAC;IAE1C,uBAAuB,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAC;IAE3D,IAAI,CAAC;QACH,WAAW,CAAC,gBAAgB,GAAG,uBAAuB,CAAC;IACzD,CAAC;IAAC,WAAM,CAAC;QACP,oEAAoE;IACtE,CAAC;AACH,CAAC","sourcesContent":["/**\n * PerformanceObserver polyfill for React Native iOS\n *\n * React Native 0.84's native PerformanceObserver has a bug on iOS that throws\n * `bad_variant_access` when the observer callback runs and calls getEntries().\n * This happens in NativePerformance.createObserver when the native bridge\n * returns data that causes a C++ variant access error.\n *\n * OpenTelemetry's FetchInstrumentation (and other libs) may use PerformanceObserver\n * for resource timing. We provide a no-op polyfill on React Native iOS to prevent\n * the crash while preserving refresh rate vitals and other functionality.\n *\n * @see https://github.com/facebook/react-native/issues (PerformanceObserver iOS)\n */\n\nimport { Platform } from 'react-native';\n\nconst globalObj =\n (typeof globalThis !== 'undefined' && globalThis) ||\n (typeof global !== 'undefined' && global) ||\n (typeof window !== 'undefined' && window) ||\n {};\n\nconst RESOURCE_ENTRY_TYPE = 'resource';\nconst FARO_RESOURCE_TIMING_PATCH = '__faroResourceTimingPatch';\n\ninterface PerformanceEntry {\n readonly duration: number;\n readonly entryType: string;\n readonly name: string;\n readonly startTime: number;\n}\n\ninterface PerformanceObserverEntryList {\n getEntries(): PerformanceEntry[];\n getEntriesByName(name: string, type?: string): PerformanceEntry[];\n getEntriesByType(type: string): PerformanceEntry[];\n}\n\ninterface PerformanceObserverInit {\n entryTypes?: string[];\n type?: string;\n buffered?: boolean;\n}\n\n/**\n * No-op PerformanceObserver that matches the Web API interface.\n * Prevents crashes when React Native's native implementation throws.\n */\nclass NoopPerformanceObserver {\n constructor(\n _callback: (\n list: PerformanceObserverEntryList,\n observer: NoopPerformanceObserver,\n options?: PerformanceObserverInit\n ) => void\n ) {\n // Callback is never invoked - avoids triggering the native bug\n }\n\n observe(_options?: PerformanceObserverInit): void {\n // No-op\n }\n\n disconnect(): void {\n // No-op\n }\n\n takeRecords(): PerformanceEntry[] {\n return [];\n }\n\n static readonly supportedEntryTypes: string[] = [];\n}\n\n/**\n * Apply React Native performance API compatibility patches.\n *\n * Resource Timing lookup is patched on all platforms because OTel web\n * instrumentation can probe it. The PerformanceObserver replacement is\n * iOS-only to avoid the native bad_variant_access crash.\n */\nexport function applyPerformanceObserverPolyfill(): void {\n const record = globalObj as Record<string, unknown>;\n patchUnsupportedResourceTimingLookup(record);\n\n if (Platform.OS !== 'ios') {\n return;\n }\n\n const existing = record['PerformanceObserver'];\n if (existing && (existing as unknown as { name?: string }).name === 'NoopPerformanceObserver') {\n return; // Already applied\n }\n\n try {\n record['PerformanceObserver'] = NoopPerformanceObserver;\n } catch {\n // Ignore if global is frozen (e.g. in some test environments)\n }\n}\n\nfunction patchUnsupportedResourceTimingLookup(record: Record<string, unknown>): void {\n const performance = record['performance'] as\n | {\n getEntriesByType?: ((entryType: string) => PerformanceEntry[]) & Record<string, boolean>;\n }\n | undefined;\n\n if (!performance?.getEntriesByType || performance.getEntriesByType[FARO_RESOURCE_TIMING_PATCH]) {\n return;\n }\n\n const originalGetEntriesByType = performance.getEntriesByType.bind(performance);\n const patchedGetEntriesByType = ((entryType: string): PerformanceEntry[] => {\n if (entryType === RESOURCE_ENTRY_TYPE) {\n // OTel web fetch instrumentation probes browser Resource Timing APIs.\n // React Native does not expose resource entries, and calling through emits\n // \"Deprecated API for given entry type.\" warnings in development.\n return [];\n }\n\n return originalGetEntriesByType(entryType);\n }) as typeof performance.getEntriesByType;\n\n patchedGetEntriesByType[FARO_RESOURCE_TIMING_PATCH] = true;\n\n try {\n performance.getEntriesByType = patchedGetEntriesByType;\n } catch {\n // Ignore if performance is read-only in a test or host environment.\n }\n}\n"]}
|
|
@@ -62,6 +62,11 @@ type ReactNativeConfigOverrides = {
|
|
|
62
62
|
consoleCaptureOptions?: Config['consoleInstrumentation'];
|
|
63
63
|
enableUserActions?: boolean;
|
|
64
64
|
userActionsOptions?: Config['userActionsInstrumentation'];
|
|
65
|
+
/**
|
|
66
|
+
* Hermes / minified release stack lines omit a file path. This must match the Metro plugin
|
|
67
|
+
* source map top-level `file` (default `bundle.js`, or your `sourceMapFile` option there).
|
|
68
|
+
*/
|
|
69
|
+
releaseBundleFilename?: string;
|
|
65
70
|
};
|
|
66
71
|
/**
|
|
67
72
|
* React Native config: Config (omit replaced props) & overrides.
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.
|
|
3
3
|
*/
|
|
4
4
|
export declare const FARO_REACT_NATIVE_NPM_NAME: "@grafana/faro-react-native";
|
|
5
|
-
export declare const FARO_REACT_NATIVE_NPM_VERSION: "1.
|
|
5
|
+
export declare const FARO_REACT_NATIVE_NPM_VERSION: "1.2.0";
|
|
@@ -22,6 +22,11 @@ export interface ErrorsInstrumentationOptions {
|
|
|
22
22
|
* Older errors are removed when this limit is reached
|
|
23
23
|
*/
|
|
24
24
|
maxDeduplicationEntries?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Bundle filename for Hermes/minified release lines (`func@line:col`).
|
|
27
|
+
* Must match the Metro plugin source map `file` field (default `bundle.js`).
|
|
28
|
+
*/
|
|
29
|
+
releaseBundleFilename?: string;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Errors instrumentation for React Native
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { ExceptionStackFrame as StackFrame } from '@grafana/faro-core';
|
|
2
|
+
/**
|
|
3
|
+
* Hermes (Android release) often reports the bundle segment as `address at index.android.bundle`
|
|
4
|
+
* inside parentheses. The collector hashes `frame.filename` for uploaded maps; it must match the
|
|
5
|
+
* source map `file` field (e.g. `index.android.bundle`), not the verbose Hermes label.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeHermesStackFilename(filename: string): string;
|
|
2
8
|
export interface ParsedStackFrame {
|
|
3
9
|
function?: string;
|
|
4
10
|
filename?: string;
|
|
5
11
|
lineno?: number;
|
|
6
12
|
colno?: number;
|
|
7
13
|
isNative?: boolean;
|
|
14
|
+
/** Set when the line matched Hermes/minified `func@line:col` (no path). */
|
|
15
|
+
releaseLine?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface StackFrameParseOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Hermes / minified RN release lines use `func@line:col` with no file path. Those frames are tagged
|
|
20
|
+
* with `releaseLine` and use this as `filename` (default `bundle.js`). Match the Metro plugin’s
|
|
21
|
+
* source map `file` field (or `sourceMapFile` option there).
|
|
22
|
+
*/
|
|
23
|
+
releaseBundleFilename?: string;
|
|
8
24
|
}
|
|
9
25
|
/**
|
|
10
26
|
* Parse a single stack trace line into a structured frame
|
|
@@ -17,11 +33,11 @@ export declare function parseStackTrace(stackTrace: string): ParsedStackFrame[];
|
|
|
17
33
|
/**
|
|
18
34
|
* Convert parsed stack frames to Faro's StackFrame format
|
|
19
35
|
*/
|
|
20
|
-
export declare function toFaroStackFrames(parsedFrames: ParsedStackFrame[]): StackFrame[];
|
|
36
|
+
export declare function toFaroStackFrames(parsedFrames: ParsedStackFrame[], options?: StackFrameParseOptions): StackFrame[];
|
|
21
37
|
/**
|
|
22
38
|
* Extract and parse stack frames from an Error object
|
|
23
39
|
*/
|
|
24
|
-
export declare function getStackFramesFromError(error: Error): StackFrame[];
|
|
40
|
+
export declare function getStackFramesFromError(error: Error, options?: StackFrameParseOptions): StackFrame[];
|
|
25
41
|
/**
|
|
26
42
|
* Get platform-specific error context
|
|
27
43
|
*/
|
|
@@ -29,7 +45,7 @@ export declare function getPlatformErrorContext(): Record<string, string>;
|
|
|
29
45
|
/**
|
|
30
46
|
* Enhance error with React Native specific information
|
|
31
47
|
*/
|
|
32
|
-
export declare function enhanceErrorWithContext(error: Error, additionalContext?: Record<string, string
|
|
48
|
+
export declare function enhanceErrorWithContext(error: Error, additionalContext?: Record<string, string>, stackParseOptions?: StackFrameParseOptions): {
|
|
33
49
|
error: Error;
|
|
34
50
|
stackFrames: StackFrame[];
|
|
35
51
|
context: Record<string, string>;
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* @see https://github.com/facebook/react-native/issues (PerformanceObserver iOS)
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* Apply
|
|
17
|
-
*
|
|
16
|
+
* Apply React Native performance API compatibility patches.
|
|
17
|
+
*
|
|
18
|
+
* Resource Timing lookup is patched on all platforms because OTel web
|
|
19
|
+
* instrumentation can probe it. The PerformanceObserver replacement is
|
|
20
|
+
* iOS-only to avoid the native bad_variant_access crash.
|
|
18
21
|
*/
|
|
19
22
|
export declare function applyPerformanceObserverPolyfill(): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Upload composed Hermes source map (iOS Release).
|
|
3
|
+
# Invoked from an autolinked Xcode Run Script phase. Expects FARO_BUNDLE_ID,
|
|
4
|
+
# FARO_SOURCEMAP_ENDPOINT, FARO_SOURCEMAP_APP_ID, FARO_SOURCEMAP_STACK_ID,
|
|
5
|
+
# FARO_SOURCEMAP_API_KEY, and optional FARO_SKIP_SOURCEMAP_UPLOAD (same names as `faro-cli metro upload`).
|
|
6
|
+
# Never fails the build — logs warnings and exits 0 when skipping.
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
# Source ios/.xcode.env then ios/.xcode.env.local before any FARO_* decisions so
|
|
11
|
+
# skips and credentials can live there (local overrides base; matches RN with-environment.sh).
|
|
12
|
+
if [ -n "${SRCROOT:-}" ]; then
|
|
13
|
+
if [ -f "$SRCROOT/.xcode.env" ]; then
|
|
14
|
+
set -a
|
|
15
|
+
# shellcheck disable=SC1090
|
|
16
|
+
. "$SRCROOT/.xcode.env"
|
|
17
|
+
set +a
|
|
18
|
+
fi
|
|
19
|
+
if [ -f "$SRCROOT/.xcode.env.local" ]; then
|
|
20
|
+
set -a
|
|
21
|
+
# shellcheck disable=SC1090
|
|
22
|
+
. "$SRCROOT/.xcode.env.local"
|
|
23
|
+
set +a
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Xcode CONFIGURATION is typically "Debug" or "Release"
|
|
28
|
+
case "$(printf '%s' "${CONFIGURATION:-}" | tr '[:upper:]' '[:lower:]')" in
|
|
29
|
+
release) ;;
|
|
30
|
+
*)
|
|
31
|
+
echo "[Faro] Skipping composed source map upload (configuration: ${CONFIGURATION:-unknown}, not Release)."
|
|
32
|
+
exit 0
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
case "${FARO_SKIP_SOURCEMAP_UPLOAD:-}" in
|
|
37
|
+
1 | true | TRUE)
|
|
38
|
+
echo "[Faro] Skipping composed source map upload (FARO_SKIP_SOURCEMAP_UPLOAD=${FARO_SKIP_SOURCEMAP_UPLOAD})."
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# Application JS root (parent of ios/)
|
|
44
|
+
JS_ROOT="${SRCROOT}/.."
|
|
45
|
+
CLI_PATH="$JS_ROOT/node_modules/@grafana/faro-metro-plugin/bin/faro-upload-source-map.js"
|
|
46
|
+
|
|
47
|
+
if [ ! -f "$CLI_PATH" ]; then
|
|
48
|
+
echo "warning: [Faro] Skipping composed source map upload — shim not found at ${CLI_PATH}. Install @grafana/faro-metro-plugin (devDependency) alongside @grafana/faro-react-native."
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# React Native Hermes pipeline writes the composed map to SOURCEMAP_FILE when set in
|
|
53
|
+
# Xcode build settings (see RN release / FE O11y docs). Fallback for common layouts.
|
|
54
|
+
MAP="${SOURCEMAP_FILE:-}"
|
|
55
|
+
if [ -z "$MAP" ]; then
|
|
56
|
+
MAP="${CONFIGURATION_BUILD_DIR}/main.jsbundle.map"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ ! -f "$MAP" ]; then
|
|
60
|
+
echo "warning: [Faro] Skipping composed source map upload — map not found at ${MAP}."
|
|
61
|
+
echo "warning: [Faro] For iOS Release + Hermes, set SOURCEMAP_FILE in Xcode Release (e.g. \$(DERIVED_FILE_DIR)/main.jsbundle.map) so react-native-xcode.sh composes main.jsbundle.map."
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
MISSING=""
|
|
66
|
+
[ -z "${FARO_BUNDLE_ID:-}" ] && MISSING="${MISSING}FARO_BUNDLE_ID "
|
|
67
|
+
[ -z "${FARO_SOURCEMAP_ENDPOINT:-}" ] && MISSING="${MISSING}FARO_SOURCEMAP_ENDPOINT "
|
|
68
|
+
[ -z "${FARO_SOURCEMAP_APP_ID:-}" ] && MISSING="${MISSING}FARO_SOURCEMAP_APP_ID "
|
|
69
|
+
[ -z "${FARO_SOURCEMAP_STACK_ID:-}" ] && MISSING="${MISSING}FARO_SOURCEMAP_STACK_ID "
|
|
70
|
+
[ -z "${FARO_SOURCEMAP_API_KEY:-}" ] && MISSING="${MISSING}FARO_SOURCEMAP_API_KEY "
|
|
71
|
+
|
|
72
|
+
if [ -n "$MISSING" ]; then
|
|
73
|
+
echo "warning: [Faro] Skipping composed source map upload — missing env: ${MISSING}"
|
|
74
|
+
echo "warning: [Faro] Export FARO_BUNDLE_ID and FARO_SOURCEMAP_* in the shell running xcodebuild, or add them to ios/.xcode.env.local."
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
NODE_BIN="${NODE_BINARY:-node}"
|
|
79
|
+
|
|
80
|
+
exec "$NODE_BIN" "$CLI_PATH" \
|
|
81
|
+
--verbose \
|
|
82
|
+
--map "$MAP" \
|
|
83
|
+
--endpoint "$FARO_SOURCEMAP_ENDPOINT" \
|
|
84
|
+
--app-id "$FARO_SOURCEMAP_APP_ID" \
|
|
85
|
+
--stack-id "$FARO_SOURCEMAP_STACK_ID" \
|
|
86
|
+
--api-key "$FARO_SOURCEMAP_API_KEY" \
|
|
87
|
+
--bundle-id "$FARO_BUNDLE_ID"
|