1function opendesk_handlers(socket) {
2 socket.on("ping", () => {
3 socket.emit("pong");
4 });
5
6 // Same gate as frappe's doc_open: the room leaks viewers, edits, typing.
7 // Tickets this socket still means to view: a stop that lands during the
8 // permission check would otherwise be undone by the late join.
9 const viewing = new Set();
10
11 socket.on("view_ticket", (ticket_id) => {
12 if (!ticket_id) return;
13 viewing.add(ticket_id);
14 socket.has_permission("OD Ticket", ticket_id).then(() => {
15 if (!viewing.has(ticket_id)) return;
16 socket.join(open_doc_room("OD Ticket", ticket_id));
17
18 // Notify all viewers in this ticket room about the new viewer
19 notify_ticket_viewers({
20 socket: socket,
21 ticket_id: ticket_id,
22 });
23 });
24 });
25
26 socket.on("stop_view_ticket", (ticket_id) => {
27 if (!ticket_id) return;
28 viewing.delete(ticket_id);
29 const room = open_doc_room("OD Ticket", ticket_id);
30 socket.leave(room);
31
32 // Notify remaining viewers that someone left
33 notify_ticket_viewers({
34 socket: socket,
35 ticket_id: ticket_id,
36 });
37 });
38
39 socket.on("ticket_get_viewers", (ticket_id) => {
40 if (!in_ticket_room(socket, ticket_id)) return;
41 // Send current viewers list to the requesting user only
42 notify_ticket_viewers({
43 socket: socket,
44 ticket_id: ticket_id,
45 toUser: true, // saying that whenever we ask for viewers, send only to the user who asked
46 });
47 });
48
49 socket.on("notify_ticket_update", (ticket_id, field, value) => {
50 if (!(ticket_id && field)) return;
51 if (!in_ticket_room(socket, ticket_id)) return;
52 const ticket_room = open_doc_room("OD Ticket", ticket_id);
53 socket.to(ticket_room).emit("ticket_update", {
54 ticket_id,
55 user: socket.user,
56 field,
57 value,
58 });
59 });
60
61 // Typing indicators
62 socket.on("opendesk_ticket_typing", (ticket_id) => {
63 if (!in_ticket_room(socket, ticket_id)) return;
64 const ticket_room = open_doc_room("OD Ticket", ticket_id);
65
66 // Broadcast to all other users in the ticket room that this user is typing
67 socket.to(ticket_room).emit("opendesk_ticket_typing", {
68 ticket_id,
69 user: socket.user,
70 });
71 });
72
73 socket.on("opendesk_ticket_typing_stopped", (ticket_id) => {
74 if (!in_ticket_room(socket, ticket_id)) return;
75 const ticket_room = open_doc_room("OD Ticket", ticket_id);
76
77 // Broadcast to all other users in the ticket room that this user stopped typing
78 socket.to(ticket_room).emit("opendesk_ticket_typing_stopped", {
79 ticket_id,
80 user: socket.user,
81 });
82 });
83}
84
85function notify_ticket_viewers(args) {
86 if (!(args && args.socket && args.ticket_id)) return;
87
88 const socket = args.socket;
89 const ticket_id = args.ticket_id;
90
91 const ticket_room = open_doc_room("OD Ticket", ticket_id);
92
93 const clients = Array.from(socket.nsp.adapter.rooms.get(ticket_room) || []);
94
95 let users = [];
96
97 // Extract user information for each socket in the room
98 socket.nsp.sockets.forEach((sock) => {
99 if (clients.includes(sock.id)) {
100 users.push(sock.user);
101 }
102 });
103
104 const target_room = args.toUser
105 ? user_room(args.socket.user) // Send to specific user only
106 : ticket_room; // Send to all viewers in the ticket room
107
108 // Emit the notification with current viewers list
109 socket.nsp.to(target_room).emit("ticket_viewers", {
110 ticket_id,
111 users: JSON.stringify(Array.from(new Set(users))), // Remove duplicate users
112 total_viewers: Array.from(new Set(users)).length,
113 });
114}
115
116// Helper functions
117const in_ticket_room = (socket, ticket_id) =>
118 socket.rooms.has(open_doc_room("OD Ticket", ticket_id));
119const open_doc_room = (doctype, docname) =>
120 "open_doc:" + doctype + "/" + docname;
121const user_room = (user) => "user:" + user;
122const ticket_response_room = (ticket_id) => "ticket_response:" + ticket_id;
123
124module.exports = opendesk_handlers;
125