socket.io.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { defineBoot } from "#q-app/wrappers";
  2. import { io } from "socket.io-client";
  3. import { reactive } from "vue";
  4. const state = reactive({
  5. activeRooms: new Set(),
  6. isConnected: false,
  7. });
  8. const socket = io(process.env.WEBSOCKET_API, {
  9. transport: ["websocket"],
  10. path: process.env.WEBSOCKET_PATH,
  11. auth: {
  12. apiKey: process.env.WEBSOCKET_API_KEY,
  13. },
  14. reconnection: true,
  15. reconnectionDelay: 1000,
  16. timeout: 20000,
  17. });
  18. /**
  19. * Join a room with automatic reconnection handling
  20. * @param {string} roomName Room name to join
  21. * @return {boolean} Success status
  22. */
  23. const joinRoom = (roomName) => {
  24. if (!roomName) return false;
  25. const fullRoomName = `${process.env.WEBSOCKET_ROOM}:${roomName}`;
  26. state.activeRooms.add(fullRoomName);
  27. if (state.isConnected) {
  28. socket.emit("join", fullRoomName);
  29. console.log(`Joined room: ${fullRoomName}`);
  30. } else {
  31. console.log(`Room ${fullRoomName} will join upon connection`);
  32. }
  33. return true;
  34. };
  35. /**
  36. * Leave a room and clean up
  37. * @param {string} roomName Room name to leave
  38. */
  39. const leaveRoom = (roomName) => {
  40. if (!roomName) return;
  41. const fullRoomName = `${process.env.WEBSOCKET_ROOM}:${roomName}`;
  42. state.activeRooms.delete(fullRoomName);
  43. if (state.isConnected) {
  44. socket.emit("leave", fullRoomName);
  45. console.log(`Left room: ${fullRoomName}`);
  46. }
  47. };
  48. const sendEventToLaravel = (eventName, data) => {
  49. const channel = process.env.WEBSOCKET_ROOM + ":" + eventName;
  50. socket.emit("eventWrapperToLaravel", {
  51. channel: channel,
  52. data: data,
  53. });
  54. };
  55. const sendEvent = (room, eventName, data) => {
  56. const channel = process.env.WEBSOCKET_ROOM + ":" + room + "@" + eventName;
  57. socket.emit("eventWrapperToNode", {
  58. channel: channel,
  59. data: data,
  60. });
  61. };
  62. export default defineBoot(async () => {
  63. socket.on("connect", () => {
  64. console.log("Connected to websocket server!");
  65. state.isConnected = true;
  66. // Rejoin all active rooms after reconnection
  67. if (state.activeRooms.size > 0) {
  68. console.log(
  69. `Rejoining ${state.activeRooms.size} rooms after reconnection`,
  70. );
  71. state.activeRooms.forEach((room) => {
  72. socket.emit("join", room);
  73. });
  74. }
  75. });
  76. socket.on("disconnect", () => {
  77. console.log("Disconnected from websocket server!");
  78. state.isConnected = false;
  79. });
  80. socket.on("connect_error", (error) => {
  81. console.error("Websocket connection error: ", error);
  82. state.isConnected = false;
  83. });
  84. socket.on("connect_timeout", (timeout) => {
  85. console.error("Websocket connection timeout: ", timeout);
  86. state.isConnected = false;
  87. });
  88. socket.on("reconnect", (attemptNumber) => {
  89. console.log(
  90. "Reconnected to websocket server! Attempt number: ",
  91. attemptNumber,
  92. );
  93. state.isConnected = true;
  94. });
  95. socket.on("reconnect_attempt", (attemptNumber) => {
  96. console.log("Reconnect attempt number: ", attemptNumber);
  97. });
  98. });
  99. /**
  100. * Helper function to create one-time event listeners with automatic cleanup
  101. * @param {string} event Event name
  102. * @param {Function} callback Callback function
  103. * @param {string|null} roomName Optional room to leave on callback
  104. * @return {Function} Cleanup function
  105. */
  106. const onceEvent = (event, callback, roomName = null) => {
  107. if (!event || typeof callback !== "function") return () => {};
  108. const wrappedCallback = (data) => {
  109. socket.off(event, wrappedCallback);
  110. if (roomName) {
  111. leaveRoom(roomName);
  112. }
  113. callback(data);
  114. };
  115. socket.on(event, wrappedCallback);
  116. return () => {
  117. socket.off(event, wrappedCallback);
  118. if (roomName) {
  119. leaveRoom(roomName);
  120. }
  121. };
  122. };
  123. export {
  124. socket,
  125. joinRoom,
  126. leaveRoom,
  127. sendEvent,
  128. sendEventToLaravel,
  129. onceEvent,
  130. state,
  131. };