diff --git a/server/client.test.ts b/server/client.test.ts
index 2dcefc3..599791a 100644
--- a/server/client.test.ts
+++ b/server/client.test.ts
@@ -1,28 +1,20 @@
///
import { assertEquals } from "jsr:@std/assert";
+import { startServer } from "./deno.ts";
-const url = "ws://0.0.0.0:6969";
+const hostname = "0.0.0.0";
+const port = 8042;
+const href = `ws://${hostname}:${port}`;
-function connect(url: string): Promise {
- return new Promise((resolve, reject) => {
+async function connect(url: string): Promise<[WebSocket, string[]]> {
+ return new Promise((resolve) => {
+ const messages: string[] = [];
const socket = new WebSocket(url);
- socket.onopen = () => resolve(socket);
- socket.onerror = (e) => reject(e);
- });
-}
-function nextMessage(socket: WebSocket): Promise {
- return new Promise((resolve, reject) => {
- const onMessage = (e: MessageEvent) => {
- socket.removeEventListener("message", onMessage);
- try {
- resolve(JSON.parse(e.data));
- } catch {
- resolve(e.data);
- }
+ socket.onmessage = ({ data }) => {
+ messages.push(data);
};
- socket.addEventListener("message", onMessage);
- socket.addEventListener("error", reject, { once: true });
+ socket.onopen = () => resolve([socket, messages]);
});
}
@@ -36,36 +28,51 @@ function close(socket: WebSocket): Promise {
});
}
-Deno.test("broadcasts a message on connect to all clients", async () => {
- const s1 = await connect(url);
- const s2 = await connect(url);
- assertEquals(await nextMessage(s1), "2 has joined!");
+function setup(
+ fn: (ctx: Deno.TestContext) => Promise,
+): (ctx: Deno.TestContext) => Promise {
+ return async (ctx) => {
+ const shutdown = startServer(hostname, port);
+ try {
+ await fn(ctx);
+ } finally {
+ await shutdown();
+ }
+ };
+}
- const s3 = connect(url);
- Promise.all([nextMessage(s1), nextMessage(s2)]).then((messages) => {
- messages.forEach((message) => {
- assertEquals(message, "3 has joined!");
- });
- });
+Deno.test(
+ "broadcasts a message on connect to all clients",
+ setup(async () => {
+ const [s1, m1] = await connect(href);
+ const [s2, m2] = await connect(href);
- await close(s1);
- await close(s2);
- await close(await s3);
-});
+ assertEquals(m1, ["1 has joined!", "2 has joined!"]);
+ assertEquals(m2, ["2 has joined!"]);
-Deno.test("removes a client from broadcast after it disconnects", async () => {
- const s4 = await connect(url);
- const s5 = await connect(url);
- const s6 = await connect(url);
+ await close(s2);
+ await close(s1);
+ }),
+);
- await close(s5);
+Deno.test(
+ "removes a client from broadcast after it disconnects",
+ setup(async () => {
+ const [s1, m1] = await connect(href);
+ const [s2, m2] = await connect(href);
+ const [s3, m3] = await connect(href);
- Promise.all([nextMessage(s4), nextMessage(s6)]).then((messages) => {
- messages.forEach((message) => {
- assertEquals(message, "5 has left!");
- });
- });
+ m1.length = 0;
+ m2.length = 0;
+ m3.length = 0;
- await close(s4);
- await close(s6);
-});
+ await close(s3);
+
+ assertEquals(m1, ["3 has left!"]);
+ assertEquals(m2, ["3 has left!"]);
+ assertEquals(m3, []);
+
+ await close(s2);
+ await close(s1);
+ }),
+);
diff --git a/server/deno.ts b/server/deno.ts
index acb792c..7cf3e3d 100644
--- a/server/deno.ts
+++ b/server/deno.ts
@@ -1,40 +1,54 @@
///
-const clientsMap = new Map();
-let nextId = 1;
+export function startServer(
+ hostname: string = "localhost",
+ port: number = 8042,
+): () => Promise {
+ let nextId = 1;
+ const clientsMap = new Map();
-const handleConnection = (socket: WebSocket): void => {
- const id = nextId++;
-
- socket.onopen = () => {
- clientsMap.set(id, socket);
- broadcast(`${id} has joined!`);
- };
-
- socket.onclose = () => {
- clientsMap.delete(id);
- broadcast(`${id} has left!`);
- };
-
- socket.onmessage = console.log;
- socket.onerror = console.error;
-};
-
-function broadcast(message: string): void {
- for (const socket of clientsMap.values()) {
- if (socket.readyState === WebSocket.OPEN) {
- socket.send(message);
+ const broadcast = (message: string): void => {
+ for (const socket of clientsMap.values()) {
+ if (socket.readyState === WebSocket.OPEN) {
+ socket.send(message);
+ }
}
- }
+ };
+
+ const handleConnection = (socket: WebSocket): void => {
+ const id = nextId++;
+
+ socket.onopen = () => {
+ clientsMap.set(id, socket);
+ broadcast(`${id} has joined!`);
+ };
+
+ socket.onclose = () => {
+ clientsMap.delete(id);
+ broadcast(`${id} has left!`);
+ };
+
+ socket.onmessage = console.log;
+ socket.onerror = console.error;
+ };
+
+ const ac = new AbortController();
+ const server = Deno.serve(
+ { hostname, port, signal: ac.signal },
+ (request) => {
+ if (request.headers.get("upgrade") !== "websocket") {
+ return new Response("Upgrade Required", { status: 426 });
+ }
+
+ const { socket, response } = Deno.upgradeWebSocket(request);
+ handleConnection(socket);
+
+ return response;
+ },
+ );
+
+ return () => {
+ ac.abort();
+ return server.finished;
+ };
}
-
-Deno.serve({ port: 6969 }, (request) => {
- if (request.headers.get("upgrade") !== "websocket") {
- return new Response("Upgrade Required", { status: 426 });
- }
-
- const { socket, response } = Deno.upgradeWebSocket(request);
- handleConnection(socket);
-
- return response;
-});
diff --git a/server/main.ts b/server/main.ts
new file mode 100644
index 0000000..8c2e840
--- /dev/null
+++ b/server/main.ts
@@ -0,0 +1,3 @@
+import { startServer } from "./deno.ts";
+
+startServer();