initial commit

This commit is contained in:
Ludwig Lehnert 2025-05-08 23:08:22 +02:00
commit e6a8e4795a
Signed by: ludwig
SSH Key Fingerprint: SHA256:4vshH9GJ8TLO1RS2fY6rDDLnq7+KVvSClCY+uEhYYRA
6 changed files with 225 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# arca-signaling
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.12. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

36
bun.lock Normal file
View File

@ -0,0 +1,36 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "arca-signaling",
"dependencies": {
"ulid": "^3.0.0",
"ws": "^8.18.2",
},
"devDependencies": {
"@types/bun": "latest",
"@types/ws": "^8.18.1",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.2.12", "", { "dependencies": { "bun-types": "1.2.12" } }, "sha512-lY/GQTXDGsolT/TiH72p1tuyUORuRrdV7VwOTOjDOt8uTBJQOJc5zz3ufwwDl0VBaoxotSk4LdP0hhjLJ6ypIQ=="],
"@types/node": ["@types/node@22.15.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"ulid": ["ulid@3.0.0", "", { "bin": { "ulid": "dist/cli.js" } }, "sha512-yvZYdXInnJve6LdlPIuYmURdS2NP41ZoF4QW7SXwbUKYt53+0eDAySO+rGSvM2O/ciuB/G+8N7GQrZ1mCJpuqw=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
}
}

95
index.ts Normal file
View File

@ -0,0 +1,95 @@
import { WebSocket, WebSocketServer } from 'ws';
import { ulid } from 'ulid';
const wss = new WebSocketServer({ port: 8080 });
const rooms = new Map<string, Set<string>>();
const sockets = new Map<string, WebSocket>();
wss.on('connection', (ws) => {
let peerId = ulid();
const join = (room: string) => {
if (!rooms.has(room)) {
rooms.set(room, new Set([peerId]));
} else {
rooms.get(room)!.add(peerId);
}
};
const unjoinAll = () => {
rooms.entries().forEach(([room, peers]) => {
peers.delete(peerId);
if (!peers.size) rooms.delete(room);
});
};
ws.on('message', (message) => {
try {
const text = message.toString('utf8');
const data = JSON.parse(text);
if (data.type === 'self') {
ws.send(JSON.stringify({
type: 'self',
id: peerId.toString(),
}));
return;
}
if (data.type === 'join' && typeof data.room === 'string') {
unjoinAll();
join(data.room);
const peers = rooms.get(data.room) ?? [];
peers.forEach((peer) => {
sockets.get(peer)!.send(JSON.stringify({
type: 'peers',
peers: peers,
}));
});
return;
}
if (data.type === 'offer' && typeof data.peer === 'string' && typeof data.offer === 'object') {
const otherWS = sockets.get(data.peer);
if (!otherWS) return;
otherWS.send(JSON.stringify({
type: 'offer',
from: peerId,
offer: data.offer,
}));
return;
}
if (data.type === 'accept' && typeof data.peer === 'string' && typeof data.counterOffer === 'object') {
const otherWS = sockets.get(data.peer);
if (!otherWS) return;
otherWS.send(JSON.stringify({
type: 'accept',
from: peerId,
counterOffer: data.counterOffer,
}));
return;
}
throw 0;
} catch (e) {
ws.send(JSON.stringify({
type: 'error',
message: 'Bad Request',
}));
}
});
ws.on('close', () => {
unjoinAll();
sockets.delete(peerId);
});
});

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "arca-signaling",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest",
"@types/ws": "^8.18.1"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"ulid": "^3.0.0",
"ws": "^8.18.2"
}
}

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}