Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 2 additions & 18 deletions developer-extension/src/background/domain/syncRules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { NetRequestRulesOptions } from '../../common/extension.types'
import { DEV_RUM_SLIM_URL, DEV_RUM_URL, DEV_SERVER_ORIGIN } from '../../common/packagesUrlConstants'
import { INTAKE_DOMAINS } from '../../common/intakeDomainConstants'
import { createLogger } from '../../common/logger'
import { onDevtoolsDisconnection, onDevtoolsMessage } from '../devtoolsPanelConnection'
Expand Down Expand Up @@ -51,26 +50,11 @@ async function getExistingRulesInfos(tabId: number) {
return { tabRuleIds, nextRuleId }
}

function buildRules(
{ tabId, useDevBundles, useRumSlim, blockIntakeRequests }: NetRequestRulesOptions,
nextRuleId: number
) {
function buildRules({ tabId, useRumSlim, blockIntakeRequests }: NetRequestRulesOptions, nextRuleId: number) {
const rules: chrome.declarativeNetRequest.Rule[] = []
let id = nextRuleId

if (useDevBundles === 'cdn') {
const devRumUrl = useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL
logger.log('add redirect to dev bundles rules')
rules.push(
createRedirectRule(/^https:\/\/.*\/datadog-(rum|rum-slim|logs)(-[\w-]+)?\.js$/, {
regexSubstitution: `${DEV_SERVER_ORIGIN}/datadog-\\1.js`,
}),
createRedirectRule(/^https:\/\/.*\/chunks\/(\w+)(-\w+)?-datadog-rum.js$/, {
regexSubstitution: `${DEV_SERVER_ORIGIN}/chunks/\\1-datadog-rum.js`,
}),
createRedirectRule('https://localhost:8443/static/datadog-rum-hotdog.js', { url: devRumUrl })
)
} else if (useRumSlim) {
if (useRumSlim) {
logger.log('add redirect to rum slim rule')
rules.push(createRedirectRule(/^(https:\/\/.*\/datadog-rum)(-slim)?/, { regexSubstitution: '\\1-slim' }))
}
Expand Down
4 changes: 3 additions & 1 deletion developer-extension/src/common/extension.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export interface DevtoolsToBackgroundMessage {
options: NetRequestRulesOptions
}

export type DevBundlesOverride = false | 'cdn' | 'npm'
export type DevBundlesOverride = false | 'npm'

export type InjectCdnProd = 'off' | 'on'
export interface NetRequestRulesOptions {
tabId: number
useDevBundles: DevBundlesOverride
Expand Down Expand Up @@ -57,4 +58,5 @@ export interface Settings {
logsConfigurationOverride: object | null
debugMode: boolean
datadogMode: boolean
injectCdnProd: InjectCdnProd
}
5 changes: 5 additions & 0 deletions developer-extension/src/common/packagesUrlConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export const PROD_REPLAY_SANDBOX_URL = `${PROD_REPLAY_SANDBOX_ORIGIN}/${PROD_REP

export const DEV_REPLAY_SANDBOX_ORIGIN = 'https://localhost:8443'
export const DEV_REPLAY_SANDBOX_URL = `${DEV_REPLAY_SANDBOX_ORIGIN}/static-apps/replay-sandbox/public/index.html`

export const CDN_BASE_URL = 'https://www.datadoghq-browser-agent.com'
export const CDN_RUM_URL = `${CDN_BASE_URL}/us1/v6/datadog-rum.js`
export const CDN_RUM_SLIM_URL = `${CDN_BASE_URL}/us1/v6/datadog-rum-slim.js`
export const CDN_LOGS_URL = `${CDN_BASE_URL}/us1/v6/datadog-logs.js`
143 changes: 139 additions & 4 deletions developer-extension/src/content-scripts/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
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_LOGS_URL,
CDN_RUM_SLIM_URL,
CDN_RUM_URL,
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'

const logger = createLogger('main')

declare global {
interface Window extends EventTarget {
Expand Down Expand Up @@ -33,6 +43,8 @@ export function main() {
) {
const ddRumGlobal = instrumentGlobal('DD_RUM')
const ddLogsGlobal = instrumentGlobal('DD_LOGS')
const shouldInjectCdnBundles = settings.injectCdnProd === 'on'
const shouldUseRedirect = settings.useDevBundles === 'npm'

if (settings.debugMode) {
setDebug(ddRumGlobal)
Expand All @@ -47,7 +59,14 @@ export function main() {
overrideInitConfiguration(ddLogsGlobal, settings.logsConfigurationOverride)
}

if (settings.useDevBundles === 'npm') {
if (shouldInjectCdnBundles && shouldUseRedirect) {
void injectCdnBundles({ useRumSlim: settings.useRumSlim }).then(() => {
injectDevBundle(settings.useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL, ddRumGlobal)
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal)
})
} else if (shouldInjectCdnBundles) {
void injectCdnBundles({ useRumSlim: settings.useRumSlim })
} else if (shouldUseRedirect) {
injectDevBundle(settings.useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL, ddRumGlobal)
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal)
}
Expand Down Expand Up @@ -83,11 +102,34 @@ function noBrowserSdkLoaded() {
return !window.DD_RUM && !window.DD_LOGS
}

function injectDevBundle(url: string, global: GlobalInstrumentation) {
function injectDevBundle(url: string, global: GlobalInstrumentation, config?: object | null) {
const existingInstance = global.get()

let initConfig = config
if (
existingInstance &&
'getInitConfiguration' in existingInstance &&
typeof existingInstance.getInitConfiguration === 'function'
) {
try {
initConfig = existingInstance.getInitConfiguration() || config
} catch {
initConfig = config
}
}

loadSdkScriptFromURL(url)
const devInstance = global.get() as SdkPublicApi

if (devInstance) {
if (initConfig && 'init' in devInstance && typeof devInstance.init === 'function') {
try {
;(devInstance as { init(config: object): void }).init(initConfig)
} catch (error) {
logger.error('[DD Browser SDK extension] Error initializing dev bundle:', error)
}
}

global.onSet((sdkInstance) => proxySdk(sdkInstance, devInstance))
global.returnValue(devInstance)
}
Expand Down Expand Up @@ -140,11 +182,18 @@ function loadSdkScriptFromURL(url: string) {
//
// We'll probably have to revisit when using actual `import()` expressions instead of relying on
// Webpack runtime to load the chunks.
// Extract the base directory URL from the full file URL.
const baseUrl = url.substring(0, url.lastIndexOf('/') + 1)

// Replace the webpack error throw to set scriptUrl.
sdkCode = sdkCode.replace(
'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
`if (!scriptUrl) scriptUrl = ${JSON.stringify(url)};`
`if (!scriptUrl) scriptUrl = ${JSON.stringify(baseUrl)};`
)

// Override webpack publicPath assignments to ensure chunks load from dev server
sdkCode = sdkCode.replace(/(__webpack_require__\.p\s*=\s*)([^;]+);/g, `$1${JSON.stringify(baseUrl)};`)

const script = document.createElement('script')
script.type = 'text/javascript'
script.text = sdkCode
Expand Down Expand Up @@ -182,3 +231,89 @@ function instrumentGlobal(global: 'DD_RUM' | 'DD_LOGS') {
function proxySdk(target: SdkPublicApi, root: SdkPublicApi) {
Object.assign(target, root)
}

function injectCdnBundles({ useRumSlim }: { useRumSlim: boolean }) {
const rumUrl = useRumSlim ? CDN_RUM_SLIM_URL : CDN_RUM_URL
const logsUrl = CDN_LOGS_URL

return injectWhenDocumentReady(() =>
Promise.all([
injectAndInitializeSDK(rumUrl, 'DD_RUM', getDefaultRumConfig()),
injectAndInitializeSDK(logsUrl, 'DD_LOGS', getDefaultLogsConfig()),
]).then(() => undefined)
)
}

function injectWhenDocumentReady<T>(callback: () => Promise<T> | T) {
if (document.readyState === 'loading') {
return new Promise<T>((resolve) => {
document.addEventListener(
'DOMContentLoaded',
() => {
resolve(callback())
},
{ once: true }
)
})
}

return Promise.resolve(callback())
}

function injectAndInitializeSDK(url: string, globalName: 'DD_RUM' | 'DD_LOGS', config: object | null) {
if (window[globalName]) {
return Promise.resolve()
}

return new Promise<void>((resolve) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onload = () => {
const sdkGlobal = window[globalName]
if (config && sdkGlobal && 'init' in sdkGlobal) {
try {
;(sdkGlobal as { init(config: object): void }).init(config)
} catch (error) {
// Ignore "already initialized" errors - this can happen when dev bundles override CDN bundles
const errorMessage = error instanceof Error ? error.message : String(error)
if (!errorMessage.includes('already initialized')) {
// Only log non-initialization errors
// eslint-disable-next-line no-console
console.error(`[DD Browser SDK extension] Error initializing ${globalName}:`, error)
}
}
}
resolve()
}
script.onerror = () => {
resolve()
}

try {
document.head.appendChild(script)
} catch {
document.documentElement.appendChild(script)
}
})
}

function getDefaultRumConfig() {
return {
applicationId: 'xxx',
clientToken: 'xxx',
site: 'datadoghq.com',
service: 'browser-sdk-extension',
allowedTrackingOrigins: [location.origin],
sessionReplaySampleRate: 100,
}
}

function getDefaultLogsConfig() {
return {
clientToken: 'xxx',
site: 'datadoghq.com',
service: 'browser-sdk-extension',
allowedTrackingOrigins: [location.origin],
}
}
2 changes: 1 addition & 1 deletion developer-extension/src/panel/components/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function Panel() {
}

function isInterceptingNetworkRequests(settings: Settings) {
return settings.blockIntakeRequests || settings.useDevBundles || settings.useRumSlim
return settings.blockIntakeRequests || settings.useRumSlim
}

function isOverridingInitConfiguration(settings: Settings) {
Expand Down
43 changes: 34 additions & 9 deletions developer-extension/src/panel/components/tabs/settingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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, InjectCdnProd } from '../../../common/extension.types'

export function SettingsTab() {
const sdkDevServerStatus = useDevServerStatus(DEV_LOGS_URL)
Expand All @@ -21,6 +21,7 @@ export function SettingsTab() {
autoFlush,
debugMode: debug,
datadogMode,
injectCdnProd,
},
setSetting,
] = useSettings()
Expand All @@ -36,8 +37,11 @@ export function SettingsTab() {
<Group>
<Text>Browser SDK</Text>
<Box style={{ marginLeft: 'auto' }}>
{sdkDevServerStatus === DevServerStatus.AVAILABLE && useDevBundles ? (
<Badge color="blue">Overridden</Badge>
{(sdkDevServerStatus === DevServerStatus.AVAILABLE && useDevBundles) ||
(sdkDevServerStatus === DevServerStatus.AVAILABLE && useDevBundles && injectCdnProd === 'on') ? (
<Badge color="blue">Injected Local Dev</Badge>
) : injectCdnProd === 'on' ? (
<Badge color="blue">Injected CDN</Badge>
) : sdkDevServerStatus === DevServerStatus.AVAILABLE ? (
<Badge color="green">Available</Badge>
) : sdkDevServerStatus === DevServerStatus.CHECKING ? (
Expand All @@ -50,12 +54,31 @@ export function SettingsTab() {
</Accordion.Control>
<Accordion.Panel>
<Box>
Use the local development version of the browser SDK. The development server must be running; to
start it, run <Code>yarn dev</Code>.
Use the local or production version of the browser SDK. For local development, the development
server must be running; to start it, run <Code>yarn dev</Code>.
</Box>

<Space h="md" />

<SettingItem
input={
<Group>
<Text>CDN Prod Injection:</Text>
<SegmentedControl
color="violet"
value={injectCdnProd || 'off'}
size="xs"
data={[
{ value: 'off', label: 'Off' },
{ value: 'on', label: 'On' },
]}
onChange={(value) => setSetting('injectCdnProd', value as InjectCdnProd)}
/>
</Group>
}
description={<>Inject the CDN Prod RUM and Logs bundles into the page.</>}
/>

<SettingItem
input={
<Group>
Expand All @@ -66,7 +89,6 @@ export function SettingsTab() {
size="xs"
data={[
{ value: 'off', label: 'Off' },
{ value: 'cdn', label: 'Redirect' },
{ value: 'npm', label: 'Inject' },
]}
onChange={(value) =>
Expand All @@ -77,9 +99,12 @@ export function SettingsTab() {
}
description={
<>
Choose an override strategy. Network request redirection is reliable, but only works for CDN
setups. Injecting the bundle into the page can work for both CDN and NPM setups, but it's not
always reliable.
Behavior:
<ul>
<li>Off / Redirect: Injects dev bundle and initializes</li>
<li>On / Off: Injects and initializes Prod CDN SDK</li>
<li>On / Redirect: Injects Prod CDN SDK, then redirects to dev server</li>
</ul>
</>
}
/>
Expand Down
1 change: 1 addition & 0 deletions developer-extension/src/panel/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const DEFAULT_SETTINGS: Readonly<Settings> = {
logsConfigurationOverride: null,
debugMode: false,
datadogMode: false,
injectCdnProd: 'off',
}

let settings: Settings | undefined
Expand Down