103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import type { ServerWebSocket } from "bun";
|
|
import type { WsData } from "./channel";
|
|
import { hasPermission, findUserById } from "./db";
|
|
import { config } from "./config";
|
|
import { state } from "./state";
|
|
import { broadcastChannelList } from "./broadcast";
|
|
|
|
export const websocketHandlers = {
|
|
open(ws: ServerWebSocket<WsData>) {
|
|
const channel = state.channels.get(ws.data.channelId);
|
|
if (channel) {
|
|
channel.addClient(ws);
|
|
broadcastChannelList();
|
|
}
|
|
// Track connection by user ID
|
|
const userId = ws.data.userId;
|
|
if (userId) {
|
|
if (!state.userConnections.has(userId)) {
|
|
state.userConnections.set(userId, new Set());
|
|
}
|
|
state.userConnections.get(userId)!.add(ws);
|
|
}
|
|
},
|
|
|
|
close(ws: ServerWebSocket<WsData>) {
|
|
const channel = state.channels.get(ws.data.channelId);
|
|
if (channel) {
|
|
channel.removeClient(ws);
|
|
broadcastChannelList();
|
|
}
|
|
// Remove from user connections tracking
|
|
const userId = ws.data.userId;
|
|
if (userId && state.userConnections.has(userId)) {
|
|
state.userConnections.get(userId)!.delete(ws);
|
|
if (state.userConnections.get(userId)!.size === 0) {
|
|
state.userConnections.delete(userId);
|
|
}
|
|
}
|
|
},
|
|
|
|
message(ws: ServerWebSocket<WsData>, message: string | Buffer) {
|
|
try {
|
|
const data = JSON.parse(String(message));
|
|
|
|
// Handle channel switching
|
|
if (data.action === "switch" && data.channelId) {
|
|
const oldChannel = state.channels.get(ws.data.channelId);
|
|
const newChannel = state.channels.get(data.channelId);
|
|
if (!newChannel) {
|
|
ws.send(JSON.stringify({ type: "error", message: "Channel not found" }));
|
|
return;
|
|
}
|
|
if (oldChannel) oldChannel.removeClient(ws);
|
|
ws.data.channelId = data.channelId;
|
|
newChannel.addClient(ws);
|
|
ws.send(JSON.stringify({ type: "switched", channelId: data.channelId }));
|
|
broadcastChannelList();
|
|
return;
|
|
}
|
|
|
|
const channel = state.channels.get(ws.data.channelId);
|
|
if (!channel) {
|
|
console.log("[WS] No channel found for:", ws.data.channelId);
|
|
return;
|
|
}
|
|
|
|
// Check permission for control actions
|
|
const userId = ws.data.userId;
|
|
if (!userId) {
|
|
console.log("[WS] No userId on connection");
|
|
return;
|
|
}
|
|
|
|
const user = findUserById(userId);
|
|
if (!user) {
|
|
console.log("[WS] User not found:", userId);
|
|
return;
|
|
}
|
|
|
|
// Guests can never control playback
|
|
if (user.is_guest) {
|
|
console.log("[WS] Guest cannot control playback");
|
|
return;
|
|
}
|
|
|
|
// Check default permissions or user-specific permissions
|
|
const canControl = user.is_admin
|
|
|| config.defaultPermissions?.includes("control")
|
|
|| hasPermission(userId, "channel", ws.data.channelId, "control");
|
|
if (!canControl) {
|
|
console.log("[WS] User lacks control permission:", user.username);
|
|
return;
|
|
}
|
|
|
|
console.log("[WS] Control action:", data.action, "from", user.username);
|
|
if (data.action === "pause") channel.pause();
|
|
else if (data.action === "unpause") channel.unpause();
|
|
else if (data.action === "seek" && typeof data.timestamp === "number") channel.seek(data.timestamp);
|
|
else if (data.action === "jump" && typeof data.index === "number") channel.jumpTo(data.index);
|
|
} catch {}
|
|
},
|
|
};
|