bar
'),
- 'data-test-id'
- )
- ).toBe('Foo')
+ describe('with html tag privacy override when privacyEnabledActionName is true', () => {
+ it('extracts inner text when privacy level is allow', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ foo
+
+ `),
+ defaultConfiguration,
+ NodePrivacyLevel.ALLOW
+ )
+ ).toEqual('foo')
+ })
+
+ it('returns placeholder when privacy level is mask', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ foo
+
+ `),
+ {
+ ...defaultConfiguration,
+ enablePrivacyForActionName: true,
+ },
+ NodePrivacyLevel.MASK
+ )
+ ).toEqual('Masked Element')
+ })
+
+ it('inherit privacy level and does not fallback to masked child text', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ `),
+ {
+ ...defaultConfiguration,
+ enablePrivacyForActionName: true,
+ },
+ NodePrivacyLevel.ALLOW
+ )
+ ).toEqual('foo')
+ })
+ it('fallback to children but not the masked one with mixed class name and attribute', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ `),
+ {
+ ...defaultConfiguration,
+ enablePrivacyForActionName: true,
+ },
+ NodePrivacyLevel.ALLOW
+ )
+ ).toEqual('bar foo')
+ })
+
+ it('inherit privacy level and does not fallback to masked child text with mixed classname and attribute', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ `),
+ {
+ ...defaultConfiguration,
+ enablePrivacyForActionName: true,
+ },
+ NodePrivacyLevel.ALLOW
+ )
+ ).toEqual('foo')
+ })
+ it('fallback to children but not the masked one with class names', () => {
+ expect(
+ getActionNameFromElement(
+ appendElement(`
+
+ `),
+ {
+ ...defaultConfiguration,
+ enablePrivacyForActionName: true,
+ },
+ NodePrivacyLevel.ALLOW
+ )
+ ).toEqual('bar foo')
+ })
})
})
})
diff --git a/packages/rum-core/src/domain/action/getActionNameFromElement.ts b/packages/rum-core/src/domain/action/getActionNameFromElement.ts
index 2d3f060303..e3e9b5ed7d 100644
--- a/packages/rum-core/src/domain/action/getActionNameFromElement.ts
+++ b/packages/rum-core/src/domain/action/getActionNameFromElement.ts
@@ -1,24 +1,47 @@
import { safeTruncate, isIE, find } from '@datadog/browser-core'
import { getParentElement } from '../../browser/polyfills'
+import { NodePrivacyLevel, getPrivacySelector } from '../privacy'
+import type { RumConfiguration } from '../configuration'
/**
* Get the action name from the attribute 'data-dd-action-name' on the element or any of its parent.
* It can also be retrieved from a user defined attribute.
*/
export const DEFAULT_PROGRAMMATIC_ACTION_NAME_ATTRIBUTE = 'data-dd-action-name'
-
-export function getActionNameFromElement(element: Element, userProgrammaticAttribute?: string): string {
+export const ACTION_NAME_PLACEHOLDER = 'Masked Element'
+export function getActionNameFromElement(
+ element: Element,
+ { enablePrivacyForActionName, actionNameAttribute: userProgrammaticAttribute }: RumConfiguration,
+ nodePrivacyLevel?: NodePrivacyLevel
+): string {
// Proceed to get the action name in two steps:
// * first, get the name programmatically, explicitly defined by the user.
// * then, use strategies that are known to return good results. Those strategies will be used on
// the element and a few parents, but it's likely that they won't succeed at all.
// * if no name is found this way, use strategies returning less accurate names as a fallback.
// Those are much likely to succeed.
- return (
+ const defaultActionName =
getActionNameFromElementProgrammatically(element, DEFAULT_PROGRAMMATIC_ACTION_NAME_ATTRIBUTE) ||
- (userProgrammaticAttribute && getActionNameFromElementProgrammatically(element, userProgrammaticAttribute)) ||
- getActionNameFromElementForStrategies(element, userProgrammaticAttribute, priorityStrategies) ||
- getActionNameFromElementForStrategies(element, userProgrammaticAttribute, fallbackStrategies) ||
+ (userProgrammaticAttribute && getActionNameFromElementProgrammatically(element, userProgrammaticAttribute))
+
+ if (nodePrivacyLevel === NodePrivacyLevel.MASK) {
+ return defaultActionName || ACTION_NAME_PLACEHOLDER
+ }
+
+ return (
+ defaultActionName ||
+ getActionNameFromElementForStrategies(
+ element,
+ userProgrammaticAttribute,
+ priorityStrategies,
+ enablePrivacyForActionName
+ ) ||
+ getActionNameFromElementForStrategies(
+ element,
+ userProgrammaticAttribute,
+ fallbackStrategies,
+ enablePrivacyForActionName
+ ) ||
''
)
}
@@ -51,12 +74,13 @@ function getActionNameFromElementProgrammatically(targetElement: Element, progra
type NameStrategy = (
element: Element | HTMLElement | HTMLInputElement | HTMLSelectElement,
- userProgrammaticAttribute: string | undefined
+ userProgrammaticAttribute: string | undefined,
+ privacyEnabledActionName?: boolean
) => string | undefined | null
const priorityStrategies: NameStrategy[] = [
// associated LABEL text
- (element, userProgrammaticAttribute) => {
+ (element, userProgrammaticAttribute, privacy) => {
// IE does not support element.labels, so we fallback to a CSS selector based on the element id
// instead
if (supportsLabelProperty()) {
@@ -67,7 +91,7 @@ const priorityStrategies: NameStrategy[] = [
const label =
element.ownerDocument &&
find(element.ownerDocument.querySelectorAll('label'), (label) => label.htmlFor === element.id)
- return label && getTextualContent(label, userProgrammaticAttribute)
+ return label && getTextualContent(label, userProgrammaticAttribute, privacy)
}
},
// INPUT button (and associated) value
@@ -81,21 +105,21 @@ const priorityStrategies: NameStrategy[] = [
}
},
// BUTTON, LABEL or button-like element text
- (element, userProgrammaticAttribute) => {
+ (element, userProgrammaticAttribute, privacyEnabledActionName) => {
if (element.nodeName === 'BUTTON' || element.nodeName === 'LABEL' || element.getAttribute('role') === 'button') {
- return getTextualContent(element, userProgrammaticAttribute)
+ return getTextualContent(element, userProgrammaticAttribute, privacyEnabledActionName)
}
},
(element) => element.getAttribute('aria-label'),
// associated element text designated by the aria-labelledby attribute
- (element, userProgrammaticAttribute) => {
+ (element, userProgrammaticAttribute, privacyEnabledActionName) => {
const labelledByAttribute = element.getAttribute('aria-labelledby')
if (labelledByAttribute) {
return labelledByAttribute
.split(/\s+/)
.map((id) => getElementById(element, id))
.filter((label): label is HTMLElement => Boolean(label))
- .map((element) => getTextualContent(element, userProgrammaticAttribute))
+ .map((element) => getTextualContent(element, userProgrammaticAttribute, privacyEnabledActionName))
.join(' ')
}
},
@@ -112,7 +136,8 @@ const priorityStrategies: NameStrategy[] = [
]
const fallbackStrategies: NameStrategy[] = [
- (element, userProgrammaticAttribute) => getTextualContent(element, userProgrammaticAttribute),
+ (element, userProgrammaticAttribute, privacyEnabledActionName) =>
+ getTextualContent(element, userProgrammaticAttribute, privacyEnabledActionName),
]
/**
@@ -123,7 +148,8 @@ const MAX_PARENTS_TO_CONSIDER = 10
function getActionNameFromElementForStrategies(
targetElement: Element,
userProgrammaticAttribute: string | undefined,
- strategies: NameStrategy[]
+ strategies: NameStrategy[],
+ privacyEnabledActionName?: boolean
) {
let element: Element | null = targetElement
let recursionCounter = 0
@@ -135,7 +161,7 @@ function getActionNameFromElementForStrategies(
element.nodeName !== 'HEAD'
) {
for (const strategy of strategies) {
- const name = strategy(element, userProgrammaticAttribute)
+ const name = strategy(element, userProgrammaticAttribute, privacyEnabledActionName)
if (typeof name === 'string') {
const trimmedName = name.trim()
if (trimmedName) {
@@ -167,7 +193,11 @@ function getElementById(refElement: Element, id: string) {
return refElement.ownerDocument ? refElement.ownerDocument.getElementById(id) : null
}
-function getTextualContent(element: Element | HTMLElement, userProgrammaticAttribute: string | undefined) {
+function getTextualContent(
+ element: Element | HTMLElement,
+ userProgrammaticAttribute: string | undefined,
+ privacyEnabledActionName?: boolean
+) {
if ((element as HTMLElement).isContentEditable) {
return
}
@@ -201,6 +231,13 @@ function getTextualContent(element: Element | HTMLElement, userProgrammaticAttri
removeTextFromElements(`[${userProgrammaticAttribute}]`)
}
+ if (privacyEnabledActionName) {
+ // remove the text of elements with privacy override
+ removeTextFromElements(
+ `${getPrivacySelector(NodePrivacyLevel.HIDDEN)}, ${getPrivacySelector(NodePrivacyLevel.MASK)}`
+ )
+ }
+
return text
}
diff --git a/packages/rum-core/src/domain/action/trackClickActions.spec.ts b/packages/rum-core/src/domain/action/trackClickActions.spec.ts
index ded25f2369..b62e5ef519 100644
--- a/packages/rum-core/src/domain/action/trackClickActions.spec.ts
+++ b/packages/rum-core/src/domain/action/trackClickActions.spec.ts
@@ -1,5 +1,5 @@
import type { Context, Duration } from '@datadog/browser-core'
-import { addDuration, clocksNow, timeStampNow, relativeNow } from '@datadog/browser-core'
+import { addDuration, clocksNow, timeStampNow, relativeNow, DefaultPrivacyLevel } from '@datadog/browser-core'
import { createNewEvent } from '@datadog/browser-core/test'
import type { TestSetupBuilder } from '../../../test'
import { setup, createFakeClick } from '../../../test'
@@ -232,6 +232,51 @@ describe('trackClickActions', () => {
expect(events.length).toBe(1)
})
+ describe('with enablePrivacyForActionName false', () => {
+ it('extracts action name when default privacy level is mask', () => {
+ setupBuilder.withConfiguration({
+ defaultPrivacyLevel: DefaultPrivacyLevel.MASK,
+ enablePrivacyForActionName: false,
+ })
+ const { clock } = setupBuilder.build()
+ emulateClick({ activity: {} })
+ expect(findActionId()).not.toBeUndefined()
+ clock.tick(EXPIRE_DELAY)
+
+ expect(events.length).toBe(1)
+ expect(events[0].name).toBe('Click me')
+ })
+ })
+
+ describe('with enablePrivacyForActionName true', () => {
+ it('does not track click actions when html override set hidden', () => {
+ setupBuilder.withConfiguration({
+ enablePrivacyForActionName: true,
+ })
+ button.setAttribute('data-dd-privacy', 'hidden')
+
+ const { clock } = setupBuilder.build()
+ emulateClick({ activity: {} })
+ clock.tick(EXPIRE_DELAY)
+
+ expect(events.length).toBe(0)
+ })
+ it('get placeholder when defaultPrivacyLevel is mask without programmatically declared action name', () => {
+ setupBuilder.withConfiguration({
+ defaultPrivacyLevel: DefaultPrivacyLevel.MASK,
+ enablePrivacyForActionName: true,
+ })
+ const { clock } = setupBuilder.build()
+
+ emulateClick({ activity: {} })
+ expect(findActionId()).not.toBeUndefined()
+ clock.tick(EXPIRE_DELAY)
+
+ expect(events.length).toBe(1)
+ expect(events[0].name).toBe('Masked Element')
+ })
+ })
+
describe('rage clicks', () => {
it('considers a chain of three clicks or more as a single action with "rage" frustration type', () => {
const { clock } = setupBuilder.build()
@@ -263,7 +308,7 @@ describe('trackClickActions', () => {
expect(events[0].events?.length).toBe(3)
})
- it('aggregates frustrationTypes from all clicks', () => {
+ it('aggregates frustration Types from all clicks', () => {
const { lifeCycle, clock } = setupBuilder.build()
// Dead
diff --git a/packages/rum-core/src/domain/action/trackClickActions.ts b/packages/rum-core/src/domain/action/trackClickActions.ts
index 7f5ff6631a..5f47ba98e0 100644
--- a/packages/rum-core/src/domain/action/trackClickActions.ts
+++ b/packages/rum-core/src/domain/action/trackClickActions.ts
@@ -14,12 +14,13 @@ import {
} from '@datadog/browser-core'
import type { FrustrationType } from '../../rawRumEvent.types'
import { ActionType } from '../../rawRumEvent.types'
-import type { RumConfiguration } from '../configuration'
import type { LifeCycle } from '../lifeCycle'
import { LifeCycleEventType } from '../lifeCycle'
import { trackEventCounts } from '../trackEventCounts'
import { PAGE_ACTIVITY_VALIDATION_DELAY, waitPageActivityEnd } from '../waitPageActivityEnd'
import { getSelectorFromElement } from '../getSelectorFromElement'
+import { getNodePrivacyLevel, NodePrivacyLevel } from '../privacy'
+import type { RumConfiguration } from '../configuration'
import type { ClickChain } from './clickChain'
import { createClickChain } from './clickChain'
import { getActionNameFromElement } from './getActionNameFromElement'
@@ -82,7 +83,7 @@ export function trackClickActions(
}>(configuration, {
onPointerDown: (pointerDownEvent) =>
processPointerDown(configuration, lifeCycle, domMutationObservable, pointerDownEvent),
- onPointerUp: ({ clickActionBase, hadActivityOnPointerDown }, startEvent, getUserActivity) =>
+ onPointerUp: ({ clickActionBase, hadActivityOnPointerDown }, startEvent, getUserActivity) => {
startClickAction(
configuration,
lifeCycle,
@@ -94,7 +95,8 @@ export function trackClickActions(
startEvent,
getUserActivity,
hadActivityOnPointerDown
- ),
+ )
+ },
})
const actionContexts: ActionContexts = {
@@ -132,7 +134,15 @@ function processPointerDown(
domMutationObservable: Observable
,
pointerDownEvent: MouseEventOnElement
) {
- const clickActionBase = computeClickActionBase(pointerDownEvent, configuration.actionNameAttribute)
+ const nodePrivacyLevel = configuration.enablePrivacyForActionName
+ ? getNodePrivacyLevel(pointerDownEvent.target, configuration.defaultPrivacyLevel)
+ : NodePrivacyLevel.ALLOW
+
+ if (nodePrivacyLevel === NodePrivacyLevel.HIDDEN) {
+ return undefined
+ }
+
+ const clickActionBase = computeClickActionBase(pointerDownEvent, nodePrivacyLevel, configuration)
let hadActivityOnPointerDown = false
@@ -208,21 +218,26 @@ function startClickAction(
type ClickActionBase = Pick
-function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string): ClickActionBase {
+function computeClickActionBase(
+ event: MouseEventOnElement,
+ nodePrivacyLevel: NodePrivacyLevel,
+ configuration: RumConfiguration
+): ClickActionBase {
const rect = event.target.getBoundingClientRect()
+
return {
type: ActionType.CLICK,
target: {
width: Math.round(rect.width),
height: Math.round(rect.height),
- selector: getSelectorFromElement(event.target, actionNameAttribute),
+ selector: getSelectorFromElement(event.target, configuration.actionNameAttribute),
},
position: {
// Use clientX and Y because for SVG element offsetX and Y are relatives to the element
x: Math.round(event.clientX - rect.left),
y: Math.round(event.clientY - rect.top),
},
- name: getActionNameFromElement(event.target, actionNameAttribute),
+ name: getActionNameFromElement(event.target, configuration, nodePrivacyLevel),
}
}
diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts
index 1fb844a800..7b75c1ea97 100644
--- a/packages/rum-core/src/domain/configuration.spec.ts
+++ b/packages/rum-core/src/domain/configuration.spec.ts
@@ -1,6 +1,10 @@
import type { InitConfiguration } from '@datadog/browser-core'
-import { DefaultPrivacyLevel, display, TraceContextInjection } from '@datadog/browser-core'
-import { EXHAUSTIVE_INIT_CONFIGURATION, SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION } from '../../../core/test'
+import { DefaultPrivacyLevel, display, ExperimentalFeature, TraceContextInjection } from '@datadog/browser-core'
+import {
+ EXHAUSTIVE_INIT_CONFIGURATION,
+ mockExperimentalFeatures,
+ SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION,
+} from '../../../core/test'
import type { ExtractTelemetryConfiguration, CamelToSnakeCase, MapInitConfigurationKey } from '../../../core/test'
import type { RumInitConfiguration } from './configuration'
import { DEFAULT_PROPAGATOR_TYPES, serializeRumConfiguration, validateAndBuildRumConfiguration } from './configuration'
@@ -326,6 +330,27 @@ describe('validateAndBuildRumConfiguration', () => {
})
})
+ describe('enablePrivacyForActionName', () => {
+ it('defaults to false', () => {
+ expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.enablePrivacyForActionName).toBeFalse()
+ })
+ it('is false when the feature is not enabled and the option is true', () => {
+ expect(
+ validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, enablePrivacyForActionName: true })!
+ .enablePrivacyForActionName
+ ).toBeFalse()
+ })
+
+ it('is only true when the feature is enabled and the option is true', () => {
+ mockExperimentalFeatures([ExperimentalFeature.ENABLE_PRIVACY_FOR_ACTION_NAME])
+ expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.enablePrivacyForActionName).toBeFalse()
+ expect(
+ validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, enablePrivacyForActionName: true })!
+ .enablePrivacyForActionName
+ ).toBeTrue()
+ })
+ })
+
describe('trackResources', () => {
it('defaults to false', () => {
expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackResources).toBeFalse()
@@ -437,6 +462,7 @@ describe('serializeRumConfiguration', () => {
traceSampleRate: 50,
traceContextInjection: TraceContextInjection.ALL,
defaultPrivacyLevel: 'allow',
+ enablePrivacyForActionName: false,
subdomain: 'foo',
sessionReplaySampleRate: 60,
startSessionReplayRecordingManually: true,
@@ -476,6 +502,7 @@ describe('serializeRumConfiguration', () => {
start_session_replay_recording_manually: true,
action_name_attribute: 'test-id',
default_privacy_level: 'allow',
+ enable_privacy_for_action_name: false,
track_resources: true,
track_long_task: true,
use_worker_url: true,
diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts
index a87e6db32a..3290a87393 100644
--- a/packages/rum-core/src/domain/configuration.ts
+++ b/packages/rum-core/src/domain/configuration.ts
@@ -11,6 +11,8 @@ import {
isPercentage,
objectHasValue,
validateAndBuildConfiguration,
+ isExperimentalFeatureEnabled,
+ ExperimentalFeature,
} from '@datadog/browser-core'
import type { RumEventDomainContext } from '../domainContext.types'
import type { RumEvent } from '../rumEvent.types'
@@ -93,7 +95,10 @@ export interface RumInitConfiguration extends InitConfiguration {
*/
startSessionReplayRecordingManually?: boolean | undefined
- // action options
+ /**
+ * Enables privacy control for action names.
+ */
+ enablePrivacyForActionName?: boolean | undefined // TODO next major: remove this option and make privacy for action name the default behavior
/**
* Enables automatic collection of users actions.
* See [Tracking User Actions](https://docs.datadoghq.com/real_user_monitoring/browser/tracking_user_actions) for further information.
@@ -132,6 +137,7 @@ export interface RumConfiguration extends Configuration {
compressIntakeRequests: boolean
applicationId: string
defaultPrivacyLevel: DefaultPrivacyLevel
+ enablePrivacyForActionName: boolean
sessionReplaySampleRate: number
startSessionReplayRecordingManually: boolean
trackUserInteractions: boolean
@@ -200,6 +206,9 @@ export function validateAndBuildRumConfiguration(
defaultPrivacyLevel: objectHasValue(DefaultPrivacyLevel, initConfiguration.defaultPrivacyLevel)
? initConfiguration.defaultPrivacyLevel
: DefaultPrivacyLevel.MASK,
+ enablePrivacyForActionName:
+ isExperimentalFeatureEnabled(ExperimentalFeature.ENABLE_PRIVACY_FOR_ACTION_NAME) &&
+ !!initConfiguration.enablePrivacyForActionName,
customerDataTelemetrySampleRate: 1,
traceContextInjection: objectHasValue(TraceContextInjection, initConfiguration.traceContextInjection)
? initConfiguration.traceContextInjection
@@ -277,6 +286,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration) {
Array.isArray(configuration.allowedTracingUrls) && configuration.allowedTracingUrls.length > 0,
selected_tracing_propagators: getSelectedTracingPropagators(configuration),
default_privacy_level: configuration.defaultPrivacyLevel,
+ enable_privacy_for_action_name: configuration.enablePrivacyForActionName,
use_excluded_activity_urls:
Array.isArray(configuration.excludedActivityUrls) && configuration.excludedActivityUrls.length > 0,
use_worker_url: !!configuration.workerUrl,
diff --git a/packages/rum/src/domain/record/privacy.spec.ts b/packages/rum-core/src/domain/privacy.spec.ts
similarity index 99%
rename from packages/rum/src/domain/record/privacy.spec.ts
rename to packages/rum-core/src/domain/privacy.spec.ts
index 1d360deb5d..2155388cf5 100644
--- a/packages/rum/src/domain/record/privacy.spec.ts
+++ b/packages/rum-core/src/domain/privacy.spec.ts
@@ -5,8 +5,11 @@ import {
PRIVACY_ATTR_VALUE_HIDDEN,
PRIVACY_ATTR_VALUE_MASK,
PRIVACY_ATTR_VALUE_MASK_USER_INPUT,
-} from '../../constants'
-import { getNodeSelfPrivacyLevel, reducePrivacyLevel, getNodePrivacyLevel, shouldMaskNode } from './privacy'
+ getNodeSelfPrivacyLevel,
+ reducePrivacyLevel,
+ getNodePrivacyLevel,
+ shouldMaskNode,
+} from './privacy'
describe('getNodePrivacyLevel', () => {
beforeEach(() => {
diff --git a/packages/rum/src/domain/record/privacy.ts b/packages/rum-core/src/domain/privacy.ts
similarity index 82%
rename from packages/rum/src/domain/record/privacy.ts
rename to packages/rum-core/src/domain/privacy.ts
index 28cabc2c49..512b65cb0b 100644
--- a/packages/rum/src/domain/record/privacy.ts
+++ b/packages/rum-core/src/domain/privacy.ts
@@ -1,18 +1,40 @@
-import { isElementNode, getParentNode, isTextNode } from '@datadog/browser-rum-core'
-import {
- NodePrivacyLevel,
- PRIVACY_ATTR_NAME,
- PRIVACY_ATTR_VALUE_ALLOW,
- PRIVACY_ATTR_VALUE_MASK,
- PRIVACY_ATTR_VALUE_MASK_USER_INPUT,
- PRIVACY_ATTR_VALUE_HIDDEN,
- PRIVACY_CLASS_ALLOW,
- PRIVACY_CLASS_MASK,
- PRIVACY_CLASS_MASK_USER_INPUT,
- PRIVACY_CLASS_HIDDEN,
- FORM_PRIVATE_TAG_NAMES,
- CENSORED_STRING_MARK,
-} from '../../constants'
+import { DefaultPrivacyLevel } from '@datadog/browser-core'
+import { isElementNode, getParentNode, isTextNode } from '../browser/htmlDomUtils'
+import { elementMatches } from '../browser/polyfills'
+
+export const NodePrivacyLevel = {
+ IGNORE: 'ignore',
+ HIDDEN: 'hidden',
+ ALLOW: DefaultPrivacyLevel.ALLOW,
+ MASK: DefaultPrivacyLevel.MASK,
+ MASK_USER_INPUT: DefaultPrivacyLevel.MASK_USER_INPUT,
+} as const
+export type NodePrivacyLevel = (typeof NodePrivacyLevel)[keyof typeof NodePrivacyLevel]
+
+export const PRIVACY_ATTR_NAME = 'data-dd-privacy'
+
+// Privacy Attrs
+export const PRIVACY_ATTR_VALUE_ALLOW = 'allow'
+export const PRIVACY_ATTR_VALUE_MASK = 'mask'
+export const PRIVACY_ATTR_VALUE_MASK_USER_INPUT = 'mask-user-input'
+export const PRIVACY_ATTR_VALUE_HIDDEN = 'hidden'
+
+// Privacy Classes - not all customers can set plain HTML attributes, so support classes too
+export const PRIVACY_CLASS_PREFIX = 'dd-privacy-'
+
+// Private Replacement Templates
+export const CENSORED_STRING_MARK = '***'
+export const CENSORED_IMG_MARK = 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=='
+
+export const FORM_PRIVATE_TAG_NAMES: { [tagName: string]: true } = {
+ INPUT: true,
+ OUTPUT: true,
+ TEXTAREA: true,
+ SELECT: true,
+ OPTION: true,
+ DATALIST: true,
+ OPTGROUP: true,
+}
const TEXT_MASKING_CHAR = 'x'
@@ -78,8 +100,6 @@ export function getNodeSelfPrivacyLevel(node: Node): NodePrivacyLevel | undefine
return
}
- const privAttr = node.getAttribute(PRIVACY_ATTR_NAME)
-
// Overrules for replay purpose
if (node.tagName === 'BASE') {
return NodePrivacyLevel.ALLOW
@@ -102,19 +122,19 @@ export function getNodeSelfPrivacyLevel(node: Node): NodePrivacyLevel | undefine
}
// Check HTML privacy attributes and classes
- if (privAttr === PRIVACY_ATTR_VALUE_HIDDEN || node.classList.contains(PRIVACY_CLASS_HIDDEN)) {
+ if (elementMatches(node, getPrivacySelector(NodePrivacyLevel.HIDDEN))) {
return NodePrivacyLevel.HIDDEN
}
- if (privAttr === PRIVACY_ATTR_VALUE_MASK || node.classList.contains(PRIVACY_CLASS_MASK)) {
+ if (elementMatches(node, getPrivacySelector(NodePrivacyLevel.MASK))) {
return NodePrivacyLevel.MASK
}
- if (privAttr === PRIVACY_ATTR_VALUE_MASK_USER_INPUT || node.classList.contains(PRIVACY_CLASS_MASK_USER_INPUT)) {
+ if (elementMatches(node, getPrivacySelector(NodePrivacyLevel.MASK_USER_INPUT))) {
return NodePrivacyLevel.MASK_USER_INPUT
}
- if (privAttr === PRIVACY_ATTR_VALUE_ALLOW || node.classList.contains(PRIVACY_CLASS_ALLOW)) {
+ if (elementMatches(node, getPrivacySelector(NodePrivacyLevel.ALLOW))) {
return NodePrivacyLevel.ALLOW
}
@@ -286,3 +306,7 @@ export function shouldIgnoreElement(element: Element): boolean {
return false
}
+
+export function getPrivacySelector(privacyLevel: string) {
+ return `[${PRIVACY_ATTR_NAME}="${privacyLevel}"], .${PRIVACY_CLASS_PREFIX}${privacyLevel}`
+}
diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts
index 379608e95a..6545182895 100644
--- a/packages/rum-core/src/index.ts
+++ b/packages/rum-core/src/index.ts
@@ -36,4 +36,5 @@ export * from './browser/htmlDomUtils'
export * from './browser/polyfills'
export { getSessionReplayUrl } from './domain/getSessionReplayUrl'
export { isLongDataUrl, sanitizeDataUrl, MAX_ATTRIBUTE_VALUE_CHAR_LENGTH } from './domain/resource/resourceUtils'
+export * from './domain/privacy'
export { SessionReplayState } from './domain/rumSessionManager'
diff --git a/packages/rum-core/src/rumEvent.types.ts b/packages/rum-core/src/rumEvent.types.ts
index 19ad95eb18..64bfdaf2ac 100644
--- a/packages/rum-core/src/rumEvent.types.ts
+++ b/packages/rum-core/src/rumEvent.types.ts
@@ -1076,6 +1076,14 @@ export type RumVitalEvent = CommonProperties &
* Name of the vital, as it is also used as facet path for its value, it must contain only letters, digits, or the characters - _ . @ $
*/
readonly name?: string
+ /**
+ * Details of the vital. It can be used as a secondary identifier (URL, React component name...)
+ */
+ readonly details?: string
+ /**
+ * Duration of the vital in nanoseconds
+ */
+ readonly duration?: number
/**
* User custom vital.
*/
diff --git a/packages/rum/src/constants.ts b/packages/rum/src/constants.ts
deleted file mode 100644
index 41006097b9..0000000000
--- a/packages/rum/src/constants.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { DefaultPrivacyLevel } from '@datadog/browser-core'
-
-export const NodePrivacyLevel = {
- IGNORE: 'ignore',
- HIDDEN: 'hidden',
- ALLOW: DefaultPrivacyLevel.ALLOW,
- MASK: DefaultPrivacyLevel.MASK,
- MASK_USER_INPUT: DefaultPrivacyLevel.MASK_USER_INPUT,
-} as const
-export type NodePrivacyLevel = (typeof NodePrivacyLevel)[keyof typeof NodePrivacyLevel]
-
-export const PRIVACY_ATTR_NAME = 'data-dd-privacy'
-
-// Privacy Attrs
-export const PRIVACY_ATTR_VALUE_ALLOW = 'allow'
-export const PRIVACY_ATTR_VALUE_MASK = 'mask'
-export const PRIVACY_ATTR_VALUE_MASK_USER_INPUT = 'mask-user-input'
-export const PRIVACY_ATTR_VALUE_HIDDEN = 'hidden'
-
-// Privacy Classes - not all customers can set plain HTML attributes, so support classes too
-export const PRIVACY_CLASS_ALLOW = 'dd-privacy-allow'
-export const PRIVACY_CLASS_MASK = 'dd-privacy-mask'
-export const PRIVACY_CLASS_MASK_USER_INPUT = 'dd-privacy-mask-user-input'
-export const PRIVACY_CLASS_HIDDEN = 'dd-privacy-hidden'
-
-// Private Replacement Templates
-export const CENSORED_STRING_MARK = '***'
-export const CENSORED_IMG_MARK = 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=='
-
-export const FORM_PRIVATE_TAG_NAMES: { [tagName: string]: true } = {
- INPUT: true,
- OUTPUT: true,
- TEXTAREA: true,
- SELECT: true,
- OPTION: true,
- DATALIST: true,
- OPTGROUP: true,
-}
diff --git a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts
index 3817d8d783..c50cc71ca9 100644
--- a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts
+++ b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts
@@ -1,8 +1,8 @@
import type { RumConfiguration } from '@datadog/browser-rum-core'
+import { NodePrivacyLevel, PRIVACY_ATTR_NAME } from '@datadog/browser-rum-core'
import { display, noop, objectValues } from '@datadog/browser-core'
import type { SerializedNodeWithId } from '../../../types'
import { serializeNodeWithId, SerializationContextStatus, createElementsScrollPositions } from '..'
-import { NodePrivacyLevel, PRIVACY_ATTR_NAME } from '../../../constants'
export const makeHtmlDoc = (htmlContent: string, privacyTag: string) => {
try {
diff --git a/packages/rum/src/domain/record/serialization/serialization.types.ts b/packages/rum/src/domain/record/serialization/serialization.types.ts
index bc5a35207c..452aee1a03 100644
--- a/packages/rum/src/domain/record/serialization/serialization.types.ts
+++ b/packages/rum/src/domain/record/serialization/serialization.types.ts
@@ -1,5 +1,4 @@
-import type { RumConfiguration } from '@datadog/browser-rum-core'
-import type { NodePrivacyLevel } from '../../../constants'
+import type { RumConfiguration, NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { ElementsScrollPositions } from '../elementsScrollPositions'
import type { ShadowRootsController } from '../shadowRootsController'
diff --git a/packages/rum/src/domain/record/serialization/serializationUtils.spec.ts b/packages/rum/src/domain/record/serialization/serializationUtils.spec.ts
index 18cac235b4..6ee0f8ced8 100644
--- a/packages/rum/src/domain/record/serialization/serializationUtils.spec.ts
+++ b/packages/rum/src/domain/record/serialization/serializationUtils.spec.ts
@@ -1,5 +1,5 @@
import { isIE } from '@datadog/browser-core'
-import { NodePrivacyLevel } from '../../../constants'
+import { NodePrivacyLevel } from '@datadog/browser-rum-core'
import {
getSerializedNodeId,
hasSerializedNode,
diff --git a/packages/rum/src/domain/record/serialization/serializationUtils.ts b/packages/rum/src/domain/record/serialization/serializationUtils.ts
index 218aa564b2..57f6ac6dba 100644
--- a/packages/rum/src/domain/record/serialization/serializationUtils.ts
+++ b/packages/rum/src/domain/record/serialization/serializationUtils.ts
@@ -1,8 +1,6 @@
import { buildUrl } from '@datadog/browser-core'
-import { getParentNode, isNodeShadowRoot } from '@datadog/browser-rum-core'
-import type { NodePrivacyLevel } from '../../../constants'
-import { CENSORED_STRING_MARK } from '../../../constants'
-import { shouldMaskNode } from '../privacy'
+import { getParentNode, isNodeShadowRoot, CENSORED_STRING_MARK, shouldMaskNode } from '@datadog/browser-rum-core'
+import type { NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { NodeWithSerializedNode } from './serialization.types'
const serializedNodeIds = new WeakMap()
diff --git a/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts b/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts
index ef09ff78a6..5163bb2ee7 100644
--- a/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts
+++ b/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts
@@ -5,8 +5,9 @@ import {
STABLE_ATTRIBUTES,
DEFAULT_PROGRAMMATIC_ACTION_NAME_ATTRIBUTE,
MAX_ATTRIBUTE_VALUE_CHAR_LENGTH,
+ NodePrivacyLevel,
+ PRIVACY_ATTR_NAME,
} from '@datadog/browser-rum-core'
-import { NodePrivacyLevel, PRIVACY_ATTR_NAME } from '../../../constants'
import { serializeAttribute } from './serializeAttribute'
const DEFAULT_CONFIGURATION = {} as RumConfiguration
diff --git a/packages/rum/src/domain/record/serialization/serializeAttribute.ts b/packages/rum/src/domain/record/serialization/serializeAttribute.ts
index 0a066088e7..dcbbcfc65b 100644
--- a/packages/rum/src/domain/record/serialization/serializeAttribute.ts
+++ b/packages/rum/src/domain/record/serialization/serializeAttribute.ts
@@ -1,7 +1,14 @@
import { startsWith } from '@datadog/browser-core'
-import { STABLE_ATTRIBUTES, isLongDataUrl, sanitizeDataUrl } from '@datadog/browser-rum-core'
+import {
+ NodePrivacyLevel,
+ PRIVACY_ATTR_NAME,
+ CENSORED_STRING_MARK,
+ CENSORED_IMG_MARK,
+ STABLE_ATTRIBUTES,
+ isLongDataUrl,
+ sanitizeDataUrl,
+} from '@datadog/browser-rum-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
-import { NodePrivacyLevel, PRIVACY_ATTR_NAME, CENSORED_STRING_MARK, CENSORED_IMG_MARK } from '../../../constants'
import { censoredImageForSize } from './serializationUtils'
export function serializeAttribute(
diff --git a/packages/rum/src/domain/record/serialization/serializeAttributes.ts b/packages/rum/src/domain/record/serialization/serializeAttributes.ts
index b6465adf78..c1111eca97 100644
--- a/packages/rum/src/domain/record/serialization/serializeAttributes.ts
+++ b/packages/rum/src/domain/record/serialization/serializeAttributes.ts
@@ -1,6 +1,5 @@
+import { NodePrivacyLevel, shouldMaskNode } from '@datadog/browser-rum-core'
import { isSafari } from '@datadog/browser-core'
-import { NodePrivacyLevel } from '../../../constants'
-import { shouldMaskNode } from '../privacy'
import { getElementInputValue, switchToAbsoluteUrl, getValidTagName } from './serializationUtils'
import type { SerializeOptions } from './serialization.types'
import { SerializationContextStatus } from './serialization.types'
diff --git a/packages/rum/src/domain/record/serialization/serializeNode.spec.ts b/packages/rum/src/domain/record/serialization/serializeNode.spec.ts
index 42f4e97d09..3e76a7bd1f 100644
--- a/packages/rum/src/domain/record/serialization/serializeNode.spec.ts
+++ b/packages/rum/src/domain/record/serialization/serializeNode.spec.ts
@@ -8,7 +8,7 @@ import {
PRIVACY_ATTR_VALUE_HIDDEN,
PRIVACY_ATTR_VALUE_MASK,
PRIVACY_ATTR_VALUE_MASK_USER_INPUT,
-} from '../../../constants'
+} from '@datadog/browser-rum-core'
import type { ElementNode, SerializedNodeWithId } from '../../../types'
import { NodeType } from '../../../types'
import { appendElement } from '../../../../../rum-core/test'
diff --git a/packages/rum/src/domain/record/serialization/serializeNode.ts b/packages/rum/src/domain/record/serialization/serializeNode.ts
index cfde247201..84726c55bc 100644
--- a/packages/rum/src/domain/record/serialization/serializeNode.ts
+++ b/packages/rum/src/domain/record/serialization/serializeNode.ts
@@ -1,4 +1,14 @@
-import { isNodeShadowRoot, hasChildNodes, forEachChildNodes } from '@datadog/browser-rum-core'
+import {
+ reducePrivacyLevel,
+ getNodeSelfPrivacyLevel,
+ getTextContent,
+ isNodeShadowRoot,
+ hasChildNodes,
+ forEachChildNodes,
+ NodePrivacyLevel,
+ PRIVACY_ATTR_NAME,
+ PRIVACY_ATTR_VALUE_HIDDEN,
+} from '@datadog/browser-rum-core'
import { assign } from '@datadog/browser-core'
import type {
DocumentFragmentNode,
@@ -11,8 +21,6 @@ import type {
TextNode,
} from '../../../types'
import { NodeType } from '../../../types'
-import { NodePrivacyLevel, PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_HIDDEN } from '../../../constants'
-import { reducePrivacyLevel, getNodeSelfPrivacyLevel, getTextContent } from '../privacy'
import { getSerializedNodeId, getValidTagName, setSerializedNodeId } from './serializationUtils'
import type { SerializeOptions } from './serialization.types'
import { serializeStyleSheets } from './serializeStyleSheets'
diff --git a/packages/rum/src/domain/record/trackers/trackInput.spec.ts b/packages/rum/src/domain/record/trackers/trackInput.spec.ts
index f7986d59b6..394fe11d12 100644
--- a/packages/rum/src/domain/record/trackers/trackInput.spec.ts
+++ b/packages/rum/src/domain/record/trackers/trackInput.spec.ts
@@ -2,8 +2,8 @@ import { DefaultPrivacyLevel, isIE } from '@datadog/browser-core'
import type { Clock } from '@datadog/browser-core/test'
import { createNewEvent, mockClock } from '@datadog/browser-core/test'
import type { RumConfiguration } from '@datadog/browser-rum-core'
+import { PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT } from '@datadog/browser-rum-core'
import { appendElement } from '../../../../../rum-core/test'
-import { PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT } from '../../../constants'
import { serializeDocument, SerializationContextStatus } from '../serialization'
import { createElementsScrollPositions } from '../elementsScrollPositions'
import { IncrementalSource, RecordType } from '../../../types'
diff --git a/packages/rum/src/domain/record/trackers/trackInput.ts b/packages/rum/src/domain/record/trackers/trackInput.ts
index 2e5232eb11..c1334e069b 100644
--- a/packages/rum/src/domain/record/trackers/trackInput.ts
+++ b/packages/rum/src/domain/record/trackers/trackInput.ts
@@ -1,11 +1,9 @@
import { instrumentSetter, assign, DOM_EVENT, addEventListeners, forEach, noop } from '@datadog/browser-core'
-import { cssEscape } from '@datadog/browser-rum-core'
+import { NodePrivacyLevel, getNodePrivacyLevel, shouldMaskNode, cssEscape } from '@datadog/browser-rum-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
-import { NodePrivacyLevel } from '../../../constants'
import { IncrementalSource } from '../../../types'
import type { BrowserIncrementalSnapshotRecord, InputData, InputState } from '../../../types'
import { getEventTarget } from '../eventsUtils'
-import { getNodePrivacyLevel, shouldMaskNode } from '../privacy'
import { getElementInputValue, getSerializedNodeId, hasSerializedNode } from '../serialization'
import { assembleIncrementalSnapshot } from '../assembly'
import type { Tracker } from './types'
diff --git a/packages/rum/src/domain/record/trackers/trackMediaInteraction.ts b/packages/rum/src/domain/record/trackers/trackMediaInteraction.ts
index 899d443e81..13afc94fac 100644
--- a/packages/rum/src/domain/record/trackers/trackMediaInteraction.ts
+++ b/packages/rum/src/domain/record/trackers/trackMediaInteraction.ts
@@ -1,10 +1,9 @@
import { DOM_EVENT, addEventListeners } from '@datadog/browser-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
-import { NodePrivacyLevel } from '../../../constants'
+import { NodePrivacyLevel, getNodePrivacyLevel } from '@datadog/browser-rum-core'
import type { BrowserIncrementalSnapshotRecord, MediaInteractionData } from '../../../types'
import { IncrementalSource, MediaInteractionType } from '../../../types'
import { getEventTarget } from '../eventsUtils'
-import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import { assembleIncrementalSnapshot } from '../assembly'
import type { Tracker } from './types'
diff --git a/packages/rum/src/domain/record/trackers/trackMouseInteraction.ts b/packages/rum/src/domain/record/trackers/trackMouseInteraction.ts
index 5cfe033b98..b5ad0895cd 100644
--- a/packages/rum/src/domain/record/trackers/trackMouseInteraction.ts
+++ b/packages/rum/src/domain/record/trackers/trackMouseInteraction.ts
@@ -1,11 +1,10 @@
import { assign, addEventListeners, DOM_EVENT } from '@datadog/browser-core'
+import { getNodePrivacyLevel, NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
-import { NodePrivacyLevel } from '../../../constants'
import type { MouseInteraction, MouseInteractionData, BrowserIncrementalSnapshotRecord } from '../../../types'
import { IncrementalSource, MouseInteractionType } from '../../../types'
import { assembleIncrementalSnapshot } from '../assembly'
import { getEventTarget } from '../eventsUtils'
-import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import type { RecordIds } from '../recordIds'
import { tryToComputeCoordinates } from './trackMove'
diff --git a/packages/rum/src/domain/record/trackers/trackMutation.spec.ts b/packages/rum/src/domain/record/trackers/trackMutation.spec.ts
index 312cdbbcc9..bd3286556b 100644
--- a/packages/rum/src/domain/record/trackers/trackMutation.spec.ts
+++ b/packages/rum/src/domain/record/trackers/trackMutation.spec.ts
@@ -1,14 +1,14 @@
import { DefaultPrivacyLevel, isIE } from '@datadog/browser-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
import { collectAsyncCalls } from '@datadog/browser-core/test'
-import { createMutationPayloadValidator } from '../../../../test'
import {
NodePrivacyLevel,
PRIVACY_ATTR_NAME,
PRIVACY_ATTR_VALUE_ALLOW,
PRIVACY_ATTR_VALUE_MASK,
PRIVACY_ATTR_VALUE_MASK_USER_INPUT,
-} from '../../../constants'
+} from '@datadog/browser-rum-core'
+import { createMutationPayloadValidator } from '../../../../test'
import type { AttributeMutation, Attributes, BrowserMutationPayload } from '../../../types'
import { NodeType } from '../../../types'
import { serializeDocument, SerializationContextStatus } from '../serialization'
diff --git a/packages/rum/src/domain/record/trackers/trackMutation.ts b/packages/rum/src/domain/record/trackers/trackMutation.ts
index 2463478917..5b705b5836 100644
--- a/packages/rum/src/domain/record/trackers/trackMutation.ts
+++ b/packages/rum/src/domain/record/trackers/trackMutation.ts
@@ -1,12 +1,14 @@
import { monitor, noop } from '@datadog/browser-core'
-import type { RumConfiguration } from '@datadog/browser-rum-core'
+import type { RumConfiguration, NodePrivacyLevelCache } from '@datadog/browser-rum-core'
import {
isNodeShadowHost,
getMutationObserverConstructor,
getParentNode,
forEachChildNodes,
+ getNodePrivacyLevel,
+ getTextContent,
+ NodePrivacyLevel,
} from '@datadog/browser-rum-core'
-import { NodePrivacyLevel } from '../../../constants'
import { IncrementalSource } from '../../../types'
import type {
BrowserMutationData,
@@ -16,8 +18,6 @@ import type {
TextMutation,
BrowserIncrementalSnapshotRecord,
} from '../../../types'
-import type { NodePrivacyLevelCache } from '../privacy'
-import { getNodePrivacyLevel, getTextContent } from '../privacy'
import type { NodeWithSerializedNode } from '../serialization'
import {
getElementInputValue,
diff --git a/packages/rum/src/domain/record/trackers/trackScroll.ts b/packages/rum/src/domain/record/trackers/trackScroll.ts
index b97430612b..28aa1ac25e 100644
--- a/packages/rum/src/domain/record/trackers/trackScroll.ts
+++ b/packages/rum/src/domain/record/trackers/trackScroll.ts
@@ -1,13 +1,11 @@
import { DOM_EVENT, throttle, addEventListener } from '@datadog/browser-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
-import { getScrollX, getScrollY } from '@datadog/browser-rum-core'
+import { getScrollX, getScrollY, getNodePrivacyLevel, NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { ElementsScrollPositions } from '../elementsScrollPositions'
import { getEventTarget } from '../eventsUtils'
-import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import { IncrementalSource } from '../../../types'
import type { BrowserIncrementalSnapshotRecord, ScrollData } from '../../../types'
-import { NodePrivacyLevel } from '../../../constants'
import { assembleIncrementalSnapshot } from '../assembly'
import type { Tracker } from './types'
diff --git a/packages/rum/src/domain/record/trackers/trackers.specHelper.ts b/packages/rum/src/domain/record/trackers/trackers.specHelper.ts
index c51f934bf7..3c4c188319 100644
--- a/packages/rum/src/domain/record/trackers/trackers.specHelper.ts
+++ b/packages/rum/src/domain/record/trackers/trackers.specHelper.ts
@@ -1,7 +1,7 @@
import { noop } from '@datadog/browser-core'
+import { NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
import type { ShadowRootsController } from '../shadowRootsController'
-import { NodePrivacyLevel } from '../../../constants'
export const DEFAULT_SHADOW_ROOT_CONTROLLER: ShadowRootsController = {
flush: noop,
diff --git a/packages/rum/src/entries/internal.ts b/packages/rum/src/entries/internal.ts
index 8b78b62997..93eb81d565 100644
--- a/packages/rum/src/entries/internal.ts
+++ b/packages/rum/src/entries/internal.ts
@@ -6,7 +6,12 @@
* changes.
*/
export type { TimeStamp } from '@datadog/browser-core'
-export { PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_HIDDEN, PRIVACY_CLASS_HIDDEN, NodePrivacyLevel } from '../constants'
+export {
+ PRIVACY_ATTR_NAME,
+ PRIVACY_ATTR_VALUE_HIDDEN,
+ PRIVACY_CLASS_PREFIX,
+ NodePrivacyLevel,
+} from '@datadog/browser-rum-core'
export * from '../types'
diff --git a/rum-events-format b/rum-events-format
index 30d4b773ab..748e69d37c 160000
--- a/rum-events-format
+++ b/rum-events-format
@@ -1 +1 @@
-Subproject commit 30d4b773abb4e33edc9d6053d3c12cd302e948a5
+Subproject commit 748e69d37c48645802918f35e7cb4557facde571