gwchq-textjam 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +202 -202
- package/README.md +266 -266
- package/dist/assets/{PyodideWorker5d8b7bf9f06568fe4573.js → PyodideWorkerb1409749c37aedc47dc8.js} +959 -959
- package/dist/assets/{pygalef3b78a56cb1d66beb61.js → pygalc0b4f32d2d2cc5a0c638.js} +495 -495
- package/dist/assets/pyodide-stdin-sw.js +238 -231
- package/dist/index.js +3913 -3099
- package/dist/style.css +17 -16
- package/package.json +301 -301
|
@@ -1,231 +1,238 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-globals */
|
|
2
|
-
|
|
3
|
-
const DEBUG_STDIN_SW = false;
|
|
4
|
-
const STDIN_ENDPOINT_PATH = "/pyodide-stdin";
|
|
5
|
-
const DEFAULT_INPUT_TIMEOUT_MS = 120000;
|
|
6
|
-
const CANCELLED_STATUS = 499;
|
|
7
|
-
const TIMEOUT_STATUS = 408;
|
|
8
|
-
const ABORTED_STATUS = 410;
|
|
9
|
-
// HTTP 204 = clean EOF (Ctrl+D, persistent stdinClosed). Distinguished from
|
|
10
|
-
// 499 cancel so the Pyodide worker can raise EOFError instead of KeyboardInterrupt.
|
|
11
|
-
const EOF_STATUS = 204;
|
|
12
|
-
|
|
13
|
-
/** @type {Map<string, { resolve: (response: Response) => void, timeoutId: number, runId: string, requestId: string, clientId: string }>} */
|
|
14
|
-
const pendingRequests = new Map();
|
|
15
|
-
/** @type {Map<string, string>} */
|
|
16
|
-
const tabClientToWindowClient = new Map();
|
|
17
|
-
|
|
18
|
-
const log = (...args) => {
|
|
19
|
-
if (!DEBUG_STDIN_SW) return;
|
|
20
|
-
console.log("[pyodide-stdin-sw]", ...args);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const pendingKey = (clientId, runId, requestId) =>
|
|
24
|
-
[clientId || "", runId || "", requestId || ""].join(":");
|
|
25
|
-
|
|
26
|
-
const noStoreHeaders = {
|
|
27
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
28
|
-
"Cache-Control": "no-store",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const responseWith = (status, body) =>
|
|
32
|
-
new Response(body, {
|
|
33
|
-
status,
|
|
34
|
-
headers: noStoreHeaders,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const finishPending = (key, status, body) => {
|
|
38
|
-
const pending = pendingRequests.get(key);
|
|
39
|
-
if (!pending) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
clearTimeout(pending.timeoutId);
|
|
44
|
-
pending.resolve(responseWith(status, body));
|
|
45
|
-
pendingRequests.delete(key);
|
|
46
|
-
return true;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const findPendingEntries = (clientId, runId, requestId) => {
|
|
50
|
-
const matches = [];
|
|
51
|
-
for (const [key, pending] of pendingRequests.entries()) {
|
|
52
|
-
if (clientId && pending.clientId !== clientId) continue;
|
|
53
|
-
if (runId && pending.runId !== runId) continue;
|
|
54
|
-
if (requestId && pending.requestId !== requestId) continue;
|
|
55
|
-
matches.push([key, pending]);
|
|
56
|
-
}
|
|
57
|
-
return matches;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
self.addEventListener("install", (event) => {
|
|
61
|
-
log("install");
|
|
62
|
-
event.waitUntil(self.skipWaiting());
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
self.addEventListener("activate", (event) => {
|
|
66
|
-
log("activate");
|
|
67
|
-
event.waitUntil(self.clients.claim());
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
self.addEventListener("fetch", (event) => {
|
|
71
|
-
const requestUrl = new URL(event.request.url);
|
|
72
|
-
if (
|
|
73
|
-
event.request.method !== "GET" ||
|
|
74
|
-
requestUrl.pathname !== STDIN_ENDPOINT_PATH
|
|
75
|
-
) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
event.respondWith(handleStdinFetch(event, requestUrl));
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const handleStdinFetch = async (event, requestUrl) => {
|
|
83
|
-
const runId = requestUrl.searchParams.get("runId") || "";
|
|
84
|
-
const requestId = requestUrl.searchParams.get("requestId") || "";
|
|
85
|
-
const tabClientId = requestUrl.searchParams.get("clientId") || "";
|
|
86
|
-
const timeoutMsRaw = Number.parseInt(
|
|
87
|
-
requestUrl.searchParams.get("timeoutMs") || "",
|
|
88
|
-
10,
|
|
89
|
-
);
|
|
90
|
-
const timeoutMs =
|
|
91
|
-
Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
|
|
92
|
-
? timeoutMsRaw
|
|
93
|
-
: DEFAULT_INPUT_TIMEOUT_MS;
|
|
94
|
-
|
|
95
|
-
const fetchClientId = event.clientId || "";
|
|
96
|
-
const clientId = tabClientId || fetchClientId;
|
|
97
|
-
|
|
98
|
-
if (!runId || !requestId || !clientId) {
|
|
99
|
-
return responseWith(400, "Invalid stdin request identifiers");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const key = pendingKey(clientId, runId, requestId);
|
|
103
|
-
if (pendingRequests.has(key)) {
|
|
104
|
-
return responseWith(409, "Duplicate stdin request");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
log("stdin request pending", { clientId, runId, requestId });
|
|
108
|
-
|
|
109
|
-
const responsePromise = new Promise((resolve) => {
|
|
110
|
-
const timeoutId = self.setTimeout(() => {
|
|
111
|
-
log("stdin request timeout", { clientId, runId, requestId });
|
|
112
|
-
finishPending(key, TIMEOUT_STATUS, "stdin request timed out");
|
|
113
|
-
}, timeoutMs);
|
|
114
|
-
|
|
115
|
-
pendingRequests.set(key, {
|
|
116
|
-
resolve,
|
|
117
|
-
timeoutId,
|
|
118
|
-
runId,
|
|
119
|
-
requestId,
|
|
120
|
-
clientId,
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const registeredWindowClientId = tabClientToWindowClient.get(clientId) || "";
|
|
125
|
-
let targetClient = null;
|
|
126
|
-
|
|
127
|
-
if (registeredWindowClientId) {
|
|
128
|
-
targetClient = await self.clients.get(registeredWindowClientId);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!targetClient && fetchClientId) {
|
|
132
|
-
const fetchClient = await self.clients.get(fetchClientId);
|
|
133
|
-
if (fetchClient?.type === "window") {
|
|
134
|
-
targetClient = fetchClient;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const message = {
|
|
139
|
-
type: "PYODIDE_STDIN_REQUEST",
|
|
140
|
-
clientId,
|
|
141
|
-
runId,
|
|
142
|
-
requestId,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
if (targetClient) {
|
|
146
|
-
targetClient.postMessage(message);
|
|
147
|
-
return responsePromise;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const matchedClients = await self.clients.matchAll({
|
|
151
|
-
includeUncontrolled: true,
|
|
152
|
-
});
|
|
153
|
-
const windowClients = matchedClients.filter(
|
|
154
|
-
(client) => client.type === "window",
|
|
155
|
-
);
|
|
156
|
-
if (!windowClients.length) {
|
|
157
|
-
finishPending(key, ABORTED_STATUS, "stdin client unavailable");
|
|
158
|
-
return responseWith(ABORTED_STATUS, "stdin client unavailable");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
for (const client of windowClients) {
|
|
162
|
-
client.postMessage(message);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return responsePromise;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
self.addEventListener("message", (event) => {
|
|
169
|
-
const data = event.data;
|
|
170
|
-
if (!data || typeof data !== "object") {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const eventClientId =
|
|
175
|
-
event.source && "id" in event.source ? event.source.id : "";
|
|
176
|
-
const clientId = data.clientId || eventClientId || "";
|
|
177
|
-
|
|
178
|
-
if (data.type === "
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
1
|
+
/* eslint-disable no-restricted-globals */
|
|
2
|
+
|
|
3
|
+
const DEBUG_STDIN_SW = false;
|
|
4
|
+
const STDIN_ENDPOINT_PATH = "/pyodide-stdin";
|
|
5
|
+
const DEFAULT_INPUT_TIMEOUT_MS = 120000;
|
|
6
|
+
const CANCELLED_STATUS = 499;
|
|
7
|
+
const TIMEOUT_STATUS = 408;
|
|
8
|
+
const ABORTED_STATUS = 410;
|
|
9
|
+
// HTTP 204 = clean EOF (Ctrl+D, persistent stdinClosed). Distinguished from
|
|
10
|
+
// 499 cancel so the Pyodide worker can raise EOFError instead of KeyboardInterrupt.
|
|
11
|
+
const EOF_STATUS = 204;
|
|
12
|
+
|
|
13
|
+
/** @type {Map<string, { resolve: (response: Response) => void, timeoutId: number, runId: string, requestId: string, clientId: string }>} */
|
|
14
|
+
const pendingRequests = new Map();
|
|
15
|
+
/** @type {Map<string, string>} */
|
|
16
|
+
const tabClientToWindowClient = new Map();
|
|
17
|
+
|
|
18
|
+
const log = (...args) => {
|
|
19
|
+
if (!DEBUG_STDIN_SW) return;
|
|
20
|
+
console.log("[pyodide-stdin-sw]", ...args);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const pendingKey = (clientId, runId, requestId) =>
|
|
24
|
+
[clientId || "", runId || "", requestId || ""].join(":");
|
|
25
|
+
|
|
26
|
+
const noStoreHeaders = {
|
|
27
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
28
|
+
"Cache-Control": "no-store",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const responseWith = (status, body) =>
|
|
32
|
+
new Response(body, {
|
|
33
|
+
status,
|
|
34
|
+
headers: noStoreHeaders,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const finishPending = (key, status, body) => {
|
|
38
|
+
const pending = pendingRequests.get(key);
|
|
39
|
+
if (!pending) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
clearTimeout(pending.timeoutId);
|
|
44
|
+
pending.resolve(responseWith(status, body));
|
|
45
|
+
pendingRequests.delete(key);
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const findPendingEntries = (clientId, runId, requestId) => {
|
|
50
|
+
const matches = [];
|
|
51
|
+
for (const [key, pending] of pendingRequests.entries()) {
|
|
52
|
+
if (clientId && pending.clientId !== clientId) continue;
|
|
53
|
+
if (runId && pending.runId !== runId) continue;
|
|
54
|
+
if (requestId && pending.requestId !== requestId) continue;
|
|
55
|
+
matches.push([key, pending]);
|
|
56
|
+
}
|
|
57
|
+
return matches;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
self.addEventListener("install", (event) => {
|
|
61
|
+
log("install");
|
|
62
|
+
event.waitUntil(self.skipWaiting());
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
self.addEventListener("activate", (event) => {
|
|
66
|
+
log("activate");
|
|
67
|
+
event.waitUntil(self.clients.claim());
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
self.addEventListener("fetch", (event) => {
|
|
71
|
+
const requestUrl = new URL(event.request.url);
|
|
72
|
+
if (
|
|
73
|
+
event.request.method !== "GET" ||
|
|
74
|
+
requestUrl.pathname !== STDIN_ENDPOINT_PATH
|
|
75
|
+
) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
event.respondWith(handleStdinFetch(event, requestUrl));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const handleStdinFetch = async (event, requestUrl) => {
|
|
83
|
+
const runId = requestUrl.searchParams.get("runId") || "";
|
|
84
|
+
const requestId = requestUrl.searchParams.get("requestId") || "";
|
|
85
|
+
const tabClientId = requestUrl.searchParams.get("clientId") || "";
|
|
86
|
+
const timeoutMsRaw = Number.parseInt(
|
|
87
|
+
requestUrl.searchParams.get("timeoutMs") || "",
|
|
88
|
+
10,
|
|
89
|
+
);
|
|
90
|
+
const timeoutMs =
|
|
91
|
+
Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
|
|
92
|
+
? timeoutMsRaw
|
|
93
|
+
: DEFAULT_INPUT_TIMEOUT_MS;
|
|
94
|
+
|
|
95
|
+
const fetchClientId = event.clientId || "";
|
|
96
|
+
const clientId = tabClientId || fetchClientId;
|
|
97
|
+
|
|
98
|
+
if (!runId || !requestId || !clientId) {
|
|
99
|
+
return responseWith(400, "Invalid stdin request identifiers");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const key = pendingKey(clientId, runId, requestId);
|
|
103
|
+
if (pendingRequests.has(key)) {
|
|
104
|
+
return responseWith(409, "Duplicate stdin request");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
log("stdin request pending", { clientId, runId, requestId });
|
|
108
|
+
|
|
109
|
+
const responsePromise = new Promise((resolve) => {
|
|
110
|
+
const timeoutId = self.setTimeout(() => {
|
|
111
|
+
log("stdin request timeout", { clientId, runId, requestId });
|
|
112
|
+
finishPending(key, TIMEOUT_STATUS, "stdin request timed out");
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
|
|
115
|
+
pendingRequests.set(key, {
|
|
116
|
+
resolve,
|
|
117
|
+
timeoutId,
|
|
118
|
+
runId,
|
|
119
|
+
requestId,
|
|
120
|
+
clientId,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const registeredWindowClientId = tabClientToWindowClient.get(clientId) || "";
|
|
125
|
+
let targetClient = null;
|
|
126
|
+
|
|
127
|
+
if (registeredWindowClientId) {
|
|
128
|
+
targetClient = await self.clients.get(registeredWindowClientId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!targetClient && fetchClientId) {
|
|
132
|
+
const fetchClient = await self.clients.get(fetchClientId);
|
|
133
|
+
if (fetchClient?.type === "window") {
|
|
134
|
+
targetClient = fetchClient;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const message = {
|
|
139
|
+
type: "PYODIDE_STDIN_REQUEST",
|
|
140
|
+
clientId,
|
|
141
|
+
runId,
|
|
142
|
+
requestId,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (targetClient) {
|
|
146
|
+
targetClient.postMessage(message);
|
|
147
|
+
return responsePromise;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const matchedClients = await self.clients.matchAll({
|
|
151
|
+
includeUncontrolled: true,
|
|
152
|
+
});
|
|
153
|
+
const windowClients = matchedClients.filter(
|
|
154
|
+
(client) => client.type === "window",
|
|
155
|
+
);
|
|
156
|
+
if (!windowClients.length) {
|
|
157
|
+
finishPending(key, ABORTED_STATUS, "stdin client unavailable");
|
|
158
|
+
return responseWith(ABORTED_STATUS, "stdin client unavailable");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const client of windowClients) {
|
|
162
|
+
client.postMessage(message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return responsePromise;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
self.addEventListener("message", (event) => {
|
|
169
|
+
const data = event.data;
|
|
170
|
+
if (!data || typeof data !== "object") {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const eventClientId =
|
|
175
|
+
event.source && "id" in event.source ? event.source.id : "";
|
|
176
|
+
const clientId = data.clientId || eventClientId || "";
|
|
177
|
+
|
|
178
|
+
if (data.type === "PYODIDE_STDIN_PROBE") {
|
|
179
|
+
if (event.source) {
|
|
180
|
+
event.source.postMessage({ type: "PYODIDE_STDIN_PROBE_ACK" });
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (data.type === "PYODIDE_STDIN_REGISTER") {
|
|
186
|
+
if (data.clientId && eventClientId) {
|
|
187
|
+
tabClientToWindowClient.set(String(data.clientId), String(eventClientId));
|
|
188
|
+
log("registered stdin tab client", {
|
|
189
|
+
clientId: String(data.clientId),
|
|
190
|
+
windowClientId: String(eventClientId),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (data.type === "PYODIDE_STDIN_RESPONSE") {
|
|
197
|
+
const key = pendingKey(clientId, data.runId, data.requestId);
|
|
198
|
+
const submitted = finishPending(key, 200, String(data.value ?? ""));
|
|
199
|
+
if (!submitted) {
|
|
200
|
+
log("stdin response for unknown request", {
|
|
201
|
+
clientId,
|
|
202
|
+
runId: data.runId,
|
|
203
|
+
requestId: data.requestId,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (data.type === "PYODIDE_STDIN_EOF") {
|
|
210
|
+
// EOF is targeted at a specific (runId, requestId) just like a normal response.
|
|
211
|
+
// The worker maps HTTP 204 → null, which Pyodide's line-based stdin treats as EOF.
|
|
212
|
+
const key = pendingKey(clientId, data.runId, data.requestId);
|
|
213
|
+
const submitted = finishPending(key, EOF_STATUS, "");
|
|
214
|
+
if (!submitted) {
|
|
215
|
+
log("stdin EOF for unknown request", {
|
|
216
|
+
clientId,
|
|
217
|
+
runId: data.runId,
|
|
218
|
+
requestId: data.requestId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (data.type === "PYODIDE_STDIN_CANCEL") {
|
|
225
|
+
const matches = findPendingEntries(clientId, data.runId, data.requestId);
|
|
226
|
+
for (const [key] of matches) {
|
|
227
|
+
finishPending(key, CANCELLED_STATUS, "stdin request cancelled");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!matches.length) {
|
|
231
|
+
log("stdin cancel for unknown request", {
|
|
232
|
+
clientId,
|
|
233
|
+
runId: data.runId,
|
|
234
|
+
requestId: data.requestId,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|