@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeEventEmitter, NativeModules, Platform } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { ReactNativeConfig } from '../../config/types';\n\nimport type { FrameMetrics, FrameMonitoringOptions } from './types';\n\n/**\n * Default configuration values matching Flutter SDK's hardcoded values.\n * These are React Native-specific advanced options for customization.\n */\nconst DEFAULT_TARGET_FPS = 60;\nconst DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 100;\nconst DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;\nconst DEFAULT_NORMALIZED_REFRESH_RATE = 60;\n\n/**\n * Frame Monitoring Instrumentation for React Native.\n *\n * Monitors frame rendering performance and detects slow/frozen frames.\n * Uses native CADisplayLink (iOS) and Choreographer (Android) for accurate metrics.\n *\n * ## Slow Frame Detection\n * Slow frames are detected using **event-based grouping** to report user-perceptible jank:\n * - Tracks consecutive frames below `targetFps` as a single \"slow frame event\"\n * - Only counts events lasting ≥50ms (~3 frames at 60fps) to filter noise\n * - This prevents reporting normal microsecond variations as slow frames\n * - Reports meaningful performance degradation that affects user experience\n *\n * ## Frozen Frame Detection\n * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 100ms).\n * These represent severe jank where the app appears unresponsive.\n *\n * Sends measurements via Faro API:\n * - `app_refresh_rate`: Current refresh rate in FPS\n * - `app_frames_rate`: Number of slow frame events (not individual frames)\n * - `app_frozen_frame`: Number of frozen frames (exceeding frozenFrameThresholdMs)\n *\n * @example\n * ```typescript\n * import { initializeFaro, FrameMonitoringInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new FrameMonitoringInstrumentation({\n * targetFps: 60,\n * frozenFrameThresholdMs: 100,\n * refreshRatePollingInterval: 30000,\n * }),\n * ],\n * });\n * ```\n */\nexport class FrameMonitoringInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-frame-monitoring';\n readonly version = VERSION;\n\n private readonly options: Required<FrameMonitoringOptions>;\n private pollingIntervalId: ReturnType<typeof setInterval> | null = null;\n private eventEmitter: NativeEventEmitter | null = null;\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(options: FrameMonitoringOptions = {}) {\n super();\n this.options = {\n targetFps: options.targetFps ?? DEFAULT_TARGET_FPS,\n frozenFrameThresholdMs: options.frozenFrameThresholdMs ?? DEFAULT_FROZEN_FRAME_THRESHOLD_MS,\n refreshRatePollingInterval: options.refreshRatePollingInterval ?? DEFAULT_REFRESH_RATE_POLLING_INTERVAL,\n normalizedRefreshRate: options.normalizedRefreshRate ?? DEFAULT_NORMALIZED_REFRESH_RATE,\n };\n }\n\n /**\n * Check if refresh rate vitals are enabled in Faro config.\n * Reads from faro.config.refreshRateVitals (single source of truth).\n */\n private get refreshRateVitalsEnabled(): boolean {\n // Access config via this.config which is set by BaseInstrumentation after initialization\n const config = this.config as ReactNativeConfig | undefined;\n return config?.refreshRateVitals ?? false;\n }\n\n initialize(): void {\n this.logDebug('Initializing frame monitoring instrumentation');\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for frame monitoring');\n return;\n }\n\n // Start native frame monitoring with configuration\n this.startNativeMonitoring(nativeModule);\n\n // Set up event listeners for real-time frame events (Android pattern)\n this.setupEventListeners(nativeModule);\n\n // Set up polling for periodic metrics collection (iOS pattern)\n this.startPolling();\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private startNativeMonitoring(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n try {\n if (typeof nativeModule.startFrameMonitoring === 'function') {\n nativeModule.startFrameMonitoring({\n targetFps: this.options.targetFps,\n frozenFrameThresholdMs: this.options.frozenFrameThresholdMs,\n normalizedRefreshRate: this.options.normalizedRefreshRate,\n });\n this.logDebug('Started native frame monitoring');\n }\n } catch (error) {\n this.logError('Failed to start native frame monitoring', error);\n }\n }\n\n private setupEventListeners(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n // Android uses event-based approach for frozen frames only\n // Slow frames are retrieved via polling (like iOS) to avoid flooding\n if (Platform.OS === 'android') {\n try {\n this.eventEmitter = new NativeEventEmitter(nativeModule);\n\n // Listen for frozen frame events\n const frozenFrameSubscription = this.eventEmitter.addListener(\n 'onFrozenFrame',\n (data: number | { count: number; durationMs: number }) => {\n // Support both formats: number (legacy) or object (with duration)\n if (typeof data === 'number') {\n this.handleFrozenFrame(data, 0);\n } else {\n this.handleFrozenFrame(data.count, data.durationMs || 0);\n }\n }\n );\n this.eventSubscriptions.push(frozenFrameSubscription);\n\n // Listen for refresh rate events (only when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (refreshRate: number) => {\n this.handleRefreshRate(refreshRate);\n });\n this.eventSubscriptions.push(refreshRateSubscription);\n }\n\n this.logDebug('Set up Android frame event listeners');\n } catch (error) {\n this.logError('Failed to set up frame event listeners', error);\n }\n }\n }\n\n private startPolling(): void {\n // Both iOS and Android use polling approach for slow frames\n // This provides consistent behavior and avoids flooding with events\n this.pollingIntervalId = setInterval(() => {\n this.pollFrameMetrics();\n }, this.options.refreshRatePollingInterval);\n }\n\n private async pollFrameMetrics(): Promise<void> {\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n return;\n }\n\n try {\n // Get refresh rate (only send when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n if (typeof nativeModule.getRefreshRate === 'function') {\n const refreshRate = await nativeModule.getRefreshRate();\n if (refreshRate !== null && refreshRate > 0) {\n this.handleRefreshRate(refreshRate);\n }\n }\n }\n\n // Get full frame metrics if available\n if (typeof nativeModule.getFrameMetrics === 'function') {\n const metrics = (await nativeModule.getFrameMetrics()) as FrameMetrics | null;\n if (metrics) {\n if (this.refreshRateVitalsEnabled && metrics.refreshRate > 0) {\n this.handleRefreshRate(metrics.refreshRate);\n }\n if (metrics.slowFrames > 0) {\n this.handleSlowFrames(metrics.slowFrames);\n }\n if (metrics.frozenFrames > 0) {\n this.handleFrozenFrame(metrics.frozenFrames, metrics.frozenDurationMs);\n }\n }\n }\n } catch (error) {\n this.logError('Failed to poll frame metrics', error);\n }\n }\n\n private handleRefreshRate(refreshRate: number): void {\n this.api.pushMeasurement(\n {\n type: 'app_refresh_rate',\n values: { refresh_rate: refreshRate },\n },\n { skipDedupe: true }\n );\n }\n\n private handleSlowFrames(count: number): void {\n // Note: Despite the name \"slow_frames\", this count represents slow frame EVENTS, not individual frames.\n // Each event is a period of consecutive slow frames lasting ≥50ms (~3 frames at 60fps).\n this.api.pushMeasurement(\n {\n type: 'app_frames_rate',\n values: { slow_frames: count },\n },\n { skipDedupe: true }\n );\n }\n\n private handleFrozenFrame(count: number, durationMs: number): void {\n // Only send frozen frame events if duration is greater than 0\n // This filters out any erroneous 0ms frozen frames\n if (durationMs > 0) {\n // 🔍 TEMP DEBUG LOG - Remove after analysis\n console.log(\n `[Faro DEBUG ${Platform.OS.toUpperCase()}] 🧊 SENDING frozen frame: count=${count}, duration=${durationMs}ms`\n );\n\n this.api.pushMeasurement(\n {\n type: 'app_frozen_frame',\n values: {\n frozen_frames: count,\n frozen_duration: durationMs,\n },\n },\n { skipDedupe: true }\n );\n }\n }\n\n /**\n * Clean up resources when instrumentation is disabled.\n */\n unpatch(): void {\n // Stop polling\n if (this.pollingIntervalId !== null) {\n clearInterval(this.pollingIntervalId);\n this.pollingIntervalId = null;\n }\n\n // Remove event subscriptions\n for (const subscription of this.eventSubscriptions) {\n subscription.remove();\n }\n this.eventSubscriptions = [];\n this.eventEmitter = null;\n\n // Stop native monitoring\n const nativeModule = this.getNativeModule();\n if (nativeModule && typeof nativeModule.stopFrameMonitoring === 'function') {\n try {\n nativeModule.stopFrameMonitoring();\n } catch (error) {\n this.logError('Failed to stop native frame monitoring', error);\n }\n }\n\n this.logDebug('Frame monitoring instrumentation stopped');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeEventEmitter, NativeModules, Platform } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { ReactNativeConfig } from '../../config/types';\n\nimport type { FrameMetrics, FrameMonitoringOptions } from './types';\n\n/**\n * Default configuration values. Frozen frame threshold aligns with Android Vitals\n * and OpenTelemetry Android slow rendering instrumentation (700ms).\n */\nconst DEFAULT_TARGET_FPS = 60;\nconst DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 700;\nconst DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;\nconst DEFAULT_NORMALIZED_REFRESH_RATE = 60;\n\n/**\n * Frame Monitoring Instrumentation for React Native.\n *\n * Monitors frame rendering performance and detects slow/frozen frames.\n * Uses native CADisplayLink (iOS) and Choreographer (Android) for accurate metrics.\n *\n * ## Slow Frame Detection\n * Slow frames are detected using **event-based grouping** to report user-perceptible jank:\n * - Tracks consecutive frames below `targetFps` as a single \"slow frame event\"\n * - Only counts events lasting ≥50ms (~3 frames at 60fps) to filter noise\n * - This prevents reporting normal microsecond variations as slow frames\n * - Reports meaningful performance degradation that affects user experience\n *\n * ## Frozen Frame Detection\n * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 700ms).\n * These represent severe jank where the app appears unresponsive.\n *\n * Sends measurements via Faro API:\n * - `app_refresh_rate`: Current refresh rate in FPS\n * - `app_frames_rate`: Number of slow frame events (not individual frames)\n * - `app_frozen_frame`: Number of frozen frames (exceeding frozenFrameThresholdMs)\n *\n * @example\n * ```typescript\n * import { initializeFaro, FrameMonitoringInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new FrameMonitoringInstrumentation({\n * targetFps: 60,\n * frozenFrameThresholdMs: 700,\n * refreshRatePollingInterval: 30000,\n * }),\n * ],\n * });\n * ```\n */\nexport class FrameMonitoringInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-frame-monitoring';\n readonly version = VERSION;\n\n private readonly options: Required<FrameMonitoringOptions>;\n private pollingIntervalId: ReturnType<typeof setInterval> | null = null;\n private eventEmitter: NativeEventEmitter | null = null;\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(options: FrameMonitoringOptions = {}) {\n super();\n this.options = {\n targetFps: options.targetFps ?? DEFAULT_TARGET_FPS,\n frozenFrameThresholdMs: options.frozenFrameThresholdMs ?? DEFAULT_FROZEN_FRAME_THRESHOLD_MS,\n refreshRatePollingInterval: options.refreshRatePollingInterval ?? DEFAULT_REFRESH_RATE_POLLING_INTERVAL,\n normalizedRefreshRate: options.normalizedRefreshRate ?? DEFAULT_NORMALIZED_REFRESH_RATE,\n };\n }\n\n /**\n * Check if refresh rate vitals are enabled in Faro config.\n * Reads from faro.config.refreshRateVitals (single source of truth).\n */\n private get refreshRateVitalsEnabled(): boolean {\n // Access config via this.config which is set by BaseInstrumentation after initialization\n const config = this.config as ReactNativeConfig | undefined;\n return config?.refreshRateVitals ?? false;\n }\n\n initialize(): void {\n this.logDebug('Initializing frame monitoring instrumentation');\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for frame monitoring');\n return;\n }\n\n // Start native frame monitoring with configuration\n this.startNativeMonitoring(nativeModule);\n\n // Set up Android refresh rate events (throttled on native side); frozen/slow frames use polling\n this.setupEventListeners(nativeModule);\n\n // Set up polling for periodic metrics collection (iOS pattern)\n this.startPolling();\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private startNativeMonitoring(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n try {\n if (typeof nativeModule.startFrameMonitoring === 'function') {\n nativeModule.startFrameMonitoring({\n targetFps: this.options.targetFps,\n frozenFrameThresholdMs: this.options.frozenFrameThresholdMs,\n normalizedRefreshRate: this.options.normalizedRefreshRate,\n });\n this.logDebug('Started native frame monitoring');\n }\n } catch (error) {\n this.logError('Failed to start native frame monitoring', error);\n }\n }\n\n private setupEventListeners(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n // Android: refresh rate only. Slow and frozen frames are polled (same as iOS) to avoid duplicate reports.\n if (Platform.OS === 'android' && this.refreshRateVitalsEnabled) {\n try {\n this.eventEmitter = new NativeEventEmitter(nativeModule);\n\n const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (...args: readonly Object[]) => {\n const [refreshRate] = args;\n\n if (typeof refreshRate !== 'number') {\n this.logWarn('Ignoring onRefreshRate event without a numeric payload');\n return;\n }\n\n this.handleRefreshRate(refreshRate);\n });\n this.eventSubscriptions.push(refreshRateSubscription);\n\n this.logDebug('Set up Android refresh rate event listener');\n } catch (error) {\n this.logError('Failed to set up frame event listeners', error);\n }\n }\n }\n\n private startPolling(): void {\n // Both iOS and Android use polling for slow and frozen frames (avoids duplicate reports on Android).\n this.pollingIntervalId = setInterval(() => {\n this.pollFrameMetrics();\n }, this.options.refreshRatePollingInterval);\n }\n\n private async pollFrameMetrics(): Promise<void> {\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n return;\n }\n\n try {\n // Get refresh rate (only send when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n if (typeof nativeModule.getRefreshRate === 'function') {\n const refreshRate = await nativeModule.getRefreshRate();\n if (refreshRate !== null && refreshRate > 0) {\n this.handleRefreshRate(refreshRate);\n }\n }\n }\n\n // Get full frame metrics if available\n if (typeof nativeModule.getFrameMetrics === 'function') {\n const metrics = (await nativeModule.getFrameMetrics()) as FrameMetrics | null;\n if (metrics) {\n if (this.refreshRateVitalsEnabled && metrics.refreshRate > 0) {\n this.handleRefreshRate(metrics.refreshRate);\n }\n if (metrics.slowFrames > 0) {\n this.handleSlowFrames(metrics.slowFrames);\n }\n if (metrics.frozenFrames > 0) {\n this.handleFrozenFrame(metrics.frozenFrames, metrics.frozenDurationMs);\n }\n }\n }\n } catch (error) {\n this.logError('Failed to poll frame metrics', error);\n }\n }\n\n private handleRefreshRate(refreshRate: number): void {\n this.api.pushMeasurement(\n {\n type: 'app_refresh_rate',\n values: { refresh_rate: refreshRate },\n },\n { skipDedupe: true }\n );\n }\n\n private handleSlowFrames(count: number): void {\n // Note: Despite the name \"slow_frames\", this count represents slow frame EVENTS, not individual frames.\n // Each event is a period of consecutive slow frames lasting ≥50ms (~3 frames at 60fps).\n this.api.pushMeasurement(\n {\n type: 'app_frames_rate',\n values: { slow_frames: count },\n },\n { skipDedupe: true }\n );\n }\n\n private handleFrozenFrame(count: number, durationMs: number): void {\n if (durationMs > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_frozen_frame',\n values: {\n frozen_frames: count,\n frozen_duration: durationMs,\n },\n },\n { skipDedupe: true }\n );\n }\n }\n\n /**\n * Clean up resources when instrumentation is disabled.\n */\n unpatch(): void {\n // Stop polling\n if (this.pollingIntervalId !== null) {\n clearInterval(this.pollingIntervalId);\n this.pollingIntervalId = null;\n }\n\n // Remove event subscriptions\n for (const subscription of this.eventSubscriptions) {\n subscription.remove();\n }\n this.eventSubscriptions = [];\n this.eventEmitter = null;\n\n // Stop native monitoring\n const nativeModule = this.getNativeModule();\n if (nativeModule && typeof nativeModule.stopFrameMonitoring === 'function') {\n try {\n nativeModule.stopFrameMonitoring();\n } catch (error) {\n this.logError('Failed to stop native frame monitoring', error);\n }\n }\n\n this.logDebug('Frame monitoring instrumentation stopped');\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration options for Frame Monitoring instrumentation.\n *\n *
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration options for Frame Monitoring instrumentation.\n *\n * Frozen frame threshold (700ms).\n *\n * Note: To enable frame monitoring, set `refreshRateVitals: true` in ReactNativeConfig.\n */\nexport interface FrameMonitoringOptions {\n /**\n * Target frames per second for slow frame detection.\n * Frames rendering below this threshold are considered \"slow\".\n * Default: 60\n */\n targetFps?: number;\n\n /**\n * Threshold in milliseconds for detecting frozen frames.\n * Frames taking longer than this are considered \"frozen\".\n * Default: 700ms (Android Vitals and OpenTelemetry Android slow rendering instrumentation)\n */\n frozenFrameThresholdMs?: number;\n\n /**\n * Interval in milliseconds for collecting and sending frame metrics.\n * Default: 30000 (30 seconds, aligned with fetchVitalsInterval)\n */\n refreshRatePollingInterval?: number;\n\n /**\n * Normalized refresh rate for ProMotion displays (120Hz, etc.).\n * Used to normalize frame rates for high-refresh-rate displays to a standard baseline.\n * Default: 60\n */\n normalizedRefreshRate?: number;\n}\n\n/**\n * Frame metrics collected by the instrumentation.\n */\nexport interface FrameMetrics {\n /**\n * Current refresh rate in FPS.\n */\n refreshRate: number;\n\n /**\n * Number of slow frames since last measurement.\n */\n slowFrames: number;\n\n /**\n * Number of frozen frames since last measurement.\n */\n frozenFrames: number;\n\n /**\n * Total duration of frozen frames in milliseconds.\n */\n frozenDurationMs: number;\n}\n\n/**\n * Native module methods for frame monitoring.\n */\nexport interface FrameMonitoringNativeModule {\n /**\n * Start frame monitoring.\n */\n startFrameMonitoring(config: {\n targetFps: number;\n frozenFrameThresholdMs: number;\n normalizedRefreshRate: number;\n }): void;\n\n /**\n * Stop frame monitoring.\n */\n stopFrameMonitoring(): void;\n\n /**\n * Get current frame metrics.\n */\n getFrameMetrics(): Promise<FrameMetrics | null>;\n\n /**\n * Get current refresh rate.\n */\n getRefreshRate(): Promise<number | null>;\n}\n"]}
|
|
@@ -8,8 +8,6 @@ const { FaroReactNativeModule } = NativeModules;
|
|
|
8
8
|
* - iOS: task_info() for memory, host_statistics() for CPU
|
|
9
9
|
* - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU
|
|
10
10
|
*
|
|
11
|
-
* Implementation ported from Faro Flutter SDK with feature parity.
|
|
12
|
-
*
|
|
13
11
|
* **Key Features**:
|
|
14
12
|
* - ✅ NO manual setup required - OS tracks metrics automatically!
|
|
15
13
|
* - ✅ Periodic collection (default: every 30 seconds)
|
|
@@ -119,15 +117,13 @@ export class PerformanceInstrumentation extends BaseInstrumentation {
|
|
|
119
117
|
}
|
|
120
118
|
const cpuUsage = FaroReactNativeModule.getCpuUsage();
|
|
121
119
|
this.logDebug(`CPU: raw value = ${cpuUsage}`);
|
|
122
|
-
// Validate CPU usage
|
|
123
|
-
// Skip null, negative, or exactly 0 (baseline reading)
|
|
120
|
+
// Validate CPU usage. Skip null, negative, or exactly 0 (baseline reading).
|
|
124
121
|
if (cpuUsage == null || cpuUsage <= 0) {
|
|
125
122
|
this.logDebug('CPU: skipping - value is null or <= 0');
|
|
126
123
|
return;
|
|
127
124
|
}
|
|
128
125
|
this.logDebug(`CPU: pushing measurement with cpu_usage = ${cpuUsage}`);
|
|
129
|
-
//
|
|
130
|
-
// in multi-core scenarios where one core is maxed out
|
|
126
|
+
// Values >= 100 can be valid in multi-core scenarios where one core is maxed out.
|
|
131
127
|
this.api.pushMeasurement({
|
|
132
128
|
type: 'app_cpu_usage',
|
|
133
129
|
values: { cpu_usage: cpuUsage },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/performance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAIlE,MAAM,EAAE,qBAAqB,EAAE,GAAG,aAAa,CAAC;AAEhD
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/performance/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { PerformanceInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app performance metrics (CPU and Memory usage)\n *\n * Collects periodic performance metrics using native OS APIs:\n * - iOS: task_info() for memory, host_statistics() for CPU\n * - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU\n *\n * **Key Features**:\n * - ✅ NO manual setup required - OS tracks metrics automatically!\n * - ✅ Periodic collection (default: every 30 seconds)\n * - ✅ Configurable per-metric enable/disable\n * - ✅ Differential CPU calculation (accurate usage percentages)\n * - ✅ Memory usage in KB (Resident Set Size)\n *\n * **Metrics Captured**:\n * - `mem_usage`: Current memory usage in KB (RSS - physical memory)\n * - `cpu_usage`: Current CPU usage percentage (0-100+)\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 21+ for CPU (Android 5.0 Lollipop, ~99% of devices)\n * - Android any version for Memory\n *\n * @example\n * ```tsx\n * import { initializeFaro, getRNInstrumentations } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://your-collector.com',\n * instrumentations: [\n * ...getRNInstrumentations({\n * memoryUsageVitals: true, // default: true\n * cpuUsageVitals: true, // default: true\n * fetchVitalsInterval: 30000, // default: 30s\n * }),\n * ],\n * });\n * ```\n */\nexport class PerformanceInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-performance';\n readonly version = VERSION;\n\n private options: Required<PerformanceInstrumentationOptions>;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: PerformanceInstrumentationOptions = {}) {\n super();\n this.options = {\n memoryUsageVitals: options.memoryUsageVitals ?? true,\n cpuUsageVitals: options.cpuUsageVitals ?? true,\n fetchVitalsInterval: options.fetchVitalsInterval ?? 30000,\n };\n }\n\n initialize(): void {\n // Only start if at least one metric is enabled\n if (!this.options.memoryUsageVitals && !this.options.cpuUsageVitals) {\n this.logInfo('Performance monitoring disabled - no metrics enabled');\n return;\n }\n\n // Check native module availability\n if (!FaroReactNativeModule) {\n this.logWarn(\n 'Native module not available. Performance instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n // Start periodic collection\n this.startPeriodicCollection();\n }\n\n private startPeriodicCollection(): void {\n // Collect immediately on initialization\n this.collectMetrics();\n\n // Then collect periodically\n this.intervalId = setInterval(() => {\n this.collectMetrics();\n }, this.options.fetchVitalsInterval);\n\n this.logInfo(\n `Performance monitoring started - collecting every ${this.options.fetchVitalsInterval}ms ` +\n `(memory: ${this.options.memoryUsageVitals}, cpu: ${this.options.cpuUsageVitals})`\n );\n }\n\n private collectMetrics(): void {\n // Collect memory if enabled\n if (this.options.memoryUsageVitals) {\n this.collectMemoryUsage();\n }\n\n // Collect CPU if enabled\n if (this.options.cpuUsageVitals) {\n this.collectCpuUsage();\n }\n }\n\n private collectMemoryUsage(): void {\n try {\n if (!FaroReactNativeModule?.getMemoryUsage) {\n this.logDebug('Memory: getMemoryUsage not available on native module');\n return;\n }\n\n const memoryUsage = FaroReactNativeModule.getMemoryUsage();\n if (memoryUsage == null || memoryUsage <= 0) {\n this.logDebug('Memory: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`Memory: pushing measurement with mem_usage = ${memoryUsage}`);\n this.api.pushMeasurement({\n type: 'app_memory',\n values: { mem_usage: memoryUsage },\n });\n } catch (error) {\n this.logError('Failed to collect memory usage', error);\n }\n }\n\n private collectCpuUsage(): void {\n try {\n if (!FaroReactNativeModule?.getCpuUsage) {\n this.logDebug('CPU: getCpuUsage not available on native module');\n return;\n }\n\n const cpuUsage = FaroReactNativeModule.getCpuUsage();\n this.logDebug(`CPU: raw value = ${cpuUsage}`);\n\n // Validate CPU usage. Skip null, negative, or exactly 0 (baseline reading).\n if (cpuUsage == null || cpuUsage <= 0) {\n this.logDebug('CPU: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`CPU: pushing measurement with cpu_usage = ${cpuUsage}`);\n // Values >= 100 can be valid in multi-core scenarios where one core is maxed out.\n this.api.pushMeasurement({\n type: 'app_cpu_usage',\n values: { cpu_usage: cpuUsage },\n });\n } catch (error) {\n this.logError('Failed to collect CPU usage', error);\n }\n }\n\n unpatch(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n this.logInfo('Performance monitoring stopped');\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { useCallback } from 'react';
|
|
14
|
+
import { StyleSheet, View } from 'react-native';
|
|
15
|
+
import { notifySessionActivity } from './directSessionActivity';
|
|
16
|
+
/**
|
|
17
|
+
* Records touch activity for the React Native subtree without emitting a user
|
|
18
|
+
* action or taking ownership of the responder.
|
|
19
|
+
*/
|
|
20
|
+
export function FaroSessionActivityBoundary(_a) {
|
|
21
|
+
var { children, onStartShouldSetResponderCapture, style } = _a, viewProps = __rest(_a, ["children", "onStartShouldSetResponderCapture", "style"]);
|
|
22
|
+
const handleStartShouldSetResponderCapture = useCallback((event) => {
|
|
23
|
+
var _a;
|
|
24
|
+
notifySessionActivity();
|
|
25
|
+
return (_a = onStartShouldSetResponderCapture === null || onStartShouldSetResponderCapture === void 0 ? void 0 : onStartShouldSetResponderCapture(event)) !== null && _a !== void 0 ? _a : false;
|
|
26
|
+
}, [onStartShouldSetResponderCapture]);
|
|
27
|
+
return (_jsx(View, Object.assign({}, viewProps, { onStartShouldSetResponderCapture: handleStartShouldSetResponderCapture, style: [styles.container, style], children: children })));
|
|
28
|
+
}
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
container: {
|
|
31
|
+
flex: 1,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=FaroSessionActivityBoundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaroSessionActivityBoundary.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/FaroSessionActivityBoundary.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAA0B,WAAW,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAA8B,UAAU,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AAE5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAIhE;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,EAKT;QALS,EAC1C,QAAQ,EACR,gCAAgC,EAChC,KAAK,OAE4B,EAD9B,SAAS,cAJ8B,yDAK3C,CADa;IAEZ,MAAM,oCAAoC,GAAG,WAAW,CACtD,CAAC,KAA4B,EAAW,EAAE;;QACxC,qBAAqB,EAAE,CAAC;QACxB,OAAO,MAAA,gCAAgC,aAAhC,gCAAgC,uBAAhC,gCAAgC,CAAG,KAAK,CAAC,mCAAI,KAAK,CAAC;IAC5D,CAAC,EACD,CAAC,gCAAgC,CAAC,CACnC,CAAC;IAEF,OAAO,CACL,KAAC,IAAI,oBACC,SAAS,IACb,gCAAgC,EAAE,oCAAoC,EACtE,KAAK,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,YAE/B,QAAQ,IACJ,CACR,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,SAAS,EAAE;QACT,IAAI,EAAE,CAAC;KACR;CACF,CAAC,CAAC","sourcesContent":["import { type PropsWithChildren, useCallback } from 'react';\nimport { type GestureResponderEvent, StyleSheet, View, type ViewProps } from 'react-native';\n\nimport { notifySessionActivity } from './directSessionActivity';\n\nexport type FaroSessionActivityBoundaryProps = PropsWithChildren<ViewProps>;\n\n/**\n * Records touch activity for the React Native subtree without emitting a user\n * action or taking ownership of the responder.\n */\nexport function FaroSessionActivityBoundary({\n children,\n onStartShouldSetResponderCapture,\n style,\n ...viewProps\n}: FaroSessionActivityBoundaryProps) {\n const handleStartShouldSetResponderCapture = useCallback(\n (event: GestureResponderEvent): boolean => {\n notifySessionActivity();\n return onStartShouldSetResponderCapture?.(event) ?? false;\n },\n [onStartShouldSetResponderCapture]\n );\n\n return (\n <View\n {...viewProps}\n onStartShouldSetResponderCapture={handleStartShouldSetResponderCapture}\n style={[styles.container, style]}\n >\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n },\n});\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { faro } from '@grafana/faro-core';
|
|
2
|
+
let directSessionActivityHandler;
|
|
3
|
+
/** Clears a handler left by a previous SDK initialization. */
|
|
4
|
+
export function clearDirectSessionActivityHandler() {
|
|
5
|
+
directSessionActivityHandler = undefined;
|
|
6
|
+
}
|
|
7
|
+
/** Registers the active session manager as the direct-activity consumer. */
|
|
8
|
+
export function registerDirectSessionActivityHandler(handler) {
|
|
9
|
+
directSessionActivityHandler = handler;
|
|
10
|
+
return () => {
|
|
11
|
+
if (directSessionActivityHandler === handler) {
|
|
12
|
+
directSessionActivityHandler = undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Refreshes session inactivity without emitting telemetry.
|
|
18
|
+
*
|
|
19
|
+
* This is a no-op until session tracking has initialized. Use it for direct
|
|
20
|
+
* interactions that do not pass through `FaroSessionActivityBoundary`.
|
|
21
|
+
*/
|
|
22
|
+
export function notifySessionActivity() {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
try {
|
|
25
|
+
directSessionActivityHandler === null || directSessionActivityHandler === void 0 ? void 0 : directSessionActivityHandler();
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// Session tracking must never interrupt the interaction being observed.
|
|
29
|
+
(_b = (_a = faro.unpatchedConsole) === null || _a === void 0 ? void 0 : _a.warn) === null || _b === void 0 ? void 0 : _b.call(_a, 'Failed to record session activity:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=directSessionActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directSessionActivity.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/directSessionActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAI1C,IAAI,4BAAsE,CAAC;AAE3E,8DAA8D;AAC9D,MAAM,UAAU,iCAAiC;IAC/C,4BAA4B,GAAG,SAAS,CAAC;AAC3C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,oCAAoC,CAAC,OAAqC;IACxF,4BAA4B,GAAG,OAAO,CAAC;IAEvC,OAAO,GAAG,EAAE;QACV,IAAI,4BAA4B,KAAK,OAAO,EAAE,CAAC;YAC7C,4BAA4B,GAAG,SAAS,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;;IACnC,IAAI,CAAC;QACH,4BAA4B,aAA5B,4BAA4B,uBAA5B,4BAA4B,EAAI,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,MAAA,MAAA,IAAI,CAAC,gBAAgB,0CAAE,IAAI,mDAAG,oCAAoC,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC","sourcesContent":["import { faro } from '@grafana/faro-core';\n\ntype DirectSessionActivityHandler = () => void;\n\nlet directSessionActivityHandler: DirectSessionActivityHandler | undefined;\n\n/** Clears a handler left by a previous SDK initialization. */\nexport function clearDirectSessionActivityHandler(): void {\n directSessionActivityHandler = undefined;\n}\n\n/** Registers the active session manager as the direct-activity consumer. */\nexport function registerDirectSessionActivityHandler(handler: DirectSessionActivityHandler): () => void {\n directSessionActivityHandler = handler;\n\n return () => {\n if (directSessionActivityHandler === handler) {\n directSessionActivityHandler = undefined;\n }\n };\n}\n\n/**\n * Refreshes session inactivity without emitting telemetry.\n *\n * This is a no-op until session tracking has initialized. Use it for direct\n * interactions that do not pass through `FaroSessionActivityBoundary`.\n */\nexport function notifySessionActivity(): void {\n try {\n directSessionActivityHandler?.();\n } catch (error) {\n // Session tracking must never interrupt the interaction being observed.\n faro.unpatchedConsole?.warn?.('Failed to record session activity:', error);\n }\n}\n"]}
|
|
@@ -1,8 +1,69 @@
|
|
|
1
|
-
import { BaseInstrumentation, dateNow, EVENT_SESSION_START,
|
|
1
|
+
import { BaseInstrumentation, dateNow, EVENT_SESSION_START, faro, VERSION } from '@grafana/faro-core';
|
|
2
|
+
import { clearDirectSessionActivityHandler, registerDirectSessionActivityHandler } from './directSessionActivity';
|
|
3
|
+
import { classifySessionActivity, isRecoveredCrashItem, SessionActivityKind } from './sessionActivity';
|
|
2
4
|
import { minimalSessionDeviceAttributes } from './sessionAttributes';
|
|
3
5
|
import { getSessionManagerByConfig, isSampled } from './sessionManager';
|
|
4
6
|
import { MAX_SESSION_PERSISTENCE_TIME } from './sessionManager/sessionConstants';
|
|
5
7
|
import { createUserSessionObject, isUserSessionValid } from './sessionManager/sessionManagerUtils';
|
|
8
|
+
const SESSION_INSTRUMENTATION_NAME = '@grafana/faro-react-native:instrumentation-session';
|
|
9
|
+
// Track the current session hook per transport so superseded hooks yield to a
|
|
10
|
+
// replacement. The shared fallback preserves sampling decisions captured on
|
|
11
|
+
// queued items after the owning instrumentation has detached.
|
|
12
|
+
const activeSessionBeforeSendHooks = new WeakMap();
|
|
13
|
+
const sessionSamplingFallbackHooks = new WeakMap();
|
|
14
|
+
function applySessionSampling(item, fallbackSamplingDecision) {
|
|
15
|
+
var _a, _b, _c, _d;
|
|
16
|
+
const attributes = (_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.attributes;
|
|
17
|
+
const samplingAttribute = (_b = attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) !== null && _b !== void 0 ? _b : fallbackSamplingDecision === null || fallbackSamplingDecision === void 0 ? void 0 : fallbackSamplingDecision.toString();
|
|
18
|
+
if (samplingAttribute === 'false') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (samplingAttribute !== 'true') {
|
|
22
|
+
return item;
|
|
23
|
+
}
|
|
24
|
+
const sampledItem = Object.assign(Object.assign({}, item), { meta: Object.assign(Object.assign({}, item.meta), { session: item.meta.session
|
|
25
|
+
? Object.assign(Object.assign({}, item.meta.session), { attributes: item.meta.session.attributes ? Object.assign({}, item.meta.session.attributes) : undefined }) : undefined }) });
|
|
26
|
+
const sampledAttributes = (_c = sampledItem.meta.session) === null || _c === void 0 ? void 0 : _c.attributes;
|
|
27
|
+
sampledAttributes === null || sampledAttributes === void 0 ? true : delete sampledAttributes['isSampled'];
|
|
28
|
+
if (Object.keys(sampledAttributes !== null && sampledAttributes !== void 0 ? sampledAttributes : {}).length === 0) {
|
|
29
|
+
(_d = sampledItem.meta.session) === null || _d === void 0 ? true : delete _d.attributes;
|
|
30
|
+
}
|
|
31
|
+
return sampledItem;
|
|
32
|
+
}
|
|
33
|
+
function ensureSessionSamplingFallback(transports) {
|
|
34
|
+
if (sessionSamplingFallbackHooks.has(transports)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const fallbackHook = (item) => activeSessionBeforeSendHooks.has(transports) ? item : applySessionSampling(item);
|
|
38
|
+
transports.addBeforeSendHooks(fallbackHook);
|
|
39
|
+
sessionSamplingFallbackHooks.set(transports, fallbackHook);
|
|
40
|
+
}
|
|
41
|
+
function removeInternalSamplingAttribute(session) {
|
|
42
|
+
var _a;
|
|
43
|
+
if (((_a = session === null || session === void 0 ? void 0 : session.attributes) === null || _a === void 0 ? void 0 : _a['isSampled']) == null) {
|
|
44
|
+
return session;
|
|
45
|
+
}
|
|
46
|
+
const attributes = Object.assign({}, session.attributes);
|
|
47
|
+
delete attributes['isSampled'];
|
|
48
|
+
return Object.assign(Object.assign({}, session), { attributes: Object.keys(attributes).length === 0 ? undefined : attributes });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Starts a new linked session at an application-defined boundary.
|
|
52
|
+
*
|
|
53
|
+
* Call this after changing or clearing the current user for logout, account
|
|
54
|
+
* changes, or another boundary that must not share a session. Calls made
|
|
55
|
+
* before Faro initializes, after teardown, or while session tracking is
|
|
56
|
+
* disabled have no effect.
|
|
57
|
+
*/
|
|
58
|
+
export function startNewSession() {
|
|
59
|
+
var _a;
|
|
60
|
+
const instrumentation = (_a = faro === null || faro === void 0 ? void 0 : faro.instrumentations) === null || _a === void 0 ? void 0 : _a.instrumentations.find(({ name }) => name === SESSION_INSTRUMENTATION_NAME);
|
|
61
|
+
if (instrumentation != null &&
|
|
62
|
+
'startNewSession' in instrumentation &&
|
|
63
|
+
typeof instrumentation.startNewSession === 'function') {
|
|
64
|
+
instrumentation.startNewSession();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
6
67
|
/**
|
|
7
68
|
* Session instrumentation for React Native
|
|
8
69
|
* Manages persistent or volatile sessions with expiration and inactivity tracking
|
|
@@ -10,8 +71,19 @@ import { createUserSessionObject, isUserSessionValid } from './sessionManager/se
|
|
|
10
71
|
export class SessionInstrumentation extends BaseInstrumentation {
|
|
11
72
|
constructor() {
|
|
12
73
|
super(...arguments);
|
|
13
|
-
this.name =
|
|
74
|
+
this.name = SESSION_INSTRUMENTATION_NAME;
|
|
14
75
|
this.version = VERSION;
|
|
76
|
+
this.isResettingSession = false;
|
|
77
|
+
this.sendSessionStartEvent = (meta) => {
|
|
78
|
+
var _a;
|
|
79
|
+
const session = meta.session;
|
|
80
|
+
if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
81
|
+
this.notifiedSession = session;
|
|
82
|
+
// no need to add attributes and session id, they are included as part of meta
|
|
83
|
+
// automatically
|
|
84
|
+
this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
15
87
|
}
|
|
16
88
|
getDefaultSessionDeviceAttributes() {
|
|
17
89
|
const cfg = this.config;
|
|
@@ -20,24 +92,20 @@ export class SessionInstrumentation extends BaseInstrumentation {
|
|
|
20
92
|
}
|
|
21
93
|
return minimalSessionDeviceAttributes();
|
|
22
94
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
27
|
-
this.notifiedSession = session;
|
|
28
|
-
// no need to add attributes and session id, they are included as part of meta
|
|
29
|
-
// automatically
|
|
30
|
-
this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
|
|
31
|
-
}
|
|
95
|
+
registerSessionMetaListener() {
|
|
96
|
+
this.metas.addListener(this.sendSessionStartEvent);
|
|
97
|
+
this.sessionMetaListenerMetas = this.metas;
|
|
32
98
|
}
|
|
33
99
|
createInitialSession(SessionManagerClass, sessionsConfig) {
|
|
34
|
-
var _a, _b, _c, _d, _e, _f
|
|
100
|
+
var _a, _b, _c, _d, _e, _f;
|
|
35
101
|
let storedUserSession = SessionManagerClass.fetchUserSession();
|
|
36
102
|
const sessionsConfigTyped = sessionsConfig;
|
|
37
103
|
const maxPersistenceMs = (_a = sessionsConfigTyped.maxSessionPersistenceTime) !== null && _a !== void 0 ? _a : MAX_SESSION_PERSISTENCE_TIME;
|
|
38
104
|
if (sessionsConfig.persistent && storedUserSession) {
|
|
39
105
|
const now = dateNow();
|
|
40
|
-
const shouldClearPersistentSession = storedUserSession.
|
|
106
|
+
const shouldClearPersistentSession = storedUserSession.started > now ||
|
|
107
|
+
storedUserSession.lastActivity > now ||
|
|
108
|
+
storedUserSession.lastActivity <= now - maxPersistenceMs;
|
|
41
109
|
if (shouldClearPersistentSession) {
|
|
42
110
|
SessionManagerClass.removeUserSession();
|
|
43
111
|
storedUserSession = null;
|
|
@@ -46,7 +114,7 @@ export class SessionInstrumentation extends BaseInstrumentation {
|
|
|
46
114
|
const defaultAttributes = this.getDefaultSessionDeviceAttributes();
|
|
47
115
|
let emitSessionStartOnInit;
|
|
48
116
|
let initialSession;
|
|
49
|
-
if (isUserSessionValid(storedUserSession)) {
|
|
117
|
+
if (!sessionsConfig.persistent && isUserSessionValid(storedUserSession)) {
|
|
50
118
|
const sessionId = storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId;
|
|
51
119
|
initialSession = createUserSessionObject({
|
|
52
120
|
sessionId,
|
|
@@ -63,71 +131,164 @@ export class SessionInstrumentation extends BaseInstrumentation {
|
|
|
63
131
|
emitSessionStartOnInit = false;
|
|
64
132
|
}
|
|
65
133
|
else {
|
|
66
|
-
const sessionId = (_e = (_d = sessionsConfig.session) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : genShortID();
|
|
67
134
|
initialSession = createUserSessionObject({
|
|
68
|
-
sessionId,
|
|
135
|
+
sessionId: (_d = sessionsConfig.session) === null || _d === void 0 ? void 0 : _d.id,
|
|
69
136
|
isSampled: isSampled(),
|
|
70
137
|
});
|
|
71
|
-
const
|
|
72
|
-
|
|
138
|
+
const sessionId = initialSession.sessionId;
|
|
139
|
+
const previousSessionId = (storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId) === sessionId ? undefined : storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId;
|
|
140
|
+
const overrides = (_e = sessionsConfig.session) === null || _e === void 0 ? void 0 : _e.overrides;
|
|
141
|
+
initialSession.sessionMeta = Object.assign({ id: sessionId, attributes: Object.assign(Object.assign(Object.assign(Object.assign({}, (_f = sessionsConfig.session) === null || _f === void 0 ? void 0 : _f.attributes), defaultAttributes), { isSampled: initialSession.isSampled.toString() }), (previousSessionId == null ? {} : { previousSession: previousSessionId })) }, (overrides ? { overrides } : {}));
|
|
73
142
|
emitSessionStartOnInit = true;
|
|
74
143
|
}
|
|
75
144
|
return { initialSession, emitSessionStartOnInit };
|
|
76
145
|
}
|
|
77
146
|
registerBeforeSendHook(SessionManagerClass) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
var _a
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// If isSampled='true', remove the attribute before sending (it's internal)
|
|
86
|
-
// If no isSampled attribute, pass through the item unchanged
|
|
87
|
-
if ((attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) === 'false') {
|
|
88
|
-
// Session is not sampled - drop this item
|
|
89
|
-
return null;
|
|
147
|
+
const registeredTransports = this.transports;
|
|
148
|
+
ensureSessionSamplingFallback(registeredTransports);
|
|
149
|
+
const beforeSendHook = (item) => {
|
|
150
|
+
var _a;
|
|
151
|
+
const activeBeforeSendHook = activeSessionBeforeSendHooks.get(registeredTransports);
|
|
152
|
+
if (activeBeforeSendHook != null && activeBeforeSendHook !== beforeSendHook) {
|
|
153
|
+
return item;
|
|
90
154
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
155
|
+
let nextItem = item;
|
|
156
|
+
let fallbackSamplingDecision;
|
|
157
|
+
const sessionManager = this.beforeSendHook === beforeSendHook ? this.sessionManagerInstance : undefined;
|
|
158
|
+
if (sessionManager != null) {
|
|
159
|
+
const storedSession = SessionManagerClass.fetchUserSession();
|
|
160
|
+
const recoveredCrash = isRecoveredCrashItem(item, storedSession === null || storedSession === void 0 ? void 0 : storedSession.sessionId);
|
|
161
|
+
// Ending an active action can send telemetry synchronously. Keep that
|
|
162
|
+
// telemetry on the current session until the explicit reset completes.
|
|
163
|
+
const checkedSession = recoveredCrash
|
|
164
|
+
? null
|
|
165
|
+
: this.isResettingSession
|
|
166
|
+
? storedSession
|
|
167
|
+
: sessionManager.checkSession(classifySessionActivity(item), storedSession);
|
|
168
|
+
if ((checkedSession === null || checkedSession === void 0 ? void 0 : checkedSession.sessionMeta) != null && ((_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.id) !== checkedSession.sessionId) {
|
|
169
|
+
nextItem = Object.assign(Object.assign({}, item), { meta: Object.assign(Object.assign({}, item.meta), { session: checkedSession.sessionMeta }) });
|
|
98
170
|
}
|
|
99
|
-
|
|
171
|
+
// New crash records carry the crash-time decision. Older records may not,
|
|
172
|
+
// so retain the previous behavior and use the live session's stable
|
|
173
|
+
// decision rather than implicitly sampling every recovered crash.
|
|
174
|
+
fallbackSamplingDecision = recoveredCrash ? storedSession === null || storedSession === void 0 ? void 0 : storedSession.isSampled : checkedSession === null || checkedSession === void 0 ? void 0 : checkedSession.isSampled;
|
|
100
175
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
return applySessionSampling(nextItem, fallbackSamplingDecision);
|
|
177
|
+
};
|
|
178
|
+
this.beforeSendHook = beforeSendHook;
|
|
179
|
+
this.beforeSendHookApi = this.api;
|
|
180
|
+
this.beforeSendHookTransports = registeredTransports;
|
|
181
|
+
registeredTransports.addBeforeSendHooks(beforeSendHook);
|
|
182
|
+
activeSessionBeforeSendHooks.set(registeredTransports, beforeSendHook);
|
|
104
183
|
}
|
|
105
184
|
initialize() {
|
|
185
|
+
// BaseInstrumentation instances can be registered more than once. Detach
|
|
186
|
+
// every resource from the previous Faro instance before binding the next.
|
|
187
|
+
this.unpatch();
|
|
106
188
|
const sessionTrackingConfig = this.config.sessionTracking;
|
|
189
|
+
// A new initialization owns direct-activity routing, including when
|
|
190
|
+
// session tracking is disabled.
|
|
191
|
+
clearDirectSessionActivityHandler();
|
|
107
192
|
if (!(sessionTrackingConfig === null || sessionTrackingConfig === void 0 ? void 0 : sessionTrackingConfig.enabled)) {
|
|
108
|
-
this.
|
|
193
|
+
this.registerSessionMetaListener();
|
|
109
194
|
return;
|
|
110
195
|
}
|
|
111
196
|
const SessionManagerClass = getSessionManagerByConfig(sessionTrackingConfig);
|
|
197
|
+
// Constructing a manager registers an AppState subscription and a metas
|
|
198
|
+
// listener, so exactly one instance is created and reused. It is also the
|
|
199
|
+
// instance `unpatch()` cleans up.
|
|
200
|
+
this.sessionManagerInstance = new SessionManagerClass(this.metas);
|
|
112
201
|
this.registerBeforeSendHook(SessionManagerClass);
|
|
113
202
|
const { initialSession, emitSessionStartOnInit } = this.createInitialSession(SessionManagerClass, sessionTrackingConfig);
|
|
114
203
|
SessionManagerClass.storeUserSession(initialSession);
|
|
115
204
|
const initialSessionMeta = initialSession.sessionMeta;
|
|
116
205
|
this.notifiedSession = initialSessionMeta;
|
|
117
206
|
this.api.setSession(initialSessionMeta);
|
|
118
|
-
this.sessionManagerInstance = new SessionManagerClass();
|
|
119
207
|
if (emitSessionStartOnInit) {
|
|
120
208
|
this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
|
|
121
209
|
}
|
|
122
|
-
this.
|
|
210
|
+
this.unregisterDirectSessionActivity = registerDirectSessionActivityHandler(() => {
|
|
211
|
+
var _a;
|
|
212
|
+
(_a = this.sessionManagerInstance) === null || _a === void 0 ? void 0 : _a.checkSession(SessionActivityKind.Meaningful);
|
|
213
|
+
});
|
|
214
|
+
this.registerSessionMetaListener();
|
|
215
|
+
}
|
|
216
|
+
/** Starts a new linked session immediately. */
|
|
217
|
+
startNewSession() {
|
|
218
|
+
var _a, _b;
|
|
219
|
+
if (this.sessionManagerInstance == null || this.isResettingSession) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.isResettingSession = true;
|
|
223
|
+
try {
|
|
224
|
+
try {
|
|
225
|
+
const activeAction = (_b = (_a = this.api).getActiveUserAction) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
226
|
+
activeAction === null || activeAction === void 0 ? void 0 : activeAction.end();
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
this.logWarn('Failed to end the active user action before resetting the session:', error);
|
|
230
|
+
}
|
|
231
|
+
this.sessionManagerInstance.resetSession();
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
this.isResettingSession = false;
|
|
235
|
+
}
|
|
123
236
|
}
|
|
124
237
|
/**
|
|
125
238
|
* Clean up session manager listeners
|
|
126
239
|
*/
|
|
127
240
|
unpatch() {
|
|
128
|
-
|
|
129
|
-
|
|
241
|
+
var _a, _b;
|
|
242
|
+
(_a = this.unregisterDirectSessionActivity) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
243
|
+
this.unregisterDirectSessionActivity = undefined;
|
|
244
|
+
const beforeSendHook = this.beforeSendHook;
|
|
245
|
+
const beforeSendHookApi = this.beforeSendHookApi;
|
|
246
|
+
const beforeSendHookTransports = this.beforeSendHookTransports;
|
|
247
|
+
const ownedActiveHook = beforeSendHook != null &&
|
|
248
|
+
beforeSendHookTransports != null &&
|
|
249
|
+
activeSessionBeforeSendHooks.get(beforeSendHookTransports) === beforeSendHook;
|
|
250
|
+
// Stop the manager's metas synchronization before removing the internal
|
|
251
|
+
// sampling marker. Keep the session hook active during that update so any
|
|
252
|
+
// surviving metas listener that emits telemetry is still attributed and
|
|
253
|
+
// filtered with the current session.
|
|
254
|
+
(_b = this.sessionManagerInstance) === null || _b === void 0 ? void 0 : _b.unpatch();
|
|
255
|
+
try {
|
|
256
|
+
if (ownedActiveHook && beforeSendHookApi) {
|
|
257
|
+
const session = beforeSendHookApi.getSession();
|
|
258
|
+
const sessionWithoutSamplingAttribute = removeInternalSamplingAttribute(session);
|
|
259
|
+
if (sessionWithoutSamplingAttribute !== session) {
|
|
260
|
+
this.isResettingSession = true;
|
|
261
|
+
try {
|
|
262
|
+
beforeSendHookApi.setSession(sessionWithoutSamplingAttribute);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
this.isResettingSession = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
this.beforeSendHook = undefined;
|
|
272
|
+
this.beforeSendHookApi = undefined;
|
|
273
|
+
this.beforeSendHookTransports = undefined;
|
|
274
|
+
if (beforeSendHook && beforeSendHookTransports) {
|
|
275
|
+
if (ownedActiveHook) {
|
|
276
|
+
activeSessionBeforeSendHooks.delete(beforeSendHookTransports);
|
|
277
|
+
}
|
|
278
|
+
beforeSendHookTransports.removeBeforeSendHooks(beforeSendHook);
|
|
279
|
+
}
|
|
280
|
+
this.sessionManagerInstance = undefined;
|
|
281
|
+
const sessionMetaListenerMetas = this.sessionMetaListenerMetas;
|
|
282
|
+
this.sessionMetaListenerMetas = undefined;
|
|
283
|
+
if (sessionMetaListenerMetas) {
|
|
284
|
+
sessionMetaListenerMetas.removeListener(this.sendSessionStartEvent);
|
|
285
|
+
}
|
|
286
|
+
this.notifiedSession = undefined;
|
|
287
|
+
this.isResettingSession = false;
|
|
130
288
|
}
|
|
131
289
|
}
|
|
290
|
+
destroy() {
|
|
291
|
+
this.unpatch();
|
|
292
|
+
}
|
|
132
293
|
}
|
|
133
294
|
//# sourceMappingURL=index.js.map
|