@grafana/faro-react-native 1.3.1 → 1.4.1
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/makeRNConfig.js +5 -5
- package/dist/cjs/config/makeRNConfig.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/makeRNConfig.js +5 -5
- package/dist/esm/config/makeRNConfig.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/makeRNConfig.ts +10 -9
- 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/initialize.test.ts +63 -2
- package/src/instrumentations/anr/instrumentation.ts +8 -1
- package/src/instrumentations/appState/index.ts +10 -2
- package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
- package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/types.ts +7 -0
- package/src/instrumentations/errors/index.ts +12 -4
- package/src/instrumentations/errors/instrumentation.test.ts +35 -0
- package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
- package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
- package/src/instrumentations/frameMonitoring/types.ts +4 -6
- package/src/instrumentations/performance/index.ts +2 -6
- package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
- package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
- package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
- package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
- package/src/instrumentations/session/directSessionActivity.ts +36 -0
- package/src/instrumentations/session/index.ts +269 -46
- package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
- package/src/instrumentations/session/sessionActivity.test.ts +68 -0
- package/src/instrumentations/session/sessionActivity.ts +58 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
- package/src/instrumentations/session/sessionAttributes.ts +12 -6
- package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
- package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
- package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
- package/src/instrumentations/session/sessionManager/types.ts +8 -0
- package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
- package/src/instrumentations/session/sessionProcess.test.ts +136 -0
- package/src/instrumentations/session/sessionProcess.ts +114 -0
- package/src/instrumentations/session/startNewSession.test.ts +337 -0
- package/src/instrumentations/startup/index.ts +2 -6
- package/src/navigation/utils.ts +3 -1
- package/src/transports/fetch/index.ts +6 -1
- package/src/transports/fetch/transport.test.ts +86 -2
- package/src/transports/fetch/transport.ts +38 -16
- package/src/transports/fetch/types.ts +7 -0
- package/src/transports/offline/ConnectivityService.ts +1 -1
- package/src/transports/offline/OfflineCache.ts +2 -2
- package/src/transports/offline/transport.ts +3 -4
- package/src/userPersistence/UserPersistence.ts +1 -2
- package/dist/cjs/utils/throttle.js +0 -42
- package/dist/cjs/utils/throttle.js.map +0 -1
- package/dist/esm/utils/throttle.js +0 -34
- package/dist/esm/utils/throttle.js.map +0 -1
- package/dist/types/utils/throttle.d.ts +0 -9
- package/src/utils/throttle.test.ts +0 -141
- package/src/utils/throttle.ts +0 -34
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EVENT_VIEW_CHANGED, TransportItemType } from '@grafana/faro-core';
|
|
2
|
+
import type { EventAttributes, TransportItem } from '@grafana/faro-core';
|
|
3
|
+
|
|
4
|
+
import { EVENT_NAVIGATION } from '../../navigation/utils';
|
|
5
|
+
import { EVENT_APP_STATE_CHANGED } from '../appState';
|
|
6
|
+
|
|
7
|
+
export enum SessionActivityKind {
|
|
8
|
+
Meaningful = 'meaningful',
|
|
9
|
+
Passive = 'passive',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type ActivityEventPayload = {
|
|
13
|
+
action?: unknown;
|
|
14
|
+
attributes?: EventAttributes;
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Classifies how a transport item affects the session inactivity window. */
|
|
19
|
+
export function classifySessionActivity(item: TransportItem): SessionActivityKind {
|
|
20
|
+
const payload = item.payload as ActivityEventPayload;
|
|
21
|
+
|
|
22
|
+
if (payload.action != null) {
|
|
23
|
+
return SessionActivityKind.Meaningful;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (item.type !== TransportItemType.EVENT) {
|
|
27
|
+
return SessionActivityKind.Passive;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (payload.name === EVENT_VIEW_CHANGED || payload.name === EVENT_NAVIGATION) {
|
|
31
|
+
return SessionActivityKind.Meaningful;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
payload.name === EVENT_APP_STATE_CHANGED &&
|
|
36
|
+
payload.attributes?.['fromState'] === 'background' &&
|
|
37
|
+
payload.attributes?.['toState'] === 'active'
|
|
38
|
+
) {
|
|
39
|
+
return SessionActivityKind.Meaningful;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return SessionActivityKind.Passive;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type CrashPayload = {
|
|
46
|
+
fatal?: unknown;
|
|
47
|
+
type?: unknown;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Recovered crashes keep the session captured by the previous process. */
|
|
51
|
+
export function isRecoveredCrashItem(item: TransportItem, currentSessionId: string | undefined): boolean {
|
|
52
|
+
if (item.type !== TransportItemType.EXCEPTION || item.meta.session?.id == null) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const payload = item.payload as CrashPayload;
|
|
57
|
+
return payload.type === 'crash' && payload.fatal === true && item.meta.session.id !== currentSessionId;
|
|
58
|
+
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
-
import { Platform } from 'react-native';
|
|
2
|
+
import { NativeModules, Platform } from 'react-native';
|
|
3
3
|
import DeviceInfo from 'react-native-device-info';
|
|
4
4
|
|
|
5
|
-
import { getSessionAttributes, loadMobileMetaForInit } from './sessionAttributes';
|
|
5
|
+
import { getSessionAttributes, loadMobileMetaForInit, minimalSessionDeviceAttributes } from './sessionAttributes';
|
|
6
|
+
import { resetSessionProcessForTests } from './sessionProcess';
|
|
6
7
|
|
|
7
8
|
const INSTALLATION_ID_STORAGE_KEY = '@grafana/faro-react-native/installation_id';
|
|
8
9
|
const STORED_INSTALLATION_ID = 'stored-installation-id';
|
|
10
|
+
const originalNativeModule = NativeModules.FaroReactNativeModule;
|
|
11
|
+
const sessionNativeModule = {
|
|
12
|
+
claimSessionPersistence: () => true,
|
|
13
|
+
getSessionProcessIdentifier: () => 'com.example.myapp',
|
|
14
|
+
isMainSessionProcess: () => true,
|
|
15
|
+
releaseSessionPersistence: () => true,
|
|
16
|
+
};
|
|
9
17
|
|
|
10
18
|
function setStoredInstallationId(value = STORED_INSTALLATION_ID): void {
|
|
11
19
|
(global as any).mockAsyncStorage = {
|
|
@@ -37,7 +45,14 @@ jest.mock('react-native-device-info', () => ({
|
|
|
37
45
|
describe('sessionAttributes', () => {
|
|
38
46
|
beforeEach(() => {
|
|
39
47
|
(global as any).mockAsyncStorage = {};
|
|
48
|
+
NativeModules.FaroReactNativeModule = sessionNativeModule;
|
|
40
49
|
jest.clearAllMocks();
|
|
50
|
+
resetSessionProcessForTests();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterAll(() => {
|
|
54
|
+
NativeModules.FaroReactNativeModule = originalNativeModule;
|
|
55
|
+
resetSessionProcessForTests();
|
|
41
56
|
});
|
|
42
57
|
|
|
43
58
|
describe('getSessionAttributes', () => {
|
|
@@ -79,6 +94,7 @@ describe('sessionAttributes', () => {
|
|
|
79
94
|
|
|
80
95
|
expect(attributes).toEqual({
|
|
81
96
|
react_native_version: '0.75.1',
|
|
97
|
+
process_name: 'com.example.myapp',
|
|
82
98
|
device_os: 'iOS',
|
|
83
99
|
device_os_version: '17.0',
|
|
84
100
|
device_os_detail: 'iOS 17.0',
|
|
@@ -177,6 +193,7 @@ describe('sessionAttributes', () => {
|
|
|
177
193
|
|
|
178
194
|
expect(attributes).toEqual({
|
|
179
195
|
react_native_version: '0.75.1',
|
|
196
|
+
process_name: 'com.example.myapp',
|
|
180
197
|
device_os: 'Android',
|
|
181
198
|
device_os_version: '15',
|
|
182
199
|
device_os_detail: 'Android 15 (SDK 35)',
|
|
@@ -439,6 +456,7 @@ describe('sessionAttributes', () => {
|
|
|
439
456
|
expect(mobileMeta).toEqual({
|
|
440
457
|
sessionAttributes: {
|
|
441
458
|
react_native_version: '0.75.1',
|
|
459
|
+
process_name: 'com.example.myapp',
|
|
442
460
|
},
|
|
443
461
|
meta: {
|
|
444
462
|
app: {
|
|
@@ -531,6 +549,7 @@ describe('sessionAttributes', () => {
|
|
|
531
549
|
// Device info is omitted when collection fails so nothing partial is sent to Faro
|
|
532
550
|
expect(attributes).toEqual({
|
|
533
551
|
react_native_version: expect.any(String),
|
|
552
|
+
process_name: 'com.example.myapp',
|
|
534
553
|
});
|
|
535
554
|
expect(attributes.device_id).toBeUndefined();
|
|
536
555
|
expect(attributes.device_os).toBeUndefined();
|
|
@@ -555,6 +574,7 @@ describe('sessionAttributes', () => {
|
|
|
555
574
|
// Any synchronous DeviceInfo failure skips all device fields (minimal payload only)
|
|
556
575
|
expect(attributes).toEqual({
|
|
557
576
|
react_native_version: expect.any(String),
|
|
577
|
+
process_name: 'com.example.myapp',
|
|
558
578
|
});
|
|
559
579
|
expect(attributes.device_id).toBeUndefined();
|
|
560
580
|
expect(attributes.device_manufacturer).toBeUndefined();
|
|
@@ -601,5 +621,20 @@ describe('sessionAttributes', () => {
|
|
|
601
621
|
expect(attributes.device_manufacturer).toBe('oneplus');
|
|
602
622
|
});
|
|
603
623
|
});
|
|
624
|
+
|
|
625
|
+
it('omits process identity when the native bridge is unavailable', () => {
|
|
626
|
+
const nativeModule = NativeModules.FaroReactNativeModule;
|
|
627
|
+
NativeModules.FaroReactNativeModule = undefined;
|
|
628
|
+
resetSessionProcessForTests();
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
expect(minimalSessionDeviceAttributes()).toEqual({
|
|
632
|
+
react_native_version: expect.any(String),
|
|
633
|
+
});
|
|
634
|
+
} finally {
|
|
635
|
+
NativeModules.FaroReactNativeModule = nativeModule;
|
|
636
|
+
resetSessionProcessForTests();
|
|
637
|
+
}
|
|
638
|
+
});
|
|
604
639
|
});
|
|
605
640
|
});
|
|
@@ -4,13 +4,14 @@ import DeviceInfo from 'react-native-device-info';
|
|
|
4
4
|
import type { Meta } from '@grafana/faro-core';
|
|
5
5
|
|
|
6
6
|
import { getInstallationId } from './installationId';
|
|
7
|
+
import { getSessionProcessInfo } from './sessionProcess';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Session attributes for React Native
|
|
10
11
|
* These attributes are automatically included with every telemetry event
|
|
11
12
|
*
|
|
12
|
-
* Core
|
|
13
|
-
*
|
|
13
|
+
* Core mobile session attributes, with additional monitoring fields
|
|
14
|
+
* (memory, device type, battery, etc.)
|
|
14
15
|
*
|
|
15
16
|
* SDK name, core version, and npm adapter are on Faro meta `sdk` (`getSdkMeta` in `metas/sdk.ts`).
|
|
16
17
|
*
|
|
@@ -20,6 +21,9 @@ export interface SessionAttributes {
|
|
|
20
21
|
/** Host app's React Native framework version (e.g. "0.75.1") from `Platform.constants`. */
|
|
21
22
|
react_native_version: string;
|
|
22
23
|
|
|
24
|
+
/** Stable Android process name or Apple bundle identifier for this runtime. */
|
|
25
|
+
process_name?: string;
|
|
26
|
+
|
|
23
27
|
/** Operating system ("iOS" or "Android") */
|
|
24
28
|
device_os?: string;
|
|
25
29
|
|
|
@@ -138,7 +142,7 @@ function getMobileDeviceType(isTablet: boolean): 'mobile' | 'tablet' | undefined
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
/**
|
|
141
|
-
* Get OS detail string
|
|
145
|
+
* Get OS detail string for session attributes.
|
|
142
146
|
* iOS: "iOS 17.0"
|
|
143
147
|
* Android: "Android 15 (SDK 35)"
|
|
144
148
|
*/
|
|
@@ -174,8 +178,10 @@ async function getDeviceOsBuildId(): Promise<string | undefined> {
|
|
|
174
178
|
* No synchronous DeviceInfo reads — use {@link getSessionAttributes} or the package async `initializeFaro`.
|
|
175
179
|
*/
|
|
176
180
|
export function minimalSessionDeviceAttributes(): SessionAttributes {
|
|
181
|
+
const processInfo = getSessionProcessInfo();
|
|
177
182
|
return {
|
|
178
183
|
react_native_version: getReactNativeVersion(),
|
|
184
|
+
...(processInfo == null ? {} : { process_name: processInfo.identifier }),
|
|
179
185
|
};
|
|
180
186
|
}
|
|
181
187
|
|
|
@@ -183,8 +189,9 @@ export function minimalSessionDeviceAttributes(): SessionAttributes {
|
|
|
183
189
|
* Get all session attributes
|
|
184
190
|
* These attributes are automatically included with every telemetry event
|
|
185
191
|
*
|
|
186
|
-
* Core attributes
|
|
192
|
+
* Core session attributes:
|
|
187
193
|
* - react_native_version (RN framework in the host app)
|
|
194
|
+
* - process_name (Android process name or iOS bundle identifier)
|
|
188
195
|
* - device_os, device_os_version, device_os_detail
|
|
189
196
|
* - device_manufacturer, device_model, device_model_name
|
|
190
197
|
* - device_brand, device_is_physical, device_id
|
|
@@ -222,7 +229,6 @@ async function collectMobileMeta(): Promise<PreloadedMobileMeta> {
|
|
|
222
229
|
// Memory info
|
|
223
230
|
const totalMemory = DeviceInfo.getTotalMemorySync();
|
|
224
231
|
const usedMemory = DeviceInfo.getUsedMemorySync();
|
|
225
|
-
const reactNativeVersion = getReactNativeVersion();
|
|
226
232
|
|
|
227
233
|
// Try to get async device info (battery, carrier)
|
|
228
234
|
let batteryLevel: string | undefined;
|
|
@@ -265,7 +271,7 @@ async function collectMobileMeta(): Promise<PreloadedMobileMeta> {
|
|
|
265
271
|
}
|
|
266
272
|
|
|
267
273
|
const attributes: SessionAttributes = {
|
|
268
|
-
|
|
274
|
+
...minimalSessionDeviceAttributes(),
|
|
269
275
|
device_os: systemName,
|
|
270
276
|
device_os_version: systemVersion,
|
|
271
277
|
device_os_detail: deviceOsDetail,
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { EVENT_SESSION_START, EVENT_VIEW_CHANGED, initializeFaro, TransportItemType } from '@grafana/faro-core';
|
|
2
|
+
import type { TransportItem } from '@grafana/faro-core';
|
|
3
|
+
import { mockConfig, MockTransport } from '@grafana/faro-test-utils';
|
|
4
|
+
|
|
5
|
+
import { ViewInstrumentation } from '../view';
|
|
6
|
+
|
|
7
|
+
import { SessionInstrumentation } from './index';
|
|
8
|
+
import { MmkvPersistentSessionsManager } from './sessionManager/MmkvPersistentSessionsManager';
|
|
9
|
+
import { VolatileSessionsManager } from './sessionManager/VolatileSessionManager';
|
|
10
|
+
|
|
11
|
+
async function initializeWithoutInstrumentations() {
|
|
12
|
+
return initializeFaro(
|
|
13
|
+
mockConfig({
|
|
14
|
+
instrumentations: [],
|
|
15
|
+
transports: [new MockTransport()],
|
|
16
|
+
url: 'http://localhost:12345/collect',
|
|
17
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sessionStartCount(transport: MockTransport): number {
|
|
23
|
+
return transport.items.filter(
|
|
24
|
+
(item) => item.type === TransportItemType.EVENT && item.payload.name === EVENT_SESSION_START
|
|
25
|
+
).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sessionItem(isSampled: boolean): TransportItem {
|
|
29
|
+
return {
|
|
30
|
+
type: TransportItemType.EVENT,
|
|
31
|
+
payload: { name: 'test' },
|
|
32
|
+
meta: {
|
|
33
|
+
session: {
|
|
34
|
+
id: 'active-session',
|
|
35
|
+
attributes: { isSampled: isSampled.toString() },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function expectSampledItem(result: TransportItem | null): TransportItem {
|
|
42
|
+
expect(result).not.toBeNull();
|
|
43
|
+
if (result == null) {
|
|
44
|
+
throw new Error('Expected a sampled item to be retained.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(result.payload).toEqual({ name: 'test' });
|
|
48
|
+
expect(result.meta.session?.attributes?.['isSampled']).toBeUndefined();
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('session cleanup', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
jest.restoreAllMocks();
|
|
55
|
+
jest.clearAllMocks();
|
|
56
|
+
VolatileSessionsManager.removeUserSession();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
VolatileSessionsManager.removeUserSession();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('removes the volatile manager metas listener with the same handler', async () => {
|
|
64
|
+
const faro = await initializeWithoutInstrumentations();
|
|
65
|
+
const addListenerSpy = jest.spyOn(faro.metas, 'addListener');
|
|
66
|
+
const removeListenerSpy = jest.spyOn(faro.metas, 'removeListener');
|
|
67
|
+
const storeSessionSpy = jest.spyOn(VolatileSessionsManager, 'storeUserSession');
|
|
68
|
+
const manager = new VolatileSessionsManager();
|
|
69
|
+
const handler = addListenerSpy.mock.calls[0]?.[0];
|
|
70
|
+
|
|
71
|
+
expect(handler).toBeDefined();
|
|
72
|
+
faro.api.setSession({ id: 'before-cleanup' });
|
|
73
|
+
expect(storeSessionSpy).toHaveBeenCalled();
|
|
74
|
+
storeSessionSpy.mockClear();
|
|
75
|
+
|
|
76
|
+
manager.unpatch();
|
|
77
|
+
manager.unpatch();
|
|
78
|
+
|
|
79
|
+
expect(removeListenerSpy).toHaveBeenCalledTimes(1);
|
|
80
|
+
expect(removeListenerSpy).toHaveBeenCalledWith(handler);
|
|
81
|
+
|
|
82
|
+
faro.api.setSession({ id: 'after-cleanup' });
|
|
83
|
+
expect(storeSessionSpy).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('removes the persistent manager metas listener with the same handler', async () => {
|
|
87
|
+
const faro = await initializeWithoutInstrumentations();
|
|
88
|
+
const addListenerSpy = jest.spyOn(faro.metas, 'addListener');
|
|
89
|
+
const removeListenerSpy = jest.spyOn(faro.metas, 'removeListener');
|
|
90
|
+
jest.spyOn(MmkvPersistentSessionsManager, 'fetchUserSession').mockReturnValue(null);
|
|
91
|
+
const storeSessionSpy = jest.spyOn(MmkvPersistentSessionsManager, 'storeUserSession').mockImplementation();
|
|
92
|
+
const manager = new MmkvPersistentSessionsManager();
|
|
93
|
+
const handler = addListenerSpy.mock.calls[0]?.[0];
|
|
94
|
+
|
|
95
|
+
expect(handler).toBeDefined();
|
|
96
|
+
faro.api.setSession({ id: 'before-cleanup' });
|
|
97
|
+
expect(storeSessionSpy).toHaveBeenCalled();
|
|
98
|
+
storeSessionSpy.mockClear();
|
|
99
|
+
|
|
100
|
+
manager.unpatch();
|
|
101
|
+
manager.unpatch();
|
|
102
|
+
|
|
103
|
+
expect(removeListenerSpy).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(removeListenerSpy).toHaveBeenCalledWith(handler);
|
|
105
|
+
|
|
106
|
+
faro.api.setSession({ id: 'after-cleanup' });
|
|
107
|
+
expect(storeSessionSpy).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('cleans up through destroy while a retained hook still enforces sampling', async () => {
|
|
111
|
+
const transport = new MockTransport();
|
|
112
|
+
const faro = initializeFaro(
|
|
113
|
+
mockConfig({
|
|
114
|
+
instrumentations: [],
|
|
115
|
+
transports: [transport],
|
|
116
|
+
url: 'http://localhost:12345/collect',
|
|
117
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
const addListenerSpy = jest.spyOn(faro.metas, 'addListener');
|
|
121
|
+
const removeListenerSpy = jest.spyOn(faro.metas, 'removeListener');
|
|
122
|
+
const removeBeforeSendHooksSpy = jest.spyOn(faro.transports, 'removeBeforeSendHooks');
|
|
123
|
+
const instrumentation = new SessionInstrumentation();
|
|
124
|
+
|
|
125
|
+
faro.instrumentations.add(instrumentation);
|
|
126
|
+
|
|
127
|
+
const registeredListeners = addListenerSpy.mock.calls.map(([listener]) => listener);
|
|
128
|
+
expect(registeredListeners).toHaveLength(2);
|
|
129
|
+
|
|
130
|
+
const beforeSendHooks = faro.transports.getBeforeSendHooks();
|
|
131
|
+
expect(beforeSendHooks).toHaveLength(2);
|
|
132
|
+
const [fallbackHook, beforeSendHook] = beforeSendHooks;
|
|
133
|
+
if (fallbackHook == null || beforeSendHook == null) {
|
|
134
|
+
throw new Error('Expected the sampling fallback and session before-send hooks to be registered.');
|
|
135
|
+
}
|
|
136
|
+
expectSampledItem(beforeSendHook(sessionItem(true)));
|
|
137
|
+
|
|
138
|
+
const storedSession = VolatileSessionsManager.fetchUserSession();
|
|
139
|
+
if (storedSession == null) {
|
|
140
|
+
throw new Error('Expected an active volatile session before cleanup.');
|
|
141
|
+
}
|
|
142
|
+
const unsampledSession = {
|
|
143
|
+
...storedSession,
|
|
144
|
+
isSampled: false,
|
|
145
|
+
sessionMeta: {
|
|
146
|
+
...storedSession.sessionMeta,
|
|
147
|
+
id: storedSession.sessionId,
|
|
148
|
+
attributes: {
|
|
149
|
+
...storedSession.sessionMeta?.attributes,
|
|
150
|
+
isSampled: 'false',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
VolatileSessionsManager.storeUserSession(unsampledSession);
|
|
155
|
+
faro.api.setSession(unsampledSession.sessionMeta);
|
|
156
|
+
expect(faro.api.getSession()?.attributes?.['isSampled']).toBe('false');
|
|
157
|
+
const sessionStartsBeforeDestroy = sessionStartCount(transport);
|
|
158
|
+
instrumentation.destroy();
|
|
159
|
+
|
|
160
|
+
expect(removeListenerSpy).toHaveBeenCalledTimes(2);
|
|
161
|
+
expect(new Set(removeListenerSpy.mock.calls.map(([listener]) => listener))).toStrictEqual(
|
|
162
|
+
new Set(registeredListeners)
|
|
163
|
+
);
|
|
164
|
+
expect(removeBeforeSendHooksSpy).toHaveBeenCalledWith(beforeSendHook);
|
|
165
|
+
// A detached session hook must not rotate sessions, but a captured queued
|
|
166
|
+
// item still carries the sampling decision that the hook must enforce.
|
|
167
|
+
expect(beforeSendHook(sessionItem(false))).toBeNull();
|
|
168
|
+
expectSampledItem(beforeSendHook(sessionItem(true)));
|
|
169
|
+
expect(fallbackHook(sessionItem(false))).toBeNull();
|
|
170
|
+
expectSampledItem(fallbackHook(sessionItem(true)));
|
|
171
|
+
|
|
172
|
+
expect(faro.api.getSession()?.attributes?.['isSampled']).toBeUndefined();
|
|
173
|
+
const itemsAfterDestroy = transport.items.length;
|
|
174
|
+
faro.transports.execute(sessionItem(false));
|
|
175
|
+
expect(transport.items).toHaveLength(itemsAfterDestroy);
|
|
176
|
+
faro.transports.execute(sessionItem(true));
|
|
177
|
+
expect(transport.items).toHaveLength(itemsAfterDestroy + 1);
|
|
178
|
+
expect(transport.items.at(-1)?.meta.session?.attributes?.['isSampled']).toBeUndefined();
|
|
179
|
+
|
|
180
|
+
faro.api.pushEvent('after-destroy');
|
|
181
|
+
expect(transport.items.at(-1)?.payload).toEqual(expect.objectContaining({ name: 'after-destroy' }));
|
|
182
|
+
expect(transport.items.at(-1)?.meta.session?.attributes?.['isSampled']).toBeUndefined();
|
|
183
|
+
|
|
184
|
+
faro.api.setSession({ id: 'after-cleanup' });
|
|
185
|
+
expect(sessionStartCount(transport)).toBe(sessionStartsBeforeDestroy);
|
|
186
|
+
|
|
187
|
+
instrumentation.destroy();
|
|
188
|
+
expect(removeListenerSpy).toHaveBeenCalledTimes(2);
|
|
189
|
+
expect(removeBeforeSendHooksSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it.each([
|
|
193
|
+
['sampled', 1, 1],
|
|
194
|
+
['unsampled', 0, 0],
|
|
195
|
+
] as const)('keeps teardown listener telemetry attributed for a %s session', async (_name, rate, expectedCount) => {
|
|
196
|
+
const transport = new MockTransport();
|
|
197
|
+
const faro = initializeFaro(
|
|
198
|
+
mockConfig({
|
|
199
|
+
instrumentations: [],
|
|
200
|
+
transports: [transport],
|
|
201
|
+
url: 'http://localhost:12345/collect',
|
|
202
|
+
view: { name: 'configured-view' },
|
|
203
|
+
sessionTracking: {
|
|
204
|
+
enabled: true,
|
|
205
|
+
persistent: false,
|
|
206
|
+
sampling: { resolve: () => rate },
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
const instrumentation = new SessionInstrumentation();
|
|
211
|
+
const checkSessionSpy = jest.spyOn(VolatileSessionsManager.prototype, 'checkSession');
|
|
212
|
+
faro.instrumentations.add(instrumentation);
|
|
213
|
+
faro.instrumentations.add(new ViewInstrumentation());
|
|
214
|
+
const storedSession = VolatileSessionsManager.fetchUserSession();
|
|
215
|
+
if (storedSession == null) {
|
|
216
|
+
throw new Error('Expected an active volatile session before cleanup.');
|
|
217
|
+
}
|
|
218
|
+
expect(storedSession.isSampled).toBe(rate === 1);
|
|
219
|
+
checkSessionSpy.mockClear();
|
|
220
|
+
const itemsBeforeDestroy = transport.items.length;
|
|
221
|
+
|
|
222
|
+
instrumentation.destroy();
|
|
223
|
+
|
|
224
|
+
expect(checkSessionSpy).not.toHaveBeenCalled();
|
|
225
|
+
const teardownItems = transport.items.slice(itemsBeforeDestroy);
|
|
226
|
+
const viewChangedItems = teardownItems.filter(
|
|
227
|
+
(item) => item.type === TransportItemType.EVENT && item.payload.name === EVENT_VIEW_CHANGED
|
|
228
|
+
);
|
|
229
|
+
expect(viewChangedItems).toHaveLength(expectedCount);
|
|
230
|
+
if (expectedCount === 1) {
|
|
231
|
+
expect(viewChangedItems[0]?.meta.session?.id).toBe(storedSession.sessionId);
|
|
232
|
+
expect(viewChangedItems[0]?.meta.session?.attributes?.['isSampled']).toBeUndefined();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('finishes cleanup when a metas listener throws during the sampling scrub', async () => {
|
|
237
|
+
const faro = await initializeWithoutInstrumentations();
|
|
238
|
+
const instrumentation = new SessionInstrumentation();
|
|
239
|
+
const removeBeforeSendHooksSpy = jest.spyOn(faro.transports, 'removeBeforeSendHooks');
|
|
240
|
+
const checkSessionSpy = jest.spyOn(VolatileSessionsManager.prototype, 'checkSession');
|
|
241
|
+
faro.instrumentations.add(instrumentation);
|
|
242
|
+
const beforeSendHook = faro.transports.getBeforeSendHooks().at(-1);
|
|
243
|
+
if (beforeSendHook == null) {
|
|
244
|
+
throw new Error('Expected a session before-send hook before cleanup.');
|
|
245
|
+
}
|
|
246
|
+
const throwingListener = jest.fn((meta) => {
|
|
247
|
+
if (meta.session == null) {
|
|
248
|
+
throw new Error('metas listener failed');
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
faro.metas.addListener(throwingListener);
|
|
252
|
+
|
|
253
|
+
expect(() => instrumentation.destroy()).toThrow('metas listener failed');
|
|
254
|
+
expect(removeBeforeSendHooksSpy).toHaveBeenCalledTimes(1);
|
|
255
|
+
expect(removeBeforeSendHooksSpy).toHaveBeenCalledWith(beforeSendHook);
|
|
256
|
+
|
|
257
|
+
faro.metas.removeListener(throwingListener);
|
|
258
|
+
checkSessionSpy.mockClear();
|
|
259
|
+
faro.api.pushEvent('after-failed-cleanup');
|
|
260
|
+
expect(checkSessionSpy).not.toHaveBeenCalled();
|
|
261
|
+
|
|
262
|
+
expect(() => instrumentation.destroy()).not.toThrow();
|
|
263
|
+
expect(removeBeforeSendHooksSpy).toHaveBeenCalledTimes(1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('binds a manager listener to the supplied metas instance', async () => {
|
|
267
|
+
const registeredFaro = await initializeWithoutInstrumentations();
|
|
268
|
+
const addListenerSpy = jest.spyOn(registeredFaro.metas, 'addListener');
|
|
269
|
+
const removeRegisteredListenerSpy = jest.spyOn(registeredFaro.metas, 'removeListener');
|
|
270
|
+
const replacementFaro = await initializeWithoutInstrumentations();
|
|
271
|
+
const addReplacementListenerSpy = jest.spyOn(replacementFaro.metas, 'addListener');
|
|
272
|
+
const removeReplacementListenerSpy = jest.spyOn(replacementFaro.metas, 'removeListener');
|
|
273
|
+
const manager = new VolatileSessionsManager(registeredFaro.metas);
|
|
274
|
+
const handler = addListenerSpy.mock.calls[0]?.[0];
|
|
275
|
+
|
|
276
|
+
manager.unpatch();
|
|
277
|
+
|
|
278
|
+
expect(addListenerSpy).toHaveBeenCalledWith(handler);
|
|
279
|
+
expect(addReplacementListenerSpy).not.toHaveBeenCalled();
|
|
280
|
+
expect(removeRegisteredListenerSpy).toHaveBeenCalledWith(handler);
|
|
281
|
+
expect(removeReplacementListenerSpy).not.toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('binds a persistent manager listener to the supplied metas instance', async () => {
|
|
285
|
+
const registeredFaro = await initializeWithoutInstrumentations();
|
|
286
|
+
const addListenerSpy = jest.spyOn(registeredFaro.metas, 'addListener');
|
|
287
|
+
const removeRegisteredListenerSpy = jest.spyOn(registeredFaro.metas, 'removeListener');
|
|
288
|
+
const replacementFaro = await initializeWithoutInstrumentations();
|
|
289
|
+
const addReplacementListenerSpy = jest.spyOn(replacementFaro.metas, 'addListener');
|
|
290
|
+
const removeReplacementListenerSpy = jest.spyOn(replacementFaro.metas, 'removeListener');
|
|
291
|
+
jest.spyOn(MmkvPersistentSessionsManager, 'fetchUserSession').mockReturnValue(null);
|
|
292
|
+
jest.spyOn(MmkvPersistentSessionsManager, 'storeUserSession').mockImplementation();
|
|
293
|
+
const manager = new MmkvPersistentSessionsManager(registeredFaro.metas);
|
|
294
|
+
const handler = addListenerSpy.mock.calls[0]?.[0];
|
|
295
|
+
|
|
296
|
+
manager.unpatch();
|
|
297
|
+
|
|
298
|
+
expect(addListenerSpy).toHaveBeenCalledWith(handler);
|
|
299
|
+
expect(addReplacementListenerSpy).not.toHaveBeenCalled();
|
|
300
|
+
expect(removeRegisteredListenerSpy).toHaveBeenCalledWith(handler);
|
|
301
|
+
expect(removeReplacementListenerSpy).not.toHaveBeenCalled();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('moves every registration when an instrumentation instance is reused', async () => {
|
|
305
|
+
const instrumentation = new SessionInstrumentation();
|
|
306
|
+
const firstTransport = new MockTransport();
|
|
307
|
+
const firstFaro = initializeFaro(
|
|
308
|
+
mockConfig({
|
|
309
|
+
instrumentations: [],
|
|
310
|
+
transports: [firstTransport],
|
|
311
|
+
url: 'http://localhost:12345/collect',
|
|
312
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
const firstRemoveListenerSpy = jest.spyOn(firstFaro.metas, 'removeListener');
|
|
316
|
+
const firstRemoveBeforeSendHooksSpy = jest.spyOn(firstFaro.transports, 'removeBeforeSendHooks');
|
|
317
|
+
const firstSetSessionSpy = jest.spyOn(firstFaro.api, 'setSession');
|
|
318
|
+
firstFaro.instrumentations.add(instrumentation);
|
|
319
|
+
const firstHook = firstFaro.transports.getBeforeSendHooks().at(-1);
|
|
320
|
+
if (firstHook == null) {
|
|
321
|
+
throw new Error('Expected the first Faro instance to retain its session hook.');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const secondTransport = new MockTransport();
|
|
325
|
+
const secondFaro = initializeFaro(
|
|
326
|
+
mockConfig({
|
|
327
|
+
instrumentations: [],
|
|
328
|
+
transports: [secondTransport],
|
|
329
|
+
url: 'http://localhost:12345/collect',
|
|
330
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
const secondAddListenerSpy = jest.spyOn(secondFaro.metas, 'addListener');
|
|
334
|
+
const secondRemoveListenerSpy = jest.spyOn(secondFaro.metas, 'removeListener');
|
|
335
|
+
secondFaro.instrumentations.add(instrumentation);
|
|
336
|
+
|
|
337
|
+
expect(firstRemoveListenerSpy).toHaveBeenCalledTimes(2);
|
|
338
|
+
expect(firstRemoveBeforeSendHooksSpy).toHaveBeenCalledWith(firstHook);
|
|
339
|
+
expect(firstSetSessionSpy).toHaveBeenCalledWith(
|
|
340
|
+
expect.objectContaining({
|
|
341
|
+
attributes: expect.not.objectContaining({ isSampled: expect.anything() }),
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
expect(secondAddListenerSpy).toHaveBeenCalledTimes(2);
|
|
345
|
+
const firstHookResult = expectSampledItem(firstHook(sessionItem(true)));
|
|
346
|
+
expect(firstHookResult.meta.session?.id).toBe('active-session');
|
|
347
|
+
|
|
348
|
+
const secondSessionStarts = sessionStartCount(secondTransport);
|
|
349
|
+
firstFaro.api.setSession({ id: 'stale-faro-session' });
|
|
350
|
+
expect(sessionStartCount(secondTransport)).toBe(secondSessionStarts);
|
|
351
|
+
|
|
352
|
+
secondFaro.api.setSession({ id: 'active-faro-session' });
|
|
353
|
+
expect(sessionStartCount(secondTransport)).toBe(secondSessionStarts + 1);
|
|
354
|
+
|
|
355
|
+
instrumentation.destroy();
|
|
356
|
+
expect(secondRemoveListenerSpy).toHaveBeenCalledTimes(2);
|
|
357
|
+
|
|
358
|
+
secondFaro.api.setSession({ id: 'after-destroy' });
|
|
359
|
+
expect(sessionStartCount(secondTransport)).toBe(secondSessionStarts + 1);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('lets a replacement hook reattribute items retained by an older Faro Core', async () => {
|
|
363
|
+
const faro = await initializeWithoutInstrumentations();
|
|
364
|
+
const firstInstrumentation = new SessionInstrumentation();
|
|
365
|
+
faro.instrumentations.add(firstInstrumentation);
|
|
366
|
+
const firstHook = faro.transports.getBeforeSendHooks().at(-1);
|
|
367
|
+
if (firstHook == null) {
|
|
368
|
+
throw new Error('Expected the first instrumentation to register a session hook.');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
faro.instrumentations.remove(firstInstrumentation);
|
|
372
|
+
expect(faro.instrumentations.instrumentations).not.toContain(firstInstrumentation);
|
|
373
|
+
expect(faro.instrumentations.instrumentations).toHaveLength(0);
|
|
374
|
+
|
|
375
|
+
const replacementInstrumentation = new SessionInstrumentation();
|
|
376
|
+
faro.instrumentations.add(replacementInstrumentation);
|
|
377
|
+
expect(faro.instrumentations.instrumentations).toContain(replacementInstrumentation);
|
|
378
|
+
|
|
379
|
+
const activeSession = VolatileSessionsManager.fetchUserSession();
|
|
380
|
+
if (activeSession == null) {
|
|
381
|
+
throw new Error('Expected the replacement instrumentation to own a session.');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const installedHooks = faro.transports.getBeforeSendHooks();
|
|
385
|
+
const hooks = installedHooks.includes(firstHook) ? installedHooks : [firstHook, ...installedHooks];
|
|
386
|
+
const result = hooks.reduce<TransportItem | null>(
|
|
387
|
+
(item, hook) => (item == null ? null : hook(item)),
|
|
388
|
+
sessionItem(false)
|
|
389
|
+
);
|
|
390
|
+
const sampledResult = expectSampledItem(result);
|
|
391
|
+
|
|
392
|
+
expect(sampledResult.meta.session?.id).toBe(activeSession.sessionId);
|
|
393
|
+
replacementInstrumentation.destroy();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('registers its manager listener on the instrumentation metas instance', async () => {
|
|
397
|
+
const ownerFaro = await initializeWithoutInstrumentations();
|
|
398
|
+
const ownerAddListenerSpy = jest.spyOn(ownerFaro.metas, 'addListener');
|
|
399
|
+
const otherFaro = await initializeWithoutInstrumentations();
|
|
400
|
+
const otherAddListenerSpy = jest.spyOn(otherFaro.metas, 'addListener');
|
|
401
|
+
const instrumentation = new SessionInstrumentation();
|
|
402
|
+
|
|
403
|
+
ownerFaro.instrumentations.add(instrumentation);
|
|
404
|
+
|
|
405
|
+
expect(ownerAddListenerSpy).toHaveBeenCalledTimes(2);
|
|
406
|
+
expect(otherAddListenerSpy).not.toHaveBeenCalled();
|
|
407
|
+
|
|
408
|
+
instrumentation.destroy();
|
|
409
|
+
});
|
|
410
|
+
});
|