From 86523cda71558e4a5735009ebffc55be0b415e60 Mon Sep 17 00:00:00 2001 From: Kirill Date: Fri, 22 May 2026 18:43:02 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20add=20SseProvider=20=E2=80=94=20SSE=20t?= =?UTF-8?q?o=20ReactQuery=20bridge=20with=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/providers/SseProvider.tsx | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 client/src/app/providers/SseProvider.tsx diff --git a/client/src/app/providers/SseProvider.tsx b/client/src/app/providers/SseProvider.tsx new file mode 100644 index 0000000..b13c74f --- /dev/null +++ b/client/src/app/providers/SseProvider.tsx @@ -0,0 +1,83 @@ +import { useEffect, useRef } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { useUnit } from 'effector-react' +import { createEventStream } from '@/shared/lib/sse' +import { $token } from '@/shared/model/auth' + +export function SseProvider() { + const token = useUnit($token) + const queryClient = useQueryClient() + const sourceRef = useRef(null) + + useEffect(() => { + if (!token) { + if (sourceRef.current) { + sourceRef.current.close() + sourceRef.current = null + } + return + } + + const es = createEventStream(token) + sourceRef.current = es + + function handleEvent(eventName: string) { + return function (event: MessageEvent) { + try { + const data = JSON.parse(event.data) + const orderId = data.orderId + + switch (eventName) { + case 'message:new': + queryClient.invalidateQueries({ queryKey: ['me', 'messages', 'unread-count'] }) + queryClient.invalidateQueries({ queryKey: ['me', 'conversations'] }) + if (orderId) { + queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] }) + queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] }) + } + break + case 'order:statusChanged': + if (orderId) { + queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] }) + queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] }) + } + break + case 'order:updated': + if (orderId) { + queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] }) + queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] }) + } + break + case 'order:new': + queryClient.invalidateQueries({ queryKey: ['admin', 'orders', 'summary'] }) + queryClient.invalidateQueries({ queryKey: ['admin', 'orders'] }) + break + } + } catch { + // ignore parse errors (e.g. heartbit comments) + } + } + } + + const messageNewHandler = handleEvent('message:new') + const orderStatusHandler = handleEvent('order:statusChanged') + const orderUpdatedHandler = handleEvent('order:updated') + const orderNewHandler = handleEvent('order:new') + + es.addEventListener('message:new', messageNewHandler) + es.addEventListener('order:statusChanged', orderStatusHandler) + es.addEventListener('order:updated', orderUpdatedHandler) + es.addEventListener('order:new', orderNewHandler) + + return () => { + es.removeEventListener('message:new', messageNewHandler) + es.removeEventListener('order:statusChanged', orderStatusHandler) + es.removeEventListener('order:updated', orderUpdatedHandler) + es.removeEventListener('order:new', orderNewHandler) + es.close() + sourceRef.current = null + } + }, [token, queryClient]) + + return null +}