diff options
Diffstat (limited to 'client-web')
-rw-r--r-- | client-web/public/app.html | 32 | ||||
-rw-r--r-- | client-web/public/assets/style/master.css | 4 | ||||
-rw-r--r-- | client-web/public/start.html | 24 | ||||
-rw-r--r-- | client-web/source/index.ts | 2 | ||||
-rw-r--r-- | client-web/source/local_user.ts | 4 | ||||
-rw-r--r-- | client-web/source/protocol/mod.ts | 46 | ||||
-rw-r--r-- | client-web/source/remote_user.ts | 12 | ||||
-rw-r--r-- | client-web/source/rnnoise.ts | 2 | ||||
-rw-r--r-- | client-web/source/room.ts | 46 | ||||
-rw-r--r-- | client-web/source/user.ts | 13 |
10 files changed, 113 insertions, 72 deletions
diff --git a/client-web/public/app.html b/client-web/public/app.html index 493796f..693e9a2 100644 --- a/client-web/public/app.html +++ b/client-web/public/app.html @@ -1,20 +1,24 @@ <!DOCTYPE html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <script defer async type="module" src="/_assets/bundle.js"></script> - <link rel="stylesheet" href="/_assets/style/master.css" /> + <script defer async type="module" src="/assets/bundle.js"></script> + <link rel="stylesheet" href="/assets/style/master.css" /> - <title>keks-meet</title> - </head> + <title>keks-meet</title> + </head> - <body> - <p> - keks-meet needs evil javascript to be enabled. Don't be afraid though, all - the code is free (AGPL-3.0-only)! Look at it on - <a href="https://codeberg.org/metamuffin/keks-meet">codeberg</a> - </p> - </body> + <body> + <p> + keks-meet needs evil javascript to be enabled. Don't be afraid + though, all the code is free (AGPL-3.0-only)! Look at it on + <a href="https://codeberg.org/metamuffin/keks-meet">codeberg</a> + </p> + <p> + If you have JS enabled, check the browser console to see if + something else failed + </p> + </body> </html> diff --git a/client-web/public/assets/style/master.css b/client-web/public/assets/style/master.css index 0b9e205..8a22d1b 100644 --- a/client-web/public/assets/style/master.css +++ b/client-web/public/assets/style/master.css @@ -10,8 +10,8 @@ } :root { - --bg: #263238; - --bg-dark: #000a12; + --bg: #212121; + --bg-dark: #070707; --bg-light: #354b58; --bg-lighter: #4f5b62; --bg-disabled: #720000; diff --git a/client-web/public/start.html b/client-web/public/start.html index 4a04174..db62bcf 100644 --- a/client-web/public/start.html +++ b/client-web/public/start.html @@ -18,8 +18,9 @@ the third version of the GNU Affero General Public Licence only. </p> <p> - To get started, just enter a unique idenfier, click 'Join', then - share the URL with your partner. + To get started, click 'Join' and share the URL with your + partner. You can also optionally customize the url by entering a + <b>secure/unguessable(!!!)</b> identifier below. </p> <noscript> keks-meet needs evil javascript to be enabled. Don't be afraid @@ -30,18 +31,21 @@ const room_input = document.createElement("input"); room_input.type = "text"; room_input.id = "room-id-input"; - room_input.placeholder = "Room ID (leave blank for random id)"; + room_input.placeholder = "Room ID"; const submit = document.createElement("input"); submit.type = "button"; submit.addEventListener("click", () => { - if (room_input.value.length == 0) - room_input.value = Math.floor(Math.random() * 0x100000) - .toString(16) - .padStart(5, "0"); - window.location.pathname = `/${encodeURIComponent( - room_input.value - )}`; + if (room_input.value.length == 0) { + const random = window.crypto.getRandomValues( + new Uint8Array(32) + ); + room_input.value = Array.from(random) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + } + const url = `/room#${encodeURIComponent(room_input.value)}`; + window.location.href = url; }); submit.value = "Join room!"; diff --git a/client-web/source/index.ts b/client-web/source/index.ts index 21bf4b8..5695573 100644 --- a/client-web/source/index.ts +++ b/client-web/source/index.ts @@ -19,7 +19,7 @@ export interface User { window.onload = () => main() export async function main() { - document.body.querySelector("p")?.remove() + document.body.querySelectorAll("p").forEach(e => e.remove()) log("*", "starting up") const room_name = window.location.pathname.substring("/".length) const conn = await (new SignalingConnection().connect(room_name)) diff --git a/client-web/source/local_user.ts b/client-web/source/local_user.ts index bb5b779..efedfc2 100644 --- a/client-web/source/local_user.ts +++ b/client-web/source/local_user.ts @@ -12,8 +12,8 @@ export class LocalUser extends User { mic_gain?: GainNode default_gain: number = PREFS.microphone_gain - constructor(room: Room, id: number, name: string) { - super(room, id, name) + constructor(room: Room, id: number) { + super(room, id) this.el.classList.add("local") this.local = true this.create_controls() diff --git a/client-web/source/protocol/mod.ts b/client-web/source/protocol/mod.ts index 4fbd607..53cbb86 100644 --- a/client-web/source/protocol/mod.ts +++ b/client-web/source/protocol/mod.ts @@ -1,5 +1,6 @@ +import { ClientboundPacket, RelayMessage, ServerboundPacket } from "../../../common/packets.d.ts" import { log } from "../logger.ts" -import { crypto_seeded_key, crypt_hash } from "./crypto.ts" +import { crypto_encrypt, crypto_seeded_key, crypt_decrypt, crypt_hash } from "./crypto.ts" export class SignalingConnection { room!: string @@ -7,6 +8,9 @@ export class SignalingConnection { signaling_id!: string key!: CryptoKey + control_handler: (_packet: ClientboundPacket) => void = () => { } + relay_handler: (_sender: number, _message: RelayMessage) => void = () => { } + constructor() { } async connect(room: string): Promise<SignalingConnection> { this.key = await crypto_seeded_key(room) @@ -14,10 +18,48 @@ export class SignalingConnection { log("ws", "connecting…") const ws_url = new URL(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling/${encodeURIComponent(this.signaling_id)}`) this.websocket = new WebSocket(ws_url) - await new Promise(r => this.websocket!.onopen = r) + this.websocket.onerror = () => this.on_error() + this.websocket.onmessage = e => { + if (typeof e.data == "string") this.on_message(e.data) + } + await new Promise<void>(r => this.websocket!.onopen = () => { + this.on_open() + r() + }) log("ws", "connection opened") return this } + on_close() { + log("ws", "websocket closed"); + setTimeout(() => { + window.location.reload() + }, 1000) + } + on_open() { + log("ws", "websocket opened"); + setInterval(() => this.send_control({ ping: null }), 30000) // stupid workaround for nginx disconnecting inactive connections + } + on_error() { + log("error", "websocket error occurred!") + } + async on_message(data: string) { + const packet: ClientboundPacket = JSON.parse(data) // TODO dont crash if invalid + this.control_handler(packet) + if (packet.message) { + const inner_json = await crypt_decrypt(this.key, packet.message.message) + const inner: RelayMessage = JSON.parse(inner_json) // TODO make sure that protocol spec is met + this.relay_handler(packet.message.sender, inner) + } + } + send_control(data: ServerboundPacket) { + log("ws", `-> ${data.relay?.recipient ?? "*"}`, data) + this.websocket.send(JSON.stringify(data)) + } + async send_relay(data: RelayMessage, recipient?: number | null) { + recipient ??= undefined // null -> undefined + const message = await crypto_encrypt(this.key, JSON.stringify(data)) + this.send_control({ relay: { recipient, message } }) + } } diff --git a/client-web/source/remote_user.ts b/client-web/source/remote_user.ts index 6cc57a5..5283a7b 100644 --- a/client-web/source/remote_user.ts +++ b/client-web/source/remote_user.ts @@ -10,13 +10,13 @@ export class RemoteUser extends User { peer: RTCPeerConnection negotiation_busy = false - constructor(room: Room, id: number, name: string) { - super(room, id, name) - log("usermodel", `added remote user: ${id} ${JSON.stringify(name)}`) + constructor(room: Room, id: number) { + super(room, id) + log("usermodel", `added remote user: ${id}`) this.peer = new RTCPeerConnection(servers) this.peer.onicecandidate = ev => { if (!ev.candidate) return - room.websocket_send({ relay: { recipient: this.id, message: { ice_candidate: ev.candidate.toJSON() } } }) + room.signaling.send_relay({ ice_candidate: ev.candidate.toJSON() }, this.id) } this.peer.ontrack = ev => { const t = ev.track @@ -38,7 +38,7 @@ export class RemoteUser extends User { await this.peer.setLocalDescription(offer_description) const offer = { type: offer_description.type, sdp: offer_description.sdp } log("webrtc", `sent offer: ${this.name}`, { a: offer }) - this.room.websocket_send({ relay: { recipient: this.id, message: { offer } } }) + this.room.signaling.send_relay({ offer }, this.id) } async on_offer(offer: RTCSessionDescriptionInit) { this.negotiation_busy = true @@ -52,7 +52,7 @@ export class RemoteUser extends User { await this.peer.setLocalDescription(answer_description) const answer = { type: answer_description.type, sdp: answer_description.sdp } log("webrtc", `sent answer: ${this.name}`, { a: answer }) - this.room.websocket_send({ relay: { recipient: this.id, message: { answer } } }) + this.room.signaling.send_relay({ answer }, this.id) this.negotiation_busy = false } async on_answer(answer: RTCSessionDescriptionInit) { diff --git a/client-web/source/rnnoise.ts b/client-web/source/rnnoise.ts index d6efb3b..2e0f857 100644 --- a/client-web/source/rnnoise.ts +++ b/client-web/source/rnnoise.ts @@ -23,7 +23,7 @@ export async function get_rnnoise_node(context: AudioContext): Promise<RNNoiseNo if (!RNNoiseNode) { log("rnnoise", "loading wasm...") script = document.createElement("script") - script.src = "/_assets/rnnoise/rnnoise-runtime.js" + script.src = "/assets/rnnoise/rnnoise-runtime.js" script.defer = true document.head.appendChild(script) //@ts-ignore asdfsfad diff --git a/client-web/source/room.ts b/client-web/source/room.ts index 6c9a7ba..9f27230 100644 --- a/client-web/source/room.ts +++ b/client-web/source/room.ts @@ -4,7 +4,7 @@ import { log } from "./logger.ts"; import { RemoteUser } from "./remote_user.ts"; import { User } from "./user.ts"; import { LocalUser } from "./local_user.ts"; -import { ServerboundPacket, ClientboundPacket } from "../../common/packets.d.ts"; +import { ClientboundPacket, RelayMessage } from "../../common/packets.d.ts"; import { SignalingConnection } from "./protocol/mod.ts"; export class Room { @@ -17,14 +17,13 @@ export class Room { constructor(public signaling: SignalingConnection) { this.el = document.createElement("div") this.el.classList.add("room") + this.signaling.control_handler = this.control_handler + this.signaling.relay_handler = this.relay_handler } - websocket_send(data: ServerboundPacket) { - log("ws", `-> ${data.relay?.recipient ?? "*"}`, data) - // this.websocket.send(JSON.stringify(data)) - } - websocket_message(packet: ClientboundPacket) { - log("ws", `<- ${packet.message?.sender ?? "control packet"}: `, packet); + control_handler(packet: ClientboundPacket) { + if (packet.message) return // let the relay handler do that + log("ws", `<- [control packet]: `, packet); if (packet.init) { this.my_id = packet.init.your_id // no need to check compat for now because this is hosted in the same place @@ -33,9 +32,9 @@ export class Room { const p = packet.client_join log("*", `${p.id} joined`); if (p.id == this.my_id) { - this.local_user = new LocalUser(this, p.id, p.name); + this.local_user = new LocalUser(this, p.id); } else { - const ru = new RemoteUser(this, p.id, p.name) + const ru = new RemoteUser(this, p.id) this.local_user.add_initial_to_remote(ru) ru.offer() this.users.set(p.id, ru) @@ -48,26 +47,17 @@ export class Room { this.users.delete(p.id) this.remote_users.delete(p.id) return - } else if (packet.message) { - const p = packet.message; - const sender = this.users.get(p.sender) - if (sender instanceof RemoteUser) { - // if (p.message.ice_candidate) sender.add_ice_candidate(p.message.ice_candidate) - // if (p.message.offer) sender.on_offer(p.message.offer) - // if (p.message.answer) sender.on_answer(p.message.answer) - } else { - console.log("!", p, sender); - } } + } - websocket_close() { - log("ws", "websocket closed"); - setTimeout(() => { - window.location.reload() - }, 1000) - } - websocket_open() { - log("ws", "websocket opened"); - setInterval(() => this.websocket_send({ ping: null }), 30000) // stupid workaround for nginx disconnecting inactive connections + relay_handler(sender_id: number, message: RelayMessage) { + const sender = this.users.get(sender_id) + if (sender instanceof RemoteUser) { + if (message.ice_candidate) sender.add_ice_candidate(message.ice_candidate) + if (message.offer) sender.on_offer(message.offer) + if (message.answer) sender.on_answer(message.answer) + } else { + console.log("!", message, sender); + } } }
\ No newline at end of file diff --git a/client-web/source/user.ts b/client-web/source/user.ts index dbf2862..057cdf4 100644 --- a/client-web/source/user.ts +++ b/client-web/source/user.ts @@ -6,13 +6,12 @@ import { TrackHandle } from "./track_handle.ts"; export abstract class User { - el: HTMLElement - - local = false - + protected el: HTMLElement + public local = false + public name?: string protected tracks: Set<TrackHandle> = new Set() - constructor(public room: Room, public id: number, public name: string) { + constructor(public room: Room, public id: number,) { this.el = document.createElement("div") this.el.classList.add("user") this.room.el.append(this.el) @@ -34,11 +33,13 @@ export abstract class User { }) } + get display_name() { return this.name ?? `guest (${this.id})` } + setup_view() { const info_el = document.createElement("div") info_el.classList.add("info") const name_el = document.createElement("span") - name_el.textContent = this.name + name_el.textContent = this.display_name name_el.classList.add("name") info_el.append(name_el) this.el.append(info_el) |