diff --git a/packages/debugger/src/domain/deliveryApi.spec.ts b/packages/debugger/src/domain/deliveryApi.spec.ts index d9dcfaa07f..182eabae28 100644 --- a/packages/debugger/src/domain/deliveryApi.spec.ts +++ b/packages/debugger/src/domain/deliveryApi.spec.ts @@ -3,9 +3,54 @@ import { registerCleanupTask, mockClock, replaceMockable } from '@datadog/browse import type { Clock } from '@datadog/browser-core/test' import { getProbes, clearProbes } from './probes' import type { Probe } from './probes' -import { startDeliveryApiPolling, stopDeliveryApiPolling, clearDeliveryApiState } from './deliveryApi' +import { + buildDeliveryApiUrl, + startDeliveryApiPolling, + stopDeliveryApiPolling, + clearDeliveryApiState, +} from './deliveryApi' import type { DeliveryApiConfiguration } from './deliveryApi' +describe('buildDeliveryApiUrl', () => { + it('should default to datadoghq.com', () => { + expect(buildDeliveryApiUrl()).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes') + }) + + it('should build URL for US1 site', () => { + expect(buildDeliveryApiUrl('datadoghq.com')).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes') + }) + + it('should build URL for EU1 site', () => { + expect(buildDeliveryApiUrl('datadoghq.eu')).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes') + }) + + it('should build URL for US3 site', () => { + expect(buildDeliveryApiUrl('us3.datadoghq.com')).toBe( + 'https://api.us3.datadoghq.com/api/unstable/debugger/frontend/probes' + ) + }) + + it('should build URL for staging site', () => { + expect(buildDeliveryApiUrl('datad0g.com')).toBe('https://api.datad0g.com/api/unstable/debugger/frontend/probes') + }) + + it('should build URL for gov site', () => { + expect(buildDeliveryApiUrl('ddog-gov.com')).toBe('https://api.ddog-gov.com/api/unstable/debugger/frontend/probes') + }) + + it('should use proxy as origin when provided', () => { + expect(buildDeliveryApiUrl('datadoghq.com', 'http://localhost:9000')).toBe( + 'http://localhost:9000/api/unstable/debugger/frontend/probes' + ) + }) + + it('should ignore site when proxy is provided', () => { + expect(buildDeliveryApiUrl('datadoghq.eu', 'http://proxy.example.com')).toBe( + 'http://proxy.example.com/api/unstable/debugger/frontend/probes' + ) + }) +}) + describe('deliveryApi', () => { let fetchSpy: jasmine.Spy let clock: Clock @@ -13,6 +58,7 @@ describe('deliveryApi', () => { function makeConfig(overrides: Partial = {}): DeliveryApiConfiguration { return { service: 'test-service', + clientToken: 'test-client-token', env: 'staging', version: '1.0.0', pollInterval: 5000, @@ -57,11 +103,19 @@ describe('deliveryApi', () => { expect(fetchSpy).toHaveBeenCalledTimes(1) const [url, options] = fetchSpy.calls.mostRecent().args - expect(url).toBe('/api/ui/debugger/probe-delivery') + expect(url).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes') expect(options.method).toBe('POST') - expect(options.credentials).toBe('same-origin') + expect(options.credentials).toBeUndefined() expect(options.headers['Content-Type']).toBe('application/json; charset=utf-8') expect(options.headers['Accept']).toBe('application/vnd.datadog.debugger-probes+json; version=1') + expect(options.headers['dd-client-token']).toBe('test-client-token') + }) + + it('should use the configured site for the request URL', () => { + startDeliveryApiPolling(makeConfig({ site: 'datadoghq.eu' })) + + const [url] = fetchSpy.calls.mostRecent().args + expect(url).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes') }) it('should send the correct request body', () => { diff --git a/packages/debugger/src/domain/deliveryApi.ts b/packages/debugger/src/domain/deliveryApi.ts index 709d75e670..63c429b1c8 100644 --- a/packages/debugger/src/domain/deliveryApi.ts +++ b/packages/debugger/src/domain/deliveryApi.ts @@ -1,23 +1,37 @@ -import type { TimeoutId } from '@datadog/browser-core' -import { display, fetch, getGlobalObject, mockable, setInterval, clearInterval } from '@datadog/browser-core' +import type { TimeoutId, Site } from '@datadog/browser-core' +import { + display, + fetch, + getGlobalObject, + mockable, + setInterval, + clearInterval, + INTAKE_SITE_US1, +} from '@datadog/browser-core' import { addProbe, removeProbe } from './probes' import type { Probe } from './probes' declare const __BUILD_ENV__SDK_VERSION__: string -const DELIVERY_API_PATH = '/api/ui/debugger/probe-delivery' -const DEFAULT_HEADERS: Record = { - 'Content-Type': 'application/json; charset=utf-8', - Accept: 'application/vnd.datadog.debugger-probes+json; version=1', -} +const DELIVERY_API_PATH = '/api/unstable/debugger/frontend/probes' export interface DeliveryApiConfiguration { service: string + clientToken: string + site?: Site + proxy?: string env?: string version?: string pollInterval?: number } +export function buildDeliveryApiUrl(site: Site = INTAKE_SITE_US1, proxy?: string): string { + if (proxy) { + return `${proxy}${DELIVERY_API_PATH}` + } + return `https://api.${site}${DELIVERY_API_PATH}` +} + interface DeliveryApiResponse { nextCursor: string updates: Probe[] @@ -31,9 +45,8 @@ let knownProbeIds = new Set() /** * Start polling the Datadog Delivery API for probe updates. * - * This is designed for dogfooding the Live Debugger inside the Datadog web UI, - * where the user is already authenticated via session cookies (ValidUser auth). - * Requests are same-origin, so no explicit domain is needed. + * Requests are authenticated via `dd-client-token` header (ClientTokenAuth) + * against the public Smart Edge route. */ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void { if (!('location' in mockable(getGlobalObject)())) { @@ -46,6 +59,12 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void } const pollInterval = config.pollInterval || 60_000 + const url = buildDeliveryApiUrl(config.site, config.proxy) + const headers: Record = { + 'Content-Type': 'application/json; charset=utf-8', + Accept: 'application/vnd.datadog.debugger-probes+json; version=1', + 'dd-client-token': config.clientToken, + } const baseRequestBody = { service: config.service, @@ -62,11 +81,10 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void body.nextCursor = currentCursor } - const response = await fetch(DELIVERY_API_PATH, { + const response = await fetch(url, { method: 'POST', - headers: { ...DEFAULT_HEADERS }, + headers, body: JSON.stringify(body), - credentials: 'same-origin', }) if (!response.ok) { diff --git a/packages/debugger/src/entries/main.ts b/packages/debugger/src/entries/main.ts index 4862c28030..e7dbfd0f0b 100644 --- a/packages/debugger/src/entries/main.ts +++ b/packages/debugger/src/entries/main.ts @@ -60,6 +60,15 @@ export interface DebuggerInitConfiguration { * @defaultValue 60000 */ pollInterval?: number + + /** + * A proxy URL for routing SDK requests. When set, delivery API requests are + * sent to `{proxy}/api/unstable/debugger/frontend/probes` instead of the + * default Datadog API host derived from `site`. + * + * @category Transport + */ + proxy?: string } /** @@ -106,6 +115,9 @@ function makeDebuggerPublicApi(): DebuggerPublicApi { startDeliveryApiPolling({ service: initConfiguration.service, + clientToken: initConfiguration.clientToken, + site: initConfiguration.site, + proxy: initConfiguration.proxy, env: initConfiguration.env, version: initConfiguration.version, pollInterval: initConfiguration.pollInterval, diff --git a/test/e2e/lib/framework/httpServers.ts b/test/e2e/lib/framework/httpServers.ts index be3d293456..9088673f23 100644 --- a/test/e2e/lib/framework/httpServers.ts +++ b/test/e2e/lib/framework/httpServers.ts @@ -14,6 +14,9 @@ export type ServerApp = (req: http.IncomingMessage, res: http.ServerResponse) => export type MockServerApp = ServerApp & { getLargeResponseWroteSize(): number +} + +export type IntakeServerApp = ServerApp & { setDebuggerProbes(probes: object[]): void } @@ -26,7 +29,7 @@ export interface Server { export interface Servers { base: Server - intake: Server + intake: Server crossOrigin: Server } diff --git a/test/e2e/lib/framework/serverApps/intake.ts b/test/e2e/lib/framework/serverApps/intake.ts index c1c5528a1c..bd6444861c 100644 --- a/test/e2e/lib/framework/serverApps/intake.ts +++ b/test/e2e/lib/framework/serverApps/intake.ts @@ -5,10 +5,19 @@ import type { IntakeRegistry } from '../intakeRegistry' export function createIntakeServerApp(intakeRegistry: IntakeRegistry) { const app = express() + let debuggerProbes: object[] = [] app.use(cors()) app.post('/', createIntakeProxyMiddleware({ onRequest: (request) => intakeRegistry.push(request) })) - return app + app.post('/api/unstable/debugger/frontend/probes', (_req, res) => { + res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] }) + }) + + return Object.assign(app, { + setDebuggerProbes(probes: object[]) { + debuggerProbes = probes + }, + }) } diff --git a/test/e2e/lib/framework/serverApps/mock.ts b/test/e2e/lib/framework/serverApps/mock.ts index ee6c71b686..876a9d0bd9 100644 --- a/test/e2e/lib/framework/serverApps/mock.ts +++ b/test/e2e/lib/framework/serverApps/mock.ts @@ -15,7 +15,6 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption const { remoteConfiguration, worker } = setupOptions ?? {} const app = express() let largeResponseBytesWritten = 0 - let debuggerProbes: object[] = [] app.use(cors()) app.disable('etag') // disable automatic resource caching @@ -228,17 +227,10 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption res.send(JSON.stringify(remoteConfiguration)) }) - app.post('/api/ui/debugger/probe-delivery', (_req, res) => { - res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] }) - }) - return Object.assign(app, { getLargeResponseWroteSize() { return largeResponseBytesWritten }, - setDebuggerProbes(probes: object[]) { - debuggerProbes = probes - }, }) } diff --git a/test/e2e/scenario/debugger.scenario.ts b/test/e2e/scenario/debugger.scenario.ts index 005142df0a..753f5f4200 100644 --- a/test/e2e/scenario/debugger.scenario.ts +++ b/test/e2e/scenario/debugger.scenario.ts @@ -5,7 +5,7 @@ import { createTest } from '../lib/framework' import type { Servers } from '../lib/framework' function setDebuggerProbes(servers: Servers, probes: object[]) { - servers.base.app.setDebuggerProbes(probes) + servers.intake.app.setDebuggerProbes(probes) } function makeProbe({