Initial commit

This commit is contained in:
savinmax 2025-06-14 15:07:58 +02:00
commit 888f397776
8 changed files with 186 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/
*.log

BIN
bun.lockb Executable file

Binary file not shown.

36
example/test.ts Normal file
View File

@ -0,0 +1,36 @@
import { WebSocket } from "ws";
let cliId = 1;
function newClient(room, interval) {
const id = cliId++;
let messageId = 0;
console.log(`Client #${id} room is`, room)
const ws = new WebSocket(`ws://localhost:8080/${room}`);
ws.addEventListener("open", () => {
ws.addEventListener("message", ev => {
console.log(`[Client#${id}] Received message:`, ev.data.toString());
});
const int = setInterval(() => {
console.log(`[Client#${id}] Sending message ${messageId}`);
ws.send(`Message#${messageId++} from Client#${id}`);
}, interval);
ws.addEventListener("close", () => {
console.log(`[Client#${id}] Closed`);
clearInterval(int);
});
});
return ws;
}
const user1 = newClient("foo", 900);
const user2 = newClient("bar", 500);
const user3 = newClient("foo", 400);
const user4 = newClient("bar", 800);
setTimeout(() => {
user1.close();
user2.close();
user3.close();
user4.close();
}, 4000);

41
index.ts Normal file
View File

@ -0,0 +1,41 @@
import {serve, ServerWebSocket} from "bun";
interface WSData {
room: string;
}
const PORT = 8080;
const MAIN_CHANNEL = "central";
const msg = (data: Record<string, unknown>) => JSON.stringify(data);
const server = serve({
fetch(req, server) {
const url = new URL(req.url);
const room = url.pathname.split("/")[1] ?? MAIN_CHANNEL;
if (server.upgrade(req, {data: {room}})) {
return;
}
return new Response("Upgrade failed :(", { status: 500 });
},
websocket: {
open(ws: ServerWebSocket<WSData>) {
const room = ws.data.room ?? MAIN_CHANNEL;
console.log(`${ws.remoteAddress} connected to '${room}' room`);
ws.subscribe(room);
},
message(ws, message) {
const room = ws.data.room ?? MAIN_CHANNEL;
ws.publish(room, message as unknown as BufferSource);
},
close(ws) {
const room = ws.data.room ?? MAIN_CHANNEL;
console.log(`${ws.remoteAddress} disconnected from '${room}' room`);
ws.unsubscribe(room);
ws.publish(room, msg({message: "someone left"}));
},
},
port: PORT,
});
console.log(`Listening on ${server.hostname}:${server.port}`);

30
package-lock.json generated Normal file
View File

@ -0,0 +1,30 @@
{
"name": "websocket-relay",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "websocket-relay",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"bun-types": "^1.0.1"
}
},
"node_modules/bun-types": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.1.tgz",
"integrity": "sha512-7NrXqhMIaNKmWn2dSWEQ50znMZqrN/5Z0NBMXvQTRu/+Y1CvoXRznFy0pnqLe024CeZgVdXoEpARNO1JZLAPGw==",
"dev": true
}
},
"dependencies": {
"bun-types": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.1.tgz",
"integrity": "sha512-7NrXqhMIaNKmWn2dSWEQ50znMZqrN/5Z0NBMXvQTRu/+Y1CvoXRznFy0pnqLe024CeZgVdXoEpARNO1JZLAPGw==",
"dev": true
}
}
}

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "websocket-relay",
"version": "1.0.0",
"author": "",
"main": "index.js",
"devDependencies": {
"bun-types": "^1.0.1",
"ws": "^8.18.2"
},
"description": "",
"license": "ISC",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

41
test/server.test.ts Normal file
View File

@ -0,0 +1,41 @@
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
import { WebSocket } from "ws";
describe("Test connections", () => {
let process: ChildProcessWithoutNullStreams;
beforeAll(async () => {
process = spawn("bun", ["run", "./index.ts"]);
await new Promise<void>((resolve, reject) => {
process.on("error", (err) => reject(err))
process.stdout.on("data", chunk => {
if (chunk.toString().includes("Listening")) {
resolve();
}
});
});
});
afterAll(() => {
process.kill();
});
it("test", async () => {
const ws1 = new WebSocket("ws://localhost:8080/");
const ws2 = new WebSocket("ws://localhost:8080/");
await new Promise<void>((resolve, reject) => {
ws1.onerror = reject;
ws2.onerror = reject;
ws2.onmessage = (data) => {
expect(data.data).toBe("TEST");
resolve();
};
ws1.onopen = () => {
ws1.send("TEST");
};
});
});
});

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": [
"ESNext"
],
"module": "nodenext",
"target": "esnext",
"moduleResolution": "nodenext",
"strict": false,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"outDir": "dist/",
"types": [
"bun-types" // add Bun global
]
}
}