Skip to content

Commit

Permalink
Integrate aymeric/remote-configuration (#2799) into staging-24
Browse files Browse the repository at this point in the history
Integrated commit sha: 10bc671

Co-authored-by: Aymeric Mortemousque <[email protected]>
  • Loading branch information
dd-mergequeue[bot] and amortemousque authored Jun 11, 2024
2 parents 1442ee6 + 10bc671 commit 4e239e6
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 53 deletions.
5 changes: 2 additions & 3 deletions packages/core/src/browser/addEventListener.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { monitor } from '../tools/monitor'
import { getZoneJsOriginalValue } from '../tools/getZoneJsOriginalValue'
import type { Configuration } from '../domain/configuration'
import type { CookieStore, CookieStoreEventMap, VisualViewport, VisualViewportEventMap } from './types'

export type TrustableEvent<E extends Event = Event> = E & { __ddIsTrusted?: boolean }
Expand Down Expand Up @@ -90,7 +89,7 @@ type EventMapFor<T> = T extends Window
* * returns a `stop` function to remove the listener
*/
export function addEventListener<Target extends EventTarget, EventName extends keyof EventMapFor<Target> & string>(
configuration: Configuration,
configuration: { allowUntrustedEvents?: boolean | undefined },
eventTarget: Target,
eventName: EventName,
listener: (event: EventMapFor<Target>[EventName] & { type: EventName }) => void,
Expand All @@ -112,7 +111,7 @@ export function addEventListener<Target extends EventTarget, EventName extends k
* * with `once: true`, the listener will be called at most once, even if different events are listened
*/
export function addEventListeners<Target extends EventTarget, EventName extends keyof EventMapFor<Target> & string>(
configuration: Configuration,
configuration: { allowUntrustedEvents?: boolean | undefined },
eventTarget: Target,
eventNames: EventName[],
listener: (event: EventMapFor<Target>[EventName] & { type: EventName }) => void,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum ExperimentalFeature {
CUSTOM_VITALS = 'custom_vitals',
TOLERANT_RESOURCE_TIMINGS = 'tolerant_resource_timings',
MICRO_FRONTEND = 'micro_frontend',
REMOTE_CONFIGURATION = 'remote_configuration',
}

const enabledExperimentalFeatures: Set<ExperimentalFeature> = new Set()
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/transport/eventBridge.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { endsWith, includes } from '../tools/utils/polyfills'
import { getGlobalObject } from '../tools/getGlobalObject'
import type { DefaultPrivacyLevel } from '../domain/configuration'

export interface BrowserWindowWithEventBridge extends Window {
DatadogEventBridge?: DatadogEventBridge
}

export interface DatadogEventBridge {
getCapabilities?(): string
getPrivacyLevel?(): string
getPrivacyLevel?(): DefaultPrivacyLevel
getAllowedWebViewHosts(): string
send(msg: string): void
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/test/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ class StubEventEmitter {
class StubXhr extends StubEventEmitter {
public static onSend: (xhr: StubXhr) => void | undefined
public response: string | undefined = undefined
public responseText: string | undefined = undefined
public status: number | undefined = undefined
public readyState: number = XMLHttpRequest.UNSENT
public onreadystatechange: () => void = noop
Expand Down Expand Up @@ -313,6 +314,7 @@ class StubXhr extends StubEventEmitter {
}
this.hasEnded = true
this.response = response
this.responseText = response
this.status = status
this.readyState = XMLHttpRequest.DONE

Expand Down
66 changes: 66 additions & 0 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import {
TrackingConsent,
createTrackingConsentState,
DefaultPrivacyLevel,
addExperimentalFeatures,
ExperimentalFeature,
resetExperimentalFeatures,
} from '@datadog/browser-core'
import type { Clock } from '@datadog/browser-core/test'
import {
cleanupSyntheticsWorkerValues,
initEventBridgeStub,
interceptRequests,
mockClock,
mockSyntheticsWorkerValues,
} from '@datadog/browser-core/test'
Expand Down Expand Up @@ -393,6 +397,48 @@ describe('preStartRum', () => {
})
})
})

describe('remote configuration', () => {
let interceptor: ReturnType<typeof interceptRequests>

beforeEach(() => {
interceptor = interceptRequests()
})

afterEach(() => {
interceptor.restore()
resetExperimentalFeatures()
})

describe('when remote_configuration ff is enabled', () => {
it('should start with the remote configuration when a remoteConfigurationId is provided', (done) => {
addExperimentalFeatures([ExperimentalFeature.REMOTE_CONFIGURATION])
interceptor.withStubXhr((xhr) => {
xhr.complete(200, '{"sessionSampleRate":50}')

expect(doStartRumSpy.calls.mostRecent().args[0].sessionSampleRate).toEqual(50)
done()
})

const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
remoteConfigurationId: '123',
})
})
})

describe('when remote_configuration ff is disabled', () => {
it('should start without the remote configuration when a remoteConfigurationId is provided', () => {
const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
remoteConfigurationId: '123',
})
expect(doStartRumSpy.calls.mostRecent().args[0].sessionSampleRate).toEqual(100)
})
})
})
})

