connection + chat

This commit is contained in:
Daniel Bulant 2022-07-27 19:32:01 +02:00
parent 2206e2774f
commit 3d3aa884fe
7 changed files with 554 additions and 201 deletions

View file

@ -8,59 +8,19 @@ class FastEvent extends Event {
}
}
const hosts: { urls: string, credential?: string, username?: string }[] = ("stun.ipfire.org:3478\n" +
"stun.rolmail.net:3478\n" +
"stun.steinbeis-smi.de:3478\n" +
"stun.marcelproust.it:3478\n" +
"stun3.3cx.com:3478\n" +
"stun.voipraider.com:3478\n" +
"stun.kore.com:3478\n" +
"stun.voipstunt.com:3478\n" +
"stun.fairytel.at:3478\n" +
"stun.h4v.eu:3478\n" +
"stun.peethultra.be:3478\n" +
"stun.ortopediacoam.it:3478\n" +
"stun.infra.net:3478\n" +
"stun.vavadating.com:3478\n" +
"stun.mixvoip.com:3478\n" +
"stun.tele2.net:3478\n" +
"stun2.3cx.com:3478\n" +
"stun.myhowto.org:3478\n" +
"stun.cellmail.com:3478\n" +
"stun.poetamatusel.org:3478\n" +
"stun.textz.com:3478\n" +
"stun.romancecompass.com:3478\n" +
"stun.ixc.ua:3478\n" +
"stun.actionvoip.com:3478\n" +
"stun.bethesda.net:3478\n" +
"stun.parcodeinebrodi.it:3478\n" +
"stun.jay.net:3478\n" +
"stun.demos.ru:3478\n" +
"stun.cloopen.com:3478\n" +
"stun.crimeastar.net:3478\n" +
"stun.vivox.com:3478\n" +
"stun.openjobs.hu:3478\n" +
"stun.kaznpu.kz:3478\n" +
"stun.linphone.org:3478\n" +
"stun.l.google.com:19302\n" +
"stun.sonetel.net:3478").split("\n").map(t => ({ urls: "stun:" + t }));
hosts.push({
urls: 'turn:relay.backups.cz',
credential: 'webrtc',
username: 'webrtc'
},
const hosts: { urls: string, credential?: string, username?: string }[] = [
{
urls: 'turn:relay.backups.cz?transport=tcp',
credential: 'webrtc',
username: 'webrtc'
});
urls: "stun:openrelay.metered.ca:80",
}
]
class ConnectedClient extends EventTarget {
conn: RTCPeerConnection;
sendChannel: RTCDataChannel;
candidates: any[] = [];
state: RTCDataChannelState | null = null;
readyState: number = 0;
pings: number[] = [];
constructor(public ws: WebsocketConnection, public name: string) {
super();
@ -72,41 +32,62 @@ class ConnectedClient extends EventTarget {
}
initializeConnection() {
this.pings = [];
console.log("Initializing connection");
this.conn = new RTCPeerConnection({
iceServers: hosts
});
this.conn.onicecandidate = e => {
console.log(e);
console.log("candidate", e, e.candidate);
if (!e.candidate) return;
this.candidates.push(e.candidate);
this.ws.send(JSON.stringify({ t: "cand", target: this.name, d: e.candidate }));
};
this.conn.onicecandidateerror = (e) => console.error(e);
this.conn.ondatachannel = e => {
this.sendChannel = e.channel;
let timer: any;
this.sendChannel.onclose = (e) => {
clearInterval(timer);
this.statusChanged();
}
this.sendChannel.onopen = (e) => {
timer = setInterval(() => {
this.send({ t: "p", d: Date.now() });
}, 300);
this.statusChanged();
}
this.conn.ondatachannel = e => this.onDataChannel(e.channel);
}
onDataChannel(channel: RTCDataChannel) {
console.log("on data channel");
this.sendChannel = channel;
let timer: any;
this.sendChannel.onclose = (e) => {
clearInterval(timer);
this.statusChanged();
this.sendChannel.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.t) {
default:
console.log("MSG", msg);
this.dispatchEvent(new FastEvent(msg.t, msg.d));
}
};
}
this.sendChannel.onopen = (e) => {
timer = setInterval(() => {
this.send({ t: "p", d: Date.now() });
}, 100);
this.statusChanged();
}
this.statusChanged();
this.sendChannel.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.t) {
case "p":
this.send({
t: "pr",
d: msg.d,
y: Date.now()
})
break;
case "pr":
this.pings.push(Date.now() - msg.d);
if(this.pings.length > 15) this.pings = this.pings.slice(-15);
players.update(t => t);
break;
case "msg":
console.log("message", msg.d);
this.dispatchEvent(new FastEvent("message", msg.d));
messages.update(t => { t.push({ author: this.name, content: msg.d });return t})
break;
default:
console.log("MSG", msg);
this.dispatchEvent(new FastEvent(msg.t, msg.d));
}
};
}
send(data: any) {
@ -120,7 +101,10 @@ class ConnectedClient extends EventTarget {
this.initializeConnection();
}
this.state = this.sendChannel.readyState;
if (this.state === "open") this.readyState = 3;
if (["closing", "closed"].includes(this.state)) this.readyState = 4;
console.log("state", this.state);
players.update(t => t);
}
}
}
@ -130,13 +114,14 @@ export class WebsocketConnection extends EventTarget {
ws: WebSocket;
fast: Map<string, ConnectedClient> = new Map();
roomName: string | null = null;
roomId: string | null = null;
roomHost: string | null = null;
constructor(public name: string) {
super();
// @ts-ignore Initialized in the next function call
this.ws = null;
this.connect();
players.set(this.fast);
}
connect() {
@ -149,11 +134,15 @@ export class WebsocketConnection extends EventTarget {
console.log("WS closed");
lastError.set(e.reason || "Connection closed");
connection.set(null);
room.set(null);
list.set(null);
});
this.ws.addEventListener("error", (e) => {
console.error("WS error");
lastError.set("Connection error");
connection.set(null);
room.set(null);
list.set(null);
});
this.ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
@ -161,58 +150,79 @@ export class WebsocketConnection extends EventTarget {
switch (msg.t) {
case "cand": {
const fast = this.fast.get(msg.source);
if (!fast) return;
if (!fast) return console.log("No fast connection");
if (fast.readyState < 1) fast.readyState == 1;
players.set(this.fast);
console.log("Received candidates");
if (fast.state === "open") return console.log("Already open");
for (const candidate of msg.d) {
fast.conn.addIceCandidate(candidate).then();
}
fast.conn.addIceCandidate(msg.d).then();
break;
}
case "desc": {
const fast = this.fast.get(msg.source);
if (!fast) return;
if (!fast) return console.log("No fast connection");
if (fast.readyState < 2) fast.readyState == 2;
players.set(this.fast);
if (fast.state === "open") return console.log("Already open");
fast.conn.setRemoteDescription(msg.d)
.then(() => fast.conn.createAnswer())
.then(answer => fast.conn.setLocalDescription(answer))
.then(() =>
this.ws.send(JSON.stringify({ t: "desc", target: fast.name, d: fast.conn.localDescription }))
)
if (msg.d.type === "answer") {
fast.conn.setRemoteDescription(msg.d);
} else if (msg.d.type === "offer") {
fast.conn.setRemoteDescription(msg.d)
.then(() => fast.conn.createAnswer())
.then(answer => fast.conn.setLocalDescription(answer))
.then(() =>
this.ws.send(JSON.stringify({ t: "desc", target: fast.name, d: fast.conn.localDescription }))
)
}
break;
}
case "join": {
const fast = new ConnectedClient(this, msg.name);
this.fast.set(msg.name, fast);
if (fast.conn.localDescription) {
this.ws.send(JSON.stringify({ t: "desc", target: msg.name, d: fast.conn.localDescription }))
}
if (fast.candidates) {
this.ws.send(JSON.stringify({ t: "cand", target: msg.name, d: fast.candidates }));
const fast = new ConnectedClient(this, msg.client);
players.set(this.fast);
this.fast.set(msg.client, fast);
if (fast.candidates && fast.candidates.length) {
for (const candidate of fast.candidates) {
this.ws.send(JSON.stringify({ t: "cand", target: msg.client, d: candidate }));
}
}
messages.update(t => { t.push({ author: " SYS ", content: `${msg.client} joined`});return t})
break;
}
case "joined": {
const clients = msg.clients;
this.fast = new Map();
for (const client of clients) {
if (client === this.name) continue;
const fast = new ConnectedClient(this, client);
if (fast.conn.localDescription) {
this.ws.send(JSON.stringify({ t: "desc", target: msg.name, d: fast.conn.localDescription }))
}
if (fast.candidates) {
this.ws.send(JSON.stringify({ t: "cand", target: msg.name, d: fast.candidates }));
}
fast.conn.createOffer()
.then(offer => fast.conn.setLocalDescription(offer))
.then(() =>
this.ws.send(JSON.stringify({ t: "desc", target: client, d: fast.conn.localDescription }))
);
fast.sendChannel = fast.conn.createDataChannel("sendChannel");
fast.onDataChannel(fast.sendChannel);
this.fast.set(client, fast);
}
// missing break on purpose
players.set(this.fast);
messages.set([{
author: " SYS ", content: `${msg.client} joined`
}]);
this.roomName = msg.name;
this.roomHost = msg.host;
room.set({
name: msg.name,
host: msg.host
});
break;
}
case "create": {
this.roomName = msg.name;
this.roomId = msg.id;
this.roomHost = this.name;
room.set({
name: msg.name,
id: msg.id
host: this.name
});
messages.update(t => { t.push({ author: " SYS ", content: `${msg.name} created the room`});return t})
break;
}
case "leave": {
@ -220,15 +230,24 @@ export class WebsocketConnection extends EventTarget {
if (!fast) return;
fast.conn.close();
this.fast.delete(msg.name);
players.set(this.fast);
messages.update(t => { t.push({ author: " SYS ", content: `${msg.client} left`});return t})
break;
}
case "host": {
this.roomHost = msg.host;
room.update(t => { t!.host = this.roomHost!; return t });
messages.update(t => { t.push({ author: " SYS ", content: `${msg.host} is now host`});return t})
break;
}
case "left": {
console.log("Left room successfully");
this.roomName = null;
this.roomId = null;
room.set(null);
this.fast.forEach(connection => connection.conn.close());
this.fast = new Map();
players.set(this.fast);
messages.set([]);
break;
}
case "list": {
@ -245,10 +264,22 @@ export class WebsocketConnection extends EventTarget {
});
}
sendMessage(msg: string) {
if (!this.roomName) return console.log("Not in a room");
for(const [, client] of this.fast) {
client.send({ t: "msg", d: msg });
}
messages.update(t => { t.push({ author: this.name, content: msg }); return t });
}
createGame(name: string) {
this.ws.send(JSON.stringify({ t: "create", name: name }));
}
join(name: string) {
this.ws.send(JSON.stringify({ t: "join", name: name }));
}
refreshList() {
this.ws.send(JSON.stringify({ t: "list" }));
listLoading.set(true);
@ -259,8 +290,10 @@ export class WebsocketConnection extends EventTarget {
}
}
export const connection: Writable<WebsocketConnection|null> = writable(null);
export const list: Writable<{ id: string, name: string, count: number }[]|null> = writable(null);
export const connection: Writable<WebsocketConnection | null> = writable(null);
export const list: Writable<{ name: string, count: number }[] | null> = writable(null);
export const listLoading = writable(true);
export const lastError: Writable<string> = writable("");
export const room: Writable<{ name: string, id: string }|null> = writable(null);
export const room: Writable<{ name: string, host: string } | null> = writable(null);
export const players: Writable<Map<string, ConnectedClient>> = writable(new Map);
export const messages: Writable<{ author: string, content: string }[]> = writable([]);

View file

@ -0,0 +1,21 @@
<button on:click><slot /></button>
<style>
button {
width: 10em;
height: 3em;
border: 10px solid #bd5ce6;
background-color: #85E65C;
color: #bd5ce6;
border-radius: 0;
padding: 0.5em;
font-size: 1.5em;
font-family: inherit;
cursor: pointer;
}
button:active {
background-color: #bd5ce6;
color: #85E65C;
}
</style>

View file

@ -0,0 +1,5 @@
<script>
import Waiting from "./waiting.svelte";
</script>
<Waiting />

View file

@ -0,0 +1,168 @@
<script lang="ts">
import { connection, messages, players, room } from '$lib/Websocket';
import Button from '../components/button.svelte';
function startGame() {}
let content = "";
function sendMessage() {
if(!content.length || content.length > 512) return;
$connection!.sendMessage(content);
content = "";
}
$: if($messages.length > 512) {
$messages = $messages.slice(-512);
}
</script>
<div class="container">
<div class="playerlist">
<ul>
<li>
{$connection?.name}
{#if $room?.host === $connection?.name}
<span class="host">Host</span>
{/if}
<span class="state">YOU</span>
</li>
{#each [...$players.values()] as player (player.name)}
<li>
{player.name}
{#if $room?.host === player.name}
<span class="host">Host</span>
{/if}
<span class="state state-{player.readyState}"
>{['joined', 'connecting', 'connecting...', 'ready', 'reconnecting'][
player.readyState
]}</span
>
{#if player.pings && player.pings.length > 0}
<span class="pings">{Math.floor(player.pings.reduce((a, b) => a + b, 0) / player.pings.length * 10) / 10}ms</span>
{/if}
</li>
{/each}
</ul>
</div>
<main>
<div class="text">
{#each $messages as message}
<div class="message">
{#if message.author !== " SYS "}
<span class="name">{message.author}</span>:
{/if}
<span class="content">{message.content}</span>
</div>
{/each}
</div>
<div class="bottom">
<form action="/" on:submit|preventDefault={sendMessage}>
<input type="text" placeholder="Chat.." bind:value={content} on:submit|preventDefault={sendMessage}>
</form>
<Button on:click={() => sendMessage()}>Send</Button>
{#if $room?.host === $connection?.name}
<Button on:click={() => startGame()}>START GAME</Button>
{:else}
<Button>Ready</Button>
{/if}
</div>
</main>
</div>
<style>
.pings {
width: 3em;
text-align: right;
display: inline-block;
}
.container {
width: 100vw;
height: 100vh;
background-color: #85e65c;
color: #bd5ce6;
display: flex;
flex-direction: row;
align-items: center;
}
.playerlist {
width: 20em;
height: 100%;
background-color: #85e65c;
color: #bd5ce6;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-right: 10px solid #bd5ce6;
}
.state {
font-size: 0.8em;
margin-left: 0.5em;
background-color: #bd5ce6;
color: #85e65c;
border-radius: 0.25em;
padding: 0.3em;
}
.state.state-0 {
background-color: red;
}
.state.state-3 {
background-color: transparent;
color: #000;
}
.state.state-4 {
background-color: #ff0000;
color: #000;
}
ul {
list-style: none;
padding: 0;
}
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-grow: 1;
height: calc(100vh - 40px);
}
.text {
flex-grow: 1;
width: calc(100% - 2rem);
margin: 1em;
}
.bottom {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
flex-grow: 0;
height: 2em;
width: 100%;
}
form {
flex-grow: 1;
}
input {
width: 100%;
background-color: #85e65c;
color: #bd5ce6;
font-size: 1.5em;
font-family: inherit;
padding: 0.5em;
border: 10px solid #bd5ce6;
border-left: none;
border-right: none;
}
.text {
overflow: auto;
}
.text .message {
height: 1.5em;
}
.text .name {
font-weight: bold;
background-color: #bd5ce6;
color: #85e65c;
padding: 0.2em;
border-radius: 0.25em;
}
</style>

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { connection, lastError, list, listLoading } from "$lib/Websocket";
import { onMount } from "svelte";
import Button from "../components/button.svelte";
onMount(() => {
let i = setInterval(() => {
@ -35,6 +36,10 @@
error = "Creating game...";
$connection!.createGame(newGameName);
}
function connect(game: { name: string, count: number }) {
$connection!.join(game.name);
}
</script>
{#if creatingGame}
@ -44,8 +49,8 @@
{error}
</div>
<div class="controls">
<button on:click={() => creatingGame = false}>CANCEL</button>
<button on:click={submit}>SUBMIT</button>
<Button on:click={() => creatingGame = false}>CANCEL</Button>
<Button on:click={submit}>SUBMIT</Button>
</div>
</dialog>
{/if}
@ -54,20 +59,20 @@
<main>
<div class="flex">
<h1>Games - {$connection?.name}</h1>
<button on:click={() => creatingGame = true}>CREATE</button>
<Button on:click={() => creatingGame = true}>CREATE</Button>
</div>
<div class="status">
{#if $listLoading} Loading... {/if}
</div>
<ul>
{#if $list}
{#each $list as $game}
<li class="flex">
{#each $list as game}
<li class="flex" on:click={() => connect(game)}>
<span>
{$game.name}
{game.name}
</span>
<span>
{$game.count}
{game.count}
</span>
</li>
{/each}
@ -133,19 +138,7 @@
font-size: 1.5rem;
cursor: pointer;
}
button {
width: 10em;
height: 3em;
border: 1px solid #bd5ce6;
border-radius: 0;
padding: 0.5em;
font-size: 1.5rem;
border: 10px solid #bd5ce6;
background-color: #85E65C;
cursor: pointer;
}
button:active {
background-color: #bd5ce6;
color: #85E65C;
input {
font-family: inherit;
}
</style>

View file

@ -1,5 +1,6 @@
<script>
import { connection, lastError, WebsocketConnection } from "$lib/Websocket";
import Button from "../components/button.svelte";
var name = "";
var error = "";
@ -25,7 +26,7 @@
<div class="error">
{error || $lastError}
</div>
<button on:click={submit}>SUBMIT</button>
<Button on:click={submit}>SUBMIT</Button>
</main>
</div>
@ -57,20 +58,4 @@
color: #bd5ce6;
font-family: inherit;
}
button {
width: 10em;
height: 3em;
border: 10px solid #bd5ce6;
background-color: #85E65C;
color: #bd5ce6;
border-radius: 0;
padding: 0.5em;
font-size: 1.5em;
font-family: inherit;
cursor: pointer;
}
button:active {
background-color: #bd5ce6;
color: #85E65C;
}
</style>

View file

@ -1,15 +1,8 @@
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const PORT = 8080;
let i = 0;
function uuid() {
return (++i).toString();
}
/**
* @typedef Room
* @property {string} id
* @property {string} name
* @property {Client} host
* @property {Client[]} clients
@ -37,16 +30,24 @@ require("uWebSockets.js")
maxPayloadLength: 16 * 1024 * 1024,
upgrade: (res, req, context) => {
console.log(
`An Http connection wants to become WebSocket, URL: ${req.getUrl()}!`
`An Http connection wants to become WebSocket, URL: ${req.getUrl()}?${req.getQuery()}!`
);
let name = req.getQuery("name").trim().toLocaleLowerCase();
if (!name || typeof name !== "string" || name.length < 2 || name.length > 64 || !name.trim()) return res.end("invalid_name");
if (
!name ||
typeof name !== "string" ||
name.length < 2 ||
name.length > 64 ||
!name.trim()
)
return res.end("invalid_name");
name = name.trim();
if ([...clients.values()].find(client => client.name === name)) return res.end("name_used");
if ([...clients.values()].find((client) => client.name === name))
return res.end("name_used");
/* This immediately calls open handler, you must not use res after this call */
res.upgrade(
{
name
name,
},
/* Spell these correctly */
req.getHeader("sec-websocket-key"),
@ -65,101 +66,248 @@ require("uWebSockets.js")
connection: ws,
name: ws.name,
ipa: decoder.decode(ws.getRemoteAddressAsText()),
room: null
room: null,
});
},
message: (ws, message, isBinary) => {
if (isBinary) return ws.end();
if (isBinary) return ws.end(0, "invalid_message");
try {
const data = JSON.parse(decoder.decode(message));
if(data.t === "ping") return ws.ping();
switch(data.t) {
const client = clients.get(ws);
console.log(client.name, data);
if (data.t === "ping") return ws.ping();
switch (data.t) {
case "ping": {
return ws.ping();
}
case "create": {
const client = clients.get(ws);
if(client.room) return ws.send(JSON.stringify({t: "error", e: "already_in_room"}));
if (client.room)
return ws.send(
JSON.stringify({
t: "error",
e: "already_in_room",
})
);
const name = data.name.trim().toLocaleLowerCase();
if (!name || typeof name !== "string" || name.length < 2 || name.length > 64 || !name.trim()) return res.send(JSON.stringify({t: "error", e: "invalid_room_name"}));
if ([...rooms.values()].find(room => room.name === name)) return ws.send(JSON.stringify({t: "error", e: "room_name_used"}));
if (
!name ||
typeof name !== "string" ||
name.length < 2 ||
name.length > 64 ||
!name.trim()
)
return res.send(
JSON.stringify({
t: "error",
e: "invalid_room_name",
})
);
if (
[...rooms.values()].find(
(room) => room.name === name
)
)
return ws.send(
JSON.stringify({
t: "error",
e: "room_name_used",
})
);
const room = {
name: name,
host: ws,
clients: [client],
id: uuid()
};
rooms.set(room.id, room);
rooms.set(room.name, room);
client.room = room;
return ws.send(JSON.stringify({ t: "create", id: room.id, name: name }));
return ws.send(
JSON.stringify({ t: "create", name: name })
);
}
case "leave": {
const client = clients.get(ws);
const room = client.room;
if (!room) return ws.send(JSON.stringify({ t: "error", e: "room_not_found" }));
if (!room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", e: "not_in_room" }));
if (!room)
return ws.send(
JSON.stringify({
t: "error",
e: "room_not_found",
})
);
if (!room.clients.includes(client))
return ws.send(
JSON.stringify({ t: "error", e: "not_in_room" })
);
room.clients.splice(room.clients.indexOf(client), 1);
if (room.clients.length === 0) {
rooms.delete(room.id);
} else if(room.host == ws) {
rooms.delete(room.name);
} else if (room.host == ws) {
room.host = room.clients[0];
room.clients.forEach(client => client.connection.send(JSON.stringify({ t: "host", host: client.name })));
room.clients.forEach((client) =>
client.connection.send(
JSON.stringify({
t: "host",
host: client.name,
})
)
);
}
client.room = null;
room.clients.forEach(client => client.connection.send(JSON.stringify({ t: "leave", id: room.id, name: client.name })));
ws.send(JSON.stringify({ t: "left", id: room.id, name: client.name }));
room.clients.forEach((client) =>
client.connection.send(
JSON.stringify({
t: "leave",
name: client.name,
})
)
);
ws.send(
JSON.stringify({ t: "left", name: client.name })
);
break;
}
case "join": {
const client = clients.get(ws);
if (!client) return ws.end();
const room = rooms.get(msg.room);
if (!room) return ws.send(JSON.stringify({ t: "error", e: "room_not_found" }));
if(client.room) return ws.send(JSON.stringify({ t: "error", e: "already_in_other_room" }));
if (room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", e: "already_in_room" }));
if(room.clients.length > 5) return ws.send(JSON.stringify({ t: "error", e: "room_full" }));
room.clients.push(ws);
ws.room = room;
room.clients.slice(0, -2).forEach(client => client.connection.send(JSON.stringify({ t: "join", id: room.id, client: client.name })));
ws.send(JSON.stringify({ t: "joined", id: room.id, name: room.name, client: client.name, clients: room.clients.map(t => t.name) }));
if (!client) return ws.end(0, "missing_client");
const room = rooms.get(data.name);
if (!room)
return ws.send(
JSON.stringify({
t: "error",
e: "room_not_found",
})
);
if (client.room)
return ws.send(
JSON.stringify({
t: "error",
e: "already_in_other_room",
})
);
if (room.clients.includes(client))
return ws.send(
JSON.stringify({
t: "error",
e: "already_in_room",
})
);
if (room.clients.length > 5)
return ws.send(
JSON.stringify({ t: "error", e: "room_full" })
);
room.clients.push(client);
client.room = room;
const srcclient = client;
room.clients
.filter((t) => t !== client)
.forEach((client) =>
client.connection.send(
JSON.stringify({
t: "join",
client: srcclient.name,
})
)
);
ws.send(
JSON.stringify({
t: "joined",
name: room.name,
client: client.name,
host: room.host.name,
clients: room.clients.map((t) => t.name),
})
);
break;
}
case "cand":
case "desc": {
const client = clients.get(ws);
if (!client) return ws.end();
if (!client) return ws.end(0, "missing_client");
const room = client.room;
if (!room) return ws.send(JSON.stringify({ t: "error", e: "room_not_found" }));
if (!room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", e: "not_in_room" }));
const targetClient = room.clients.find(t => t.name === msg.target);
if(!targetClient) return ws.send(JSON.stringify({ t: "error", e: "target_not_found" }));
if(!room.clients.includes(targetClient)) return ws.send(JSON.stringify({ t: "error", e: "target_not_in_room" }));
targetClient.connection.send(JSON.stringify({ t: msg.t, id: room.id, source: client.name, d: msg.d }));
if (!room)
return ws.send(
JSON.stringify({
t: "error",
e: "room_not_found",
})
);
if (!room.clients.includes(client))
return ws.send(
JSON.stringify({ t: "error", e: "not_in_room" })
);
const targetClient = room.clients.find(
(t) => t.name === data.target
);
if (!targetClient)
return ws.send(
JSON.stringify({
t: "error",
e: "target_not_found",
})
);
if (!room.clients.includes(targetClient))
return ws.send(
JSON.stringify({
t: "error",
e: "target_not_in_room",
})
);
targetClient.connection.send(
JSON.stringify({
t: data.t,
source: client.name,
d: data.d,
})
);
}
case "list": {
ws.send(JSON.stringify({ t: "list", rooms: [...rooms.values()].filter(t => t.clients.length < 5).map(t => ({ id: t.id, name: t.name, count: t.clients.length }))}));
ws.send(
JSON.stringify({
t: "list",
rooms: [...rooms.values()]
.filter((t) => t.clients.length < 5)
.map((t) => ({
name: t.name,
host: t.host.name,
count: t.clients.length,
})),
})
);
break;
}
}
} catch (e) {
return ws.end();
console.warn(e);
return ws.end(0, "internal");
}
},
close: (ws, code, message) => {
console.log("DIS1", ws);
if (clients.get(ws)) {
const client = clients.get(ws);
console.log("DIS", client.name, client.ipa);
let room = client.room;
if (room) {
room.clients.splice(room.clients.indexOf(ws), 1);
if (room.clients.length === 0) {
rooms.delete(room.id);
} else if(room.host == ws) {
room.host = room.clients[0];
room.clients.forEach(client => client.connection.send(JSON.stringify({ t: "host", host: client.name })));
console.log("DIS1", ws, code, message);
try {
if (clients.get(ws)) {
const client = clients.get(ws);
console.log("DIS", client.name, client.ipa);
let room = client.room;
if (room) {
room.clients.splice(room.clients.indexOf(client), 1);
if (room.clients.length === 0) {
rooms.delete(room.name);
} else if (room.host == ws) {
room.host = room.clients[0];
room.clients.forEach((client) =>
client.connection.send(
JSON.stringify({
t: "host",
host: client.name,
})
)
);
}
const srcclient = client;
room.clients.forEach((client) =>
client.connection.send(JSON.stringify({ t: "leave", client: srcclient.name }))
);
}
}
} catch (e) {
console.warn("Error during closing", e);
}
clients.delete(ws);
},