diff --git a/package-lock.json b/package-lock.json index e7e7b1e..7354842 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-replaced-by-ci", "license": "BSD-3-Clause", "devDependencies": { - "@bjorn3/browser_wasi_shim": "^0.2.17", + "@bjorn3/browser_wasi_shim": "^0.2.20", "@playwright/test": "^1.39.0", "@types/node": "^20.8.7", "@typescript-eslint/eslint-plugin": "^6.8.0", @@ -37,9 +37,9 @@ } }, "node_modules/@bjorn3/browser_wasi_shim": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.2.17.tgz", - "integrity": "sha512-B2qcaGROo4e2s4nXb3VPATrczVrntM4BUXtAU1gEzUOfqKTcVuePq4NfhH5hmLBSvZ45YcT4gflDRUFYqLhkxA==", + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.2.20.tgz", + "integrity": "sha512-URvkOAWWRQ8gazkBRgQ/e0H6R0VlOALJ9/vI2VBttG23MHzUZzy5KFyKAFVI1qL/hbqLwnZw/sIXFkeS6OH5EQ==", "dev": true }, "node_modules/@esbuild/android-arm": { diff --git a/package.json b/package.json index fca8f88..e852a13 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "author": "The Extism Authors ", "license": "BSD-3-Clause", "devDependencies": { - "@bjorn3/browser_wasi_shim": "^0.2.17", + "@bjorn3/browser_wasi_shim": "^0.2.20", "@playwright/test": "^1.39.0", "@types/node": "^20.8.7", "@typescript-eslint/eslint-plugin": "^6.8.0", diff --git a/src/foreground-plugin.ts b/src/foreground-plugin.ts index b85229c..bbb9c96 100644 --- a/src/foreground-plugin.ts +++ b/src/foreground-plugin.ts @@ -156,7 +156,7 @@ async function instantiateModule( } if (wasi === null) { - wasi = await loadWasi(opts.allowedPaths, opts.enableWasiOutput); + wasi = await loadWasi(opts.allowedPaths, opts.enableWasiOutput, opts.wasiOptions); wasiList.push(wasi); imports.wasi_snapshot_preview1 = await wasi.importObject(); } @@ -211,9 +211,9 @@ async function instantiateModule( const instance = providerExports.find((xs) => xs.name === '_start') ? await instantiateModule([...current, module], provider, imports, opts, wasiList, names, modules, new Map()) : !linked.has(provider) - ? (await instantiateModule([...current, module], provider, imports, opts, wasiList, names, modules, linked), - linked.get(provider)) - : linked.get(provider); + ? (await instantiateModule([...current, module], provider, imports, opts, wasiList, names, modules, linked), + linked.get(provider)) + : linked.get(provider); if (!instance) { // circular import, either make a trampoline or bail @@ -254,10 +254,10 @@ async function instantiateModule( const guestType = instance.exports.hs_init ? 'haskell' : instance.exports._initialize - ? 'reactor' - : instance.exports._start - ? 'command' - : 'none'; + ? 'reactor' + : instance.exports._start + ? 'command' + : 'none'; if (wasi) { await wasi?.initialize(instance); diff --git a/src/interfaces.ts b/src/interfaces.ts index 7bcab64..3942937 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -123,6 +123,19 @@ export interface Plugin { reset(): Promise; } +/** + * Options for initializing WASI. + */ +export interface WASIOptions { + /** + * A list of file descriptors; only available in browser environments. + * + * See [`@bjorn3/browser_wasi_shim`](https://github.com/bjorn3/browser_wasi_shim) for more + * details on use. + */ + fileDescriptors: any[]; +} + /** * Options for initializing an Extism plugin. */ @@ -167,6 +180,7 @@ export interface ExtismPluginOptions { functions?: { [key: string]: { [key: string]: (callContext: CallContext, ...args: any[]) => any } } | undefined; allowedPaths?: { [key: string]: string } | undefined; allowedHosts?: string[] | undefined; + wasiOptions?: WASIOptions; /** * Whether WASI stdout should be forwarded to the host. @@ -189,6 +203,7 @@ export interface InternalConfig { wasiEnabled: boolean; config: PluginConfig; sharedArrayBufferSize: number; + wasiOptions: WASIOptions | null; } export interface InternalWasi { diff --git a/src/mod.ts b/src/mod.ts index 075cb38..089cb85 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -99,6 +99,7 @@ export async function createPlugin( config: opts.config, enableWasiOutput: opts.enableWasiOutput, sharedArrayBufferSize: Number(opts.sharedArrayBufferSize) || 1 << 16, + wasiOptions: opts.wasiOptions ?? null, }; return (opts.runInWorker ? _createBackgroundPlugin : _createForegroundPlugin)(ic, names, moduleData); diff --git a/src/polyfills/browser-wasi.ts b/src/polyfills/browser-wasi.ts index e2d4db3..1aacfdd 100644 --- a/src/polyfills/browser-wasi.ts +++ b/src/polyfills/browser-wasi.ts @@ -1,48 +1,29 @@ -import { WASI, Fd, File, OpenFile, wasi } from '@bjorn3/browser_wasi_shim'; -import { type InternalWasi } from '../mod.ts'; - -class Output extends Fd { - #mode: string; - - constructor(mode: string) { - super(); - this.#mode = mode; - } - - fd_write(view8: Uint8Array, iovs: [wasi.Iovec]): { ret: number; nwritten: number } { - let nwritten = 0; - const decoder = new TextDecoder(); - const str = iovs.reduce((acc, iovec, idx, all) => { - nwritten += iovec.buf_len; - const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - return acc + decoder.decode(buffer, { stream: idx !== all.length - 1 }); - }, ''); - - (console[this.#mode] as any)(str); - - return { ret: 0, nwritten }; - } -} +import { WASI, Fd, File, OpenFile, ConsoleStdout } from '@bjorn3/browser_wasi_shim'; +import { type WASIOptions, type InternalWasi } from '../interfaces.ts'; export async function loadWasi( _allowedPaths: { [from: string]: string }, enableWasiOutput: boolean, + wasiOptions: WASIOptions | null ): Promise { const args: Array = []; const envVars: Array = []; + const fileDescriptors = (wasiOptions?.fileDescriptors || []) as Fd[] const fds: Fd[] = enableWasiOutput ? [ - new Output('log'), // fd 0 is dup'd to stdout - new Output('log'), - new Output('error'), - ] + ConsoleStdout.lineBuffered((msg) => console.log(msg)), // fd 0 is dup'd to stdout + ConsoleStdout.lineBuffered((msg) => console.log(msg)), + ConsoleStdout.lineBuffered((msg) => console.warn(msg)), + ...fileDescriptors, + ] : [ - new OpenFile(new File([])), // stdin - new OpenFile(new File([])), // stdout - new OpenFile(new File([])), // stderr - ]; + new OpenFile(new File([])), // stdin + new OpenFile(new File([])), // stdout + new OpenFile(new File([])), // stderr + ...fileDescriptors, + ]; - const context = new WASI(args, envVars, fds); + const context = new WASI(args, envVars, fds, { debug: false }); return { async importObject() { @@ -78,7 +59,7 @@ export async function loadWasi( context.start({ exports: { memory, - _start: () => {}, + _start: () => { }, }, }); } diff --git a/src/polyfills/deno-wasi.ts b/src/polyfills/deno-wasi.ts index 3aa3067..a1e4e95 100644 --- a/src/polyfills/deno-wasi.ts +++ b/src/polyfills/deno-wasi.ts @@ -1,5 +1,5 @@ import Context from './deno-snapshot_preview1.ts'; -import { type InternalWasi } from '../interfaces.ts'; +import { type WASIOptions, type InternalWasi } from '../interfaces.ts'; import { devNull } from 'node:os'; import { open } from 'node:fs/promises'; import { closeSync } from 'node:fs'; @@ -20,7 +20,7 @@ async function createDevNullFDs() { return { async close() { needsClose = false; - await Promise.all([stdin.close(), stdout.close()]).catch(() => {}); + await Promise.all([stdin.close(), stdout.close()]).catch(() => { }); }, fds: [stdin.fd, stdout.fd, stdout.fd], }; @@ -29,11 +29,12 @@ async function createDevNullFDs() { export async function loadWasi( allowedPaths: { [from: string]: string }, enableWasiOutput: boolean, + _wasiOptions: WASIOptions | null, ): Promise { const { close, fds: [stdin, stdout, stderr], - } = enableWasiOutput ? { async close() {}, fds: [0, 1, 2] } : await createDevNullFDs(); + } = enableWasiOutput ? { async close() { }, fds: [0, 1, 2] } : await createDevNullFDs(); const context = new Context({ preopens: allowedPaths, exitOnReturn: false, @@ -76,7 +77,7 @@ export async function loadWasi( context.start({ exports: { memory, - _start: () => {}, + _start: () => { }, }, }); } diff --git a/src/polyfills/node-wasi.ts b/src/polyfills/node-wasi.ts index 512515a..2c39ff1 100644 --- a/src/polyfills/node-wasi.ts +++ b/src/polyfills/node-wasi.ts @@ -1,5 +1,5 @@ import { WASI } from 'wasi'; -import { type InternalWasi } from '../interfaces.ts'; +import { type WASIOptions, type InternalWasi } from '../interfaces.ts'; import { devNull } from 'node:os'; import { open } from 'node:fs/promises'; import { closeSync } from 'node:fs'; @@ -26,7 +26,7 @@ async function createDevNullFDs() { fr.register(stdout, stdout.fd); close = async () => { needsClose = false; - await Promise.all([stdin.close(), stdout.close()]).catch(() => {}); + await Promise.all([stdin.close(), stdout.close()]).catch(() => { }); }; } @@ -39,11 +39,12 @@ async function createDevNullFDs() { export async function loadWasi( allowedPaths: { [from: string]: string }, enableWasiOutput: boolean, + _wasiOptions: WASIOptions | null, ): Promise { const { close, fds: [stdin, stdout, stderr], - } = enableWasiOutput ? { async close() {}, fds: [0, 1, 2] } : await createDevNullFDs(); + } = enableWasiOutput ? { async close() { }, fds: [0, 1, 2] } : await createDevNullFDs(); const context = new WASI({ version: 'preview1', @@ -87,7 +88,7 @@ export async function loadWasi( context.start({ exports: { memory, - _start: () => {}, + _start: () => { }, }, }); }