diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index 881d42fbf..4bc448d2a 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -41,6 +41,7 @@ import { BufferSingleton } from './sdk/DatadogProvider/Buffer/BufferSingleton'; import { NativeDdSdk } from './sdk/DdSdkInternal'; import { GlobalState } from './sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from './sdk/UserInfoSingleton/UserInfoSingleton'; +import type { UserInfo } from './sdk/UserInfoSingleton/types'; import { adaptLongTaskThreshold } from './utils/longTasksUtils'; import { version as sdkVersion } from './version'; @@ -251,19 +252,23 @@ export class DdSdkReactNative { }; /** - * Sets the user information. - * @param id: A mandatory unique user identifier (relevant to your business domain). - * @param name: The user name or alias. - * @param email: The user email. - * @param extraInfo: Additional information. + * Sets the user information. Requires a user ID — setting user properties + * like name or email implies a known user, so an ID must be provided. + * To add custom attributes without setting a user ID, use {@link addUserExtraInfo} instead. + * @param userInfo: The user object (id is required, name, email and extraInfo are optional). * @returns a Promise. */ - static setUserInfo = async (userInfo: { - id: string; - name?: string; - email?: string; - extraInfo?: Record; - }): Promise => { + static setUserInfo = async ( + userInfo: UserInfo & { id: string } + ): Promise => { + if (typeof userInfo.id !== 'string' || userInfo.id.length === 0) { + InternalLog.log( + 'setUserInfo requires a valid user ID. Please provide a non-empty string as the id field.', + SdkVerbosity.WARN + ); + return; + } + InternalLog.log( `Setting user ${JSON.stringify(userInfo)}`, SdkVerbosity.DEBUG @@ -296,25 +301,8 @@ export class DdSdkReactNative { SdkVerbosity.DEBUG ); - const userInfo = UserInfoSingleton.getInstance().getUserInfo(); - if (!userInfo) { - InternalLog.log( - 'Skipped adding User Extra Info: User Info is currently undefined. A user ID must be set before adding extra info. Please call setUserInfo() first.', - SdkVerbosity.WARN - ); - - return; - } - const updatedUserInfo = { - ...userInfo, - extraInfo: { - ...userInfo.extraInfo, - ...extraUserInfo - } - }; - await NativeDdSdk.addUserExtraInfo(extraUserInfo); - UserInfoSingleton.getInstance().setUserInfo(updatedUserInfo); + UserInfoSingleton.getInstance().addUserExtraInfo(extraUserInfo); }; /** diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 2ec1d2233..4a3902d32 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -1298,6 +1298,24 @@ describe('DdSdkReactNative', () => { } }); }); + + it('calls SDK method when addUserExtraInfo without prior setUserInfo', async () => { + // GIVEN + const extraInfo = { testId: 'abc123' }; + + // WHEN + await DdSdkReactNative.addUserExtraInfo(extraInfo); + + // THEN + expect(NativeDdSdk.addUserExtraInfo).toHaveBeenCalledWith( + extraInfo + ); + expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ + extraInfo: { + testId: 'abc123' + } + }); + }); }); describe('clearUserInfo', () => { @@ -1320,9 +1338,9 @@ describe('DdSdkReactNative', () => { // THEN expect(NativeDdSdk.clearUserInfo).toHaveBeenCalledTimes(1); expect(NativeDdSdk.setUserInfo).toHaveBeenCalled(); - expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( - undefined - ); + expect( + UserInfoSingleton.getInstance().getUserInfo() + ).toBeUndefined(); }); }); diff --git a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts index 2408fdbf2..a33e494e9 100644 --- a/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts +++ b/packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts @@ -11,9 +11,22 @@ import type { UserInfo } from './types'; class UserInfoProvider { private userInfo: UserInfo | undefined = undefined; - setUserInfo = (userInfo: UserInfo) => { + setUserInfo = (userInfo: UserInfo & { id: string }) => { + if (typeof userInfo.id !== 'string' || userInfo.id.length === 0) { + return; + } this.userInfo = userInfo; - setCachedUserId(this.userInfo.id); + setCachedUserId(userInfo.id); + }; + + addUserExtraInfo = (extraInfo: Record) => { + this.userInfo = { + ...this.userInfo, + extraInfo: { + ...this.userInfo?.extraInfo, + ...extraInfo + } + }; }; getUserInfo = (): UserInfo | undefined => { diff --git a/packages/core/src/sdk/UserInfoSingleton/types.ts b/packages/core/src/sdk/UserInfoSingleton/types.ts index dd14eb150..0c4975b75 100644 --- a/packages/core/src/sdk/UserInfoSingleton/types.ts +++ b/packages/core/src/sdk/UserInfoSingleton/types.ts @@ -5,7 +5,7 @@ */ export type UserInfo = { - readonly id: string; + readonly id?: string; readonly name?: string; readonly email?: string; readonly extraInfo?: Record; diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 4d3fda013..49dc64bc2 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -47,13 +47,12 @@ export type DdSdkType = { removeAttributes(keys: string[]): Promise; /** - * Sets the user information. - * @param id: A unique user identifier (relevant to your business domain) - * @param name: The user name or alias. - * @param email: The user email. - * @param extraInfo: Additional information. + * Sets the user information. Requires a user ID — setting user properties + * like name or email implies a known user, so an ID must be provided. + * To add custom attributes without setting a user ID, use {@link addUserExtraInfo} instead. + * @param userInfo: The user object (id is required, name, email and extraInfo are optional). */ - setUserInfo(userInfo: UserInfo): Promise; + setUserInfo(userInfo: UserInfo & { id: string }): Promise; /** * Clears the user information. @@ -128,7 +127,7 @@ export type DdTraceType = { // Core export type UserInfo = { - id: string; + id?: string; name?: string; email?: string; extraInfo?: object;