colabbd/src/index.ts

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}`);