UNPKG

wasmrs-js

Version:

A JavaScript implementation of the RSocket protocol over WebAssembly.

120 lines (106 loc) 3.37 kB
// 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; } }