replace chat polling with websocket
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, useRef, useCallback, useEffect } from "react";
|
||||
import { createStompClient } from "@/lib/chatSocket";
|
||||
|
||||
const ChatWidgetContext = createContext(null);
|
||||
const API_BASE = "";
|
||||
@@ -60,24 +61,30 @@ export function ChatWidgetProvider({ children }) {
|
||||
const [liveSending, setLiveSending] = useState(false);
|
||||
const [switchingToHuman, setSwitchingToHuman] = useState(false);
|
||||
|
||||
const pollRef = useRef(null);
|
||||
const stompRef = useRef(null);
|
||||
const activeConvIdRef = useRef(null);
|
||||
const tokenRef = useRef(null); // FIX: store token so polling can restart
|
||||
const tokenRef = useRef(null);
|
||||
|
||||
const stopPolling = useCallback(() => {
|
||||
if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
|
||||
const disconnectStomp = useCallback(() => {
|
||||
if (stompRef.current) {
|
||||
stompRef.current.deactivate();
|
||||
stompRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchLiveMessages = useCallback(async (convId, token) => {
|
||||
if (!convId || !token) return;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations/${convId}/messages`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) setLiveMessages(data);
|
||||
} catch { /* silent */ }
|
||||
const subscribeToConversation = useCallback((client, convId) => {
|
||||
client.subscribe(`/topic/chat/conversations/${convId}`, (frame) => {
|
||||
try {
|
||||
const msg = JSON.parse(frame.body);
|
||||
setLiveMessages((prev) => prev.some((m) => m.id === msg.id) ? prev : [...prev, msg]);
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
client.subscribe(`/user/queue/chat/conversations`, (frame) => {
|
||||
try {
|
||||
const conv = JSON.parse(frame.body);
|
||||
if (conv.id === convId) setActiveConv(conv);
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadConversations = useCallback(async (token) => {
|
||||
@@ -97,21 +104,35 @@ export function ChatWidgetProvider({ children }) {
|
||||
|
||||
const openLiveConversation = useCallback(async (convId, token) => {
|
||||
if (!convId || !token) return;
|
||||
stopPolling();
|
||||
tokenRef.current = token; // FIX: save token for polling restart
|
||||
disconnectStomp();
|
||||
tokenRef.current = token;
|
||||
setActiveConvId(convId);
|
||||
activeConvIdRef.current = convId;
|
||||
setLiveMessages([]);
|
||||
setView("live");
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations/${convId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (res.ok) setActiveConv(await res.json());
|
||||
} catch { /* silent */ }
|
||||
await fetchLiveMessages(convId, token);
|
||||
pollRef.current = setInterval(() => fetchLiveMessages(convId, token), 2500);
|
||||
}, [stopPolling, fetchLiveMessages]);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations/${convId}/messages`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) setLiveMessages(data);
|
||||
}
|
||||
} catch { /* silent */ }
|
||||
|
||||
const client = createStompClient(token);
|
||||
client.onConnect = () => subscribeToConversation(client, convId);
|
||||
stompRef.current = client;
|
||||
client.activate();
|
||||
}, [disconnectStomp, subscribeToConversation]);
|
||||
|
||||
const sendLiveMessage = useCallback(async (text, token, convId) => {
|
||||
if (!text.trim() || liveSending || !token || !convId) return;
|
||||
@@ -122,11 +143,14 @@ export function ChatWidgetProvider({ children }) {
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({ content: text }),
|
||||
});
|
||||
if (res.ok) await fetchLiveMessages(convId, token);
|
||||
if (res.ok) {
|
||||
const msg = await res.json();
|
||||
setLiveMessages((prev) => prev.some((m) => m.id === msg.id) ? prev : [...prev, msg]);
|
||||
}
|
||||
} catch { /* silent */ } finally {
|
||||
setLiveSending(false);
|
||||
}
|
||||
}, [liveSending, fetchLiveMessages]);
|
||||
}, [liveSending]);
|
||||
|
||||
const startLiveChat = useCallback(async (token) => {
|
||||
if (!token || switchingToHuman) return;
|
||||
@@ -149,19 +173,22 @@ export function ChatWidgetProvider({ children }) {
|
||||
}
|
||||
}, [switchingToHuman, openLiveConversation]);
|
||||
|
||||
// FIX: Single effect that handles both stopping AND restarting polling
|
||||
useEffect(() => {
|
||||
if (!isOpen || view !== "live") {
|
||||
stopPolling();
|
||||
} else if (isOpen && view === "live" && activeConvIdRef.current && tokenRef.current) {
|
||||
stopPolling();
|
||||
fetchLiveMessages(activeConvIdRef.current, tokenRef.current);
|
||||
pollRef.current = setInterval(
|
||||
() => fetchLiveMessages(activeConvIdRef.current, tokenRef.current),
|
||||
2500
|
||||
);
|
||||
disconnectStomp();
|
||||
} else if (isOpen && view === "live" && activeConvIdRef.current && tokenRef.current && !stompRef.current) {
|
||||
const convId = activeConvIdRef.current;
|
||||
const token = tokenRef.current;
|
||||
const client = createStompClient(token);
|
||||
client.onConnect = () => subscribeToConversation(client, convId);
|
||||
stompRef.current = client;
|
||||
client.activate();
|
||||
}
|
||||
}, [isOpen, view, stopPolling, fetchLiveMessages]);
|
||||
}, [isOpen, view, disconnectStomp, subscribeToConversation]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => disconnectStomp();
|
||||
}, [disconnectStomp]);
|
||||
|
||||
const toggleOpen = useCallback(() => setIsOpen((o) => !o), []);
|
||||
const openView = useCallback((v) => setView(v), []);
|
||||
@@ -185,4 +212,4 @@ export function useChatWidget() {
|
||||
const ctx = useContext(ChatWidgetContext);
|
||||
if (!ctx) throw new Error("useChatWidget must be used within ChatWidgetProvider");
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user