diff --git a/packages/core/src/domain/automaticErrorCollection.ts b/packages/core/src/domain/automaticErrorCollection.ts index ab998c9f60..18934372da 100644 --- a/packages/core/src/domain/automaticErrorCollection.ts +++ b/packages/core/src/domain/automaticErrorCollection.ts @@ -3,7 +3,7 @@ import { resetXhrProxy, startXhrProxy, XhrCompleteContext } from '../browser/xhr import { ErrorSource, formatUnknownError, RawError, toStackTraceString } from '../tools/error' import { Observable } from '../tools/observable' import { jsonStringify, ONE_MINUTE, RequestType } from '../tools/utils' -import { Configuration, isIntakeRequest } from './configuration' +import { Configuration } from './configuration' import { monitor } from './internalMonitoring' import { computeStackTrace, Handler, report, StackTrace } from './tracekit' @@ -94,7 +94,7 @@ export function trackNetworkError(configuration: Configuration, errorObservable: startFetchProxy().onRequestComplete((context) => handleCompleteRequest(RequestType.FETCH, context)) function handleCompleteRequest(type: RequestType, request: XhrCompleteContext | FetchCompleteContext) { - if (!isIntakeRequest(request.url, configuration) && (isRejected(request) || isServerError(request))) { + if (!configuration.isIntakeUrl(request.url) && (isRejected(request) || isServerError(request))) { errorObservable.notify({ message: `${format(type)} error ${request.method} ${request.url}`, resource: { diff --git a/packages/core/src/domain/configuration.spec.ts b/packages/core/src/domain/configuration.spec.ts index a063e19f08..b9936680ba 100644 --- a/packages/core/src/domain/configuration.spec.ts +++ b/packages/core/src/domain/configuration.spec.ts @@ -1,5 +1,5 @@ import { BuildEnv, BuildMode, Datacenter } from '../boot/init' -import { buildConfiguration, isIntakeRequest } from './configuration' +import { buildConfiguration } from './configuration' describe('configuration', () => { const clientToken = 'some_client_token' @@ -52,9 +52,9 @@ describe('configuration', () => { describe('proxyHost', () => { it('should replace endpoint host add set it as a query parameter', () => { - const configuration = buildConfiguration({ clientToken, proxyHost: 'proxy.io' }, usEnv) + const configuration = buildConfiguration({ clientToken, site: 'datadoghq.eu', proxyHost: 'proxy.io' }, usEnv) expect(configuration.rumEndpoint).toMatch(/^https:\/\/proxy\.io\//) - expect(configuration.rumEndpoint).toContain('?ddhost=rum-http-intake.logs.datadoghq.com&') + expect(configuration.rumEndpoint).toContain('?ddhost=rum-http-intake.logs.datadoghq.eu&') }) }) @@ -104,31 +104,68 @@ describe('configuration', () => { }) }) - describe('isIntakeRequest', () => { + describe('isIntakeUrl', () => { it('should not detect non intake request', () => { const configuration = buildConfiguration({ clientToken }, usEnv) - expect(isIntakeRequest('https://www.foo.com', configuration)).toBe(false) + expect(configuration.isIntakeUrl('https://www.foo.com')).toBe(false) }) - it('should detect intake request', () => { + it('should detect intake request for EU site', () => { + const configuration = buildConfiguration({ clientToken, site: 'datadoghq.eu' }, usEnv) + expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true) + }) + + it('should detect intake request for US site', () => { const configuration = buildConfiguration({ clientToken }, usEnv) - expect(isIntakeRequest('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true) - expect(isIntakeRequest('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true) - expect(isIntakeRequest('https://public-trace-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe( + + expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true) + }) + + it('should detect alternate intake domains for US site', () => { + const configuration = buildConfiguration({ clientToken, useAlternateIntakeDomains: true }, usEnv) + expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://trace.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true) + }) + + it('should handle sites with subdomains and classic intake', () => { + const configuration = buildConfiguration({ clientToken, site: 'us3.datadoghq.com' }, usEnv) + expect(configuration.isIntakeUrl('https://rum-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://browser-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe( true ) }) + it('should handle sites with subdomains and alternate intake', () => { + const configuration = buildConfiguration( + { clientToken, site: 'us3.datadoghq.com', useAlternateIntakeDomains: true }, + usEnv + ) + expect(configuration.isIntakeUrl('https://rum.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://logs.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://trace.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true) + }) + it('should detect proxy intake request', () => { let configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com' }, usEnv) - expect(isIntakeRequest('https://www.proxy.com/v1/input/xxx', configuration)).toBe(true) + expect(configuration.isIntakeUrl('https://www.proxy.com/v1/input/xxx')).toBe(true) + configuration = buildConfiguration( + { clientToken, proxyHost: 'www.proxy.com', useAlternateIntakeDomains: true }, + usEnv + ) + expect(configuration.isIntakeUrl('https://www.proxy.com/v1/input/xxx')).toBe(true) configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com/custom/path' }, usEnv) - expect(isIntakeRequest('https://www.proxy.com/custom/path/v1/input/xxx', configuration)).toBe(true) + expect(configuration.isIntakeUrl('https://www.proxy.com/custom/path/v1/input/xxx')).toBe(true) }) it('should not detect request done on the same host as the proxy', () => { const configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com' }, usEnv) - expect(isIntakeRequest('https://www.proxy.com/foo', configuration)).toBe(false) + expect(configuration.isIntakeUrl('https://www.proxy.com/foo')).toBe(false) }) it('should detect replica intake request', () => { @@ -136,12 +173,25 @@ describe('configuration', () => { { clientToken, site: 'foo.com', replica: { clientToken } }, { ...usEnv, buildMode: BuildMode.STAGING } ) - expect(isIntakeRequest('https://rum-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true) - expect(isIntakeRequest('https://browser-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true) - expect(isIntakeRequest('https://public-trace-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true) + expect(configuration.isIntakeUrl('https://rum-http-intake.logs.foo.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://browser-http-intake.logs.foo.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.foo.com/v1/input/xxx')).toBe(true) + + expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true) + }) + + it('should detect replica intake request with alternate intake domains', () => { + const configuration = buildConfiguration( + { clientToken, site: 'foo.com', replica: { clientToken }, useAlternateIntakeDomains: true }, + { ...usEnv, buildMode: BuildMode.STAGING } + ) + expect(configuration.isIntakeUrl('https://rum.browser-intake-foo.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://logs.browser-intake-foo.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://trace.browser-intake-foo.com/v1/input/xxx')).toBe(true) - expect(isIntakeRequest('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true) - expect(isIntakeRequest('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true) + expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true) + expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true) }) }) }) diff --git a/packages/core/src/domain/configuration.ts b/packages/core/src/domain/configuration.ts index 605cf91a06..2cb04c6f49 100644 --- a/packages/core/src/domain/configuration.ts +++ b/packages/core/src/domain/configuration.ts @@ -1,6 +1,5 @@ import { BuildEnv, BuildMode, Datacenter, INTAKE_SITE } from '../boot/init' import { CookieOptions, getCurrentSite } from '../browser/cookie' -import { getPathName, haveSameOrigin } from '../tools/urlPolyfill' import { includes, ONE_KILO_BYTE, ONE_SECOND } from '../tools/utils' export const DEFAULT_CONFIGURATION = { @@ -55,6 +54,7 @@ export interface UserConfiguration { env?: string version?: string + useAlternateIntakeDomains?: boolean useCrossSiteSessionCookie?: boolean useSecureSessionCookie?: boolean trackSessionAcrossSubdomains?: boolean @@ -79,6 +79,7 @@ export type Configuration = typeof DEFAULT_CONFIGURATION & { service?: string isEnabled: (feature: string) => boolean + isIntakeUrl: (url: string) => boolean // only on staging build mode replica?: ReplicaConfiguration @@ -104,6 +105,21 @@ interface TransportConfiguration { version?: string } +const ENDPOINTS = { + alternate: { + logs: 'logs', + rum: 'rum', + trace: 'trace', + }, + classic: { + logs: 'browser', + rum: 'rum', + trace: 'public-trace', + }, +} +type IntakeType = keyof typeof ENDPOINTS +type EndpointType = keyof (typeof ENDPOINTS)[IntakeType] + export function buildConfiguration(userConfiguration: UserConfiguration, buildEnv: BuildEnv): Configuration { const transportConfiguration: TransportConfiguration = { applicationId: userConfiguration.applicationId, @@ -121,21 +137,26 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn ? userConfiguration.enableExperimentalFeatures : [] + const intakeType: IntakeType = userConfiguration.useAlternateIntakeDomains ? 'alternate' : 'classic' + const intakeUrls = getIntakeUrls(intakeType, transportConfiguration, userConfiguration.replica !== undefined) const configuration: Configuration = { cookieOptions: buildCookieOptions(userConfiguration), isEnabled: (feature: string) => { return includes(enableExperimentalFeatures, feature) }, - logsEndpoint: getEndpoint('browser', transportConfiguration), + logsEndpoint: getEndpoint(intakeType, 'logs', transportConfiguration), proxyHost: userConfiguration.proxyHost, - rumEndpoint: getEndpoint('rum', transportConfiguration), + rumEndpoint: getEndpoint(intakeType, 'rum', transportConfiguration), service: userConfiguration.service, - traceEndpoint: getEndpoint('public-trace', transportConfiguration), + traceEndpoint: getEndpoint(intakeType, 'trace', transportConfiguration), + + isIntakeUrl: (url) => intakeUrls.some((intakeUrl) => url.indexOf(intakeUrl) === 0), ...DEFAULT_CONFIGURATION, } if (userConfiguration.internalMonitoringApiKey) { configuration.internalMonitoringEndpoint = getEndpoint( - 'browser', + intakeType, + 'logs', transportConfiguration, 'browser-agent-internal-monitoring' ) @@ -174,12 +195,13 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn configuration.replica = { applicationId: userConfiguration.replica.applicationId, internalMonitoringEndpoint: getEndpoint( - 'browser', + intakeType, + 'logs', replicaTransportConfiguration, 'browser-agent-internal-monitoring' ), - logsEndpoint: getEndpoint('browser', replicaTransportConfiguration), - rumEndpoint: getEndpoint('rum', replicaTransportConfiguration), + logsEndpoint: getEndpoint(intakeType, 'logs', replicaTransportConfiguration), + rumEndpoint: getEndpoint(intakeType, 'rum', replicaTransportConfiguration), } } } @@ -200,13 +222,18 @@ export function buildCookieOptions(userConfiguration: UserConfiguration) { return cookieOptions } -function getEndpoint(type: string, conf: TransportConfiguration, source?: string) { +function getEndpoint( + intakeType: IntakeType, + endpointType: EndpointType, + conf: TransportConfiguration, + source?: string +) { const tags = `sdk_version:${conf.sdkVersion}` + `${conf.env ? `,env:${conf.env}` : ''}` + `${conf.service ? `,service:${conf.service}` : ''}` + `${conf.version ? `,version:${conf.version}` : ''}` - const datadogHost = `${type}-http-intake.logs.${conf.site}` + const datadogHost = getHost(intakeType, endpointType, conf.site) const host = conf.proxyHost ? conf.proxyHost : datadogHost const proxyParameter = conf.proxyHost ? `ddhost=${datadogHost}&` : '' const applicationIdParameter = conf.applicationId ? `_dd.application_id=${conf.applicationId}&` : '' @@ -215,18 +242,33 @@ function getEndpoint(type: string, conf: TransportConfiguration, source?: string return `https://${host}/v1/input/${conf.clientToken}?${parameters}` } -export function isIntakeRequest(url: string, configuration: Configuration) { - return ( - getPathName(url).indexOf('/v1/input/') !== -1 && - (haveSameOrigin(url, configuration.logsEndpoint) || - haveSameOrigin(url, configuration.rumEndpoint) || - haveSameOrigin(url, configuration.traceEndpoint) || - (!!configuration.internalMonitoringEndpoint && haveSameOrigin(url, configuration.internalMonitoringEndpoint)) || - (!!configuration.replica && - (haveSameOrigin(url, configuration.replica.logsEndpoint) || - haveSameOrigin(url, configuration.replica.rumEndpoint) || - haveSameOrigin(url, configuration.replica.internalMonitoringEndpoint)))) - ) +function getHost(intakeType: IntakeType, endpointType: EndpointType, site: string) { + const endpoint = ENDPOINTS[intakeType][endpointType] + if (intakeType === 'classic') { + return `${endpoint}-http-intake.logs.${site}` + } + const domainParts = site.split('.') + const extension = domainParts.pop() + const suffix = `${domainParts.join('-')}.${extension}` + return `${endpoint}.browser-intake-${suffix}` +} + +function getIntakeUrls(intakeType: IntakeType, conf: TransportConfiguration, withReplica: boolean) { + if (conf.proxyHost) { + return [`https://${conf.proxyHost}/v1/input/`] + } + const sites = [conf.site] + if (conf.buildMode === BuildMode.STAGING && withReplica) { + sites.push(INTAKE_SITE[Datacenter.US]) + } + const urls = [] + const endpointTypes = Object.keys(ENDPOINTS[intakeType]) as EndpointType[] + for (const site of sites) { + for (const endpointType of endpointTypes) { + urls.push(`https://${getHost(intakeType, endpointType, site)}/v1/input/`) + } + } + return urls } function mustUseSecureCookie(userConfiguration: UserConfiguration) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f2d9ed3705..e5356f2eb7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,10 +1,4 @@ -export { - DEFAULT_CONFIGURATION, - Configuration, - UserConfiguration, - isIntakeRequest, - buildCookieOptions, -} from './domain/configuration' +export { DEFAULT_CONFIGURATION, Configuration, UserConfiguration, buildCookieOptions } from './domain/configuration' export { startAutomaticErrorCollection, ErrorObservable } from './domain/automaticErrorCollection' export { computeStackTrace } from './domain/tracekit' export { diff --git a/packages/core/src/tools/specHelper.ts b/packages/core/src/tools/specHelper.ts index a1ff5af86f..e51d760a6c 100644 --- a/packages/core/src/tools/specHelper.ts +++ b/packages/core/src/tools/specHelper.ts @@ -6,6 +6,16 @@ export const SPEC_ENDPOINTS: Partial = { logsEndpoint: 'https://logs-intake.com/v1/input/abcde?foo=bar', rumEndpoint: 'https://rum-intake.com/v1/input/abcde?foo=bar', traceEndpoint: 'https://trace-intake.com/v1/input/abcde?foo=bar', + + isIntakeUrl: (url: string) => { + const intakeUrls = [ + 'https://monitoring-intake.com/v1/input/', + 'https://logs-intake.com/v1/input/', + 'https://rum-intake.com/v1/input/', + 'https://trace-intake.com/v1/input/', + ] + return intakeUrls.some((intakeUrl) => url.indexOf(intakeUrl) === 0) + }, } export function isSafari() { diff --git a/packages/rum/src/domain/rumEventsCollection/resource/resourceUtils.ts b/packages/rum/src/domain/rumEventsCollection/resource/resourceUtils.ts index 7764df0dad..c4115f4e62 100644 --- a/packages/rum/src/domain/rumEventsCollection/resource/resourceUtils.ts +++ b/packages/rum/src/domain/rumEventsCollection/resource/resourceUtils.ts @@ -3,7 +3,6 @@ import { Configuration, getPathName, includes, - isIntakeRequest, isValidUrl, msToNs, ResourceType, @@ -204,5 +203,5 @@ export function computeSize(entry: RumPerformanceResourceTiming) { } export function isAllowedRequestUrl(configuration: Configuration, url: string) { - return url && !isIntakeRequest(url, configuration) + return url && !configuration.isIntakeUrl(url) }