haskell-wasm-repl/web/worker.js

213 lines
8.3 KiB
JavaScript
Raw Normal View History

2024-10-10 19:12:48 +05:30
importScripts("https://cdn.jsdelivr.net/npm/xterm-pty@0.9.4/workerTools.js");
importScripts(location.origin + "/browser_wasi_shim/index.js");
importScripts(location.origin + "/browser_wasi_shim/wasi_defs.js");
importScripts(location.origin + "/worker-util.js");
importScripts(location.origin + "/wasi-util.js");
onmessage = (msg) => {
if (serveIfInitMsg(msg)) {
return;
}
var ttyClient = new TtyClient(msg.data);
var args = [];
var env = [];
var fds = [];
var netParam = null;
var listenfd = 3;
fetch(getImagename(), { credentials: 'same-origin' }).then((resp) => {
resp['arrayBuffer']().then((wasm) => {
if (netParam) {
if (netParam.mode == 'delegate') {
args = ['arg0', '--net=socket', '--mac', genmac()];
} else if (netParam.mode == 'browser') {
recvCert().then((cert) => {
var certDir = getCertDir(cert);
fds = [
undefined, // 0: stdin
undefined, // 1: stdout
undefined, // 2: stderr
certDir, // 3: certificates dir
undefined, // 4: socket listenfd
undefined, // 5: accepted socket fd (multi-connection is unsupported)
// 6...: used by wasi shim
];
args = ['arg0', '--net=socket=listenfd=4', '--mac', genmac()];
env = [
"SSL_CERT_FILE=/.wasmenv/proxy.crt",
"https_proxy=http://192.168.127.253:80",
"http_proxy=http://192.168.127.253:80",
"HTTPS_PROXY=http://192.168.127.253:80",
"HTTP_PROXY=http://192.168.127.253:80"
];
listenfd = 4;
startWasi(wasm, ttyClient, args, env, fds, listenfd, 5);
});
return;
}
}
startWasi(wasm, ttyClient, args, env, fds, listenfd, 5);
})
});
};
function startWasi(wasm, ttyClient, args, env, fds, listenfd, connfd) {
var wasi = new WASI(args, env, fds);
wasiHack(wasi, ttyClient, connfd);
wasiHackSocket(wasi, listenfd, connfd);
WebAssembly.instantiate(wasm, {
"wasi_snapshot_preview1": wasi.wasiImport,
}).then((inst) => {
wasi.start(inst.instance);
});
}
// wasiHack patches wasi object for integrating it to xterm-pty.
function wasiHack(wasi, ttyClient, connfd) {
// definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt
const ERRNO_INVAL = 28;
const ERRNO_AGAIN= 6;
var _fd_read = wasi.wasiImport.fd_read;
wasi.wasiImport.fd_read = (fd, iovs_ptr, iovs_len, nread_ptr) => {
if (fd == 0) {
var buffer = new DataView(wasi.inst.exports.memory.buffer);
var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer);
var iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len);
var nread = 0;
for (i = 0; i < iovecs.length; i++) {
var iovec = iovecs[i];
if (iovec.buf_len == 0) {
continue;
}
var data = ttyClient.onRead(iovec.buf_len);
buffer8.set(data, iovec.buf);
nread += data.length;
}
buffer.setUint32(nread_ptr, nread, true);
return 0;
} else {
console.log("fd_read: unknown fd " + fd);
return _fd_read.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nread_ptr]);
}
return ERRNO_INVAL;
}
var _fd_write = wasi.wasiImport.fd_write;
wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => {
if ((fd == 1) || (fd == 2)) {
var buffer = new DataView(wasi.inst.exports.memory.buffer);
var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer);
var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len);
var wtotal = 0
for (i = 0; i < iovecs.length; i++) {
var iovec = iovecs[i];
var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len);
if (buf.length == 0) {
continue;
}
ttyClient.onWrite(Array.from(buf));
wtotal += buf.length;
}
buffer.setUint32(nwritten_ptr, wtotal, true);
return 0;
} else {
console.log("fd_write: unknown fd " + fd);
return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]);
}
return ERRNO_INVAL;
}
wasi.wasiImport.poll_oneoff = (in_ptr, out_ptr, nsubscriptions, nevents_ptr) => {
if (nsubscriptions == 0) {
return ERRNO_INVAL;
}
let buffer = new DataView(wasi.inst.exports.memory.buffer);
let in_ = Subscription.read_bytes_array(buffer, in_ptr, nsubscriptions);
let isReadPollStdin = false;
let isReadPollConn = false;
let isClockPoll = false;
let pollSubStdin;
let pollSubConn;
let clockSub;
let timeout = Number.MAX_VALUE;
for (let sub of in_) {
if (sub.u.tag.variant == "fd_read") {
if ((sub.u.data.fd != 0) && (sub.u.data.fd != connfd)) {
console.log("poll_oneoff: unknown fd " + sub.u.data.fd);
return ERRNO_INVAL; // only fd=0 and connfd is supported as of now (FIXME)
}
if (sub.u.data.fd == 0) {
isReadPollStdin = true;
pollSubStdin = sub;
} else {
isReadPollConn = true;
pollSubConn = sub;
}
} else if (sub.u.tag.variant == "clock") {
if (sub.u.data.timeout < timeout) {
timeout = sub.u.data.timeout
isClockPoll = true;
clockSub = sub;
}
} else {
console.log("poll_oneoff: unknown variant " + sub.u.tag.variant);
return ERRNO_INVAL; // FIXME
}
}
let events = [];
if (isReadPollStdin || isReadPollConn || isClockPoll) {
var readable = false;
if (isReadPollStdin || (isClockPoll && timeout > 0)) {
readable = ttyClient.onWaitForReadable(timeout / 1000000000);
}
if (readable && isReadPollStdin) {
let event = new Event();
event.userdata = pollSubStdin.userdata;
event.error = 0;
event.type = new EventType("fd_read");
events.push(event);
}
if (isReadPollConn) {
var sockreadable = sockWaitForReadable();
if (sockreadable == errStatus) {
return ERRNO_INVAL;
} else if (sockreadable == true) {
let event = new Event();
event.userdata = pollSubConn.userdata;
event.error = 0;
event.type = new EventType("fd_read");
events.push(event);
}
}
if (isClockPoll) {
let event = new Event();
event.userdata = clockSub.userdata;
event.error = 0;
event.type = new EventType("clock");
events.push(event);
}
}
var len = events.length;
Event.write_bytes_array(buffer, out_ptr, events);
buffer.setUint32(nevents_ptr, len, true);
return 0;
}
}
function getNetParam() {
var vars = location.search.substring(1).split('&');
for (var i = 0; i < vars.length; i++) {
var kv = vars[i].split('=');
if (decodeURIComponent(kv[0]) == 'net') {
return {
mode: kv[1],
param: kv[2],
};
}
}
return null;
}
function genmac(){
return "02:XX:XX:XX:XX:XX".replace(/X/g, function() {
return "0123456789ABCDEF".charAt(Math.floor(Math.random() * 16))
});
}