Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/kernel/src/wasm/io-using-service-worker.ts
1067 views
1
/*
2
Synchronous blocking IO using service workers and XMLHttpRequest,
3
in cases when can't use atomics. By "IO", we also include "IO with
4
the system", e.g., signals.
5
6
This is inspired by the sync-message package.
7
8
References:
9
10
- https://github.com/alexmojaki/sync-message
11
- https://jasonformat.com/javascript-sleep/
12
- https://stackoverflow.com/questions/10590213/synchronously-wait-for-message-in-web-worker
13
- https://github.com/pyodide/pyodide/issues/1503
14
15
*/
16
17
import type { IOProvider } from "./types";
18
import { SIGINT } from "./constants";
19
import debug from "debug";
20
import { v4 as uuidv4 } from "uuid";
21
22
const log = debug("wasm:io-provider");
23
24
export default class IOProviderUsingServiceWorker implements IOProvider {
25
private id: string = uuidv4();
26
27
constructor() {
28
log("IOProviderUsingXMLHttpRequest", "id = ", this.id);
29
registerServiceWorker();
30
}
31
32
getExtraOptions() {
33
return { id: this.id };
34
}
35
36
private async send(
37
target: "write-signal" | "write-stdin" | "read-output",
38
body: object
39
): Promise<any> {
40
const url = `/python-wasm-sw/${target}`;
41
try {
42
return await fetch(url, { method: "POST", body: JSON.stringify(body) });
43
} catch (err) {
44
console.warn("failed to send to service worker", { url, body }, err);
45
}
46
}
47
48
signal(sig: number = SIGINT): void {
49
log("signal", sig);
50
this.send("write-signal", { sig, id: this.id });
51
}
52
53
writeToStdin(data: Buffer): void {
54
log("writeToStdin", data);
55
this.send("write-stdin", { data: data.toString(), id: this.id });
56
}
57
58
async readOutput(): Promise<Buffer> {
59
const output = await this.send("read-output", { id: this.id });
60
return Buffer.from(await output.text());
61
}
62
}
63
64
function getURL(): string {
65
// @ts-ignore this import.meta.url issue -- actually only consumed by webpack
66
const url = new URL("./worker/service-worker.js", import.meta.url).href;
67
console.log("service worker url = ", url);
68
return url;
69
}
70
71
function hasServiceWorker() {
72
if (!navigator.serviceWorker) {
73
console.warn(
74
"WARNING: service worker is not available, so nothing is going to work"
75
);
76
return false;
77
}
78
return true;
79
}
80
81
async function registerServiceWorker() {
82
if (!hasServiceWorker()) return;
83
const url = getURL();
84
const reg = await navigator.serviceWorker.register(url);
85
if (reg.active?.state != "activated") {
86
// I think there is no way around this, since it is an unfortunate
87
// part of the service worker spec.
88
console.warn("Reloading page to activate service worker...");
89
if (localStorage["python-wasm-service-worker-broken"]) {
90
// use local storage to avoid DOS of server
91
setTimeout(() => {
92
location.reload();
93
}, 3000);
94
} else {
95
localStorage["python-wasm-service-worker-broken"] = true;
96
location.reload();
97
}
98
} else {
99
// It probably worked.
100
delete localStorage["python-wasm-service-worker-broken"];
101
}
102
}
103
104
/*
105
fixServiceWorker:
106
107
There is exactly one situation where I know this is definitely needed, though
108
browsers I think could revoke the service worker at any time, so it is good to
109
have an automated way to fix this. Also, this may be useful if we upgrade the
110
service worker and add a new URL endpoint, since this will get triggered.
111
112
1. Open python-wasm using a service worker on an iphone or ipad in safari.
113
114
2. Maybe open another page so the python-wasm page is in the background.
115
(Probably not needed.)
116
117
3. Suspend your phone and wait 1 minute.
118
119
4. Turn phone back on. The service worker *might* be completely broken and no
120
amount of refreshing fixes it. Without the workaround below, the only option is
121
for the user to clear all private data associated with the site, or wait a while
122
(maybe an hour) and things maybe start to work again.
123
124
I think this is caused by the page suspending in the middle of a "get stdin"
125
call. This code below does seem to effectively work around the problem, at the
126
expense of a page refresh when you return to the page. This isn't uncommon on
127
safari anyways though, since it often dumps pages to save memory.
128
129
This doesn't seem to happen on any desktop browsers (including safari) as far
130
as I can tell, even when suspending/resuming a laptop.
131
132
There may be a better fix involving changing how the service worker behaves,
133
but that is for another commit, and another day.
134
*/
135
export async function fixServiceWorker() {
136
if (!hasServiceWorker()) return;
137
console.warn("The service work seems to be disabled. Fixing it...");
138
const url = getURL();
139
try {
140
const reg = await navigator.serviceWorker.register(url);
141
await reg.unregister();
142
} catch (err) {
143
console.warn(err);
144
}
145
location.reload();
146
}
147
148