Skip to content

Commit 3c37331

Browse files
committed
fix: add backward compatibility support for blocking remote configuration loading
1 parent f9e745c commit 3c37331

7 files changed

Lines changed: 590 additions & 217 deletions

File tree

packages/rum-core/src/boot/preStartRum.spec.ts

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,53 @@ describe('preStartRum', () => {
422422
})
423423
})
424424

425-
describe('remote configuration', () => {
425+
describe('remote configuration sync loading', () => {
426+
let interceptor: ReturnType<typeof interceptRequests>
427+
428+
beforeEach(() => {
429+
interceptor = interceptRequests()
430+
})
431+
432+
it('should start with the remote configuration when a remoteConfigurationId is provided', async () => {
433+
interceptor.withFetch(() =>
434+
Promise.resolve({
435+
ok: true,
436+
json: () => Promise.resolve({ rum: { sessionSampleRate: 50 } }),
437+
})
438+
)
439+
const { strategy, doStartRumSpy } = createPreStartStrategyWithDefaults()
440+
strategy.init(
441+
{
442+
...DEFAULT_INIT_CONFIGURATION,
443+
remoteConfigurationId: '123',
444+
},
445+
PUBLIC_API
446+
)
447+
await collectAsyncCalls(doStartRumSpy, 1)
448+
expect(doStartRumSpy.calls.mostRecent().args[0].sessionSampleRate).toEqual(50)
449+
})
450+
451+
it('should start with the remote configuration when remoteConfiguration.sync is true', async () => {
452+
interceptor.withFetch(() =>
453+
Promise.resolve({
454+
ok: true,
455+
json: () => Promise.resolve({ rum: { sessionSampleRate: 50 } }),
456+
})
457+
)
458+
const { strategy, doStartRumSpy } = createPreStartStrategyWithDefaults()
459+
strategy.init(
460+
{
461+
...DEFAULT_INIT_CONFIGURATION,
462+
remoteConfiguration: { id: '123', sync: true },
463+
},
464+
PUBLIC_API
465+
)
466+
await collectAsyncCalls(doStartRumSpy, 1)
467+
expect(doStartRumSpy.calls.mostRecent().args[0].sessionSampleRate).toEqual(50)
468+
})
469+
})
470+
471+
describe('remote configuration async loading', () => {
426472
const REMOTE_CONFIGURATION_ID = '123'
427473
let interceptor: ReturnType<typeof interceptRequests>
428474

@@ -448,7 +494,7 @@ describe('preStartRum', () => {
448494
strategy.init(
449495
{
450496
...DEFAULT_INIT_CONFIGURATION,
451-
remoteConfigurationId: REMOTE_CONFIGURATION_ID,
497+
remoteConfiguration: { id: REMOTE_CONFIGURATION_ID },
452498
sessionSampleRate: 25,
453499
},
454500
PUBLIC_API
@@ -464,7 +510,7 @@ describe('preStartRum', () => {
464510
strategy.init(
465511
{
466512
...DEFAULT_INIT_CONFIGURATION,
467-
remoteConfigurationId: REMOTE_CONFIGURATION_ID,
513+
remoteConfiguration: { id: REMOTE_CONFIGURATION_ID },
468514
},
469515
PUBLIC_API
470516
)
@@ -576,7 +622,29 @@ describe('preStartRum', () => {
576622
expect(strategy.initConfiguration).toEqual(initConfiguration)
577623
})
578624

579-
it('exposes the user configuration when a remoteConfigurationId is provided (cache miss)', () => {
625+
it('returns the initConfiguration with the remote configuration when a remoteConfigurationId is provided (sync loading)', (done) => {
626+
interceptor.withFetch(() =>
627+
Promise.resolve({
628+
ok: true,
629+
json: () => Promise.resolve({ rum: { sessionSampleRate: 50 } }),
630+
})
631+
)
632+
const { strategy, doStartRumSpy } = createPreStartStrategyWithDefaults()
633+
doStartRumSpy.and.callFake(() => {
634+
expect(strategy.initConfiguration?.sessionSampleRate).toEqual(50)
635+
done()
636+
return {} as StartRumResult
637+
})
638+
strategy.init(
639+
{
640+
...DEFAULT_INIT_CONFIGURATION,
641+
remoteConfigurationId: '123',
642+
},
643+
PUBLIC_API
644+
)
645+
})
646+
647+
it('exposes the user configuration when remoteConfiguration.id is provided (async loading, cache miss)', () => {
580648
interceptor.withFetch(() =>
581649
Promise.resolve({
582650
ok: true,
@@ -587,7 +655,7 @@ describe('preStartRum', () => {
587655
const { strategy } = createPreStartStrategyWithDefaults()
588656
const userInitConfiguration: RumInitConfiguration = {
589657
...DEFAULT_INIT_CONFIGURATION,
590-
remoteConfigurationId: '123',
658+
remoteConfiguration: { id: '123' },
591659
}
592660
strategy.init(userInitConfiguration, PUBLIC_API)
593661

packages/rum-core/src/boot/preStartRum.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ import { createHooks } from '../domain/hooks'
3939
import type { RumConfiguration, RumInitConfiguration } from '../domain/configuration'
4040

4141
import {
42+
fetchAndApplyRemoteConfiguration,
4243
getRemoteConfiguration,
44+
getRemoteConfigurationId,
4345
validateAndBuildRumConfiguration,
4446
serializeRumConfiguration,
4547
} from '../domain/configuration'
@@ -244,10 +246,23 @@ export function createPreStartStrategy(
244246

245247
callPluginsMethod(initConfiguration.plugins, 'onInit', { initConfiguration, publicApi })
246248

247-
if (initConfiguration.remoteConfigurationId) {
248-
const supportedContextManagers = { user: userContext, context: globalContext }
249+
const hasRemoteConfiguration = getRemoteConfigurationId(initConfiguration)
249250

250-
doInit(getRemoteConfiguration(initConfiguration, supportedContextManagers), errorStack)
251+
if (hasRemoteConfiguration) {
252+
const supportedContextManagers = { user: userContext, context: globalContext }
253+
const isSyncLoading = !!initConfiguration.remoteConfigurationId || !!initConfiguration.remoteConfiguration?.sync
254+
255+
if (isSyncLoading) {
256+
fetchAndApplyRemoteConfiguration(initConfiguration, supportedContextManagers)
257+
.then((resolvedInitConfiguration) => {
258+
if (resolvedInitConfiguration) {
259+
doInit(resolvedInitConfiguration, errorStack)
260+
}
261+
})
262+
.catch(monitorError)
263+
} else {
264+
doInit(getRemoteConfiguration(initConfiguration, supportedContextManagers), errorStack)
265+
}
251266
} else {
252267
doInit(initConfiguration, errorStack)
253268
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,7 @@ describe('serializeRumConfiguration', () => {
825825
trackResources: true,
826826
trackLongTasks: true,
827827
remoteConfigurationId: '123',
828+
remoteConfiguration: { id: '123', sync: false },
828829
remoteConfigurationProxy: 'config',
829830
plugins: [{ name: 'foo', getConfigurationTelemetry: () => ({ bar: true }) }],
830831
trackFeatureFlagsForEvents: ['vital'],
@@ -845,7 +846,8 @@ describe('serializeRumConfiguration', () => {
845846
: Key extends 'trackLongTasks'
846847
? 'track_long_task' // We forgot the s, keeping this for backward compatibility
847848
: // The following options are not reported as telemetry. Please avoid adding more of them.
848-
Key extends 'applicationId' | 'subdomain'
849+
// `remoteConfiguration` is covered by the legacy `remote_configuration_id` field.
850+
Key extends 'applicationId' | 'subdomain' | 'remoteConfiguration'
849851
? never
850852
: CamelToSnakeCase<Key>
851853
// By specifying the type here, we can ensure that serializeConfiguration is returning an

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,22 @@ export interface RumInitConfiguration extends InitConfiguration {
138138
compressIntakeRequests?: boolean | undefined
139139

140140
/**
141-
* [Internal option] Id of the remote configuration
141+
* [Internal option] Id of the remote configuration.
142+
* Prefer `remoteConfiguration.id` for the non-blocking cache-and-reload path.
142143
*
143144
* @internal
144145
*/
145146
remoteConfigurationId?: string | undefined
146147

148+
/**
149+
* [Internal option] Remote configuration descriptor. By default the SDK reads a cached
150+
* configuration synchronously and refreshes it in the background. Set `sync: true` to fall back
151+
* to the legacy blocking fetch.
152+
*
153+
* @internal
154+
*/
155+
remoteConfiguration?: { id: string; sync?: boolean } | undefined
156+
147157
/**
148158
* [Internal option] set a proxy URL for the remote configuration
149159
*
@@ -666,7 +676,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration) {
666676
...plugin.getConfigurationTelemetry?.(),
667677
})),
668678
track_feature_flags_for_events: configuration.trackFeatureFlagsForEvents,
669-
remote_configuration_id: configuration.remoteConfigurationId,
679+
remote_configuration_id: configuration.remoteConfigurationId ?? configuration.remoteConfiguration?.id,
670680
profiling_sample_rate: configuration.profilingSampleRate,
671681
use_remote_configuration_proxy: !!configuration.remoteConfigurationProxy,
672682
track_resource_headers: getTrackResourceHeadersTelemetryValue(configuration.trackResourceHeaders),

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ describe('remoteConfiguration', () => {
752752
})
753753
})
754754

755-
describe('getRemoteConfiguration', () => {
755+
describe('async loading (getRemoteConfiguration)', () => {
756756
const REMOTE_CONFIGURATION_ID = 'rc-test-id'
757757
const CACHE_KEY = buildCacheKey(REMOTE_CONFIGURATION_ID)
758758
const FRESH_RUM_CONFIG: RumRemoteConfiguration = { applicationId: 'fresh-app' }
@@ -787,7 +787,7 @@ describe('remoteConfiguration', () => {
787787
initConfiguration = {
788788
...DEFAULT_INIT_CONFIGURATION,
789789
applicationId: 'init-app',
790-
remoteConfigurationId: REMOTE_CONFIGURATION_ID,
790+
remoteConfiguration: { id: REMOTE_CONFIGURATION_ID },
791791
}
792792
supportedContextManagers = { user: createContextManager(), context: createContextManager() }
793793
interceptor = interceptRequests()

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,40 @@ export async function fetchRemoteConfiguration(
288288
}
289289
}
290290

291+
export function getRemoteConfigurationId(configuration: RumInitConfiguration): string | undefined {
292+
return configuration.remoteConfiguration?.id ?? configuration.remoteConfigurationId
293+
}
294+
291295
export function buildEndpoint(configuration: RumInitConfiguration) {
292296
if (configuration.remoteConfigurationProxy) {
293297
return configuration.remoteConfigurationProxy
294298
}
295-
return `https://sdk-configuration.${buildEndpointHost(configuration)}/${REMOTE_CONFIGURATION_VERSION}/${encodeURIComponent(configuration.remoteConfigurationId!)}.json`
299+
const id = getRemoteConfigurationId(configuration)!
300+
return `https://sdk-configuration.${buildEndpointHost(configuration)}/${REMOTE_CONFIGURATION_VERSION}/${encodeURIComponent(id)}.json`
301+
}
302+
303+
export async function fetchAndApplyRemoteConfiguration(
304+
initConfiguration: RumInitConfiguration,
305+
supportedContextManagers: SupportedContextManagers
306+
) {
307+
let rumInitConfiguration: RumInitConfiguration | undefined
308+
const metrics = initMetrics()
309+
const fetchResult = await fetchRemoteConfiguration(initConfiguration)
310+
if (!fetchResult.ok) {
311+
metrics.increment('fetch', 'failure')
312+
display.error(fetchResult.error)
313+
} else {
314+
metrics.increment('fetch', 'success')
315+
rumInitConfiguration = applyRemoteConfiguration(
316+
initConfiguration,
317+
fetchResult.value,
318+
supportedContextManagers,
319+
metrics
320+
)
321+
}
322+
// monitor-until: forever
323+
addTelemetryMetrics(TelemetryMetrics.REMOTE_CONFIGURATION_METRIC_NAME, { metrics: metrics.get() })
324+
return rumInitConfiguration
296325
}
297326

298327
function doBackgroundCacheSync(
@@ -322,7 +351,7 @@ export function getRemoteConfiguration(
322351
supportedContextManagers: SupportedContextManagers
323352
): RumInitConfiguration {
324353
const configurationCache = createConfigurationCache({
325-
remoteConfigurationId: initConfiguration.remoteConfigurationId!,
354+
remoteConfigurationId: getRemoteConfigurationId(initConfiguration)!,
326355
})
327356
const metrics = initMetrics()
328357

0 commit comments

Comments
 (0)