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]