diff --git a/.changeset/nasty-tables-sort.md b/.changeset/nasty-tables-sort.md new file mode 100644 index 000000000..1a864c9d1 --- /dev/null +++ b/.changeset/nasty-tables-sort.md @@ -0,0 +1,5 @@ +--- +'@segment/analytics-next': patch +--- + +Fixes calls to .identify() with null as id diff --git a/packages/browser/src/core/arguments-resolver/__tests__/index.test.ts b/packages/browser/src/core/arguments-resolver/__tests__/index.test.ts index 7c56255d3..2bc6f6572 100644 --- a/packages/browser/src/core/arguments-resolver/__tests__/index.test.ts +++ b/packages/browser/src/core/arguments-resolver/__tests__/index.test.ts @@ -429,6 +429,31 @@ describe(resolveUserArguments, () => { expect(traits).toEqual(userTraits) expect(options).toEqual({}) }) + + it('should accept (undefined, traits)', () => { + user.reset() + const [id, traits, options] = resolver(undefined, userTraits) + expect(traits).toEqual(userTraits) + expect(options).toEqual({}) + expect(id).toEqual(null) + }) + + it('should accept (null, traits) with unknown identity', () => { + user.reset() + const [id, traits, options] = resolver(null, userTraits) + expect(traits).toEqual(userTraits) + expect(options).toEqual({}) + expect(id).toEqual(null) + }) + + it('should accept (null, traits) when identity is set', () => { + user.reset() + user.identify('something') + const [id, traits, options] = resolver(null, userTraits) + expect(traits).toEqual(userTraits) + expect(options).toEqual({}) + expect(id).toEqual('something') + }) }) describe(resolveAliasArguments, () => { diff --git a/packages/browser/src/core/arguments-resolver/index.ts b/packages/browser/src/core/arguments-resolver/index.ts index 2274d6926..795381e9e 100644 --- a/packages/browser/src/core/arguments-resolver/index.ts +++ b/packages/browser/src/core/arguments-resolver/index.ts @@ -111,22 +111,56 @@ export const resolveUserArguments = ( user: U ): ResolveUser => { return (...args): ReturnType> => { - let id: string | ID | null = null - id = args.find(isString) ?? args.find(isNumber)?.toString() ?? user.id() - - const objects = args.filter((obj) => { - if (id === null) { - return isPlainObject(obj) + const values: { + id?: ID + traits?: T | null + options?: Options + callback?: Callback + } = {} + // It's a stack so it's reversed so that we go through each of the expected arguments + const orderStack: Array = [ + 'callback', + 'options', + 'traits', + 'id', + ] + + // Read each argument and eval the possible values here + for (const arg of args) { + let current = orderStack.pop() + if (current === 'id') { + if (isString(arg) || isNumber(arg)) { + values.id = arg.toString() + continue + } + if (arg === null || arg === undefined) { + continue + } + // First argument should always be the id, if it is not a valid value we can skip it + current = orderStack.pop() } - return isPlainObject(obj) || obj === null - }) as Array - const traits = (objects[0] ?? {}) as T - const opts = (objects[1] ?? {}) as Options + // Traits and Options + if ( + (current === 'traits' || current === 'options') && + (arg === null || arg === undefined || isPlainObject(arg)) + ) { + values[current] = arg as T + } - const resolvedCallback = args.find(isFunction) as Callback | undefined + // Callback + if (isFunction(arg)) { + values.callback = arg as Callback + break // This is always the last argument + } + } - return [id, traits, opts, resolvedCallback] + return [ + values.id ?? user.id(), + (values.traits ?? {}) as T, + values.options ?? {}, + values.callback, + ] } }