Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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`
158 changes: 152 additions & 6 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,9 +59,16 @@ export function main() {
overrideInitConfiguration(ddLogsGlobal, settings.logsConfigurationOverride)
}

if (settings.useDevBundles === 'npm') {
injectDevBundle(settings.useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL, ddRumGlobal)
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal)
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, getDefaultRumConfig())
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal, getDefaultLogsConfig())
}
}
}
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,29 @@ 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)

// Override webpack's scriptUrl detection in multiple places to ensure chunks load from dev server
// 1. Replace the error throw with our base URL
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)};`
)

// 2. Set scriptUrl early if it's determined from document.currentScript or similar
sdkCode = sdkCode.replace(/var scriptUrl\s*=\s*[^;]+;/g, `var scriptUrl = ${JSON.stringify(baseUrl)};`)

// 3. Override __webpack_require__.p (publicPath) if it exists
sdkCode = sdkCode.replace(
/__webpack_require__\.p\s*=\s*[^;]+;/g,
`__webpack_require__.p = ${JSON.stringify(baseUrl)};`
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because the SDK tried to load the recorder from the wrong place. So when we inject and then override need to adjust the recorder URL.
Open to suggestions!


// 4. Inject publicPath override at the start of the webpack runtime
const publicPathOverride = `(function(){try{if(typeof __webpack_require__!=='undefined'){__webpack_require__.p=${JSON.stringify(baseUrl)};}}catch(e){}})();`
sdkCode = publicPathOverride + sdkCode

const script = document.createElement('script')
script.type = 'text/javascript'
script.text = sdkCode
Expand Down Expand Up @@ -182,3 +242,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