diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts index 0e93fb94b0..e4a81f2eb2 100644 --- a/packages/rum-core/src/index.ts +++ b/packages/rum-core/src/index.ts @@ -1,4 +1,4 @@ -export { RumUserConfiguration, RumPublicApi, makeRumPublicApi } from './boot/rumPublicApi' +export { RumUserConfiguration, RumPublicApi, makeRumPublicApi, StartRum } from './boot/rumPublicApi' export { ProvidedSource } from './domain/rumEventsCollection/error/errorCollection' export { RumEvent, diff --git a/packages/rum-recorder/src/boot/recorder.entry.ts b/packages/rum-recorder/src/boot/recorder.entry.ts index 6ab06e16f5..91dfbfc123 100644 --- a/packages/rum-recorder/src/boot/recorder.entry.ts +++ b/packages/rum-recorder/src/boot/recorder.entry.ts @@ -1,27 +1,10 @@ import { defineGlobal, getGlobalObject } from '@datadog/browser-core' -import { - CommonContext, - makeRumPublicApi, - RumPublicApi, - RumUserConfiguration, - startRum, -} from '@datadog/browser-rum-core' +import { RumPublicApi, startRum } from '@datadog/browser-rum-core' import { startRecording } from './recorder' +import { makeRumRecorderPublicApi } from './rumRecorderPublicApi' -function startRumAndRecording(userConfiguration: RumUserConfiguration, getCommonContext: () => CommonContext) { - const startRumResult = startRum(userConfiguration, () => ({ - ...getCommonContext(), - hasReplay: true, - })) - - const { lifeCycle, parentContexts, configuration, session } = startRumResult - startRecording(lifeCycle, userConfiguration.applicationId, configuration, session, parentContexts) - - return startRumResult -} - -export const datadogRum = makeRumPublicApi(startRumAndRecording) +export const datadogRum = makeRumRecorderPublicApi(startRum, startRecording) interface BrowserWindow extends Window { DD_RUM?: RumPublicApi diff --git a/packages/rum-recorder/src/boot/rumRecorderPublicApi.spec.ts b/packages/rum-recorder/src/boot/rumRecorderPublicApi.spec.ts new file mode 100644 index 0000000000..38ee85b32f --- /dev/null +++ b/packages/rum-recorder/src/boot/rumRecorderPublicApi.spec.ts @@ -0,0 +1,87 @@ +import { Configuration } from '@datadog/browser-core' +import { RumPublicApi, StartRum } from '@datadog/browser-rum-core' +import { makeRumRecorderPublicApi, StartRecording } from './rumRecorderPublicApi' + +const DEFAULT_INIT_CONFIGURATION = { applicationId: 'xxx', clientToken: 'xxx' } + +describe('makeRumRecorderPublicApi', () => { + let rumGlobal: RumPublicApi & { startSessionRecord?(): void } + let startRecordingSpy: jasmine.Spy + let startRumSpy: jasmine.Spy + let enabledFlags: string[] = [] + + beforeEach(() => { + enabledFlags = [] + startRecordingSpy = jasmine.createSpy() + startRumSpy = jasmine.createSpy().and.callFake(() => { + const configuration: Partial = { + isEnabled(flag: string) { + return enabledFlags.indexOf(flag) >= 0 + }, + } + return ({ configuration } as unknown) as ReturnType + }) + rumGlobal = makeRumRecorderPublicApi(startRumSpy, startRecordingSpy) + }) + + function getCommonContext() { + return startRumSpy.calls.first().args[1]() + } + + describe('init', () => { + it('should start RUM when init is called', () => { + expect(startRumSpy).not.toHaveBeenCalled() + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(startRumSpy).toHaveBeenCalled() + }) + + it('should start recording when init is called', () => { + expect(startRecordingSpy).not.toHaveBeenCalled() + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(startRecordingSpy).toHaveBeenCalled() + }) + + it('should set commonContext.hasReplay to true', () => { + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(getCommonContext().hasReplay).toBe(true) + }) + }) + + describe('experimental flag postpone_start_recording', () => { + it('if disabled, startSessionRecord should not be defined', () => { + expect(rumGlobal.startSessionRecord).toBeUndefined() + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(rumGlobal.startSessionRecord).toBeUndefined() + }) + + it('if enabled, recording should not start when calling init()', () => { + enabledFlags = ['postpone_start_recording'] + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(startRecordingSpy).not.toHaveBeenCalled() + }) + + it('if enabled, startSessionRecord should be defined', () => { + enabledFlags = ['postpone_start_recording'] + expect(rumGlobal.startSessionRecord).toBeUndefined() + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(rumGlobal.startSessionRecord).toEqual(jasmine.any(Function)) + }) + + it('if enabled, commonContext.hasReplay should be true only if startSessionRecord is called', () => { + enabledFlags = ['postpone_start_recording'] + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + expect(getCommonContext().hasReplay).toBe(false) + rumGlobal.startSessionRecord!() + expect(getCommonContext().hasReplay).toBe(true) + }) + + it('if enabled, calling startSessionRecord multiple times should only start recording once', () => { + enabledFlags = ['postpone_start_recording'] + rumGlobal.init(DEFAULT_INIT_CONFIGURATION) + rumGlobal.startSessionRecord!() + rumGlobal.startSessionRecord!() + rumGlobal.startSessionRecord!() + expect(startRecordingSpy).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/rum-recorder/src/boot/rumRecorderPublicApi.ts b/packages/rum-recorder/src/boot/rumRecorderPublicApi.ts new file mode 100644 index 0000000000..79163fda19 --- /dev/null +++ b/packages/rum-recorder/src/boot/rumRecorderPublicApi.ts @@ -0,0 +1,37 @@ +import { monitor } from '@datadog/browser-core' +import { makeRumPublicApi, StartRum } from '@datadog/browser-rum-core' + +import { startRecording } from './recorder' + +export type StartRecording = typeof startRecording + +export function makeRumRecorderPublicApi(startRumImpl: StartRum, startRecordingImpl: StartRecording) { + const rumRecorderGlobal = makeRumPublicApi((userConfiguration, getCommonContext) => { + let isRecording = false + + const startRumResult = startRumImpl(userConfiguration, () => ({ + ...getCommonContext(), + hasReplay: isRecording, + })) + + const { lifeCycle, parentContexts, configuration, session } = startRumResult + + if (configuration.isEnabled('postpone_start_recording')) { + ;(rumRecorderGlobal as any).startSessionRecord = monitor(startSessionRecord) + } else { + startSessionRecord() + } + + function startSessionRecord() { + if (isRecording) { + return + } + + isRecording = true + startRecordingImpl(lifeCycle, userConfiguration.applicationId, configuration, session, parentContexts) + } + + return startRumResult + }) + return rumRecorderGlobal +}