blastoise/websocket.ts

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 {}
},
};