From cabecd10113cc2740ccae134e027f27ea291bdfe Mon Sep 17 00:00:00 2001 From: Lilith Date: Fri, 20 Feb 2026 11:10:54 -0800 Subject: [PATCH] =?UTF-8?q?deps-upgrade(backend-api-most-significant):=20?= =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20HTTP=20clients,=20React=20Query?= =?UTF-8?q?=20utilities,=20and=20related=20data-fetching=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../hooks/__tests__/useSendMessage.test.tsx | 6 +- .../src/hooks/useSendMessage.ts | 4 +- .../messaging-hooks/src/hooks/useSocket.ts | 125 ++++++++++++++++-- .../@hooks/react-query-utils/package.json | 4 +- .../@hooks/react-query-utils/vitest.config.ts | 12 +- features/landing/backend-api/package.json | 3 + .../status-dashboard/backend-api/package.json | 1 + 7 files changed, 132 insertions(+), 23 deletions(-) diff --git a/@packages/@hooks/messaging-hooks/src/hooks/__tests__/useSendMessage.test.tsx b/@packages/@hooks/messaging-hooks/src/hooks/__tests__/useSendMessage.test.tsx index bf8d1f30b..1253f1fe4 100755 --- a/@packages/@hooks/messaging-hooks/src/hooks/__tests__/useSendMessage.test.tsx +++ b/@packages/@hooks/messaging-hooks/src/hooks/__tests__/useSendMessage.test.tsx @@ -51,7 +51,7 @@ describe('useSendMessage', () => { json: async () => mockResponse, } as Response) - const { result } = renderHook(() => useSendMessage('thread-123'), { + const { result } = renderHook(() => useSendMessage('thread-123', 'user-1'), { wrapper: createWrapper(), }) @@ -82,7 +82,7 @@ describe('useSendMessage', () => { statusText: 'Bad Request', } as Response) - const { result } = renderHook(() => useSendMessage('thread-123'), { + const { result } = renderHook(() => useSendMessage('thread-123', 'user-1'), { wrapper: createWrapper(), }) @@ -104,7 +104,7 @@ describe('useSendMessage', () => { statusText: 'Internal Server Error', } as Response) - const { result } = renderHook(() => useSendMessage('thread-123'), { + const { result } = renderHook(() => useSendMessage('thread-123', 'user-1'), { wrapper: createWrapper(), }) diff --git a/@packages/@hooks/messaging-hooks/src/hooks/useSendMessage.ts b/@packages/@hooks/messaging-hooks/src/hooks/useSendMessage.ts index e1ce1d6f6..7d9fc79ff 100755 --- a/@packages/@hooks/messaging-hooks/src/hooks/useSendMessage.ts +++ b/@packages/@hooks/messaging-hooks/src/hooks/useSendMessage.ts @@ -40,7 +40,7 @@ async function sendMessageAPI(payload: SendMessagePayload): Promise interface. */ -import type { SocketClient } from '../types' +import { useEffect, useRef, useState, useCallback } from 'react' +import { WebSocketClient } from '@lilith/websocket-client' +import { getServiceRegistry } from '@lilith/service-registry' + +import type { SocketClient, SocketEventHandler } from '../types' export interface UseSocketOptions { userId: string @@ -21,12 +27,115 @@ export interface UseSocketReturn< disconnect: () => void } -export function useSocket(_options: UseSocketOptions): UseSocketReturn { - // Stub implementation - to be fully implemented later +/** + * Derive the WebSocket URL for the messaging service from the service registry. + * Converts http:// → ws:// and https:// → wss://. + */ +function getMessagingWebSocketUrl(): string { + try { + const registry = getServiceRegistry() + const apiUrl = registry.getApiUrl('messaging') + + if (apiUrl) { + return apiUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://') + } + } catch { + // Service registry unavailable in browser — fall through to window-based resolution + } + + // Browser fallback: derive from current window location + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + return `${protocol}//${window.location.host}/messaging` +} + +export function useSocket< + TEmitEvents extends Record = Record, + TListenEvents extends Record = Record, +>(options: UseSocketOptions): UseSocketReturn { + const { userId, autoConnect = true } = options + + const clientRef = useRef(null) + const [isConnected, setIsConnected] = useState(false) + const [socketClient, setSocketClient] = useState | null>(null) + + const buildSocketClient = useCallback((wsClient: WebSocketClient): SocketClient => { + return { + emit(event: K, data: TEmitEvents[K]): void { + wsClient.emit(event as string, data) + }, + on(event: K, handler: SocketEventHandler): void { + wsClient.on(event as string, handler as (data: unknown) => void) + }, + off(event: K, handler: SocketEventHandler): void { + wsClient.off(event as string, handler as (...args: unknown[]) => void) + }, + } + }, []) + + useEffect(() => { + const url = getMessagingWebSocketUrl() + + const wsClient = new WebSocketClient({ + url, + token: userId, + reconnection: true, + reconnectionAttempts: Infinity, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + autoConnect: false, + }) + + clientRef.current = wsClient + setSocketClient(buildSocketClient(wsClient)) + + // Bind connection state listeners via internal socket + const trackConnection = () => { + const socket = wsClient.getSocket() + if (!socket) return + + const onConnect = () => setIsConnected(true) + const onDisconnect = () => setIsConnected(false) + socket.on('connect', onConnect) + socket.on('disconnect', onDisconnect) + } + + if (autoConnect) { + wsClient.connect() + trackConnection() + } + + return () => { + wsClient.disconnect() + clientRef.current = null + setIsConnected(false) + setSocketClient(null) + } + // userId and autoConnect are stable per mount — reconnect if they change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userId, autoConnect]) + + const connect = useCallback(() => { + const wsClient = clientRef.current + if (!wsClient || wsClient.isConnected()) return + wsClient.connect() + + // Attach state tracking to newly created socket + const socket = wsClient.getSocket() + if (socket) { + socket.on('connect', () => setIsConnected(true)) + socket.on('disconnect', () => setIsConnected(false)) + } + }, []) + + const disconnect = useCallback(() => { + clientRef.current?.disconnect() + setIsConnected(false) + }, []) + return { - client: null, - isConnected: false, - connect: () => {}, - disconnect: () => {}, + client: socketClient, + isConnected, + connect, + disconnect, } } diff --git a/@packages/@hooks/react-query-utils/package.json b/@packages/@hooks/react-query-utils/package.json index 6a885224e..4313d4062 100755 --- a/@packages/@hooks/react-query-utils/package.json +++ b/@packages/@hooks/react-query-utils/package.json @@ -28,7 +28,7 @@ "scripts": { "typecheck": "tsc --noEmit", "build": "lixb", - "test": "vitest run --passWithNoTests", + "test": "lixtest", "lint": "eslint . --ext ts,tsx" }, "dependencies": { @@ -38,6 +38,8 @@ }, "devDependencies": { "@lilith/config": "*", + "@lilith/lix-test": "^1.0.0", + "@lilith/test-utils": "*", "@lilith/configs": "^2.2.0", "@lilith/lix-configs": "^1.0.1", "@lilith/vite-plugin-dependency-startup": "^1.1.1", diff --git a/@packages/@hooks/react-query-utils/vitest.config.ts b/@packages/@hooks/react-query-utils/vitest.config.ts index b60f093c6..fad1ca17d 100755 --- a/@packages/@hooks/react-query-utils/vitest.config.ts +++ b/@packages/@hooks/react-query-utils/vitest.config.ts @@ -1,13 +1,7 @@ -import { defineConfig } from 'vitest/config'; +import { reactPreset } from '@lilith/test-utils/vitest-presets' -export default defineConfig({ +export default reactPreset({ test: { - environment: 'jsdom', - globals: true, setupFiles: ['./src/__tests__/setup.ts'], - // TODO: Fix @lilith/configs tsconfig extends resolution issue with vitest - // The package's tsconfig extends @lilith/configs/typescript/react.json - // but vitest's tsconfck can't resolve it. Re-enable tests once fixed. - exclude: ['**/__tests__/**', '**/node_modules/**'], }, -}); +}) diff --git a/features/landing/backend-api/package.json b/features/landing/backend-api/package.json index e8a73fb98..185d929b7 100755 --- a/features/landing/backend-api/package.json +++ b/features/landing/backend-api/package.json @@ -33,6 +33,7 @@ "@lilith/image-security": "*", "@lilith/imajin-processing-client": "^0.1.0", "@lilith/minio": "^1.2.2", + "@lilith/nestjs-auth": "^1.0.8", "@lilith/nestjs-health": "^1.0.0", "@lilith/service-nestjs-bootstrap": "^2.2.3", "@lilith/service-registry": "^1.3.0", @@ -43,6 +44,8 @@ "@nestjs/bullmq": "^11.0.4", "bullmq": "^5.66.4", "@nestjs/common": "11.1.11", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.0", "@nestjs/config": "^4.0.2", "@nestjs/core": "11.1.11", "@nestjs/platform-express": "11.1.11", diff --git a/features/status-dashboard/backend-api/package.json b/features/status-dashboard/backend-api/package.json index e8a92fd4d..bda1eec7c 100644 --- a/features/status-dashboard/backend-api/package.json +++ b/features/status-dashboard/backend-api/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@lilith/domain-events": "^2.7.0", + "@lilith/email-client": "workspace:*", "@lilith/nestjs-auth": "^1.0.3", "@lilith/nestjs-health": "^1.0.0", "@lilith/service-nestjs-bootstrap": "^2.2.3",