Skip to content

Commit 4d848e6

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 bf9a162 commit 4d848e6

7 files changed

Lines changed: 102 additions & 27 deletions

File tree

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

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,56 @@ 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 { buildDeliveryApiUrl, startDeliveryApiPolling, stopDeliveryApiPolling, clearDeliveryApiState } from './deliveryApi'
77
import type { DeliveryApiConfiguration } from './deliveryApi'
88

9+
describe('buildDeliveryApiUrl', () => {
10+
it('should default to datadoghq.com', () => {
11+
expect(buildDeliveryApiUrl()).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
12+
})
13+
14+
it('should build URL for US1 site', () => {
15+
expect(buildDeliveryApiUrl('datadoghq.com')).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
16+
})
17+
18+
it('should build URL for EU1 site', () => {
19+
expect(buildDeliveryApiUrl('datadoghq.eu')).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes')
20+
})
21+
22+
it('should build URL for US3 site', () => {
23+
expect(buildDeliveryApiUrl('us3.datadoghq.com')).toBe(
24+
'https://api.us3.datadoghq.com/api/unstable/debugger/frontend/probes'
25+
)
26+
})
27+
28+
it('should build URL for staging site', () => {
29+
expect(buildDeliveryApiUrl('datad0g.com')).toBe('https://api.datad0g.com/api/unstable/debugger/frontend/probes')
30+
})
31+
32+
it('should build URL for gov site', () => {
33+
expect(buildDeliveryApiUrl('ddog-gov.com')).toBe('https://api.ddog-gov.com/api/unstable/debugger/frontend/probes')
34+
})
35+
36+
it('should use proxy as origin when provided', () => {
37+
expect(buildDeliveryApiUrl('datadoghq.com', 'http://localhost:9000')).toBe(
38+
'http://localhost:9000/api/unstable/debugger/frontend/probes'
39+
)
40+
})
41+
42+
it('should ignore site when proxy is provided', () => {
43+
expect(buildDeliveryApiUrl('datadoghq.eu', 'http://proxy.example.com')).toBe(
44+
'http://proxy.example.com/api/unstable/debugger/frontend/probes'
45+
)
46+
})
47+
})
48+
949
describe('deliveryApi', () => {
1050
let fetchSpy: jasmine.Spy
1151
let clock: Clock
1252

1353
function makeConfig(overrides: Partial<DeliveryApiConfiguration> = {}): DeliveryApiConfiguration {
1454
return {
55+
clientToken: 'test-client-token',
1556
applicationId: 'test-app-id',
1657
env: 'staging',
1758
version: '1.0.0',
@@ -57,11 +98,19 @@ describe('deliveryApi', () => {
5798

5899
expect(fetchSpy).toHaveBeenCalledTimes(1)
59100
const [url, options] = fetchSpy.calls.mostRecent().args
60-
expect(url).toBe('/api/ui/debugger/probe-delivery')
101+
expect(url).toBe('https://api.datadoghq.com/api/unstable/debugger/frontend/probes')
61102
expect(options.method).toBe('POST')
62-
expect(options.credentials).toBe('same-origin')
103+
expect(options.credentials).toBeUndefined()
63104
expect(options.headers['Content-Type']).toBe('application/json; charset=utf-8')
64105
expect(options.headers['Accept']).toBe('application/vnd.datadog.debugger-probes+json; version=1')
106+
expect(options.headers['dd-client-token']).toBe('test-client-token')
107+
})
108+
109+
it('should use the configured site for the request URL', () => {
110+
startDeliveryApiPolling(makeConfig({ site: 'datadoghq.eu' }))
111+
112+
const [url] = fetchSpy.calls.mostRecent().args
113+
expect(url).toBe('https://api.datadoghq.eu/api/unstable/debugger/frontend/probes')
65114
})
66115

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

packages/debugger/src/domain/deliveryApi.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
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 { display, fetch, getGlobalObject, mockable, setInterval, clearInterval, INTAKE_SITE_US1 } from '@datadog/browser-core'
33
import { addProbe, removeProbe } from './probes'
44
import type { Probe } from './probes'
55

66
declare const __BUILD_ENV__SDK_VERSION__: string
77

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-
}
8+
const DELIVERY_API_PATH = '/api/unstable/debugger/frontend/probes'
139

1410
export interface DeliveryApiConfiguration {
11+
clientToken: string
12+
site?: Site
13+
proxy?: string
1514
applicationId: string
1615
env?: string
1716
version?: string
1817
pollInterval?: number
1918
}
2019

20+
export function buildDeliveryApiUrl(site: Site = INTAKE_SITE_US1, proxy?: string): string {
21+
if (proxy) {
22+
return `${proxy}${DELIVERY_API_PATH}`
23+
}
24+
return `https://api.${site}${DELIVERY_API_PATH}`
25+
}
26+
2127
interface DeliveryApiResponse {
2228
nextCursor: string
2329
updates: Probe[]
@@ -31,9 +37,8 @@ let knownProbeIds = new Set<string>()
3137
/**
3238
* Start polling the Datadog Delivery API for probe updates.
3339
*
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.
40+
* Requests are authenticated via `dd-client-token` header (ClientTokenAuth)
41+
* against the public Smart Edge route.
3742
*/
3843
export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void {
3944
if (!('location' in mockable(getGlobalObject)())) {
@@ -46,6 +51,12 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void
4651
}
4752

4853
const pollInterval = config.pollInterval || 60_000
54+
const url = buildDeliveryApiUrl(config.site, config.proxy)
55+
const headers: Record<string, string> = {
56+
'Content-Type': 'application/json; charset=utf-8',
57+
Accept: 'application/vnd.datadog.debugger-probes+json; version=1',
58+
'dd-client-token': config.clientToken,
59+
}
4960

5061
const baseRequestBody = {
5162
applicationId: config.applicationId,
@@ -62,11 +73,10 @@ export function startDeliveryApiPolling(config: DeliveryApiConfiguration): void
6273
body.nextCursor = currentCursor
6374
}
6475

65-
const response = await fetch(DELIVERY_API_PATH, {
76+
const response = await fetch(url, {
6677
method: 'POST',
67-
headers: { ...DEFAULT_HEADERS },
78+
headers,
6879
body: JSON.stringify(body),
69-
credentials: 'same-origin',
7080
})
7181

7282
if (!response.ok) {

packages/debugger/src/entries/main.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ export interface DebuggerInitConfiguration {
6767
* @defaultValue 60000
6868
*/
6969
pollInterval?: number
70+
71+
/**
72+
* A proxy URL for routing SDK requests. When set, delivery API requests are
73+
* sent to `{proxy}/api/unstable/debugger/frontend/probes` instead of the
74+
* default Datadog API host derived from `site`.
75+
*
76+
* @category Transport
77+
*/
78+
proxy?: string
7079
}
7180

7281
/**
@@ -113,6 +122,9 @@ function makeDebuggerPublicApi(): DebuggerPublicApi {
113122
}
114123

115124
startDeliveryApiPolling({
125+
clientToken: initConfiguration.clientToken,
126+
site: initConfiguration.site,
127+
proxy: initConfiguration.proxy,
116128
applicationId: initConfiguration.applicationId,
117129
env: initConfiguration.env,
118130
version: initConfiguration.version,

test/e2e/lib/framework/httpServers.ts

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

1313
export type MockServerApp = ServerApp & {
1414
getLargeResponseWroteSize(): number
15+
}
16+
17+
export type IntakeServerApp = ServerApp & {
1518
setDebuggerProbes(probes: object[]): void
1619
}
1720

@@ -24,7 +27,7 @@ export interface Server<App extends ServerApp> {
2427

2528
export interface Servers {
2629
base: Server<MockServerApp>
27-
intake: Server<ServerApp>
30+
intake: Server<IntakeServerApp>
2831
crossOrigin: Server<MockServerApp>
2932
}
3033

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
@@ -220,17 +219,10 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption
220219
res.send(JSON.stringify(remoteConfiguration))
221220
})
222221

223-
app.post('/api/ui/debugger/probe-delivery', (_req, res) => {
224-
res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] })
225-
})
226-
227222
return Object.assign(app, {
228223
getLargeResponseWroteSize() {
229224
return largeResponseBytesWritten
230225
},
231-
setDebuggerProbes(probes: object[]) {
232-
debuggerProbes = probes
233-
},
234226
})
235227
}
236228

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({

0 commit comments

Comments
 (0)