wasmrs-js
Version:
A JavaScript implementation of the RSocket protocol over WebAssembly.
120 lines (106 loc) • 3.37 kB
text/typescript
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import {
PreopenDirectory,
WASI as WasiShim,
SyncOPFSFile,
Fd,
} from '@candlecorp/browser_wasi_shim';
import { WasiInterface, WasiOptions } from '../wasi';
class CallbackFd extends Fd {
constructor(private fn: (line: string) => void) {
super();
}
fd_write(
view8: Uint8Array,
iovs: [wasi.Iovec]
): { ret: number; nwritten: number } {
let nwritten = 0;
for (const iovec of iovs) {
const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len);
this.fn(new TextDecoder().decode(buffer));
nwritten += iovec.buf_len;
}
return { ret: 0, nwritten };
}
}
class NullFd extends Fd {
constructor() {
super();
}
}
async function openOpfsDirectory(
root: FileSystemDirectoryHandle,
to: string
): Promise<PreopenDirectory> {
const files: Record<string, SyncOPFSFile | PreopenDirectory> = {};
for await (const [name, item] of root.entries()) {
if (item.kind === 'file') {
const file = item as FileSystemFileHandle;
files[name] = new SyncOPFSFile(await file.createSyncAccessHandle());
} else if (item.kind === 'directory') {
files[name] = await openOpfsDirectory(item, to + '/' + name);
} else {
throw new Error('Unknown item kind: ' + item.kind);
}
}
return new PreopenDirectory(to, files);
}
export class WASI implements WasiInterface {
constructor(private wasi: WasiShim) {}
static async create(options: WasiOptions): Promise<WasiInterface> {
const opfsRoot = await navigator.storage.getDirectory();
const files: Fd[] = [];
if (options.stdin === 0) {
files.push(new NullFd());
} else if (options.stdin === undefined) {
files.push(new NullFd());
} else {
throw new Error('Unsupported stdin mode');
}
if (options.stdout === 1) {
files.push(new CallbackFd(console.log.bind(console, 'stdout: ')));
} else if (options.stdin === undefined) {
files.push(new NullFd());
} else {
throw new Error('Unsupported stdout mode');
}
if (options.stderr === 2) {
files.push(new CallbackFd(console.error.bind(console, 'stderr: ')));
} else if (options.stdin === undefined) {
files.push(new NullFd());
} else {
throw new Error('Unsupported stderr mode');
}
for (const [to, from] of Object.entries(options.preopens || {})) {
if (from.startsWith('opfs:')) {
const opfsPath = from.slice(5);
if (opfsPath === '/') {
files.push(await openOpfsDirectory(opfsRoot, to));
} else {
const root = await opfsRoot.getDirectoryHandle(opfsPath, {
create: true,
});
files.push(await openOpfsDirectory(root, to));
}
}
}
const env = Object.entries(options.env || {}).map(
([key, value]) => `${key}=${value}`
);
return new WASI(
new WasiShim(options.args || [], env, files, { debug: true })
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
start(instance: any): void {
this.wasi.start(instance);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialize(instance: any): void {
this.wasi.initialize(instance);
}
getImports(): WebAssembly.ModuleImports {
return this.wasi.wasiImport;
}
}