import { catchUserErrors } from '../../tools/catchUserErrors' import { DOCS_ORIGIN, MORE_DETAILS, display } from '../../tools/display' import type { RawTelemetryConfiguration } from '../telemetry' import { isPercentage } from '../../tools/utils/numberUtils' import { objectHasValue } from '../../tools/utils/objectUtils' import type { CookieOptions } from '../../browser/cookie' import { getCurrentSite } from '../../browser/cookie' import { TrackingConsent } from '../trackingConsent' import type { SessionPersistence } from '../session/sessionConstants' import type { MatchOption } from '../../tools/matchOption' import { isAllowedTrackingOrigins } from '../allowedTrackingOrigins' import { INTAKE_SITE_US1 } from '../intakeSites' import type { Site } from '../intakeSites' /** * Default privacy level for the browser SDK. * * [Replay Privacy Options](https://docs.datadoghq.com/real_user_monitoring/session_replay/browser/privacy_options) for further information. */ export const DefaultPrivacyLevel = { ALLOW: 'allow', MASK: 'mask', MASK_USER_INPUT: 'mask-user-input', MASK_UNLESS_ALLOWLISTED: 'mask-unless-allowlisted', } as const export type DefaultPrivacyLevel = (typeof DefaultPrivacyLevel)[keyof typeof DefaultPrivacyLevel] /** * Trace context injection option. * * See [Connect RUM and Traces](https://docs.datadoghq.com/real_user_monitoring/platform/connect_rum_and_traces/?tab=browserrum) for further information. */ export const TraceContextInjection = { ALL: 'all', SAMPLED: 'sampled', } as const /** * Trace context injection option. * * See [Connect RUM and Traces](https://docs.datadoghq.com/real_user_monitoring/platform/connect_rum_and_traces/?tab=browserrum) for further information. * */ export type TraceContextInjection = (typeof TraceContextInjection)[keyof typeof TraceContextInjection] export interface InitConfiguration { /** * The client token for Datadog. Required for authenticating your application with Datadog. * * @category Authentication */ clientToken: string /** * A callback function that can be used to modify events before they are sent to Datadog. * * @category Data Collection */ beforeSend?: GenericBeforeSendCallback | undefined /** * The percentage of sessions tracked. A value between 0 and 100. * * @category Data Collection * @defaultValue 100 */ sessionSampleRate?: number | undefined /** * The percentage of telemetry events sent. A value between 0 and 100. * * @category Data Collection * @defaultValue 20 */ telemetrySampleRate?: number | undefined /** * Initialization fails silently if the RUM Browser SDK is already initialized on the page. * * @defaultValue false */ silentMultipleInit?: boolean | undefined /** * Which storage strategy to use for persisting sessions. Can be 'cookie', 'local-storage', or 'memory'. * When an array is provided, the SDK will attempt each persistence type in the order specified. * * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values * * Note: 'memory' option is only for use with single-page applications. All page loads will start a new session, likely resulting in an increase in total number of RUM sessions * * @category Session Persistence * @defaultValue "cookie" */ sessionPersistence?: SessionPersistence | SessionPersistence[] | undefined /** * Allow listening to DOM events dispatched programmatically ([untrusted events](https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted)). Enabling this option can be useful if you heavily rely on programmatic events, such as in an automated UI test environment. * * @defaultValue false */ allowUntrustedEvents?: boolean | undefined /** * Store global context and user context in localStorage to preserve them along the user navigation. * See [Contexts life cycle](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#contexts-life-cycle) for further information. * * @defaultValue false */ storeContextsAcrossPages?: boolean | undefined /** * Set the initial user tracking consent state. * See [User tracking consent](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#user-tracking-consent) for further information. * * @category Privacy * @defaultValue granted */ trackingConsent?: TrackingConsent | undefined /** * List of origins where the SDK is allowed to run when used in a browser extension context. * Matches urls against the extensions origin. * If not provided and the SDK is running in a browser extension, the SDK will not run. */ allowedTrackingOrigins?: MatchOption[] | undefined // transport options /** * Optional proxy URL, for example: https://www.proxy.com/path. * See [Proxy Your Browser RUM Data](https://docs.datadoghq.com/real_user_monitoring/guide/proxy-rum-data) for further information. * * @category Transport */ proxy?: string | ProxyFn | undefined /** * The Datadog [site](https://docs.datadoghq.com/getting_started/site) parameter of your organization. * * @category Transport * @defaultValue datadoghq.com */ site?: Site | undefined /** * The OpenObserve ingestion API version used to build intake URLs. * * @category Transport * @defaultValue v1 */ apiVersion?: string | undefined /** * The OpenObserve organization identifier, used as a path segment of intake URLs. * * @category Transport * @defaultValue default */ organizationIdentifier?: string | undefined /** * Send data to the intake over plain HTTP instead of HTTPS. Only intended for local * development against a non-TLS OpenObserve instance. * * @category Transport */ insecureHTTP?: boolean | undefined // tag and context options /** * The service name for your application. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags). * * @category Data Collection */ service?: string | undefined | null /** * The application’s environment, for example: prod, pre-prod, and staging. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags). * * @category Data Collection */ env?: string | undefined | null /** * The application’s version, for example: 1.2.3, 6c44da20, and 2020.02.13. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags). * * @category Data Collection */ version?: string | undefined | null // cookie options /** * Use a partitioned secure cross-site session cookie. This allows the RUM Browser SDK to run when the site is loaded from another one (iframe). Implies `useSecureSessionCookie`. * * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values * * @category Session Persistence * @defaultValue false */ usePartitionedCrossSiteSessionCookie?: boolean | undefined /** * Use a secure session cookie. This disables RUM events sent on insecure (non-HTTPS) connections. * * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values * * @category Session Persistence * @defaultValue false */ useSecureSessionCookie?: boolean | undefined /** * Preserve the session across subdomains for the same site. * * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values * * @category Session Persistence * @defaultValue false */ trackSessionAcrossSubdomains?: boolean | undefined /** * Track anonymous user for the same site and extend cookie expiration date * * @category Data Collection * @defaultValue true */ trackAnonymousUser?: boolean | undefined // internal options /** * [Internal option] Enable experimental features * * @internal */ enableExperimentalFeatures?: string[] | undefined /** * [Internal option] Configure the dual shipping to another datacenter * * @internal */ replica?: ReplicaInitConfiguration | undefined /** * [Internal option] Set the datacenter from where the data is dual shipped * * @internal */ datacenter?: string /** * [Internal option] The percentage of telemetry configuration sent. A value between 0 and 100. * * @internal * @defaultValue 5 */ telemetryConfigurationSampleRate?: number /** * [Internal option] The percentage of telemetry usage sent. A value between 0 and 100. * * @internal * @defaultValue 5 */ telemetryUsageSampleRate?: number /** * [Internal option] Additional configuration for the SDK. * * @internal */ source?: 'browser' | 'flutter' | 'unity' | undefined /** * [Internal option] Additional configuration for the SDK. * * @internal */ sdkVersion?: string | undefined /** * [Internal option] Additional configuration for the SDK. * * @internal */ variant?: string | undefined } // This type is only used to build the core configuration. Logs and RUM SDKs are using a proper type // for this option. type GenericBeforeSendCallback = (event: any, context?: any) => unknown /** * path: /api/vX/product * parameters: xxx=yyy&zzz=aaa */ export type ProxyFn = (options: { path: string; parameters: string; subdomain?: string }) => string /** * @internal */ export interface ReplicaInitConfiguration { applicationId?: string clientToken: string } export type SdkSource = 'browser' | 'flutter' | 'unity' export interface Configuration { clientToken: string datacenter: string | undefined proxy: string | ProxyFn | undefined site: Site apiVersion: string organizationIdentifier: string insecureHTTP: boolean | undefined source: SdkSource beforeSend: GenericBeforeSendCallback | undefined cookieOptions: CookieOptions | undefined sessionPersistence: SessionPersistence | SessionPersistence[] | undefined sessionSampleRate: number telemetrySampleRate: number telemetryConfigurationSampleRate: number telemetryUsageSampleRate: number service?: string | undefined version?: string | undefined env?: string | undefined silentMultipleInit: boolean trackingConsent: TrackingConsent storeContextsAcrossPages: boolean trackAnonymousUser?: boolean // internal sdkVersion: string | undefined variant: string | undefined replica: ReplicaInitConfiguration | undefined } function isString(tag: unknown, tagName: string): tag is string | undefined | null { if (tag !== undefined && tag !== null && typeof tag !== 'string') { display.error(`${tagName} must be defined as a string`) return false } return true } export function isSampleRate(sampleRate: unknown, name: string) { if (sampleRate !== undefined && !isPercentage(sampleRate)) { display.error(`${name} Sample Rate should be a number between 0 and 100`) return false } return true } export function validateAndBuildConfiguration( initConfiguration: InitConfiguration, errorStack?: string ): Configuration | undefined { if (!initConfiguration?.clientToken) { display.error('Client Token is not configured, we will not send any data.') return } if ( initConfiguration.allowedTrackingOrigins !== undefined && !Array.isArray(initConfiguration.allowedTrackingOrigins) ) { display.error('Allowed Tracking Origins must be an array') return } if ( !isSampleRate(initConfiguration.sessionSampleRate, 'Session') || !isSampleRate(initConfiguration.telemetrySampleRate, 'Telemetry') || !isSampleRate(initConfiguration.telemetryConfigurationSampleRate, 'Telemetry Configuration') || !isSampleRate(initConfiguration.telemetryUsageSampleRate, 'Telemetry Usage') || !isString(initConfiguration.version, 'Version') || !isString(initConfiguration.env, 'Env') || !isString(initConfiguration.service, 'Service') || !isAllowedTrackingOrigins(initConfiguration, errorStack ?? '') ) { return } if ( initConfiguration.trackingConsent !== undefined && !objectHasValue(TrackingConsent, initConfiguration.trackingConsent) ) { display.error('Tracking Consent should be either "granted" or "not-granted"') return } return { clientToken: initConfiguration.clientToken, proxy: initConfiguration.proxy, site: initConfiguration.site || INTAKE_SITE_US1, apiVersion: initConfiguration.apiVersion ?? 'v1', organizationIdentifier: initConfiguration.organizationIdentifier ?? 'default', insecureHTTP: initConfiguration.insecureHTTP, source: validateSource(initConfiguration.source), beforeSend: initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'), cookieOptions: buildCookieOptions(initConfiguration), sessionPersistence: initConfiguration.sessionPersistence, sessionSampleRate: initConfiguration.sessionSampleRate ?? 100, telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20, telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5, telemetryUsageSampleRate: initConfiguration.telemetryUsageSampleRate ?? 5, service: initConfiguration.service ?? undefined, env: initConfiguration.env ?? undefined, version: initConfiguration.version ?? undefined, datacenter: initConfiguration.datacenter ?? undefined, silentMultipleInit: !!initConfiguration.silentMultipleInit, trackingConsent: initConfiguration.trackingConsent ?? TrackingConsent.GRANTED, trackAnonymousUser: initConfiguration.trackAnonymousUser ?? true, storeContextsAcrossPages: !!initConfiguration.storeContextsAcrossPages, /** * The source of the SDK, used for support plugins purposes. */ variant: initConfiguration.variant, sdkVersion: initConfiguration.sdkVersion, replica: initConfiguration.replica, } } export function buildCookieOptions(initConfiguration: InitConfiguration): CookieOptions | undefined { const cookieOptions: CookieOptions = {} cookieOptions.secure = !!initConfiguration.useSecureSessionCookie || !!initConfiguration.usePartitionedCrossSiteSessionCookie cookieOptions.crossSite = !!initConfiguration.usePartitionedCrossSiteSessionCookie cookieOptions.partitioned = !!initConfiguration.usePartitionedCrossSiteSessionCookie if (initConfiguration.trackSessionAcrossSubdomains) { const currentSite = getCurrentSite() if (!currentSite) { return } cookieOptions.domain = currentSite } return cookieOptions } export function serializeConfiguration(initConfiguration: InitConfiguration) { return { session_sample_rate: initConfiguration.sessionSampleRate, telemetry_sample_rate: initConfiguration.telemetrySampleRate, telemetry_configuration_sample_rate: initConfiguration.telemetryConfigurationSampleRate, telemetry_usage_sample_rate: initConfiguration.telemetryUsageSampleRate, use_before_send: !!initConfiguration.beforeSend, use_partitioned_cross_site_session_cookie: initConfiguration.usePartitionedCrossSiteSessionCookie, use_secure_session_cookie: initConfiguration.useSecureSessionCookie, use_proxy: !!initConfiguration.proxy, silent_multiple_init: initConfiguration.silentMultipleInit, track_session_across_subdomains: initConfiguration.trackSessionAcrossSubdomains, track_anonymous_user: initConfiguration.trackAnonymousUser, session_persistence: Array.isArray(initConfiguration.sessionPersistence) ? initConfiguration.sessionPersistence[0] : initConfiguration.sessionPersistence, store_contexts_across_pages: !!initConfiguration.storeContextsAcrossPages, allow_untrusted_events: !!initConfiguration.allowUntrustedEvents, tracking_consent: initConfiguration.trackingConsent, use_allowed_tracking_origins: Array.isArray(initConfiguration.allowedTrackingOrigins), source: initConfiguration.source, sdk_version: initConfiguration.sdkVersion, variant: initConfiguration.variant, } satisfies RawTelemetryConfiguration } function validateSource(source: string | undefined): SdkSource { if (source === 'flutter' || source === 'unity') { return source } return 'browser' }