Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
13f348e
👷 build modules using a script
BenoitZugmeyer Oct 30, 2025
daf1130
👷 build bundles using a script
BenoitZugmeyer Oct 31, 2025
a299840
👷 adjust sandbox build
BenoitZugmeyer Oct 31, 2025
de520ca
👷 fix check-packages
BenoitZugmeyer Oct 31, 2025
2be9686
🔥 remove now unused npm-run-all dependency
BenoitZugmeyer Nov 4, 2025
3b108a1
👌 revamp build-package script
BenoitZugmeyer Nov 6, 2025
0092167
merge main
BenoitZugmeyer Nov 6, 2025
8722954
adjust build-package script to use the native globSync
BenoitZugmeyer Nov 6, 2025
02b3e38
👷 handle dev-extension to run in electron
thomas-lebeau Nov 11, 2025
540c639
Merge branch 'benoit/reduce-build-boilerplate' into thomas.lebeau/ele…
thomas-lebeau Nov 11, 2025
d81ce59
Merge branch 'benoit/reduce-build-boilerplate' into thomas.lebeau/ele…
thomas-lebeau Nov 11, 2025
bbcfe4e
🙈 [wip] Add Electron package with initial setup and integration for D…
thomas-lebeau Nov 11, 2025
a16f3a6
Extract sendRumEvent + Add ApplicationLaunch view
bcaudan Nov 12, 2025
ac4259c
add dummy exception tracking to link with view / session context
bcaudan Nov 13, 2025
e7fd56a
Track activity and update view time spent
bcaudan Nov 13, 2025
154ad4e
Use observable to send and assemble RUM events
bcaudan Nov 13, 2025
949f563
Generate RUM errors from error spans
bcaudan Nov 13, 2025
0ef6e03
♻️ rename bride.ts to renderer and move expoerted function in separat…
thomas-lebeau Nov 14, 2025
19dfc92
✨ add ipcMain and move tracer
thomas-lebeau Nov 14, 2025
86ff2f3
refactor: move setupMainBridge in it's own file
thomas-lebeau Nov 14, 2025
7134ff5
🚧 send trace
mormubis Nov 14, 2025
1678750
♻️ put back batching
mormubis Nov 14, 2025
a514452
refactor: better ipc span name
thomas-lebeau Nov 14, 2025
08d9503
attach RUM context to spans
bcaudan Nov 14, 2025
200a387
extract rum files
bcaudan Nov 14, 2025
8562721
extract trace directory
bcaudan Nov 14, 2025
dbaeeff
feat: add electron plugin for browser-rum
thomas-lebeau Nov 14, 2025
11e5c05
fix: conflicts
thomas-lebeau Nov 14, 2025
50a9e3d
fix: disable side-effect-free check for electron package
thomas-lebeau Nov 14, 2025
11ec945
fix(eslint): specify listener type in withDatadogCarrier function
thomas-lebeau Nov 14, 2025
4cc417e
fix(lint): no more deep imports
thomas-lebeau Nov 14, 2025
225dc46
Generate RUM resources from resources spans
bcaudan Nov 17, 2025
bdec71a
Increment error and resource counter
bcaudan Nov 17, 2025
9327732
fix: parse span and trace IDs as integers in ipcRenderer
thomas-lebeau Nov 17, 2025
c2605b8
chore: update TODOs in main.ts for IPC, telemetry, and testing improv…
thomas-lebeau Nov 17, 2025
f6dc907
chore: update TODOs in main.ts to include source and user agent in th…
thomas-lebeau Nov 18, 2025
3df655d
Merge branch 'main' into thomas.lebeau/electron
bcaudan Nov 18, 2025
fb5713a
fix licenses
bcaudan Nov 18, 2025
9b78148
fix peerDeps
bcaudan Nov 18, 2025
d401f7e
fix license
bcaudan Nov 18, 2025
098aeec
attach service/env/version
bcaudan Nov 19, 2025
b720a93
add required deps to package with dd-trace
bcaudan Nov 20, 2025
f32666f
attach same service,env,version to traces
bcaudan Nov 20, 2025
73d283e
allow to provide renderer variable with or without context isolation …
bcaudan Nov 20, 2025
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
5 changes: 5 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ prod,@tabler/icons-react,MIT,Copyright (c) 2020-2023 Paweł Kuna
prod,clsx,MIT,Copyright (c) Luke Edwards <[email protected]> (lukeed.com)
prod,react,MIT,Copyright (c) Facebook, Inc. and its affiliates.
prod,react-dom,MIT,Copyright (c) Facebook, Inc. and its affiliates.
prod,@msgpack/msgpack,Apache-2.0,Copyright (c) 2010 Peter Griess
prod,dd-trace,Apache-2.0,Copyright (c) 2018, Datadog Inc.
prod,graphql,MIT,Copyright (c) GraphQL Contributors
prod,@openfeature/core,Apache-2.0,Copyright OpenFeature Maintainers
dev,typedoc,Apache-2.0,TypeStrong
dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
dev,@jsdevtools/coverage-istanbul-loader,MIT,Copyright (c) 2015 James Messinger
Expand Down Expand Up @@ -84,3 +88,4 @@ dev,react-refresh-typescript,MIT,Copyright (c) Piotr Monwid-Olechnowicz
dev,webpack-dev-server,MIT,Copyright JS Foundation and other contributors
dev,http-server,MIT,Copyright http-party contributors
dev,react-router,MIT,Copyright (c) React Training LLC 2015-2019 Copyright (c) Remix Software Inc. 2020-2021 Copyright (c) Shopify Inc. 2022-2023
dev,electron,MIT,Copyright (c) Electron contributors Copyright (c) 2013-2020 GitHub Inc.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useEvents({
}, [eventCollectionStrategy])

