import { defineBoot } from "#q-app/wrappers"; import { io } from "socket.io-client"; import { reactive } from "vue"; const state = reactive({ activeRooms: new Set(), isConnected: false, }); const socket = io(process.env.WEBSOCKET_API, { transport: ["websocket"], path: process.env.WEBSOCKET_PATH, auth: { apiKey: process.env.WEBSOCKET_API_KEY, }, reconnection: true, reconnectionDelay: 1000, timeout: 20000, }); /** * Join a room with automatic reconnection handling * @param {string} roomName Room name to join * @return {boolean} Success status */ const joinRoom = (roomName) => { if (!roomName) return false; const fullRoomName = `${process.env.WEBSOCKET_ROOM}:${roomName}`; state.activeRooms.add(fullRoomName); if (state.isConnected) { socket.emit("join", fullRoomName); console.log(`Joined room: ${fullRoomName}`); } else { console.log(`Room ${fullRoomName} will join upon connection`); } return true; }; /** * Leave a room and clean up * @param {string} roomName Room name to leave */ const leaveRoom = (roomName) => { if (!roomName) return; const fullRoomName = `${process.env.WEBSOCKET_ROOM}:${roomName}`; state.activeRooms.delete(fullRoomName); if (state.isConnected) { socket.emit("leave", fullRoomName); console.log(`Left room: ${fullRoomName}`); } }; const sendEventToLaravel = (eventName, data) => { const channel = process.env.WEBSOCKET_ROOM + ":" + eventName; socket.emit("eventWrapperToLaravel", { channel: channel, data: data, }); }; const sendEvent = (room, eventName, data) => { const channel = process.env.WEBSOCKET_ROOM + ":" + room + "@" + eventName; socket.emit("eventWrapperToNode", { channel: channel, data: data, }); }; export default defineBoot(async () => { socket.on("connect", () => { console.log("Connected to websocket server!"); state.isConnected = true; // Rejoin all active rooms after reconnection if (state.activeRooms.size > 0) { console.log( `Rejoining ${state.activeRooms.size} rooms after reconnection`, ); state.activeRooms.forEach((room) => { socket.emit("join", room); }); } }); socket.on("disconnect", () => { console.log("Disconnected from websocket server!"); state.isConnected = false; }); socket.on("connect_error", (error) => { console.error("Websocket connection error: ", error); state.isConnected = false; }); socket.on("connect_timeout", (timeout) => { console.error("Websocket connection timeout: ", timeout); state.isConnected = false; }); socket.on("reconnect", (attemptNumber) => { console.log( "Reconnected to websocket server! Attempt number: ", attemptNumber, ); state.isConnected = true; }); socket.on("reconnect_attempt", (attemptNumber) => { console.log("Reconnect attempt number: ", attemptNumber); }); }); /** * Helper function to create one-time event listeners with automatic cleanup * @param {string} event Event name * @param {Function} callback Callback function * @param {string|null} roomName Optional room to leave on callback * @return {Function} Cleanup function */ const onceEvent = (event, callback, roomName = null) => { if (!event || typeof callback !== "function") return () => {}; const wrappedCallback = (data) => { socket.off(event, wrappedCallback); if (roomName) { leaveRoom(roomName); } callback(data); }; socket.on(event, wrappedCallback); return () => { socket.off(event, wrappedCallback); if (roomName) { leaveRoom(roomName); } }; }; export { socket, joinRoom, leaveRoom, sendEvent, sendEventToLaravel, onceEvent, state, };