aboutsummaryrefslogtreecommitdiff
path: root/client-web
diff options
context:
space:
mode:
Diffstat (limited to 'client-web')
-rw-r--r--client-web/source/index.ts6
-rw-r--r--client-web/source/logger.ts14
-rw-r--r--client-web/source/protocol/crypto.ts80
-rw-r--r--client-web/source/protocol/mod.ts15
-rw-r--r--client-web/source/room.ts36
5 files changed, 113 insertions, 38 deletions
diff --git a/client-web/source/index.ts b/client-web/source/index.ts
index 670cb46..21bf4b8 100644
--- a/client-web/source/index.ts
+++ b/client-web/source/index.ts
@@ -2,6 +2,7 @@
import { log } from "./logger.ts"
import { create_menu } from "./menu.ts";
+import { SignalingConnection } from "./protocol/mod.ts";
import { Room } from "./room.ts"
export const servers: RTCConfiguration = {
@@ -17,11 +18,12 @@ export interface User {
window.onload = () => main()
-export function main() {
+export async function main() {
document.body.querySelector("p")?.remove()
log("*", "starting up")
const room_name = window.location.pathname.substring("/".length)
- const room = new Room(room_name)
+ const conn = await (new SignalingConnection().connect(room_name))
+ const room = new Room(conn)
create_menu()
document.body.append(room.el)
}
diff --git a/client-web/source/logger.ts b/client-web/source/logger.ts
index e00b1d0..952355e 100644
--- a/client-web/source/logger.ts
+++ b/client-web/source/logger.ts
@@ -1,14 +1,16 @@
/// <reference lib="dom" />
const log_tag_color = {
- "*": "#FF4444",
- webrtc: "#FF44FF",
- media: "#FFFF44",
- ws: "#44FFFF",
- rnnoise: "#2222FF",
- usermodel: "#44FF44",
+ "*": "#ff4a7c",
+ crypto: "#c14aff",
+ webrtc: "#ff4ade",
+ ws: "#544aff",
+ media: "#4af5ff",
+ rnnoise: "#4aff7e",
+ usermodel: "#a6ff4a",
error: "#FF0000",
}
+
export type LogTag = keyof typeof log_tag_color
let logger_container: HTMLDivElement
diff --git a/client-web/source/protocol/crypto.ts b/client-web/source/protocol/crypto.ts
new file mode 100644
index 0000000..6cd2ba3
--- /dev/null
+++ b/client-web/source/protocol/crypto.ts
@@ -0,0 +1,80 @@
+import { log } from "../logger.ts";
+
+//! I am not a crypto expert at all! Please read carefully and report any issues to me.
+
+export async function crypto_seeded_key(seed: string): Promise<CryptoKey> {
+ log("crypto", "importing seed…")
+ const seed_key = await window.crypto.subtle.importKey(
+ "raw",
+ new TextEncoder().encode(seed),
+ "PBKDF2",
+ false,
+ ["deriveKey"]
+ )
+ //? TODO is it possible to use a unique seed per session here?
+ // const salt = window.crypto.getRandomValues(new Uint8Array(16));
+ const salt = base64_to_buf("thisisagoodsaltAAAAAAA==") // valid "unique" 16-byte base-64 string
+ log("crypto", "deriving key…")
+ const key = await window.crypto.subtle.deriveKey(
+ {
+ name: "PBKDF2",
+ salt,
+ iterations: 250000,
+ hash: "SHA-256",
+ },
+ seed_key,
+ { name: "AES-GCM", length: 256 },
+ false,
+ ["encrypt", "decrypt"]
+ )
+ console.log(key);
+ log("crypto", "ready")
+ return key
+}
+
+export async function crypto_encrypt(key: CryptoKey, data: string): Promise<string> {
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
+ const ciphertext = new Uint8Array(await window.crypto.subtle.encrypt(
+ { name: "AES-GCM", iv },
+ key,
+ new TextEncoder().encode(data)
+ ));
+ const buf = new Uint8Array(iv.byteLength + ciphertext.byteLength);
+ buf.set(iv, 0);
+ buf.set(ciphertext, iv.byteLength);
+ const b64 = buf_to_base64(buf);
+ return b64;
+}
+
+export async function crypt_decrypt(key: CryptoKey, data: string): Promise<string> {
+ const buf = base64_to_buf(data);
+ const iv = buf.slice(0, 12);
+ const ciphertext = buf.slice(12);
+ const decryptedContent = await window.crypto.subtle.decrypt(
+ { name: "AES-GCM", iv },
+ key,
+ ciphertext
+ );
+ const plain = new TextDecoder().decode(decryptedContent);
+ return plain
+}
+
+// const buf_to_base64 = (buf: Uint8Array) => btoa(String.fromCharCode.apply(null, buf));
+// const base64_to_buf = (b64: string) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
+
+export function base64_to_buf(data: string): Uint8Array {
+ const binary_string = globalThis.atob(data);
+ const bytes = new Uint8Array(binary_string.length);
+ for (let i = 0; i < binary_string.length; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes;
+}
+
+export function buf_to_base64(bytes: Uint8Array): string {
+ let binary = '';
+ for (let i = 0; i < bytes.byteLength; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return globalThis.btoa(binary);
+} \ No newline at end of file
diff --git a/client-web/source/protocol/mod.ts b/client-web/source/protocol/mod.ts
index 76b1290..f86e96f 100644
--- a/client-web/source/protocol/mod.ts
+++ b/client-web/source/protocol/mod.ts
@@ -1,6 +1,17 @@
+import { crypto_seeded_key } from "./crypto.ts"
export class SignalingConnection {
- constructor(room: string) {
-
+ room!: string
+ websocket!: WebSocket
+ signaling_id!: string
+ key!: CryptoKey
+
+ constructor() { }
+ async connect(room: string): Promise<SignalingConnection> {
+ this.key = await crypto_seeded_key(room)
+ 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)
+ return this
}
}
diff --git a/client-web/source/room.ts b/client-web/source/room.ts
index 78454bb..6c9a7ba 100644
--- a/client-web/source/room.ts
+++ b/client-web/source/room.ts
@@ -5,43 +5,23 @@ 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 { PREFS } from "./preferences.ts";
-import { ep } from "./helper.ts";
+import { SignalingConnection } from "./protocol/mod.ts";
export class Room {
el: HTMLElement
- name: string
users: Map<number, User> = new Map()
remote_users: Map<number, RemoteUser> = new Map()
local_user!: LocalUser
my_id!: number
- websocket: WebSocket
- constructor(name: string) {
- this.name = name
+ constructor(public signaling: SignalingConnection) {
this.el = document.createElement("div")
this.el.classList.add("room")
-
- const ws_url = new URL(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/${encodeURIComponent(name)}/signaling`)
- ws_url.searchParams.set("username", PREFS.username)
- this.websocket = new WebSocket(ws_url)
- this.websocket.onclose = () => this.websocket_close()
-
- // const connecting_text = ep("Upgrading to a websocket connection…")
- // this.el.append(connecting_text)
-
- this.websocket.onopen = () => {
- // connecting_text.remove()
- this.websocket_open()
- }
- this.websocket.onmessage = (ev) => {
- this.websocket_message(JSON.parse(ev.data))
- }
}
websocket_send(data: ServerboundPacket) {
log("ws", `-> ${data.relay?.recipient ?? "*"}`, data)
- this.websocket.send(JSON.stringify(data))
+ // this.websocket.send(JSON.stringify(data))
}
websocket_message(packet: ClientboundPacket) {
log("ws", `<- ${packet.message?.sender ?? "control packet"}: `, packet);
@@ -51,7 +31,7 @@ export class Room {
log("*", `server: ${packet.init.version}`)
} else if (packet.client_join) {
const p = packet.client_join
- log("*", `${this.name} ${p.id} joined`);
+ log("*", `${p.id} joined`);
if (p.id == this.my_id) {
this.local_user = new LocalUser(this, p.id, p.name);
} else {
@@ -63,7 +43,7 @@ export class Room {
}
} else if (packet.client_leave) {
const p = packet.client_leave;
- log("*", `${this.name} ${p.id} left`);
+ log("*", `${p.id} left`);
this.remote_users.get(p.id)!.leave()
this.users.delete(p.id)
this.remote_users.delete(p.id)
@@ -72,9 +52,9 @@ export class Room {
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)
+ // 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);
}