websocket.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { useEffect } from 'react';
  2. import {
  3. GLOBAL_SOCKET_NS,
  4. useGlobalSocket,
  5. useSWRStatic,
  6. } from '@growi/core/dist/swr';
  7. import type { Socket } from 'socket.io-client';
  8. import type { SWRResponse } from 'swr';
  9. import { SocketEventName } from '~/interfaces/websocket';
  10. import { useIsGuestUser } from '~/stores-universal/context';
  11. import loggerFactory from '~/utils/logger';
  12. const logger = loggerFactory('growi:stores:ui');
  13. export const GLOBAL_ADMIN_SOCKET_NS = '/admin';
  14. export const GLOBAL_ADMIN_SOCKET_KEY = 'globalAdminSocket';
  15. /*
  16. * Global Socket
  17. */
  18. export const useSetupGlobalSocket = (): void => {
  19. const { data: socket, mutate } = useGlobalSocket();
  20. const { data: isGuestUser } = useIsGuestUser();
  21. useEffect(() => {
  22. // Skip Socket.IO connection for guest users (not logged in)
  23. // Guest users don't need real-time updates as they can only read pages
  24. if (isGuestUser) {
  25. logger.debug('Socket.IO connection skipped for guest user');
  26. return;
  27. }
  28. if (socket != null) {
  29. return;
  30. }
  31. mutate(async () => {
  32. const { io } = await import('socket.io-client');
  33. const newSocket = io(GLOBAL_SOCKET_NS, {
  34. transports: ['websocket'],
  35. });
  36. newSocket.on('error', (err) => {
  37. logger.error(err);
  38. });
  39. newSocket.on('connect_error', (err) => {
  40. logger.error('Failed to connect with websocket.', err);
  41. });
  42. return newSocket;
  43. });
  44. // Cleanup function to disconnect socket when component unmounts or user logs out
  45. return () => {
  46. if (
  47. socket != null &&
  48. typeof socket === 'object' &&
  49. 'disconnect' in socket
  50. ) {
  51. logger.debug('Disconnecting Socket.IO connection');
  52. (socket as Socket).disconnect();
  53. mutate(undefined, false); // Clear the SWR cache without revalidation
  54. }
  55. };
  56. }, [socket, isGuestUser, mutate]);
  57. };
  58. // comment out for porduction build error: https://github.com/growilabs/growi/pull/7131
  59. /*
  60. * Global Admin Socket
  61. */
  62. // export const useSetupGlobalAdminSocket = (shouldInit: boolean): SWRResponse<Socket, Error> => {
  63. // let socket: Socket | undefined;
  64. // if (shouldInit) {
  65. // socket = io(GLOBAL_ADMIN_SOCKET_NS, {
  66. // transports: ['websocket'],
  67. // });
  68. // socket.on('error', (err) => { logger.error(err) });
  69. // socket.on('connect_error', (err) => { logger.error('Failed to connect with websocket.', err) });
  70. // }
  71. // return useStaticSWR(shouldInit ? GLOBAL_ADMIN_SOCKET_KEY : null, socket);
  72. // };
  73. export const useGlobalAdminSocket = (): SWRResponse<Socket, Error> => {
  74. return useSWRStatic(GLOBAL_ADMIN_SOCKET_KEY);
  75. };
  76. export const useSetupGlobalSocketForPage = (
  77. pageId: string | undefined,
  78. ): void => {
  79. const { data: socket } = useGlobalSocket();
  80. useEffect(() => {
  81. if (socket == null || pageId == null) {
  82. return;
  83. }
  84. socket.emit(SocketEventName.JoinPage, { pageId });
  85. return () => {
  86. socket.emit(SocketEventName.LeavePage, { pageId });
  87. };
  88. }, [pageId, socket]);
  89. };