describe('getInternalContext', () => {
Expand All @@ -417,14 +463,18 @@ describe('preStartRum', () => {
describe('initConfiguration', () => {
let strategy: Strategy
let initConfiguration: RumInitConfiguration
let interceptor: ReturnType<typeof interceptRequests>

beforeEach(() => {
interceptor = interceptRequests()
strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy)
initConfiguration = { ...DEFAULT_INIT_CONFIGURATION, service: 'my-service', version: '1.4.2', env: 'dev' }
})

afterEach(() => {
cleanupSyntheticsWorkerValues()
interceptor.restore()
resetExperimentalFeatures()
})

it('is undefined before init', () => {
Expand Down Expand Up @@ -452,6 +502,22 @@ describe('preStartRum', () => {

expect(strategy.initConfiguration).toEqual(initConfiguration)
})

it('returns the initConfiguration with the remote configuration when a remoteConfigurationId is provided', (done) => {
addExperimentalFeatures([ExperimentalFeature.REMOTE_CONFIGURATION])
interceptor.withStubXhr((xhr) => {
xhr.complete(200, '{"sessionSampleRate":50}')

expect(strategy.initConfiguration?.sessionSampleRate).toEqual(50)
done()
})

const strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
remoteConfigurationId: '123',
})
})
})

describe('buffers API calls before starting RUM', () => {
Expand Down
97 changes: 56 additions & 41 deletions packages/rum-core/src/boot/preStartRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
clocksNow,
assign,
getEventBridge,
addTelemetryConfiguration,
ExperimentalFeature,
isExperimentalFeatureEnabled,
initFeatureFlags,
addTelemetryConfiguration,
} from '@datadog/browser-core'
import type { TrackingConsentState, DeflateWorker } from '@datadog/browser-core'
import {
Expand All @@ -20,7 +22,7 @@ import {
} from '../domain/configuration'
import type { CommonContext } from '../domain/contexts/commonContext'
import type { ViewOptions } from '../domain/view/trackViews'
import { serializeRumConfiguration } from '../domain/configuration'
import { fetchAndApplyRemoteConfiguration, serializeRumConfiguration } from '../domain/configuration'
import type { RumPublicApiOptions, Strategy } from './rumPublicApi'
import type { StartRumResult } from './startRum'

Expand Down Expand Up @@ -73,29 +75,62 @@ export function createPreStartStrategy(
bufferApiCalls.drain(startRumResult)
}

function doInit(initConfiguration: RumInitConfiguration) {
const eventBridgeAvailable = canUseEventBridge()
if (eventBridgeAvailable) {
initConfiguration = overrideInitConfigurationForBridge(initConfiguration)
}

// Update the exposed initConfiguration to reflect the bridge and remote configuration overrides
cachedInitConfiguration = initConfiguration
addTelemetryConfiguration(serializeRumConfiguration(initConfiguration))

if (cachedConfiguration) {
displayAlreadyInitializedError('DD_RUM', initConfiguration)
return
}

const configuration = validateAndBuildRumConfiguration(initConfiguration)
if (!configuration) {
return
}

if (!eventBridgeAvailable && !configuration.sessionStoreStrategyType) {
display.warn('No storage available for session. We will not send any data.')
return
}

if (configuration.compressIntakeRequests && !eventBridgeAvailable && startDeflateWorker) {
deflateWorker = startDeflateWorker(
configuration,
'Datadog RUM',
// Worker initialization can fail asynchronously, especially in Firefox where even CSP
// issues are reported asynchronously. For now, the SDK will continue its execution even if
// data won't be sent to Datadog. We could improve this behavior in the future.
noop
)
if (!deflateWorker) {
// `startDeflateWorker` should have logged an error message explaining the issue
return
}
}

cachedConfiguration = configuration
trackingConsentState.tryToInit(configuration.trackingConsent)
tryStartRum()
}

return {
init(initConfiguration) {
if (!initConfiguration) {
display.error('Missing configuration')
return
}

// Set the experimental feature flags as early as possible, so we can use them in most places
initFeatureFlags(initConfiguration.enableExperimentalFeatures)

const eventBridgeAvailable = canUseEventBridge()
if (eventBridgeAvailable) {
initConfiguration = overrideInitConfigurationForBridge(initConfiguration)
}

// Expose the initial configuration regardless of initialization success.
cachedInitConfiguration = initConfiguration
addTelemetryConfiguration(serializeRumConfiguration(initConfiguration))

if (cachedConfiguration) {
displayAlreadyInitializedError('DD_RUM', initConfiguration)
return
}

// If we are in a Synthetics test configured to automatically inject a RUM instance, we want
// to completely discard the customer application RUM instance by ignoring their init() call.
Expand All @@ -105,34 +140,14 @@ export function createPreStartStrategy(
return
}

const configuration = validateAndBuildRumConfiguration(initConfiguration)
if (!configuration) {
return
}

if (!eventBridgeAvailable && !configuration.sessionStoreStrategyType) {
display.warn('No storage available for session. We will not send any data.')
return
}

if (configuration.compressIntakeRequests && !eventBridgeAvailable && startDeflateWorker) {
deflateWorker = startDeflateWorker(
configuration,
'Datadog RUM',
// Worker initialization can fail asynchronously, especially in Firefox where even CSP
// issues are reported asynchronously. For now, the SDK will continue its execution even if
// data won't be sent to Datadog. We could improve this behavior in the future.
noop
)
if (!deflateWorker) {
// `startDeflateWorker` should have logged an error message explaining the issue
return
}
if (
initConfiguration.remoteConfigurationId &&
isExperimentalFeatureEnabled(ExperimentalFeature.REMOTE_CONFIGURATION)
) {
fetchAndApplyRemoteConfiguration(initConfiguration, doInit)
} else {
doInit(initConfiguration)
}

cachedConfiguration = configuration
trackingConsentState.tryToInit(configuration.trackingConsent)
tryStartRum()
},

get initConfiguration() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { InitConfiguration } from '@datadog/browser-core'
import { DefaultPrivacyLevel, display, TraceContextInjection } from '@datadog/browser-core'
import { EXHAUSTIVE_INIT_CONFIGURATION, SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION } from '../../../core/test'
import type { ExtractTelemetryConfiguration, CamelToSnakeCase, MapInitConfigurationKey } from '../../../core/test'
import { EXHAUSTIVE_INIT_CONFIGURATION, SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION } from '@datadog/browser-core/test'
import type {
ExtractTelemetryConfiguration,
CamelToSnakeCase,
MapInitConfigurationKey,
} from '@datadog/browser-core/test'
import type { RumInitConfiguration } from './configuration'
import { DEFAULT_PROPAGATOR_TYPES, serializeRumConfiguration, validateAndBuildRumConfiguration } from './configuration'

Expand Down Expand Up @@ -445,6 +449,7 @@ describe('serializeRumConfiguration', () => {
trackViewsManually: true,
trackResources: true,
trackLongTasks: true,
remoteConfigurationId: '123',
}

type MapRumInitConfigurationKey<Key extends string> = Key extends keyof InitConfiguration
Expand All @@ -453,7 +458,7 @@ describe('serializeRumConfiguration', () => {
? `use_${CamelToSnakeCase<Key>}`
: Key extends 'trackLongTasks'
? 'track_long_task' // oops
: Key extends 'applicationId' | 'subdomain'
: Key extends 'applicationId' | 'subdomain' | 'remoteConfigurationId'
? never
: CamelToSnakeCase<Key>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
objectHasValue,
validateAndBuildConfiguration,
} from '@datadog/browser-core'
import type { RumEventDomainContext } from '../domainContext.types'
import type { RumEvent } from '../rumEvent.types'
import { isTracingOption } from './tracing/tracer'
import type { PropagatorType, TracingOption } from './tracing/tracer.types'
import type { RumEventDomainContext } from '../../domainContext.types'
import type { RumEvent } from '../../rumEvent.types'
import { isTracingOption } from '../tracing/tracer'
import type { PropagatorType, TracingOption } from '../tracing/tracer.types'

export const DEFAULT_PROPAGATOR_TYPES: PropagatorType[] = ['tracecontext', 'datadog']

Expand Down Expand Up @@ -50,6 +50,7 @@ export interface RumInitConfiguration extends InitConfiguration {
* See [Content Security Policy guidelines](https://docs.datadoghq.com/integrations/content_security_policy_logs/?tab=firefox#use-csp-with-real-user-monitoring-and-session-replay) for further information.
*/
compressIntakeRequests?: boolean | undefined
remoteConfigurationId?: string | undefined

// tracing options
/**
Expand Down Expand Up @@ -204,6 +205,7 @@ export function validateAndBuildRumConfiguration(
traceContextInjection: objectHasValue(TraceContextInjection, initConfiguration.traceContextInjection)
? initConfiguration.traceContextInjection
: TraceContextInjection.ALL,
storeContextsAcrossPages: initConfiguration.storeContextsAcrossPages,
},
baseConfiguration
)
Expand Down
2 changes: 2 additions & 0 deletions packages/rum-core/src/domain/configuration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './configuration'
export * from './remoteConfiguration'
Loading

0 comments on commit 4e239e6

Please sign in to comment.