From 194f76ce0c5f312345fd9b2735aa9dcda668c360 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Mon, 1 Jun 2026 12:14:52 -0700 Subject: [PATCH] [AI-FSSDK] [FSSDK-12721] Skip ODP identify event when fewer than 2 valid identifiers --- lib/odp/odp_manager.spec.ts | 59 +++++++++++++++++++++++++++++++++---- lib/odp/odp_manager.ts | 17 ++++++++++- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 9ae0daf69..03137f14e 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -618,7 +618,7 @@ describe('DefaultOdpManager', () => { expect(identifiers).toEqual(new Map([['fs_user_id', 'user'], ['vuid', 'vuid_a']])); }); - it('sends identified event when called with just fs_user_id in first parameter', async () => { + it('does not send identified event when called with just fs_user_id and no vuid set', async () => { const eventManager = getMockOdpEventManager(); eventManager.onRunning.mockReturnValue(Promise.resolve()); @@ -634,12 +634,10 @@ describe('DefaultOdpManager', () => { await odpManager.onRunning(); odpManager.identifyUser('user'); - expect(mockSendEvents).toHaveBeenCalledOnce(); - const { identifiers } = mockSendEvents.mock.calls[0][0]; - expect(identifiers).toEqual(new Map([['fs_user_id', 'user']])); + expect(mockSendEvents).not.toHaveBeenCalled(); }); - it('sends identified event when called with just vuid in first parameter', async () => { + it('does not send identified event when called with just vuid in first parameter and no stored vuid', async () => { const eventManager = getMockOdpEventManager(); eventManager.onRunning.mockReturnValue(Promise.resolve()); @@ -655,9 +653,58 @@ describe('DefaultOdpManager', () => { await odpManager.onRunning(); odpManager.identifyUser('vuid_a'); + expect(mockSendEvents).not.toHaveBeenCalled(); + }); + + it('sends identified event when called with fs_user_id and vuid is set via setVuid', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + // Set vuid first, then identify with fs_user_id + odpManager.setVuid('vuid_123'); + + // Clear the client_initialized event call + mockSendEvents.mockClear(); + + odpManager.identifyUser('user'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { type, action, identifiers } = mockSendEvents.mock.calls[0][0]; + expect(type).toEqual('fullstack'); + expect(action).toEqual('identified'); + // identifyUser passes {fs_user_id: 'user'}, and sendEvent augments with stored vuid + expect(identifiers).toEqual(new Map([['fs_user_id', 'user'], ['vuid', 'vuid_123']])); + }); + + it('sends identified event with three identifiers (fs_user_id, explicit vuid, and stored vuid)', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('user', 'vuid_a'); expect(mockSendEvents).toHaveBeenCalledOnce(); const { identifiers } = mockSendEvents.mock.calls[0][0]; - expect(identifiers).toEqual(new Map([['vuid', 'vuid_a']])); + expect(identifiers).toEqual(new Map([['fs_user_id', 'user'], ['vuid', 'vuid_a']])); }); it('should reject onRunning() if stopped in new state', async () => { diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 7525d0efb..7d46d0e42 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -212,7 +212,7 @@ export class DefaultOdpManager extends BaseService implements OdpManager { identifyUser(userId: string, vuid?: string): void { const identifiers = new Map(); - + let finalUserId: Maybe = userId; let finalVuid: Maybe = vuid; @@ -229,6 +229,21 @@ export class DefaultOdpManager extends BaseService implements OdpManager { identifiers.set(ODP_USER_KEY.FS_USER_ID, finalUserId); } + // Include the stored vuid when counting identifiers to determine if there + // are enough to warrant sending an identify event. The vuid would be added + // later in sendEvent(), but we need to account for it here for the count. + const allIdentifiers = new Map(identifiers); + if (!allIdentifiers.has(ODP_USER_KEY.VUID) && this.vuid) { + allIdentifiers.set(ODP_USER_KEY.VUID, this.vuid); + } + + // An identify event requires at least 2 identifiers to link (e.g., vuid + fs_user_id). + // A single identifier has no cross-reference value and would generate unnecessary traffic. + if (allIdentifiers.size < 2) { + this.logger?.debug('ODP identify event is not dispatched (only one identifier provided).'); + return; + } + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.IDENTIFIED, identifiers); this.sendEvent(event); }