diff --git a/deno.lock b/deno.lock
index 7b2008d..2cadff8 100644
--- a/deno.lock
+++ b/deno.lock
@@ -2,6 +2,7 @@
"version": "5",
"specifiers": {
"jsr:@std/assert@*": "1.0.19",
+ "jsr:@std/http@*": "1.1.3",
"jsr:@std/internal@^1.0.12": "1.0.14"
},
"jsr": {
@@ -11,6 +12,9 @@
"jsr:@std/internal"
]
},
+ "@std/http@1.1.3": {
+ "integrity": "73a98e05ff58aa18bea562d286172e6c815b1eddd4fcab194beda19e3683b88c"
+ },
"@std/internal@1.0.14": {
"integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7"
}
diff --git a/engine/lib/assertNever.ts b/engine/lib/assertNever.ts
new file mode 100644
index 0000000..dd4d548
--- /dev/null
+++ b/engine/lib/assertNever.ts
@@ -0,0 +1,3 @@
+export function assertNever(): never {
+ throw new Error("never");
+}
diff --git a/server/client.test.ts b/server/client.test.ts
index ae7e99b..88d5dce 100644
--- a/server/client.test.ts
+++ b/server/client.test.ts
@@ -1,107 +1,14 @@
///
import { assertEquals } from "jsr:@std/assert";
-import { startServer } from "./deno.ts";
-
-const hostname = "0.0.0.0";
-const port = 8042;
-const href = `ws://${hostname}:${port}`;
-
-function connect(url: string): Promise<[WebSocket, string[]]> {
- return new Promise((resolve, reject) => {
- const messages: string[] = [];
- const socket = new WebSocket(url);
-
- socket.onopen = () => resolve([socket, messages]);
- socket.onerror = (error) => reject(error);
- socket.onmessage = ({ data }) => {
- messages.push(data);
- };
- });
-}
-
-function close(socket: WebSocket): Promise {
- return new Promise((resolve) => {
- if (socket.readyState === WebSocket.CLOSED) {
- return resolve();
- }
- socket.onclose = () => resolve();
- socket.close();
- });
-}
-
-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();
- }
- };
-}
-
-function flush(): Promise {
- return new Promise((resolve) => setTimeout(resolve, 0));
-}
+import handler from "./handler.ts";
+import { close, connect, flush, href, setup } from "./testSetup.ts";
Deno.test(
- "Welcome all clients on connect.",
+ "test",
setup(async () => {
const [s1, m1] = await connect(href);
- const [s2, m2] = await connect(href);
-
await flush();
-
- assertEquals(m1, ["Welcome 1, type /join ."]);
- assertEquals(m2, ["Welcome 2, type /join ."]);
-
- await close(s2);
+ assertEquals(m1, []);
await close(s1);
- }),
-);
-
-Deno.test(
- "Messages all clients in a room on joining.",
- setup(async () => {
- const [s1, m1] = await connect(href);
- const [s2, m2] = await connect(href);
-
- await flush();
- m1.length = 0;
- m2.length = 0;
-
- s1.send("/join meh");
- s2.send("/join meh");
- await flush();
-
- assertEquals(m1, ["1 has joined!", "2 has joined!"]);
- assertEquals(m2, ["2 has joined!"]);
-
- await close(s2);
- await close(s1);
- }),
-);
-
-Deno.test(
- "Messages remaining members if one left the room.",
- setup(async () => {
- const [s1, m1] = await connect(href);
- const [s2, m2] = await connect(href);
-
- s1.send("/join meh");
- s2.send("/join meh");
- await flush();
-
- m1.length = 0;
- m2.length = 0;
- await close(s2);
- await flush();
-
- assertEquals(m1, ["2 has left!"]);
- assertEquals(m2, []);
-
- await close(s1);
- }),
+ }, handler),
);
diff --git a/server/handler.ts b/server/handler.ts
new file mode 100644
index 0000000..793b036
--- /dev/null
+++ b/server/handler.ts
@@ -0,0 +1,21 @@
+import { getCookies } from "jsr:@std/http/cookie";
+
+function getSessionId(request: Request): null | string {
+ getCookies(request.headers);
+ return null;
+}
+
+export default function (request: Request): Response {
+ if (request.headers.get("upgrade") !== "websocket") {
+ return new Response("Upgrade required.", { status: 426 });
+ }
+
+ const sessonId = getSessionId(request);
+ if (!sessonId) {
+ return new Response("Session requierd.", { status: 401 });
+ }
+
+ const { socket, response } = Deno.upgradeWebSocket(request);
+
+ return response;
+}
diff --git a/server/main.ts b/server/main.ts
index 8c2e840..8796ee3 100644
--- a/server/main.ts
+++ b/server/main.ts
@@ -1,3 +1,4 @@
-import { startServer } from "./deno.ts";
+import handler from "./handler.ts";
+import { startServer } from "./server.ts";
-startServer();
+startServer(handler);
diff --git a/server/ref/deno.test.ts b/server/ref/deno.test.ts
new file mode 100644
index 0000000..2292d19
--- /dev/null
+++ b/server/ref/deno.test.ts
@@ -0,0 +1,64 @@
+///
+import { assertEquals } from "jsr:@std/assert";
+import { close, connect, flush, href, setup } from "../testSetup.ts";
+import { getHandler } from "./deno.ts";
+
+Deno.test(
+ "Welcome all clients on connect.",
+ setup(async () => {
+ const [s1, m1] = await connect(href);
+ const [s2, m2] = await connect(href);
+
+ await flush();
+
+ assertEquals(m1, ["Welcome 1, type /join ."]);
+ assertEquals(m2, ["Welcome 2, type /join ."]);
+
+ await close(s2);
+ await close(s1);
+ }, getHandler()),
+);
+
+Deno.test(
+ "Messages all clients in a room on joining.",
+ setup(async () => {
+ const [s1, m1] = await connect(href);
+ const [s2, m2] = await connect(href);
+
+ await flush();
+ m1.length = 0;
+ m2.length = 0;
+
+ s1.send("/join meh");
+ s2.send("/join meh");
+ await flush();
+
+ assertEquals(m1, ["1 has joined!", "2 has joined!"]);
+ assertEquals(m2, ["2 has joined!"]);
+
+ await close(s2);
+ await close(s1);
+ }, getHandler()),
+);
+
+Deno.test(
+ "Messages remaining members if one left the room.",
+ setup(async () => {
+ const [s1, m1] = await connect(href);
+ const [s2, m2] = await connect(href);
+
+ s1.send("/join meh");
+ s2.send("/join meh");
+ await flush();
+
+ m1.length = 0;
+ m2.length = 0;
+ await close(s2);
+ await flush();
+
+ assertEquals(m1, ["2 has left!"]);
+ assertEquals(m2, []);
+
+ await close(s1);
+ }, getHandler()),
+);
diff --git a/server/deno.ts b/server/ref/deno.ts
similarity index 75%
rename from server/deno.ts
rename to server/ref/deno.ts
index 38df654..5695eb4 100644
--- a/server/deno.ts
+++ b/server/ref/deno.ts
@@ -1,9 +1,6 @@
///
-export function startServer(
- hostname: string = "localhost",
- port: number = 8042,
-): () => Promise {
+export function getHandler(): Deno.ServeHandler {
let nextId = 1;
const clientsMap = new Map();
@@ -70,23 +67,14 @@ export function startServer(
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 });
- }
+ return (request) => {
+ if (request.headers.get("upgrade") !== "websocket") {
+ return new Response("Upgrade Required", { status: 426 });
+ }
- const { socket, response } = Deno.upgradeWebSocket(request);
- handleConnection(socket);
+ const { socket, response } = Deno.upgradeWebSocket(request);
+ handleConnection(socket);
- return response;
- },
- );
-
- return () => {
- ac.abort();
- return server.finished;
+ return response;
};
}
diff --git a/server/server.ts b/server/server.ts
new file mode 100644
index 0000000..733d9f2
--- /dev/null
+++ b/server/server.ts
@@ -0,0 +1,15 @@
+///
+
+export function startServer(
+ handler: Deno.ServeHandler,
+ hostname: string = "localhost",
+ port: number = 8042,
+): () => Promise {
+ const ac = new AbortController();
+ const server = Deno.serve({ hostname, port, signal: ac.signal }, handler);
+
+ return () => {
+ ac.abort();
+ return server.finished;
+ };
+}
diff --git a/server/testSetup.ts b/server/testSetup.ts
new file mode 100644
index 0000000..8c11808
--- /dev/null
+++ b/server/testSetup.ts
@@ -0,0 +1,46 @@
+import { startServer } from "./server.ts";
+
+export const hostname = "0.0.0.0";
+export const port = 8042;
+export const href = `ws://${hostname}:${port}`;
+
+export function setup(
+ fn: (ctx: Deno.TestContext) => Promise,
+ handler: Deno.ServeHandler,
+): (ctx: Deno.TestContext) => Promise {
+ return async (ctx) => {
+ const shutdown = startServer(handler, hostname, port);
+ try {
+ await fn(ctx);
+ } finally {
+ await shutdown();
+ }
+ };
+}
+
+export function connect(url: string): Promise<[WebSocket, string[]]> {
+ return new Promise((resolve, reject) => {
+ const messages: string[] = [];
+ const socket = new WebSocket(url);
+
+ socket.onopen = () => resolve([socket, messages]);
+ socket.onerror = (error) => reject(error);
+ socket.onmessage = ({ data }) => {
+ messages.push(data);
+ };
+ });
+}
+
+export function close(socket: WebSocket): Promise {
+ return new Promise((resolve) => {
+ if (socket.readyState === WebSocket.CLOSED) {
+ return resolve();
+ }
+ socket.onclose = () => resolve();
+ socket.close();
+ });
+}
+
+export function flush(): Promise {
+ return new Promise((resolve) => setTimeout(resolve, 0));
+}