From 328b2d3bcb4acb4faa26db0db9161e78c0ee210d Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Wed, 12 Feb 2025 12:23:00 +0100 Subject: [PATCH 01/11] wip tyopings --- packages/core/src/config/types.ts | 34 +++++++++++++++++++++++++++++++ packages/core/src/metas/types.ts | 13 +++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 4a6334970..d95c3ff9e 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -193,6 +193,9 @@ export interface Config

{ errorSerializer?: LogArgsSerializer; }; + /** + * Configuration for the page tracking + */ pageTracking?: { /** * The page meta for initial page settings @@ -204,6 +207,37 @@ export interface Config

{ */ generatePageId?: (location: Location) => string; }; + + grafanaCloud?: GrafanaCloudOnlyConfigurations; + // /** + // * Configuration for the location tracking (Grafana cloud only) + // */ + // locationTracking?: { + // /** + // * Enable or disable geolocation tracking. + // * Geolocation tracking must be enabled in the Grafana Cloud settings first. + // * It cannot be enabled solely on the client side. + // * This option allows control over tracking on the client side to comply with user + // * privacy requirements. + // */ + // enabled?: boolean; + // }; } export type Patterns = Array; + +type GrafanaCloudOnlyConfigurations = { + /** + * Configuration for the location tracking (Grafana cloud only) + */ + locationTracking?: { + /** + * Enable or disable geolocation tracking. + * Geolocation tracking must be enabled in the Grafana Cloud settings first. + * It cannot be enabled solely on the client side. + * This option allows control over tracking on the client side to comply with user + * privacy requirements. + */ + enabled?: boolean; + }; +}; diff --git a/packages/core/src/metas/types.ts b/packages/core/src/metas/types.ts index 873895547..7d4f5d384 100644 --- a/packages/core/src/metas/types.ts +++ b/packages/core/src/metas/types.ts @@ -91,8 +91,19 @@ export interface Meta { } /** - * Overrides are instructions that allow the receiver to override certain properties. + * MetaOverrides are instructions that allow the receiver to override certain properties (Grafana Cloud only). */ export type MetaOverrides = { + /** + * New service name + */ serviceName?: string; }; + +/** + * Commands instruct the receiver to change behavior. + * These commands can be used to modify tracking settings or other configurations. + */ +export type MetaCommands = { + geolocationTrackingEnabled?: boolean; +}; From ca49ffcfa78b2f4f5cc6bc0c0d49e030f81ada74 Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Wed, 12 Feb 2025 15:51:34 +0100 Subject: [PATCH 02/11] add location tracking command --- demo/src/client/faro/initialize.ts | 4 +++ packages/core/src/config/types.ts | 25 +++------------- packages/core/src/metas/types.ts | 1 + .../web-sdk/src/config/makeCoreConfig.test.ts | 29 +++++++++++++++++++ packages/web-sdk/src/config/makeCoreConfig.ts | 27 ++++++++++++++++- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/demo/src/client/faro/initialize.ts b/demo/src/client/faro/initialize.ts index 558b39891..1fa4da1d4 100644 --- a/demo/src/client/faro/initialize.ts +++ b/demo/src/client/faro/initialize.ts @@ -42,6 +42,10 @@ export function initializeFaro(): Faro { version: env.package.version, environment: env.mode.name, }, + + locationTracking: { + enabled: true, + }, }); faro.api.pushLog(['Faro was initialized']); diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index d95c3ff9e..a9cf9a0a3 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -127,7 +127,7 @@ export interface Config