useEffect(() => {
if (!preserveEvents) {
if (!preserveEvents && 'webNavigation' in chrome) {
const clearCurrentEvents = (details: chrome.webNavigation.WebNavigationTransitionCallbackDetails) => {
if (details.transitionType === 'reload' && details.tabId === chrome.devtools.inspectedWindow.tabId) {
clearEvents()
Expand Down
1 change: 1 addition & 0 deletions eslint-local-rules/disallowSideEffects.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const packagesWithoutSideEffect = new Set([
'@datadog/browser-rum-core',
'react',
'react-router-dom',
'electron',
])

/**
Expand Down
2 changes: 1 addition & 1 deletion eslint-local-rules/enforceProdDepsImports.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default {
return moduleVisitor((source) => {
const importTypeResult = importType(source.value, context)
// Use an allow list instead of a deny list to make the rule more future-proof.
if (importTypeResult === 'parent' || importTypeResult === 'sibling') {
if (importTypeResult === 'parent' || importTypeResult === 'sibling' || importTypeResult === 'builtin') {
return
}

Expand Down
12 changes: 12 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,18 @@ export default tseslint.config(
},
},

{
files: ['packages/electron/src/**/*.ts'],
ignores: [SPEC_FILES],
rules: {
// TODO: verify this is safe
'local-rules/disallow-side-effects': 'off',

// TODO: remove before merging this to main: quick dev only
'no-console': 'off',
},
},

{
// Files executed by nodejs
files: [
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"scripts": {
"postinstall": "scripts/cli init_submodule",
"build": "yarn workspaces foreach --all --parallel --topological run build",
"build:electron": "lerna run build:modules --scope=@datadog/electron",
"build:bundle": "yarn workspaces foreach --all --parallel run build:bundle",
"build:apps": "node scripts/build/build-test-apps.ts",
"build:docs:json": "typedoc --logLevel Verbose --json ./docs.json",
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"types": "cjs/index.d.ts",
"sideEffects": false,
"scripts": {
"pack": "yarn pack",
"build": "node ../../scripts/build/build-package.ts --modules"
},
"repository": {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/browser/cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
generateUUID,
} from '../tools/utils/stringUtils'
import { buildUrl } from '../tools/utils/urlPolyfill'
import { getGlobalObject } from '../tools/globalObject'

export interface CookieOptions {
secure?: boolean
Expand Down Expand Up @@ -65,7 +66,7 @@ export function deleteCookie(name: string, options?: CookieOptions) {
}

export function areCookiesAuthorized(options: CookieOptions): boolean {
if (document.cookie === undefined || document.cookie === null) {
if (getGlobalObject<Window>().document?.cookie === undefined || getGlobalObject<Window>().document?.cookie === null) {
return false
}
try {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/domain/resourceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const ResourceType = {
FONT: 'font',
MEDIA: 'media',
OTHER: 'other',
NATIVE: 'native',
} as const

export type ResourceType = (typeof ResourceType)[keyof typeof ResourceType]
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { Configuration, InitConfiguration, EndpointBuilder, ProxyFn } from './domain/configuration'
export type { Configuration, InitConfiguration, EndpointBuilder, ProxyFn, TrackType } from './domain/configuration'
export {
validateAndBuildConfiguration,
DefaultPrivacyLevel,
Expand All @@ -7,6 +7,7 @@ export {
isSampleRate,
buildEndpointHost,
isIntakeUrl,
createEndpointBuilder,
} from './domain/configuration'
export * from './domain/intakeSites'
export type { TrackingConsentState } from './domain/trackingConsent'
Expand Down Expand Up @@ -59,7 +60,17 @@ export {
SESSION_NOT_TRACKED,
SessionPersistence,
} from './domain/session/sessionConstants'
export type { BandwidthStats, HttpRequest, HttpRequestEvent, Payload, FlushEvent, FlushReason } from './transport'
export type {
BandwidthStats,
HttpRequest,
HttpRequestEvent,
Payload,
FlushEvent,
FlushReason,
DatadogEventBridge,
BrowserWindowWithEventBridge,
Batch,
} from './transport'
export {
createHttpRequest,
canUseEventBridge,
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/tools/utils/byteUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { globalObject } from '../globalObject'

export const ONE_KIBI_BYTE = 1024
export const ONE_MEBI_BYTE = 1024 * ONE_KIBI_BYTE

Expand All @@ -16,8 +18,8 @@ export function computeBytesCount(candidate: string): number {
return candidate.length
}

if (window.TextEncoder !== undefined) {
return new TextEncoder().encode(candidate).length
if ('TextEncoder' in globalObject) {
return new (globalObject as { TextEncoder: typeof TextEncoder }).TextEncoder().encode(candidate).length
}

return new Blob([candidate]).size
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/transport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type { BandwidthStats, HttpRequest, HttpRequestEvent, Payload, RetryInfo
export { createHttpRequest } from './httpRequest'
export type { BrowserWindowWithEventBridge, DatadogEventBridge } from './eventBridge'
export { canUseEventBridge, bridgeSupports, getEventBridge, BridgeCapability } from './eventBridge'
export type { Batch } from './batch'
export { createBatch } from './batch'
export type { FlushController, FlushEvent, FlushReason } from './flushController'
export { createFlushController, FLUSH_DURATION_LIMIT } from './flushController'
9 changes: 9 additions & 0 deletions packages/electron/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*
!/bundle/**/*.js
!/cjs/**/*
!/esm/**/*
!/src/**/*
/src/**/*.spec.ts
/src/**/*.specHelper.ts
!/internal/*
!/internal-synthetics/*
2 changes: 2 additions & 0 deletions packages/electron/.yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
save-exact true

47 changes: 47 additions & 0 deletions packages/electron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@datadog/electron",
"version": "6.23.0",
"license": "Apache-2.0",
"main": "cjs/entries/main.js",
"module": "esm/entries/main.js",
"types": "cjs/entries/main.d.ts",
"scripts": {
"pack": "yarn pack",
"build": "node ../../scripts/build/build-package.ts --modules --bundle datadog-electron.js",
"build:modules": "node ../../scripts/build/build-package.ts --modules",
"build:bundle": "node ../../scripts/build/build-package.ts --bundle datadog-electron.js"
},
"dependencies": {
"@datadog/browser-core": "6.24.0",
"@datadog/browser-rum-core": "6.24.0",
"@msgpack/msgpack": "3.1.2",
"@openfeature/core": "1.9.1",
"dd-trace": "^5.76.0",
"graphql": "16.12.0"
},
"peerDependencies": {
"@datadog/browser-logs": "6.24.0",
"electron": "39"
},
"peerDependenciesMeta": {
"@datadog/browser-logs": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "https://github.com/DataDog/browser-sdk.git",
"directory": "packages/electron"
},
"devDependencies": {
"@types/pako": "2.0.4",
"electron": "39",
"pako": "2.1.0"
},
"volta": {
"extends": "../../package.json"
},
"publishConfig": {
"access": "public"
}
}
6 changes: 6 additions & 0 deletions packages/electron/plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"main": "../cjs/entries/plugin.js",
"module": "../esm/entries/plugin.js",
"types": "../cjs/entries/plugin.d.ts"
}
6 changes: 6 additions & 0 deletions packages/electron/renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"main": "../cjs/entries/renderer.js",
"module": "../esm/entries/renderer.js",
"types": "../cjs/entries/renderer.d.ts"
}
24 changes: 24 additions & 0 deletions packages/electron/src/domain/main/bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Observable } from '@datadog/browser-core'
import type { RumEvent } from '@datadog/browser-rum-core'
import { ipcMain } from 'electron'
import type { CollectedRumEvent } from '../rum/events'

interface BridgeEvent {
eventType: 'rum'
event: RumEvent & { session: { id: string } } & { application: { id: string } }
}

export function setupMainBridge(rumEventObservable: Observable<CollectedRumEvent>) {
ipcMain.handle('datadog:send', (_event, msg: string) => {
const serverRumEvent = JSON.parse(msg) as BridgeEvent

if (serverRumEvent.eventType !== 'rum') {
// TODO: handle other types of events (telemetry, session replays, Logs, ....)
console.log('not a rum event', serverRumEvent)

return
}

rumEventObservable.notify({ event: serverRumEvent.event, source: 'renderer' })
})
}
47 changes: 47 additions & 0 deletions packages/electron/src/domain/main/ipcMain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { IpcMain } from 'electron'
import { ipcMain } from 'electron'
import tracer from '../trace/tracer'
import type { DatadogCarrier } from '../trace/trace'

const SPAN_NAME_PREFIX = 'ipcMain'

const isDatadogCarrier = (arg: any): arg is DatadogCarrier => typeof arg === 'object' && arg?.__dd_carrier === true

export function createIpcMain(): IpcMain {
const ddIpcMain = { ...ipcMain }

ddIpcMain.on = withDatadogCarrier('on', ipcMain.on.bind(ipcMain))
ddIpcMain.off = withDatadogCarrier('off', ipcMain.off.bind(ipcMain))
ddIpcMain.once = withDatadogCarrier('once', ipcMain.once.bind(ipcMain))
ddIpcMain.addListener = withDatadogCarrier('addListener', ipcMain.addListener.bind(ipcMain))
ddIpcMain.removeListener = withDatadogCarrier('removeListener', ipcMain.removeListener.bind(ipcMain))
ddIpcMain.handle = withDatadogCarrier('handle', ipcMain.handle.bind(ipcMain))
ddIpcMain.handleOnce = withDatadogCarrier('handleOnce', ipcMain.handleOnce.bind(ipcMain))

ddIpcMain.removeAllListeners = ipcMain.removeAllListeners.bind(ipcMain)
ddIpcMain.removeHandler = ipcMain.removeHandler.bind(ipcMain)

return ddIpcMain
}

function withDatadogCarrier<T extends (...args: any[]) => R, R>(name: string, fn: T): (...args: Parameters<T>) => R {
return (...args: Parameters<T>) => {
const channel = args[0]
const listener = args[1] as (...args: any[]) => R
const spanName = `${SPAN_NAME_PREFIX}.${name}.${channel}`

return fn(channel, (...args: Parameters<typeof listener>) => {
const lastArg = args[args.length - 1]
if (isDatadogCarrier(lastArg)) {
const parentContext = tracer.extract('text_map', lastArg)
args.pop() // remove the carrier from the args

if (parentContext) {
return tracer.trace(spanName, { childOf: parentContext }, () => listener(...args))
}
}

return tracer.trace(spanName, () => listener(...args))
})
}
}
Loading