global-socket.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { useCallback, useEffect } from 'react';
  2. import { atom, useAtomValue, useSetAtom } from 'jotai';
  3. import type { Socket } from 'socket.io-client';
  4. import { SocketEventName } from '~/interfaces/websocket';
  5. import { useCurrentPageId } from '~/states/page';
  6. import loggerFactory from '~/utils/logger';
  7. import { useIsGuestUser } from '../context';
  8. const logger = loggerFactory('growi:states:websocket');
  9. // Constants
  10. export const GLOBAL_SOCKET_NS = '/';
  11. // WebSocket connection atom
  12. const globalSocketAtom = atom<Socket | null>(null);
  13. /**
  14. * Hook to get WebSocket connection
  15. */
  16. export const useGlobalSocket = (): Socket | null =>
  17. useAtomValue(globalSocketAtom);
  18. /**
  19. * Hook to initialize WebSocket connection
  20. * Alternative to useSetupGlobalSocket
  21. */
  22. export const useSetupGlobalSocket = (): void => {
  23. const setSocket = useSetAtom(globalSocketAtom);
  24. const socket = useAtomValue(globalSocketAtom);
  25. const isGuestUser = useIsGuestUser();
  26. const initializeSocket = useCallback(async () => {
  27. try {
  28. // Dynamic import of socket.io-client
  29. const { io } = await import('socket.io-client');
  30. const newSocket = io(GLOBAL_SOCKET_NS, {
  31. transports: ['websocket'],
  32. });
  33. // Error handling
  34. newSocket.on('error', (err) => {
  35. logger.error(err);
  36. });
  37. newSocket.on('connect_error', (err) => {
  38. logger.error('Failed to connect with websocket.', err);
  39. });
  40. // Store connection in atom
  41. setSocket(newSocket);
  42. } catch (error) {
  43. logger.error('Failed to initialize WebSocket:', error);
  44. }
  45. }, [setSocket]);
  46. useEffect(() => {
  47. if (!isGuestUser && socket == null) {
  48. initializeSocket();
  49. }
  50. }, [isGuestUser, socket, initializeSocket]);
  51. };
  52. /**
  53. * Hook for page-specific WebSocket room management
  54. * Alternative to useSetupGlobalSocketForPage
  55. */
  56. export const useSetupGlobalSocketForPage = (): void => {
  57. const socket = useAtomValue(globalSocketAtom);
  58. const pageId = useCurrentPageId(true);
  59. useEffect(() => {
  60. if (socket == null || pageId == null) {
  61. return;
  62. }
  63. socket.emit(SocketEventName.JoinPage, { pageId });
  64. return () => {
  65. socket.emit(SocketEventName.LeavePage, { pageId });
  66. };
  67. }, [pageId, socket]);
  68. };