Skip to content

Commit cd05b6a

Browse files
committed
Default debugger init version from build metadata
Use build-plugin injected Live Debugger metadata as the default runtime `init().version`, and warn when an explicit init version disagrees. Document the fallback behavior and cover the new version resolution path in unit tests.
1 parent 0a9836c commit cd05b6a

3 files changed

Lines changed: 143 additions & 30 deletions

File tree

packages/debugger/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ datadogDebugger.init({
1616
site: '<DATADOG_SITE>',
1717
service: 'my-web-application',
1818
// env: 'production',
19-
// version: '1.0.0',
19+
// version: 'my-deployed-build-version',
2020
})
2121
```
2222

23+
When you also use the Datadog Live Debugger build plugin, `init().version` defaults to the build-time `liveDebugger.version` metadata injected into the bundle. If you pass both values explicitly and they differ, the SDK keeps the `init()` value and logs a warning.
24+
25+
If provided, `version` should be set to the immutable deployed browser build identifier used for source map upload and browser build resolution. If omitted, debugger delivery and snapshots still work, but browser build lookup and source-aware resolution may be unavailable.
26+
2327
## Troubleshooting
2428

2529
Need help? Contact [Datadog Support][3].
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,98 @@
1+
import { display } from '@datadog/browser-core'
2+
import { registerCleanupTask, replaceMockableWithSpy } from '@datadog/browser-core/test'
3+
import { initDebuggerTransport } from '../domain/api'
4+
import { startDeliveryApiPolling } from '../domain/deliveryApi'
5+
import { startDebuggerBatch } from '../transport/startDebuggerBatch'
6+
import type { BrowserWindow } from './main'
17
import { datadogDebugger } from './main'
28

39
describe('datadogDebugger', () => {
10+
const browserWindow: BrowserWindow = window
11+
12+
beforeEach(() => {
13+
delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__
14+
delete browserWindow.$dd_entry
15+
delete browserWindow.$dd_return
16+
delete browserWindow.$dd_throw
17+
delete browserWindow.$dd_probes
18+
19+
registerCleanupTask(() => {
20+
delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__
21+
delete browserWindow.$dd_entry
22+
delete browserWindow.$dd_return
23+
delete browserWindow.$dd_throw
24+
delete browserWindow.$dd_probes
25+
})
26+
})
27+
428
it('should only expose init, version, and onReady', () => {
529
expect(datadogDebugger).toEqual({
630
init: jasmine.any(Function),
731
version: jasmine.any(String),
832
onReady: jasmine.any(Function),
933
})
1034
})
35+
36+
it('should default the init version from build-plugin metadata', async () => {
37+
browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' }
38+
replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({
39+
flushController: undefined as any,
40+
add: () => undefined,
41+
flush: () => undefined,
42+
stop: () => undefined,
43+
upsert: () => undefined,
44+
}))
45+
const initTransportSpy = replaceMockableWithSpy(initDebuggerTransport)
46+
const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling)
47+
48+
datadogDebugger.init({
49+
clientToken: 'client-token',
50+
service: 'service-name',
51+
env: 'staging',
52+
})
53+
54+
await flushPromises()
55+
56+
expect(initTransportSpy).toHaveBeenCalledWith(
57+
jasmine.objectContaining({ version: 'build-version' }),
58+
jasmine.anything()
59+
)
60+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'build-version' }))
61+
expect(browserWindow.$dd_entry).toBeDefined()
62+
expect(browserWindow.$dd_return).toBeDefined()
63+
expect(browserWindow.$dd_throw).toBeDefined()
64+
expect(browserWindow.$dd_probes).toBeDefined()
65+
})
66+
67+
it('should warn when the explicit init version mismatches build-plugin metadata', async () => {
68+
browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' }
69+
replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({
70+
flushController: undefined as any,
71+
add: () => undefined,
72+
flush: () => undefined,
73+
stop: () => undefined,
74+
upsert: () => undefined,
75+
}))
76+
replaceMockableWithSpy(initDebuggerTransport)
77+
const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling)
78+
const warnSpy = spyOn(display, 'warn')
79+
80+
datadogDebugger.init({
81+
clientToken: 'client-token',
82+
service: 'service-name',
83+
env: 'staging',
84+
version: 'runtime-version',
85+
})
86+
87+
await flushPromises()
88+
89+
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringMatching(/does not match the build-plugin version/))
90+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'runtime-version' }))
91+
})
1192
})
93+
94+
async function flushPromises() {
95+
for (let i = 0; i < 10; i++) {
96+
await Promise.resolve()
97+
}
98+
}

packages/debugger/src/entries/main.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@
66
* @see [Live Debugger Documentation](https://docs.datadoghq.com/tracing/live_debugger/)
77
*/
88

9-
import { defineGlobal, getGlobalObject, makePublicApi } from '@datadog/browser-core'
9+
import { defineGlobal, display, getGlobalObject, makePublicApi, mockable } from '@datadog/browser-core'
1010
import type { PublicApi, Site } from '@datadog/browser-core'
11-
import { onEntry, onReturn, onThrow, initDebuggerTransport } from '../domain/api'
11+
import { initDebuggerTransport, onEntry, onReturn, onThrow } from '../domain/api'
1212
import { startDeliveryApiPolling } from '../domain/deliveryApi'
1313
import { getProbes } from '../domain/probes'
1414
import { startDebuggerBatch } from '../transport/startDebuggerBatch'
1515

16-
type DebuggerInstrumentationGlobal = typeof globalThis & {
17-
$dd_entry?: typeof onEntry
18-
$dd_return?: typeof onReturn
19-
$dd_throw?: typeof onThrow
20-
$dd_probes?: typeof getProbes
16+
export interface DebuggerBuildMetadata {
17+
version?: string
2118
}
2219

2320
/**
@@ -120,39 +117,68 @@ export interface DatadogDebugger extends PublicApi {
120117
* service: 'my-app',
121118
* site: 'datadoghq.com',
122119
* env: 'production'
120+
* version: 'my-deployed-build-version',
123121
* })
124122
* ```
125123
*/
126124
init: (initConfiguration: DebuggerInitConfiguration) => void
127125
}
128126

