@grafana/faro-react-native 1.0.0 → 1.1.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 CHANGED
@@ -47,7 +47,12 @@ initializeFaro({
47
47
  fetchVitalsInterval: 30000, // optional, ms
48
48
  frameMonitoringOptions: {}, // optional, when refreshRateVitals true
49
49
  enableTracing: false, // optional, requires @grafana/faro-react-native-tracing
50
- tracingOptions: {}, // optional
50
+ tracingOptions: {
51
+ instrumentationOptions: {
52
+ // Traces fetch() by default. Enable this for axios/direct XHR apps.
53
+ enableXhrInstrumentation: false,
54
+ },
55
+ }, // optional
51
56
 
52
57
  // Instrumentation options - optional
53
58
  consoleCaptureOptions: {},
@@ -85,8 +90,8 @@ initializeFaro({
85
90
  - **App State Instrumentation** - Tracks when app goes to background/foreground
86
91
  - **Performance Instrumentation** - Monitors CPU usage, memory usage, and app startup time using native OS APIs
87
92
  - **Startup Instrumentation** - Automatically tracks app startup duration from process start
88
- - **HTTP Instrumentation** - Tracks HTTP requests (fetch) and correlates them with user actions
89
- - **XHR Instrumentation** - Tracks XMLHttpRequest calls (replaced by TracingInstrumentation when `enableTracing` is true)
93
+ - **HTTP Instrumentation** - Tracks `fetch()` requests and correlates them with user actions
94
+ - **XHR Instrumentation** - Tracks `XMLHttpRequest` calls when tracing is disabled; with `enableTracing: true`, enable XHR tracing via `tracingOptions.instrumentationOptions.enableXhrInstrumentation`
90
95
 
91
96
  **Enabled by default** (opt-out via config):
92
97
 
@@ -5,5 +5,5 @@ exports.FARO_REACT_NATIVE_NPM_VERSION = exports.FARO_REACT_NATIVE_NPM_NAME = voi
5
5
  * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.
6
6
  */
7
7
  exports.FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native';
8
- exports.FARO_REACT_NATIVE_NPM_VERSION = '1.0.0';
8
+ exports.FARO_REACT_NATIVE_NPM_VERSION = '1.1.0';
9
9
  //# sourceMappingURL=faroRNPackageMeta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,0BAA0B,GAAG,4BAAqC,CAAC;AACnE,QAAA,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.0.0' as const;\n"]}
1
+ {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,0BAA0B,GAAG,4BAAqC,CAAC;AACnE,QAAA,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.1.0' as const;\n"]}
@@ -20,6 +20,8 @@ var globalObj = (typeof globalThis !== 'undefined' && globalThis) ||
20
20
  (typeof global !== 'undefined' && global) ||
21
21
  (typeof window !== 'undefined' && window) ||
22
22
  {};
23
+ var RESOURCE_ENTRY_TYPE = 'resource';
24
+ var FARO_RESOURCE_TIMING_PATCH = '__faroResourceTimingPatch';
23
25
  /**
24
26
  * No-op PerformanceObserver that matches the Web API interface.
25
27
  * Prevents crashes when React Native's native implementation throws.
@@ -41,14 +43,18 @@ var NoopPerformanceObserver = /** @class */ (function () {
41
43
  return NoopPerformanceObserver;
42
44
  }());
43
45
  /**
44
- * Apply polyfill on React Native iOS to prevent bad_variant_access crash.
45
- * Must run before any code that might use PerformanceObserver.
46
+ * Apply React Native performance API compatibility patches.
47
+ *
48
+ * Resource Timing lookup is patched on all platforms because OTel web
49
+ * instrumentation can probe it. The PerformanceObserver replacement is
50
+ * iOS-only to avoid the native bad_variant_access crash.
46
51
  */
47
52
  function applyPerformanceObserverPolyfill() {
53
+ var record = globalObj;
54
+ patchUnsupportedResourceTimingLookup(record);
48
55
  if (react_native_1.Platform.OS !== 'ios') {
49
56
  return;
50
57
  }
51
- var record = globalObj;
52
58
  var existing = record['PerformanceObserver'];
53
59
  if (existing && existing.name === 'NoopPerformanceObserver') {
54
60
  return; // Already applied
@@ -60,4 +66,27 @@ function applyPerformanceObserverPolyfill() {
60
66
  // Ignore if global is frozen (e.g. in some test environments)
61
67
  }
62
68
  }
69
+ function patchUnsupportedResourceTimingLookup(record) {
70
+ var performance = record['performance'];
71
+ if (!(performance === null || performance === void 0 ? void 0 : performance.getEntriesByType) || performance.getEntriesByType[FARO_RESOURCE_TIMING_PATCH]) {
72
+ return;
73
+ }
74
+ var originalGetEntriesByType = performance.getEntriesByType.bind(performance);
75
+ var patchedGetEntriesByType = (function (entryType) {
76
+ if (entryType === RESOURCE_ENTRY_TYPE) {
77
+ // OTel web fetch instrumentation probes browser Resource Timing APIs.
78
+ // React Native does not expose resource entries, and calling through emits
79
+ // "Deprecated API for given entry type." warnings in development.
80
+ return [];
81
+ }
82
+ return originalGetEntriesByType(entryType);
83
+ });
84
+ patchedGetEntriesByType[FARO_RESOURCE_TIMING_PATCH] = true;
85
+ try {
86
+ performance.getEntriesByType = patchedGetEntriesByType;
87
+ }
88
+ catch (_a) {
89
+ // Ignore if performance is read-only in a test or host environment.
90
+ }
91
+ }
63
92
  //# sourceMappingURL=performanceObserverPolyfill.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"performanceObserverPolyfill.js","sourceRoot":"","sources":["../../../src/util/performanceObserverPolyfill.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AA+DH,4EAgBC;AA7ED,6CAAwC;AAExC,IAAM,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;IACE,iCACE,SAIS;QAET,+DAA+D;IACjE,CAAC;IAED,yCAAO,GAAP,UAAQ,QAAkC;QACxC,QAAQ;IACV,CAAC;IAED,4CAAU,GAAV;QACE,QAAQ;IACV,CAAC;IAED,6CAAW,GAAX;QACE,OAAO,EAAE,CAAC;IACZ,CAAC;IAEe,2CAAmB,GAAa,EAAE,CAAC;IACrD,8BAAC;CAAA,AAxBD,IAwBC;AAED;;;GAGG;AACH,SAAgB,gCAAgC;IAC9C,IAAI,uBAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,IAAM,MAAM,GAAG,SAAoC,CAAC;IACpD,IAAM,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;;AAqEH,4EAkBC;AArFD,6CAAwC;AAExC,IAAM,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,IAAM,mBAAmB,GAAG,UAAU,CAAC;AACvC,IAAM,0BAA0B,GAAG,2BAA2B,CAAC;AAqB/D;;;GAGG;AACH;IACE,iCACE,SAIS;QAET,+DAA+D;IACjE,CAAC;IAED,yCAAO,GAAP,UAAQ,QAAkC;QACxC,QAAQ;IACV,CAAC;IAED,4CAAU,GAAV;QACE,QAAQ;IACV,CAAC;IAED,6CAAW,GAAX;QACE,OAAO,EAAE,CAAC;IACZ,CAAC;IAEe,2CAAmB,GAAa,EAAE,CAAC;IACrD,8BAAC;CAAA,AAxBD,IAwBC;AAED;;;;;;GAMG;AACH,SAAgB,gCAAgC;IAC9C,IAAM,MAAM,GAAG,SAAoC,CAAC;IACpD,oCAAoC,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,uBAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,IAAM,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,IAAM,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,IAAM,wBAAwB,GAAG,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChF,IAAM,uBAAuB,GAAG,CAAC,UAAC,SAAiB;QACjD,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"]}
@@ -2,5 +2,5 @@
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 const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native';
5
- export const FARO_REACT_NATIVE_NPM_VERSION = '1.0.0';
5
+ export const FARO_REACT_NATIVE_NPM_VERSION = '1.1.0';
6
6
  //# sourceMappingURL=faroRNPackageMeta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,4BAAqC,CAAC;AAChF,MAAM,CAAC,MAAM,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.0.0' as const;\n"]}
1
+ {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,4BAAqC,CAAC;AAChF,MAAM,CAAC,MAAM,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.1.0' as const;\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"]}
@@ -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.1.0";
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/faro-react-native",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Faro instrumentations, metas, transports for React Native.",
5
5
  "keywords": [
6
6
  "observability",
@@ -87,5 +87,5 @@
87
87
  "publishConfig": {
88
88
  "access": "public"
89
89
  },
90
- "gitHead": "00172e294e05bcf2faa2193d01aabc43c3cc8134"
90
+ "gitHead": "8ca367978d53ef163886a2e99690c6f8a61416dc"
91
91
  }
@@ -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 const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;
5
- export const FARO_REACT_NATIVE_NPM_VERSION = '1.0.0' as const;
5
+ export const FARO_REACT_NATIVE_NPM_VERSION = '1.1.0' as const;
@@ -0,0 +1,48 @@
1
+ import { applyPerformanceObserverPolyfill } from './performanceObserverPolyfill';
2
+
3
+ describe('applyPerformanceObserverPolyfill', () => {
4
+ const originalPerformance = global.performance;
5
+ const originalPerformanceObserver = global.PerformanceObserver;
6
+
7
+ afterEach(() => {
8
+ Object.defineProperty(global, 'performance', {
9
+ configurable: true,
10
+ value: originalPerformance,
11
+ writable: true,
12
+ });
13
+ Object.defineProperty(global, 'PerformanceObserver', {
14
+ configurable: true,
15
+ value: originalPerformanceObserver,
16
+ writable: true,
17
+ });
18
+ });
19
+
20
+ it('returns an empty list for unsupported resource timing lookups', () => {
21
+ const getEntriesByType = jest.fn(() => [{ entryType: 'mark', name: 'test' }]);
22
+ Object.defineProperty(global, 'performance', {
23
+ configurable: true,
24
+ value: { getEntriesByType },
25
+ writable: true,
26
+ });
27
+
28
+ applyPerformanceObserverPolyfill();
29
+
30
+ expect(global.performance.getEntriesByType('resource')).toEqual([]);
31
+ expect(getEntriesByType).not.toHaveBeenCalledWith('resource');
32
+ });
33
+
34
+ it('delegates non-resource timing lookups to the original implementation', () => {
35
+ const markEntry = { entryType: 'mark', name: 'test' };
36
+ const getEntriesByType = jest.fn(() => [markEntry]);
37
+ Object.defineProperty(global, 'performance', {
38
+ configurable: true,
39
+ value: { getEntriesByType },
40
+ writable: true,
41
+ });
42
+
43
+ applyPerformanceObserverPolyfill();
44
+
45
+ expect(global.performance.getEntriesByType('mark')).toEqual([markEntry]);
46
+ expect(getEntriesByType).toHaveBeenCalledWith('mark');
47
+ });
48
+ });
@@ -21,6 +21,9 @@ const globalObj =
21
21
  (typeof window !== 'undefined' && window) ||
22
22
  {};
23
23
 
24
+ const RESOURCE_ENTRY_TYPE = 'resource';
25
+ const FARO_RESOURCE_TIMING_PATCH = '__faroResourceTimingPatch';
26
+
24
27
  interface PerformanceEntry {
25
28
  readonly duration: number;
26
29
  readonly entryType: string;
@@ -71,15 +74,20 @@ class NoopPerformanceObserver {
71
74
  }
72
75
 
73
76
  /**
74
- * Apply polyfill on React Native iOS to prevent bad_variant_access crash.
75
- * Must run before any code that might use PerformanceObserver.
77
+ * Apply React Native performance API compatibility patches.
78
+ *
79
+ * Resource Timing lookup is patched on all platforms because OTel web
80
+ * instrumentation can probe it. The PerformanceObserver replacement is
81
+ * iOS-only to avoid the native bad_variant_access crash.
76
82
  */
77
83
  export function applyPerformanceObserverPolyfill(): void {
84
+ const record = globalObj as Record<string, unknown>;
85
+ patchUnsupportedResourceTimingLookup(record);
86
+
78
87
  if (Platform.OS !== 'ios') {
79
88
  return;
80
89
  }
81
90
 
82
- const record = globalObj as Record<string, unknown>;
83
91
  const existing = record['PerformanceObserver'];
84
92
  if (existing && (existing as unknown as { name?: string }).name === 'NoopPerformanceObserver') {
85
93
  return; // Already applied
@@ -91,3 +99,35 @@ export function applyPerformanceObserverPolyfill(): void {
91
99
  // Ignore if global is frozen (e.g. in some test environments)
92
100
  }
93
101
  }
102
+
103
+ function patchUnsupportedResourceTimingLookup(record: Record<string, unknown>): void {
104
+ const performance = record['performance'] as
105
+ | {
106
+ getEntriesByType?: ((entryType: string) => PerformanceEntry[]) & Record<string, boolean>;
107
+ }
108
+ | undefined;
109
+
110
+ if (!performance?.getEntriesByType || performance.getEntriesByType[FARO_RESOURCE_TIMING_PATCH]) {
111
+ return;
112
+ }
113
+
114
+ const originalGetEntriesByType = performance.getEntriesByType.bind(performance);
115
+ const patchedGetEntriesByType = ((entryType: string): PerformanceEntry[] => {
116
+ if (entryType === RESOURCE_ENTRY_TYPE) {
117
+ // OTel web fetch instrumentation probes browser Resource Timing APIs.
118
+ // React Native does not expose resource entries, and calling through emits
119
+ // "Deprecated API for given entry type." warnings in development.
120
+ return [];
121
+ }
122
+
123
+ return originalGetEntriesByType(entryType);
124
+ }) as typeof performance.getEntriesByType;
125
+
126
+ patchedGetEntriesByType[FARO_RESOURCE_TIMING_PATCH] = true;
127
+
128
+ try {
129
+ performance.getEntriesByType = patchedGetEntriesByType;
130
+ } catch {
131
+ // Ignore if performance is read-only in a test or host environment.
132
+ }
133
+ }