diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/client/helper.ts | 43 | ||||
-rw-r--r-- | source/client/index.ts | 70 | ||||
-rw-r--r-- | source/client/local_user.ts | 125 | ||||
-rw-r--r-- | source/client/logger.ts | 50 | ||||
-rw-r--r-- | source/client/menu.ts | 26 | ||||
-rw-r--r-- | source/client/remote_user.ts | 74 | ||||
-rw-r--r-- | source/client/rnnoise.ts | 38 | ||||
-rw-r--r-- | source/client/room.ts | 72 | ||||
-rw-r--r-- | source/client/track_handle.ts | 26 | ||||
-rw-r--r-- | source/client/user.ts | 80 | ||||
-rw-r--r-- | source/packets.ts | 21 | ||||
-rw-r--r-- | source/server/index.ts | 77 | ||||
-rw-r--r-- | source/server/room.ts | 58 |
13 files changed, 0 insertions, 760 deletions
diff --git a/source/client/helper.ts b/source/client/helper.ts deleted file mode 100644 index 31e500a..0000000 --- a/source/client/helper.ts +++ /dev/null @@ -1,43 +0,0 @@ -/// <reference lib="dom" /> - -import { parameters } from "./index.ts" - -export function get_query_params(): { [key: string]: string } { - const q: { [key: string]: string } = {} - for (const kv of window.location.hash.substring(1).split("&")) { - const [key, value] = kv.split("=") - q[decodeURIComponent(key)] = decodeURIComponent(value) - } - return q -} - -export function hex_id(len = 8): string { - if (len > 8) return hex_id() + hex_id(len - 8) - return Math.floor(Math.random() * 16 ** len).toString(16).padStart(len, "0") -} - -export function parameter_bool(name: string, def: boolean): boolean { - const v = parameters[name] - if (!v) return def - if (v == "0" || v == "false" || v == "no") return false - if (v == "1" || v == "true" || v == "yes") return true - alert(`parameter ${name} is invalid`) - return def -} - -export function parameter_number(name: string, def: number): number { - const v = parameters[name] - if (!v) return def - const n = parseFloat(v) - if (Number.isNaN(n)) { - alert(`parameter ${name} is invalid`) - return def - } - return n -} - -export function parameter_string(name: string, def: string): string { - const v = parameters[name] - if (!v) return def - return v -} diff --git a/source/client/index.ts b/source/client/index.ts deleted file mode 100644 index 745dd67..0000000 --- a/source/client/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/// <reference lib="dom" /> - -import { get_query_params } from "./helper.ts" -import { log } from "./logger.ts" -import { create_menu } from "./menu.ts"; -import { Room } from "./room.ts" - -export const servers: RTCConfiguration = { - iceServers: [{ urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] }], - iceCandidatePoolSize: 10, -} - -export interface User { - peer: RTCPeerConnection - stream: MediaStream, -} - -export const parameters = get_query_params() - -window.onload = () => main() - -export function main() { - document.body.querySelector("p")?.remove() - log("*", "starting up") - if (window.location.pathname.startsWith("/room/")) { - const room_name = window.location.pathname.substring("/room/".length) - const room = new Room(room_name) - create_menu(room) - document.body.append(room.el) - } else { - create_menu() - document.body.append(create_start_screen()) - } -} - -function create_start_screen() { - const with_text_content = (a: string) => (b: string) => { - const e = document.createElement(a) - e.textContent = b - return e - } - const p = with_text_content("p") - const h2 = with_text_content("h2") - - const el = document.createElement("div") - el.append( - h2("keks-meet"), - p("A web conferencing application using webrtc"), - p("keks-meet is free software! It is licenced under the terms of the third version of the GNU Affero General Public Licence only."), - p("To get started, just enter a unique idenfier, click 'Join', then share the URL with your partner.") - ) - - const room_input = document.createElement("input") - room_input.type = "text" - room_input.id = "room-id-input" - 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() * 10000).toString(16).padStart(5, "0") - window.location.pathname = `/room/${encodeURIComponent(room_input.value)}` - }) - submit.value = "Join room" - - el.classList.add("start-box") - el.append(room_input, document.createElement("br"), submit) - - return el -} diff --git a/source/client/local_user.ts b/source/client/local_user.ts deleted file mode 100644 index ca91a19..0000000 --- a/source/client/local_user.ts +++ /dev/null @@ -1,125 +0,0 @@ -/// <reference lib="dom" /> - -import { parameter_bool, parameter_number } from "./helper.ts"; -import { log } from "./logger.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { get_rnnoise_node } from "./rnnoise.ts"; -import { Room } from "./room.ts"; -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts"; - - -export class LocalUser extends User { - mic_gain?: GainNode - default_gain: number = parameter_number("mic_gain", 1) - - constructor(room: Room, name: string) { - super(room, name) - this.el.classList.add("local") - this.local = true - this.create_controls() - this.add_initial_tracks() - log("usermodel", `added local user: ${this.name}`) - } - - async add_initial_tracks() { - if (parameter_bool("mic_enabled", false)) this.publish_track(await this.create_mic_track()) - if (parameter_bool("camera_enabled", false)) this.publish_track(await this.create_camera_track()) - if (parameter_bool("screen_enabled", false)) this.publish_track(await this.create_screen_track()) - } - - publish_track(t: TrackHandle) { - this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) - this.add_track(t) - t.addEventListener("ended", () => { - this.room.remote_users.forEach(u => { - u.peer.getSenders().forEach(s => { - if (s.track == t.track) u.peer.removeTrack(s) - }) - }) - }) - } - - add_initial_to_remote(u: RemoteUser) { - this.tracks.forEach(t => u.peer.addTrack(t.track)) - } - - create_controls() { - const mic_toggle = document.createElement("input") - const camera_toggle = document.createElement("input") - const screen_toggle = document.createElement("input") - mic_toggle.type = camera_toggle.type = screen_toggle.type = "button" - mic_toggle.value = "Microphone" - camera_toggle.value = "Camera" - screen_toggle.value = "Screen" - - const create = async (_e: HTMLElement, tp: Promise<TrackHandle>) => { - log("media", "awaiting track") - const t = await tp - log("media", "got track") - this.publish_track(t) - } - - mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track())) - camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track())) - screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screen_track())) - - const el = document.createElement("div") - el.classList.add("local-controls") - el.append(mic_toggle, camera_toggle, screen_toggle) - document.body.append(el) - } - - - async create_camera_track() { - log("media", "requesting user media (camera)") - const user_media = await window.navigator.mediaDevices.getUserMedia({ video: true }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - async create_screen_track() { - log("media", "requesting user media (screen)") - const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - async create_mic_track() { - log("media", "requesting user media (audio)") - const use_rnnoise = parameter_bool("rnnoise", true) - const audio_contraints = use_rnnoise ? { - channelCount: { ideal: 1 }, - noiseSuppression: { ideal: false }, - echoCancellation: { ideal: true }, - autoGainControl: { ideal: false }, - } : true; - - const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints }) - const context = new AudioContext() - const source = context.createMediaStreamSource(user_media) - const destination = context.createMediaStreamDestination() - const gain = context.createGain() - gain.gain.value = this.default_gain - this.mic_gain = gain - - let rnnoise: RNNoiseNode; - if (use_rnnoise) { - rnnoise = await get_rnnoise_node(context) - source.connect(rnnoise) - rnnoise.connect(gain) - } else { - source.connect(gain) - } - gain.connect(destination) - - const t = new TrackHandle(destination.stream.getAudioTracks()[0], true) - - t.addEventListener("ended", () => { - user_media.getTracks().forEach(t => t.stop()) - source.disconnect() - if (rnnoise) rnnoise.disconnect() - gain.disconnect() - destination.disconnect() - this.mic_gain = undefined - }) - - return t - } -} diff --git a/source/client/logger.ts b/source/client/logger.ts deleted file mode 100644 index e00b1d0..0000000 --- a/source/client/logger.ts +++ /dev/null @@ -1,50 +0,0 @@ -/// <reference lib="dom" /> - -const log_tag_color = { - "*": "#FF4444", - webrtc: "#FF44FF", - media: "#FFFF44", - ws: "#44FFFF", - rnnoise: "#2222FF", - usermodel: "#44FF44", - error: "#FF0000", -} -export type LogTag = keyof typeof log_tag_color - -let logger_container: HTMLDivElement - -// TODO maybe log time aswell -// deno-lint-ignore no-explicit-any -export function log(tag: LogTag, message: string, ...data: any[]) { - for (let i = 0; i < data.length; i++) { - const e = data[i]; - if (e instanceof MediaStreamTrack) data[i] = `(${e.kind}) ${e.id}` - } - console.log(`%c[${tag}] ${message}`, "color:" + log_tag_color[tag], ...data); - - if (logger_container) { - const e = document.createElement("p") - e.classList.add("logger-line") - e.textContent = `[${tag}] ${message}` - e.style.color = log_tag_color[tag] - logger_container.append(e) - setTimeout(() => { - e.remove() - }, tag == "error" ? 60000 : 6000) - } -} - -globalThis.addEventListener("load", () => { - const d = document.createElement("div") - d.classList.add("logger-container") - document.body.append(d) - logger_container = d - - // clear the console every hour so logs dont accumulate - setInterval(() => console.clear(), 1000 * 60 * 60) -}) - -globalThis.onerror = (_ev, source, line, col, err) => { - log("error", `${err?.name} ${err?.message}`, err) - log("error", `on ${source}:${line}:${col}`, err) -}
\ No newline at end of file diff --git a/source/client/menu.ts b/source/client/menu.ts deleted file mode 100644 index 1401b42..0000000 --- a/source/client/menu.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Room } from "./room.ts"; - -export function create_menu(room?: Room) { - const menu = document.createElement("div") - menu.classList.add("menu-overlay") - document.body.append(menu) - - const item = (name: string, cb: (() => void) | string) => { - const p = document.createElement("p") - const a = document.createElement("a") - a.classList.add("menu-item") - a.textContent = name - if (typeof cb == "string") a.href = cb - else a.addEventListener("click", cb), a.href = "#" - p.append(a) - return p - } - - if (room) menu.append( - item("Settings", () => alert("todo, refer to the url parameters in the docs for now")) - ) - menu.append( - item("Licence", "/licence"), - item("Sources / Documentation", "https://codeberg.org/metamuffin/keks-meet"), - ) -}
\ No newline at end of file diff --git a/source/client/remote_user.ts b/source/client/remote_user.ts deleted file mode 100644 index a0fdeaf..0000000 --- a/source/client/remote_user.ts +++ /dev/null @@ -1,74 +0,0 @@ -/// <reference lib="dom" /> - -import { servers } from "./index.ts" -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts" - -export class RemoteUser extends User { - peer: RTCPeerConnection - negotiation_busy = false - - constructor(room: Room, name: string) { - super(room, name) - log("usermodel", `added remote user: ${name}`) - this.peer = new RTCPeerConnection(servers) - this.peer.onicecandidate = ev => { - if (!ev.candidate) return - room.websocket_send({ ice_candiate: ev.candidate.toJSON(), receiver: this.name }) - } - this.peer.ontrack = ev => { - const t = ev.track - log("media", `remote track: ${this.name}`, t) - this.add_track(new TrackHandle(t)) - } - this.peer.onnegotiationneeded = async () => { - log("webrtc", `negotiation needed: ${this.name}`) - while (this.negotiation_busy) { - await new Promise<void>(r => setTimeout(() => r(), 100)) - } - this.offer() - } - } - - async offer() { - this.negotiation_busy = true - const offer_description = await this.peer.createOffer() - 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({ receiver: this.name, offer }) - } - async on_offer(offer: RTCSessionDescriptionInit) { - this.negotiation_busy = true - log("webrtc", `got offer: ${this.name}`, { a: offer }) - const offer_description = new RTCSessionDescription(offer) - await this.peer.setRemoteDescription(offer_description) - this.answer() - } - async answer() { - const answer_description = await this.peer.createAnswer() - 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({ receiver: this.name, answer }) - this.negotiation_busy = false - } - async on_answer(answer: RTCSessionDescriptionInit) { - log("webrtc", `got answer: ${this.name}`, { a: answer }) - const answer_description = new RTCSessionDescription(answer) - await this.peer.setRemoteDescription(answer_description) - this.negotiation_busy = false - } - - add_ice_candidate(candidate: RTCIceCandidateInit) { - this.peer.addIceCandidate(new RTCIceCandidate(candidate)) - } - - leave() { - log("usermodel", `remove remote user: ${this.name}`) - this.peer.close() - this.room.el.removeChild(this.el) - } -}
\ No newline at end of file diff --git a/source/client/rnnoise.ts b/source/client/rnnoise.ts deleted file mode 100644 index 7867682..0000000 --- a/source/client/rnnoise.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// <reference lib="dom" /> - -import { log } from "./logger.ts" - -declare global { - class RNNoiseNode extends AudioWorkletNode { - static register(context: AudioContext): Promise<void> - constructor(context: AudioContext) - // deno-lint-ignore no-explicit-any - onstatus: (data: any) => void - update(something: boolean): void - } -} - - -// TODO fix leak -export async function get_rnnoise_node(context: AudioContext): Promise<RNNoiseNode> { - log("rnnoise", "enabled") - //@ts-ignore asfdasfd - let RNNoiseNode: typeof RNNoiseNode = window.RNNoiseNode; - - let script: HTMLScriptElement; - if (!RNNoiseNode) { - log("rnnoise", "loading wasm...") - script = document.createElement("script") - script.src = "/_rnnoise/rnnoise-runtime.js" - script.defer = true - document.head.appendChild(script) - //@ts-ignore asdfsfad - while (!window.RNNoiseNode) await new Promise<void>(r => setTimeout(() => r(), 100)) - //@ts-ignore asfdsadfsafd - RNNoiseNode = window.RNNoiseNode; - log("rnnoise", "loaded") - } - - await RNNoiseNode.register(context) - return new RNNoiseNode(context) -}
\ No newline at end of file diff --git a/source/client/room.ts b/source/client/room.ts deleted file mode 100644 index 145fb1b..0000000 --- a/source/client/room.ts +++ /dev/null @@ -1,72 +0,0 @@ -/// <reference lib="dom" /> - -import { log } from "./logger.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { User } from "./user.ts"; -import { LocalUser } from "./local_user.ts"; -import { hex_id, parameter_string } from "./helper.ts"; -import { PacketS, PacketC } from "../packets.ts"; - - -export class Room { - el: HTMLElement - name: string - users: Map<string, User> = new Map() - remote_users: Map<string, RemoteUser> = new Map() - local_user: LocalUser - websocket: WebSocket - - constructor(name: string) { - this.name = name - this.el = document.createElement("div") - this.el.classList.add("room") - this.websocket = new WebSocket(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling/${encodeURIComponent(name)}`) - this.websocket.onclose = () => this.websocket_close() - this.websocket.onopen = () => this.websocket_open() - this.websocket.onmessage = (ev) => { - this.websocket_message(JSON.parse(ev.data)) - } - this.local_user = new LocalUser(this, parameter_string("username", `guest-${hex_id()}`)) - } - - websocket_send(data: PacketS) { - log("ws", `-> ${data.receiver ?? "*"}`, data) - this.websocket.send(JSON.stringify(data)) - } - websocket_message(packet: PacketC) { - if (packet.join) { - log("*", `${this.name} ${packet.sender} joined`); - const ru = new RemoteUser(this, packet.sender) - this.local_user.add_initial_to_remote(ru) - if (!packet.stable) ru.offer() - this.users.set(packet.sender, ru) - this.remote_users.set(packet.sender, ru) - return - } - const sender = this.remote_users.get(packet.sender) - if (!sender) return console.warn(`unknown sender ${packet.sender}`) - if (packet.leave) { - log("*", `${this.name} ${packet.sender} left`); - sender.leave() - this.users.delete(packet.sender) - this.remote_users.delete(packet.sender) - return - } - if (!packet.data) return console.warn("dataless packet") - log("ws", `<- ${packet.sender}: `, packet.data); - if (packet.data.ice_candiate) sender.add_ice_candidate(packet.data.ice_candiate) - if (packet.data.offer) sender.on_offer(packet.data.offer) - if (packet.data.answer) sender.on_answer(packet.data.answer) - } - websocket_close() { - log("ws", "websocket closed"); - setTimeout(() => { - window.location.reload() - }, 1000) - } - websocket_open() { - log("ws", "websocket opened"); - this.websocket.send(this.local_user.name) - setInterval(() => this.websocket_send({}), 30000) // stupid workaround for nginx disconnection inactive connections - } -}
\ No newline at end of file diff --git a/source/client/track_handle.ts b/source/client/track_handle.ts deleted file mode 100644 index 98b2b2f..0000000 --- a/source/client/track_handle.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// <reference lib="dom" /> - -export class TrackHandle extends EventTarget { - constructor( - public track: MediaStreamTrack, - public local = false - ) { - super() - track.onended = () => this.dispatchEvent(new CustomEvent("ended")) - // TODO research how onmute and onunmute behave - track.onmute = () => this.dispatchEvent(new CustomEvent("ended")) // onmute seems to be called when the remote ends the track - track.onunmute = () => this.dispatchEvent(new CustomEvent("started")) - - this.addEventListener("ended", () => { - // drop all references to help gc - track.onunmute = track.onmute = track.onended = null - }) - } - - get kind() { return this.track.kind } - get label() { return this.track.label } - get muted() { return this.track.muted } - get id() { return this.track.id } - - end() { this.track.stop(); this.dispatchEvent(new CustomEvent("ended")) } -} diff --git a/source/client/user.ts b/source/client/user.ts deleted file mode 100644 index bda875f..0000000 --- a/source/client/user.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// <reference lib="dom" /> - -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; - - -export abstract class User { - name: string - room: Room - - el: HTMLElement - - local = false - - protected tracks: Set<TrackHandle> = new Set() - - constructor(room: Room, name: string) { - this.name = name - this.room = room - this.el = document.createElement("div") - this.el.classList.add("user") - this.room.el.append(this.el) - this.setup_view() - } - - add_track(t: TrackHandle) { - this.tracks.add(t) - this.create_track_element(t) - t.addEventListener("ended", () => { - log("media", "track ended", t) - this.tracks.delete(t) - }) - t.addEventListener("mute", () => { - log("media", "track muted", t) - }) - t.addEventListener("unmute", () => { - log("media", "track unmuted", t) - }) - } - - 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.classList.add("name") - info_el.append(name_el) - this.el.append(info_el) - } - - create_track_element(t: TrackHandle) { - const is_video = t.kind == "video" - const media_el = is_video ? document.createElement("video") : document.createElement("audio") - const stream = new MediaStream([t.track]) - media_el.srcObject = stream - media_el.classList.add("media") - media_el.autoplay = true - media_el.controls = true - - if (this.local) media_el.muted = true - - - const el = document.createElement("div") - if (t.local) { - const end_button = document.createElement("button") - end_button.textContent = "End" - end_button.addEventListener("click", () => { - t.end() - }) - el.append(end_button) - } - el.append(media_el) - this.el.append(el) - t.addEventListener("ended", () => { - media_el.srcObject = null - el.remove() - }) - } -}
\ No newline at end of file diff --git a/source/packets.ts b/source/packets.ts deleted file mode 100644 index f42d99b..0000000 --- a/source/packets.ts +++ /dev/null @@ -1,21 +0,0 @@ - -// copy pasted from dom.lib.d.ts because it can not be referenced in the server. -type F_RTCSdpType = "answer" | "offer" | "pranswer" | "rollback"; -interface F_RTCSessionDescriptionInit { sdp?: string; type: F_RTCSdpType; } -interface F_RTCIceCandidateInit { candidate?: string; sdpMLineIndex?: number | null; sdpMid?: string | null; usernameFragment?: string | null; } - -export interface PacketC { - sender: string, - data?: PacketS, - join?: boolean, // user just joined - leave?: boolean, // user left - stable?: boolean // user "joined" because you joined aka. user was already there -} -export interface PacketS { - receiver?: string - ice_candiate?: F_RTCIceCandidateInit - offer?: F_RTCSessionDescriptionInit - answer?: F_RTCSessionDescriptionInit -} - - diff --git a/source/server/index.ts b/source/server/index.ts deleted file mode 100644 index de2ba4e..0000000 --- a/source/server/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Application, Router, RouterContext, send } from "https://deno.land/x/oak@v10.4.0/mod.ts"; -import { api } from "./room.ts"; -import { bundle } from "https://deno.land/x/emit@0.1.1/mod.ts"; - -const app = new Application() -const root = new Router() - - -root.get("/", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) -root.get("/room/:id", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) - -root.get("/licen(c|s)e", async c => { - c.response.body = await Deno.readTextFile("LICENCE") - c.response.headers.set("Content-Type", "text/plain") -}) - -root.get("/favicon.ico", c => { c.response.status = 204 }) - -// deno-lint-ignore no-explicit-any -function respondWithType(mimeType: string, f: () => string): (c: RouterContext<any, any, any>) => void { - return c => { - c.response.headers.set("Content-Type", mimeType) - c.response.body = f() - } -} - -let bundle_code = "" -root.get("/bundle.js", respondWithType("application/javascript", () => bundle_code)) - -root.use(api.routes()) - -function mountFilesystem(r: Router, route: string, path: string) { - r.get(route + "/(.*)", async (context) => { - console.log(context.request.url.pathname.substring(route.length)); - await send(context, context.request.url.pathname.substring(route.length), { root: Deno.cwd() + path }); - }) -} - -mountFilesystem(root, "/_style", "/public/style") -mountFilesystem(root, "/_rnnoise", "/public/rnnoise") - -app.use(root.routes()) -app.use(root.allowedMethods()) - -app.addEventListener("listen", ({ hostname, port, secure }) => { - console.log(`listening on: ${secure ? "https://" : "http://"}${hostname}:${port}`); -}); - -app.listen({ - hostname: Deno.env.get("HOSTNAME") ?? "127.0.0.1", - port: parseInt(Deno.env.get("PORT") ?? "8080") -}); - - -let refresh_needed = false -let refresh_pending = false -async function refresh() { - refresh_needed = true - if (refresh_pending) return - refresh_needed = false - refresh_pending = true - - try { - const { code } = await bundle("source/client/index.ts", { compilerOptions: { checkJs: false } }) - bundle_code = code - } catch (e) { console.error(e) } - - refresh_pending = false - if (refresh_needed) refresh() -} - -refresh() -for await (const event of Deno.watchFs("source/client")) { - if (event.kind == "modify" || event.kind == "create") { - refresh() - } -} diff --git a/source/server/room.ts b/source/server/room.ts deleted file mode 100644 index d9c1a25..0000000 --- a/source/server/room.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Router } from "https://deno.land/x/oak@v10.4.0/router.ts"; -import { PacketC, PacketS } from "../packets.ts"; - -export const api = new Router() - -type Room = Map<string, WebSocket> -const rooms: Map<string, Room> = new Map() - -function send_packet(ws: WebSocket, packet: PacketC) { - ws.send(JSON.stringify(packet)) -} - -api.get("/signaling/:id", c => { - const ws = c.upgrade() - - const room_name = c.params.id - const room: Room = rooms.get(c.params.id) ?? new Map() - let initialized = false - let user_name = "" - - const init = (n: string) => { - if (room.get(n)) return ws.close() - initialized = true - user_name = n - rooms.set(c.params.id, room) - room.forEach(uws => send_packet(uws, { sender: user_name, join: true })) - room.forEach((_, uname) => send_packet(ws, { sender: uname, join: true, stable: true })) - room.set(user_name, ws) - console.log(`[${room_name}] ${user_name} joined`) - } - ws.onclose = () => { - room.delete(user_name) - room.forEach(uws => send_packet(uws, { sender: user_name, leave: true })) - if (room.size == 0) rooms.delete(room_name) - console.log(`[${room_name}] ${user_name} left`) - } - ws.onmessage = ev => { - const message = ev.data.toString() - if (!initialized) return init(message) - let in_packet: PacketS; - try { in_packet = JSON.parse(message) } - catch (_e) { return } - - if (JSON.stringify(in_packet) == "{}") return // drop ping - - console.log(`[${room_name}] ${user_name} -> ${in_packet.receiver ?? "*"}: ${message.substr(0, 100)}`) - const out_packet: PacketC = { sender: user_name, data: in_packet } - - if (in_packet.receiver) { - const rws = room.get(in_packet.receiver) - if (rws) send_packet(rws, out_packet) - } else { - room.forEach((uws, uname) => { - if (uname != user_name) send_packet(uws, out_packet) - }) - } - } -}) |