import type { TrackingConsentState } from '@datadog/browser-core' import { createBoundedBuffer, canUseEventBridge, display, displayAlreadyInitializedError, initFeatureFlags, initFetchObservable, noop, timeStampNow, buildAccountContextManager, CustomerContextKey, bufferContextCalls, addTelemetryConfiguration, buildGlobalContextManager, buildUserContextManager, startTelemetry, TelemetryService, mockable, } from '@datadog/browser-core' import type { Hooks } from '../domain/hooks' import { createHooks } from '../domain/hooks' import type { LogsConfiguration, LogsInitConfiguration } from '../domain/configuration' import { serializeLogsConfiguration, validateAndBuildLogsConfiguration } from '../domain/configuration' import type { CommonContext } from '../rawLogsEvent.types' import type { Strategy } from './logsPublicApi' import type { StartLogsResult } from './startLogs' export type DoStartLogs = ( initConfiguration: LogsInitConfiguration, configuration: LogsConfiguration, hooks: Hooks ) => StartLogsResult export function createPreStartStrategy( getCommonContext: () => CommonContext, trackingConsentState: TrackingConsentState, doStartLogs: DoStartLogs ): Strategy { const bufferApiCalls = createBoundedBuffer() // TODO next major: remove the globalContext, accountContextManager, userContext from preStartStrategy and use an empty context instead const globalContext = buildGlobalContextManager() bufferContextCalls(globalContext, CustomerContextKey.globalContext, bufferApiCalls) const accountContext = buildAccountContextManager() bufferContextCalls(accountContext, CustomerContextKey.accountContext, bufferApiCalls) const userContext = buildUserContextManager() bufferContextCalls(userContext, CustomerContextKey.userContext, bufferApiCalls) let cachedInitConfiguration: LogsInitConfiguration | undefined let cachedConfiguration: LogsConfiguration | undefined const hooks = createHooks() const trackingConsentStateSubscription = trackingConsentState.observable.subscribe(tryStartLogs) function tryStartLogs() { if (!cachedConfiguration || !cachedInitConfiguration || !trackingConsentState.isGranted()) { return } mockable(startTelemetry)(TelemetryService.LOGS, cachedConfiguration, hooks) trackingConsentStateSubscription.unsubscribe() const startLogsResult = doStartLogs(cachedInitConfiguration, cachedConfiguration, hooks) bufferApiCalls.drain(startLogsResult) } return { init(initConfiguration, errorStack) { 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) if (canUseEventBridge()) { initConfiguration = overrideInitConfigurationForBridge(initConfiguration) } // Expose the initial configuration regardless of initialization success. cachedInitConfiguration = initConfiguration addTelemetryConfiguration(serializeLogsConfiguration(initConfiguration)) if (cachedConfiguration) { displayAlreadyInitializedError('DD_LOGS', initConfiguration) return } const configuration = validateAndBuildLogsConfiguration(initConfiguration, errorStack) if (!configuration) { return } cachedConfiguration = configuration // Instrument fetch to track network requests // This is needed in case the consent is not granted and some customer // library (Apollo Client) is storing uninstrumented fetch to be used later // The subscrption is needed so that the instrumentation process is completed initFetchObservable().subscribe(noop) trackingConsentState.tryToInit(configuration.trackingConsent) tryStartLogs() }, get initConfiguration() { return cachedInitConfiguration }, globalContext, accountContext, userContext, getInternalContext: noop as () => undefined, handleLog(message, statusType, handlingStack, context = getCommonContext(), date = timeStampNow()) { bufferApiCalls.add((startLogsResult) => startLogsResult.handleLog(message, statusType, handlingStack, context, date) ) }, } } function overrideInitConfigurationForBridge(initConfiguration: LogsInitConfiguration): LogsInitConfiguration { return { ...initConfiguration, clientToken: 'empty' } }