Skip to content

Commit c3334a6

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 653aee9 commit c3334a6

3 files changed

Lines changed: 139 additions & 19 deletions

File tree

packages/debugger/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ datadogDebugger.init({
2121
})
2222
```
2323

24+
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.
25+
2426
If [Datadog RUM][3] is also initialized on the page, debugger snapshots automatically include RUM context (session, view, user action) without any additional configuration.
2527

2628
## Troubleshooting
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,100 @@
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+
applicationId: 'app-id',
50+
clientToken: 'client-token',
51+
service: 'service-name',
52+
env: 'staging',
53+
})
54+
55+
await flushPromises()
56+
57+
expect(initTransportSpy).toHaveBeenCalledWith(
58+
jasmine.objectContaining({ version: 'build-version' }),
59+
jasmine.anything()
60+
)
61+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'build-version' }))
62+
expect(browserWindow.$dd_entry).toBeDefined()
63+
expect(browserWindow.$dd_return).toBeDefined()
64+
expect(browserWindow.$dd_throw).toBeDefined()
65+
expect(browserWindow.$dd_probes).toBeDefined()
66+
})
67+
68+
it('should warn when the explicit init version mismatches build-plugin metadata', async () => {
69+
browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' }
70+
replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({
71+
flushController: undefined as any,
72+
add: () => undefined,
73+
flush: () => undefined,
74+
stop: () => undefined,
75+
upsert: () => undefined,
76+
}))
77+
replaceMockableWithSpy(initDebuggerTransport)
78+
const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling)
79+
const warnSpy = spyOn(display, 'warn')
80+
81+
datadogDebugger.init({
82+
applicationId: 'app-id',
83+
clientToken: 'client-token',
84+
service: 'service-name',
85+
env: 'staging',
86+
version: 'runtime-version',
87+
})
88+
89+
await flushPromises()
90+
91+
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringMatching(/does not match the build-plugin version/))
92+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'runtime-version' }))
93+
})
1194
})
95+
96+
async function flushPromises() {
97+
for (let i = 0; i < 10; i++) {
98+
await Promise.resolve()
99+
}
100+
}

packages/debugger/src/entries/main.ts

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
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+
export interface DebuggerBuildMetadata {
17+
version?: string
18+
}
19+
1620
/**
1721
* Configuration options for initializing the Live Debugger SDK
1822
*/
@@ -94,29 +98,58 @@ export interface DebuggerPublicApi extends PublicApi {
9498
init: (initConfiguration: DebuggerInitConfiguration) => void
9599
}
96100

101+
export interface BrowserWindow extends Window {
102+
DD_DEBUGGER?: DebuggerPublicApi
103+
__DD_LIVE_DEBUGGER_BUILD__?: DebuggerBuildMetadata
104+
$dd_entry?: typeof onEntry
105+
$dd_return?: typeof onReturn
106+
$dd_throw?: typeof onThrow
107+
$dd_probes?: typeof getProbes
108+
}
109+
110+
function resolveDebuggerVersion(initConfiguration: DebuggerInitConfiguration): string | undefined {
111+
const buildVersion = getGlobalObject<BrowserWindow>().__DD_LIVE_DEBUGGER_BUILD__?.version
112+
113+
if (
114+
initConfiguration.version !== undefined &&
115+
buildVersion !== undefined &&
116+
initConfiguration.version !== buildVersion
117+
) {
118+
display.warn(
119+
`Debugger: init version "${initConfiguration.version}" does not match the build-plugin version "${buildVersion}". Using the init version.`
120+
)
121+
}
122+
123+
return initConfiguration.version ?? buildVersion
124+
}
125+
97126
/**
98127
* Create the public API for the Live Debugger
99128
*/
100129
function makeDebuggerPublicApi(): DebuggerPublicApi {
101130
return makePublicApi<DebuggerPublicApi>({
102131
init: (initConfiguration: DebuggerInitConfiguration) => {
132+
const resolvedConfiguration = {
133+
...initConfiguration,
134+
version: resolveDebuggerVersion(initConfiguration),
135+
}
136+
103137
// Initialize debugger's own transport
104-
const batch = startDebuggerBatch(initConfiguration)
105-
initDebuggerTransport(initConfiguration, batch)
138+
const batch = mockable(startDebuggerBatch)(resolvedConfiguration)
139+
mockable(initDebuggerTransport)(resolvedConfiguration, batch)
106140

107141
// Expose internal hooks on globalThis for instrumented code
108-
if (typeof globalThis !== 'undefined') {
109-
;(globalThis as any).$dd_entry = onEntry
110-
;(globalThis as any).$dd_return = onReturn
111-
;(globalThis as any).$dd_throw = onThrow
112-
;(globalThis as any).$dd_probes = getProbes
113-
}
142+
const debuggerGlobal = getGlobalObject<BrowserWindow>()
143+
debuggerGlobal.$dd_entry = onEntry
144+
debuggerGlobal.$dd_return = onReturn
145+
debuggerGlobal.$dd_throw = onThrow
146+
debuggerGlobal.$dd_probes = getProbes
114147

115-
startDeliveryApiPolling({
116-
applicationId: initConfiguration.applicationId,
117-
env: initConfiguration.env,
118-
version: initConfiguration.version,
119-
pollInterval: initConfiguration.pollInterval,
148+
mockable(startDeliveryApiPolling)({
149+
applicationId: resolvedConfiguration.applicationId,
150+
env: resolvedConfiguration.env,
151+
version: resolvedConfiguration.version,
152+
pollInterval: resolvedConfiguration.pollInterval,
120153
})
121154
},
122155
})
@@ -131,8 +164,4 @@ function makeDebuggerPublicApi(): DebuggerPublicApi {
131164
*/
132165
export const datadogDebugger = makeDebuggerPublicApi()
133166

134-
export interface BrowserWindow extends Window {
135-
DD_DEBUGGER?: DebuggerPublicApi
136-
}
137-
138167
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_DEBUGGER', datadogDebugger)

0 commit comments

Comments
 (0)