function opendesk_handlers(socket) { socket.on("ping", () => { socket.emit("pong"); }); // Same gate as frappe's doc_open: the room leaks viewers, edits, typing. // Tickets this socket still means to view: a stop that lands during the // permission check would otherwise be undone by the late join. const viewing = new Set(); socket.on("view_ticket", (ticket_id) => { if (!ticket_id) return; viewing.add(ticket_id); socket.has_permission("OD Ticket", ticket_id).then(() => { if (!viewing.has(ticket_id)) return; socket.join(open_doc_room("OD Ticket", ticket_id)); // Notify all viewers in this ticket room about the new viewer notify_ticket_viewers({ socket: socket, ticket_id: ticket_id, }); }); }); socket.on("stop_view_ticket", (ticket_id) => { if (!ticket_id) return; viewing.delete(ticket_id); const room = open_doc_room("OD Ticket", ticket_id); socket.leave(room); // Notify remaining viewers that someone left notify_ticket_viewers({ socket: socket, ticket_id: ticket_id, }); }); socket.on("ticket_get_viewers", (ticket_id) => { if (!in_ticket_room(socket, ticket_id)) return; // Send current viewers list to the requesting user only notify_ticket_viewers({ socket: socket, ticket_id: ticket_id, toUser: true, // saying that whenever we ask for viewers, send only to the user who asked }); }); socket.on("notify_ticket_update", (ticket_id, field, value) => { if (!(ticket_id && field)) return; if (!in_ticket_room(socket, ticket_id)) return; const ticket_room = open_doc_room("OD Ticket", ticket_id); socket.to(ticket_room).emit("ticket_update", { ticket_id, user: socket.user, field, value, }); }); // Typing indicators socket.on("opendesk_ticket_typing", (ticket_id) => { if (!in_ticket_room(socket, ticket_id)) return; const ticket_room = open_doc_room("OD Ticket", ticket_id); // Broadcast to all other users in the ticket room that this user is typing socket.to(ticket_room).emit("opendesk_ticket_typing", { ticket_id, user: socket.user, }); }); socket.on("opendesk_ticket_typing_stopped", (ticket_id) => { if (!in_ticket_room(socket, ticket_id)) return; const ticket_room = open_doc_room("OD Ticket", ticket_id); // Broadcast to all other users in the ticket room that this user stopped typing socket.to(ticket_room).emit("opendesk_ticket_typing_stopped", { ticket_id, user: socket.user, }); }); } function notify_ticket_viewers(args) { if (!(args && args.socket && args.ticket_id)) return; const socket = args.socket; const ticket_id = args.ticket_id; const ticket_room = open_doc_room("OD Ticket", ticket_id); const clients = Array.from(socket.nsp.adapter.rooms.get(ticket_room) || []); let users = []; // Extract user information for each socket in the room socket.nsp.sockets.forEach((sock) => { if (clients.includes(sock.id)) { users.push(sock.user); } }); const target_room = args.toUser ? user_room(args.socket.user) // Send to specific user only : ticket_room; // Send to all viewers in the ticket room // Emit the notification with current viewers list socket.nsp.to(target_room).emit("ticket_viewers", { ticket_id, users: JSON.stringify(Array.from(new Set(users))), // Remove duplicate users total_viewers: Array.from(new Set(users)).length, }); } // Helper functions const in_ticket_room = (socket, ticket_id) => socket.rooms.has(open_doc_room("OD Ticket", ticket_id)); const open_doc_room = (doctype, docname) => "open_doc:" + doctype + "/" + docname; const user_room = (user) => "user:" + user; const ticket_response_room = (ticket_id) => "ticket_response:" + ticket_id; module.exports = opendesk_handlers;