Skip to content
5 changes: 5 additions & 0 deletions developer-extension/src/common/extension.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export type SdkMessage =

export type EventCollectionStrategy = 'sdk' | 'requests'

export type InjectionVariant = 'local-dev' | 'cdn'

export type SdkInjectionType = 'RUM' | 'LOGS' | 'BOTH'
export interface Settings {
useDevBundles: DevBundlesOverride
useDevReplaySandbox: boolean
Expand All @@ -57,4 +60,6 @@ export interface Settings {
logsConfigurationOverride: object | null
debugMode: boolean
datadogMode: boolean
injectionVariant: InjectionVariant
sdkInjectionType: SdkInjectionType
Comment on lines +63 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 warning: ‏The settings should be redesigned to make it more explicit and ideally avoid forbidden states. Things that should be clarified at the settings level:

  • useDevBundles: npm won't work if injectionVariant is not cdn.
  • How can I disable injection?
  • Why injection variant CDN only available in datadogMode?
  • What's the difference between injectionVariant and sdkInjectionType just by looking at property names?

My suggestion: keep things simple. Always inject both RUM and Logs CDN bundles with a default config. If dev bundle override is enabled, those bundles will be overridden (including rum-slim override). If config override is enabled, the config will be overridden. No need to do anything at the injection level.

So in the end you have a single option, inject: boolean. No need to enforce datadogMode usage, people can use it it's fine.

}
7 changes: 7 additions & 0 deletions developer-extension/src/common/packagesUrlConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ export const DEV_LOGS_URL = `${DEV_SERVER_ORIGIN}/datadog-logs.js`
export const DEV_RUM_SLIM_URL = `${DEV_SERVER_ORIGIN}/datadog-rum-slim.js`
export const DEV_RUM_URL = `${DEV_SERVER_ORIGIN}/datadog-rum.js`

export const CDN_BASE_URL = 'https://www.datadoghq-browser-agent.com'
// This version corresponds to the major version of the Browser SDK and needs to be manually updated when bumping major versions
export const CDN_VERSION = 'v6'
export const CDN_RUM_URL = `${CDN_BASE_URL}/${CDN_VERSION}/datadog-rum.js`
export const CDN_RUM_SLIM_URL = `${CDN_BASE_URL}/${CDN_VERSION}/datadog-rum-slim.js`
export const CDN_LOGS_URL = `${CDN_BASE_URL}/${CDN_VERSION}/datadog-logs.js`
Comment on lines +9 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like those constants aren't used


// To follow web-ui development, this version will need to be manually updated from time to time.
// When doing that, be sure to update types and implement any protocol changes.
export const PROD_REPLAY_SANDBOX_VERSION = '0.119.0'
Expand Down
119 changes: 114 additions & 5 deletions developer-extension/src/content-scripts/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Settings } from '../common/extension.types'
import { EventListeners } from '../common/eventListeners'
import { DEV_LOGS_URL, DEV_RUM_SLIM_URL, DEV_RUM_URL } from '../common/packagesUrlConstants'
import { CDN_BASE_URL, CDN_VERSION, DEV_LOGS_URL, DEV_RUM_SLIM_URL, DEV_RUM_URL } from '../common/packagesUrlConstants'
import { SESSION_STORAGE_SETTINGS_KEY } from '../common/sessionKeyConstant'
import { createLogger } from '../common/logger'

