Path: blob/main/core/kernel/src/wasm/io-using-service-worker.ts
1067 views
/*1Synchronous blocking IO using service workers and XMLHttpRequest,2in cases when can't use atomics. By "IO", we also include "IO with3the system", e.g., signals.45This is inspired by the sync-message package.67References:89- https://github.com/alexmojaki/sync-message10- https://jasonformat.com/javascript-sleep/11- https://stackoverflow.com/questions/10590213/synchronously-wait-for-message-in-web-worker12- https://github.com/pyodide/pyodide/issues/15031314*/1516import type { IOProvider } from "./types";17import { SIGINT } from "./constants";18import debug from "debug";19import { v4 as uuidv4 } from "uuid";2021const log = debug("wasm:io-provider");2223export default class IOProviderUsingServiceWorker implements IOProvider {24private id: string = uuidv4();2526constructor() {27log("IOProviderUsingXMLHttpRequest", "id = ", this.id);28registerServiceWorker();29}3031getExtraOptions() {32return { id: this.id };33}3435private async send(36target: "write-signal" | "write-stdin" | "read-output",37body: object38): Promise<any> {39const url = `/python-wasm-sw/${target}`;40try {41return await fetch(url, { method: "POST", body: JSON.stringify(body) });42} catch (err) {43console.warn("failed to send to service worker", { url, body }, err);44}45}4647signal(sig: number = SIGINT): void {48log("signal", sig);49this.send("write-signal", { sig, id: this.id });50}5152writeToStdin(data: Buffer): void {53log("writeToStdin", data);54this.send("write-stdin", { data: data.toString(), id: this.id });55}5657async readOutput(): Promise<Buffer> {58const output = await this.send("read-output", { id: this.id });59return Buffer.from(await output.text());60}61}6263function getURL(): string {64// @ts-ignore this import.meta.url issue -- actually only consumed by webpack65const url = new URL("./worker/service-worker.js", import.meta.url).href;66console.log("service worker url = ", url);67return url;68}6970function hasServiceWorker() {71if (!navigator.serviceWorker) {72console.warn(73"WARNING: service worker is not available, so nothing is going to work"74);75return false;76}77return true;78}7980async function registerServiceWorker() {81if (!hasServiceWorker()) return;82const url = getURL();83const reg = await navigator.serviceWorker.register(url);84if (reg.active?.state != "activated") {85// I think there is no way around this, since it is an unfortunate86// part of the service worker spec.87console.warn("Reloading page to activate service worker...");88if (localStorage["python-wasm-service-worker-broken"]) {89// use local storage to avoid DOS of server90setTimeout(() => {91location.reload();92}, 3000);93} else {94localStorage["python-wasm-service-worker-broken"] = true;95location.reload();96}97} else {98// It probably worked.99delete localStorage["python-wasm-service-worker-broken"];100}101}102103/*104fixServiceWorker:105106There is exactly one situation where I know this is definitely needed, though107browsers I think could revoke the service worker at any time, so it is good to108have an automated way to fix this. Also, this may be useful if we upgrade the109service worker and add a new URL endpoint, since this will get triggered.1101111. Open python-wasm using a service worker on an iphone or ipad in safari.1121132. Maybe open another page so the python-wasm page is in the background.114(Probably not needed.)1151163. Suspend your phone and wait 1 minute.1171184. Turn phone back on. The service worker *might* be completely broken and no119amount of refreshing fixes it. Without the workaround below, the only option is120for the user to clear all private data associated with the site, or wait a while121(maybe an hour) and things maybe start to work again.122123I think this is caused by the page suspending in the middle of a "get stdin"124call. This code below does seem to effectively work around the problem, at the125expense of a page refresh when you return to the page. This isn't uncommon on126safari anyways though, since it often dumps pages to save memory.127128This doesn't seem to happen on any desktop browsers (including safari) as far129as I can tell, even when suspending/resuming a laptop.130131There may be a better fix involving changing how the service worker behaves,132but that is for another commit, and another day.133*/134export async function fixServiceWorker() {135if (!hasServiceWorker()) return;136console.warn("The service work seems to be disabled. Fixing it...");137const url = getURL();138try {139const reg = await navigator.serviceWorker.register(url);140await reg.unregister();141} catch (err) {142console.warn(err);143}144location.reload();145}146147148