Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
69bc2b4
👷 build package modules with tsdown
thomas-lebeau Jun 11, 2026
e52e4e1
♻️ remove @oxc-project/runtime dependency
thomas-lebeau Jun 12, 2026
482b404
👷 fix browser-rum build race condition with browser-worker
thomas-lebeau Jun 12, 2026
e381820
👷 derive tsdown entries per package layout
thomas-lebeau Jun 12, 2026
cb55841
👷 drop topological ordering from build:bundle
thomas-lebeau Jun 12, 2026
4e17e5a
👷 standardize tsdown entries on src/entries convention
thomas-lebeau Jun 12, 2026
78bb3e5
📝 document src/entries and .mjs ESM convention for js-core
thomas-lebeau Jun 12, 2026
edc5440
👷 update yarn.lock for browser-core 7.3.0 and js-core 0.0.2
thomas-lebeau Jun 15, 2026
f510dab
👷 bump browser-worker devDependency to 7.3.0 in browser-rum
thomas-lebeau Jun 15, 2026
08f9012
📝 Add AGENTS.md for browser-core package
thomas-lebeau Jun 9, 2026
960803a
✨ Add monitor and util sub-paths to @datadog/js-core
thomas-lebeau Jun 9, 2026
c19bd7e
✅ Add unit tests for @datadog/js-core monitor and display
thomas-lebeau Jun 9, 2026
2964227
♻️ Migrate browser-core monitoring/display to @datadog/js-core
thomas-lebeau Jun 10, 2026
f8ba9e3
🐛 align js-core ESM exports with tsdown .mjs output
thomas-lebeau Jun 12, 2026
433ad3a
👷 pin rolldown to 1.1.1 to fix non-deterministic ESM import drop
thomas-lebeau Jun 12, 2026
b90799b
✅ trim browser-core monitor spec to only cover browser-specific wiring
thomas-lebeau Jun 15, 2026
7980483
👷 update yarn.lock workspace references to 7.3.0 and js-core 0.0.2
thomas-lebeau Jun 15, 2026
3b589d5
Merge remote-tracking branch 'origin/main' into thomas.lebeau/add-bro…
thomas-lebeau Jun 16, 2026
46a12f6
refactor(monitor): stop re-exporting setDebugMode from browser-core
thomas-lebeau Jun 16, 2026
2ebe098
style(browser-core): fix import order in init.ts
thomas-lebeau Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint-local-rules/disallowSideEffects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const pathsWithSideEffect = new Set([
// Those packages are known to have no side effects when evaluated
const packagesWithoutSideEffect = new Set([
'@datadog/js-core/time',
'@datadog/js-core/util',
'@datadog/js-core/monitor',
'@datadog/browser-core',
'@datadog/browser-rum-core',
'@datadog/browser-rum-react/internal',
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
"webpack-dev-middleware": "8.0.3",
"ws": "8.21.0"
},
"resolutions": {
"rolldown": "1.1.1"
},
"volta": {
"node": "26.3.0",
"yarn": "4.16.0"
Expand Down
21 changes: 21 additions & 0 deletions packages/browser-core/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# `@datadog/browser-core` package

`@datadog/browser-core` is primarily consumed by other `@datadog/browser-*` packages within this
monorepo. It is published to npm but **not supported for external use** and carries **no semver
stability guarantee**.

## Breaking changes are allowed

External consumers exist but are unsupported — we do not guarantee compatibility for them. You may
freely:

- Remove or rename exported symbols
- Change function signatures (parameters, return types, arity)
- Reorganize modules and re-exports

When migrating code from `@datadog/browser-core` to `@datadog/js-core`, do **not** keep a
backward-compatible re-export in `browser-core` just to avoid a "breaking change" — there is no
breaking change here. Move the code and update all internal call sites directly.

Review comments that warn about "breaking changes" in this package (e.g. removed exports,
signature changes) are **not applicable** and should be disregarded.
2 changes: 1 addition & 1 deletion packages/browser-core/src/boot/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { setDebugMode } from '@datadog/js-core/util'
import { catchUserErrors } from '../tools/catchUserErrors'
import { setDebugMode } from '../tools/monitor'
import { display } from '../tools/display'

// replaced at build time
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-core/src/domain/bufferedData.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { clocksNow } from '@datadog/js-core/time'
import { ConsoleApiName } from '@datadog/js-core/util'
import type { MockFetch } from '../../test'
import { collectAsyncCalls, mockFetch, mockXhr, registerCleanupTask, replaceMockable, withXhr } from '../../test'
import { Observable } from '../tools/observable'
import { resetFetchObservable } from '../browser/fetchObservable'
import { resetXhrObservable } from '../browser/xhrObservable'
import { ConsoleApiName } from '../tools/display'
import { noop } from '../tools/utils/functionUtils'
import { resetConsoleObservable } from './console/consoleObservable'
import type { BufferedData } from './bufferedData'
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-core/src/domain/bufferedData.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ConsoleApiName } from '@datadog/js-core/util'
import type { Observable, Subscription } from '../tools/observable'
import { BufferedObservable } from '../tools/observable'
import { mockable } from '../tools/mockable'
import type { FetchContext } from '../browser/fetchObservable'
import { initFetchObservable } from '../browser/fetchObservable'
import type { XhrContext } from '../browser/xhrObservable'
import { initXhrObservable } from '../browser/xhrObservable'
import { ConsoleApiName } from '../tools/display'
import { addTelemetryDebug } from './telemetry'
import type { RawError } from './error/error.types'
import { trackRuntimeError } from './error/trackRuntimeError'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { ConsoleApiName } from '@datadog/js-core/util'
import { ignoreConsoleLogs } from '../../../test'
import { ConsoleApiName } from '../../tools/display'
import type { Subscription } from '../../tools/observable'
import type { ErrorConsoleLog } from './consoleObservable'
import { initConsoleObservable } from './consoleObservable'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { clocksNow } from '@datadog/js-core/time'
import { ConsoleApiName, globalConsole } from '@datadog/js-core/util'
import { isError, computeRawError } from '../error/error'
import { Observable, mergeObservables } from '../../tools/observable'
import { ConsoleApiName, globalConsole } from '../../tools/display'
import { callMonitored } from '../../tools/monitor'
import { sanitize } from '../../tools/serialisation/sanitize'
import { jsonStringify } from '../../tools/serialisation/jsonStringify'
Expand Down
9 changes: 6 additions & 3 deletions packages/browser-core/src/domain/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { clocksNow } from '@datadog/js-core/time'
import { getDebugMode } from '@datadog/js-core/util'
import type { Context } from '../../tools/serialisation/context'
import { ConsoleApiName } from '../../tools/display'
import { NO_ERROR_STACK_PRESENT_MESSAGE, isError } from '../error/error'
import { toStackTraceString } from '../../tools/stackTrace/handlingStack'
import { getExperimentalFeatures } from '../../tools/experimentalFeatures'
Expand All @@ -9,7 +9,8 @@ import type { Configuration } from '../configuration'
import { buildTags } from '../tags'
import { INTAKE_SITE_STAGING, INTAKE_SITE_US1_FED, INTAKE_SITE_US2_FED } from '../intakeSites'
import { BufferedObservable, Observable } from '../../tools/observable'
import { displayIfDebugEnabled, startMonitorErrorCollection } from '../../tools/monitor'
import { startMonitorErrorCollection } from '../../tools/monitor'
import { display } from '../../tools/display'
import { sendToExtension } from '../../tools/sendToExtension'
import { performDraw } from '../../tools/utils/numberUtils'
import { jsonStringify } from '../../tools/serialisation/jsonStringify'
Expand Down Expand Up @@ -261,7 +262,9 @@ function isTelemetryReplicationAllowed(configuration: Configuration) {
}

export function addTelemetryDebug(message: string, context?: Context) {
displayIfDebugEnabled(ConsoleApiName.debug, message, context)
if (getDebugMode()) {
display.debug('[Telemetry]', message, context)
}
getTelemetryObservable().notify({
rawEvent: {
type: TelemetryType.LOG,
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export {
addTelemetryUsage,
addTelemetryMetrics,
} from './domain/telemetry'
export { monitored, monitor, callMonitored, setDebugMode, monitorError } from './tools/monitor'
export { monitored, monitor, callMonitored, monitorError } from './tools/monitor'
export type { Subscription } from './tools/observable'
export { Observable, BufferedObservable } from './tools/observable'
export type { SessionManager, SessionContext } from './domain/session/sessionManager'
Expand Down
56 changes: 2 additions & 54 deletions packages/browser-core/src/tools/display.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,7 @@
/* eslint-disable local-rules/disallow-side-effects */
/**
* Keep references on console methods to avoid triggering patched behaviors
*
* NB: in some setup, console could already be patched by another SDK.
* In this case, some display messages can be sent by the other SDK
* but we should be safe from infinite loop nonetheless.
*/
import { createDisplay } from '@datadog/js-core/util'

export const ConsoleApiName = {
log: 'log',
debug: 'debug',
info: 'info',
warn: 'warn',
error: 'error',
} as const

export type ConsoleApiName = (typeof ConsoleApiName)[keyof typeof ConsoleApiName]

export interface Display {
debug: typeof console.debug
log: typeof console.log
info: typeof console.info
warn: typeof console.warn
error: typeof console.error
}

/**
* When building JS bundles, some users might use a plugin[1] or configuration[2] to remove
* "console.*" references. This causes some issue as we expect `console.*` to be defined.
* As a workaround, let's use a variable alias, so those expressions won't be taken into account by
* simple static analysis.
*
* [1]: https://babeljs.io/docs/babel-plugin-transform-remove-console/
* [2]: https://github.com/terser/terser#compress-options (look for drop_console)
*/
export const globalConsole = console

export const originalConsoleMethods = {} as Display
Object.keys(ConsoleApiName).forEach((name) => {
originalConsoleMethods[name as ConsoleApiName] = globalConsole[name as ConsoleApiName]
})

const PREFIX = 'Datadog Browser SDK:'

export function createDisplay(prefix: string): Display {
return {
debug: originalConsoleMethods.debug.bind(globalConsole, prefix),
log: originalConsoleMethods.log.bind(globalConsole, prefix),
info: originalConsoleMethods.info.bind(globalConsole, prefix),
warn: originalConsoleMethods.warn.bind(globalConsole, prefix),
error: originalConsoleMethods.error.bind(globalConsole, prefix),
}
}

export const display: Display = createDisplay(PREFIX)
export const display = createDisplay('Datadog Browser SDK:')

export const DOCS_ORIGIN = 'https://docs.datadoghq.com'
export const DOCS_TROUBLESHOOTING = `${DOCS_ORIGIN}/real_user_monitoring/browser/troubleshooting`
Expand Down
160 changes: 10 additions & 150 deletions packages/browser-core/src/tools/monitor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { display } from './display'
import { callMonitored, monitor, monitored, startMonitorErrorCollection, setDebugMode } from './monitor'
import { callMonitored, monitored, startMonitorErrorCollection } from './monitor'

describe('monitor', () => {
let onMonitorErrorCollectedSpy: jasmine.Spy<(error: unknown) => void>
Expand All @@ -8,162 +7,23 @@ describe('monitor', () => {
onMonitorErrorCollectedSpy = jasmine.createSpy()
})

describe('decorator', () => {
it('catches monitored errors but does not report them before startMonitorErrorCollection', () => {
class Candidate {
@monitored
monitoredThrowing() {
throwing() {
throw new Error('monitored')
}

@monitored
monitoredStringErrorThrowing() {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw 'string error'
}

@monitored
monitoredObjectErrorThrowing() {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw { foo: 'bar' }
}

@monitored
monitoredNotThrowing() {
return 1
}

notMonitoredThrowing() {
throw new Error('not monitored')
}
}

let candidate: Candidate
beforeEach(() => {
candidate = new Candidate()
})

describe('before initialization', () => {
it('should not monitor', () => {
expect(() => candidate.notMonitoredThrowing()).toThrowError('not monitored')
expect(() => candidate.monitoredThrowing()).toThrowError('monitored')
expect(candidate.monitoredNotThrowing()).toEqual(1)
})
})

describe('after initialization', () => {
beforeEach(() => {
startMonitorErrorCollection(onMonitorErrorCollectedSpy)
})

it('should preserve original behavior', () => {
expect(candidate.monitoredNotThrowing()).toEqual(1)
})

it('should catch error', () => {
expect(() => candidate.notMonitoredThrowing()).toThrowError()
expect(() => candidate.monitoredThrowing()).not.toThrowError()
})

it('should report error', () => {
candidate.monitoredThrowing()

expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith(new Error('monitored'))
})

it('should report string error', () => {
candidate.monitoredStringErrorThrowing()

expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith('string error')
})

it('should report object error', () => {
candidate.monitoredObjectErrorThrowing()

expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith({ foo: 'bar' })
})
})
const candidate = new Candidate()
expect(() => candidate.throwing()).not.toThrowError()
expect(onMonitorErrorCollectedSpy).not.toHaveBeenCalled()
})

describe('function', () => {
const notThrowing = () => 1
const throwing = () => {
it('reports errors to the collection callback after startMonitorErrorCollection', () => {
startMonitorErrorCollection(onMonitorErrorCollectedSpy)
callMonitored(() => {
throw new Error('error')
}

beforeEach(() => {
startMonitorErrorCollection(onMonitorErrorCollectedSpy)
})

describe('direct call', () => {
it('should preserve original behavior', () => {
expect(callMonitored(notThrowing)).toEqual(1)
})

it('should catch error', () => {
expect(() => callMonitored(throwing)).not.toThrowError()
})

it('should report error', () => {
callMonitored(throwing)

expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith(new Error('error'))
})
})

describe('wrapper', () => {
it('should preserve original behavior', () => {
const decorated = monitor(notThrowing)
expect(decorated()).toEqual(1)
})

it('should catch error', () => {
const decorated = monitor(throwing)
expect(() => decorated()).not.toThrowError()
})

it('should report error', () => {
monitor(throwing)()

expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith(new Error('error'))
})
})
})

describe('setDebugMode', () => {
let displaySpy: jasmine.Spy

beforeEach(() => {
displaySpy = spyOn(display, 'error')
})

it('when not called, should not display error', () => {
callMonitored(() => {
throw new Error('message')
})

expect(displaySpy).not.toHaveBeenCalled()
})

it('when called, should display error', () => {
setDebugMode(true)

callMonitored(() => {
throw new Error('message')
})

expect(displaySpy).toHaveBeenCalledOnceWith('[MONITOR]', new Error('message'))
})

it('displays errors thrown by the onMonitorErrorCollected callback', () => {
setDebugMode(true)
onMonitorErrorCollectedSpy.and.throwError(new Error('unexpected'))
startMonitorErrorCollection(onMonitorErrorCollectedSpy)

callMonitored(() => {
throw new Error('message')
})
expect(displaySpy).toHaveBeenCalledWith('[MONITOR]', new Error('message'))
expect(displaySpy).toHaveBeenCalledWith('[MONITOR]', new Error('unexpected'))
})
expect(onMonitorErrorCollectedSpy).toHaveBeenCalledOnceWith(new Error('error'))
})
})
Loading
Loading