{ */ onSessionChange?: (oldSession: MetaSession | null, newSession: MetaSession) => void; /** - * Then sampling rate for the session based sampler (default: 1). If a session is not part of a sample, no signals for this session are tracked. + * Then sampling rate for the session based smpler (default: 1). If a session is not part of a sample, no signals for this session are tracked. */ samplingRate?: number; /** @@ -208,25 +208,6 @@ export interface Config

{ generatePageId?: (location: Location) => string; }; - grafanaCloud?: GrafanaCloudOnlyConfigurations; - // /** - // * Configuration for the location tracking (Grafana cloud only) - // */ - // locationTracking?: { - // /** - // * Enable or disable geolocation tracking. - // * Geolocation tracking must be enabled in the Grafana Cloud settings first. - // * It cannot be enabled solely on the client side. - // * This option allows control over tracking on the client side to comply with user - // * privacy requirements. - // */ - // enabled?: boolean; - // }; -} - -export type Patterns = Array; - -type GrafanaCloudOnlyConfigurations = { /** * Configuration for the location tracking (Grafana cloud only) */ @@ -240,4 +221,6 @@ type GrafanaCloudOnlyConfigurations = { */ enabled?: boolean; }; -}; +} + +export type Patterns = Array; diff --git a/packages/core/src/metas/types.ts b/packages/core/src/metas/types.ts index 7d4f5d384..eb833017f 100644 --- a/packages/core/src/metas/types.ts +++ b/packages/core/src/metas/types.ts @@ -45,6 +45,7 @@ export interface MetaSession { id?: string; attributes?: MetaAttributes; overrides?: MetaOverrides; + commands?: MetaCommands; } export interface MetaPage { diff --git a/packages/web-sdk/src/config/makeCoreConfig.test.ts b/packages/web-sdk/src/config/makeCoreConfig.test.ts index c94464bf7..c19d90b0e 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.test.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.test.ts @@ -107,4 +107,33 @@ describe('config', () => { expect(config.ignoreUrls[0].test(url)).toBe(true); } ); + + it('updates the command object in the session config with the locationTracking.enabled value', () => { + const browserConfig = { + url: 'http://example.com/my-collector', + app: {}, + }; + + let config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: true } }); + expect(config?.sessionTracking?.session?.commands).toHaveProperty('geolocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.commands?.geolocationTrackingEnabled).toBe(true); + + config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: false } }); + expect(config?.sessionTracking?.session?.commands).toHaveProperty('geolocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.commands?.geolocationTrackingEnabled).toBe(false); + + // Also test that the session object is not created or mutated if locationTracking is not enabled + config = makeCoreConfig(browserConfig); + expect(config?.sessionTracking?.session).toBeUndefined(); + + const sessionMeta = { id: 'test', attributes: { foo: 'bar' } }; + config = makeCoreConfig({ + ...browserConfig, + sessionTracking: { session: sessionMeta }, + }); + + expect(config?.sessionTracking?.session).toBeDefined(); + expect(config?.sessionTracking?.session?.commands).toBeUndefined(); + expect(config?.sessionTracking?.session).toStrictEqual(sessionMeta); + }); }); diff --git a/packages/web-sdk/src/config/makeCoreConfig.ts b/packages/web-sdk/src/config/makeCoreConfig.ts index a871290a3..08d6ff524 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.ts @@ -5,9 +5,10 @@ import { defaultInternalLoggerLevel, defaultLogArgsSerializer, defaultUnpatchedConsole, + isEmpty, isObject, } from '@grafana/faro-core'; -import type { Config, MetaItem, Transport } from '@grafana/faro-core'; +import type { Config, MetaItem, MetaSession, Transport } from '@grafana/faro-core'; import { defaultEventDomain } from '../consts'; import { parseStacktrace } from '../instrumentations'; @@ -53,6 +54,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config { trackWebVitalsAttribution, user, view, + locationTracking, // properties with default values dedupe = true, @@ -94,6 +96,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config { sessionTracking: { ...defaultSessionTrackingConfig, ...sessionTracking, + ...crateSessionMetaWithAttachedControlCommands({ locationTracking, sessionTracking }), }, user, view, @@ -119,3 +122,25 @@ function createDefaultMetas(browserConfig: BrowserConfig): MetaItem[] { return initialMetas; } + +function crateSessionMetaWithAttachedControlCommands({ + locationTracking, + sessionTracking, +}: Pick): { session: MetaSession } | {} { + const commands: MetaSession['commands'] = {}; + + if (locationTracking?.enabled != null) { + commands.geolocationTrackingEnabled = locationTracking.enabled; + } + + if (isEmpty(commands)) { + return {}; + } + + return { + session: { + ...(sessionTracking?.session ?? {}), + commands, + }, + }; +} From bbc0ef1d7dc38c29f7b52bf70cdb26ebbb7392ba Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Wed, 12 Feb 2025 18:13:31 +0100 Subject: [PATCH 03/11] add tests --- packages/core/src/metas/types.ts | 16 +-- .../web-sdk/src/config/makeCoreConfig.test.ts | 10 +- packages/web-sdk/src/config/makeCoreConfig.ts | 8 +- .../session/instrumentation.test.ts | 128 ++++++++++++++++++ .../session/instrumentation.ts | 9 +- 5 files changed, 153 insertions(+), 18 deletions(-) diff --git a/packages/core/src/metas/types.ts b/packages/core/src/metas/types.ts index eb833017f..f7d4f8b87 100644 --- a/packages/core/src/metas/types.ts +++ b/packages/core/src/metas/types.ts @@ -45,7 +45,6 @@ export interface MetaSession { id?: string; attributes?: MetaAttributes; overrides?: MetaOverrides; - commands?: MetaCommands; } export interface MetaPage { @@ -96,15 +95,16 @@ export interface Meta { */ export type MetaOverrides = { /** - * New service name + * New service name (Grafana Cloud only) */ serviceName?: string; -}; -/** - * Commands instruct the receiver to change behavior. - * These commands can be used to modify tracking settings or other configurations. - */ -export type MetaCommands = { + /** + * Enable or disable geolocation tracking (Grafana Cloud only). + * Geolocation tracking must be enabled in the Grafana Cloud settings first. + * It cannot be enabled solely on the client side. + * This option allows control over tracking on the client side to comply with user + * privacy requirements. + */ geolocationTrackingEnabled?: boolean; }; diff --git a/packages/web-sdk/src/config/makeCoreConfig.test.ts b/packages/web-sdk/src/config/makeCoreConfig.test.ts index c19d90b0e..be2c72153 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.test.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.test.ts @@ -115,12 +115,12 @@ describe('config', () => { }; let config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: true } }); - expect(config?.sessionTracking?.session?.commands).toHaveProperty('geolocationTrackingEnabled'); - expect(config?.sessionTracking?.session?.commands?.geolocationTrackingEnabled).toBe(true); + expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geolocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.overrides?.geolocationTrackingEnabled).toBe(true); config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: false } }); - expect(config?.sessionTracking?.session?.commands).toHaveProperty('geolocationTrackingEnabled'); - expect(config?.sessionTracking?.session?.commands?.geolocationTrackingEnabled).toBe(false); + expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geolocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.overrides?.geolocationTrackingEnabled).toBe(false); // Also test that the session object is not created or mutated if locationTracking is not enabled config = makeCoreConfig(browserConfig); @@ -133,7 +133,7 @@ describe('config', () => { }); expect(config?.sessionTracking?.session).toBeDefined(); - expect(config?.sessionTracking?.session?.commands).toBeUndefined(); + expect(config?.sessionTracking?.session?.overrides).toBeUndefined(); expect(config?.sessionTracking?.session).toStrictEqual(sessionMeta); }); }); diff --git a/packages/web-sdk/src/config/makeCoreConfig.ts b/packages/web-sdk/src/config/makeCoreConfig.ts index 08d6ff524..7157c4237 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.ts @@ -127,20 +127,20 @@ function crateSessionMetaWithAttachedControlCommands({ locationTracking, sessionTracking, }: Pick): { session: MetaSession } | {} { - const commands: MetaSession['commands'] = {}; + const overrides: MetaSession['overrides'] = {}; if (locationTracking?.enabled != null) { - commands.geolocationTrackingEnabled = locationTracking.enabled; + overrides.geolocationTrackingEnabled = locationTracking.enabled; } - if (isEmpty(commands)) { + if (isEmpty(overrides)) { return {}; } return { session: { ...(sessionTracking?.session ?? {}), - commands, + overrides, }, }; } diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts index 72880f080..f3e953b04 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts @@ -691,4 +691,132 @@ describe('SessionInstrumentation', () => { }, }); }); + + it('Adds the overrides from the initial sessionMeta added to the Faro configuration.', () => { + const mockSessionMeta: MetaSession = { + id: 'new-session', + attributes: { + foo: 'bar', + isSampled: 'true', + }, + overrides: { + serviceName: 'my-service', + }, + }; + + const { metas } = initializeFaro( + mockConfig({ + instrumentations: [new SessionInstrumentation()], + sessionTracking: { + enabled: true, + session: mockSessionMeta, + samplingRate: 1, // default + }, + }) + ); + + expect(metas.value.session).toStrictEqual(mockSessionMeta); + }); + + it('Uses the overrides from the stored user session on resumed sessions.', () => { + const transport = new MockTransport(); + + const sessionId = '123'; + + const initialSessionMeta: MetaSession = { + id: sessionId, + attributes: { + foo: 'bar', + isSampled: 'true', + }, + overrides: { + serviceName: 'my-service', + }, + }; + + const storedUserSession: FaroUserSession = { + ...createUserSessionObject({ + sessionId, + }), + sessionMeta: initialSessionMeta, + }; + + mockStorage[STORAGE_KEY] = JSON.stringify(storedUserSession); + + expect(JSON.parse(mockStorage[STORAGE_KEY]).sessionMeta).toStrictEqual(initialSessionMeta); + + initializeFaro( + mockConfig({ + transports: [transport], + instrumentations: [new SessionInstrumentation()], + sessionTracking: { + enabled: true, + session: { + overrides: { + serviceName: 'do-not-use-this-override-because-it-should-be-overwritten-by-the-one-from-storage', + }, + }, + }, + }) + ); + + const sessionFromStorage: FaroUserSession = JSON.parse(mockStorage[STORAGE_KEY]); + + expect(sessionFromStorage.sessionId).toBe(sessionId); + expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); + }); + + it('Uses the overrides from the config if the stored session is invalid.', () => { + const transport = new MockTransport(); + + const sessionId = '123'; + const startTimeValid = dateNow() - 5 * 60 * 1000; + const lastActivityInvalid = dateNow() - SESSION_EXPIRATION_TIME; + + const storedSessionMeta: MetaSession = { + id: sessionId, + attributes: { + foo: 'bar', + isSampled: 'true', + }, + overrides: { + serviceName: 'my-service', + }, + }; + + const storedUserSession: FaroUserSession = { + ...createUserSessionObject({ + sessionId, + started: startTimeValid, + lastActivity: lastActivityInvalid, + }), + sessionMeta: storedSessionMeta, + }; + + mockStorage[STORAGE_KEY] = JSON.stringify(storedUserSession); + + expect(JSON.parse(mockStorage[STORAGE_KEY]).sessionMeta).toStrictEqual(storedSessionMeta); + + const initialSessionMeta = { + ...storedSessionMeta, + overrides: { + serviceName: 'do-not-use-this-override-because-it-should-be-overwritten-by-the-one-from-storage', + }, + }; + + initializeFaro( + mockConfig({ + transports: [transport], + instrumentations: [new SessionInstrumentation()], + sessionTracking: { + enabled: true, + session: initialSessionMeta, + }, + }) + ); + + const sessionFromStorage: FaroUserSession = JSON.parse(mockStorage[STORAGE_KEY]); + + expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); + }); }); diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.ts index 0251bcb47..6cef29af4 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.ts @@ -77,8 +77,10 @@ export class SessionInstrumentation extends BaseInstrumentation { }); const userSessionMeta = userSession?.sessionMeta; + const overrides = userSessionMeta?.overrides ?? sessionsConfig.session?.overrides; initialSession.sessionMeta = { + ...sessionsConfig.session, id: sessionId, attributes: { ...sessionsConfig.session?.attributes, @@ -86,7 +88,8 @@ export class SessionInstrumentation extends BaseInstrumentation { // For valid resumed sessions we do not want to recalculate the sampling decision on each init phase. isSampled: initialSession.isSampled.toString(), }, - overrides: userSessionMeta?.overrides, + // Resumed session we want to keep the previous overrides + ...(overrides ? { overrides } : {}), }; lifecycleType = EVENT_SESSION_RESUME; @@ -98,12 +101,16 @@ export class SessionInstrumentation extends BaseInstrumentation { isSampled: isSampled(), }); + const overrides = sessionsConfig.session?.overrides; + initialSession.sessionMeta = { id: sessionId, attributes: { isSampled: initialSession.isSampled.toString(), ...sessionsConfig.session?.attributes, }, + // new session we don't care about previous overrides + ...(overrides ? { overrides } : {}), }; lifecycleType = EVENT_SESSION_START; From 500837b98fcb7edfd47cab9680b096ca4fed615f Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Thu, 13 Feb 2025 14:16:22 +0100 Subject: [PATCH 04/11] cleanup --- demo/src/client/faro/initialize.ts | 1 - packages/core/src/api/meta/initialize.ts | 2 +- packages/core/src/api/meta/types.ts | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/demo/src/client/faro/initialize.ts b/demo/src/client/faro/initialize.ts index 1fa4da1d4..66c268a98 100644 --- a/demo/src/client/faro/initialize.ts +++ b/demo/src/client/faro/initialize.ts @@ -2,7 +2,6 @@ import { createRoutesFromChildren, matchRoutes, Routes, useLocation, useNavigati import { initializeFaro as coreInit, - genShortID, getWebInstrumentations, ReactIntegration, ReactRouterVersion, diff --git a/packages/core/src/api/meta/initialize.ts b/packages/core/src/api/meta/initialize.ts index f93429607..6bb933bfd 100644 --- a/packages/core/src/api/meta/initialize.ts +++ b/packages/core/src/api/meta/initialize.ts @@ -42,7 +42,7 @@ export function initializeMetaAPI( metaSession = { session: { - // if session is empty, session manager force creates a new session + // if session is undefined, session manager force creates a new session ...(isEmpty(session) ? undefined : session), ...(overrides ? { overrides } : {}), }, diff --git a/packages/core/src/api/meta/types.ts b/packages/core/src/api/meta/types.ts index 2670b21fd..0ed57cc83 100644 --- a/packages/core/src/api/meta/types.ts +++ b/packages/core/src/api/meta/types.ts @@ -1,12 +1,14 @@ import type { MetaOverrides, MetaPage, MetaSession, MetaUser, MetaView } from '../../metas'; +type OverridesAvailableThroughApi = Pick; + export interface MetaAPI { setUser: (user?: MetaUser | undefined) => void; resetUser: () => void; setSession: ( session?: MetaSession | undefined, options?: { - overrides: MetaOverrides; + overrides: OverridesAvailableThroughApi; } ) => void; resetSession: () => void; @@ -14,13 +16,12 @@ export interface MetaAPI { setView: ( view?: MetaView | undefined, options?: { - overrides: MetaOverrides; + overrides: OverridesAvailableThroughApi; } ) => void; getView: () => MetaView | undefined; /** * If a string is provided, it will be used as the page id. - * @returns */ setPage: (page?: MetaPage | string | undefined) => void; getPage: () => MetaPage | undefined; From c0024ee3218081dca1b37c904f199d85f30cd8b5 Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Thu, 13 Feb 2025 17:26:29 +0100 Subject: [PATCH 05/11] correctly create overrides object --- packages/core/src/api/meta/initialize.ts | 12 ++++++-- packages/web-sdk/src/config/makeCoreConfig.ts | 4 +-- .../session/instrumentation.ts | 29 ++++++++++--------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/core/src/api/meta/initialize.ts b/packages/core/src/api/meta/initialize.ts index 6bb933bfd..2ee49c717 100644 --- a/packages/core/src/api/meta/initialize.ts +++ b/packages/core/src/api/meta/initialize.ts @@ -34,7 +34,15 @@ export function initializeMetaAPI( }; const setSession: MetaAPI['setSession'] = (session, options) => { - const overrides = options?.overrides; + const newOverrides = options?.overrides; + const overrides = newOverrides + ? { + overrides: { + ...metaSession?.session?.overrides, + ...newOverrides, + }, + } + : {}; if (metaSession) { metas.remove(metaSession); @@ -44,7 +52,7 @@ export function initializeMetaAPI( session: { // if session is undefined, session manager force creates a new session ...(isEmpty(session) ? undefined : session), - ...(overrides ? { overrides } : {}), + ...overrides, }, }; diff --git a/packages/web-sdk/src/config/makeCoreConfig.ts b/packages/web-sdk/src/config/makeCoreConfig.ts index 7157c4237..03712c7a3 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.ts @@ -96,7 +96,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config { sessionTracking: { ...defaultSessionTrackingConfig, ...sessionTracking, - ...crateSessionMetaWithAttachedControlCommands({ locationTracking, sessionTracking }), + ...crateSessionMeta({ locationTracking, sessionTracking }), }, user, view, @@ -123,7 +123,7 @@ function createDefaultMetas(browserConfig: BrowserConfig): MetaItem[] { return initialMetas; } -function crateSessionMetaWithAttachedControlCommands({ +function crateSessionMeta({ locationTracking, sessionTracking, }: Pick): { session: MetaSession } | {} { diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.ts index 6cef29af4..2eee570d9 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.ts @@ -52,44 +52,47 @@ export class SessionInstrumentation extends BaseInstrumentation { initialSession: FaroUserSession; lifecycleType: LifecycleType; } { - let userSession: FaroUserSession | null = SessionManager.fetchUserSession(); + let storedUserSession: FaroUserSession | null = SessionManager.fetchUserSession(); - if (sessionsConfig.persistent && sessionsConfig.maxSessionPersistenceTime && userSession) { + if (sessionsConfig.persistent && sessionsConfig.maxSessionPersistenceTime && storedUserSession) { const now = dateNow(); - const shouldClearPersistentSession = userSession.lastActivity < now - sessionsConfig.maxSessionPersistenceTime; + const shouldClearPersistentSession = + storedUserSession.lastActivity < now - sessionsConfig.maxSessionPersistenceTime; if (shouldClearPersistentSession) { PersistentSessionsManager.removeUserSession(); - userSession = null; + storedUserSession = null; } } let lifecycleType: LifecycleType; let initialSession: FaroUserSession; - if (isUserSessionValid(userSession)) { - const sessionId = userSession?.sessionId; + if (isUserSessionValid(storedUserSession)) { + const sessionId = storedUserSession?.sessionId; initialSession = createUserSessionObject({ sessionId, - isSampled: userSession!.isSampled || false, - started: userSession?.started, + isSampled: storedUserSession!.isSampled || false, + started: storedUserSession?.started, }); - const userSessionMeta = userSession?.sessionMeta; - const overrides = userSessionMeta?.overrides ?? sessionsConfig.session?.overrides; + const storedUserSessionMeta = storedUserSession?.sessionMeta; + + // For resumed sessions we want to merge the previous overrides with the configured ones. + // If the same key is present in both, the new one will override the old one. + const overrides = { ...storedUserSessionMeta?.overrides, ...sessionsConfig.session?.overrides }; initialSession.sessionMeta = { ...sessionsConfig.session, id: sessionId, attributes: { ...sessionsConfig.session?.attributes, - ...userSessionMeta?.attributes, + ...storedUserSessionMeta?.attributes, // For valid resumed sessions we do not want to recalculate the sampling decision on each init phase. isSampled: initialSession.isSampled.toString(), }, - // Resumed session we want to keep the previous overrides - ...(overrides ? { overrides } : {}), + overrides, }; lifecycleType = EVENT_SESSION_RESUME; From d3d8e393f0ddda8e7d25881e3f52eb684a9f7261 Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 10:38:49 +0100 Subject: [PATCH 06/11] rename property --- .../core/src/api/meta/initilialize.test.ts | 29 +++++++++++++++++++ packages/core/src/config/types.ts | 2 +- packages/core/src/metas/types.ts | 2 +- .../web-sdk/src/config/makeCoreConfig.test.ts | 16 +++++----- packages/web-sdk/src/config/makeCoreConfig.ts | 12 ++++---- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/core/src/api/meta/initilialize.test.ts b/packages/core/src/api/meta/initilialize.test.ts index f45499736..b18ecdb3d 100644 --- a/packages/core/src/api/meta/initilialize.test.ts +++ b/packages/core/src/api/meta/initilialize.test.ts @@ -78,6 +78,35 @@ describe('Meta API', () => { api.setSession(undefined, { overrides }); expect(api.getSession()).toEqual({ overrides }); }); + + it('merges the new overrides with the existing session meta overrides', () => { + const initialSession = { id: 'my-session' }; + + const { api } = initializeFaro( + mockConfig({ + sessionTracking: { + session: initialSession, + }, + geoLocationTracking: {}, + }) + ); + + expect(api.getSession()).toEqual(initialSession); + + const overrides = { serviceName: 'service-1' }; + + const newSession = { id: 'my-new-session' }; + api.setSession(newSession, { overrides }); + expect(api.getSession()).toEqual({ ...newSession, attributes: initialSession.attributes, overrides }); + + const newOverrides = { serviceName: 'service-2' }; + api.setSession({}, { overrides: newOverrides }); + expect(api.getSession()).toEqual({ + ...newSession, + attributes: initialSession.attributes, + overrides: newOverrides, + }); + }); }); describe('setPage / getPage', () => { diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index a9cf9a0a3..61f7df811 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -211,7 +211,7 @@ export interface Config

{ /** * Configuration for the location tracking (Grafana cloud only) */ - locationTracking?: { + geoLocationTracking?: { /** * Enable or disable geolocation tracking. * Geolocation tracking must be enabled in the Grafana Cloud settings first. diff --git a/packages/core/src/metas/types.ts b/packages/core/src/metas/types.ts index f7d4f8b87..db3ac6866 100644 --- a/packages/core/src/metas/types.ts +++ b/packages/core/src/metas/types.ts @@ -106,5 +106,5 @@ export type MetaOverrides = { * This option allows control over tracking on the client side to comply with user * privacy requirements. */ - geolocationTrackingEnabled?: boolean; + geoLocationTrackingEnabled?: boolean; }; diff --git a/packages/web-sdk/src/config/makeCoreConfig.test.ts b/packages/web-sdk/src/config/makeCoreConfig.test.ts index be2c72153..f49d8f014 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.test.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.test.ts @@ -108,21 +108,21 @@ describe('config', () => { } ); - it('updates the command object in the session config with the locationTracking.enabled value', () => { + it('updates the command object in the session config with the geoLocationTracking.enabled value', () => { const browserConfig = { url: 'http://example.com/my-collector', app: {}, }; - let config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: true } }); - expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geolocationTrackingEnabled'); - expect(config?.sessionTracking?.session?.overrides?.geolocationTrackingEnabled).toBe(true); + let config = makeCoreConfig({ ...browserConfig, geoLocationTracking: { enabled: true } }); + expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geoLocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.overrides?.geoLocationTrackingEnabled).toBe(true); - config = makeCoreConfig({ ...browserConfig, locationTracking: { enabled: false } }); - expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geolocationTrackingEnabled'); - expect(config?.sessionTracking?.session?.overrides?.geolocationTrackingEnabled).toBe(false); + config = makeCoreConfig({ ...browserConfig, geoLocationTracking: { enabled: false } }); + expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geoLocationTrackingEnabled'); + expect(config?.sessionTracking?.session?.overrides?.geoLocationTrackingEnabled).toBe(false); - // Also test that the session object is not created or mutated if locationTracking is not enabled + // Also test that the session object is not created or mutated if geoLocationTracking is not enabled config = makeCoreConfig(browserConfig); expect(config?.sessionTracking?.session).toBeUndefined(); diff --git a/packages/web-sdk/src/config/makeCoreConfig.ts b/packages/web-sdk/src/config/makeCoreConfig.ts index 03712c7a3..033235824 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.ts @@ -54,7 +54,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config { trackWebVitalsAttribution, user, view, - locationTracking, + geoLocationTracking, // properties with default values dedupe = true, @@ -96,7 +96,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config { sessionTracking: { ...defaultSessionTrackingConfig, ...sessionTracking, - ...crateSessionMeta({ locationTracking, sessionTracking }), + ...crateSessionMeta({ geoLocationTracking, sessionTracking }), }, user, view, @@ -124,13 +124,13 @@ function createDefaultMetas(browserConfig: BrowserConfig): MetaItem[] { } function crateSessionMeta({ - locationTracking, + geoLocationTracking, sessionTracking, -}: Pick): { session: MetaSession } | {} { +}: Pick): { session: MetaSession } | {} { const overrides: MetaSession['overrides'] = {}; - if (locationTracking?.enabled != null) { - overrides.geolocationTrackingEnabled = locationTracking.enabled; + if (geoLocationTracking?.enabled != null) { + overrides.geoLocationTrackingEnabled = geoLocationTracking.enabled; } if (isEmpty(overrides)) { From 19f2276e0a7ff0ee3c04a4d80a03299dc63b707d Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 10:49:40 +0100 Subject: [PATCH 07/11] typo --- packages/web-sdk/src/config/makeCoreConfig.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/web-sdk/src/config/makeCoreConfig.test.ts b/packages/web-sdk/src/config/makeCoreConfig.test.ts index f49d8f014..afa30d0d7 100644 --- a/packages/web-sdk/src/config/makeCoreConfig.test.ts +++ b/packages/web-sdk/src/config/makeCoreConfig.test.ts @@ -108,10 +108,13 @@ describe('config', () => { } ); - it('updates the command object in the session config with the geoLocationTracking.enabled value', () => { + it('updates the overrides object in the session config with the geoLocationTracking.enabled value', () => { const browserConfig = { url: 'http://example.com/my-collector', app: {}, + sessionTracking: { + session: { id: 'my-session' }, + }, }; let config = makeCoreConfig({ ...browserConfig, geoLocationTracking: { enabled: true } }); @@ -124,7 +127,7 @@ describe('config', () => { // Also test that the session object is not created or mutated if geoLocationTracking is not enabled config = makeCoreConfig(browserConfig); - expect(config?.sessionTracking?.session).toBeUndefined(); + expect(config?.sessionTracking?.session).toBeDefined(); const sessionMeta = { id: 'test', attributes: { foo: 'bar' } }; config = makeCoreConfig({ From 1e04d3a3f63b1ff59975da742c89f0071cc746a9 Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 11:37:07 +0100 Subject: [PATCH 08/11] meta api tests --- .../core/src/api/meta/initilialize.test.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/core/src/api/meta/initilialize.test.ts b/packages/core/src/api/meta/initilialize.test.ts index b18ecdb3d..995b61609 100644 --- a/packages/core/src/api/meta/initilialize.test.ts +++ b/packages/core/src/api/meta/initilialize.test.ts @@ -82,30 +82,35 @@ describe('Meta API', () => { it('merges the new overrides with the existing session meta overrides', () => { const initialSession = { id: 'my-session' }; - const { api } = initializeFaro( - mockConfig({ - sessionTracking: { - session: initialSession, - }, - geoLocationTracking: {}, - }) - ); + const mc = mockConfig({ + sessionTracking: { + session: initialSession, + }, + geoLocationTracking: { + enabled: false, + }, + }); - expect(api.getSession()).toEqual(initialSession); + // mockConfig is the result of calling makeCoreConfig in faro-web-sdk package. + // It it reads the geoLocationTracking properties it adds them to the sessionTracking.session.overrides object. + mc.sessionTracking!.session!.overrides = { geoLocationTrackingEnabled: false }; - const overrides = { serviceName: 'service-1' }; + const { api } = initializeFaro(mc); + expect(api.getSession()?.id).toEqual(initialSession.id); + expect(api.getSession()?.overrides).toBeDefined(); + expect(api.getSession()?.overrides).toStrictEqual({ geoLocationTrackingEnabled: false }); + + const overrides = { serviceName: 'service-1' }; const newSession = { id: 'my-new-session' }; api.setSession(newSession, { overrides }); - expect(api.getSession()).toEqual({ ...newSession, attributes: initialSession.attributes, overrides }); + expect(api.getSession()?.id).toEqual(newSession.id); + expect(api.getSession()?.overrides).toStrictEqual({ ...overrides, geoLocationTrackingEnabled: false }); const newOverrides = { serviceName: 'service-2' }; - api.setSession({}, { overrides: newOverrides }); - expect(api.getSession()).toEqual({ - ...newSession, - attributes: initialSession.attributes, - overrides: newOverrides, - }); + api.setSession(newSession, { overrides: newOverrides }); + expect(api.getSession()?.id).toEqual(newSession.id); + expect(api.getSession()?.overrides).toStrictEqual({ ...newOverrides, geoLocationTrackingEnabled: false }); }); }); From 72dd67fde571d769b85bdd1956756bbcb272410f Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 12:00:24 +0100 Subject: [PATCH 09/11] update session instrumentation overrides logic and add test --- .../session/instrumentation.test.ts | 52 ++++++++++++++++++- .../session/instrumentation.ts | 2 +- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts index f3e953b04..a1bca0196 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts @@ -766,7 +766,7 @@ describe('SessionInstrumentation', () => { expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); }); - it('Uses the overrides from the config if the stored session is invalid.', () => { + it('Uses only the overrides provided in the config if the stored session is invalid.', () => { const transport = new MockTransport(); const sessionId = '123'; @@ -819,4 +819,54 @@ describe('SessionInstrumentation', () => { expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); }); + + it('Initially configured session overrides take precedence over stored overrides when resuming a valid session.', () => { + const transport = new MockTransport(); + + const sessionId = '123'; + + const storedSessionMeta: MetaSession = { + id: sessionId, + attributes: { + foo: 'bar', + isSampled: 'true', + }, + overrides: { + serviceName: 'my-service', + }, + }; + + const storedUserSession: FaroUserSession = { + ...createUserSessionObject({ + sessionId, + }), + sessionMeta: storedSessionMeta, + }; + + mockStorage[STORAGE_KEY] = JSON.stringify(storedUserSession); + + expect(JSON.parse(mockStorage[STORAGE_KEY]).sessionMeta).toStrictEqual(storedSessionMeta); + + const initialSessionMeta = { + ...storedSessionMeta, + overrides: { + serviceName: 'do-not-use-this-override-because-it-should-be-overwritten-by-the-one-from-storage', + }, + }; + + initializeFaro( + mockConfig({ + transports: [transport], + instrumentations: [new SessionInstrumentation()], + sessionTracking: { + enabled: true, + session: initialSessionMeta, + }, + }) + ); + + const sessionFromStorage: FaroUserSession = JSON.parse(mockStorage[STORAGE_KEY]); + + expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); + }); }); diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.ts index 2eee570d9..db8e28f4f 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.ts @@ -81,7 +81,7 @@ export class SessionInstrumentation extends BaseInstrumentation { // For resumed sessions we want to merge the previous overrides with the configured ones. // If the same key is present in both, the new one will override the old one. - const overrides = { ...storedUserSessionMeta?.overrides, ...sessionsConfig.session?.overrides }; + const overrides = { ...sessionsConfig.session?.overrides, ...storedUserSessionMeta?.overrides }; initialSession.sessionMeta = { ...sessionsConfig.session, From fce3c6f0a93b5ea7273662e0a00484d55751ccec Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 12:42:24 +0100 Subject: [PATCH 10/11] update test --- .../session/instrumentation.test.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts index a1bca0196..c17b7788b 100644 --- a/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts +++ b/packages/web-sdk/src/instrumentations/session/instrumentation.test.ts @@ -823,23 +823,14 @@ describe('SessionInstrumentation', () => { it('Initially configured session overrides take precedence over stored overrides when resuming a valid session.', () => { const transport = new MockTransport(); - const sessionId = '123'; - const storedSessionMeta: MetaSession = { - id: sessionId, - attributes: { - foo: 'bar', - isSampled: 'true', - }, overrides: { serviceName: 'my-service', }, }; const storedUserSession: FaroUserSession = { - ...createUserSessionObject({ - sessionId, - }), + ...createUserSessionObject(), sessionMeta: storedSessionMeta, }; @@ -867,6 +858,6 @@ describe('SessionInstrumentation', () => { const sessionFromStorage: FaroUserSession = JSON.parse(mockStorage[STORAGE_KEY]); - expect(sessionFromStorage.sessionMeta).toStrictEqual(initialSessionMeta); + expect(sessionFromStorage.sessionMeta?.overrides).toStrictEqual(storedSessionMeta.overrides); }); }); From 55ec1a4bf15ea3b128ac55028eacd53fd45fccd5 Mon Sep 17 00:00:00 2001 From: "marco.schaefer" Date: Fri, 14 Feb 2025 12:42:34 +0100 Subject: [PATCH 11/11] review: fix typo --- packages/core/src/config/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 61f7df811..51173137f 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -127,7 +127,7 @@ export interface Config

{ */ onSessionChange?: (oldSession: MetaSession | null, newSession: MetaSession) => void; /** - * Then sampling rate for the session based smpler (default: 1). If a session is not part of a sample, no signals for this session are tracked. + * Then sampling rate for the session based sampler (default: 1). If a session is not part of a sample, no signals for this session are tracked. */ samplingRate?: number; /**