Skip to content

Commit 331c32c

Browse files
committed
Switch debugger delivery to public ClientTokenAuth endpoint
Use the new POST /api/unstable/debugger/frontend/probes endpoint with dd-client-token header authentication instead of the same-origin /api/ui/debugger/probe-delivery route that relied on session cookies. - Build delivery URL from site config (https://api.{site}/...) - Authenticate via dd-client-token header instead of credentials: same-origin - Add proxy config for routing delivery requests through a custom origin - Move delivery mock from base server to intake server in E2E tests
1 parent e739e7e commit 331c32c

9 files changed

Lines changed: 139 additions & 31 deletions

File tree

packages/debugger/src/domain/deliveryApi.spec.ts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,80 @@ import { registerCleanupTask, mockClock, replaceMockable } from '@datadog/browse
33
import type { Clock } from '@datadog/browser-core/test'
44
import { getProbes, clearProbes } from './probes'
55
import type { Probe } from './probes'
6-
import { startDeliveryApiPolling, stopDeliveryApiPolling, clearDeliveryApiState } from './deliveryApi'
6+
import {
7+
buildDeliveryApiUrl,
8+
startDeliveryApiPolling,
9+
stopDeliveryApiPolling,
10+
clearDeliveryApiState,
11+
} from './deliveryApi'
712
import type { DeliveryApiConfiguration } from './deliveryApi'
813

14+
describe('buildDeliveryApiUrl', () => {
15+
it('should default to datadoghq.com', () => {
16+
expect(buildDeliveryApiUrl()).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
17+
})
18+
19+
it('should build URL for US1 site', () => {
20+
expect(buildDeliveryApiUrl('datadoghq.com')).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
21+
})
22+
23+
it('should build URL for EU1 site', () => {
24+
expect(buildDeliveryApiUrl('datadoghq.eu')).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes')
25+
})
26+
27+
it('should build URL for US3 site', () => {
28+
expect(buildDeliveryApiUrl('us3.datadoghq.com')).toBe(
29+
'https://api.us3.datadoghq.com/api/unstable/debugger/frontend/probes'
30+
)
31+
})
32+
33+
it('should build URL for staging site', () => {
34+
expect(buildDeliveryApiUrl('datad0g.com')).toBe('https://api.datad0g.com/api/unstable/debugger/frontend/probes')
35+
})
36+
37+
it('should build URL for gov site', () => {
38+
expect(buildDeliveryApiUrl('ddog-gov.com')).toBe('https://api.ddog-gov.com/api/unstable/debugger/frontend/probes')
39+
})
40+
41+
it('should use proxy as origin when provided', () => {
42+
expect(buildDeliveryApiUrl('datadoghq.com', 'http://localhost:9000')).toBe(
43+
'http://localhost:9000/api/unstable/debugger/frontend/probes'
44+
)
45+
})
46+
47+
it('should ignore site when proxy is provided', () => {
48+
expect(buildDeliveryApiUrl('datadoghq.eu', 'http://proxy.example.com')).toBe(
49+
'http://proxy.example.com/api/unstable/debugger/frontend/probes'
50+
)
51+
})
52+
53+
it('should trim a trailing slash from a proxy origin to avoid a double-slash path', () => {
54+
expect(buildDeliveryApiUrl('datadoghq.com', 'https://proxy.example.com/')).toBe(
55+
'https://proxy.example.com/api/unstable/debugger/frontend/probes'
56+
)
57+
})
58+
59+
it('should trim a trailing slash from a proxy that has a sub-path', () => {
60+
expect(buildDeliveryApiUrl('datadoghq.com', 'https://proxy.example.com/dd/')).toBe(
61+
'https://proxy.example.com/dd/api/unstable/debugger/frontend/probes'
62+
)
63+
})
64+
65+
it('should preserve a proxy sub-path that has no trailing slash', () => {
66+
expect(buildDeliveryApiUrl('datadoghq.com', 'https://proxy.example.com/dd')).toBe(
67+
'https://proxy.example.com/dd/api/unstable/debugger/frontend/probes'
68+
)
69+
})
70+
})
71+
972
describe('deliveryApi', () => {
1073
let fetchSpy: jasmine.Spy
1174
let clock: Clock
1275

1376
function makeConfig(overrides: Partial<DeliveryApiConfiguration> = {}): DeliveryApiConfiguration {
1477
return {
1578
service: 'test-service',
79+
clientToken: 'test-client-token',
1680
env: 'staging',
1781
version: '1.0.0',
1882
pollInterval: 5000,
@@ -57,11 +121,19 @@ describe('deliveryApi', () => {
57121

58122
expect(fetchSpy).toHaveBeenCalledTimes(1)
59123
const [url, options] = fetchSpy.calls.mostRecent().args
60-
expect(url).toBe('/api/ui/debugger/probe-delivery')
124+
expect(url).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
61125
expect(options.method).toBe('POST')
62-
expect(options.credentials).toBe('same-origin')
126+
expect(options.credentials).toBeUndefined()
63127
expect(options.headers['Content-Type']).toBe('application/json; charset=utf-8')
64128
expect(options.headers['Accept']).toBe('application/vnd.datadog.debugger-probes+json; version=1')
129+
expect(options.headers['dd-client-token']).toBe('test-client-token')
130+
})
131+
132+
it('should use the configured site for the request URL', () => {
133+
startDeliveryApiPolling(makeConfig({ site: 'datadoghq.eu' }))
134+
135+
const [url] = fetchSpy.calls.mostRecent().args
136+
expect(url).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes')
65137
})
66138

67139
it('should send the correct request body', () => {

packages/debugger/src/domain/deliveryApi.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
1-
import type { TimeoutId } from '@datadog/browser-core'
2-
import { display, fetch, getGlobalObject, mockable, setInterval, clearInterval } from '@datadog/browser-core'
1+
import type { TimeoutId, Site } from '@datadog/browser-core'
2+
import {
3+
display,
4+
fetch,
5+
getGlobalObject,
6+
mockable,
7+
setInterval,
8+
clearInterval,
9+
INTAKE_SITE_US1,
10+
} from '@datadog/browser-core'
311
import { addProbe, removeProbe } from './probes'
412
import type { Probe } from './probes'
513

614
declare const __BUILD_ENV__SDK_VERSION__: string
715

8-
const DELIVERY_API_PATH = '/api/ui/debugger/probe-delivery'
9-
const DEFAULT_HEADERS: Record<string, string> = {
10-
'Content-Type': 'application/json; charset=utf-8',
11-
Accept: 'application/vnd.datadog.debugger-probes+json; version=1',
12-
}
16+
const DELIVERY_API_PATH = '/api/unstable/debugger/frontend/probes'
1317

1418
export interface DeliveryApiConfiguration {
1519
service: string
20+
clientToken: string
21+
site?: Site
22+
proxy?: string
1623
env?: string
1724
version?: string
1825
pollInterval?: number
1926
}
2027

28+
export function buildDeliveryApiUrl(site: Site = INTAKE_SITE_US1, proxy?: string): string {
29+
if (proxy) {
30+
proxy = proxy.endsWith('/') ? proxy.slice(0, -1) : proxy
31+
return `${proxy}${DELIVERY_API_PATH}`
32+
}
33+
return `https://api.${site}${DELIVERY_API_PATH}`
34+
}
35+
2136
interface DeliveryApiResponse {
2237
nextCursor: string
2338
updates: Probe[]
@@ -31,9 +46,8 @@ let knownProbeIds = new Set<string>()
3146
/**
3247
* Start polling the Datadog Delivery API for probe updates.
3348
*
34-
* This is designed for dogfooding the Live Debugger inside the Datadog web UI,
35-
* where the user is already authenticated via session cookies (ValidUser auth).
36-
* Requests are same-origin, so no explicit domain is needed.
49+
* Requests are authenticated via `dd-client-token` header (ClientTokenAuth)
50+
* against the public Smart Edge route.
3751
*/
3852
export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void {
3953
if (!('location' in mockable(getGlobalObject)())) {
@@ -46,6 +60,12 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void
4660
}
4761

4862
const pollInterval = config.pollInterval || 60_000
63+
const url = buildDeliveryApiUrl(config.site, config.proxy)
64+
const headers: Record<string, string> = {
65+
'Content-Type': 'application/json; charset=utf-8',
66+
Accept: 'application/vnd.datadog.debugger-probes+json; version=1',
67+
'dd-client-token': config.clientToken,
68+
}
4969

5070
const baseRequestBody = {
5171
service: config.service,
@@ -62,11 +82,10 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void
6282
body.nextCursor = currentCursor
6383
}
6484

65-
const response = await fetch(DELIVERY_API_PATH, {
85+
const response = await fetch(url, {
6686
method: 'POST',
67-
headers: { ...DEFAULT_HEADERS },
87+
headers,
6888
body: JSON.stringify(body),
69-
credentials: 'same-origin',
7089
})
7190

7291
if (!response.ok) {

packages/debugger/src/entries/main.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ export interface DebuggerInitConfiguration {
6060
* @defaultValue 60000
6161
*/
6262
pollInterval?: number
63+
64+
/**
65+
* A proxy URL for routing SDK requests. When set, delivery API requests are
66+
* sent to `{proxy}/api/unstable/debugger/frontend/probes` instead of the
67+
* default Datadog API host derived from `site`.
68+
*
69+
* @category Transport
70+
*/
71+
proxy?: string
6372
}
6473

6574
/**
@@ -106,6 +115,9 @@ function makeDebuggerPublicApi(): DatadogDebugger {
106115

107116
startDeliveryApiPolling({
108117
service: initConfiguration.service,
118+
clientToken: initConfiguration.clientToken,
119+
site: initConfiguration.site,
120+
proxy: initConfiguration.proxy,
109121
env: initConfiguration.env,
110122
version: initConfiguration.version,
111123
pollInterval: initConfiguration.pollInterval,

test/e2e/lib/framework/httpServers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export type ServerApp = (req: http.IncomingMessage, res: http.ServerResponse) =>
1414

1515
export type MockServerApp = ServerApp & {
1616
getLargeResponseWroteSize(): number
17+
}
18+
19+
export type IntakeServerApp = ServerApp & {
1720
setDebuggerProbes(probes: object[]): void
1821
}
1922

@@ -26,7 +29,7 @@ export interface Server<App extends ServerApp> {
2629

2730
export interface Servers {
2831
base: Server<MockServerApp>
29-
intake: Server<ServerApp>
32+
intake: Server<IntakeServerApp>
3033
crossOrigin: Server<MockServerApp>
3134
}
3235

test/e2e/lib/framework/serverApps/intake.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ import type { IntakeRegistry } from '../intakeRegistry'
55

66
export function createIntakeServerApp(intakeRegistry: IntakeRegistry) {
77
const app = express()
8+
let debuggerProbes: object[] = []
89

910
app.use(cors())
1011

1112
app.post('/', createIntakeProxyMiddleware({ onRequest: (request) => intakeRegistry.push(request) }))
1213

13-
return app
14+
app.post('/api/unstable/debugger/frontend/probes', (_req, res) => {
15+
res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] })
16+
})
17+
18+
return Object.assign(app, {
19+
setDebuggerProbes(probes: object[]) {
20+
debuggerProbes = probes
21+
},
22+
})
1423
}

test/e2e/lib/framework/serverApps/mock.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption
1515
const { remoteConfiguration, worker } = setupOptions ?? {}
1616
const app = express()
1717
let largeResponseBytesWritten = 0
18-
let debuggerProbes: object[] = []
1918

2019
app.use(cors())
2120
app.disable('etag') // disable automatic resource caching
@@ -228,17 +227,10 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption
228227
res.send(JSON.stringify(remoteConfiguration))
229228
})
230229

231-
app.post('/api/ui/debugger/probe-delivery', (_req, res) => {
232-
res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] })
233-
})
234-
235230
return Object.assign(app, {
236231
getLargeResponseWroteSize() {
237232
return largeResponseBytesWritten
238233
},
239-
setDebuggerProbes(probes: object[]) {
240-
debuggerProbes = probes
241-
},
242234
})
243235
}
244236

test/e2e/scenario/debugger.scenario.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createTest } from '../lib/framework'
55
import type { Servers } from '../lib/framework'
66

77
function setDebuggerProbes(servers: Servers, probes: object[]) {
8-
servers.base.app.setDebuggerProbes(probes)
8+
servers.intake.app.setDebuggerProbes(probes)
99
}
1010

1111
function makeProbe({

test/performance/createBenchmarkTest.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function createBenchmarkTest(scenarioName: string) {
5858
}
5959

6060
if (shouldInjectDebugger(scenarioConfiguration)) {
61-
await injectDebugger(page, scenarioConfiguration)
61+
await injectDebugger(page, scenarioConfiguration, server.origin)
6262
}
6363

6464
await runner(page, takeMeasurements, buildAppUrl(server.origin, scenarioConfiguration))
@@ -150,11 +150,11 @@ async function injectRumSDK(page: Page, scenarioConfiguration: ScenarioConfigura
150150
* statistical soundness: it ensures V8 can JIT-optimize against the final code path during
151151
* the warmup phase, instead of deoptimizing mid-measurement when probes appear.
152152
*
153-
* Step 3 starts an async probe-delivery poll (mocked by the test server) and flips
153+
* Step 3 starts an async probe-delivery poll (proxied to the test server) and flips
154154
* `__benchmarkReady` once the response is observed. The benchmark scenario is responsible
155155
* for awaiting that flag before running its warmup.
156156
*/
157-
async function injectDebugger(page: Page, scenarioConfiguration: ScenarioConfiguration) {
157+
async function injectDebugger(page: Page, scenarioConfiguration: ScenarioConfiguration, proxy: string) {
158158
await page.addInitScript(() => {
159159
;(window as any).USE_INSTRUMENTED = true
160160
;(window as BrowserWindow).__benchmarkReady = false
@@ -172,6 +172,7 @@ async function injectDebugger(page: Page, scenarioConfiguration: ScenarioConfigu
172172
const configuration: DebuggerInitConfiguration = {
173173
clientToken: CLIENT_TOKEN,
174174
site: DATADOG_SITE,
175+
proxy,
175176
// The mock probe-delivery handler keys off `service` to decide which probes to return,
176177
// so we use the configuration name to keep parallel benchmark workers isolated.
177178
service: scenarioConfiguration,

test/performance/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface Server {
1414

1515
// Probe-delivery endpoint hardcoded in `packages/debugger/src/domain/deliveryApi.ts`.
1616
// Must be served from the same origin as the page since the debugger uses same-origin fetch.
17-
const DEBUGGER_PROBE_DELIVERY_PATH = '/api/ui/debugger/probe-delivery'
17+
const DEBUGGER_PROBE_DELIVERY_PATH = '/api/unstable/debugger/frontend/probes'
1818

1919
export function startPerformanceServer(scenarioName: string): Promise<Server> {
2020
return new Promise((resolve, reject) => {

0 commit comments

Comments
 (0)