Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ecceaf7
feat:add sourcemaps and telemetry
cy-moi Jul 7, 2025
655da60
feat:add sourcemaps and telemetry
cy-moi Jul 8, 2025
5954af8
update js-instrument
cy-moi Jul 9, 2025
8a9fbd0
fix:integrity
cy-moi Jul 10, 2025
ae53259
fix: add loadInclude; remove unused code
cy-moi Jul 10, 2025
6cd5f50
fix: integrity
cy-moi Jul 10, 2025
5f51ae3
fix: remove too many logs
cy-moi Jul 10, 2025
2031208
fix: exclude by default spcial charaters; remove logs of apikey
cy-moi Jul 11, 2025
df69942
fix:make configuration slim; make sdk opt-in with config; make e2e mo…
cy-moi Jul 15, 2025
b8eedfc
fix:integrity
cy-moi Jul 15, 2025
92fd9fb
feat:upgrade js-instrument
cy-moi Jul 15, 2025
cdbd040
Merge branch 'master' into congyao/add-sourcemaps-and-telemetry
cy-moi Jul 15, 2025
3d4189f
fix:update helpers function to not extract twice
cy-moi Jul 15, 2025
63d80d6
fix:minor polish and add privacy plugin back to e2e full config
cy-moi Jul 15, 2025
1bff567
fix:improve privacy-helper
cy-moi Jul 15, 2025
b900a91
feat: use global variable to be more reliable
cy-moi Jul 18, 2025
2f3fb8a
fix: check if file exists before injection
cy-moi Jul 21, 2025
5a450ff
fix: add to DD_ALLOW in lower case
cy-moi Jul 21, 2025
91dd248
fix: Improve code
cy-moi Jul 24, 2025
8a0f8ab
fix: improve types
cy-moi Jul 25, 2025
b3cab69
fix: update configurations
cy-moi Jul 25, 2025
f1cb0b1
fix: make injected function pure
cy-moi Jul 25, 2025
a4d8a8b
Update packages/plugins/rum/src/privacy/constants.ts
cy-moi Jul 28, 2025
31a8910
Improve code
cy-moi Jul 29, 2025
03fc9a1
Update packages/plugins/rum/src/built/privacy-helpers.ts
cy-moi Jul 30, 2025
6b75c77
Throw errors unless injection files non-exist
cy-moi Jul 30, 2025
2d32edd
fix:integrity
cy-moi Jul 30, 2025
5101569
fix:add log forward
cy-moi Jul 30, 2025
37ead59
Update packages/plugins/injection/src/esbuild.ts
cy-moi Jul 31, 2025
5933f3e
Merge branch 'master' into congyao/add-sourcemaps-and-telemetry
yoannmoinet Aug 4, 2025
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions LICENSES-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Component,Origin,Licence,Copyright
@rollup/plugin-json,virtual,MIT,rollup (https://github.com/rollup/plugins/tree/master/packages/json#readme)
@rollup/plugin-node-resolve,virtual,MIT,Rich Harris (https://github.com/rollup/plugins/tree/master/packages/node-resolve/#readme)
@rollup/plugin-terser,virtual,MIT,Peter Placzek (https://github.com/rollup/plugins/tree/master/packages/terser#readme)
@rollup/plugin-typescript,virtual,MIT,Oskar Segersvärd (https://github.com/rollup/plugins/tree/master/packages/typescript/#readme)
@rollup/pluginutils,virtual,MIT,Rich Harris (https://github.com/rollup/plugins/tree/master/packages/pluginutils#readme)
@rollup/rollup-darwin-arm64,npm,MIT,Lukas Taegert-Atkinson (https://rollupjs.org/)
@rollup/rollup-darwin-x64,npm,MIT,Lukas Taegert-Atkinson (https://rollupjs.org/)
Expand Down Expand Up @@ -903,6 +904,7 @@ to-regex-range,npm,MIT,Jon Schlinkert (https://github.com/micromatch/to-regex-ra
tough-cookie,npm,BSD-3-Clause,Jeremy Stashewsky (https://github.com/salesforce/tough-cookie)
ts-api-utils,virtual,MIT,JoshuaKGoldberg (https://www.npmjs.com/package/ts-api-utils)
ts-jest,virtual,MIT,Kulshekhar Kabra (https://kulshekhar.github.io/ts-jest)
ts-loader,virtual,MIT,John Reilly (https://github.com/TypeStrong/ts-loader)
ts-node,virtual,MIT,Blake Embrey (https://typestrong.org/ts-node)
tsconfig-paths,npm,MIT,Jonas Kello (https://www.npmjs.com/package/tsconfig-paths)
tslib,npm,0BSD,Microsoft Corp. (https://www.typescriptlang.org/)
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/rum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@datadog/js-instrumentation-wasm": "0.9.4",
"@datadog/js-instrumentation-wasm": "1.0.2",
"@dd/core": "workspace:*",
"@rollup/pluginutils": "5.1.4",
"chalk": "2.3.1"
Expand Down
10 changes: 9 additions & 1 deletion packages/plugins/rum/src/built/privacy-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ globalAny.$DD_ALLOW = new Set();

export function $(newValues: string[] | TemplateStringsArray) {
const initialSize = globalAny.$DD_ALLOW.size;
newValues.forEach((value) => globalAny.$DD_ALLOW.add(value));
newValues.forEach((value) => {
if ((value as unknown as TemplateStringsArray).raw) {
(value as unknown as TemplateStringsArray).raw.forEach((raw) => {
globalAny.$DD_ALLOW.add(raw);
});
} else {
globalAny.$DD_ALLOW.add(value);
}
});
Comment thread
cy-moi marked this conversation as resolved.
Outdated
if (globalAny.$DD_ALLOW.size !== initialSize) {
if (globalAny.$DD_ALLOW_OBSERVERS) {
globalAny.$DD_ALLOW_OBSERVERS.forEach((cb: () => void) => cb());
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/rum/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const getPlugins: GetPlugins = ({ options, context }) => {

if (validatedOptions.privacy) {
// Add the privacy plugin.
const privacyPlugin = getPrivacyPlugin(validatedOptions.privacy);
const privacyPlugin = getPrivacyPlugin(validatedOptions.privacy, context);
if (privacyPlugin) {
plugins.push(privacyPlugin);
}
Expand Down
2 changes: 0 additions & 2 deletions packages/plugins/rum/src/privacy/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('Rum Privacy Plugin', () => {
disabled: true,
exclude: [],
include: [],
module: 'esm',
},
},
}),
Expand All @@ -49,7 +48,6 @@ describe('Rum Privacy Plugin', () => {
disabled: false,
exclude: [],
include: [],
module: 'esm',
},
},
}),
Expand Down
61 changes: 46 additions & 15 deletions packages/plugins/rum/src/privacy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Copyright 2019-Present Datadog, Inc.

import { instrument } from '@datadog/js-instrumentation-wasm';
import type { PluginOptions } from '@dd/core/types';
import type { GlobalContext, PluginOptions } from '@dd/core/types';
import { createFilter } from '@rollup/pluginutils';
import fs from 'node:fs';
import path from 'node:path';
Expand All @@ -12,38 +12,50 @@ import { PRIVACY_HELPERS_MODULE_ID, PLUGIN_NAME } from './constants';
import { buildTransformOptions } from './transform';
import type { PrivacyOptions } from './types';

export const getPrivacyPlugin = (pluginOptions: PrivacyOptions): PluginOptions | undefined => {
export const getPrivacyPlugin = (
pluginOptions: PrivacyOptions,
context: GlobalContext,
): PluginOptions | undefined => {
const log = context.getLogger(PLUGIN_NAME);

if (pluginOptions.disabled) {
return;
}

const transformOptions = buildTransformOptions(pluginOptions);
const transformFilter = createFilter(pluginOptions.include, pluginOptions.exclude);

// Read the privacy helpers code
const privacyHelpersPath = path.join(
__dirname,
pluginOptions.module === 'cjs' ? './privacy-helpers.js' : './privacy-helpers.mjs',
);

const privacyHelpersModuleId = pluginOptions.helpersModule ?? PRIVACY_HELPERS_MODULE_ID;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need the ?? ... part?
We don't do it in buildTransformOptions().

return {
name: PLUGIN_NAME,
// Enforce when the plugin will be executed.
// Not supported by Rollup and ESBuild.
// https://vitejs.dev/guide/api-plugin.html#plugin-ordering
enforce: 'pre',
enforce: 'post',
// webpack's id filter is outside of loader logic,
// an additional hook is needed for better perf on webpack
async resolveId(source) {
if (source === PRIVACY_HELPERS_MODULE_ID) {
return { id: PRIVACY_HELPERS_MODULE_ID };
if (source.includes(privacyHelpersModuleId)) {
return { id: source };
}
return null;
},

loadInclude(id) {
if (id.includes(privacyHelpersModuleId)) {
return true;
}
return false;
},

async load(id) {
if (id === PRIVACY_HELPERS_MODULE_ID) {
return { code: fs.readFileSync(privacyHelpersPath, 'utf8') };
let privacyHelpersPath: string;
if (id.includes(privacyHelpersModuleId)) {
if (id.endsWith('.cjs')) {
privacyHelpersPath = path.join(__dirname, 'privacy-helpers.js');
} else {
privacyHelpersPath = path.join(__dirname, 'privacy-helpers.mjs');
}
return { code: fs.readFileSync(privacyHelpersPath, 'utf8'), map: null };
Comment thread
cy-moi marked this conversation as resolved.
Outdated
}
return null;
},
Expand All @@ -53,7 +65,26 @@ export const getPrivacyPlugin = (pluginOptions: PrivacyOptions): PluginOptions |
return transformFilter(id);
},
async transform(code, id) {
return instrument({ id, code }, transformOptions);
try {
if (
context.bundler.name === 'esbuild' ||
context.bundler.name === 'webpack' ||
context.bundler.name === 'rspack'
) {
Comment thread
cy-moi marked this conversation as resolved.
Outdated
transformOptions.output = {
...transformOptions.output,
inlineSourceMap: false,
embedCodeInSourceMap: true,
};
}
const result = instrument({ id, code }, transformOptions);
return result;
} catch (e) {
log.error(`Instrumentation Error: ${e}`);
return {
code,
};
}
},
};
};
11 changes: 3 additions & 8 deletions packages/plugins/rum/src/privacy/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import type { InstrumentationOptions } from '@datadog/js-instrumentation-wasm';

import { PRIVACY_HELPERS_MODULE_ID } from './constants';
import type { PrivacyOptions } from './types';

export interface TransformOutput {
Expand All @@ -14,16 +13,12 @@ export interface TransformOutput {

export function buildTransformOptions(pluginOptions: PrivacyOptions): InstrumentationOptions {
return {
input: {
module: pluginOptions.module,
jsx: pluginOptions.jsx,
typescript: pluginOptions.typescript,
},
privacy: {
addToDictionaryHelper: {
import: {
module: PRIVACY_HELPERS_MODULE_ID,
func: '$',
cjsModule: `${pluginOptions.helpersModule}.cjs`,
esmModule: `${pluginOptions.helpersModule}.mjs`,
func: pluginOptions.addToDictionaryFunctionName ?? '$',
},
},
},
Expand Down
8 changes: 3 additions & 5 deletions packages/plugins/rum/src/privacy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import type { Assign } from '@dd/core/types';
export interface PrivacyOptions {
exclude?: RegExp[] | string[];
include?: RegExp[] | string[];
module?: 'cjs' | 'esm';
jsx?: boolean;
transformStrategy?: 'ast';
typescript?: boolean;
helpersModule?: string;
addToDictionaryFunctionName?: string;
disabled?: boolean | undefined;
}

export type PrivacyOptionsWithDefaults = Assign<
PrivacyOptions,
Pick<Required<PrivacyOptions>, 'exclude' | 'include' | 'module' | 'transformStrategy'>
Pick<Required<PrivacyOptions>, 'exclude' | 'include'>
>;
3 changes: 3 additions & 0 deletions packages/plugins/rum/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const getInjectionValue = (
context: GlobalContext,
): InjectedValue => {
const sdkOpts = options.sdk;
if (sdkOpts.disabled) {
return '';
}
Comment thread
cy-moi marked this conversation as resolved.
Outdated
// We already have the clientToken, we can inject it directly.
if (sdkOpts.clientToken) {
return getContent(options);
Expand Down
63 changes: 36 additions & 27 deletions packages/plugins/rum/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,48 @@ export type RumOptions = {
export type RumPublicApi = typeof datadogRum;
export type RumInitConfiguration = ExpRumInitConfiguration;

export type SDKOptions = Assign<
// Base SDK options without discriminated union
type BaseSDKOptions = 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'
>
>;
// Discriminated union for SDK options
export type SDKOptions =
| (Partial<BaseSDKOptions> & { disabled: true }) // When disabled, all properties are optional
| (BaseSDKOptions & { disabled?: false | undefined }); // When enabled, required properties must be provided

// When disabled is true, use the options as-is. When disabled is false/undefined, make required properties required
export type SDKOptionsWithDefaults = SDKOptions extends { disabled: true }
? SDKOptions
: Assign<
BaseSDKOptions,
{
disabled?: boolean;
} & Pick<
Required<BaseSDKOptions>,
| 'applicationId'
| 'allowUntrustedEvents'
| 'compressIntakeRequests'
| 'defaultPrivacyLevel'
| 'enablePrivacyForActionName'
| 'sessionReplaySampleRate'
| 'sessionSampleRate'
| 'silentMultipleInit'
| 'site'
| 'startSessionReplayRecordingManually'
| 'storeContextsAcrossPages'
| 'telemetrySampleRate'
| 'traceSampleRate'
| 'trackingConsent'
| 'trackLongTasks'
| 'trackResources'
| 'trackUserInteractions'
| 'trackViewsManually'
>
>;
Comment thread
cy-moi marked this conversation as resolved.
Outdated

export type RumOptionsWithDefaults = {
disabled?: boolean;
Expand Down
77 changes: 77 additions & 0 deletions packages/plugins/rum/src/validate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 { defaultPluginOptions } from '@dd/tests/_jest/helpers/mocks';

import { validateOptions } from './validate';

const mockLogger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
getLogger: jest.fn(),
time: jest.fn(),
};

describe('Test privacy plugin option exclude regex', () => {
test('should exclude all files that start with special characters', () => {
const options = validateOptions(
{ ...defaultPluginOptions, rum: { sdk: { applicationId: 'app_id' } } },
mockLogger,
);
// test regex
options.privacy?.exclude.forEach((regex) => {
if (typeof regex === 'string') {
return;
}
expect(regex.test('!test.js')).toBe(true);
expect(regex.test('@test.js')).toBe(true);
expect(regex.test('#test.js')).toBe(true);
expect(regex.test('$test.js')).toBe(true);
expect(regex.test('^test.js')).toBe(true);
});
});

test('should include absolute and relative paths', () => {
const options = validateOptions(
{ ...defaultPluginOptions, rum: { sdk: { applicationId: 'app_id' } } },
mockLogger,
);
// test regex
options.privacy?.exclude.forEach((regex) => {
if (typeof regex === 'string') {
return;
}
expect(regex.test('/Users/test/test.js')).toBe(true);
expect(regex.test('./test.js')).toBe(true);
});
});

test('should exclude node_modules', () => {
const options = validateOptions(
{ ...defaultPluginOptions, rum: { sdk: { applicationId: 'app_id' } } },
mockLogger,
);
options.privacy?.exclude.forEach((regex) => {
if (typeof regex === 'string') {
return;
}
expect(regex.test('/node_modules/test.js')).toBe(true);
});
});

test('should exclude .preval files', () => {
const options = validateOptions(
{ ...defaultPluginOptions, rum: { sdk: { applicationId: 'app_id' } } },
mockLogger,
);
options.privacy?.exclude.forEach((regex) => {
if (typeof regex === 'string') {
return;
}
expect(regex.test('.preval.js')).toBe(true);
});
});
});
Comment thread
cy-moi marked this conversation as resolved.
Loading