/** * coms/client/public/wsModule.js * WebSocket module: handles connection, dispatching incoming packets to * data and render modules, and provides a sendMessage API. */ import { addMessage, addNotice, getReplyTo, addUser, removeUser, updateUser, getGroupedUsers, setMyId, clearData, users, setFocused, setReplyTo, } from "./data.js"; import { renderChat } from "./render.js"; /** * Initialize WebSocket connection and wire up event handlers. * * @param {string} username - The user's chosen name. * @param {HTMLElement} chatEl - The container element for chat entries. * @param {HTMLElement} inputContainer - The message input area element. * @param {HTMLElement} usersListEl - The element displaying the online users. * @returns {{ socket: WebSocket, sendMessage: (content: string) => void }} */ export function initWebSocket(username, chatEl, inputContainer, usersListEl) { const socket = new WebSocket( //"ws://localhost:8080"); (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + "/ws", "coms", ); socket.onopen = () => { // Announce join. socket.send(JSON.stringify({ type: "join", data: { name: username } })); renderChat(chatEl, inputContainer); }; socket.onmessage = (event) => { const packet = JSON.parse(event.data); switch (packet.type) { case "welcome": { // Initial handshake. Populate ids, online list, and history if provided. clearData(); const { id, online = [], history = [] } = packet.data; setMyId(String(id)); online.forEach(({ id: uid, name }) => addUser(String(uid), name)); history.forEach((m) => { addMessage({ id: String(m.id), username: m.author?.name || "Anon", authorId: m.author?.id ? String(m.author.id) : null, content: m.content, ts: m.timestamp, parent: m.parent === null || m.parent === undefined || m.parent === "-1" ? null : String(m.parent), }); }); const groups = getGroupedUsers(); usersListEl.innerHTML = groups.length ? "Online: " + groups.join(", ") : "Online: ?"; break; } case "join_evt": { // A new user joined. const { id, name } = packet.data; addUser(String(id), name); addNotice( `${new Option(name).innerHTML} joined.`, ); const groups = getGroupedUsers(); usersListEl.innerHTML = "Online: " + groups.join(", "); break; } case "leave_evt": { const { id, name } = packet.data; const sid = String(id); const lastName = users.get(sid) || name; addNotice( `${new Option(lastName).innerHTML} left.`, ); removeUser(sid); const groups = getGroupedUsers(); usersListEl.innerHTML = "Online: " + groups.join(", "); break; } case "name_evt": { // A user changed name. const { user: { id, name: oldName }, new_name: newName, } = packet.data; updateUser(String(id), newName); addNotice( `${new Option(oldName).innerHTML} changed name to ${new Option(newName).innerHTML}`, ); const groups = getGroupedUsers(); usersListEl.innerHTML = "Online: " + groups.join(", "); break; } case "msg_evt": { // A chat message arrived. const { id, author, content, parent, timestamp } = packet.data; const username = author?.name || "Anon"; const ts = timestamp || Math.floor(Date.now() / 1000); if (author?.id) addUser(String(author.id), username); addMessage({ id: String(id), username, authorId: author?.id ? String(author.id) : null, content, ts, parent: parent === null || parent === undefined || parent === "-1" ? null : String(parent), }); break; } case "msg_ack": { const { status, id } = packet.data; if (status !== "success") { addNotice( `Message failed: ${new Option(status).innerHTML}`, ); } else if (id) { const replyTarget = getReplyTo(); if (replyTarget === null) { const sid = String(id); setFocused(sid); setReplyTo(sid); } } break; } case "name_ack": { const { status, name } = packet.data; if (status !== "success") { addNotice( `Name change failed: ${new Option(status).innerHTML}`, ); } break; } case "ping": { const ts = packet.data?.ts ?? Math.floor(Date.now() / 1000); socket.send(JSON.stringify({ type: "pong", data: { ts } })); break; } default: console.warn("Unknown packet type:", packet.type); } renderChat(chatEl, inputContainer); }; socket.onerror = (err) => { console.error("WebSocket error:", err); addNotice( 'WebSocket error. See console for details.', ); renderChat(chatEl, inputContainer); }; socket.onclose = () => { addNotice("Disconnected."); renderChat(chatEl, inputContainer); }; /** * Send a chat message via WebSocket. * @param {string} content - The message text to send. */ function sendMessage(content) { const parent = getReplyTo(); socket.send( JSON.stringify({ type: "msg", data: { content, parent: parent == null ? -1 : parent }, }), ); } /** * Send a name change packet via WebSocket. * @param {string} username - The new username to set. */ function sendName(username) { socket.send(JSON.stringify({ type: "name", data: { name: username } })); } return { socket, sendMessage, sendName }; }