From 3aa8477e1a3d27cd650e3fb465c489590aa8fdb3 Mon Sep 17 00:00:00 2001 From: Ruifeng Xue <2463436064@qq.com> Date: Sun, 17 May 2026 16:37:30 +0800 Subject: [PATCH] fix(socket): use core auth user id for socket state scoping (#1984) --- .../__tests__/socketService.events.test.ts | 4 +- .../services/__tests__/socketService.test.ts | 59 ++++++++++++++++++- app/src/services/socketService.ts | 23 +------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/app/src/services/__tests__/socketService.events.test.ts b/app/src/services/__tests__/socketService.events.test.ts index 87942eade..66bd22f63 100644 --- a/app/src/services/__tests__/socketService.events.test.ts +++ b/app/src/services/__tests__/socketService.events.test.ts @@ -27,7 +27,9 @@ vi.mock('../../store/channelConnectionsSlice', () => ({ upsertChannelConnection: vi.fn((x: unknown) => x), })); vi.mock('../../lib/coreState/store', () => ({ - getCoreStateSnapshot: vi.fn(() => ({ snapshot: { sessionToken: null } })), + getCoreStateSnapshot: vi.fn(() => ({ + snapshot: { auth: { userId: 'core-user-id' }, sessionToken: null }, + })), })); class MockMCPTransport {} vi.mock('../../lib/mcp', () => ({ SocketIOMCPTransportImpl: MockMCPTransport })); diff --git a/app/src/services/__tests__/socketService.test.ts b/app/src/services/__tests__/socketService.test.ts index bbc231245..2b04daa36 100644 --- a/app/src/services/__tests__/socketService.test.ts +++ b/app/src/services/__tests__/socketService.test.ts @@ -47,7 +47,9 @@ vi.mock('../../store/connectivitySlice', () => ({ // Mock coreState vi.mock('../../lib/coreState/store', () => ({ - getCoreStateSnapshot: vi.fn(() => ({ snapshot: { sessionToken: null } })), + getCoreStateSnapshot: vi.fn(() => ({ + snapshot: { auth: { userId: 'core-user-id' }, sessionToken: null }, + })), })); // Mock MCP as a class so `new SocketIOMCPTransportImpl(...)` works at runtime. @@ -98,6 +100,61 @@ describe('socketService — resolveCoreSocketBaseUrl uses getCoreRpcUrl', () => await pollUntil(() => expect(hoisted.getCoreRpcUrlMock).toHaveBeenCalled()); }); + it('scopes socket state from core auth userId instead of decoding the JWT payload', async () => { + const { getCoreStateSnapshot } = await import('../../lib/coreState/store'); + const { setStatusForUser } = await import('../../store/socketSlice'); + const setStatusForUserMock = vi.mocked(setStatusForUser); + setStatusForUserMock.mockClear(); + + vi.mocked(getCoreStateSnapshot).mockReturnValue({ + snapshot: { + auth: { userId: 'core-user-id' }, + sessionToken: 'header.eyJ1c2VySWQiOiJqd3QtdXNlci1pZCJ9.signature', + }, + } as ReturnType); + + hoisted.getCoreRpcUrlMock.mockResolvedValue('http://127.0.0.1:7788/rpc'); + + const { socketService } = await import('../socketService'); + socketService.disconnect(); + socketService.connect('header.eyJ1c2VySWQiOiJqd3QtdXNlci1pZCJ9.signature'); + + await pollUntil(() => + expect(setStatusForUserMock).toHaveBeenCalledWith({ + userId: 'core-user-id', + status: 'connecting', + }) + ); + expect(setStatusForUserMock).not.toHaveBeenCalledWith({ + userId: 'jwt-user-id', + status: 'connecting', + }); + }); + + it('falls back to pending when the core auth snapshot is not available yet', async () => { + const { getCoreStateSnapshot } = await import('../../lib/coreState/store'); + const { setStatusForUser } = await import('../../store/socketSlice'); + const setStatusForUserMock = vi.mocked(setStatusForUser); + + vi.mocked(getCoreStateSnapshot).mockReturnValue({ + snapshot: { sessionToken: 'mock-token' }, + } as ReturnType); + + hoisted.getCoreRpcUrlMock.mockResolvedValue('http://127.0.0.1:7788/rpc'); + + const { socketService } = await import('../socketService'); + socketService.disconnect(); + setStatusForUserMock.mockClear(); + socketService.connect('mock-token-with-missing-auth'); + + await pollUntil(() => + expect(setStatusForUserMock).toHaveBeenCalledWith({ + userId: '__pending__', + status: 'connecting', + }) + ); + }); + it('strips /rpc suffix from the resolved RPC URL to derive the socket base', async () => { const { io } = await import('socket.io-client'); const ioMock = vi.mocked(io); diff --git a/app/src/services/socketService.ts b/app/src/services/socketService.ts index d257cf831..19c59841e 100644 --- a/app/src/services/socketService.ts +++ b/app/src/services/socketService.ts @@ -39,12 +39,6 @@ async function resolveCoreSocketBaseUrl(): Promise { return coreSocketBaseFromRpcUrl(rpcUrl); } -interface JwtPayload { - tgUserId?: string; - userId?: string; - sub?: string; -} - interface ChannelConnectionUpdatedEvent { channel: ChannelType; authMode: ChannelAuthMode; @@ -99,22 +93,7 @@ function normalizeChannelConnectionUpdatePayload( } function getSocketUserId(): string { - const token = getCoreStateSnapshot().snapshot.sessionToken; - if (!token) return '__pending__'; - - try { - const parts = token.split('.'); - if (parts.length !== 3) return '__pending__'; - - const payloadBase64 = parts[1].replace(/-/g, '+').replace(/_/g, '/'); - const payloadJson = atob(payloadBase64); - const payload = JSON.parse(payloadJson) as JwtPayload; - - const id = payload.tgUserId || payload.userId || payload.sub; - return id || '__pending__'; - } catch { - return '__pending__'; - } + return getCoreStateSnapshot().snapshot?.auth?.userId ?? '__pending__'; } class SocketService {