declare global {
interface Window extends EventTarget {
Expand All @@ -11,6 +12,8 @@ declare global {
}
}

const logger = createLogger('content-script-main')

interface SdkPublicApi {
[key: string]: (...args: any[]) => unknown
}
Expand All @@ -22,7 +25,6 @@ function main() {
}

sendEventsToExtension()

const settings = getSettings()

if (
Expand All @@ -47,9 +49,11 @@ function main() {
overrideInitConfiguration(ddLogsGlobal, settings.logsConfigurationOverride)
}

if (settings.useDevBundles === 'npm') {
if (settings.injectionVariant === 'local-dev' && settings.useDevBundles === 'npm') {
injectDevBundle(settings.useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL, ddRumGlobal)
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal)
} else if (settings.injectionVariant === 'cdn' && settings.datadogMode) {
injectCdnBundle(settings)
}
}
}
Expand Down Expand Up @@ -126,8 +130,7 @@ function loadSdkScriptFromURL(url: string) {
xhr.open('GET', url, false) // `false` makes the request synchronous
xhr.send()
} catch (error) {
// eslint-disable-next-line no-console
console.error(`[DD Browser SDK extension] Error while loading ${url}:`, error)
logger.error(`Error while loading ${url}:`, error)
return
}
if (xhr.status === 200) {
Expand Down Expand Up @@ -184,3 +187,109 @@ function instrumentGlobal(global: 'DD_RUM' | 'DD_LOGS') {
function proxySdk(target: SdkPublicApi, root: SdkPublicApi) {
Object.assign(target, root)
}

function injectCdnBundle(settings: Settings) {
const injectWhenReady = () => {
if (settings.sdkInjectionType === 'RUM' || settings.sdkInjectionType === 'BOTH') {
const rumSite = (settings.rumConfigurationOverride as any)?.site as string | undefined
const rumUrl = getRumBundleUrl(settings.useRumSlim ? 'rum-slim' : 'rum', rumSite)
const rumConfig =
settings.rumConfigurationOverride ||
(settings.datadogMode
? {
applicationId: 'xxx',
clientToken: 'xxx',
site: 'datad0g.com',
allowedTrackingOrigins: [location.origin],
sessionReplaySampleRate: 100,
}
: null)
injectAndInitializeSDK(rumUrl, 'DD_RUM', rumConfig as any)
}

if (settings.sdkInjectionType === 'LOGS' || settings.sdkInjectionType === 'BOTH') {
const logsSite = (settings.logsConfigurationOverride as any)?.site as string | undefined
const logsUrl = getLogsBundleUrl(logsSite)
const logsConfig =
settings.logsConfigurationOverride ||
(settings.datadogMode
? {
clientToken: 'xxx',
site: 'datad0g.com',
allowedTrackingOrigins: [location.origin],
}
: null)
injectAndInitializeSDK(logsUrl, 'DD_LOGS', logsConfig as any)
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', injectWhenReady, { once: true })
} else {
injectWhenReady()
}
}

function getRumBundleUrl(bundle: 'rum' | 'rum-slim', site?: string): string {
const region = getCdnRegion(site)
return `${CDN_BASE_URL}/${region}/${CDN_VERSION}/datadog-${bundle}.js`
}

function getLogsBundleUrl(site?: string) {
const region = getCdnRegion(site)
return `${CDN_BASE_URL}/${region}/${CDN_VERSION}/datadog-logs.js`
}
Comment on lines +233 to +241
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏Just include us1 prod rum and logs. No need to worry about regions, staging, etc.


function getCdnRegion(site?: string) {
if (!site || site === 'datadoghq.com') {
return 'us1'
}
if (site === 'datadoghq.eu') {
return 'eu1'
}
if (site?.startsWith('us3.')) {
return 'us3'
}
if (site?.startsWith('us5.')) {
return 'us5'
}
if (site?.endsWith('datad0g.com')) {
return 'us3'
}
Comment on lines +256 to +258
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 warning: ‏this seems incorrect


return 'us1'
}
Copy link
Contributor Author

@BeltranBulbarellaDD BeltranBulbarellaDD Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably remove this, the thing is that I was getting 403 so this was the fallback.
Gonna look for a better way.
(Open to suggestions)


function injectAndInitializeSDK(url: string, globalName: 'DD_RUM' | 'DD_LOGS', config: object | null) {
// If the SDK is already loaded, don't try to load it again
if (window[globalName]) {
logger.log(`${globalName} already exists, skipping injection`)
return
}

if (url.includes('datadoghq-browser-agent.com')) {
const script = document.createElement('script')
script.src = url
script.async = true
script.onload = () => {
if (config && window[globalName] && 'init' in window[globalName]) {
try {
window[globalName].init(config)
} catch (e) {
logger.error(`Error initializing ${globalName}:`, e)
}
} else {
logger.log(`${globalName} loaded. No init called (no config provided).`)
}
}
script.onerror = (e) => {
logger.error(`Error loading ${globalName} script:`, e)
}
try {
document.head.appendChild(script)
} catch (appendErr) {
logger.error('failed to append script to head, retrying on documentElement', appendErr)
document.documentElement.appendChild(script)
}
}
}
92 changes: 80 additions & 12 deletions developer-extension/src/panel/components/tabs/settingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { DevServerStatus, useDevServerStatus } from '../../hooks/useDevServerSta
import { useSettings } from '../../hooks/useSettings'
import { Columns } from '../columns'
import { TabBase } from '../tabBase'
import type { DevBundlesOverride, EventCollectionStrategy } from '../../../common/extension.types'
import type {
DevBundlesOverride,
EventCollectionStrategy,
InjectionVariant,
SdkInjectionType,
} from '../../../common/extension.types'

export function SettingsTab() {
const sdkDevServerStatus = useDevServerStatus(DEV_LOGS_URL)
Expand All @@ -21,10 +26,34 @@ export function SettingsTab() {
autoFlush,
debugMode: debug,
datadogMode,
injectionVariant,
sdkInjectionType,
},
setSetting,
] = useSettings()

const badgeStatus = () => {
const toBadge = (color: 'blue' | 'green' | 'yellow' | 'red', text: string) => <Badge color={color}>{text}</Badge>

const overridden = useDevBundles && (injectionVariant === 'cdn' || sdkDevServerStatus === DevServerStatus.AVAILABLE)
if (overridden) {
return toBadge('blue', 'Overridden')
}

if (injectionVariant === 'cdn') {
return toBadge('green', 'Available')
}

switch (sdkDevServerStatus) {
case DevServerStatus.AVAILABLE:
return toBadge('green', 'Available')
case DevServerStatus.CHECKING:
return toBadge('yellow', 'Checking...')
default:
return toBadge('red', 'Unavailable')
}
}

return (
<TabBase>
<div className="dd-privacy-allow">
Expand All @@ -35,17 +64,7 @@ export function SettingsTab() {
<Accordion.Control>
<Group>
<Text>Browser SDK</Text>
<Box style={{ marginLeft: 'auto' }}>
{sdkDevServerStatus === DevServerStatus.AVAILABLE && useDevBundles ? (
<Badge color="blue">Overridden</Badge>
) : sdkDevServerStatus === DevServerStatus.AVAILABLE ? (
<Badge color="green">Available</Badge>
) : sdkDevServerStatus === DevServerStatus.CHECKING ? (
<Badge color="yellow">Checking...</Badge>
) : (
<Badge color="red">Unavailable</Badge>
)}
</Box>
<Box style={{ marginLeft: 'auto' }}>{badgeStatus()}</Box>
</Group>
</Accordion.Control>
<Accordion.Panel>
Expand All @@ -56,6 +75,55 @@ export function SettingsTab() {

<Space h="md" />

{datadogMode && (
<>
<SettingItem
input={
<Group>
<Text>Injection variant:</Text>
<SegmentedControl
color="violet"
value={injectionVariant}
size="xs"
data={[
{ value: 'local-dev', label: 'Local Dev' },
{ value: 'cdn', label: 'CDN' },
]}
onChange={(value) => {
setSetting('injectionVariant', value as InjectionVariant)
}}
/>
</Group>
}
description={<></>}
/>

{injectionVariant === 'cdn' && (
<SettingItem
input={
<Group>
<Text>SDK injection type:</Text>
<SegmentedControl
color="violet"
value={sdkInjectionType}
size="xs"
data={[
{ value: 'RUM', label: 'RUM' },
{ value: 'LOGS', label: 'LOGS' },
{ value: 'BOTH', label: 'BOTH' },
]}
onChange={(value) => {
setSetting('sdkInjectionType', value as SdkInjectionType)
}}
/>
</Group>
}
description={<></>}
/>
)}
</>
)}

<SettingItem
input={
<Group>
Expand Down
14 changes: 13 additions & 1 deletion developer-extension/src/panel/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const DEFAULT_SETTINGS: Readonly<Settings> = {
logsConfigurationOverride: null,
debugMode: false,
datadogMode: false,
injectionVariant: 'local-dev',
sdkInjectionType: 'BOTH',
}

let settings: Settings | undefined
Expand Down Expand Up @@ -46,9 +48,19 @@ async function loadSettingsFromStorage() {

function setSetting<Name extends keyof Settings>(name: Name, value: Settings[Name]) {
settings![name] = value

const settingsToStore: Partial<Settings> = { [name]: value }

// Reset injectionVariant to default when Datadog mode is disabled
if (name === 'datadogMode' && value === false) {
settings!.injectionVariant = DEFAULT_SETTINGS.injectionVariant
settingsToStore.injectionVariant = DEFAULT_SETTINGS.injectionVariant
}

onSettingsChange.notify()

chrome.storage.local
.set({ [name]: value })
.set(settingsToStore)
.catch((error) => logger.error('Error while storing setting to the storage', error))
if (settings) {
syncSettingsWithSessionStorage(settings)
Expand Down