@grafana/faro-react-native 1.3.1 → 1.4.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 +176 -30
- package/android/build.gradle +17 -12
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashIds.kt +44 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +134 -38
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashSessionStore.kt +433 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +11 -2
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +128 -17
- package/android/src/main/java/com/grafana/faro/reactnative/FaroSessionProcess.kt +88 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FrameMonitor.kt +34 -42
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterBelowApi30Test.kt +21 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterTest.kt +59 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashSessionStoreTest.kt +268 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashTraceCacheTest.kt +52 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroSessionProcessTest.kt +127 -0
- package/dist/cjs/config/getRNInstrumentations.js +6 -8
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/sampling.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/dataCollection/DataCollectionPolicy.js +1 -2
- package/dist/cjs/dataCollection/DataCollectionPolicy.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/index.js +8 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/instrumentations/anr/instrumentation.js +10 -1
- package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/appState/index.js +8 -2
- package/dist/cjs/instrumentations/appState/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +19 -6
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +331 -0
- package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +8 -3
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js +20 -31
- package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/frameMonitoring/types.js.map +1 -1
- package/dist/cjs/instrumentations/performance/index.js +2 -6
- package/dist/cjs/instrumentations/performance/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js +48 -0
- package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
- package/dist/cjs/instrumentations/session/directSessionActivity.js +37 -0
- package/dist/cjs/instrumentations/session/directSessionActivity.js.map +1 -0
- package/dist/cjs/instrumentations/session/index.js +214 -45
- package/dist/cjs/instrumentations/session/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionActivity.js +43 -0
- package/dist/cjs/instrumentations/session/sessionActivity.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionAttributes.js +8 -8
- package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +199 -24
- package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -13
- package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js +72 -0
- package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +124 -96
- package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/types.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionProcess.js +96 -0
- package/dist/cjs/instrumentations/session/sessionProcess.js.map +1 -0
- package/dist/cjs/instrumentations/startup/index.js +2 -6
- package/dist/cjs/instrumentations/startup/index.js.map +1 -1
- package/dist/cjs/navigation/utils.js +3 -1
- package/dist/cjs/navigation/utils.js.map +1 -1
- package/dist/cjs/transports/fetch/index.js.map +1 -1
- package/dist/cjs/transports/fetch/transport.js +56 -25
- package/dist/cjs/transports/fetch/transport.js.map +1 -1
- package/dist/cjs/transports/fetch/types.js.map +1 -1
- package/dist/cjs/transports/offline/ConnectivityService.js +1 -1
- package/dist/cjs/transports/offline/ConnectivityService.js.map +1 -1
- package/dist/cjs/transports/offline/OfflineCache.js +2 -2
- package/dist/cjs/transports/offline/OfflineCache.js.map +1 -1
- package/dist/cjs/transports/offline/transport.js +3 -4
- package/dist/cjs/transports/offline/transport.js.map +1 -1
- package/dist/cjs/userPersistence/UserPersistence.js +1 -2
- package/dist/cjs/userPersistence/UserPersistence.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +6 -8
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/sampling.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/dataCollection/DataCollectionPolicy.js +1 -2
- package/dist/esm/dataCollection/DataCollectionPolicy.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/instrumentations/anr/instrumentation.js +6 -1
- package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/appState/index.js +8 -2
- package/dist/esm/instrumentations/appState/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +15 -4
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +230 -0
- package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +8 -3
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/frameMonitoring/instrumentation.js +17 -32
- package/dist/esm/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/frameMonitoring/types.js.map +1 -1
- package/dist/esm/instrumentations/performance/index.js +2 -6
- package/dist/esm/instrumentations/performance/index.js.map +1 -1
- package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js +34 -0
- package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
- package/dist/esm/instrumentations/session/directSessionActivity.js +32 -0
- package/dist/esm/instrumentations/session/directSessionActivity.js.map +1 -0
- package/dist/esm/instrumentations/session/index.js +207 -46
- package/dist/esm/instrumentations/session/index.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionActivity.js +38 -0
- package/dist/esm/instrumentations/session/sessionActivity.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionAttributes.js +7 -7
- package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +190 -27
- package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -14
- package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js +56 -0
- package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js +1 -1
- package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +120 -44
- package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/types.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionProcess.js +89 -0
- package/dist/esm/instrumentations/session/sessionProcess.js.map +1 -0
- package/dist/esm/instrumentations/startup/index.js +2 -6
- package/dist/esm/instrumentations/startup/index.js.map +1 -1
- package/dist/esm/navigation/utils.js +2 -1
- package/dist/esm/navigation/utils.js.map +1 -1
- package/dist/esm/transports/fetch/index.js.map +1 -1
- package/dist/esm/transports/fetch/transport.js +38 -14
- package/dist/esm/transports/fetch/transport.js.map +1 -1
- package/dist/esm/transports/fetch/types.js.map +1 -1
- package/dist/esm/transports/offline/ConnectivityService.js +1 -1
- package/dist/esm/transports/offline/ConnectivityService.js.map +1 -1
- package/dist/esm/transports/offline/OfflineCache.js +2 -2
- package/dist/esm/transports/offline/OfflineCache.js.map +1 -1
- package/dist/esm/transports/offline/transport.js +3 -4
- package/dist/esm/transports/offline/transport.js.map +1 -1
- package/dist/esm/userPersistence/UserPersistence.js +1 -2
- package/dist/esm/userPersistence/UserPersistence.js.map +1 -1
- package/dist/types/config/getRNInstrumentations.d.ts +1 -2
- package/dist/types/config/sampling.d.ts +0 -3
- package/dist/types/config/types.d.ts +5 -3
- package/dist/types/dataCollection/DataCollectionPolicy.d.ts +1 -2
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
- package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +2 -2
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +2 -2
- package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +2 -2
- package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
- package/dist/types/instrumentations/performance/index.d.ts +0 -2
- package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
- package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
- package/dist/types/instrumentations/session/index.d.ts +20 -1
- package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
- package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
- package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
- package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
- package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
- package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
- package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
- package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
- package/dist/types/instrumentations/startup/index.d.ts +2 -6
- package/dist/types/navigation/utils.d.ts +1 -0
- package/dist/types/transports/fetch/index.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +8 -1
- package/dist/types/transports/fetch/types.d.ts +5 -0
- package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
- package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
- package/dist/types/transports/offline/transport.d.ts +3 -4
- package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
- package/ios/FaroCrashReporter.swift +214 -21
- package/ios/FaroReactNative.swift +24 -0
- package/ios/FaroReactNativeModule.h +2 -1
- package/ios/FaroReactNativeModule.mm +121 -6
- package/ios/RefreshRateVitals.swift +34 -38
- package/package.json +7 -5
- package/src/config/getRNInstrumentations.ts +6 -8
- package/src/config/makeRNConfig.test.ts +6 -0
- package/src/config/sampling.ts +0 -3
- package/src/config/types.ts +5 -3
- package/src/dataCollection/DataCollectionPolicy.ts +1 -2
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/index.ts +8 -2
- package/src/instrumentations/anr/instrumentation.ts +8 -1
- package/src/instrumentations/appState/index.ts +10 -2
- package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
- package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/types.ts +7 -0
- package/src/instrumentations/errors/index.ts +12 -4
- package/src/instrumentations/errors/instrumentation.test.ts +35 -0
- package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
- package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
- package/src/instrumentations/frameMonitoring/types.ts +4 -6
- package/src/instrumentations/performance/index.ts +2 -6
- package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
- package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
- package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
- package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
- package/src/instrumentations/session/directSessionActivity.ts +36 -0
- package/src/instrumentations/session/index.ts +269 -46
- package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
- package/src/instrumentations/session/sessionActivity.test.ts +68 -0
- package/src/instrumentations/session/sessionActivity.ts +58 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
- package/src/instrumentations/session/sessionAttributes.ts +12 -6
- package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
- package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
- package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
- package/src/instrumentations/session/sessionManager/types.ts +8 -0
- package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
- package/src/instrumentations/session/sessionProcess.test.ts +136 -0
- package/src/instrumentations/session/sessionProcess.ts +114 -0
- package/src/instrumentations/session/startNewSession.test.ts +337 -0
- package/src/instrumentations/startup/index.ts +2 -6
- package/src/navigation/utils.ts +3 -1
- package/src/transports/fetch/index.ts +6 -1
- package/src/transports/fetch/transport.test.ts +86 -2
- package/src/transports/fetch/transport.ts +38 -16
- package/src/transports/fetch/types.ts +7 -0
- package/src/transports/offline/ConnectivityService.ts +1 -1
- package/src/transports/offline/OfflineCache.ts +2 -2
- package/src/transports/offline/transport.ts +3 -4
- package/src/userPersistence/UserPersistence.ts +1 -2
- package/dist/cjs/utils/throttle.js +0 -42
- package/dist/cjs/utils/throttle.js.map +0 -1
- package/dist/esm/utils/throttle.js +0 -34
- package/dist/esm/utils/throttle.js.map +0 -1
- package/dist/types/utils/throttle.d.ts +0 -9
- package/src/utils/throttle.test.ts +0 -141
- package/src/utils/throttle.ts +0 -34
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { NativeModules } from 'react-native';
|
|
11
|
+
import { TransportItemType } from '@grafana/faro-core';
|
|
12
|
+
import { FetchTransport } from '../../transports/fetch';
|
|
13
|
+
import { BaseCrashReportingInstrumentation } from './BaseCrashReportingInstrumentation';
|
|
14
|
+
// Keep aligned with FaroCrashSessionStore.MAX_CONTEXT_AGE_MS on Android.
|
|
15
|
+
const MAX_REPLAY_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
/**
|
|
17
|
+
* Shared recovered-crash delivery contract for native platforms.
|
|
18
|
+
*
|
|
19
|
+
* Native code owns crash-time persistence. JavaScript owns validation, transport
|
|
20
|
+
* acceptance, and acknowledgement so retryable failures never delete a report.
|
|
21
|
+
*/
|
|
22
|
+
export class RecoveredCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.nativeModule = null;
|
|
26
|
+
this.supportsSessionReplay = false;
|
|
27
|
+
this.metasListenerRegistered = false;
|
|
28
|
+
this.lastRecordedSessionSignature = null;
|
|
29
|
+
this.activeSessionId = null;
|
|
30
|
+
this.activeSessionActivatedAtMs = 0;
|
|
31
|
+
this.recordSessionContextListener = (meta) => {
|
|
32
|
+
this.recordSessionContext(meta);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
prepareCrashReporting(nativeModule) {
|
|
36
|
+
this.supportsSessionReplay =
|
|
37
|
+
typeof nativeModule.recordCrashSessionContext === 'function' &&
|
|
38
|
+
typeof nativeModule.getPendingCrashReports === 'function' &&
|
|
39
|
+
typeof nativeModule.acknowledgeCrashReports === 'function';
|
|
40
|
+
if (!this.supportsSessionReplay) {
|
|
41
|
+
this.logWarn('Native module does not support original-session crash replay; using legacy crash reporting');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.nativeModule = nativeModule;
|
|
45
|
+
this.recordSessionContext(this.metas.value);
|
|
46
|
+
if (!this.metasListenerRegistered) {
|
|
47
|
+
this.metas.addListener(this.recordSessionContextListener);
|
|
48
|
+
this.metasListenerRegistered = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
processCrashReports(nativeModule) {
|
|
52
|
+
const _super = Object.create(null, {
|
|
53
|
+
processCrashReports: { get: () => super.processCrashReports }
|
|
54
|
+
});
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
if (!this.supportsSessionReplay) {
|
|
58
|
+
yield _super.processCrashReports.call(this, nativeModule);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const replayStartedAtMs = Date.now();
|
|
63
|
+
const crashReports = (yield nativeModule.getPendingCrashReports());
|
|
64
|
+
if (!(crashReports === null || crashReports === void 0 ? void 0 : crashReports.length)) {
|
|
65
|
+
this.logDebug('No crash reports from previous session');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const processedReportIds = new Set();
|
|
69
|
+
for (const crashJson of crashReports) {
|
|
70
|
+
let parsedCrash;
|
|
71
|
+
try {
|
|
72
|
+
parsedCrash = JSON.parse(crashJson);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
this.logError('Failed to parse recovered crash report', error);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!parsedCrash || typeof parsedCrash !== 'object' || Array.isArray(parsedCrash)) {
|
|
79
|
+
this.logWarn('Skipping malformed recovered crash report');
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const crash = parsedCrash;
|
|
83
|
+
const reportId = (_a = crash.reportId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
84
|
+
if (!reportId) {
|
|
85
|
+
this.logWarn('Skipping malformed recovered crash report');
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!this.hasValidTimestamp(crash)) {
|
|
89
|
+
this.logWarn(`Discarding malformed recovered crash ${reportId}`);
|
|
90
|
+
yield this.acknowledgeCrashReport(nativeModule, reportId);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (processedReportIds.has(reportId)) {
|
|
94
|
+
this.logDebug(`Skipping duplicate recovered crash report ${reportId}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
processedReportIds.add(reportId);
|
|
98
|
+
if (!this.isWithinReplayWindow(crash.timestamp, replayStartedAtMs)) {
|
|
99
|
+
yield this.acknowledgeCrashReport(nativeModule, reportId);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (this.shouldSkipCrashReport(crash)) {
|
|
103
|
+
yield this.acknowledgeCrashReport(nativeModule, reportId);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const sessionId = (_b = crash.sessionId) === null || _b === void 0 ? void 0 : _b.trim();
|
|
107
|
+
if (!sessionId) {
|
|
108
|
+
this.logWarn(`Discarding recovered crash ${reportId} because its original session context is unavailable`);
|
|
109
|
+
yield this.acknowledgeCrashReport(nativeModule, reportId);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const outcome = yield this.replayCrashReport(Object.assign(Object.assign({}, crash), { reportId,
|
|
113
|
+
sessionId }));
|
|
114
|
+
if (outcome === 'accepted' || outcome === 'filtered') {
|
|
115
|
+
yield this.acknowledgeCrashReport(nativeModule, reportId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logError('Failed to process recovered crash reports', error);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
recordSessionContext(meta) {
|
|
125
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
126
|
+
const sessionId = (_b = (_a = meta.session) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.trim();
|
|
127
|
+
if (!sessionId || !this.nativeModule) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (this.activeSessionId !== sessionId) {
|
|
131
|
+
this.activeSessionId = sessionId;
|
|
132
|
+
this.activeSessionActivatedAtMs = Date.now();
|
|
133
|
+
this.activeSessionIsSampled = undefined;
|
|
134
|
+
}
|
|
135
|
+
const sampledValue = (_d = (_c = meta.session) === null || _c === void 0 ? void 0 : _c.attributes) === null || _d === void 0 ? void 0 : _d['isSampled'];
|
|
136
|
+
const incomingIsSampled = sampledValue === 'true' ? true : sampledValue === 'false' ? false : undefined;
|
|
137
|
+
if (incomingIsSampled !== undefined) {
|
|
138
|
+
this.activeSessionIsSampled = incomingIsSampled;
|
|
139
|
+
}
|
|
140
|
+
const isSampled = incomingIsSampled !== null && incomingIsSampled !== void 0 ? incomingIsSampled : this.activeSessionIsSampled;
|
|
141
|
+
const sessionContext = Object.assign(Object.assign(Object.assign(Object.assign({ sessionId, activatedAt: this.activeSessionActivatedAtMs }, (isSampled === undefined ? {} : { isSampled })), (((_e = meta.app) === null || _e === void 0 ? void 0 : _e.version) ? { appVersion: meta.app.version } : {})), (((_f = meta.app) === null || _f === void 0 ? void 0 : _f.release) ? { appRelease: meta.app.release } : {})), (((_g = meta.app) === null || _g === void 0 ? void 0 : _g.bundleId) ? { appBundleId: meta.app.bundleId } : {}));
|
|
142
|
+
const signature = JSON.stringify(sessionContext);
|
|
143
|
+
if (signature === this.lastRecordedSessionSignature) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const stored = this.nativeModule.recordCrashSessionContext(sessionContext);
|
|
148
|
+
if (stored === true) {
|
|
149
|
+
this.lastRecordedSessionSignature = signature;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
this.logError('Failed to persist crash session context', error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
replayCrashReport(crash) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
if (this.transports.isPaused()) {
|
|
159
|
+
return 'retry';
|
|
160
|
+
}
|
|
161
|
+
const fetchTransport = this.transports.transports.find((transport) => transport instanceof FetchTransport);
|
|
162
|
+
if (!fetchTransport) {
|
|
163
|
+
this.logWarn('Fetch transport is unavailable; leaving recovered crash pending');
|
|
164
|
+
return 'retry';
|
|
165
|
+
}
|
|
166
|
+
// Recovered crashes intentionally bypass transport fan-out. Native storage
|
|
167
|
+
// handles retries, and only FetchTransport reports collector acceptance before ack.
|
|
168
|
+
let item = this.buildRecoveredCrashItem(crash);
|
|
169
|
+
try {
|
|
170
|
+
for (const hook of this.transports.getBeforeSendHooks()) {
|
|
171
|
+
const modifiedItem = hook(item);
|
|
172
|
+
// Match faro-core's filter(Boolean) behavior for invalid hook results.
|
|
173
|
+
if (!modifiedItem) {
|
|
174
|
+
return 'filtered';
|
|
175
|
+
}
|
|
176
|
+
item = modifiedItem;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
this.logError('Failed to apply beforeSend hook to recovered crash', error);
|
|
181
|
+
return 'retry';
|
|
182
|
+
}
|
|
183
|
+
const result = yield fetchTransport.sendWithResult([item]);
|
|
184
|
+
return result.outcome === 'accepted' ? 'accepted' : 'retry';
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
buildRecoveredCrashItem(crash) {
|
|
188
|
+
var _a;
|
|
189
|
+
const { context, errorMessage, stackFrames } = this.buildCrashReportDetails(crash);
|
|
190
|
+
const currentMeta = this.metas.value;
|
|
191
|
+
const isSampledAttribute = crash.isSampled === undefined ? undefined : { isSampled: String(crash.isSampled) };
|
|
192
|
+
// Current user, view, and page metadata belong to the restarted session.
|
|
193
|
+
const meta = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (currentMeta.sdk ? { sdk: Object.assign({}, currentMeta.sdk) } : {})), { app: Object.assign(Object.assign(Object.assign(Object.assign({}, ((_a = currentMeta.app) !== null && _a !== void 0 ? _a : {})), (crash.appVersion ? { version: crash.appVersion } : {})), (crash.appRelease ? { release: crash.appRelease } : {})), (crash.appBundleId ? { bundleId: crash.appBundleId } : {})) }), (currentMeta.os ? { os: Object.assign({}, currentMeta.os) } : {})), (currentMeta.device ? { device: Object.assign({}, currentMeta.device) } : {})), { session: Object.assign({ id: crash.sessionId }, (isSampledAttribute ? { attributes: isSampledAttribute } : {})) });
|
|
194
|
+
return {
|
|
195
|
+
type: TransportItemType.EXCEPTION,
|
|
196
|
+
meta,
|
|
197
|
+
payload: Object.assign({ type: 'crash', value: errorMessage, timestamp: new Date(crash.timestamp).toISOString(), context, fatal: true }, (stackFrames.length ? { stacktrace: { frames: stackFrames } } : {})),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
acknowledgeCrashReport(nativeModule, reportId) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
try {
|
|
203
|
+
yield nativeModule.acknowledgeCrashReports([reportId]);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
this.logError(`Failed to acknowledge recovered crash ${reportId}`, error);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
hasValidTimestamp(crash) {
|
|
211
|
+
return typeof crash.timestamp === 'number' && Number.isFinite(crash.timestamp) && crash.timestamp > 0;
|
|
212
|
+
}
|
|
213
|
+
isWithinReplayWindow(timestampMs, replayStartedAtMs) {
|
|
214
|
+
return timestampMs <= replayStartedAtMs && replayStartedAtMs - timestampMs <= MAX_REPLAY_AGE_MS;
|
|
215
|
+
}
|
|
216
|
+
unpatch() {
|
|
217
|
+
if (this.metasListenerRegistered) {
|
|
218
|
+
this.metas.removeListener(this.recordSessionContextListener);
|
|
219
|
+
this.metasListenerRegistered = false;
|
|
220
|
+
}
|
|
221
|
+
this.nativeModule = null;
|
|
222
|
+
this.supportsSessionReplay = false;
|
|
223
|
+
this.lastRecordedSessionSignature = null;
|
|
224
|
+
this.activeSessionId = null;
|
|
225
|
+
this.activeSessionActivatedAtMs = 0;
|
|
226
|
+
this.activeSessionIsSampled = undefined;
|
|
227
|
+
super.unpatch();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=RecoveredCrashReportingInstrumentation.js.map
|
package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecoveredCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { TransportItemType } from '@grafana/faro-core';\nimport type { ExceptionEvent, Meta, TransportItem } from '@grafana/faro-core';\n\nimport { FetchTransport } from '../../transports/fetch';\n\nimport { BaseCrashReportingInstrumentation } from './BaseCrashReportingInstrumentation';\nimport type { CrashReport } from './types';\n\n// Keep aligned with FaroCrashSessionStore.MAX_CONTEXT_AGE_MS on Android.\nconst MAX_REPLAY_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype RecoveredCrashReport = CrashReport & {\n reportId: string;\n sessionId: string;\n timestamp: number;\n};\n\n/**\n * Shared recovered-crash delivery contract for native platforms.\n *\n * Native code owns crash-time persistence. JavaScript owns validation, transport\n * acceptance, and acknowledgement so retryable failures never delete a report.\n */\nexport abstract class RecoveredCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {\n private nativeModule: typeof NativeModules.FaroReactNativeModule | null = null;\n private supportsSessionReplay = false;\n private metasListenerRegistered = false;\n private lastRecordedSessionSignature: string | null = null;\n private activeSessionId: string | null = null;\n private activeSessionActivatedAtMs = 0;\n private activeSessionIsSampled: boolean | undefined;\n\n private readonly recordSessionContextListener = (meta: Meta): void => {\n this.recordSessionContext(meta);\n };\n\n protected override prepareCrashReporting(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n this.supportsSessionReplay =\n typeof nativeModule.recordCrashSessionContext === 'function' &&\n typeof nativeModule.getPendingCrashReports === 'function' &&\n typeof nativeModule.acknowledgeCrashReports === 'function';\n\n if (!this.supportsSessionReplay) {\n this.logWarn('Native module does not support original-session crash replay; using legacy crash reporting');\n return;\n }\n\n this.nativeModule = nativeModule;\n this.recordSessionContext(this.metas.value);\n if (!this.metasListenerRegistered) {\n this.metas.addListener(this.recordSessionContextListener);\n this.metasListenerRegistered = true;\n }\n }\n\n protected override async processCrashReports(\n nativeModule: typeof NativeModules.FaroReactNativeModule\n ): Promise<void> {\n if (!this.supportsSessionReplay) {\n await super.processCrashReports(nativeModule);\n return;\n }\n\n try {\n const replayStartedAtMs = Date.now();\n const crashReports = (await nativeModule.getPendingCrashReports()) as string[] | null;\n if (!crashReports?.length) {\n this.logDebug('No crash reports from previous session');\n return;\n }\n\n const processedReportIds = new Set<string>();\n for (const crashJson of crashReports) {\n let parsedCrash: unknown;\n try {\n parsedCrash = JSON.parse(crashJson);\n } catch (error) {\n this.logError('Failed to parse recovered crash report', error);\n continue;\n }\n if (!parsedCrash || typeof parsedCrash !== 'object' || Array.isArray(parsedCrash)) {\n this.logWarn('Skipping malformed recovered crash report');\n continue;\n }\n const crash = parsedCrash as CrashReport;\n\n const reportId = crash.reportId?.trim();\n if (!reportId) {\n this.logWarn('Skipping malformed recovered crash report');\n continue;\n }\n if (!this.hasValidTimestamp(crash)) {\n this.logWarn(`Discarding malformed recovered crash ${reportId}`);\n await this.acknowledgeCrashReport(nativeModule, reportId);\n continue;\n }\n if (processedReportIds.has(reportId)) {\n this.logDebug(`Skipping duplicate recovered crash report ${reportId}`);\n continue;\n }\n processedReportIds.add(reportId);\n\n if (!this.isWithinReplayWindow(crash.timestamp, replayStartedAtMs)) {\n await this.acknowledgeCrashReport(nativeModule, reportId);\n continue;\n }\n if (this.shouldSkipCrashReport(crash)) {\n await this.acknowledgeCrashReport(nativeModule, reportId);\n continue;\n }\n\n const sessionId = crash.sessionId?.trim();\n if (!sessionId) {\n this.logWarn(`Discarding recovered crash ${reportId} because its original session context is unavailable`);\n await this.acknowledgeCrashReport(nativeModule, reportId);\n continue;\n }\n\n const outcome = await this.replayCrashReport({\n ...crash,\n reportId,\n sessionId,\n });\n if (outcome === 'accepted' || outcome === 'filtered') {\n await this.acknowledgeCrashReport(nativeModule, reportId);\n }\n }\n } catch (error) {\n this.logError('Failed to process recovered crash reports', error);\n }\n }\n\n private recordSessionContext(meta: Meta): void {\n const sessionId = meta.session?.id?.trim();\n if (!sessionId || !this.nativeModule) {\n return;\n }\n\n if (this.activeSessionId !== sessionId) {\n this.activeSessionId = sessionId;\n this.activeSessionActivatedAtMs = Date.now();\n this.activeSessionIsSampled = undefined;\n }\n\n const sampledValue = meta.session?.attributes?.['isSampled'];\n const incomingIsSampled = sampledValue === 'true' ? true : sampledValue === 'false' ? false : undefined;\n if (incomingIsSampled !== undefined) {\n this.activeSessionIsSampled = incomingIsSampled;\n }\n const isSampled = incomingIsSampled ?? this.activeSessionIsSampled;\n const sessionContext = {\n sessionId,\n activatedAt: this.activeSessionActivatedAtMs,\n ...(isSampled === undefined ? {} : { isSampled }),\n ...(meta.app?.version ? { appVersion: meta.app.version } : {}),\n ...(meta.app?.release ? { appRelease: meta.app.release } : {}),\n ...(meta.app?.bundleId ? { appBundleId: meta.app.bundleId } : {}),\n };\n const signature = JSON.stringify(sessionContext);\n if (signature === this.lastRecordedSessionSignature) {\n return;\n }\n\n try {\n const stored = this.nativeModule.recordCrashSessionContext(sessionContext);\n if (stored === true) {\n this.lastRecordedSessionSignature = signature;\n }\n } catch (error) {\n this.logError('Failed to persist crash session context', error);\n }\n }\n\n private async replayCrashReport(crash: RecoveredCrashReport): Promise<'accepted' | 'filtered' | 'retry'> {\n if (this.transports.isPaused()) {\n return 'retry';\n }\n\n const fetchTransport = this.transports.transports.find(\n (transport): transport is FetchTransport => transport instanceof FetchTransport\n );\n if (!fetchTransport) {\n this.logWarn('Fetch transport is unavailable; leaving recovered crash pending');\n return 'retry';\n }\n\n // Recovered crashes intentionally bypass transport fan-out. Native storage\n // handles retries, and only FetchTransport reports collector acceptance before ack.\n let item: TransportItem = this.buildRecoveredCrashItem(crash);\n try {\n for (const hook of this.transports.getBeforeSendHooks()) {\n const modifiedItem = hook(item);\n // Match faro-core's filter(Boolean) behavior for invalid hook results.\n if (!modifiedItem) {\n return 'filtered';\n }\n item = modifiedItem;\n }\n } catch (error) {\n this.logError('Failed to apply beforeSend hook to recovered crash', error);\n return 'retry';\n }\n\n const result = await fetchTransport.sendWithResult([item]);\n return result.outcome === 'accepted' ? 'accepted' : 'retry';\n }\n\n private buildRecoveredCrashItem(crash: RecoveredCrashReport): TransportItem<ExceptionEvent> {\n const { context, errorMessage, stackFrames } = this.buildCrashReportDetails(crash);\n const currentMeta = this.metas.value;\n const isSampledAttribute = crash.isSampled === undefined ? undefined : { isSampled: String(crash.isSampled) };\n // Current user, view, and page metadata belong to the restarted session.\n const meta: Meta = {\n ...(currentMeta.sdk ? { sdk: { ...currentMeta.sdk } } : {}),\n app: {\n ...(currentMeta.app ?? {}),\n ...(crash.appVersion ? { version: crash.appVersion } : {}),\n ...(crash.appRelease ? { release: crash.appRelease } : {}),\n ...(crash.appBundleId ? { bundleId: crash.appBundleId } : {}),\n },\n ...(currentMeta.os ? { os: { ...currentMeta.os } } : {}),\n ...(currentMeta.device ? { device: { ...currentMeta.device } } : {}),\n session: {\n id: crash.sessionId,\n ...(isSampledAttribute ? { attributes: isSampledAttribute } : {}),\n },\n };\n\n return {\n type: TransportItemType.EXCEPTION,\n meta,\n payload: {\n type: 'crash',\n value: errorMessage,\n timestamp: new Date(crash.timestamp).toISOString(),\n context,\n fatal: true,\n ...(stackFrames.length ? { stacktrace: { frames: stackFrames } } : {}),\n },\n };\n }\n\n private async acknowledgeCrashReport(\n nativeModule: typeof NativeModules.FaroReactNativeModule,\n reportId: string\n ): Promise<void> {\n try {\n await nativeModule.acknowledgeCrashReports([reportId]);\n } catch (error) {\n this.logError(`Failed to acknowledge recovered crash ${reportId}`, error);\n }\n }\n\n private hasValidTimestamp(crash: CrashReport): crash is CrashReport & { timestamp: number } {\n return typeof crash.timestamp === 'number' && Number.isFinite(crash.timestamp) && crash.timestamp > 0;\n }\n\n private isWithinReplayWindow(timestampMs: number, replayStartedAtMs: number): boolean {\n return timestampMs <= replayStartedAtMs && replayStartedAtMs - timestampMs <= MAX_REPLAY_AGE_MS;\n }\n\n override unpatch(): void {\n if (this.metasListenerRegistered) {\n this.metas.removeListener(this.recordSessionContextListener);\n this.metasListenerRegistered = false;\n }\n this.nativeModule = null;\n this.supportsSessionReplay = false;\n this.lastRecordedSessionSignature = null;\n this.activeSessionId = null;\n this.activeSessionActivatedAtMs = 0;\n this.activeSessionIsSampled = undefined;\n super.unpatch();\n }\n}\n"]}
|
package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VERSION } from '@grafana/faro-core';
|
|
2
|
-
import {
|
|
2
|
+
import { RecoveredCrashReportingInstrumentation } from '../RecoveredCrashReportingInstrumentation';
|
|
3
3
|
import { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';
|
|
4
4
|
import { parseAndroidCrashTrace } from './parseAndroidCrashTrace';
|
|
5
5
|
/**
|
|
@@ -14,7 +14,7 @@ import { parseAndroidCrashTrace } from './parseAndroidCrashTrace';
|
|
|
14
14
|
*
|
|
15
15
|
* ANRs are reported separately by ANRInstrumentation, not via ApplicationExitInfo replay.
|
|
16
16
|
*/
|
|
17
|
-
export class AndroidCrashReportingInstrumentation extends
|
|
17
|
+
export class AndroidCrashReportingInstrumentation extends RecoveredCrashReportingInstrumentation {
|
|
18
18
|
constructor() {
|
|
19
19
|
super(...arguments);
|
|
20
20
|
this.name = '@grafana/faro-react-native:instrumentation-crash-android';
|
package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AndroidCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"AndroidCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,sCAAsC,EAAE,MAAM,2CAA2C,CAAC;AAGnG,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAAgC,MAAM,0BAA0B,CAAC;AAEhG;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,oCAAqC,SAAQ,sCAAsC;IAAhG;;QACW,SAAI,GAAG,0DAA0D,CAAC;QAClE,YAAO,GAAG,OAAO,CAAC;IAuC7B,CAAC;IArCW,eAAe,CAAC,KAAa;QACrC,OAAO,sBAAsB,CAAC,KAAK,EAAE;YACnC,qBAAqB,EAAE,IAAI,CAAC,OAAO,CAAC,qBAAqB;SAC1D,CAAC,CAAC;IACL,CAAC;IAES,wBAAwB,CAAC,KAAkB,EAAE,WAA2C;QAChG,OAAO,wBAAwB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IAES,qBAAqB,CAAC,KAAkB;QAChD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAES,cAAc,CAAC,WAA2C;;QAClE,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,gGAAgG;QAChG,OAAO,CAAC,GAAG,CAAC,MAAA,WAAW,CAAC,QAAQ,mCAAI,EAAE,CAAC,EAAE,GAAG,CAAC,MAAA,WAAW,CAAC,MAAM,mCAAI,EAAE,CAAC,EAAE,GAAG,CAAC,MAAA,WAAW,CAAC,YAAY,mCAAI,EAAE,CAAC,CAAC,CAAC;IAC/G,CAAC;IAES,oBAAoB,CAAC,WAA2C,EAAE,OAA+B;QACzG,yEAAyE;QACzE,gFAAgF;QAChF,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,qBAAqB,CAAC,GAAG,WAAW,CAAC,aAAa,CAAC;QAC7D,CAAC;IACH,CAAC;IAES,sBAAsB;QAC9B,OAAO,+GAA+G,CAAC;IACzH,CAAC;IAES,0BAA0B;QAClC,OAAO,8FAA8F,CAAC;IACxG,CAAC;CACF","sourcesContent":["import { VERSION } from '@grafana/faro-core';\n\nimport { RecoveredCrashReportingInstrumentation } from '../RecoveredCrashReportingInstrumentation';\nimport type { CrashReport } from '../types';\n\nimport { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';\nimport { parseAndroidCrashTrace, type ParsedAndroidCrashTrace } from './parseAndroidCrashTrace';\n\n/**\n * Android Crash Reporting Instrumentation.\n *\n * Uses ApplicationExitInfo API (Android 11+ / API 30+) to retrieve crash reports\n * from previous sessions. Captures:\n * - CRASH: Java/Kotlin exception crashes\n * - CRASH_NATIVE: Native (NDK) crashes\n * - LOW_MEMORY: App killed due to low memory\n * - EXCESSIVE_RESOURCE_USAGE: App killed due to excessive resource usage\n *\n * ANRs are reported separately by ANRInstrumentation, not via ApplicationExitInfo replay.\n */\nexport class AndroidCrashReportingInstrumentation extends RecoveredCrashReportingInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-crash-android';\n readonly version = VERSION;\n\n protected parseCrashTrace(trace: string): ParsedAndroidCrashTrace | null {\n return parseAndroidCrashTrace(trace, {\n releaseBundleFilename: this.options.releaseBundleFilename,\n });\n }\n\n protected resolveCrashErrorMessage(crash: CrashReport, parsedTrace: ParsedAndroidCrashTrace | null): string {\n return resolveCrashErrorMessage(crash, parsedTrace);\n }\n\n protected shouldSkipCrashReport(crash: CrashReport): boolean {\n return shouldSkipCrashReport(crash);\n }\n\n protected getStackFrames(parsedTrace: ParsedAndroidCrashTrace | null): any[] {\n if (!parsedTrace) {\n return [];\n }\n // Android has JS frames, Java/Kotlin frames (R8 retrace), and NDK tombstone rows (.so retrace).\n return [...(parsedTrace.jsFrames ?? []), ...(parsedTrace.frames ?? []), ...(parsedTrace.nativeFrames ?? [])];\n }\n\n protected buildPlatformContext(parsedTrace: ParsedAndroidCrashTrace | null, context: Record<string, string>): void {\n // Store the native Java/Kotlin exception class separately for drill-down\n // Plugin overview metrics key off exception_type = 'crash', so keep that stable\n if (parsedTrace?.exceptionType) {\n context['nativeExceptionType'] = parsedTrace.exceptionType;\n }\n }\n\n protected getParseFailureWarning(): string {\n return 'Native crash trace present but no frames parsed; check Android trace format or UncaughtExceptionHandler cache';\n }\n\n protected getTraceUnavailableWarning(): string {\n return 'Native crash trace unavailable (ApplicationExitInfo traceInputStream was null at crash time)';\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VERSION } from '@grafana/faro-core';
|
|
2
|
-
import {
|
|
2
|
+
import { RecoveredCrashReportingInstrumentation } from '../RecoveredCrashReportingInstrumentation';
|
|
3
3
|
import { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';
|
|
4
4
|
import { parseIosCrashTrace } from './parseIosCrashTrace';
|
|
5
5
|
/**
|
|
@@ -16,7 +16,7 @@ import { parseIosCrashTrace } from './parseIosCrashTrace';
|
|
|
16
16
|
*
|
|
17
17
|
* Stack frames are symbolicated on the native side before being surfaced.
|
|
18
18
|
*/
|
|
19
|
-
export class IosCrashReportingInstrumentation extends
|
|
19
|
+
export class IosCrashReportingInstrumentation extends RecoveredCrashReportingInstrumentation {
|
|
20
20
|
constructor() {
|
|
21
21
|
super(...arguments);
|
|
22
22
|
this.name = '@grafana/faro-react-native:instrumentation-crash-ios';
|
package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IosCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"IosCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,sCAAsC,EAAE,MAAM,2CAA2C,CAAC;AAGnG,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAA4B,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEpF;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,gCAAiC,SAAQ,sCAAsC;IAA5F;;QACW,SAAI,GAAG,sDAAsD,CAAC;QAC9D,YAAO,GAAG,OAAO,CAAC;IAqC7B,CAAC;IAnCW,eAAe,CAAC,KAAa;QACrC,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAES,wBAAwB,CAAC,KAAkB,EAAE,WAAuC;QAC5F,OAAO,wBAAwB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IAES,qBAAqB,CAAC,KAAkB;QAChD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAES,cAAc,CAAC,WAAuC;;QAC9D,OAAO,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,MAAM,mCAAI,EAAE,CAAC;IACnC,CAAC;IAES,oBAAoB,CAAC,WAAuC,EAAE,OAA+B;QACrG,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,qBAAqB,CAAC,GAAG,WAAW,CAAC,aAAa,CAAC;QAC7D,CAAC;QACD,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,UAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,YAAY,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC;QACjD,CAAC;QACD,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,iBAAiB,EAAE,CAAC;YACnC,OAAO,CAAC,mBAAmB,CAAC,GAAG,WAAW,CAAC,iBAAiB,CAAC;QAC/D,CAAC;IACH,CAAC;IAES,sBAAsB;QAC9B,OAAO,mFAAmF,CAAC;IAC7F,CAAC;IAES,0BAA0B;QAClC,OAAO,+EAA+E,CAAC;IACzF,CAAC;CACF","sourcesContent":["import { VERSION } from '@grafana/faro-core';\n\nimport { RecoveredCrashReportingInstrumentation } from '../RecoveredCrashReportingInstrumentation';\nimport type { CrashReport } from '../types';\n\nimport { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';\nimport { type ParsedIosCrashTrace, parseIosCrashTrace } from './parseIosCrashTrace';\n\n/**\n * iOS Crash Reporting Instrumentation.\n *\n * Uses PLCrashReporter to capture native crashes from previous sessions:\n * - SIGSEGV: Segmentation fault / invalid memory access\n * - SIGABRT: Abnormal termination (e.g., assertion failure, uncaught exception)\n * - SIGBUS: Bus error / alignment/hardware error\n * - SIGILL: Illegal instruction\n * - SIGFPE: Floating point exception\n * - SIGTRAP: Breakpoint/debugger trap\n * - Mach exceptions (EXC_BAD_ACCESS, EXC_CRASH, etc.)\n *\n * Stack frames are symbolicated on the native side before being surfaced.\n */\nexport class IosCrashReportingInstrumentation extends RecoveredCrashReportingInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-crash-ios';\n readonly version = VERSION;\n\n protected parseCrashTrace(trace: string): ParsedIosCrashTrace | null {\n return parseIosCrashTrace(trace);\n }\n\n protected resolveCrashErrorMessage(crash: CrashReport, parsedTrace: ParsedIosCrashTrace | null): string {\n return resolveCrashErrorMessage(crash, parsedTrace);\n }\n\n protected shouldSkipCrashReport(crash: CrashReport): boolean {\n return shouldSkipCrashReport(crash);\n }\n\n protected getStackFrames(parsedTrace: ParsedIosCrashTrace | null): any[] {\n return parsedTrace?.frames ?? [];\n }\n\n protected buildPlatformContext(parsedTrace: ParsedIosCrashTrace | null, context: Record<string, string>): void {\n if (parsedTrace?.exceptionName) {\n context['nativeExceptionName'] = parsedTrace.exceptionName;\n }\n if (parsedTrace?.signalName) {\n context['signalName'] = parsedTrace.signalName;\n }\n if (parsedTrace?.signalDescription) {\n context['signalDescription'] = parsedTrace.signalDescription;\n }\n }\n\n protected getParseFailureWarning(): string {\n return 'Native crash trace present but no frames parsed; check iOS PLCrashReporter format';\n }\n\n protected getTraceUnavailableWarning(): string {\n return 'Native crash trace unavailable (PLCrashReporter trace was null at crash time)';\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Crash report data structure returned from native module.\n */\nexport interface CrashReport {\n trace?: string;\n signal?: string;\n timestamp?: number;\n description?: string;\n processName?: string;\n pid?: number;\n importance?: number;\n // iOS-specific fields\n reason?: string; // Signal name (e.g., 'SIGSEGV')\n status?: number; // Exit status code\n}\n\n/**\n * Configuration options for crash reporting instrumentation.\n */\nexport interface CrashReportingOptions {\n /**\n * Enable or disable crash reporting. Defaults to true.\n */\n enabled?: boolean;\n\n /**\n * Optional release bundle filename hint for R8 retracing.\n * e.g., \"index.android.bundle\"\n */\n releaseBundleFilename?: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Crash report data structure returned from native module.\n */\nexport interface CrashReport {\n reportId?: string;\n trace?: string;\n signal?: string;\n timestamp?: number;\n description?: string;\n processName?: string;\n pid?: number;\n importance?: number;\n // iOS-specific fields\n reason?: string; // Signal name (e.g., 'SIGSEGV')\n status?: number; // Exit status code\n // Recovered-crash session context\n sessionId?: string;\n isSampled?: boolean;\n appVersion?: string;\n appRelease?: string;\n appBundleId?: string;\n}\n\n/**\n * Configuration options for crash reporting instrumentation.\n */\nexport interface CrashReportingOptions {\n /**\n * Enable or disable crash reporting. Defaults to true.\n */\n enabled?: boolean;\n\n /**\n * Optional release bundle filename hint for R8 retracing.\n * e.g., \"index.android.bundle\"\n */\n releaseBundleFilename?: string;\n}\n"]}
|
|
@@ -53,8 +53,13 @@ export class ErrorsInstrumentation extends BaseInstrumentation {
|
|
|
53
53
|
// Store the original error handler
|
|
54
54
|
this.originalErrorHandler = global.ErrorUtils.getGlobalHandler();
|
|
55
55
|
// Set our custom handler
|
|
56
|
-
global.ErrorUtils.setGlobalHandler((
|
|
56
|
+
global.ErrorUtils.setGlobalHandler((thrown, isFatal) => {
|
|
57
57
|
try {
|
|
58
|
+
// JS can throw any value, so normalise before the Error-shaped paths
|
|
59
|
+
// below. String() can itself throw — on a null-prototype object, or one
|
|
60
|
+
// with a throwing toString — so keep it inside the try, or finally never
|
|
61
|
+
// runs and React Native's own handler never sees the crash.
|
|
62
|
+
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
58
63
|
// Check if error should be ignored
|
|
59
64
|
if (this.shouldIgnoreError(error)) {
|
|
60
65
|
return;
|
|
@@ -71,7 +76,7 @@ export class ErrorsInstrumentation extends BaseInstrumentation {
|
|
|
71
76
|
this.api.pushError(enhancedError, {
|
|
72
77
|
type: enhancedError.name || 'Error',
|
|
73
78
|
context,
|
|
74
|
-
fatal: isFatal
|
|
79
|
+
fatal: isFatal,
|
|
75
80
|
stackFrames,
|
|
76
81
|
});
|
|
77
82
|
// Track error fingerprint for deduplication
|
|
@@ -85,7 +90,7 @@ export class ErrorsInstrumentation extends BaseInstrumentation {
|
|
|
85
90
|
finally {
|
|
86
91
|
// Always call the original handler to maintain normal error behavior
|
|
87
92
|
if (this.originalErrorHandler) {
|
|
88
|
-
this.originalErrorHandler(
|
|
93
|
+
this.originalErrorHandler(thrown, isFatal);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
});
|
|
@@ -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 /**\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 },\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 fatal: isFatal ?? false,\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 fatal: false,\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\n// Mirrors RN's ErrorHandler: a thrown value is not necessarily an Error, and\n// isFatal is always supplied by the runtime.\ntype ErrorHandlerCallback = (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((thrown: unknown, isFatal: boolean) => {\n try {\n // JS can throw any value, so normalise before the Error-shaped paths\n // below. String() can itself throw — on a null-prototype object, or one\n // with a throwing toString — so keep it inside the try, or finally never\n // runs and React Native's own handler never sees the crash.\n const error = thrown instanceof Error ? thrown : new Error(String(thrown));\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.UNCAUGHT,\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 fatal: isFatal,\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(thrown, 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 fatal: false,\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"]}
|
|
@@ -10,11 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
11
11
|
import { BaseInstrumentation, VERSION } from '@grafana/faro-core';
|
|
12
12
|
/**
|
|
13
|
-
* Default configuration values
|
|
14
|
-
*
|
|
13
|
+
* Default configuration values. Frozen frame threshold aligns with Android Vitals
|
|
14
|
+
* and OpenTelemetry Android slow rendering instrumentation (700ms).
|
|
15
15
|
*/
|
|
16
16
|
const DEFAULT_TARGET_FPS = 60;
|
|
17
|
-
const DEFAULT_FROZEN_FRAME_THRESHOLD_MS =
|
|
17
|
+
const DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 700;
|
|
18
18
|
const DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;
|
|
19
19
|
const DEFAULT_NORMALIZED_REFRESH_RATE = 60;
|
|
20
20
|
/**
|
|
@@ -31,7 +31,7 @@ const DEFAULT_NORMALIZED_REFRESH_RATE = 60;
|
|
|
31
31
|
* - Reports meaningful performance degradation that affects user experience
|
|
32
32
|
*
|
|
33
33
|
* ## Frozen Frame Detection
|
|
34
|
-
* Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default
|
|
34
|
+
* Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 700ms).
|
|
35
35
|
* These represent severe jank where the app appears unresponsive.
|
|
36
36
|
*
|
|
37
37
|
* Sends measurements via Faro API:
|
|
@@ -48,7 +48,7 @@ const DEFAULT_NORMALIZED_REFRESH_RATE = 60;
|
|
|
48
48
|
* instrumentations: [
|
|
49
49
|
* new FrameMonitoringInstrumentation({
|
|
50
50
|
* targetFps: 60,
|
|
51
|
-
* frozenFrameThresholdMs:
|
|
51
|
+
* frozenFrameThresholdMs: 700,
|
|
52
52
|
* refreshRatePollingInterval: 30000,
|
|
53
53
|
* }),
|
|
54
54
|
* ],
|
|
@@ -90,7 +90,7 @@ export class FrameMonitoringInstrumentation extends BaseInstrumentation {
|
|
|
90
90
|
}
|
|
91
91
|
// Start native frame monitoring with configuration
|
|
92
92
|
this.startNativeMonitoring(nativeModule);
|
|
93
|
-
// Set up
|
|
93
|
+
// Set up Android refresh rate events (throttled on native side); frozen/slow frames use polling
|
|
94
94
|
this.setupEventListeners(nativeModule);
|
|
95
95
|
// Set up polling for periodic metrics collection (iOS pattern)
|
|
96
96
|
this.startPolling();
|
|
@@ -118,30 +118,20 @@ export class FrameMonitoringInstrumentation extends BaseInstrumentation {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
setupEventListeners(nativeModule) {
|
|
121
|
-
// Android
|
|
122
|
-
|
|
123
|
-
if (Platform.OS === 'android') {
|
|
121
|
+
// Android: refresh rate only. Slow and frozen frames are polled (same as iOS) to avoid duplicate reports.
|
|
122
|
+
if (Platform.OS === 'android' && this.refreshRateVitalsEnabled) {
|
|
124
123
|
try {
|
|
125
124
|
this.eventEmitter = new NativeEventEmitter(nativeModule);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
this.handleFrozenFrame(data.count, data.durationMs || 0);
|
|
125
|
+
const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (...args) => {
|
|
126
|
+
const [refreshRate] = args;
|
|
127
|
+
if (typeof refreshRate !== 'number') {
|
|
128
|
+
this.logWarn('Ignoring onRefreshRate event without a numeric payload');
|
|
129
|
+
return;
|
|
134
130
|
}
|
|
131
|
+
this.handleRefreshRate(refreshRate);
|
|
135
132
|
});
|
|
136
|
-
this.eventSubscriptions.push(
|
|
137
|
-
|
|
138
|
-
if (this.refreshRateVitalsEnabled) {
|
|
139
|
-
const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (refreshRate) => {
|
|
140
|
-
this.handleRefreshRate(refreshRate);
|
|
141
|
-
});
|
|
142
|
-
this.eventSubscriptions.push(refreshRateSubscription);
|
|
143
|
-
}
|
|
144
|
-
this.logDebug('Set up Android frame event listeners');
|
|
133
|
+
this.eventSubscriptions.push(refreshRateSubscription);
|
|
134
|
+
this.logDebug('Set up Android refresh rate event listener');
|
|
145
135
|
}
|
|
146
136
|
catch (error) {
|
|
147
137
|
this.logError('Failed to set up frame event listeners', error);
|
|
@@ -149,8 +139,7 @@ export class FrameMonitoringInstrumentation extends BaseInstrumentation {
|
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
startPolling() {
|
|
152
|
-
// Both iOS and Android use polling
|
|
153
|
-
// This provides consistent behavior and avoids flooding with events
|
|
142
|
+
// Both iOS and Android use polling for slow and frozen frames (avoids duplicate reports on Android).
|
|
154
143
|
this.pollingIntervalId = setInterval(() => {
|
|
155
144
|
this.pollFrameMetrics();
|
|
156
145
|
}, this.options.refreshRatePollingInterval);
|
|
@@ -207,11 +196,7 @@ export class FrameMonitoringInstrumentation extends BaseInstrumentation {
|
|
|
207
196
|
}, { skipDedupe: true });
|
|
208
197
|
}
|
|
209
198
|
handleFrozenFrame(count, durationMs) {
|
|
210
|
-
// Only send frozen frame events if duration is greater than 0
|
|
211
|
-
// This filters out any erroneous 0ms frozen frames
|
|
212
199
|
if (durationMs > 0) {
|
|
213
|
-
// 🔍 TEMP DEBUG LOG - Remove after analysis
|
|
214
|
-
console.log(`[Faro DEBUG ${Platform.OS.toUpperCase()}] 🧊 SENDING frozen frame: count=${count}, duration=${durationMs}ms`);
|
|
215
200
|
this.api.pushMeasurement({
|
|
216
201
|
type: 'app_frozen_frame',
|
|
217
202
|
values: {
|