useWebSocket.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. "use client";
  2. import { useCallback, useEffect, useRef } from "react";
  3. import { WSClient, WSMessage } from "@/lib/websocket";
  4. interface UseWebSocketOptions<T> {
  5. conversationId: number | null;
  6. enabled?: boolean;
  7. isVisitor?: boolean; // 是否是访客(默认为 true)
  8. agentId?: number; // 客服ID(如果是客服连接,需要传递)
  9. wsToken?: string; // 客服 WS 令牌(登录后下发)
  10. accessToken?: string; // 访客会话令牌
  11. onMessage: (payload: WSMessage<T>) => void;
  12. onError?: (error: Event) => void;
  13. onClose?: () => void;
  14. }
  15. export function useWebSocket<T>({
  16. conversationId,
  17. enabled = true,
  18. isVisitor = true, // 默认是访客
  19. agentId,
  20. wsToken,
  21. accessToken,
  22. onMessage,
  23. onError,
  24. onClose,
  25. }: UseWebSocketOptions<T>) {
  26. // 使用 useRef 存储最新的回调函数,避免因回调函数变化导致重新连接
  27. const onMessageRef = useRef(onMessage);
  28. const onErrorRef = useRef(onError);
  29. const onCloseRef = useRef(onClose);
  30. const clientRef = useRef<WSClient<T> | null>(null);
  31. // 更新 ref 的值
  32. useEffect(() => {
  33. onMessageRef.current = onMessage;
  34. onErrorRef.current = onError;
  35. onCloseRef.current = onClose;
  36. }, [onMessage, onError, onClose]);
  37. useEffect(() => {
  38. if (!conversationId || !enabled) {
  39. clientRef.current?.disconnect();
  40. clientRef.current = null;
  41. return;
  42. }
  43. // 客服端必须带 wsToken;否则后端会 401,且所有实时能力(新消息/草稿/已读)都不可用。
  44. if (!isVisitor && !wsToken) {
  45. clientRef.current?.disconnect();
  46. clientRef.current = null;
  47. return;
  48. }
  49. if (isVisitor && !accessToken) {
  50. clientRef.current?.disconnect();
  51. clientRef.current = null;
  52. return;
  53. }
  54. const client = new WSClient<T>({
  55. conversationId,
  56. isVisitor,
  57. agentId,
  58. wsToken,
  59. accessToken,
  60. // 使用 ref 的 current 值,这样即使回调函数变化也不会导致重新连接
  61. onMessage: (payload) => onMessageRef.current(payload),
  62. onError: onErrorRef.current
  63. ? (error) => onErrorRef.current?.(error)
  64. : undefined,
  65. onClose: onCloseRef.current ? () => onCloseRef.current?.() : undefined,
  66. });
  67. clientRef.current = client;
  68. client.connect();
  69. return () => {
  70. client.disconnect();
  71. if (clientRef.current === client) {
  72. clientRef.current = null;
  73. }
  74. };
  75. // 只依赖 conversationId、enabled、isVisitor 和 agentId,不依赖回调函数
  76. // 回调函数通过 useRef 存储,不会导致重新连接
  77. }, [conversationId, enabled, isVisitor, agentId, wsToken, accessToken]);
  78. const send = useCallback((type: string, data?: unknown): boolean => {
  79. return clientRef.current?.send(type, data) ?? false;
  80. }, []);
  81. return { send };
  82. }