127+
export interface BrowserWindow extends Window {
128+
DD_DEBUGGER?: DatadogDebugger
129+
__DD_LIVE_DEBUGGER_BUILD__?: DebuggerBuildMetadata
130+
$dd_entry?: typeof onEntry
131+
$dd_return?: typeof onReturn
132+
$dd_throw?: typeof onThrow
133+
$dd_probes?: typeof getProbes
134+
}
135+
136+
function resolveDebuggerVersion(initConfiguration: DebuggerInitConfiguration): string | undefined {
137+
const buildVersion = getGlobalObject<BrowserWindow>().__DD_LIVE_DEBUGGER_BUILD__?.version
138+
139+
if (
140+
initConfiguration.version !== undefined &&
141+
buildVersion !== undefined &&
142+
initConfiguration.version !== buildVersion
143+
) {
144+
display.warn(
145+
`Debugger: init version "${initConfiguration.version}" does not match the build-plugin version "${buildVersion}". Using the init version.`
146+
)
147+
}
148+
149+
return initConfiguration.version ?? buildVersion
150+
}
151+
129152
/**
130153
* Create the public API for the Live Debugger
131154
*/
132155
function makeDebuggerPublicApi(): DatadogDebugger {
133156
return makePublicApi<DatadogDebugger>({
134157
init: (initConfiguration: DebuggerInitConfiguration) => {
158+
const resolvedConfiguration = {
159+
...initConfiguration,
160+
version: resolveDebuggerVersion(initConfiguration),
161+
}
162+
135163
// Initialize debugger's own transport
136-
const batch = startDebuggerBatch(initConfiguration)
137-
initDebuggerTransport(initConfiguration, batch)
164+
const batch = mockable(startDebuggerBatch)(resolvedConfiguration)
165+
mockable(initDebuggerTransport)(resolvedConfiguration, batch)
138166

139167
// Expose internal hooks on globalThis for instrumented code
140-
if (typeof globalThis !== 'undefined') {
141-
const debuggerGlobal = globalThis as DebuggerInstrumentationGlobal
142-
debuggerGlobal.$dd_entry = onEntry
143-
debuggerGlobal.$dd_return = onReturn
144-
debuggerGlobal.$dd_throw = onThrow
145-
debuggerGlobal.$dd_probes = getProbes
146-
}
147-
148-
startDeliveryApiPolling({
149-
service: initConfiguration.service,
150-
clientToken: initConfiguration.clientToken,
151-
site: initConfiguration.site,
152-
proxy: initConfiguration.proxy,
153-
env: initConfiguration.env,
154-
version: initConfiguration.version,
155-
pollInterval: initConfiguration.pollInterval,
168+
const debuggerGlobal = getGlobalObject<BrowserWindow>()
169+
debuggerGlobal.$dd_entry = onEntry
170+
debuggerGlobal.$dd_return = onReturn
171+
debuggerGlobal.$dd_throw = onThrow
172+
debuggerGlobal.$dd_probes = getProbes
173+
174+
mockable(startDeliveryApiPolling)({
175+
service: resolvedConfiguration.service,
176+
clientToken: resolvedConfiguration.clientToken,
177+
site: resolvedConfiguration.site,
178+
proxy: resolvedConfiguration.proxy,
179+
env: resolvedConfiguration.env,
180+
version: resolvedConfiguration.version,
181+
pollInterval: resolvedConfiguration.pollInterval,
156182
})
157183
},
158184
})
@@ -167,8 +193,4 @@ function makeDebuggerPublicApi(): DatadogDebugger {
167193
*/
168194
export const datadogDebugger = makeDebuggerPublicApi()
169195

170-
export interface BrowserWindow extends Window {
171-
DD_DEBUGGER?: DatadogDebugger
172-
}
173-
174196
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_DEBUGGER', datadogDebugger)

0 commit comments

Comments
 (0)