@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
|
@@ -3,7 +3,7 @@ import type { CachedPayload, OfflineCache } from './types';
|
|
|
3
3
|
* File-based offline cache implementation using AsyncStorage.
|
|
4
4
|
*
|
|
5
5
|
* Stores cached telemetry payloads as JSONL (one JSON object per line)
|
|
6
|
-
*
|
|
6
|
+
* Persistent offline telemetry cache backed by AsyncStorage.
|
|
7
7
|
*
|
|
8
8
|
* Uses a mutex pattern for thread-safe file access.
|
|
9
9
|
*/
|
|
@@ -22,7 +22,7 @@ export declare class AsyncStorageOfflineCache implements OfflineCache {
|
|
|
22
22
|
getCount(): Promise<number>;
|
|
23
23
|
/**
|
|
24
24
|
* Execute an operation with a mutex lock to ensure thread-safe access.
|
|
25
|
-
*
|
|
25
|
+
* Async lock to serialize cache read/write operations.
|
|
26
26
|
*/
|
|
27
27
|
private withLock;
|
|
28
28
|
private readAllInternal;
|
|
@@ -8,8 +8,7 @@ import type { OfflineTransportOptions } from './types';
|
|
|
8
8
|
* When the device is offline, telemetry is cached to AsyncStorage.
|
|
9
9
|
* When connectivity is restored, cached telemetry is replayed through the wrapped transports.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* - Uses AsyncStorage for persistent caching (matching Flutter's SharedPreferences)
|
|
11
|
+
* Uses AsyncStorage for persistent offline telemetry caching.
|
|
13
12
|
* - Respects maxCacheDuration to skip expired entries
|
|
14
13
|
* - Uses mutex pattern for thread-safe cache access
|
|
15
14
|
* - Excludes itself when replaying to prevent infinite loops
|
|
@@ -72,12 +71,12 @@ export declare class OfflineTransport extends BaseTransport {
|
|
|
72
71
|
dispose(): void;
|
|
73
72
|
/**
|
|
74
73
|
* Replay cached payloads through other transports.
|
|
75
|
-
*
|
|
74
|
+
* Reads cached telemetry from AsyncStorage.
|
|
76
75
|
*/
|
|
77
76
|
private replayCachedPayloads;
|
|
78
77
|
/**
|
|
79
78
|
* Send items to all other registered transports.
|
|
80
|
-
*
|
|
79
|
+
* Sends cached telemetry when connectivity is restored.
|
|
81
80
|
*/
|
|
82
81
|
private sendToOtherTransports;
|
|
83
82
|
}
|
|
@@ -6,8 +6,7 @@ import type { UserPersistence, UserPersistenceOptions } from './types';
|
|
|
6
6
|
* Stores user information persistently so it can be restored on app restart.
|
|
7
7
|
* This ensures early telemetry events include user identification.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)
|
|
9
|
+
* Uses AsyncStorage for persistent user data across app sessions.
|
|
11
10
|
* - Stores user as JSON
|
|
12
11
|
* - Handles null/cleared users
|
|
13
12
|
*
|
|
@@ -21,6 +21,17 @@ public class FaroCrashReporter: NSObject {
|
|
|
21
21
|
|
|
22
22
|
private static var crashReporter: PLCrashReporter?
|
|
23
23
|
private static var isEnabled = false
|
|
24
|
+
private static let stateLock = NSLock()
|
|
25
|
+
// activatedAt keeps the persisted context aligned with Android. iOS does not
|
|
26
|
+
// use it to match a pending PLCrashReporter report.
|
|
27
|
+
private static let sessionContextKeys = [
|
|
28
|
+
"sessionId",
|
|
29
|
+
"activatedAt",
|
|
30
|
+
"isSampled",
|
|
31
|
+
"appVersion",
|
|
32
|
+
"appRelease",
|
|
33
|
+
"appBundleId"
|
|
34
|
+
]
|
|
24
35
|
|
|
25
36
|
/// Signal descriptions for human-readable crash messages.
|
|
26
37
|
private static let signalDescriptions: [String: String] = [
|
|
@@ -50,6 +61,12 @@ public class FaroCrashReporter: NSObject {
|
|
|
50
61
|
///
|
|
51
62
|
/// - Returns: true if successfully enabled, false otherwise
|
|
52
63
|
@objc public static func enable() -> Bool {
|
|
64
|
+
stateLock.lock()
|
|
65
|
+
defer { stateLock.unlock() }
|
|
66
|
+
return enableLocked()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private static func enableLocked() -> Bool {
|
|
53
70
|
guard !isEnabled else {
|
|
54
71
|
return true
|
|
55
72
|
}
|
|
@@ -90,53 +107,145 @@ public class FaroCrashReporter: NSObject {
|
|
|
90
107
|
}
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
///
|
|
110
|
+
/// Records the active Faro session in PLCrashReporter's crash-safe custom data.
|
|
94
111
|
///
|
|
95
|
-
///
|
|
96
|
-
///
|
|
97
|
-
///
|
|
112
|
+
/// PLCrashReporter copies this data into the report at crash time, allowing a
|
|
113
|
+
/// recovered crash to retain its original session after the app restarts.
|
|
114
|
+
/// Keep this payload minimal because it is embedded in every crash report.
|
|
115
|
+
///
|
|
116
|
+
/// - Parameter sessionContext: Sanitized session and app fields from JavaScript
|
|
117
|
+
/// - Returns: true when the context was stored, false when it was invalid
|
|
118
|
+
@objc public static func recordSessionContext(_ sessionContext: [String: Any]) -> Bool {
|
|
119
|
+
let sanitizedContext = sanitizeSessionContext(sessionContext)
|
|
120
|
+
guard sanitizedContext["sessionId"] != nil,
|
|
121
|
+
JSONSerialization.isValidJSONObject(sanitizedContext) else {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
do {
|
|
126
|
+
let data = try JSONSerialization.data(withJSONObject: sanitizedContext)
|
|
127
|
+
stateLock.lock()
|
|
128
|
+
defer { stateLock.unlock() }
|
|
129
|
+
guard enableLocked(), let reporter = crashReporter else {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
reporter.customData = data
|
|
133
|
+
return true
|
|
134
|
+
} catch {
|
|
135
|
+
print("[FaroCrashReporter] Failed to serialize crash session context: \(error)")
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Gets crash reports from previous sessions without deleting them.
|
|
141
|
+
///
|
|
142
|
+
/// JavaScript acknowledges a report only after successful delivery or an
|
|
143
|
+
/// intentional local discard.
|
|
98
144
|
///
|
|
99
145
|
/// - Returns: Array of crash report JSON strings, or nil if no crashes
|
|
100
|
-
@objc public static func
|
|
101
|
-
|
|
102
|
-
guard enable() else {
|
|
146
|
+
@objc public static func getPendingCrashReports() -> [String]? {
|
|
147
|
+
guard let reporter = enabledReporter(), reporter.hasPendingCrashReport() else {
|
|
103
148
|
return nil
|
|
104
149
|
}
|
|
105
150
|
|
|
106
|
-
|
|
151
|
+
do {
|
|
152
|
+
let data = try reporter.loadPendingCrashReportDataAndReturnError()
|
|
153
|
+
let reportId = reportIdentifier(for: data)
|
|
154
|
+
|
|
155
|
+
do {
|
|
156
|
+
let plcrReport = try PLCrashReport(data: data)
|
|
157
|
+
let crashReport = try FaroCrashReport(from: plcrReport)
|
|
158
|
+
if let json = exportCrashReportAsJSON(crashReport, reportId: reportId) {
|
|
159
|
+
return [json]
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
print("[FaroCrashReporter] Failed to parse pending crash report: \(error)")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Return a stable marker so JavaScript can acknowledge an unreadable
|
|
166
|
+
// report rather than assigning it to the new session or retrying forever.
|
|
167
|
+
return [malformedReportJSON(reportId: reportId)]
|
|
168
|
+
} catch {
|
|
169
|
+
print("[FaroCrashReporter] Failed to load pending crash report: \(error)")
|
|
170
|
+
_ = purgeUnreadablePendingReport(reporter)
|
|
107
171
|
return nil
|
|
108
172
|
}
|
|
173
|
+
}
|
|
109
174
|
|
|
175
|
+
/// Purges the pending report only when its stable ID was acknowledged.
|
|
176
|
+
///
|
|
177
|
+
/// - Parameter reportIds: Stable IDs handled by JavaScript
|
|
178
|
+
/// - Returns: true when no matching report remains or the matching report was purged
|
|
179
|
+
@objc public static func acknowledgeCrashReports(_ reportIds: [String]) -> Bool {
|
|
180
|
+
guard let reporter = enabledReporter() else {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
110
183
|
guard reporter.hasPendingCrashReport() else {
|
|
111
|
-
return
|
|
184
|
+
return true
|
|
112
185
|
}
|
|
113
186
|
|
|
114
|
-
|
|
187
|
+
let acknowledgedIds = Set(
|
|
188
|
+
reportIds
|
|
189
|
+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
190
|
+
.filter { !$0.isEmpty }
|
|
191
|
+
)
|
|
192
|
+
guard !acknowledgedIds.isEmpty else {
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
115
195
|
|
|
116
196
|
do {
|
|
117
197
|
let data = try reporter.loadPendingCrashReportDataAndReturnError()
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Convert to JSON matching Android format
|
|
122
|
-
if let jsonString = exportCrashReportAsJSON(crashReport) {
|
|
123
|
-
crashReports.append(jsonString)
|
|
198
|
+
guard acknowledgedIds.contains(reportIdentifier(for: data)) else {
|
|
199
|
+
return true
|
|
124
200
|
}
|
|
201
|
+
return reporter.purgePendingCrashReport()
|
|
125
202
|
} catch {
|
|
126
|
-
print("[FaroCrashReporter] Failed to
|
|
203
|
+
print("[FaroCrashReporter] Failed to acknowledge pending crash report: \(error)")
|
|
204
|
+
return purgeUnreadablePendingReport(reporter)
|
|
127
205
|
}
|
|
206
|
+
}
|
|
128
207
|
|
|
129
|
-
|
|
130
|
-
|
|
208
|
+
/// Gets crash reports from previous sessions as JSON strings.
|
|
209
|
+
///
|
|
210
|
+
/// Returns an array of JSON strings, each representing a crash report.
|
|
211
|
+
/// The JSON format matches the Android implementation for consistency.
|
|
212
|
+
/// This legacy API eagerly purges returned reports for older JavaScript bundles
|
|
213
|
+
/// that cannot acknowledge delivery.
|
|
214
|
+
///
|
|
215
|
+
/// - Returns: Array of crash report JSON strings, or nil if no crashes
|
|
216
|
+
@objc public static func getCrashReports() -> [String]? {
|
|
217
|
+
guard let crashReports = getPendingCrashReports() else {
|
|
218
|
+
return nil
|
|
219
|
+
}
|
|
131
220
|
|
|
132
|
-
|
|
221
|
+
let reportIds = crashReports.compactMap(reportIdentifier(from:))
|
|
222
|
+
_ = acknowledgeCrashReports(reportIds)
|
|
223
|
+
return crashReports
|
|
133
224
|
}
|
|
134
225
|
|
|
135
226
|
// MARK: - Private Helpers
|
|
136
227
|
|
|
228
|
+
private static func enabledReporter() -> PLCrashReporter? {
|
|
229
|
+
stateLock.lock()
|
|
230
|
+
defer { stateLock.unlock() }
|
|
231
|
+
guard enableLocked() else {
|
|
232
|
+
return nil
|
|
233
|
+
}
|
|
234
|
+
return crashReporter
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private static func purgeUnreadablePendingReport(_ reporter: PLCrashReporter) -> Bool {
|
|
238
|
+
let purged = reporter.purgePendingCrashReport()
|
|
239
|
+
if !purged {
|
|
240
|
+
print("[FaroCrashReporter] Failed to purge unreadable pending crash report")
|
|
241
|
+
}
|
|
242
|
+
return purged
|
|
243
|
+
}
|
|
244
|
+
|
|
137
245
|
/// Converts a FaroCrashReport to JSON string matching the Android format.
|
|
138
|
-
private static func exportCrashReportAsJSON(_ crashReport: FaroCrashReport) -> String? {
|
|
246
|
+
private static func exportCrashReportAsJSON(_ crashReport: FaroCrashReport, reportId: String) -> String? {
|
|
139
247
|
var json: [String: Any] = [:]
|
|
248
|
+
json["reportId"] = reportId
|
|
140
249
|
|
|
141
250
|
// Reason - matches Android's "reason" field
|
|
142
251
|
// For iOS, we use the signal name as the reason
|
|
@@ -176,6 +285,14 @@ public class FaroCrashReporter: NSObject {
|
|
|
176
285
|
json["incidentId"] = incidentId
|
|
177
286
|
}
|
|
178
287
|
|
|
288
|
+
if let sessionContext = decodeSessionContext(crashReport.contextData) {
|
|
289
|
+
for key in sessionContextKeys {
|
|
290
|
+
if let value = sessionContext[key] {
|
|
291
|
+
json[key] = value
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
179
296
|
// Convert to JSON string
|
|
180
297
|
do {
|
|
181
298
|
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
|
|
@@ -186,6 +303,82 @@ public class FaroCrashReporter: NSObject {
|
|
|
186
303
|
}
|
|
187
304
|
}
|
|
188
305
|
|
|
306
|
+
private static func sanitizeSessionContext(_ context: [String: Any]) -> [String: Any] {
|
|
307
|
+
var sanitized: [String: Any] = [:]
|
|
308
|
+
|
|
309
|
+
if let sessionId = nonEmptyString(context["sessionId"]) {
|
|
310
|
+
sanitized["sessionId"] = sessionId
|
|
311
|
+
}
|
|
312
|
+
// Android uses activatedAt to map a crash to persisted session history.
|
|
313
|
+
// iOS keeps it in the shared context shape, although PLCrashReporter
|
|
314
|
+
// already snapshots the one active context directly into the report.
|
|
315
|
+
if let activatedAt = context["activatedAt"] as? NSNumber,
|
|
316
|
+
activatedAt.doubleValue.isFinite,
|
|
317
|
+
activatedAt.doubleValue > 0 {
|
|
318
|
+
sanitized["activatedAt"] = activatedAt
|
|
319
|
+
}
|
|
320
|
+
if let isSampled = context["isSampled"] as? NSNumber {
|
|
321
|
+
sanitized["isSampled"] = isSampled.boolValue
|
|
322
|
+
}
|
|
323
|
+
for key in ["appVersion", "appRelease", "appBundleId"] {
|
|
324
|
+
if let value = nonEmptyString(context[key]) {
|
|
325
|
+
sanitized[key] = value
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return sanitized
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private static func decodeSessionContext(_ data: Data?) -> [String: Any]? {
|
|
333
|
+
guard let data else {
|
|
334
|
+
return nil
|
|
335
|
+
}
|
|
336
|
+
do {
|
|
337
|
+
guard let context = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
338
|
+
nonEmptyString(context["sessionId"]) != nil else {
|
|
339
|
+
return nil
|
|
340
|
+
}
|
|
341
|
+
return sanitizeSessionContext(context)
|
|
342
|
+
} catch {
|
|
343
|
+
print("[FaroCrashReporter] Failed to decode crash session context: \(error)")
|
|
344
|
+
return nil
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private static func nonEmptyString(_ value: Any?) -> String? {
|
|
349
|
+
guard let string = value as? String else {
|
|
350
|
+
return nil
|
|
351
|
+
}
|
|
352
|
+
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
353
|
+
return trimmed.isEmpty ? nil : trimmed
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private static func malformedReportJSON(reportId: String) -> String {
|
|
357
|
+
let data = try? JSONSerialization.data(withJSONObject: ["reportId": reportId])
|
|
358
|
+
return data.flatMap { String(data: $0, encoding: .utf8) } ?? "{\"reportId\":\"\(reportId)\"}"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private static func reportIdentifier(from crashReportJSON: String) -> String? {
|
|
362
|
+
guard let data = crashReportJSON.data(using: .utf8),
|
|
363
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
364
|
+
return nil
|
|
365
|
+
}
|
|
366
|
+
return nonEmptyString(json["reportId"])
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private static func reportIdentifier(for data: Data) -> String {
|
|
370
|
+
// PLCrashReporter stores a single immutable report. A deterministic hash
|
|
371
|
+
// gives both retrieval and acknowledgement the same stable opaque ID,
|
|
372
|
+
// including when the report itself cannot be parsed.
|
|
373
|
+
var hash: UInt64 = 14_695_981_039_346_656_037
|
|
374
|
+
for byte in data {
|
|
375
|
+
hash ^= UInt64(byte)
|
|
376
|
+
hash = hash &* 1_099_511_628_211
|
|
377
|
+
}
|
|
378
|
+
let hex = String(hash, radix: 16)
|
|
379
|
+
return "ios-\(String(repeating: "0", count: max(0, 16 - hex.count)))\(hex)"
|
|
380
|
+
}
|
|
381
|
+
|
|
189
382
|
/// Creates a human-readable description of the crash.
|
|
190
383
|
///
|
|
191
384
|
/// This returns just the descriptive portion. The TypeScript layer formats the
|
|
@@ -20,6 +20,15 @@ import Foundation
|
|
|
20
20
|
/// but include improvements where applicable (e.g., better memory metric).
|
|
21
21
|
@objc(FaroReactNative)
|
|
22
22
|
public class FaroReactNative: NSObject {
|
|
23
|
+
/// Stable bundle identity used to isolate persisted session chains.
|
|
24
|
+
@objc public static func getSessionProcessIdentifier() -> String {
|
|
25
|
+
return Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// App extensions have an NSExtension dictionary in their main bundle.
|
|
29
|
+
@objc public static func isMainSessionProcess() -> Bool {
|
|
30
|
+
return Bundle.main.object(forInfoDictionaryKey: "NSExtension") == nil
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
// MARK: - App Startup Time
|
|
25
34
|
|
|
@@ -172,4 +181,19 @@ public class FaroReactNative: NSObject {
|
|
|
172
181
|
@objc public static func getCrashReports() -> [String]? {
|
|
173
182
|
return FaroCrashReporter.getCrashReports()
|
|
174
183
|
}
|
|
184
|
+
|
|
185
|
+
/// Records the active Faro session in PLCrashReporter's custom crash data.
|
|
186
|
+
@objc public static func recordCrashSessionContext(_ sessionContext: [String: Any]) -> Bool {
|
|
187
|
+
return FaroCrashReporter.recordSessionContext(sessionContext)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Gets pending crash reports without deleting them.
|
|
191
|
+
@objc public static func getPendingCrashReports() -> [String]? {
|
|
192
|
+
return FaroCrashReporter.getPendingCrashReports()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Purges only the pending report whose stable ID was acknowledged.
|
|
196
|
+
@objc public static func acknowledgeCrashReports(_ reportIds: [String]) -> Bool {
|
|
197
|
+
return FaroCrashReporter.acknowledgeCrashReports(reportIds)
|
|
198
|
+
}
|
|
175
199
|
}
|
|
@@ -2,10 +2,81 @@
|
|
|
2
2
|
#import <React/RCTBridgeModule.h>
|
|
3
3
|
#import "FaroReactNative-Swift.h"
|
|
4
4
|
|
|
5
|
+
static NSLock *FaroSessionPersistenceLock(void)
|
|
6
|
+
{
|
|
7
|
+
static NSLock *lock;
|
|
8
|
+
static dispatch_once_t onceToken;
|
|
9
|
+
dispatch_once(&onceToken, ^{
|
|
10
|
+
lock = [[NSLock alloc] init];
|
|
11
|
+
});
|
|
12
|
+
return lock;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Weak ownership also clears the claim if teardown skips `invalidate`.
|
|
16
|
+
static __weak FaroReactNativeModule *FaroSessionPersistenceOwner;
|
|
17
|
+
|
|
18
|
+
@interface FaroReactNativeModule ()
|
|
19
|
+
- (BOOL)claimSessionPersistenceOwnership;
|
|
20
|
+
- (BOOL)releaseSessionPersistenceOwnership;
|
|
21
|
+
@end
|
|
22
|
+
|
|
5
23
|
@implementation FaroReactNativeModule
|
|
6
24
|
|
|
7
25
|
RCT_EXPORT_MODULE(FaroReactNativeModule)
|
|
8
26
|
|
|
27
|
+
/// Stable process identity used to isolate persisted session chains.
|
|
28
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getSessionProcessIdentifier)
|
|
29
|
+
{
|
|
30
|
+
return [FaroReactNative getSessionProcessIdentifier];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Whether this runtime belongs to the host application's main process.
|
|
34
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isMainSessionProcess)
|
|
35
|
+
{
|
|
36
|
+
return @([FaroReactNative isMainSessionProcess]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Allows only one React Native runtime in a process to persist sessions.
|
|
40
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(claimSessionPersistence)
|
|
41
|
+
{
|
|
42
|
+
return @([self claimSessionPersistenceOwnership]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Releases persistence only when this native module instance owns it.
|
|
46
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(releaseSessionPersistence)
|
|
47
|
+
{
|
|
48
|
+
return @([self releaseSessionPersistenceOwnership]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
- (BOOL)claimSessionPersistenceOwnership
|
|
52
|
+
{
|
|
53
|
+
NSLock *lock = FaroSessionPersistenceLock();
|
|
54
|
+
[lock lock];
|
|
55
|
+
BOOL claimed = FaroSessionPersistenceOwner == nil || FaroSessionPersistenceOwner == self;
|
|
56
|
+
if (claimed) {
|
|
57
|
+
FaroSessionPersistenceOwner = self;
|
|
58
|
+
}
|
|
59
|
+
[lock unlock];
|
|
60
|
+
return claimed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (BOOL)releaseSessionPersistenceOwnership
|
|
64
|
+
{
|
|
65
|
+
NSLock *lock = FaroSessionPersistenceLock();
|
|
66
|
+
[lock lock];
|
|
67
|
+
BOOL released = FaroSessionPersistenceOwner == self;
|
|
68
|
+
if (released) {
|
|
69
|
+
FaroSessionPersistenceOwner = nil;
|
|
70
|
+
}
|
|
71
|
+
[lock unlock];
|
|
72
|
+
return released;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
- (void)invalidate
|
|
76
|
+
{
|
|
77
|
+
[self releaseSessionPersistenceOwnership];
|
|
78
|
+
}
|
|
79
|
+
|
|
9
80
|
/// Synchronous method for immediate access from JavaScript
|
|
10
81
|
/// Returns app startup duration in milliseconds from process start to current time
|
|
11
82
|
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getAppStartDuration)
|
|
@@ -34,9 +105,9 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCpuUsage)
|
|
|
34
105
|
/// @param config Dictionary with targetFps, frozenFrameThresholdMs, normalizedRefreshRate
|
|
35
106
|
RCT_EXPORT_METHOD(startFrameMonitoring:(NSDictionary *)config)
|
|
36
107
|
{
|
|
37
|
-
double targetFps = [[config objectForKey:@"targetFps"] doubleValue] ?:
|
|
38
|
-
double frozenFrameThresholdMs = [[config objectForKey:@"frozenFrameThresholdMs"] doubleValue] ?:
|
|
39
|
-
double normalizedRefreshRate = [[config objectForKey:@"normalizedRefreshRate"] doubleValue] ?:
|
|
108
|
+
double targetFps = [[config objectForKey:@"targetFps"] doubleValue] ?: RefreshRateVitals.defaultTargetFps;
|
|
109
|
+
double frozenFrameThresholdMs = [[config objectForKey:@"frozenFrameThresholdMs"] doubleValue] ?: RefreshRateVitals.defaultFrozenFrameThresholdMs;
|
|
110
|
+
double normalizedRefreshRate = [[config objectForKey:@"normalizedRefreshRate"] doubleValue] ?: RefreshRateVitals.defaultNormalizedRefreshRate;
|
|
40
111
|
|
|
41
112
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
42
113
|
[[RefreshRateVitals shared] configureWithTargetFps:targetFps
|
|
@@ -79,12 +150,13 @@ RCT_EXPORT_METHOD(getFrameMetrics:(RCTPromiseResolveBlock)resolve
|
|
|
79
150
|
{
|
|
80
151
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
81
152
|
RefreshRateVitals *vitals = [RefreshRateVitals shared];
|
|
82
|
-
|
|
153
|
+
NSDictionary *frozenMetrics = [vitals getAndResetFrozenMetrics];
|
|
154
|
+
|
|
83
155
|
NSDictionary *metrics = @{
|
|
84
156
|
@"refreshRate": @([vitals getRefreshRate]),
|
|
85
157
|
@"slowFrames": @([vitals getAndResetSlowFrames]),
|
|
86
|
-
@"frozenFrames": @
|
|
87
|
-
@"frozenDurationMs": @
|
|
158
|
+
@"frozenFrames": frozenMetrics[@"count"],
|
|
159
|
+
@"frozenDurationMs": frozenMetrics[@"durationMs"]
|
|
88
160
|
};
|
|
89
161
|
|
|
90
162
|
resolve(metrics);
|
|
@@ -121,4 +193,47 @@ RCT_EXPORT_METHOD(getCrashReport:(RCTPromiseResolveBlock)resolve
|
|
|
121
193
|
}
|
|
122
194
|
}
|
|
123
195
|
|
|
196
|
+
/// Store the active session synchronously so it is available if the process crashes.
|
|
197
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(recordCrashSessionContext:(NSDictionary *)sessionContext)
|
|
198
|
+
{
|
|
199
|
+
return @([FaroReactNative recordCrashSessionContext:sessionContext]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Get pending crash reports without deleting them.
|
|
203
|
+
RCT_EXPORT_METHOD(getPendingCrashReports:(RCTPromiseResolveBlock)resolve
|
|
204
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
205
|
+
{
|
|
206
|
+
NSArray<NSString *> *crashReports = [FaroReactNative getPendingCrashReports];
|
|
207
|
+
if (crashReports != nil && crashReports.count > 0) {
|
|
208
|
+
resolve(crashReports);
|
|
209
|
+
} else {
|
|
210
|
+
resolve([NSNull null]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Delete only crash reports that JavaScript has finished handling.
|
|
215
|
+
RCT_EXPORT_METHOD(acknowledgeCrashReports:(NSArray *)reportIds
|
|
216
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
217
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
218
|
+
{
|
|
219
|
+
NSMutableArray<NSString *> *sanitizedReportIds = [NSMutableArray array];
|
|
220
|
+
for (id reportId in reportIds) {
|
|
221
|
+
if (![reportId isKindOfClass:[NSString class]]) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
NSString *trimmedReportId = [(NSString *)reportId stringByTrimmingCharactersInSet:
|
|
226
|
+
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
227
|
+
if (trimmedReportId.length > 0) {
|
|
228
|
+
[sanitizedReportIds addObject:trimmedReportId];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if ([FaroReactNative acknowledgeCrashReports:sanitizedReportIds]) {
|
|
233
|
+
resolve([NSNull null]);
|
|
234
|
+
} else {
|
|
235
|
+
reject(@"E_CRASH_ACK_FAILED", @"Failed to acknowledge recovered crash report", nil);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
124
239
|
@end
|