aboutsummaryrefslogtreecommitdiff
path: root/client-web/source
diff options
context:
space:
mode:
Diffstat (limited to 'client-web/source')
-rw-r--r--client-web/source/download_stream.ts (renamed from client-web/source/sw/download_stream.ts)14
-rw-r--r--client-web/source/index.ts4
-rw-r--r--client-web/source/keybinds.ts2
-rw-r--r--client-web/source/resource/file.ts2
-rw-r--r--client-web/source/sw/client.ts (renamed from client-web/source/sw/init.ts)35
-rw-r--r--client-web/source/sw/download.ts36
-rw-r--r--client-web/source/sw/protocol.ts9
-rw-r--r--client-web/source/sw/worker.ts115
8 files changed, 162 insertions, 55 deletions
diff --git a/client-web/source/sw/download_stream.ts b/client-web/source/download_stream.ts
index 35a3c7e..5aafde1 100644
--- a/client-web/source/sw/download_stream.ts
+++ b/client-web/source/download_stream.ts
@@ -4,8 +4,8 @@
Copyright (C) 2022 metamuffin <metamuffin@disroot.org>
*/
/// <reference lib="dom" />
-import { log } from "../logger.ts"
-import { SW } from "./init.ts"
+import { log } from "./logger.ts"
+import { send_sw_message, SW_ENABLED } from "./sw/client.ts"
function FallbackStreamDownload(size: number, filename?: string, progress?: (position: number) => void) {
log({ scope: "*", warn: true }, "downloading to memory because serviceworker is not available")
@@ -39,19 +39,23 @@ export function StreamDownload({ size, filename, cancel, progress }: {
cancel: () => void,
progress: (position: number) => void
}) {
- if (!SW) FallbackStreamDownload(size, filename, progress)
+ if (!SW_ENABLED) FallbackStreamDownload(size, filename, progress)
let position = 0
+ // the sw will handle this download
const path = `/download/${encodeURIComponent(filename ?? "file")}`
const { port1, port2 } = new MessageChannel()
- SW!.postMessage({ path, size }, [port2])
+ send_sw_message({ download: { path, size } }, [port2])
const a = document.createElement("a")
a.href = path
a.download = filename ?? "file"
a.target = "_blank"
- a.click()
+ // TODO this delay is part of a race condition btw
+ setTimeout(() => {
+ a.click()
+ }, 100)
port1.onmessage = ev => {
if (ev.data.abort) {
diff --git a/client-web/source/index.ts b/client-web/source/index.ts
index c92ca2b..8da99ea 100644
--- a/client-web/source/index.ts
+++ b/client-web/source/index.ts
@@ -5,7 +5,7 @@
*/
/// <reference lib="dom" />
-import { init_serviceworker } from "./sw/init.ts";
+import { init_serviceworker } from "./sw/client.ts";
import { esection, OVERLAYS } from "./helper.ts";
import { setup_keybinds } from "./keybinds.ts";
import { log, LOGGER_CONTAINER } from "./logger.ts"
@@ -65,7 +65,7 @@ export async function main() {
if (!globalThis.RTCPeerConnection) return log({ scope: "webrtc", error: true }, "WebRTC not supported.")
if (!globalThis.isSecureContext) log({ scope: "*", warn: true }, "This page is not in a 'Secure Context'")
if (!globalThis.crypto.subtle) return log({ scope: "crypto", error: true }, "SubtleCrypto not availible")
- if (!globalThis.navigator.serviceWorker) log({ scope: "*", warn: true }, "Your browser does not support the Service Worker API, some features dont work without it.")
+ if (!globalThis.navigator.serviceWorker) log({ scope: "*", warn: true }, "Your browser does not support the Service Worker API, forced automatic updates are unavoidable.")
if (room_secret.length < 8) log({ scope: "crypto", warn: true }, "Room name is very short. e2ee is insecure!")
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!")
diff --git a/client-web/source/keybinds.ts b/client-web/source/keybinds.ts
index d096501..28bd263 100644
--- a/client-web/source/keybinds.ts
+++ b/client-web/source/keybinds.ts
@@ -7,7 +7,7 @@
import { create_camera_res, create_mic_res, create_screencast_res } from "./resource/track.ts";
import { Room } from "./room.ts"
-import { update_serviceworker } from "./sw/init.ts";
+import { update_serviceworker } from "./sw/client.ts";
export function setup_keybinds(room: Room) {
// let command_mode = false
diff --git a/client-web/source/resource/file.ts b/client-web/source/resource/file.ts
index 38bc566..f577580 100644
--- a/client-web/source/resource/file.ts
+++ b/client-web/source/resource/file.ts
@@ -7,7 +7,7 @@
import { display_filesize, ebutton, ediv, espan, sleep } from "../helper.ts";
import { log } from "../logger.ts";
-import { StreamDownload } from "../sw/download_stream.ts";
+import { StreamDownload } from "../download_stream.ts";
import { RemoteUser } from "../user/remote.ts";
import { LocalResource, ResourceHandlerDecl } from "./mod.ts";
diff --git a/client-web/source/sw/init.ts b/client-web/source/sw/client.ts
index d082038..1396826 100644
--- a/client-web/source/sw/init.ts
+++ b/client-web/source/sw/client.ts
@@ -6,25 +6,34 @@
/// <reference lib="dom" />
import { log } from "../logger.ts"
+import { SWMessage } from "./protocol.ts"
+
+export let SW_ENABLED = false
-export let SW: ServiceWorker | undefined
export async function init_serviceworker() {
let reg = await globalThis.navigator.serviceWorker.getRegistration()
if (reg) {
log("sw", "service worker already installed")
+ SW_ENABLED = true
} else {
log("sw", "registering service worker")
await globalThis.navigator.serviceWorker.register("/sw.js", { scope: "/", type: "module" })
log("sw", "worker installed")
reg = await globalThis.navigator.serviceWorker.getRegistration();
if (!reg) throw new Error("we just registered the sw!?");
+ SW_ENABLED = !!reg
}
- const i = setInterval(() => {
- if (reg!.active) {
- SW = reg!.active
- clearInterval(i)
- }
- }, 100)
+ start_handler()
+ log("sw", "checking for updates")
+ send_sw_message({ check_version: true })
+}
+
+export async function send_sw_message(message: SWMessage, transfer?: Transferable[]) {
+ const reg = await globalThis.navigator.serviceWorker.getRegistration();
+ if (!reg) throw new Error("no sw");
+ if (!reg.active) throw new Error("no sw");
+ if (transfer) reg.active.postMessage(message, transfer)
+ else reg.active.postMessage(message, transfer)
}
export async function update_serviceworker() {
@@ -33,3 +42,15 @@ export async function update_serviceworker() {
log("sw", "cleared all workers")
setTimeout(() => window.location.reload(), 500)
}
+
+function start_handler() {
+ globalThis.navigator.serviceWorker.addEventListener("message", event => {
+ const message: SWMessage = event.data;
+ if (message.version_info) {
+ log("sw", JSON.stringify(message.version_info))
+ }
+ if (message.updated) {
+ log("*", "updated")
+ }
+ })
+}
diff --git a/client-web/source/sw/download.ts b/client-web/source/sw/download.ts
new file mode 100644
index 0000000..02c5e1a
--- /dev/null
+++ b/client-web/source/sw/download.ts
@@ -0,0 +1,36 @@
+export const streams = new Map<string, { readable: ReadableStream, size: number }>()
+
+export function handle_download_request(path: string, event: FetchEvent) {
+ const stream = streams.get(path)
+ if (stream) {
+ streams.delete(path)
+ console.log(`-> stream response`);
+ event.respondWith(
+ new Response(
+ stream.readable,
+ {
+ headers: new Headers({
+ "content-type": "application/octet-stream; charset=utf-8", // TODO transmit and set accordingly
+ "content-security-policy": "default-src 'none'",
+ "content-length": `${stream.size}`,
+ })
+ }
+ )
+ )
+ }
+ event.respondWith(new Response("download failed", { status: 400, headers: new Headers({ "content-type": "text/plain" }) }))
+}
+
+export function port_to_readable(port: MessagePort): ReadableStream {
+ return new ReadableStream({
+ start(controller) {
+ console.log("ReadableStream started");
+ port.onmessage = event => {
+ if (event.data === "end") controller.close()
+ else if (event.data === "abort") controller.error("aborted")
+ else controller.enqueue(event.data)
+ }
+ },
+ cancel() { console.log("ReadableStream cancelled"); port.postMessage({ abort: true }) },
+ })
+}
diff --git a/client-web/source/sw/protocol.ts b/client-web/source/sw/protocol.ts
new file mode 100644
index 0000000..7cca220
--- /dev/null
+++ b/client-web/source/sw/protocol.ts
@@ -0,0 +1,9 @@
+
+export interface SWMessage {
+ download?: { path: string, size: number }
+ check_version?: boolean,
+ update?: boolean,
+
+ version_info?: { installed_version: string, available_version: string }
+ updated?: boolean
+}
diff --git a/client-web/source/sw/worker.ts b/client-web/source/sw/worker.ts
index a06f3db..442f08a 100644
--- a/client-web/source/sw/worker.ts
+++ b/client-web/source/sw/worker.ts
@@ -4,9 +4,12 @@
Copyright (C) 2022 metamuffin <metamuffin@disroot.org>
*/
/// <reference no-default-lib="true"/>
-
/// <reference lib="esnext" />
/// <reference lib="webworker" />
+
+import { handle_download_request, port_to_readable, streams } from "./download.ts";
+import { SWMessage } from "./protocol.ts";
+
declare const self: ServiceWorkerGlobalScope; export { };
console.log("hello from the keks-meet service worker");
@@ -25,53 +28,87 @@ self.addEventListener("unload", () => {
console.log("unload")
})
-const streams = new Map<string, { readable: ReadableStream, size: number }>()
-
-self.addEventListener("message", ev => {
- const { path, size } = ev.data, port = ev.ports[0]
- const readable = port_to_readable(port)
- streams.set(path, { readable, size })
+self.addEventListener("message", async ev => {
+ const message: SWMessage = ev.data;
+ console.log("incoming message", message);
+ if (message.download) {
+ const { path, size } = message.download, port = ev.ports[0]
+ const readable = port_to_readable(port)
+ streams.set(path, { readable, size })
+ }
+ if (message.check_version) {
+ broadcast_response(await check_for_updates())
+ }
+ if (message.update) {
+ broadcast_response(await update())
+ }
})
-function port_to_readable(port: MessagePort): ReadableStream {
- return new ReadableStream({
- start(controller) {
- console.log("ReadableStream started");
- port.onmessage = event => {
- if (event.data === "end") controller.close()
- else if (event.data === "abort") controller.error("aborted")
- else controller.enqueue(event.data)
- }
- },
- cancel() { console.log("ReadableStream cancelled"); port.postMessage({ abort: true }) },
- })
+async function broadcast_response(message: SWMessage) {
+ const clients = await self.clients.matchAll({})
+ console.log(clients);
+ clients.forEach(c => c.postMessage(message))
}
-self.addEventListener("fetch", event => {
+self.addEventListener("fetch", async event => {
const { request } = event;
if (!request.url.startsWith(self.origin)) return
const path = request.url.substring(self.origin.length)
console.log(request.method, path);
- const stream = streams.get(path)
- if (stream) {
- streams.delete(path)
- console.log(`-> stream response`);
- return event.respondWith(
- new Response(
- stream.readable,
- {
- headers: new Headers({
- "content-type": "application/octet-stream; charset=utf-8", // TODO transmit and set accordingly
- "content-security-policy": "default-src 'none'",
- "content-length": `${stream.size}`,
- })
- }
- )
- )
- }
-
+ if (path.startsWith("/download")) return handle_download_request(path, event)
+ if (path.startsWith("/signaling")) return event.respondWith(fetch(request))
if (path == "/swtest") return event.respondWith(new Response("works!", { headers: new Headers({ "content-type": "text/plain" }) }))
- event.respondWith(fetch(request))
+ const cache = await caches.open("v1")
+ // const cached = await cache.match(request)
+ // if (cached) {
+ // console.log("-> cached");
+ // return cached
+ // }
+ console.log("-> forwarding to the server");
+ const response = await fetch(request);
+ cache.put(request, response.clone())
+ event.respondWith(response.clone())
})
+
+
+async function update(): Promise<SWMessage> {
+ console.log("updating...");
+ await caches.delete("v1")
+ await Promise.all(
+ [
+ "/",
+ "/room",
+ "/config.json",
+ "/assets/bundle.js",
+ "/favicon.ico"
+ ]
+ .map(cache_preload)
+ )
+ return { updated: true }
+}
+
+async function cache_preload(path: string) {
+ const cache = await caches.open("v1")
+ const req = new Request(path)
+ const res = await fetch(req)
+ await cache.put(req, res)
+}
+
+async function check_for_updates(): Promise<SWMessage> {
+ console.log("checking for updates");
+ const cache = await caches.open("v1")
+ const res = await fetch("/version")
+ const res2 = await cache.match(new Request("/version"));
+ const available_version = await res.text()
+ const installed_version = res2 ? await res2.text() : "none";
+ console.log({ available_version, installed_version });
+ return {
+ version_info: {
+ available_version,
+ installed_version
+ }
+ }
+}
+