@grafana/faro-react-native 1.3.0 → 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 +20 -15
- 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/anr/instrumentation.d.ts +1 -1
- package/dist/types/instrumentations/appState/index.d.ts +1 -1
- package/dist/types/instrumentations/console/index.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
- package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +3 -3
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +3 -3
- package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +3 -3
- package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
- package/dist/types/instrumentations/http/index.d.ts +1 -1
- package/dist/types/instrumentations/performance/index.d.ts +1 -3
- 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 +21 -2
- 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 +3 -7
- package/dist/types/instrumentations/userActions/index.d.ts +1 -1
- package/dist/types/instrumentations/view/index.d.ts +1 -1
- package/dist/types/instrumentations/xhr/index.d.ts +1 -1
- package/dist/types/navigation/utils.d.ts +1 -0
- package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
- package/dist/types/transports/console/transport.d.ts +1 -1
- package/dist/types/transports/fetch/index.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +9 -2
- 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 +4 -5
- 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 +8 -6
- 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,18 +1,40 @@
|
|
|
1
|
+
import { TransportItemType } from '@grafana/faro-core';
|
|
2
|
+
import type { TransportItem } from '@grafana/faro-core';
|
|
1
3
|
import { mockConfig, MockTransport } from '@grafana/faro-test-utils';
|
|
2
4
|
|
|
5
|
+
import { SamplingRate } from '../../config/sampling';
|
|
3
6
|
import { initializeFaro } from '../../initialize';
|
|
7
|
+
import { EVENT_NAVIGATION } from '../../navigation/utils';
|
|
8
|
+
import { EVENT_APP_STATE_CHANGED } from '../appState';
|
|
4
9
|
|
|
5
10
|
import { SessionInstrumentation } from './index';
|
|
11
|
+
import type { FaroUserSession } from './sessionManager';
|
|
12
|
+
import { VolatileSessionsManager } from './sessionManager/VolatileSessionManager';
|
|
6
13
|
|
|
7
14
|
function testEventItems(transport: MockTransport) {
|
|
8
15
|
return transport.items.filter((i) => i.type === 'event' && i.payload.name === 'test_event');
|
|
9
16
|
}
|
|
10
17
|
|
|
18
|
+
function getVolatileSession(): FaroUserSession {
|
|
19
|
+
const session = VolatileSessionsManager.fetchUserSession();
|
|
20
|
+
if (session == null) {
|
|
21
|
+
throw new Error('Expected an active volatile session.');
|
|
22
|
+
}
|
|
23
|
+
return session;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
describe('SessionInstrumentation beforeSend hook', () => {
|
|
12
27
|
let transport: MockTransport;
|
|
13
28
|
|
|
14
29
|
beforeEach(() => {
|
|
30
|
+
jest.useFakeTimers();
|
|
31
|
+
jest.setSystemTime(new Date('2026-08-19T12:00:00.000Z'));
|
|
15
32
|
jest.clearAllMocks();
|
|
33
|
+
VolatileSessionsManager.removeUserSession();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
jest.useRealTimers();
|
|
16
38
|
});
|
|
17
39
|
|
|
18
40
|
it('should pass through items when session is sampled (isSampled="true")', async () => {
|
|
@@ -57,6 +79,7 @@ describe('SessionInstrumentation beforeSend hook', () => {
|
|
|
57
79
|
sessionTracking: {
|
|
58
80
|
enabled: true,
|
|
59
81
|
persistent: false,
|
|
82
|
+
sampling: new SamplingRate(0),
|
|
60
83
|
},
|
|
61
84
|
})
|
|
62
85
|
);
|
|
@@ -74,6 +97,33 @@ describe('SessionInstrumentation beforeSend hook', () => {
|
|
|
74
97
|
expect(testEventItems(transport)).toHaveLength(0);
|
|
75
98
|
});
|
|
76
99
|
|
|
100
|
+
it('keeps dropping items after an overrides-only session update', async () => {
|
|
101
|
+
transport = new MockTransport();
|
|
102
|
+
|
|
103
|
+
const faro = await initializeFaro(
|
|
104
|
+
mockConfig({
|
|
105
|
+
url: 'http://localhost:12345/collect',
|
|
106
|
+
transports: [transport],
|
|
107
|
+
instrumentations: [new SessionInstrumentation()],
|
|
108
|
+
sessionTracking: {
|
|
109
|
+
enabled: true,
|
|
110
|
+
persistent: false,
|
|
111
|
+
sampling: new SamplingRate(0),
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
faro.api.setSession({
|
|
117
|
+
id: faro.api.getSession()?.id,
|
|
118
|
+
overrides: { serviceName: 'override-service' },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// The internal sampling decision must survive an overrides-only update.
|
|
122
|
+
faro.api.pushEvent('test_event', { data: 'test' });
|
|
123
|
+
|
|
124
|
+
expect(testEventItems(transport)).toHaveLength(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
77
127
|
it('should pass through items when no isSampled attribute exists', async () => {
|
|
78
128
|
transport = new MockTransport();
|
|
79
129
|
|
|
@@ -126,4 +176,200 @@ describe('SessionInstrumentation beforeSend hook', () => {
|
|
|
126
176
|
// Should send the test event (session is set at init)
|
|
127
177
|
expect(testEventItems(transport)).toHaveLength(1);
|
|
128
178
|
});
|
|
179
|
+
|
|
180
|
+
it('does not refresh inactivity for passive telemetry', async () => {
|
|
181
|
+
transport = new MockTransport();
|
|
182
|
+
const faro = await initializeFaro(
|
|
183
|
+
mockConfig({
|
|
184
|
+
url: 'http://localhost:12345/collect',
|
|
185
|
+
transports: [transport],
|
|
186
|
+
instrumentations: [new SessionInstrumentation()],
|
|
187
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
const session = getVolatileSession();
|
|
191
|
+
const previousActivity = Date.now() - 5 * 60 * 1000;
|
|
192
|
+
VolatileSessionsManager.storeUserSession({ ...session, lastActivity: previousActivity });
|
|
193
|
+
|
|
194
|
+
faro.api.pushEvent('poll_complete');
|
|
195
|
+
|
|
196
|
+
expect(VolatileSessionsManager.fetchUserSession()).toMatchObject({
|
|
197
|
+
sessionId: session.sessionId,
|
|
198
|
+
lastActivity: previousActivity,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('rotates passive telemetry at the exact inactivity boundary before sending it', async () => {
|
|
203
|
+
transport = new MockTransport();
|
|
204
|
+
const faro = await initializeFaro(
|
|
205
|
+
mockConfig({
|
|
206
|
+
url: 'http://localhost:12345/collect',
|
|
207
|
+
transports: [transport],
|
|
208
|
+
instrumentations: [new SessionInstrumentation()],
|
|
209
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
const previousSession = getVolatileSession();
|
|
213
|
+
VolatileSessionsManager.storeUserSession({
|
|
214
|
+
...previousSession,
|
|
215
|
+
lastActivity: Date.now() - 15 * 60 * 1000,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
faro.api.pushEvent('poll_complete');
|
|
219
|
+
|
|
220
|
+
const nextSession = getVolatileSession();
|
|
221
|
+
const sentEvent = transport.items.find(
|
|
222
|
+
(item) => item.type === TransportItemType.EVENT && item.payload.name === 'poll_complete'
|
|
223
|
+
);
|
|
224
|
+
expect(nextSession.sessionId).not.toBe(previousSession.sessionId);
|
|
225
|
+
expect(nextSession.sessionMeta?.attributes?.['previousSession']).toBe(previousSession.sessionId);
|
|
226
|
+
expect(sentEvent?.meta.session?.id).toBe(nextSession.sessionId);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('rotates at the exact four-hour lifetime boundary before sending the triggering item', async () => {
|
|
230
|
+
transport = new MockTransport();
|
|
231
|
+
const faro = await initializeFaro(
|
|
232
|
+
mockConfig({
|
|
233
|
+
url: 'http://localhost:12345/collect',
|
|
234
|
+
transports: [transport],
|
|
235
|
+
instrumentations: [new SessionInstrumentation()],
|
|
236
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
const previousSession = getVolatileSession();
|
|
240
|
+
VolatileSessionsManager.storeUserSession({
|
|
241
|
+
...previousSession,
|
|
242
|
+
started: Date.now() - 4 * 60 * 60 * 1000,
|
|
243
|
+
lastActivity: Date.now() - 1000,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
faro.api.pushEvent(EVENT_NAVIGATION, { screen: 'checkout' });
|
|
247
|
+
|
|
248
|
+
const nextSession = getVolatileSession();
|
|
249
|
+
const navigationEvent = transport.items.find(
|
|
250
|
+
(item) => item.type === TransportItemType.EVENT && item.payload.name === EVENT_NAVIGATION
|
|
251
|
+
);
|
|
252
|
+
expect(nextSession.sessionId).not.toBe(previousSession.sessionId);
|
|
253
|
+
expect(nextSession.sessionMeta?.attributes?.['previousSession']).toBe(previousSession.sessionId);
|
|
254
|
+
expect(navigationEvent?.meta.session?.id).toBe(nextSession.sessionId);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('refreshes inactivity for navigation and tracked user actions', async () => {
|
|
258
|
+
transport = new MockTransport();
|
|
259
|
+
const faro = await initializeFaro(
|
|
260
|
+
mockConfig({
|
|
261
|
+
url: 'http://localhost:12345/collect',
|
|
262
|
+
transports: [transport],
|
|
263
|
+
instrumentations: [new SessionInstrumentation()],
|
|
264
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
const session = getVolatileSession();
|
|
268
|
+
|
|
269
|
+
VolatileSessionsManager.storeUserSession({ ...session, lastActivity: Date.now() - 10 * 60 * 1000 });
|
|
270
|
+
faro.api.pushEvent(EVENT_NAVIGATION, { screen: 'checkout' });
|
|
271
|
+
expect(VolatileSessionsManager.fetchUserSession()?.lastActivity).toBe(Date.now());
|
|
272
|
+
|
|
273
|
+
jest.advanceTimersByTime(1000);
|
|
274
|
+
VolatileSessionsManager.storeUserSession({ ...session, lastActivity: Date.now() - 10 * 60 * 1000 });
|
|
275
|
+
const action = faro.api.startUserAction('checkout');
|
|
276
|
+
faro.api.pushEvent('background_work_complete');
|
|
277
|
+
action?.end();
|
|
278
|
+
expect(VolatileSessionsManager.fetchUserSession()?.lastActivity).toBe(Date.now());
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('rotates a foreground return at the exact boundary before sending it', async () => {
|
|
282
|
+
transport = new MockTransport();
|
|
283
|
+
const faro = await initializeFaro(
|
|
284
|
+
mockConfig({
|
|
285
|
+
url: 'http://localhost:12345/collect',
|
|
286
|
+
transports: [transport],
|
|
287
|
+
instrumentations: [new SessionInstrumentation()],
|
|
288
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
const previousSession = getVolatileSession();
|
|
292
|
+
VolatileSessionsManager.storeUserSession({
|
|
293
|
+
...previousSession,
|
|
294
|
+
lastActivity: Date.now() - 15 * 60 * 1000,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
faro.api.pushEvent(EVENT_APP_STATE_CHANGED, { fromState: 'background', toState: 'active' });
|
|
298
|
+
|
|
299
|
+
const nextSession = getVolatileSession();
|
|
300
|
+
const lifecycleEvent = transport.items.find(
|
|
301
|
+
(item) => item.type === TransportItemType.EVENT && item.payload.name === EVENT_APP_STATE_CHANGED
|
|
302
|
+
);
|
|
303
|
+
expect(nextSession.sessionId).not.toBe(previousSession.sessionId);
|
|
304
|
+
expect(lifecycleEvent?.meta.session?.id).toBe(nextSession.sessionId);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('preserves the crash-time session on recovered crashes', async () => {
|
|
308
|
+
transport = new MockTransport();
|
|
309
|
+
const faro = await initializeFaro(
|
|
310
|
+
mockConfig({
|
|
311
|
+
url: 'http://localhost:12345/collect',
|
|
312
|
+
transports: [transport],
|
|
313
|
+
instrumentations: [new SessionInstrumentation()],
|
|
314
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
const currentSession = getVolatileSession();
|
|
318
|
+
const recoveredCrash: TransportItem = {
|
|
319
|
+
type: TransportItemType.EXCEPTION,
|
|
320
|
+
meta: {
|
|
321
|
+
session: {
|
|
322
|
+
id: 'crashed-session',
|
|
323
|
+
attributes: { isSampled: 'true' },
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
payload: {
|
|
327
|
+
type: 'crash',
|
|
328
|
+
value: 'native crash',
|
|
329
|
+
fatal: true,
|
|
330
|
+
timestamp: new Date().toISOString(),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const result = faro.transports
|
|
335
|
+
.getBeforeSendHooks()
|
|
336
|
+
.reduce<TransportItem | null>((item, hook) => (item == null ? null : hook(item)), recoveredCrash);
|
|
337
|
+
|
|
338
|
+
expect(result?.meta.session).toStrictEqual({ id: 'crashed-session' });
|
|
339
|
+
expect(VolatileSessionsManager.fetchUserSession()).toStrictEqual(currentSession);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('uses the live sampling decision when an older recovered crash has no sampling metadata', async () => {
|
|
343
|
+
transport = new MockTransport();
|
|
344
|
+
const faro = await initializeFaro(
|
|
345
|
+
mockConfig({
|
|
346
|
+
url: 'http://localhost:12345/collect',
|
|
347
|
+
transports: [transport],
|
|
348
|
+
instrumentations: [new SessionInstrumentation()],
|
|
349
|
+
sessionTracking: {
|
|
350
|
+
enabled: true,
|
|
351
|
+
persistent: false,
|
|
352
|
+
sampling: new SamplingRate(0),
|
|
353
|
+
},
|
|
354
|
+
})
|
|
355
|
+
);
|
|
356
|
+
const currentSession = getVolatileSession();
|
|
357
|
+
const recoveredCrash: TransportItem = {
|
|
358
|
+
type: TransportItemType.EXCEPTION,
|
|
359
|
+
meta: { session: { id: 'crashed-session' } },
|
|
360
|
+
payload: {
|
|
361
|
+
type: 'crash',
|
|
362
|
+
value: 'native crash',
|
|
363
|
+
fatal: true,
|
|
364
|
+
timestamp: new Date().toISOString(),
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const result = faro.transports
|
|
369
|
+
.getBeforeSendHooks()
|
|
370
|
+
.reduce<TransportItem | null>((item, hook) => (item == null ? null : hook(item)), recoveredCrash);
|
|
371
|
+
|
|
372
|
+
expect(result).toBeNull();
|
|
373
|
+
expect(VolatileSessionsManager.fetchUserSession()).toStrictEqual(currentSession);
|
|
374
|
+
});
|
|
129
375
|
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { EVENT_SESSION_START, initializeFaro } from '@grafana/faro-core';
|
|
2
|
+
import { mockConfig, MockTransport } from '@grafana/faro-test-utils';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
clearDirectSessionActivityHandler,
|
|
6
|
+
notifySessionActivity,
|
|
7
|
+
registerDirectSessionActivityHandler,
|
|
8
|
+
} from './directSessionActivity';
|
|
9
|
+
import { SessionInstrumentation } from './index';
|
|
10
|
+
import type { FaroUserSession } from './sessionManager';
|
|
11
|
+
import { VolatileSessionsManager } from './sessionManager/VolatileSessionManager';
|
|
12
|
+
|
|
13
|
+
function getVolatileSession(): FaroUserSession {
|
|
14
|
+
const session = VolatileSessionsManager.fetchUserSession();
|
|
15
|
+
if (session == null) {
|
|
16
|
+
throw new Error('Expected an active volatile session.');
|
|
17
|
+
}
|
|
18
|
+
return session;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('direct session activity', () => {
|
|
22
|
+
const instrumentations: SessionInstrumentation[] = [];
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.useFakeTimers();
|
|
26
|
+
jest.setSystemTime(new Date('2026-08-20T12:00:00.000Z'));
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
VolatileSessionsManager.removeUserSession();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
instrumentations.splice(0).forEach((instrumentation) => instrumentation.unpatch());
|
|
33
|
+
clearDirectSessionActivityHandler();
|
|
34
|
+
VolatileSessionsManager.removeUserSession();
|
|
35
|
+
jest.useRealTimers();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does nothing before session tracking initializes', () => {
|
|
39
|
+
expect(VolatileSessionsManager.fetchUserSession()).toBeNull();
|
|
40
|
+
expect(() => notifySessionActivity()).not.toThrow();
|
|
41
|
+
expect(VolatileSessionsManager.fetchUserSession()).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('does not let an activity handler failure escape into the app', () => {
|
|
45
|
+
const unregister = registerDirectSessionActivityHandler(() => {
|
|
46
|
+
throw new Error('activity failed');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(() => notifySessionActivity()).not.toThrow();
|
|
50
|
+
unregister();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('refreshes an active session without emitting telemetry', async () => {
|
|
54
|
+
const transport = new MockTransport();
|
|
55
|
+
const instrumentation = new SessionInstrumentation();
|
|
56
|
+
instrumentations.push(instrumentation);
|
|
57
|
+
await initializeFaro(
|
|
58
|
+
mockConfig({
|
|
59
|
+
transports: [transport],
|
|
60
|
+
instrumentations: [instrumentation],
|
|
61
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
const session = getVolatileSession();
|
|
65
|
+
VolatileSessionsManager.storeUserSession({
|
|
66
|
+
...session,
|
|
67
|
+
lastActivity: Date.now() - 10 * 60 * 1000,
|
|
68
|
+
});
|
|
69
|
+
transport.items = [];
|
|
70
|
+
|
|
71
|
+
notifySessionActivity();
|
|
72
|
+
|
|
73
|
+
expect(VolatileSessionsManager.fetchUserSession()).toMatchObject({
|
|
74
|
+
sessionId: session.sessionId,
|
|
75
|
+
lastActivity: Date.now(),
|
|
76
|
+
});
|
|
77
|
+
expect(transport.items).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rotates at the inactivity boundary before later telemetry is attributed', async () => {
|
|
81
|
+
const transport = new MockTransport();
|
|
82
|
+
const instrumentation = new SessionInstrumentation();
|
|
83
|
+
instrumentations.push(instrumentation);
|
|
84
|
+
const faro = await initializeFaro(
|
|
85
|
+
mockConfig({
|
|
86
|
+
transports: [transport],
|
|
87
|
+
instrumentations: [instrumentation],
|
|
88
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
const previousSession = getVolatileSession();
|
|
92
|
+
VolatileSessionsManager.storeUserSession({
|
|
93
|
+
...previousSession,
|
|
94
|
+
lastActivity: Date.now() - 15 * 60 * 1000,
|
|
95
|
+
});
|
|
96
|
+
transport.items = [];
|
|
97
|
+
|
|
98
|
+
notifySessionActivity();
|
|
99
|
+
faro.api.pushEvent('pressed_button');
|
|
100
|
+
|
|
101
|
+
const nextSession = getVolatileSession();
|
|
102
|
+
const sessionStartEvents = transport.items.filter(
|
|
103
|
+
(item) => item.type === 'event' && item.payload.name === EVENT_SESSION_START
|
|
104
|
+
);
|
|
105
|
+
const pressedButton = transport.items.find(
|
|
106
|
+
(item) => item.type === 'event' && item.payload.name === 'pressed_button'
|
|
107
|
+
);
|
|
108
|
+
expect(nextSession.sessionId).not.toBe(previousSession.sessionId);
|
|
109
|
+
expect(nextSession.sessionMeta?.attributes?.['previousSession']).toBe(previousSession.sessionId);
|
|
110
|
+
expect(sessionStartEvents).toHaveLength(1);
|
|
111
|
+
expect(pressedButton?.meta.session?.id).toBe(nextSession.sessionId);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not retain a handler when a later initialization disables session tracking', async () => {
|
|
115
|
+
const enabledInstrumentation = new SessionInstrumentation();
|
|
116
|
+
const disabledInstrumentation = new SessionInstrumentation();
|
|
117
|
+
instrumentations.push(enabledInstrumentation, disabledInstrumentation);
|
|
118
|
+
await initializeFaro(
|
|
119
|
+
mockConfig({
|
|
120
|
+
instrumentations: [enabledInstrumentation],
|
|
121
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
const session = getVolatileSession();
|
|
125
|
+
const lastActivity = Date.now() - 10 * 60 * 1000;
|
|
126
|
+
VolatileSessionsManager.storeUserSession({ ...session, lastActivity });
|
|
127
|
+
|
|
128
|
+
await initializeFaro(
|
|
129
|
+
mockConfig({
|
|
130
|
+
instrumentations: [disabledInstrumentation],
|
|
131
|
+
sessionTracking: { enabled: false, persistent: false },
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
notifySessionActivity();
|
|
135
|
+
|
|
136
|
+
expect(VolatileSessionsManager.fetchUserSession()?.lastActivity).toBe(lastActivity);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('stops recording direct activity after session instrumentation is unpatched', async () => {
|
|
140
|
+
const instrumentation = new SessionInstrumentation();
|
|
141
|
+
instrumentations.push(instrumentation);
|
|
142
|
+
await initializeFaro(
|
|
143
|
+
mockConfig({
|
|
144
|
+
transports: [new MockTransport()],
|
|
145
|
+
instrumentations: [instrumentation],
|
|
146
|
+
sessionTracking: { enabled: true, persistent: false },
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
const session = getVolatileSession();
|
|
150
|
+
const lastActivity = Date.now() - 10 * 60 * 1000;
|
|
151
|
+
VolatileSessionsManager.storeUserSession({ ...session, lastActivity });
|
|
152
|
+
|
|
153
|
+
instrumentation.unpatch();
|
|
154
|
+
notifySessionActivity();
|
|
155
|
+
|
|
156
|
+
expect(VolatileSessionsManager.fetchUserSession()?.lastActivity).toBe(lastActivity);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { faro } from '@grafana/faro-core';
|
|
2
|
+
|
|
3
|
+
type DirectSessionActivityHandler = () => void;
|
|
4
|
+
|
|
5
|
+
let directSessionActivityHandler: DirectSessionActivityHandler | undefined;
|
|
6
|
+
|
|
7
|
+
/** Clears a handler left by a previous SDK initialization. */
|
|
8
|
+
export function clearDirectSessionActivityHandler(): void {
|
|
9
|
+
directSessionActivityHandler = undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Registers the active session manager as the direct-activity consumer. */
|
|
13
|
+
export function registerDirectSessionActivityHandler(handler: DirectSessionActivityHandler): () => void {
|
|
14
|
+
directSessionActivityHandler = handler;
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
if (directSessionActivityHandler === handler) {
|
|
18
|
+
directSessionActivityHandler = undefined;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Refreshes session inactivity without emitting telemetry.
|
|
25
|
+
*
|
|
26
|
+
* This is a no-op until session tracking has initialized. Use it for direct
|
|
27
|
+
* interactions that do not pass through `FaroSessionActivityBoundary`.
|
|
28
|
+
*/
|
|
29
|
+
export function notifySessionActivity(): void {
|
|
30
|
+
try {
|
|
31
|
+
directSessionActivityHandler?.();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Session tracking must never interrupt the interaction being observed.
|
|
34
|
+
faro.unpatchedConsole?.warn?.('Failed to record session activity:', error);
|
|
35
|
+
}
|
|
36
|
+
}
|