Skip to content

Commit

Permalink
✨[Developer extension] npm setup override support (#2304)
Browse files Browse the repository at this point in the history
* Inject dev bundle

* Override init configuration
  • Loading branch information
amortemousque authored Jan 17, 2024
1 parent abc2169 commit 4308cbb
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 54 deletions.
2 changes: 1 addition & 1 deletion developer-extension/src/background/domain/syncRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function buildRules(
const rules: chrome.declarativeNetRequest.Rule[] = []
let id = nextRuleId

if (useDevBundles) {
if (useDevBundles === 'cdn') {
const devRumUrl = useRumSlim ? DEV_RUM_SLIM_URL : DEV_RUM_URL
logger.log('add redirect to dev bundles rules')
rules.push(
Expand Down
2 changes: 2 additions & 0 deletions developer-extension/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export const enum PanelTabs {
}

export const DEFAULT_PANEL_TAB = PanelTabs.Events

export const SESSION_STORAGE_SETTINGS_KEY = '__ddBrowserSdkExtensionSettings'
17 changes: 16 additions & 1 deletion developer-extension/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export type DevtoolsToBackgroundMessage = {
options: NetRequestRulesOptions
}

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

export interface NetRequestRulesOptions {
tabId: number
useDevBundles: boolean
useDevBundles: DevBundlesOverride
useRumSlim: boolean
blockIntakeRequests: boolean
}
Expand All @@ -40,3 +42,16 @@ export type SdkMessage =
segment: BrowserSegmentMetadata
}
}

export type EventCollectionStrategy = 'sdk' | 'requests'

export interface Settings {
useDevBundles: DevBundlesOverride
useRumSlim: boolean
blockIntakeRequests: boolean
autoFlush: boolean
preserveEvents: boolean
eventCollectionStrategy: EventCollectionStrategy
rumConfigurationOverride: object | null
logsConfigurationOverride: object | null
}
158 changes: 148 additions & 10 deletions developer-extension/src/content-scripts/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,149 @@
// This script is executed in the "main" execution world, the same world as the webpage. Thus, it
// can define a global callback variable to listen to SDK events.

;(window as any).__ddBrowserSdkExtensionCallback = (message: unknown) => {
// Relays any message to the "isolated" content-script via a custom event.
window.dispatchEvent(
new CustomEvent('__ddBrowserSdkMessage', {
detail: message,
})
)
import type { Settings } from '../common/types'
import { EventListeners } from '../common/eventListeners'
import { DEV_LOGS_URL, DEV_RUM_URL, SESSION_STORAGE_SETTINGS_KEY } from '../common/constants'

declare global {
interface Window extends EventTarget {
DD_RUM?: SdkPublicApi
DD_LOGS?: SdkPublicApi
__ddBrowserSdkExtensionCallback?: (message: unknown) => void
}
}

type SdkPublicApi = { [key: string]: (...args: any[]) => unknown }

function main() {
// Prevent multiple executions when the devetools are reconnecting
if (window.__ddBrowserSdkExtensionCallback) {
return
}

sendEventsToExtension()

const settings = getSettings()

if (
settings &&
// Avoid instrumenting SDK global variables if the SDKs are already loaded.
// This happens when the page is loaded and then the devtools are opened.
noBrowserSdkLoaded()
) {
const ddRumGlobal = instrumentGlobal('DD_RUM')
const ddLogsGlobal = instrumentGlobal('DD_LOGS')

if (settings.rumConfigurationOverride) {
overrideInitConfiguration(ddRumGlobal, settings.rumConfigurationOverride)
}

if (settings.logsConfigurationOverride) {
overrideInitConfiguration(ddLogsGlobal, settings.logsConfigurationOverride)
}

if (settings.useDevBundles === 'npm') {
injectDevBundle(DEV_RUM_URL, ddRumGlobal)
injectDevBundle(DEV_LOGS_URL, ddLogsGlobal)
}
}
}

main()

function sendEventsToExtension() {
// This script is executed in the "main" execution world, the same world as the webpage. Thus, it
// can define a global callback variable to listen to SDK events.
window.__ddBrowserSdkExtensionCallback = (message: unknown) => {
// Relays any message to the "isolated" content-script via a custom event.
window.dispatchEvent(
new CustomEvent('__ddBrowserSdkMessage', {
detail: message,
})
)
}
}

function getSettings() {
try {
// sessionStorage access throws in sandboxed iframes
const stringSettings = sessionStorage.getItem(SESSION_STORAGE_SETTINGS_KEY)
// JSON.parse throws if the stringSettings is not a valid JSON
return JSON.parse(stringSettings || 'null') as Settings | null
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error getting settings', error)
}
}

function noBrowserSdkLoaded() {
return !window.DD_RUM && !window.DD_LOGS
}

function injectDevBundle(url: string, global: GlobalInstrumentation) {
loadSdkScriptFromURL(url)
const devInstance = global.get() as SdkPublicApi

if (devInstance) {
global.onSet((sdkInstance) => proxySdk(sdkInstance, devInstance))
global.returnValue(devInstance)
}
}

function overrideInitConfiguration(global: GlobalInstrumentation, configurationOverride: object) {
global.onSet((sdkInstance) => {
// Ensure the sdkInstance has an 'init' method, excluding async stubs.
if ('init' in sdkInstance) {
const originalInit = sdkInstance.init
sdkInstance.init = (config: any) => {
originalInit({ ...config, ...configurationOverride })
}
}
})
}

function loadSdkScriptFromURL(url: string) {
const xhr = new XMLHttpRequest()
try {
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)
return
}
if (xhr.status === 200) {
const script = document.createElement('script')
script.type = 'text/javascript'
script.text = xhr.responseText

document.documentElement.prepend(script)
}
}

type GlobalInstrumentation = ReturnType<typeof instrumentGlobal>
function instrumentGlobal(global: 'DD_RUM' | 'DD_LOGS') {
const eventListeners = new EventListeners<SdkPublicApi>()
let returnedInstance: SdkPublicApi | undefined
let lastInstance: SdkPublicApi | undefined
Object.defineProperty(window, global, {
set(sdkInstance: SdkPublicApi) {
eventListeners.notify(sdkInstance)
lastInstance = sdkInstance
},
get(): SdkPublicApi | undefined {
return returnedInstance ?? lastInstance
},
})

return {
get: () => window[global],
onSet: (callback: (sdkInstance: SdkPublicApi) => void) => {
eventListeners.subscribe(callback)
},
returnValue: (sdkInstance: SdkPublicApi) => {
returnedInstance = sdkInstance
},
}
}

function proxySdk(target: SdkPublicApi, root: SdkPublicApi) {
Object.assign(target, root)
}
17 changes: 15 additions & 2 deletions developer-extension/src/panel/components/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { datadogRum } from '@datadog/browser-rum'
import { useEvents } from '../hooks/useEvents'
import { useAutoFlushEvents } from '../hooks/useAutoFlushEvents'
import { useNetworkRules } from '../hooks/useNetworkRules'
import type { Settings } from '../hooks/useSettings'
import { useSettings } from '../hooks/useSettings'
import { DEFAULT_PANEL_TAB, PanelTabs } from '../../common/constants'
import type { Settings } from '../../common/types'
import { SettingsTab } from './tabs/settingsTab'
import { InfosTab } from './tabs/infosTab'
import { EventsTab, DEFAULT_COLUMNS } from './tabs/eventsTab'
Expand Down Expand Up @@ -35,7 +35,16 @@ export function Panel() {
<Tabs color="violet" value={activeTab} className={classes.tabs} onChange={updateActiveTab}>
<Tabs.List className="dd-privacy-allow">
<Tabs.Tab value={PanelTabs.Events}>Events</Tabs.Tab>
<Tabs.Tab value={PanelTabs.Infos}>
<Tabs.Tab
value={PanelTabs.Infos}
rightSection={
isOverridingInitConfiguration(settings) && (
<Text c="orange" fw="bold" title="Overriding init configuration">
</Text>
)
}
>
<Text>Infos</Text>
</Tabs.Tab>
<Tabs.Tab value={PanelTabs.Replay}>
Expand Down Expand Up @@ -81,3 +90,7 @@ export function Panel() {
function isInterceptingNetworkRequests(settings: Settings) {
return settings.blockIntakeRequests || settings.useDevBundles || settings.useRumSlim
}

function isOverridingInitConfiguration(settings: Settings) {
return settings.rumConfigurationOverride || settings.logsConfigurationOverride
}
Loading

0 comments on commit 4308cbb

Please sign in to comment.