Socket fun
This commit is contained in:
+52
-45
@@ -1,28 +1,20 @@
|
||||
/// <reference lib="deno.ns" />
|
||||
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<WebSocket> {
|
||||
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<unknown> {
|
||||
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<void> {
|
||||
});
|
||||
}
|
||||
|
||||
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<void>,
|
||||
): (ctx: Deno.TestContext) => Promise<void> {
|
||||
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);
|
||||
}),
|
||||
);
|
||||
|
||||
+49
-35
@@ -1,40 +1,54 @@
|
||||
/// <reference lib="deno.ns" />
|
||||
|
||||
const clientsMap = new Map<number, WebSocket>();
|
||||
let nextId = 1;
|
||||
export function startServer(
|
||||
hostname: string = "localhost",
|
||||
port: number = 8042,
|
||||
): () => Promise<void> {
|
||||
let nextId = 1;
|
||||
const clientsMap = new Map<number, WebSocket>();
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { startServer } from "./deno.ts";
|
||||
|
||||
startServer();
|
||||
Reference in New Issue
Block a user