Skip to content

Commit 562214c

Browse files
authored
✨[RUM-10960][Remote config] add support for new static fields (#3731)
* ⬆️ synchronize remote config schemas * ✨ Parse and apply new configuration fields
1 parent e74c3e1 commit 562214c

File tree

4 files changed

+211
-8
lines changed

4 files changed

+211
-8
lines changed

packages/rum-core/src/domain/configuration/remoteConfiguration.spec.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { DefaultPrivacyLevel, INTAKE_SITE_US1 } from '@datadog/browser-core'
1+
import { DefaultPrivacyLevel, INTAKE_SITE_US1, display } from '@datadog/browser-core'
22
import { interceptRequests } from '@datadog/browser-core/test'
33
import type { RumInitConfiguration } from './configuration'
4-
import type { RemoteConfiguration } from './remoteConfiguration'
4+
import type { RumRemoteConfiguration } from './remoteConfiguration'
55
import { applyRemoteConfiguration, buildEndpoint, fetchRemoteConfiguration } from './remoteConfiguration'
66

77
const DEFAULT_INIT_CONFIGURATION: RumInitConfiguration = {
@@ -91,11 +91,33 @@ describe('remoteConfiguration', () => {
9191
})
9292

9393
describe('applyRemoteConfiguration', () => {
94+
let displaySpy: jasmine.Spy
95+
96+
beforeEach(() => {
97+
displaySpy = spyOn(display, 'error')
98+
})
99+
94100
it('should override the initConfiguration options with the ones from the remote configuration', () => {
95-
const rumRemoteConfiguration: RemoteConfiguration['rum'] = {
101+
const rumRemoteConfiguration: RumRemoteConfiguration = {
96102
applicationId: 'yyy',
97103
sessionSampleRate: 1,
98104
sessionReplaySampleRate: 1,
105+
traceSampleRate: 1,
106+
trackSessionAcrossSubdomains: true,
107+
allowedTrackingOrigins: [
108+
{ rcSerializedType: 'string', value: 'https://example.com' },
109+
{ rcSerializedType: 'regex', value: '^https:\\/\\/app-\\w+\\.datadoghq\\.com' },
110+
],
111+
allowedTracingUrls: [
112+
{
113+
match: { rcSerializedType: 'string', value: 'https://example.com' },
114+
propagatorTypes: ['b3', 'tracecontext'],
115+
},
116+
{
117+
match: { rcSerializedType: 'regex', value: '^https:\\/\\/app-\\w+\\.datadoghq\\.com' },
118+
propagatorTypes: ['datadog', 'b3multi'],
119+
},
120+
],
99121
defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW,
100122
}
101123
expect(applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration)).toEqual({
@@ -104,9 +126,42 @@ describe('remoteConfiguration', () => {
104126
service: 'xxx',
105127
sessionSampleRate: 1,
106128
sessionReplaySampleRate: 1,
129+
traceSampleRate: 1,
130+
trackSessionAcrossSubdomains: true,
131+
allowedTrackingOrigins: ['https://example.com', /^https:\/\/app-\w+\.datadoghq\.com/],
132+
allowedTracingUrls: [
133+
{ match: 'https://example.com', propagatorTypes: ['b3', 'tracecontext'] },
134+
{ match: /^https:\/\/app-\w+\.datadoghq\.com/, propagatorTypes: ['datadog', 'b3multi'] },
135+
],
107136
defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW,
108137
})
109138
})
139+
140+
it('should display an error if the remote config contains invalid regex', () => {
141+
const rumRemoteConfiguration: RumRemoteConfiguration = {
142+
applicationId: 'yyy',
143+
allowedTrackingOrigins: [{ rcSerializedType: 'regex', value: 'Hello(?|!)' }],
144+
}
145+
expect(applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration)).toEqual({
146+
...DEFAULT_INIT_CONFIGURATION,
147+
applicationId: 'yyy',
148+
allowedTrackingOrigins: [undefined as any],
149+
})
150+
expect(displaySpy).toHaveBeenCalledWith("Invalid regex in the remote configuration: 'Hello(?|!)'")
151+
})
152+
153+
it('should display an error if an unsupported `rcSerializedType` is provided', () => {
154+
const rumRemoteConfiguration: RumRemoteConfiguration = {
155+
applicationId: 'yyy',
156+
allowedTrackingOrigins: [{ rcSerializedType: 'foo' as any, value: 'bar' }],
157+
}
158+
expect(applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration)).toEqual({
159+
...DEFAULT_INIT_CONFIGURATION,
160+
applicationId: 'yyy',
161+
allowedTrackingOrigins: [undefined as any],
162+
})
163+
expect(displaySpy).toHaveBeenCalledWith('Unsupported remote configuration: "rcSerializedType": "foo"')
164+
})
110165
})
111166

112167
describe('buildEndpoint', () => {

packages/rum-core/src/domain/configuration/remoteConfiguration.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { display, buildEndpointHost } from '@datadog/browser-core'
1+
import { display, buildEndpointHost, mapValues } from '@datadog/browser-core'
22
import type { RumInitConfiguration } from './configuration'
33
import type { RumSdkConfig } from './remoteConfiguration.types'
44

55
export type RemoteConfiguration = RumSdkConfig
6-
type RumRemoteConfiguration = Exclude<RemoteConfiguration['rum'], undefined>
6+
export type RumRemoteConfiguration = Exclude<RemoteConfiguration['rum'], undefined>
77
const REMOTE_CONFIGURATION_VERSION = 'v1'
8-
const STATIC_OPTIONS: Array<keyof RumInitConfiguration> = [
8+
const SUPPORTED_FIELDS: Array<keyof RumInitConfiguration> = [
99
'applicationId',
1010
'service',
1111
'env',
@@ -14,7 +14,12 @@ const STATIC_OPTIONS: Array<keyof RumInitConfiguration> = [
1414
'sessionReplaySampleRate',
1515
'defaultPrivacyLevel',
1616
'enablePrivacyForActionName',
17+
'traceSampleRate',
18+
'trackSessionAcrossSubdomains',
19+
'allowedTracingUrls',
20+
'allowedTrackingOrigins',
1721
]
22+
type SerializedOption = { rcSerializedType: 'string'; value: string } | { rcSerializedType: 'regex'; value: string }
1823

1924
export async function fetchAndApplyRemoteConfiguration(initConfiguration: RumInitConfiguration) {
2025
const fetchResult = await fetchRemoteConfiguration(initConfiguration)
@@ -33,14 +38,52 @@ export function applyRemoteConfiguration(
3338
// - explicitly set each supported field to limit risk in case an attacker can create configurations
3439
// - check the existence in the remote config to avoid clearing a provided init field
3540
const appliedConfiguration = { ...initConfiguration } as RumInitConfiguration & { [key: string]: unknown }
36-
STATIC_OPTIONS.forEach((option: string) => {
41+
SUPPORTED_FIELDS.forEach((option: string) => {
3742
if (option in rumRemoteConfiguration) {
38-
appliedConfiguration[option] = rumRemoteConfiguration[option]
43+
appliedConfiguration[option] = resolveConfigurationProperty(rumRemoteConfiguration[option])
3944
}
4045
})
4146
return appliedConfiguration
4247
}
4348

49+
function resolveConfigurationProperty(property: unknown): unknown {
50+
if (Array.isArray(property)) {
51+
return property.map(resolveConfigurationProperty)
52+
}
53+
if (isObject(property)) {
54+
if (isSerializedOption(property)) {
55+
const type = property.rcSerializedType
56+
switch (type) {
57+
case 'string':
58+
return property.value
59+
case 'regex':
60+
return resolveRegex(property.value)
61+
default:
62+
display.error(`Unsupported remote configuration: "rcSerializedType": "${type as string}"`)
63+
return
64+
}
65+
}
66+
return mapValues(property, resolveConfigurationProperty)
67+
}
68+
return property
69+
}
70+
71+
function isObject(property: unknown): property is { [key: string]: unknown } {
72+
return typeof property === 'object' && property !== null
73+
}
74+
75+
function isSerializedOption(value: object): value is SerializedOption {
76+
return 'rcSerializedType' in value
77+
}
78+
79+
function resolveRegex(pattern: string): RegExp | undefined {
80+
try {
81+
return new RegExp(pattern)
82+
} catch {
83+
display.error(`Invalid regex in the remote configuration: '${pattern}'`)
84+
}
85+
}
86+
4487
type FetchRemoteConfigurationResult = { ok: true; value: RumRemoteConfiguration } | { ok: false; error: Error }
4588

4689
export async function fetchRemoteConfiguration(

packages/rum-core/src/domain/configuration/remoteConfiguration.types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,45 @@ export interface RumSdkConfig {
4343
* Privacy control for action name
4444
*/
4545
enablePrivacyForActionName?: boolean
46+
/**
47+
* URLs where tracing is allowed
48+
*/
49+
allowedTracingUrls?: {
50+
match: {
51+
/**
52+
* Remote config serialized type of match
53+
*/
54+
rcSerializedType: 'string' | 'regex'
55+
/**
56+
* Match value
57+
*/
58+
value: string
59+
}
60+
/**
61+
* List of propagator types
62+
*/
63+
propagatorTypes: ('datadog' | 'b3' | 'b3multi' | 'tracecontext')[]
64+
}[]
65+
/**
66+
* Origins where tracking is allowed
67+
*/
68+
allowedTrackingOrigins?: {
69+
/**
70+
* Remote config serialized type of match
71+
*/
72+
rcSerializedType: 'string' | 'regex'
73+
/**
74+
* Match value
75+
*/
76+
value: string
77+
}[]
78+
/**
79+
* The percentage of traces sampled
80+
*/
81+
traceSampleRate?: number
82+
/**
83+
* Whether to track sessions across subdomains
84+
*/
85+
trackSessionAcrossSubdomains?: boolean
4686
}
4787
}

remote-configuration/rum-sdk-config.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,71 @@
4646
"enablePrivacyForActionName": {
4747
"type": "boolean",
4848
"description": "Privacy control for action name"
49+
},
50+
"allowedTracingUrls": {
51+
"type": "array",
52+
"description": "URLs where tracing is allowed",
53+
"items": {
54+
"type": "object",
55+
"additionalProperties": false,
56+
"required": ["match", "propagatorTypes"],
57+
"properties": {
58+
"match": {
59+
"type": "object",
60+
"additionalProperties": false,
61+
"required": ["rcSerializedType", "value"],
62+
"properties": {
63+
"rcSerializedType": {
64+
"type": "string",
65+
"enum": ["string", "regex"],
66+
"description": "Remote config serialized type of match"
67+
},
68+
"value": {
69+
"type": "string",
70+
"description": "Match value"
71+
}
72+
}
73+
},
74+
"propagatorTypes": {
75+
"type": "array",
76+
"description": "List of propagator types",
77+
"items": {
78+
"type": "string",
79+
"enum": ["datadog", "b3", "b3multi", "tracecontext"]
80+
}
81+
}
82+
}
83+
}
84+
},
85+
"allowedTrackingOrigins": {
86+
"type": "array",
87+
"description": "Origins where tracking is allowed",
88+
"items": {
89+
"type": "object",
90+
"additionalProperties": false,
91+
"required": ["rcSerializedType", "value"],
92+
"properties": {
93+
"rcSerializedType": {
94+
"type": "string",
95+
"enum": ["string", "regex"],
96+
"description": "Remote config serialized type of match"
97+
},
98+
"value": {
99+
"type": "string",
100+
"description": "Match value"
101+
}
102+
}
103+
}
104+
},
105+
"traceSampleRate": {
106+
"type": "number",
107+
"description": "The percentage of traces sampled",
108+
"minimum": 0,
109+
"maximum": 100
110+
},
111+
"trackSessionAcrossSubdomains": {
112+
"type": "boolean",
113+
"description": "Whether to track sessions across subdomains"
49114
}
50115
}
51116
}

0 commit comments

Comments
 (0)