From a19c1ebb350a9271f9080baa9f080c216fc2712b Mon Sep 17 00:00:00 2001 From: Fiona Date: Fri, 16 Oct 2026 18:45:38 -0700 Subject: [PATCH] fix(rum-legacy): send user and account identifiers as strings The intake decodes usr.id, usr.name, usr.email, account.id and account.name as strings and rejects the whole event when one of them is a number. The modern bundle converts these fields with String() before attaching them; the legacy bundle stored them as given, so a page that passed a numeric user id lost every event from IE9-11 while the same page reported normally from other browsers. setUser, setUserProperty, setAccount and setAccountProperty now convert the same fields the same way. Empty values (undefined, null, '') are left alone, as in the modern bundle, and custom fields keep their type. --- .../rum-legacy/src/boot/publicApi.spec.ts | 35 +++++++++++++++++++ packages/rum-legacy/src/boot/publicApi.ts | 24 +++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/packages/rum-legacy/src/boot/publicApi.spec.ts b/packages/rum-legacy/src/boot/publicApi.spec.ts index 864de097d1..f05a7981ba 100644 --- a/packages/rum-legacy/src/boot/publicApi.spec.ts +++ b/packages/rum-legacy/src/boot/publicApi.spec.ts @@ -382,6 +382,41 @@ describe('public api', () => { expect(eventsOfType('error')[0].usr).toEqual({ id: 'u-1', name: 'Ada' }) }) + it('sends the user id, name and email as strings, as the modern bundle does', () => { + api.setUser({ id: 42, name: 7, email: 1, tier: 3 }) + api.addError(new Error('boom')) + flush() + + // The intake types these three as strings and rejects the whole event otherwise. Custom + // fields keep their type. + expect(eventsOfType('error')[0].usr).toEqual({ id: '42', name: '7', email: '1', tier: 3 }) + }) + + it('converts a user property set on its own as well', () => { + api.setUserProperty('id', 42) + api.addError(new Error('boom')) + flush() + + expect(eventsOfType('error')[0].usr).toEqual({ id: '42' }) + }) + + it('leaves an empty user id alone rather than sending the string "null"', () => { + api.setUser({ id: null, name: 'Ada' }) + api.addError(new Error('boom')) + flush() + + expect(eventsOfType('error')[0].usr).toEqual({ id: null, name: 'Ada' }) + }) + + it('sends the account id and name as strings', () => { + api.setAccount({ id: 9, name: 1 }) + api.setAccountProperty('name', 2) + api.addError(new Error('boom')) + flush() + + expect(eventsOfType('error')[0].account).toEqual({ id: '9', name: '2' }) + }) + it('leaves out an account without an id, which the format would reject', () => { api.setAccount({ name: 'no id here' }) api.addError(new Error('boom')) diff --git a/packages/rum-legacy/src/boot/publicApi.ts b/packages/rum-legacy/src/boot/publicApi.ts index 1a2ccd4d8d..bfb87c4965 100644 --- a/packages/rum-legacy/src/boot/publicApi.ts +++ b/packages/rum-legacy/src/boot/publicApi.ts @@ -38,6 +38,24 @@ type Context = { [key: string]: any } const TRACKING_CONSENT_GRANTED = 'granted' const TRACKING_CONSENT_NOT_GRANTED = 'not-granted' +const USER_STRING_FIELDS = ['id', 'name', 'email'] +const ACCOUNT_STRING_FIELDS = ['id', 'name'] + +/* + * The intake types these fields as strings and rejects the whole event when one is not, and pages + * commonly pass a numeric id. The modern bundle converts them for the same reason; empty values are + * left alone, as it does, rather than sent as "null" or "undefined". + */ +function stringifyFields(context: Context, keys: string[]): Context { + for (let i = 0; i < keys.length; i++) { + const value = context[keys[i]] + if (value !== undefined && value !== null && value !== '') { + context[keys[i]] = String(value) + } + } + return context +} + export function makeRumLegacyPublicApi() { let running: ReturnType | undefined let initConfiguration: LegacyInitConfiguration | undefined @@ -303,11 +321,12 @@ export function makeRumLegacyPublicApi() { }), setUser: monitor((user: Context) => { - userContext = user ? shallowMerge(user, {}) : {} + userContext = stringifyFields(user ? shallowMerge(user, {}) : {}, USER_STRING_FIELDS) }), getUser: monitor(() => shallowMerge(userContext, {})), setUserProperty: monitor((key: string, value: any) => { userContext[key] = value + stringifyFields(userContext, USER_STRING_FIELDS) }), removeUserProperty: monitor((key: string) => { delete userContext[key] @@ -317,11 +336,12 @@ export function makeRumLegacyPublicApi() { }), setAccount: monitor((account: Context) => { - accountContext = account ? shallowMerge(account, {}) : {} + accountContext = stringifyFields(account ? shallowMerge(account, {}) : {}, ACCOUNT_STRING_FIELDS) }), getAccount: monitor(() => shallowMerge(accountContext, {})), setAccountProperty: monitor((key: string, value: any) => { accountContext[key] = value + stringifyFields(accountContext, ACCOUNT_STRING_FIELDS) }), removeAccountProperty: monitor((key: string) => { delete accountContext[key]