@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.
Files changed (47) hide show
  1. package/README.md +80 -3
  2. package/android/build.gradle +145 -0
  3. package/dist/cjs/config/getRNInstrumentations.js +3 -1
  4. package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
  5. package/dist/cjs/config/makeRNConfig.js +7 -4
  6. package/dist/cjs/config/makeRNConfig.js.map +1 -1
  7. package/dist/cjs/config/types.js.map +1 -1
  8. package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
  9. package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
  10. package/dist/cjs/instrumentations/errors/index.js +3 -2
  11. package/dist/cjs/instrumentations/errors/index.js.map +1 -1
  12. package/dist/cjs/instrumentations/errors/stackTraceParser.js +98 -37
  13. package/dist/cjs/instrumentations/errors/stackTraceParser.js.map +1 -1
  14. package/dist/cjs/util/performanceObserverPolyfill.js +32 -3
  15. package/dist/cjs/util/performanceObserverPolyfill.js.map +1 -1
  16. package/dist/esm/config/getRNInstrumentations.js +3 -1
  17. package/dist/esm/config/getRNInstrumentations.js.map +1 -1
  18. package/dist/esm/config/makeRNConfig.js +7 -4
  19. package/dist/esm/config/makeRNConfig.js.map +1 -1
  20. package/dist/esm/config/types.js.map +1 -1
  21. package/dist/esm/generated/faroRNPackageMeta.js +1 -1
  22. package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
  23. package/dist/esm/instrumentations/errors/index.js +3 -2
  24. package/dist/esm/instrumentations/errors/index.js.map +1 -1
  25. package/dist/esm/instrumentations/errors/stackTraceParser.js +97 -37
  26. package/dist/esm/instrumentations/errors/stackTraceParser.js.map +1 -1
  27. package/dist/esm/util/performanceObserverPolyfill.js +32 -3
  28. package/dist/esm/util/performanceObserverPolyfill.js.map +1 -1
  29. package/dist/types/config/types.d.ts +5 -0
  30. package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
  31. package/dist/types/instrumentations/errors/index.d.ts +5 -0
  32. package/dist/types/instrumentations/errors/stackTraceParser.d.ts +19 -3
  33. package/dist/types/util/performanceObserverPolyfill.d.ts +5 -2
  34. package/ios/faro-upload-composed-source-map.sh +87 -0
  35. package/package.json +7 -10
  36. package/react-native.config.js +9 -1
  37. package/src/config/getRNInstrumentations.ts +5 -1
  38. package/src/config/makeRNConfig.test.ts +11 -0
  39. package/src/config/makeRNConfig.ts +7 -4
  40. package/src/config/types.ts +5 -0
  41. package/src/generated/faroRNPackageMeta.ts +1 -1
  42. package/src/initialize.test.ts +25 -0
  43. package/src/instrumentations/errors/index.ts +25 -8
  44. package/src/instrumentations/errors/stackTraceParser.test.ts +65 -0
  45. package/src/instrumentations/errors/stackTraceParser.ts +121 -37
  46. package/src/util/performanceObserverPolyfill.test.ts +48 -0
  47. 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
- * Parse a single stack trace line into a structured frame
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 parseStackTraceLine(line) {
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
- function: match[1] || '<anonymous>',
39
- filename: match[2],
40
- lineno: parseInt(match[3], 10),
41
- colno: parseInt(match[4], 10),
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
- function: match[1] || '<anonymous>',
49
- filename: match[2],
50
- lineno: parseInt(match[3], 10),
51
- colno: parseInt(match[4], 10),
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
- function: '<anonymous>',
59
- filename: match[1],
60
- lineno: parseInt(match[2], 10),
61
- colno: parseInt(match[3], 10),
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
- function: match[1] || '<native>',
69
- filename: '<native>',
70
- isNative: true,
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
- function: match[1] || '<anonymous>',
78
- filename: '<unknown>',
79
- lineno: parseInt(match[2], 10),
80
- colno: parseInt(match[3], 10),
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
- // Couldn't parse this line
84
- return null;
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 = parseStackTraceLine(line);
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
- return parsedFrames.map((frame, _index) => ({
108
- filename: frame.filename || '<unknown>',
109
- function: frame.function || '<anonymous>',
110
- lineno: frame.lineno,
111
- colno: frame.colno,
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 polyfill on React Native iOS to prevent bad_variant_access crash.
41
- * Must run before any code that might use PerformanceObserver.
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;AAqBL;;;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;;;GAGG;AACH,MAAM,UAAU,gCAAgC;IAC9C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,SAAoC,CAAC;IACpD,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","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\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 polyfill on React Native iOS to prevent bad_variant_access crash.\n * Must run before any code that might use PerformanceObserver.\n */\nexport function applyPerformanceObserverPolyfill(): void {\n if (Platform.OS !== 'ios') {\n return;\n }\n\n const record = globalObj as Record<string, unknown>;\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"]}
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.0.0";
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 polyfill on React Native iOS to prevent bad_variant_access crash.
17
- * Must run before any code that might use PerformanceObserver.
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"