107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import { initDb } from "./db";
|
|
import { decode } from "./protocol";
|
|
import {
|
|
type Client,
|
|
getOrCreateSession,
|
|
getSession,
|
|
type WsData,
|
|
} from "./session";
|
|
|
|
const PORT = Number(process.env.PORT) || 4040;
|
|
|
|
// Initialize database
|
|
const DB_PATH = process.env.COLLABD_DB || "collabd.db";
|
|
initDb(DB_PATH);
|
|
console.debug(`database initialized: ${DB_PATH}`);
|
|
|
|
function isValidRoomName(name: unknown): name is string {
|
|
if (typeof name !== "string") return false;
|
|
if (name.length === 0 || name.length > 64) return false;
|
|
return /^[a-zA-Z0-9_-]+$/.test(name);
|
|
}
|
|
|
|
export const server = Bun.serve<WsData>({
|
|
port: PORT,
|
|
fetch(req, server) {
|
|
const url = new URL(req.url);
|
|
if (url.pathname === "/ws") {
|
|
const upgraded = server.upgrade(req, {
|
|
data: { room: null, client: null },
|
|
});
|
|
if (!upgraded) {
|
|
return new Response("websocket upgrade failed", { status: 400 });
|
|
}
|
|
return;
|
|
}
|
|
return new Response("collabd running");
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
// create client object once and store in ws.data
|
|
const client: Client = { ws };
|
|
ws.data.client = client;
|
|
console.debug("client connected");
|
|
},
|
|
message(ws, raw) {
|
|
const msg = decode(raw.toString());
|
|
if (!msg) return;
|
|
|
|
// reuse the client object from ws.data
|
|
const client = ws.data.client;
|
|
if (!client) return;
|
|
|
|
switch (msg.type) {
|
|
case "join": {
|
|
if (!isValidRoomName(msg.room)) {
|
|
try {
|
|
ws.send(
|
|
JSON.stringify({ type: "error", message: "invalid room name" }),
|
|
);
|
|
} catch (err) {
|
|
console.debug("failed to send error to client:", err);
|
|
}
|
|
break;
|
|
}
|
|
const session = getOrCreateSession(msg.room);
|
|
ws.data.room = msg.room;
|
|
session.join(client);
|
|
console.debug(`client joined room: ${msg.room}`);
|
|
break;
|
|
}
|
|
case "leave": {
|
|
if (ws.data.room) {
|
|
const session = getSession(ws.data.room);
|
|
session?.leave(client);
|
|
ws.data.room = null;
|
|
}
|
|
break;
|
|
}
|
|
case "update": {
|
|
if (ws.data.room) {
|
|
const session = getSession(ws.data.room);
|
|
session?.applyUpdate(new Uint8Array(msg.data), client);
|
|
}
|
|
break;
|
|
}
|
|
case "awareness": {
|
|
if (ws.data.room) {
|
|
const session = getSession(ws.data.room);
|
|
if (session && "data" in msg) {
|
|
session.broadcastAwareness(client, msg.data);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
close(ws) {
|
|
if (ws.data.room && ws.data.client) {
|
|
const session = getSession(ws.data.room);
|
|
session?.leave(ws.data.client);
|
|
}
|
|
console.debug("client disconnected");
|
|
},
|
|
},
|
|
});
|
|
|
|
console.log(`collabd listening on :${PORT}`);
|