From 7999a458ed6e8c618f459c1f8e788906c154750c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 6 Feb 2024 14:02:45 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=85=F0=9F=91=8C=20add=20an=20e2e=20te?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/trackingConsent.scenario.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index 1da5367d69..0ace17fd02 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -59,4 +59,17 @@ describe('tracking consent', () => { expect(firstView.view.id).not.toEqual(lastView.view.id) expect(await findSessionCookie()).not.toEqual(initialSessionId) }) + + createTest('using setTrackingConsent before init overrides the init parameter') + .withRum({ enableExperimentalFeatures: ['tracking_consent'], trackingConsent: 'not-granted' }) + .withRumInit((configuration) => { + window.DD_RUM!.setTrackingConsent('granted') + window.DD_RUM!.init(configuration) + }) + .run(async ({ intakeRegistry }) => { + await flushEvents() + + expect(intakeRegistry.isEmpty).toBe(false) + expect(await findSessionCookie()).toBeDefined() + }) }) From 33298d8ebbe84f1890ff6ef0b407b140a526147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 6 Feb 2024 16:22:28 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=91=8C=20add=20jsdoc=20(also=20the=20?= =?UTF-8?q?logs=20public=20API=20I=20forgot)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logs/src/boot/logsPublicApi.ts | 16 +++++++++++++++- packages/rum-core/src/boot/rumPublicApi.ts | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/logs/src/boot/logsPublicApi.ts b/packages/logs/src/boot/logsPublicApi.ts index 6da8789070..4f384dc3b6 100644 --- a/packages/logs/src/boot/logsPublicApi.ts +++ b/packages/logs/src/boot/logsPublicApi.ts @@ -1,4 +1,4 @@ -import type { Context, User } from '@datadog/browser-core' +import type { Context, TrackingConsent, User } from '@datadog/browser-core' import { CustomerDataType, assign, @@ -73,6 +73,20 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { init: monitor((initConfiguration: LogsInitConfiguration) => strategy.init(initConfiguration)), + /** + * Set the tracking consent of the current user. + * + * @param {"granted" | "not-granted"} trackingConsent The user tracking consent + * + * Logs will be sent only if it is set to "granted". This value won't be stored by the library + * across page loads: you will need to call this method or set the appropriate `trackingConsent` + * field in the init() method at each page load. + * + * If this method is called before the init() method, the provided value will take precedence + * over the one provided as initialization parameter. + */ + setTrackingConsent: monitor((trackingConsent: TrackingConsent) => strategy.setTrackingConsent(trackingConsent)), + getGlobalContext: monitor(() => globalContextManager.getContext()), setGlobalContext: monitor((context) => globalContextManager.setContext(context)), diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index b5e012c3f5..fc17a6657d 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -146,6 +146,18 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp const rumPublicApi = makePublicApi({ init: monitor((initConfiguration: RumInitConfiguration) => strategy.init(initConfiguration)), + /** + * Set the tracking consent of the current user. + * + * @param {"granted" | "not-granted"} trackingConsent The user tracking consent + * + * Data will be sent only if it is set to "granted". This value won't be stored by the library + * across page loads: you will need to call this method or set the appropriate `trackingConsent` + * field in the init() method at each page load. + * + * If this method is called before the init() method, the provided value will take precedence + * over the one provided as initialization parameter. + */ setTrackingConsent: monitor((trackingConsent: TrackingConsent) => strategy.setTrackingConsent(trackingConsent)), setGlobalContextProperty: monitor((key, value) => globalContextManager.setContextProperty(key, value)), From 3e1b702bafbdf8b36b717ddddc03aeb79b98412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 6 Feb 2024 16:45:42 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=91=8C=20rename=20tracking=20consent?= =?UTF-8?q?=20state=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/session/sessionManager.spec.ts | 8 ++++---- .../core/src/domain/trackingConsent.spec.ts | 18 +++++++++--------- packages/core/src/domain/trackingConsent.ts | 8 ++++---- packages/logs/src/boot/preStartLogs.ts | 4 ++-- packages/logs/src/boot/startLogs.ts | 2 +- packages/rum-core/src/boot/preStartRum.ts | 4 ++-- packages/rum-core/src/boot/startRum.ts | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index 69731c836a..c208bce424 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -508,7 +508,7 @@ describe('startSessionManager', () => { const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED) const sessionManager = startSessionManagerWithDefaults({ trackingConsentState }) - trackingConsentState.set(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) expectSessionIdToNotBeDefined(sessionManager) expect(getCookie(SESSION_STORE_KEY)).toBeUndefined() @@ -518,7 +518,7 @@ describe('startSessionManager', () => { const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED) const sessionManager = startSessionManagerWithDefaults({ trackingConsentState }) - trackingConsentState.set(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) @@ -530,11 +530,11 @@ describe('startSessionManager', () => { const sessionManager = startSessionManagerWithDefaults({ trackingConsentState }) const initialSessionId = sessionManager.findActiveSession()!.id - trackingConsentState.set(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) expectSessionIdToNotBeDefined(sessionManager) - trackingConsentState.set(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) clock.tick(STORAGE_POLL_DELAY) diff --git a/packages/core/src/domain/trackingConsent.spec.ts b/packages/core/src/domain/trackingConsent.spec.ts index b61ef354bc..72c46c36c4 100644 --- a/packages/core/src/domain/trackingConsent.spec.ts +++ b/packages/core/src/domain/trackingConsent.spec.ts @@ -23,29 +23,29 @@ describe('createTrackingConsentState', () => { expect(trackingConsentState.isGranted()).toBeTrue() }) - it('can be set to granted', () => { + it('can be updated to granted', () => { const trackingConsentState = createTrackingConsentState() - trackingConsentState.set(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) expect(trackingConsentState.isGranted()).toBeTrue() }) - it('notifies when the consent is set', () => { + it('notifies when the consent is updated', () => { const spy = jasmine.createSpy() const trackingConsentState = createTrackingConsentState() trackingConsentState.observable.subscribe(spy) - trackingConsentState.set(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) expect(spy).toHaveBeenCalledTimes(1) }) - it('can set a consent state if not defined', () => { + it('can init a consent state if not defined yet', () => { const trackingConsentState = createTrackingConsentState() - trackingConsentState.setIfNotDefined(TrackingConsent.GRANTED) + trackingConsentState.tryToInit(TrackingConsent.GRANTED) expect(trackingConsentState.isGranted()).toBeTrue() }) - it('does not set a consent state if already defined', () => { + it('does not init a consent state if already defined', () => { const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED) - trackingConsentState.setIfNotDefined(TrackingConsent.NOT_GRANTED) + trackingConsentState.tryToInit(TrackingConsent.NOT_GRANTED) expect(trackingConsentState.isGranted()).toBeTrue() }) }) @@ -64,7 +64,7 @@ describe('createTrackingConsentState', () => { expect(trackingConsentState.isGranted()).toBeTrue() trackingConsentState = createTrackingConsentState() - trackingConsentState.set(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) expect(trackingConsentState.isGranted()).toBeTrue() }) }) diff --git a/packages/core/src/domain/trackingConsent.ts b/packages/core/src/domain/trackingConsent.ts index 1e16dd0074..1f78474a9d 100644 --- a/packages/core/src/domain/trackingConsent.ts +++ b/packages/core/src/domain/trackingConsent.ts @@ -8,8 +8,8 @@ export const TrackingConsent = { export type TrackingConsent = (typeof TrackingConsent)[keyof typeof TrackingConsent] export interface TrackingConsentState { - setIfNotDefined: (trackingConsent: TrackingConsent) => void - set: (trackingConsent: TrackingConsent) => void + tryToInit: (trackingConsent: TrackingConsent) => void + update: (trackingConsent: TrackingConsent) => void isGranted: () => boolean observable: Observable } @@ -18,12 +18,12 @@ export function createTrackingConsentState(currentConsent?: TrackingConsent): Tr const observable = new Observable() return { - setIfNotDefined(trackingConsent: TrackingConsent) { + tryToInit(trackingConsent: TrackingConsent) { if (!currentConsent) { currentConsent = trackingConsent } }, - set(trackingConsent: TrackingConsent) { + update(trackingConsent: TrackingConsent) { currentConsent = trackingConsent observable.notify() }, diff --git a/packages/logs/src/boot/preStartLogs.ts b/packages/logs/src/boot/preStartLogs.ts index ea7bb0cbe9..798b703ddd 100644 --- a/packages/logs/src/boot/preStartLogs.ts +++ b/packages/logs/src/boot/preStartLogs.ts @@ -62,11 +62,11 @@ export function createPreStartStrategy( } cachedConfiguration = configuration - trackingConsentState.setIfNotDefined(configuration.trackingConsent) + trackingConsentState.tryToInit(configuration.trackingConsent) tryStartLogs() }, - setTrackingConsent: trackingConsentState.set, + setTrackingConsent: trackingConsentState.update, get initConfiguration() { return cachedInitConfiguration diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 81dba2577c..24708809a4 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -73,7 +73,7 @@ export function startLogs( return { handleLog, getInternalContext: internalContext.get, - setTrackingConsent: trackingConsentState.set, + setTrackingConsent: trackingConsentState.update, stop: () => { cleanupTasks.forEach((task) => task()) }, diff --git a/packages/rum-core/src/boot/preStartRum.ts b/packages/rum-core/src/boot/preStartRum.ts index 6f043d5887..788f398ac2 100644 --- a/packages/rum-core/src/boot/preStartRum.ts +++ b/packages/rum-core/src/boot/preStartRum.ts @@ -122,7 +122,7 @@ export function createPreStartStrategy( } cachedConfiguration = configuration - trackingConsentState.setIfNotDefined(configuration.trackingConsent) + trackingConsentState.tryToInit(configuration.trackingConsent) tryStartRum() }, @@ -130,7 +130,7 @@ export function createPreStartStrategy( return cachedInitConfiguration }, - setTrackingConsent: trackingConsentState.set, + setTrackingConsent: trackingConsentState.update, getInternalContext: noop as () => undefined, diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index e69f2f9b48..d261a02610 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -179,7 +179,7 @@ export function startRum( addError, addTiming, addFeatureFlagEvaluation: featureFlagContexts.addFeatureFlagEvaluation, - setTrackingConsent: trackingConsentState.set, + setTrackingConsent: trackingConsentState.update, startView, lifeCycle, viewContexts, From 2bfb3269335690c6fc64a7be9304789e04bd3179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Wed, 7 Feb 2024 11:59:46 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=8C=20re-use=20TrackingConsentStat?= =?UTF-8?q?e=20instance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logs/src/boot/logsPublicApi.ts | 9 +-- packages/logs/src/boot/preStartLogs.spec.ts | 33 +++++++---- packages/logs/src/boot/preStartLogs.ts | 9 ++- packages/logs/src/boot/startLogs.spec.ts | 57 ++++++++++++++++--- packages/logs/src/boot/startLogs.ts | 10 ++-- .../rum-core/src/boot/preStartRum.spec.ts | 45 ++++++++------- packages/rum-core/src/boot/preStartRum.ts | 12 ++-- .../rum-core/src/boot/rumPublicApi.spec.ts | 1 - packages/rum-core/src/boot/rumPublicApi.ts | 9 ++- packages/rum-core/src/boot/startRum.spec.ts | 5 +- packages/rum-core/src/boot/startRum.ts | 10 ++-- 11 files changed, 130 insertions(+), 70 deletions(-) diff --git a/packages/logs/src/boot/logsPublicApi.ts b/packages/logs/src/boot/logsPublicApi.ts index 4f384dc3b6..77a0e86f83 100644 --- a/packages/logs/src/boot/logsPublicApi.ts +++ b/packages/logs/src/boot/logsPublicApi.ts @@ -12,6 +12,7 @@ import { storeContextManager, displayAlreadyInitializedError, deepClone, + createTrackingConsentState, } from '@datadog/browser-core' import type { LogsInitConfiguration } from '../domain/configuration' import type { HandlerType, StatusType } from '../domain/logger' @@ -32,7 +33,6 @@ const LOGS_STORAGE_KEY = 'logs' export interface Strategy { init: (initConfiguration: LogsInitConfiguration) => void - setTrackingConsent: StartLogsResult['setTrackingConsent'] initConfiguration: LogsInitConfiguration | undefined getInternalContext: StartLogsResult['getInternalContext'] handleLog: StartLogsResult['handleLog'] @@ -44,18 +44,19 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { customerDataTrackerManager.getOrCreateTracker(CustomerDataType.GlobalContext) ) const userContextManager = createContextManager(customerDataTrackerManager.getOrCreateTracker(CustomerDataType.User)) + const trackingConsentState = createTrackingConsentState() function getCommonContext() { return buildCommonContext(globalContextManager, userContextManager) } - let strategy = createPreStartStrategy(getCommonContext, (initConfiguration, configuration) => { + let strategy = createPreStartStrategy(getCommonContext, trackingConsentState, (initConfiguration, configuration) => { if (initConfiguration.storeContextsAcrossPages) { storeContextManager(configuration, globalContextManager, LOGS_STORAGE_KEY, CustomerDataType.GlobalContext) storeContextManager(configuration, userContextManager, LOGS_STORAGE_KEY, CustomerDataType.User) } - const startLogsResult = startLogsImpl(initConfiguration, configuration, getCommonContext) + const startLogsResult = startLogsImpl(initConfiguration, configuration, getCommonContext, trackingConsentState) strategy = createPostStartStrategy(initConfiguration, startLogsResult) return startLogsResult @@ -85,7 +86,7 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { * If this method is called before the init() method, the provided value will take precedence * over the one provided as initialization parameter. */ - setTrackingConsent: monitor((trackingConsent: TrackingConsent) => strategy.setTrackingConsent(trackingConsent)), + setTrackingConsent: monitor((trackingConsent: TrackingConsent) => trackingConsentState.update(trackingConsent)), getGlobalContext: monitor(() => globalContextManager.getContext()), diff --git a/packages/logs/src/boot/preStartLogs.spec.ts b/packages/logs/src/boot/preStartLogs.spec.ts index e91dc4c022..0dcf4e543e 100644 --- a/packages/logs/src/boot/preStartLogs.spec.ts +++ b/packages/logs/src/boot/preStartLogs.spec.ts @@ -5,8 +5,14 @@ import { initEventBridgeStub, mockExperimentalFeatures, } from '@datadog/browser-core/test' -import type { TimeStamp } from '@datadog/browser-core' -import { ExperimentalFeature, ONE_SECOND, TrackingConsent, display } from '@datadog/browser-core' +import type { TimeStamp, TrackingConsentState } from '@datadog/browser-core' +import { + ExperimentalFeature, + ONE_SECOND, + TrackingConsent, + createTrackingConsentState, + display, +} from '@datadog/browser-core' import type { CommonContext } from '../rawLogsEvent.types' import type { HybridInitConfiguration, LogsConfiguration, LogsInitConfiguration } from '../domain/configuration' import { StatusType, type Logger } from '../domain/logger' @@ -37,7 +43,7 @@ describe('preStartLogs', () => { handleLog: handleLogSpy, } as unknown as StartLogsResult) getCommonContextSpy = jasmine.createSpy() - strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) + strategy = createPreStartStrategy(getCommonContextSpy, createTrackingConsentState(), doStartLogsSpy) clock = mockClock() }) @@ -203,19 +209,26 @@ describe('preStartLogs', () => { describe('internal context', () => { it('should return undefined if not initialized', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) + const strategy = createPreStartStrategy(getCommonContextSpy, createTrackingConsentState(), doStartLogsSpy) expect(strategy.getInternalContext()).toBeUndefined() }) }) describe('tracking consent', () => { + let strategy: Strategy + let trackingConsentState: TrackingConsentState + + beforeEach(() => { + trackingConsentState = createTrackingConsentState() + strategy = createPreStartStrategy(getCommonContextSpy, trackingConsentState, doStartLogsSpy) + }) + describe('with tracking_consent enabled', () => { beforeEach(() => { mockExperimentalFeatures([ExperimentalFeature.TRACKING_CONSENT]) }) it('does not start logs if tracking consent is not granted at init', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -224,8 +237,7 @@ describe('preStartLogs', () => { }) it('starts logs if tracking consent is granted before init', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) - strategy.setTrackingConsent(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -234,8 +246,7 @@ describe('preStartLogs', () => { }) it('does not start logs if tracking consent is not withdrawn before init', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) - strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.GRANTED, @@ -246,7 +257,6 @@ describe('preStartLogs', () => { describe('with tracking_consent disabled', () => { it('ignores the trackingConsent init param', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -255,8 +265,7 @@ describe('preStartLogs', () => { }) it('ignores setTrackingConsent', () => { - const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy) - strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) strategy.init(DEFAULT_INIT_CONFIGURATION) expect(doStartLogsSpy).toHaveBeenCalledTimes(1) }) diff --git a/packages/logs/src/boot/preStartLogs.ts b/packages/logs/src/boot/preStartLogs.ts index 798b703ddd..36764617b4 100644 --- a/packages/logs/src/boot/preStartLogs.ts +++ b/packages/logs/src/boot/preStartLogs.ts @@ -1,8 +1,8 @@ +import type { TrackingConsentState } from '@datadog/browser-core' import { BoundedBuffer, assign, canUseEventBridge, - createTrackingConsentState, display, displayAlreadyInitializedError, noop, @@ -19,19 +19,20 @@ import type { StartLogsResult } from './startLogs' export function createPreStartStrategy( getCommonContext: () => CommonContext, + trackingConsentState: TrackingConsentState, doStartLogs: (initConfiguration: LogsInitConfiguration, configuration: LogsConfiguration) => StartLogsResult ): Strategy { const bufferApiCalls = new BoundedBuffer() let cachedInitConfiguration: LogsInitConfiguration | undefined let cachedConfiguration: LogsConfiguration | undefined - const trackingConsentState = createTrackingConsentState() - trackingConsentState.observable.subscribe(tryStartLogs) + const trackingConsentStateSubscription = trackingConsentState.observable.subscribe(tryStartLogs) function tryStartLogs() { if (!cachedConfiguration || !cachedInitConfiguration || !trackingConsentState.isGranted()) { return } + trackingConsentStateSubscription.unsubscribe() const startLogsResult = doStartLogs(cachedInitConfiguration, cachedConfiguration) bufferApiCalls.drain(startLogsResult) @@ -66,8 +67,6 @@ export function createPreStartStrategy( tryStartLogs() }, - setTrackingConsent: trackingConsentState.update, - get initConfiguration() { return cachedInitConfiguration }, diff --git a/packages/logs/src/boot/startLogs.spec.ts b/packages/logs/src/boot/startLogs.spec.ts index 250941f397..d86fc661ec 100644 --- a/packages/logs/src/boot/startLogs.spec.ts +++ b/packages/logs/src/boot/startLogs.spec.ts @@ -7,6 +7,8 @@ import { SESSION_STORE_KEY, createCustomerDataTracker, noop, + createTrackingConsentState, + TrackingConsent, } from '@datadog/browser-core' import type { Request } from '@datadog/browser-core/test' import { @@ -81,7 +83,12 @@ describe('logs', () => { describe('request', () => { it('should send the needed data', () => { - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + baseConfiguration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) handleLog({ message: 'message', status: StatusType.warn, context: { foo: 'bar' } }, logger, COMMON_CONTEXT) @@ -107,7 +114,8 @@ describe('logs', () => { ;({ handleLog, stop: stopLogs } = startLogs( initConfiguration, { ...baseConfiguration, batchMessagesLimit: 3 }, - () => COMMON_CONTEXT + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) )) registerCleanupTask(stopLogs) @@ -120,7 +128,12 @@ describe('logs', () => { it('should send bridge event when bridge is present', () => { const sendSpy = spyOn(initEventBridgeStub(), 'send') - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + baseConfiguration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) handleLog(DEFAULT_MESSAGE, logger) @@ -140,14 +153,24 @@ describe('logs', () => { const sendSpy = spyOn(initEventBridgeStub(), 'send') let configuration = { ...baseConfiguration, sessionSampleRate: 0 } - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + configuration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) handleLog(DEFAULT_MESSAGE, logger) expect(sendSpy).not.toHaveBeenCalled() configuration = { ...baseConfiguration, sessionSampleRate: 100 } - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + configuration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) handleLog(DEFAULT_MESSAGE, logger) @@ -160,7 +183,8 @@ describe('logs', () => { ;({ handleLog, stop: stopLogs } = startLogs( initConfiguration, { ...baseConfiguration, forwardConsoleLogs: ['log'] }, - () => COMMON_CONTEXT + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) )) registerCleanupTask(stopLogs) @@ -177,7 +201,12 @@ describe('logs', () => { }) it('creates a session on normal conditions', () => { - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + baseConfiguration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) expect(getCookie(SESSION_STORE_KEY)).not.toBeUndefined() @@ -185,7 +214,12 @@ describe('logs', () => { it('does not create a session if event bridge is present', () => { initEventBridgeStub() - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + baseConfiguration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) expect(getCookie(SESSION_STORE_KEY)).toBeUndefined() @@ -193,7 +227,12 @@ describe('logs', () => { it('does not create a session if synthetics worker will inject RUM', () => { mockSyntheticsWorkerValues({ injectsRum: true }) - ;({ handleLog, stop: stopLogs } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) + ;({ handleLog, stop: stopLogs } = startLogs( + initConfiguration, + baseConfiguration, + () => COMMON_CONTEXT, + createTrackingConsentState(TrackingConsent.GRANTED) + )) registerCleanupTask(stopLogs) expect(getCookie(SESSION_STORE_KEY)).toBeUndefined() diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 24708809a4..bf495779ad 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -1,10 +1,9 @@ +import type { TrackingConsentState } from '@datadog/browser-core' import { - TrackingConsent, sendToExtension, createPageExitObservable, willSyntheticsInjectRum, canUseEventBridge, - createTrackingConsentState, } from '@datadog/browser-core' import { startLogsSessionManager, startLogsSessionManagerStub } from '../domain/logsSessionManager' import type { LogsConfiguration, LogsInitConfiguration } from '../domain/configuration' @@ -28,7 +27,10 @@ export type StartLogsResult = ReturnType export function startLogs( initConfiguration: LogsInitConfiguration, configuration: LogsConfiguration, - getCommonContext: () => CommonContext + getCommonContext: () => CommonContext, + + // Tracking consent should always be granted when the startLogs is called. + trackingConsentState: TrackingConsentState ) { const lifeCycle = new LifeCycle() const cleanupTasks: Array<() => void> = [] @@ -37,7 +39,6 @@ export function startLogs( const reportError = startReportError(lifeCycle) const pageExitObservable = createPageExitObservable(configuration) - const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED) const session = configuration.sessionStoreStrategyType && !canUseEventBridge() && !willSyntheticsInjectRum() @@ -73,7 +74,6 @@ export function startLogs( return { handleLog, getInternalContext: internalContext.get, - setTrackingConsent: trackingConsentState.update, stop: () => { cleanupTasks.forEach((task) => task()) }, diff --git a/packages/rum-core/src/boot/preStartRum.spec.ts b/packages/rum-core/src/boot/preStartRum.spec.ts index a55d4a2f51..a6d1df7742 100644 --- a/packages/rum-core/src/boot/preStartRum.spec.ts +++ b/packages/rum-core/src/boot/preStartRum.spec.ts @@ -1,4 +1,4 @@ -import type { DeflateWorker, RelativeTime, TimeStamp } from '@datadog/browser-core' +import type { DeflateWorker, RelativeTime, TimeStamp, TrackingConsentState } from '@datadog/browser-core' import { display, getTimeStamp, @@ -7,6 +7,7 @@ import { clocksNow, TrackingConsent, ExperimentalFeature, + createTrackingConsentState, } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { @@ -54,7 +55,7 @@ describe('preStartRum', () => { beforeEach(() => { displaySpy = spyOn(display, 'error') - strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) }) it('should start when the configuration is valid', () => { @@ -136,7 +137,7 @@ describe('preStartRum', () => { it('should not initialize if session cannot be handled and bridge is not present', () => { spyOnProperty(document, 'cookie', 'get').and.returnValue('') const displaySpy = spyOn(display, 'warn') - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) strategy.init(DEFAULT_INIT_CONFIGURATION) expect(doStartRumSpy).not.toHaveBeenCalled() expect(displaySpy).toHaveBeenCalled() @@ -151,6 +152,7 @@ describe('preStartRum', () => { ignoreInitIfSyntheticsWillInjectRum: true, }, getCommonContextSpy, + createTrackingConsentState(), doStartRumSpy ) strategy.init(DEFAULT_INIT_CONFIGURATION) @@ -166,6 +168,7 @@ describe('preStartRum', () => { ignoreInitIfSyntheticsWillInjectRum: false, }, getCommonContextSpy, + createTrackingConsentState(), doStartRumSpy ) strategy.init(DEFAULT_INIT_CONFIGURATION) @@ -187,6 +190,7 @@ describe('preStartRum', () => { createDeflateEncoder: noop as any, }, getCommonContextSpy, + createTrackingConsentState(), doStartRumSpy ) }) @@ -255,7 +259,7 @@ describe('preStartRum', () => { startView: startViewSpy, addTiming: addTimingSpy, } as unknown as StartRumResult) - strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) }) afterEach(() => { @@ -312,7 +316,7 @@ describe('preStartRum', () => { it('calling startView then init does not start rum if tracking consent is not granted', () => { mockExperimentalFeatures([ExperimentalFeature.TRACKING_CONSENT]) - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) strategy.startView({ name: 'foo' }) strategy.init({ ...MANUAL_CONFIGURATION, @@ -379,14 +383,14 @@ describe('preStartRum', () => { describe('getInternalContext', () => { it('returns undefined', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) expect(strategy.getInternalContext()).toBe(undefined) }) }) describe('stopSession', () => { it('does not buffer the call before starting RUM', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) const stopSessionSpy = jasmine.createSpy() doStartRumSpy.and.returnValue({ stopSession: stopSessionSpy } as unknown as StartRumResult) @@ -401,7 +405,7 @@ describe('preStartRum', () => { let initConfiguration: RumInitConfiguration beforeEach(() => { - strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) initConfiguration = { ...DEFAULT_INIT_CONFIGURATION, service: 'my-service', version: '1.4.2', env: 'dev' } }) @@ -427,6 +431,7 @@ describe('preStartRum', () => { ignoreInitIfSyntheticsWillInjectRum: true, }, getCommonContextSpy, + createTrackingConsentState(), doStartRumSpy ) strategy.init(initConfiguration) @@ -439,7 +444,7 @@ describe('preStartRum', () => { let strategy: Strategy beforeEach(() => { - strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) + strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy) }) it('addAction', () => { @@ -508,13 +513,20 @@ describe('preStartRum', () => { }) describe('tracking consent', () => { + let strategy: Strategy + let trackingConsentState: TrackingConsentState + + beforeEach(() => { + trackingConsentState = createTrackingConsentState() + strategy = createPreStartStrategy({}, getCommonContextSpy, trackingConsentState, doStartRumSpy) + }) + describe('with tracking_consent enabled', () => { beforeEach(() => { mockExperimentalFeatures([ExperimentalFeature.TRACKING_CONSENT]) }) it('does not start rum if tracking consent is not granted at init', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -523,8 +535,7 @@ describe('preStartRum', () => { }) it('starts rum if tracking consent is granted before init', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) - strategy.setTrackingConsent(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -533,8 +544,7 @@ describe('preStartRum', () => { }) it('does not start rum if tracking consent is withdrawn before init', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) - strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.GRANTED, @@ -543,8 +553,7 @@ describe('preStartRum', () => { }) it('does not start rum if no view is started', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) - strategy.setTrackingConsent(TrackingConsent.GRANTED) + trackingConsentState.update(TrackingConsent.GRANTED) strategy.init({ ...MANUAL_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -555,7 +564,6 @@ describe('preStartRum', () => { describe('with tracking_consent disabled', () => { it('ignores the trackingConsent init param', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) strategy.init({ ...DEFAULT_INIT_CONFIGURATION, trackingConsent: TrackingConsent.NOT_GRANTED, @@ -564,8 +572,7 @@ describe('preStartRum', () => { }) it('ignores setTrackingConsent', () => { - const strategy = createPreStartStrategy({}, getCommonContextSpy, doStartRumSpy) - strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED) + trackingConsentState.update(TrackingConsent.NOT_GRANTED) strategy.init(DEFAULT_INIT_CONFIGURATION) expect(doStartRumSpy).toHaveBeenCalledTimes(1) }) diff --git a/packages/rum-core/src/boot/preStartRum.ts b/packages/rum-core/src/boot/preStartRum.ts index 788f398ac2..9228b69228 100644 --- a/packages/rum-core/src/boot/preStartRum.ts +++ b/packages/rum-core/src/boot/preStartRum.ts @@ -1,7 +1,6 @@ import { BoundedBuffer, display, - type DeflateWorker, canUseEventBridge, displayAlreadyInitializedError, willSyntheticsInjectRum, @@ -9,8 +8,8 @@ import { timeStampNow, clocksNow, assign, - createTrackingConsentState, } from '@datadog/browser-core' +import type { TrackingConsentState, DeflateWorker } from '@datadog/browser-core' import { validateAndBuildRumConfiguration, type RumConfiguration, @@ -24,6 +23,7 @@ import type { StartRumResult } from './startRum' export function createPreStartStrategy( { ignoreInitIfSyntheticsWillInjectRum, startDeflateWorker }: RumPublicApiOptions, getCommonContext: () => CommonContext, + trackingConsentState: TrackingConsentState, doStartRum: ( initConfiguration: RumInitConfiguration, configuration: RumConfiguration, @@ -39,14 +39,16 @@ export function createPreStartStrategy( let cachedInitConfiguration: RumInitConfiguration | undefined let cachedConfiguration: RumConfiguration | undefined - const trackingConsentState = createTrackingConsentState() - trackingConsentState.observable.subscribe(tryStartRum) + + const trackingConsentStateSubscription = trackingConsentState.observable.subscribe(tryStartRum) function tryStartRum() { if (!cachedInitConfiguration || !cachedConfiguration || !trackingConsentState.isGranted()) { return } + trackingConsentStateSubscription.unsubscribe() + let initialViewOptions: ViewOptions | undefined if (cachedConfiguration.trackViewsManually) { @@ -130,8 +132,6 @@ export function createPreStartStrategy( return cachedInitConfiguration }, - setTrackingConsent: trackingConsentState.update, - getInternalContext: noop as () => undefined, stopSession: noop, diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index eb0aded88d..77396190ff 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -21,7 +21,6 @@ const noopStartRum = (): ReturnType => ({ addFeatureFlagEvaluation: () => undefined, startView: () => undefined, getInternalContext: () => undefined, - setTrackingConsent: () => undefined, lifeCycle: {} as any, viewContexts: {} as any, session: {} as any, diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index fc17a6657d..84a40f0adf 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -26,6 +26,7 @@ import { createCustomerDataTrackerManager, storeContextManager, displayAlreadyInitializedError, + createTrackingConsentState, } from '@datadog/browser-core' import type { LifeCycle } from '../domain/lifeCycle' import type { ViewContexts } from '../domain/contexts/viewContexts' @@ -73,7 +74,6 @@ const RUM_STORAGE_KEY = 'rum' export interface Strategy { init: (initConfiguration: RumInitConfiguration) => void - setTrackingConsent: StartRumResult['setTrackingConsent'] initConfiguration: RumInitConfiguration | undefined getInternalContext: StartRumResult['getInternalContext'] stopSession: StartRumResult['stopSession'] @@ -90,6 +90,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp customerDataTrackerManager.getOrCreateTracker(CustomerDataType.GlobalContext) ) const userContextManager = createContextManager(customerDataTrackerManager.getOrCreateTracker(CustomerDataType.User)) + const trackingConsentState = createTrackingConsentState() function getCommonContext() { return buildCommonContext(globalContextManager, userContextManager, recorderApi) @@ -98,6 +99,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp let strategy = createPreStartStrategy( options, getCommonContext, + trackingConsentState, (initConfiguration, configuration, deflateWorker, initialViewOptions) => { if (initConfiguration.storeContextsAcrossPages) { @@ -118,7 +120,8 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp initialViewOptions, deflateWorker && options.createDeflateEncoder ? (streamId) => options.createDeflateEncoder!(configuration, deflateWorker, streamId) - : createIdentityEncoder + : createIdentityEncoder, + trackingConsentState ) recorderApi.onRumStart( @@ -158,7 +161,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp * If this method is called before the init() method, the provided value will take precedence * over the one provided as initialization parameter. */ - setTrackingConsent: monitor((trackingConsent: TrackingConsent) => strategy.setTrackingConsent(trackingConsent)), + setTrackingConsent: monitor((trackingConsent: TrackingConsent) => trackingConsentState.update(trackingConsent)), setGlobalContextProperty: monitor((key, value) => globalContextManager.setContextProperty(key, value)), diff --git a/packages/rum-core/src/boot/startRum.spec.ts b/packages/rum-core/src/boot/startRum.spec.ts index 5b2c570f1d..81b5a80a63 100644 --- a/packages/rum-core/src/boot/startRum.spec.ts +++ b/packages/rum-core/src/boot/startRum.spec.ts @@ -9,6 +9,8 @@ import { relativeNow, createIdentityEncoder, createCustomerDataTracker, + createTrackingConsentState, + TrackingConsent, } from '@datadog/browser-core' import { createNewEvent, @@ -334,7 +336,8 @@ describe('view events', () => { customerDataTrackerManager, () => ({ user: {}, context: {}, hasReplay: undefined }), undefined, - createIdentityEncoder + createIdentityEncoder, + createTrackingConsentState(TrackingConsent.GRANTED) ) ) interceptor = interceptRequests() diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index d261a02610..9de9458c72 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -5,6 +5,7 @@ import type { DeflateEncoderStreamId, Encoder, CustomerDataTrackerManager, + TrackingConsentState, } from '@datadog/browser-core' import { sendToExtension, @@ -16,8 +17,6 @@ import { getEventBridge, addTelemetryDebug, CustomerDataType, - createTrackingConsentState, - TrackingConsent, } from '@datadog/browser-core' import { createDOMMutationObservable } from '../browser/domMutationObservable' import { startPerformanceCollection } from '../browser/performanceCollection' @@ -58,7 +57,10 @@ export function startRum( customerDataTrackerManager: CustomerDataTrackerManager, getCommonContext: () => CommonContext, initialViewOptions: ViewOptions | undefined, - createEncoder: (streamId: DeflateEncoderStreamId) => Encoder + createEncoder: (streamId: DeflateEncoderStreamId) => Encoder, + + // Tracking consent should always be granted when the startRum is called. + trackingConsentState: TrackingConsentState ) { const cleanupTasks: Array<() => void> = [] const lifeCycle = new LifeCycle() @@ -90,7 +92,6 @@ export function startRum( customerDataTrackerManager.getOrCreateTracker(CustomerDataType.FeatureFlag) ) - const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED) const pageExitObservable = createPageExitObservable(configuration) const pageExitSubscription = pageExitObservable.subscribe((event) => { lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, event) @@ -179,7 +180,6 @@ export function startRum( addError, addTiming, addFeatureFlagEvaluation: featureFlagContexts.addFeatureFlagEvaluation, - setTrackingConsent: trackingConsentState.update, startView, lifeCycle, viewContexts,