From 9cf7731d315341dcae1cc9462e52399a8aa3c230 Mon Sep 17 00:00:00 2001 From: "seth.fowler" Date: Thu, 20 Aug 2026 17:52:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20[PANA-8578]=20Update=20schema=20?= =?UTF-8?q?to=20support=20session=20replay=20string=20roles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- .../domain/telemetry/telemetryEvent.types.ts | 49 ++++++- .../browser-rum-core/src/rumEvent.types.ts | 33 ++++- .../record/serialization/changeDecoder.ts | 6 + .../record/serialization/stringTable.ts | 17 ++- .../browser-rum/src/types/sessionReplay.ts | 124 ++++++++++++++++-- .../src/types/sessionReplayConstants.ts | 6 + packages/browser-rum/test/record/elements.ts | 5 +- yarn.lock | 8 +- 9 files changed, 220 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 2bfe65be0f..03ebf3ec1a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "woke": "scripts/cli woke" }, "devDependencies": { - "@datadog/rum-events-format": "DataDog/rum-events-format#commit=e18f1d3b6a018f00061239bf14fbfb748c9538a3", + "@datadog/rum-events-format": "DataDog/rum-events-format#commit=131bf26801ca82202df1ef5e0124551cdb656410", "@eslint/js": "10.0.1", "@jsdevtools/coverage-istanbul-loader": "3.0.5", "@microsoft/api-extractor": "7.58.12", diff --git a/packages/browser-core/src/domain/telemetry/telemetryEvent.types.ts b/packages/browser-core/src/domain/telemetry/telemetryEvent.types.ts index dac746d2a6..4ca74a71b0 100644 --- a/packages/browser-core/src/domain/telemetry/telemetryEvent.types.ts +++ b/packages/browser-core/src/domain/telemetry/telemetryEvent.types.ts @@ -463,6 +463,36 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & { * Whether a proxy is used for remote configuration */ use_remote_configuration_proxy?: boolean + /** + * Metadata of the remote configuration currently applied for this session + */ + remote_configuration?: { + /** + * Identifier of the remote configuration bundle this metadata belongs to + */ + config_id?: string + /** + * CDN version identifier of the applied configuration + */ + version_id?: string + /** + * CDN publish timestamp of the applied configuration, in ms from epoch + */ + last_modified?: number + /** + * Timestamp at which the device fetched and cached this configuration version, in ms from epoch + */ + last_synced?: number + /** + * Timestamp at which this configuration version was first observed as applied by the device, in ms from epoch. Stamped once and reused on every subsequent session that runs on the same version + */ + first_applied?: number + /** + * Identifier of the sync that produced this configuration version, used to deduplicate repeat sessions from the same device without a persistent identifier + */ + sync_id?: string + [k: string]: unknown + } /** * The percentage of sessions with Profiling enabled */ @@ -487,6 +517,14 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & { * Whether the beta track WebSockets feature is enabled */ beta_track_web_sockets?: boolean + /** + * Whether tracing feature's client-side-stats generation is enabled + */ + use_client_side_stats?: boolean + /** + * Whether trace sampling rules are configured + */ + use_trace_sampling_rules?: boolean [k: string]: unknown } [k: string]: unknown @@ -558,7 +596,7 @@ export type TelemetryBrowserFeaturesUsage = /** * Schema of mobile specific features usage */ -export type TelemetryMobileFeaturesUsage = TrackWebView | AndroidNetworkInstrumentation +export type TelemetryMobileFeaturesUsage = TrackWebView | Timeseries | AndroidNetworkInstrumentation /** * Schema of common properties of Telemetry events @@ -598,7 +636,7 @@ export interface CommonTelemetryProperties { | 'unity' | 'kotlin-multiplatform' | 'electron' - | 'rum-cpp' + | 'cpp' | 'maui' /** * The version of the SDK generating the telemetry event @@ -995,6 +1033,13 @@ export interface TrackWebView { feature: 'trackWebView' [k: string]: unknown } +export interface Timeseries { + /** + * Timeseries tracking enabled + */ + feature: 'timeseries' + [k: string]: unknown +} export interface AndroidNetworkInstrumentation { /** * Android network instrumentation diff --git a/packages/browser-rum-core/src/rumEvent.types.ts b/packages/browser-rum-core/src/rumEvent.types.ts index dcdabc0c1b..c79bdd1e92 100644 --- a/packages/browser-rum-core/src/rumEvent.types.ts +++ b/packages/browser-rum-core/src/rumEvent.types.ts @@ -104,7 +104,7 @@ export type RumActionEvent = CommonProperties & /** * View properties */ - readonly view?: { + readonly view: { /** * Is the action starting in the foreground (focus in browser) */ @@ -178,6 +178,9 @@ export type RumTransitionEvent = CommonProperties & { * RUM event type */ readonly type: 'transition' + readonly view: { + [k: string]: unknown + } /** * Stream properties */ @@ -487,7 +490,7 @@ export type RumErrorEvent = CommonProperties & /** * View properties */ - readonly view?: { + readonly view: { /** * Is the error starting in the foreground (focus in browser) */ @@ -536,6 +539,9 @@ export type RumLongTaskEvent = CommonProperties & * RUM event type */ readonly type: 'long_task' + readonly view: { + [k: string]: unknown + } /** * Long Task properties */ @@ -669,6 +675,9 @@ export type RumResourceEvent = CommonProperties & * RUM event type */ readonly type: 'resource' + readonly view: { + [k: string]: unknown + } /** * Resource properties */ @@ -824,6 +833,10 @@ export type RumResourceEvent = CommonProperties & * Delivery type of the resource */ readonly delivery_type?: 'cache' | 'navigational-prefetch' | 'other' + /** + * Whether the resource was served from the device's local cache + */ + readonly local_cache_hit?: boolean /** * The provider for this resource */ @@ -943,6 +956,9 @@ export type RumViewUpdateEvent = ViewContainerSchema & * RUM event type */ readonly type: 'view_update' + readonly view: { + [k: string]: unknown + } [k: string]: unknown } export type RumVitalEvent = RumVitalDurationEvent | RumVitalOperationStepEvent @@ -975,6 +991,9 @@ export type RumVitalEventCommonProperties = CommonProperties & * RUM event type */ readonly type: 'vital' + readonly view: { + [k: string]: unknown + } /** * Vital properties */ @@ -1107,12 +1126,12 @@ export interface CommonProperties { | 'unity' | 'kotlin-multiplatform' | 'electron' - | 'rum-cpp' + | 'cpp' | 'maui' /** * View properties */ - readonly view: { + readonly view?: { /** * UUID of the view */ @@ -1392,6 +1411,10 @@ export interface CommonProperties { * The percentage of sessions with traced resources */ readonly trace_sample_rate?: number + /** + * Session Replay experimental features enabled in the SDK configuration + */ + readonly session_replay_experimental_features?: string[] [k: string]: unknown } /** @@ -1453,7 +1476,7 @@ export interface ViewContainerSchema { | 'unity' | 'kotlin-multiplatform' | 'electron' - | 'rum-cpp' + | 'cpp' | 'maui' [k: string]: unknown } diff --git a/packages/browser-rum/src/domain/record/serialization/changeDecoder.ts b/packages/browser-rum/src/domain/record/serialization/changeDecoder.ts index 95cfd35461..64de29067b 100644 --- a/packages/browser-rum/src/domain/record/serialization/changeDecoder.ts +++ b/packages/browser-rum/src/domain/record/serialization/changeDecoder.ts @@ -114,6 +114,12 @@ function decodeChangeRecord( break } + case ChangeType.AddRoleAnnotatedStrings: + case ChangeType.InputValue: + case ChangeType.InputSelection: + // These change types exist in the schema, but nothing generates them yet. + throw new Error(`Unsupported ChangeType: ${change[0]}`) + default: change satisfies never throw new Error(`Unsupported ChangeType: ${change[0] as any}`) diff --git a/packages/browser-rum/src/domain/record/serialization/stringTable.ts b/packages/browser-rum/src/domain/record/serialization/stringTable.ts index 4bc9a10363..782c7774bf 100644 --- a/packages/browser-rum/src/domain/record/serialization/stringTable.ts +++ b/packages/browser-rum/src/domain/record/serialization/stringTable.ts @@ -1,8 +1,9 @@ +import type { StringOrStringReference } from '../../../types' import type { StringId } from '../itemIds' export interface StringTable { add(newString: string): void - decode(stringOrStringId: number | string): string + decode(value: StringOrStringReference): string } export function createStringTable(): StringTable { @@ -11,13 +12,17 @@ export function createStringTable(): StringTable { add(newString: string): void { strings.set(strings.size as StringId, newString) }, - decode(stringOrStringId: number | string): string { - if (typeof stringOrStringId === 'string') { - return stringOrStringId + decode(value: StringOrStringReference): string { + if (typeof value === 'string') { + return value // A plain string literal. } - const referencedString = strings.get(stringOrStringId as StringId) + if (typeof value === 'object') { + return value.string // A role-annotated string literal. + } + + const referencedString = strings.get(value as StringId) if (referencedString === undefined) { - throw new Error(`Reference to unknown string: ${stringOrStringId}`) + throw new Error(`Reference to unknown string: ${value}`) } return referencedString }, diff --git a/packages/browser-rum/src/types/sessionReplay.ts b/packages/browser-rum/src/types/sessionReplay.ts index 78b49bb89d..d148eb9c49 100644 --- a/packages/browser-rum/src/types/sessionReplay.ts +++ b/packages/browser-rum/src/types/sessionReplay.ts @@ -113,8 +113,11 @@ export type Change = | [8, ...AttachedStyleSheetsChange[]] | [9, ...MediaPlaybackStateChange[]] | [10, ...VisualViewportChange[]] + | [11, ...AddRoleAnnotatedStringsChange[]] + | [12, ...InputValueChange[]] + | [13, ...InputSelectionChange[]] /** - * Browser-specific. Schema representing the addition of a string to the string table. + * Browser-specific. Schema representing the addition of a string to the string table, annotated as belonging to the default string role. */ export type AddStringChange = string /** @@ -133,7 +136,10 @@ export type AddNodeChange = * * @minItems 2 */ -export type AddCDataSectionNodeChange = [InsertionPoint, '#cdata-section' | StringReference] +export type AddCDataSectionNodeChange = [ + InsertionPoint, + '#cdata-section' | { role: 1; string: '#cdata-section' } | StringReference, +] /** * Browser-specific. Schema representing the insertion point of a node which is being added to the document. */ @@ -166,33 +172,84 @@ export type StringReference = number */ export type AddDocTypeNodeChange = [ InsertionPoint, - '#doctype' | StringReference, + '#doctype' | { role: 1; string: '#doctype' } | StringReference, StringOrStringReference, StringOrStringReference, StringOrStringReference, ] /** - * Browser-specific. Schema representing a string, either expressed as a literal or as an index into the string table. + * Browser-specific. Schema representing a string, either expressed as a literal, as a literal with an associated string role, or as an index into the string table. + */ +export type StringOrStringReference = StringLiteral | RoleAnnotatedStringLiteral | StringReference +/** + * Browser-specific. Schema representing a string, expressed as a literal. + */ +export type StringLiteral = string +/** + * Browser-specific. Schema representing a string role. + */ +export type StringRoleId = + | StringRoleDefault + | StringRoleNodeName + | StringRoleAttributeName + | StringRoleAttributeValue + | StringRoleTextContent + | StringRoleFormInput + | StringRoleCSS + | StringRoleURL +/** + * The default string role, used for uncategorized strings. Strings added by an AddStringChange are added to this string role. + */ +export type StringRoleDefault = 0 +/** + * A string role containing DOM node names (e.g. 'div', '#text'). + */ +export type StringRoleNodeName = 1 +/** + * A string role containing DOM attribute names. + */ +export type StringRoleAttributeName = 2 +/** + * A string role containing DOM attribute values. + */ +export type StringRoleAttributeValue = 3 +/** + * A string role containing DOM text content. + */ +export type StringRoleTextContent = 4 +/** + * A string role containing DOM form input values. + */ +export type StringRoleFormInput = 5 +/** + * A string role containing CSS inline styles and stylesheets. */ -export type StringOrStringReference = string | StringReference +export type StringRoleCSS = 6 +/** + * A string role containing URLs. + */ +export type StringRoleURL = 7 /** * Schema representing the addition of a new #document node. * * @minItems 2 */ -export type AddDocumentNodeChange = [InsertionPoint, '#document' | StringReference] +export type AddDocumentNodeChange = [InsertionPoint, '#document' | { role: 1; string: '#document' } | StringReference] /** * Schema representing the addition of a new #document-fragment node. * * @minItems 2 */ -export type AddDocumentFragmentNodeChange = [InsertionPoint, '#document-fragment' | StringReference] +export type AddDocumentFragmentNodeChange = [ + InsertionPoint, + '#document-fragment' | { role: 1; string: '#document-fragment' } | StringReference, +] /** * Schema representing the addition of a new element node. * * @minItems 2 */ -export type AddElementNodeChange = [InsertionPoint, string | StringReference, ...AttributeAssignment[]] +export type AddElementNodeChange = [InsertionPoint, StringOrStringReference, ...AttributeAssignment[]] /** * Schema representing an assignment of a value to an attribute. The format is [name, value]. * @@ -204,13 +261,20 @@ export type AttributeAssignment = [StringOrStringReference, StringOrStringRefere * * @minItems 2 */ -export type AddShadowRootNodeChange = [InsertionPoint, '#shadow-root' | StringReference] +export type AddShadowRootNodeChange = [ + InsertionPoint, + '#shadow-root' | { role: 1; string: '#shadow-root' } | StringReference, +] /** * Schema representing the addition of a new #text node. * * @minItems 3 */ -export type AddTextNodeChange = [InsertionPoint, '#text' | StringReference, StringOrStringReference] +export type AddTextNodeChange = [ + InsertionPoint, + '#text' | { role: 1; string: '#text' } | StringReference, + StringOrStringReference, +] /** * Browser-specific. Schema representing the removal of a node from the document. */ @@ -338,6 +402,39 @@ export type VisualViewportHeight = number * The pinch-zoom scaling factor applied to the visual viewport. */ export type VisualViewportScale = number +/** + * Browser-specific. Schema representing the addition of a sequence of strings to the string table, annotated as belonging to a particular string role. + * + * @minItems 1 + */ +export type AddRoleAnnotatedStringsChange = [StringRoleId, ...StringLiteral[]] +/** + * Browser-specific. Schema representing a change to the value of a 'value-serializable' form input element, where 'value-serializable' means that the element's state can be reconstructed from its 'value' property. + * + * @minItems 2 + */ +export type InputValueChange = [NodeId, StringOrStringReference] +/** + * Browser-specific. Schema representing a change to the selection state of one or more 'selection-driven' form input elements; selection-driven inputs include checkboxes, radio buttons, and , the change targets the