Skip to content

Commit

Permalink
Merge pull request #119 from DataDog/yoann/inject-browser-sdk
Browse files Browse the repository at this point in the history
[rum] Inject browser sdk
  • Loading branch information
yoannmoinet authored Feb 27, 2025
2 parents e115d3d + a36d275 commit 3c307f3
Show file tree
Hide file tree
Showing 37 changed files with 829 additions and 29 deletions.
7 changes: 6 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,12 @@ module.exports = {
},
},
{
files: ['rollup.config.mjs', 'packages/core/**/*', 'packages/published/**/*'],
files: [
'rollup.config.mjs',
'packages/core/**/*',
'packages/published/**/*',
'packages/plugins/**/built/*',
],
rules: {
'import/no-extraneous-dependencies': 'off',
},
Expand Down
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ packages/tests/src/plugins/telemetry @DataDog/f
packages/plugins/error-tracking @yoannmoinet
packages/tests/src/plugins/error-tracking @yoannmoinet

# Rum
packages/plugins/rum @yoannmoinet
packages/tests/src/plugins/rum @yoannmoinet

# Analytics
packages/plugins/analytics @yoannmoinet
packages/tests/src/plugins/analytics @yoannmoinet
2 changes: 0 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ jobs:
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:all

- run: yarn typecheck:all

- run: yarn cli integrity

- run: git diff --exit-code && git diff --cached --exit-code || (echo "Please run 'yarn cli integrity' and commit the result." && exit 1)
Binary file not shown.
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions LICENSES-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ Component,Origin,Licence,Copyright
@babel/types,npm,MIT,The Babel Team (https://babel.dev/docs/en/next/babel-types)
@bcoe/v8-coverage,npm,MIT,Charles Samborski (https://demurgos.github.io/v8-coverage)
@cspotcode/source-map-support,npm,MIT,(https://www.npmjs.com/package/@cspotcode/source-map-support)
@datadog/browser-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-core)
@datadog/browser-rum,virtual,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum)
@datadog/browser-rum-core,npm,Apache-2.0,(https://www.npmjs.com/package/@datadog/browser-rum-core)
@esbuild/darwin-arm64,npm,MIT,(https://www.npmjs.com/package/@esbuild/darwin-arm64)
@esbuild/linux-x64,npm,MIT,(https://www.npmjs.com/package/@esbuild/linux-x64)
@eslint-community/eslint-utils,virtual,MIT,Toru Nagashima (https://github.com/eslint-community/eslint-utils#readme)
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type { TrackedFilesMatcher } from '@dd/internal-git-plugin/trackedFilesMa
// #imports-injection-marker
import type { ErrorTrackingOptions } from '@dd/error-tracking-plugin/types';
import type * as errorTracking from '@dd/error-tracking-plugin';
import type { RumOptions } from '@dd/rum-plugin/types';
import type * as rum from '@dd/rum-plugin';
import type { TelemetryOptions } from '@dd/telemetry-plugin/types';
import type * as telemetry from '@dd/telemetry-plugin';
// #imports-injection-marker
Expand Down Expand Up @@ -147,6 +149,7 @@ export interface Options extends BaseOptions {
// Each product should have a unique entry.
// #types-injection-marker
[errorTracking.CONFIG_KEY]?: ErrorTrackingOptions;
[rum.CONFIG_KEY]?: RumOptions;
[telemetry.CONFIG_KEY]?: TelemetryOptions;
// #types-injection-marker
customPlugins?: GetCustomPlugins;
Expand Down
2 changes: 2 additions & 0 deletions packages/factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Most of the time they will interact via the global context.
### Analytics

> Send some analytics data to Datadog internally.
>
> It gives you acces to the `context.sendLog()` function.
#### [📝 Full documentation ➡️](/packages/plugins/analytics#readme)

Expand Down
1 change: 1 addition & 0 deletions packages/factory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@dd/internal-bundler-report-plugin": "workspace:*",
"@dd/internal-git-plugin": "workspace:*",
"@dd/internal-injection-plugin": "workspace:*",
"@dd/rum-plugin": "workspace:*",
"@dd/telemetry-plugin": "workspace:*",
"chalk": "2.3.1",
"unplugin": "1.16.0"
Expand Down
6 changes: 6 additions & 0 deletions packages/factory/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { getContext, validateOptions } from './helpers';
// #imports-injection-marker
import type { OptionsWithErrorTracking } from '@dd/error-tracking-plugin/types';
import * as errorTracking from '@dd/error-tracking-plugin';
import type { OptionsWithRum } from '@dd/rum-plugin/types';
import * as rum from '@dd/rum-plugin';
import type { OptionsWithTelemetry } from '@dd/telemetry-plugin/types';
import * as telemetry from '@dd/telemetry-plugin';
import { getAnalyticsPlugins } from '@dd/internal-analytics-plugin';
Expand All @@ -39,6 +41,7 @@ import { getInjectionPlugins } from '@dd/internal-injection-plugin';
// #imports-injection-marker
// #types-export-injection-marker
export type { types as ErrorTrackingTypes } from '@dd/error-tracking-plugin';
export type { types as RumTypes } from '@dd/rum-plugin';
export type { types as TelemetryTypes } from '@dd/telemetry-plugin';
// #types-export-injection-marker

Expand Down Expand Up @@ -104,6 +107,9 @@ export const buildPluginFactory = ({
) {
plugins.push(...errorTracking.getPlugins(options as OptionsWithErrorTracking, context));
}
if (options[rum.CONFIG_KEY] && options[rum.CONFIG_KEY].disabled !== true) {
plugins.push(...rum.getPlugins(options as OptionsWithRum, context));
}
if (options[telemetry.CONFIG_KEY] && options[telemetry.CONFIG_KEY].disabled !== true) {
plugins.push(...telemetry.getPlugins(options as OptionsWithTelemetry, context));
}
Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/injection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Particularly useful :
- to initialise some global dependencies.
- ...

## Inject content

It gives you access to the `context.inject()` function.

All the injections will be resolved during the `buildStart` hook,<br/>
Expand Down
33 changes: 33 additions & 0 deletions packages/plugins/rum/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@dd/rum-plugin",
"packageManager": "[email protected]",
"license": "MIT",
"private": true,
"author": "Datadog",
"description": "Interact with Real User Monitoring (RUM) directly from your build system.",
"homepage": "https://github.com/DataDog/build-plugins/tree/main/packages/plugins/rum#readme",
"repository": {
"type": "git",
"url": "https://github.com/DataDog/build-plugins",
"directory": "packages/plugins/rum"
},
"toBuild": {
"rum-browser-sdk": {
"entry": "./src/built/rum-browser-sdk.ts"
}
},
"exports": {
".": "./src/index.ts",
"./*": "./src/*.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@dd/core": "workspace:*",
"chalk": "2.3.1"
},
"devDependencies": {
"@datadog/browser-rum": "6.0.0"
}
}
13 changes: 13 additions & 0 deletions packages/plugins/rum/src/built/rum-browser-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

/* global globalThis */

import { datadogRum } from '@datadog/browser-rum';

// To please TypeScript.
const globalAny: any = globalThis;

// Also them to the global DD_RUM object.
globalAny.DD_RUM = datadogRum;
8 changes: 8 additions & 0 deletions packages/plugins/rum/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import type { PluginName } from '@dd/core/types';

export const CONFIG_KEY = 'rum' as const;
export const PLUGIN_NAME: PluginName = 'datadog-rum-plugin' as const;
63 changes: 63 additions & 0 deletions packages/plugins/rum/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import type { PluginOptions, GetPlugins, GlobalContext } from '@dd/core/types';
import { InjectPosition } from '@dd/core/types';
import path from 'path';

import { CONFIG_KEY, PLUGIN_NAME } from './constants';
import { getInjectionValue } from './sdk';
import type {
OptionsWithRum,
RumOptions,
RumOptionsWithSdk,
RumPublicApi,
RumInitConfiguration,
} from './types';
import { validateOptions } from './validate';

export { CONFIG_KEY, PLUGIN_NAME };

export const helpers = {
// Add the helpers you'd like to expose here.
};

export type types = {
// Add the types you'd like to expose here.
RumOptions: RumOptions;
OptionsWithRum: OptionsWithRum;
RumPublicApi: RumPublicApi;
RumInitConfiguration: RumInitConfiguration;
};

export const getPlugins: GetPlugins<OptionsWithRum> = (
opts: OptionsWithRum,
context: GlobalContext,
) => {
const log = context.getLogger(PLUGIN_NAME);
const plugins: PluginOptions[] = [];
// Verify configuration.
const options = validateOptions(opts, log);

// NOTE: These files are built from "@dd/tools/rollupConfig.mjs" and available in the distributed package.
if (options.sdk) {
// Inject the SDK from the CDN.
context.inject({
type: 'file',
// Using MIDDLE otherwise it's not executed in context.
position: InjectPosition.MIDDLE,
// This file is being built alongside the bundler plugin.
value: path.join(__dirname, './rum-browser-sdk.js'),
});

// Inject the SDK Initialization.
context.inject({
type: 'code',
position: InjectPosition.MIDDLE,
value: getInjectionValue(options as RumOptionsWithSdk, context),
});
}

return plugins;
};
71 changes: 71 additions & 0 deletions packages/plugins/rum/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { doRequest } from '@dd/core/helpers';
import type { GlobalContext, InjectedValue } from '@dd/core/types';

import type { RumOptionsWithDefaults, RumOptionsWithSdk } from './types';

type RumAppResponse = {
data: {
attributes: {
client_token: string;
};
};
};

const getContent = (opts: RumOptionsWithDefaults) => {
return `DD_RUM.init({${JSON.stringify(opts.sdk).replace(/(^{|}$)/g, '')}});
`;
};

export const getInjectionValue = (
options: RumOptionsWithSdk,
context: GlobalContext,
): InjectedValue => {
const sdkOpts = options.sdk;
// We already have the clientToken, we can inject it directly.
if (sdkOpts.clientToken) {
return getContent(options);
}

// Let's try and fetch the clientToken from the API.
if (!context.auth?.apiKey || !context.auth?.appKey) {
throw new Error(
'Missing "auth.apiKey" and/or "auth.appKey" to fetch "rum.sdk.clientToken".',
);
}

// Return the value as an async function so it gets resolved during buildStart.
return async () => {
let clientToken: string;
try {
// Fetch the client token from the API.
const appResponse = await doRequest<RumAppResponse>({
url: `https://api.datadoghq.com/api/v2/rum/applications/${sdkOpts.applicationId}`,
type: 'json',
auth: context.auth,
});

clientToken = appResponse.data?.attributes?.client_token;
} catch (e: any) {
// Could not fetch the clientToken.
// Let's crash the build.
throw new Error(`Could not fetch the clientToken: ${e.message}`);
}

// Still no clientToken.
if (!clientToken) {
throw new Error('Missing clientToken in the API response.');
}

return getContent({
...options,
sdk: {
clientToken,
...sdkOpts,
},
});
};
};
64 changes: 64 additions & 0 deletions packages/plugins/rum/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import type {
datadogRum,
RumInitConfiguration as ExpRumInitConfiguration,
} from '@datadog/browser-rum';
import type { Assign, GetPluginsOptions } from '@dd/core/types';

import type { CONFIG_KEY } from './constants';

export type RumOptions = {
disabled?: boolean;
sdk?: SDKOptions;
};

export type RumPublicApi = typeof datadogRum;
export type RumInitConfiguration = ExpRumInitConfiguration;

export type SDKOptions = Assign<
RumInitConfiguration,
{
// We make clientToken optional because we'll try to fetch it via API if absent.
clientToken?: string;
}
>;

// Define the SDK options with known defaults.
export type SDKOptionsWithDefaults = Assign<
SDKOptions,
Pick<
Required<SDKOptions>,
| 'applicationId'
| 'allowUntrustedEvents'
| 'compressIntakeRequests'
| 'defaultPrivacyLevel'
| 'enablePrivacyForActionName'
| 'sessionReplaySampleRate'
| 'sessionSampleRate'
| 'silentMultipleInit'
| 'site'
| 'startSessionReplayRecordingManually'
| 'storeContextsAcrossPages'
| 'telemetrySampleRate'
| 'traceSampleRate'
| 'trackingConsent'
| 'trackLongTasks'
| 'trackResources'
| 'trackUserInteractions'
| 'trackViewsManually'
>
>;

export type RumOptionsWithDefaults = {
disabled?: boolean;
sdk?: SDKOptionsWithDefaults;
};

export type RumOptionsWithSdk = Assign<RumOptionsWithDefaults, { sdk: SDKOptionsWithDefaults }>;

export interface OptionsWithRum extends GetPluginsOptions {
[CONFIG_KEY]: RumOptions;
}
Loading

0 comments on commit 3c307f3

Please sign in to comment.