aboutsummaryrefslogtreecommitdiff
path: root/client-web
diff options
context:
space:
mode:
Diffstat (limited to 'client-web')
-rw-r--r--client-web/source/helper.ts7
-rw-r--r--client-web/source/index.ts6
-rw-r--r--client-web/source/menu.ts2
-rw-r--r--client-web/source/preferences/decl.ts3
-rw-r--r--client-web/source/protocol/mod.ts32
-rw-r--r--client-web/source/room.ts4
-rw-r--r--client-web/source/room_watches.ts58
-rw-r--r--client-web/style/master.sass1
-rw-r--r--client-web/style/watches.sass26
9 files changed, 117 insertions, 22 deletions
diff --git a/client-web/source/helper.ts b/client-web/source/helper.ts
index ecfdf95..49c36fa 100644
--- a/client-web/source/helper.ts
+++ b/client-web/source/helper.ts
@@ -77,3 +77,10 @@ export function display_filesize(n: number): string {
if (n > 1000) return (n / 1000).toFixed(1) + "kB"
return n.toString() + "B"
}
+
+export class EventEmitter<E> {
+ private handlers: Set<(e: E) => unknown> = new Set()
+ public dispatch(e: E) { this.handlers.forEach(h => h(e)) }
+ public add_listener(listener: (e: E) => unknown) { this.handlers.add(listener) }
+ public remove_listener(listener: (e: E) => unknown) { this.handlers.delete(listener) }
+}
diff --git a/client-web/source/index.ts b/client-web/source/index.ts
index f5f6b2d..3dbac6d 100644
--- a/client-web/source/index.ts
+++ b/client-web/source/index.ts
@@ -69,7 +69,7 @@ export async function main() {
if (room_secret.length == 0) return window.location.href = "/" // send them back to the start page
if (PREFS.warn_redirect) log({ scope: "crypto", warn: true }, "You were redirected from the old URL format. The server knows the room secret now - e2ee is insecure!")
- const conn = await (new SignalingConnection().connect(room_secret))
+ const conn = await (new SignalingConnection().connect())
const rtc_config: RTCConfiguration = {
iceCandidatePoolSize: 10,
iceServers: [{
@@ -81,9 +81,6 @@ export async function main() {
r = new Room(conn, rtc_config)
- conn.control_handler = (a) => r.control_handler(a)
- conn.relay_handler = (a, b) => r.relay_handler(a, b)
-
setup_keybinds(r)
r.on_ready = () => {
const sud = e("div", { class: "side-ui" })
@@ -92,4 +89,5 @@ export async function main() {
}
if (globalThis.navigator.serviceWorker) init_serviceworker()
+ await conn.join(room_secret)
}
diff --git a/client-web/source/menu.ts b/client-web/source/menu.ts
index 583f28b..3ad9c99 100644
--- a/client-web/source/menu.ts
+++ b/client-web/source/menu.ts
@@ -40,7 +40,7 @@ export function control_bar(room: Room, side_ui_container: HTMLElement): HTMLEle
const leave = e("button", { class: "leave", onclick() { window.location.href = "/" } }, "Leave")
const chat = side_ui(side_ui_container, room.chat.element, "Chat")
const prefs = side_ui(side_ui_container, ui_preferences(), "Settings")
- const rwatches = side_ui(side_ui_container, ui_room_watches(), "Known Rooms")
+ const rwatches = side_ui(side_ui_container, ui_room_watches(room.signaling), "Known Rooms")
const local_controls = [ //ediv({ class: "local-controls", aria_label: "local resources" },
e("button", { onclick: () => room.local_user.await_add_resource(create_mic_res()) }, "Microphone"),
e("button", { onclick: () => room.local_user.await_add_resource(create_camera_res()) }, "Camera"),
diff --git a/client-web/source/preferences/decl.ts b/client-web/source/preferences/decl.ts
index 40dfcd6..5f2d8a2 100644
--- a/client-web/source/preferences/decl.ts
+++ b/client-web/source/preferences/decl.ts
@@ -42,5 +42,6 @@ export const PREF_DECLS = {
notify_join: { type: bool, default: true, description: "Send notifications when users join" },
notify_leave: { type: bool, default: true, description: "Send notifications when users leave" },
- enable_onbeforeunload: { type: bool, default: true, description: "Prompt for confirmation when leaving the site while local resources are active" }
+ enable_onbeforeunload: { type: bool, default: true, description: "Prompt for confirmation when leaving the site while local resources are active" },
+ room_watches: { type: string, default: "Public=public", description: "Known rooms (as semicolon seperated list of name=secret pairs)" }
}
diff --git a/client-web/source/protocol/mod.ts b/client-web/source/protocol/mod.ts
index 83ee8cb..0db1162 100644
--- a/client-web/source/protocol/mod.ts
+++ b/client-web/source/protocol/mod.ts
@@ -4,23 +4,22 @@
Copyright (C) 2023 metamuffin <metamuffin@disroot.org>
*/
import { ClientboundPacket, RelayMessage, RelayMessageWrapper, ServerboundPacket } from "../../../common/packets.d.ts"
+import { EventEmitter } from "../helper.ts";
import { log } from "../logger.ts"
import { crypto_encrypt, crypto_seeded_key, crypt_decrypt, crypto_hash } from "./crypto.ts"
export class SignalingConnection {
- room!: string
websocket!: WebSocket
- room_hash!: string
- key!: CryptoKey
+ room?: string
+ room_hash?: string
+ key?: CryptoKey
my_id?: number // needed for outgoing relay messages
- control_handler: (_packet: ClientboundPacket) => void = () => { }
- relay_handler: (_sender: number, _message: RelayMessage) => void = () => { }
+ control_handler = new EventEmitter<ClientboundPacket>()
+ relay_handler = new EventEmitter<[number, RelayMessage]>()
constructor() { }
- async connect(room: string): Promise<SignalingConnection> {
- this.key = await crypto_seeded_key(room)
- this.room_hash = await crypto_hash(room)
+ async connect(): Promise<SignalingConnection> {
log("ws", "connecting…")
const ws_url = new URL(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling`)
this.websocket = new WebSocket(ws_url)
@@ -44,21 +43,28 @@ export class SignalingConnection {
}
on_open() {
log("ws", "websocket opened");
- this.send_control({ join: { hash: this.room_hash } })
setInterval(() => this.send_control({ ping: null }), 30000) // stupid workaround for nginx disconnecting inactive connections
}
+
+ async join(room: string) {
+ this.room = room;
+ this.key = await crypto_seeded_key(room)
+ this.room_hash = await crypto_hash(room)
+ this.send_control({ join: { hash: this.room_hash } })
+ }
+
on_error() {
log({ scope: "ws", error: true }, "websocket error occurred!")
}
async on_message(data: string) {
const packet: ClientboundPacket = JSON.parse(data) // TODO dont crash if invalid
- this.control_handler(packet)
+ this.control_handler.dispatch(packet)
if (packet.init) this.my_id = packet.init.your_id;
if (packet.message) {
- const plain_json = await crypt_decrypt(this.key, packet.message.message)
+ const plain_json = await crypt_decrypt(this.key!, packet.message.message)
const plain: RelayMessageWrapper = JSON.parse(plain_json) // TODO make sure that protocol spec is met
if (plain.sender == packet.message.sender)
- this.relay_handler(packet.message.sender, plain.inner)
+ this.relay_handler.dispatch([packet.message.sender, plain.inner])
else {
log({ scope: "crypto", warn: true }, `message dropped: sender inconsistent (${plain.sender} != ${packet.message.sender})`)
}
@@ -71,7 +77,7 @@ export class SignalingConnection {
async send_relay(data: RelayMessage, recipient?: number | null) {
recipient ??= undefined // null -> undefined
const packet: RelayMessageWrapper = { inner: data, sender: this.my_id! }
- const message = await crypto_encrypt(this.key, JSON.stringify(packet))
+ const message = await crypto_encrypt(this.key!, JSON.stringify(packet))
this.send_control({ relay: { recipient, message } })
}
}
diff --git a/client-web/source/room.ts b/client-web/source/room.ts
index ba18162..8fd165d 100644
--- a/client-web/source/room.ts
+++ b/client-web/source/room.ts
@@ -24,6 +24,8 @@ export class Room {
constructor(public signaling: SignalingConnection, public rtc_config: RTCConfiguration) {
this.element = e("div", { class: "room", aria_label: "user list" })
+ signaling.control_handler.add_listener(p => this.control_handler(p))
+ signaling.relay_handler.add_listener(([a, b]) => this.relay_handler(a, b))
}
control_handler(packet: ClientboundPacket) {
@@ -58,4 +60,4 @@ export class Room {
log("ws", `<- [relay from ${sender.display_name}]: `, message);
sender.on_relay(message)
}
-} \ No newline at end of file
+}
diff --git a/client-web/source/room_watches.ts b/client-web/source/room_watches.ts
index 331022d..d91972d 100644
--- a/client-web/source/room_watches.ts
+++ b/client-web/source/room_watches.ts
@@ -1,7 +1,61 @@
import { e } from "./helper.ts";
+import { PREFS } from "./preferences/mod.ts";
+import { crypto_hash } from "./protocol/crypto.ts";
+import { SignalingConnection } from "./protocol/mod.ts";
-export function ui_room_watches(): HTMLElement {
- const listing = e("div", {})
+interface Watch {
+ secret: string,
+ hash: string,
+ name: string,
+ user_count: number,
+}
+
+export function ui_room_watches(conn: SignalingConnection): HTMLElement {
+ const listing = e("div", { class: "room-watches-listing" })
+
+ const watches: Watch[] = []
+ const update_watches = () => (conn.send_control({ watch_rooms: watches.map(w => w.hash) }), update_listing());
+
+ (async () => {
+ for (const e of PREFS.room_watches.split(";")) {
+ const [name, secret] = e.split("=");
+ watches.push({
+ name,
+ secret,
+ hash: await crypto_hash(secret),
+ user_count: 0
+ })
+ }
+ update_watches()
+ })()
+
+ conn.control_handler.add_listener(packet => {
+ if (packet.room_info) {
+ const w = watches.find(w => w.hash == packet.room_info!.hash)
+ w!.user_count = packet.room_info.user_count
+ update_listing()
+ }
+ })
+
+ const update_listing = () => {
+ listing.innerHTML = ""
+ for (const w of watches) {
+ const ucont = []
+ if (w.user_count > 0) ucont.push(e("div", {}))
+ if (w.user_count > 1) ucont.push(e("div", {}))
+ if (w.user_count > 2) ucont.push(e("div", {}))
+ if (w.user_count > 3) ucont.push(e("span", {}, `+${w.user_count - 3}`))
+ listing.append(e("li", {},
+ e("a", {
+ href: "#" + encodeURIComponent(w.secret),
+ class: w.secret == conn.room ? "current-room" : []
+ },
+ w.name,
+ e("div", { class: "users" }, ...ucont)
+ )
+ ))
+ }
+ }
return e("div", { class: "room-watches" },
e("h2", {}, "Known Rooms"),
diff --git a/client-web/style/master.sass b/client-web/style/master.sass
index 6a26b67..0863129 100644
--- a/client-web/style/master.sass
+++ b/client-web/style/master.sass
@@ -9,6 +9,7 @@
@use 'side'
@use 'menu'
@use 'start'
+@use 'watches'
@import url("/assets/font/include.css")
@import url("/overrides.css")
diff --git a/client-web/style/watches.sass b/client-web/style/watches.sass
new file mode 100644
index 0000000..ee45f5d
--- /dev/null
+++ b/client-web/style/watches.sass
@@ -0,0 +1,26 @@
+.room-watches-listing
+ list-style: none
+ a
+ display: inline-block
+ width: 100%
+ margin: 0.5em
+ border-radius: 4px
+ padding: 0.5em
+ background-color: var(--bg-light)
+
+ .current-room
+ background-color: var(--ac-dark)
+
+ .users
+ float: right
+ display: inline-block
+ div
+ margin: 0.25em
+ display: inline-block
+ width: 1em
+ height: 1em
+ border-radius: 3px
+ background-color: rgb(146, 243, 73)
+ span
+ display: inline-block
+ color: #bbbbbb