diff options
| author | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-31 13:08:08 -0500 |
|---|---|---|
| committer | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-31 13:08:08 -0500 |
| commit | a80df869a92c330fbad9695057720b11a18af901 (patch) | |
| tree | 5307272821cb3adc3f0348413a3b904290890044 /crates/chat-crypto/browser | |
| parent | 9139398d465f5899cc31bc4cdd54daa7392ca550 (diff) | |
Forbid Rust lints and enforce strict typed TypeScript linting
Diffstat (limited to 'crates/chat-crypto/browser')
| -rw-r--r-- | crates/chat-crypto/browser/protocol.d.ts | 6 | ||||
| -rw-r--r-- | crates/chat-crypto/browser/wasi.ts | 27 |
2 files changed, 19 insertions, 14 deletions
diff --git a/crates/chat-crypto/browser/protocol.d.ts b/crates/chat-crypto/browser/protocol.d.ts index 6803c4c..30b5bf8 100644 --- a/crates/chat-crypto/browser/protocol.d.ts +++ b/crates/chat-crypto/browser/protocol.d.ts @@ -49,9 +49,9 @@ declare namespace StackChat { interface WorkerRequest { bytes: ArrayBuffer; request: CryptoRequest } interface Execution { output: string; randomCalls: number; randomBytes: number } interface Wasi { - runCommand(bytes: ArrayBuffer, request: CryptoRequest): Promise<unknown>; - runProbe(bytes: ArrayBuffer): Promise<Execution>; + readonly runCommand: (bytes: ArrayBuffer, request: CryptoRequest) => Promise<unknown>; + readonly runProbe: (bytes: ArrayBuffer) => Promise<Execution>; } } -declare var stackChatWasi: Readonly<StackChat.Wasi>; +declare const stackChatWasi: Readonly<StackChat.Wasi>; diff --git a/crates/chat-crypto/browser/wasi.ts b/crates/chat-crypto/browser/wasi.ts index a037519..f2d9290 100644 --- a/crates/chat-crypto/browser/wasi.ts +++ b/crates/chat-crypto/browser/wasi.ts @@ -15,8 +15,12 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 +function isStart(value: WebAssembly.ExportValue | undefined): value is () => unknown { + return typeof value === 'function' && value.length === 0; +} + async function execute(bytes: ArrayBuffer, input: string): Promise<StackChat.Execution> { - let memory: WebAssembly.Memory | undefined; + const instanceState: { memory: WebAssembly.Memory | undefined } = { memory: undefined }; let output = ''; let errors = ''; const stdoutDecoder = new TextDecoder('utf-8', { fatal: true }); @@ -26,15 +30,16 @@ async function execute(bytes: ArrayBuffer, input: string): Promise<StackChat.Exe let randomCalls = 0; let randomBytes = 0; const memoryBuffer = (): ArrayBuffer => { - if (!memory) throw new Error('WASI memory is unavailable'); + const memory = instanceState.memory; + if (memory === undefined) throw new Error('WASI memory is unavailable'); return memory.buffer; }; const data = (): DataView => new DataView(memoryBuffer()); - const put32 = (address: number, value: number) => data().setUint32(address, value, true); - const noEntries = (count: number, size: number) => { put32(count, 0); put32(size, 0); return 0; }; + const put32 = (address: number, value: number): void => { data().setUint32(address, value, true); }; + const noEntries = (count: number, size: number): number => { put32(count, 0); put32(size, 0); return 0; }; class Exit extends Error { readonly code: number; - constructor(code: number) { super(`WASI exit ${code}`); this.code = code; } + constructor(code: number) { super(`WASI exit ${String(code)}`); this.code = code; } } const wasi: Record<string, WebAssembly.ImportValue> = { args_sizes_get: noEntries, @@ -108,7 +113,7 @@ async function execute(bytes: ArrayBuffer, input: string): Promise<StackChat.Exe 'path_filestat_get', 'path_filestat_set_times', 'path_readlink', 'path_unlink_file', 'path_rename', 'path_create_directory', 'path_remove_directory', 'path_link', 'path_symlink', 'sock_accept', 'sock_recv', 'sock_send', 'sock_shutdown', 'poll_oneoff', - ]) wasi[name] = () => 76; + ]) wasi[name] = (): number => 76; const module = await WebAssembly.compile(bytes); for (const entry of WebAssembly.Module.imports(module)) { if (entry.module !== 'wasi_snapshot_preview1' || typeof wasi[entry.name] !== 'function') { @@ -118,17 +123,17 @@ async function execute(bytes: ArrayBuffer, input: string): Promise<StackChat.Exe const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: wasi }); const exportedMemory = instance.exports['memory']; const start = instance.exports['_start']; - if (!(exportedMemory instanceof WebAssembly.Memory) || typeof start !== 'function') throw new Error('Invalid WASI exports'); - memory = exportedMemory; + if (!(exportedMemory instanceof WebAssembly.Memory) || !isStart(start)) throw new Error('Invalid WASI exports'); + instanceState.memory = exportedMemory; try { start(); } - catch (error) { if (!(error instanceof Exit) || error.code !== 0) throw new Error(`${error instanceof Error ? error.message : 'WASI execution failed'}: ${errors}`); } + catch (error) { if (!(error instanceof Exit) || error.code !== 0) throw new Error(`${error instanceof Error ? error.message : 'WASI execution failed'}: ${errors}`, { cause: error }); } output += stdoutDecoder.decode(); errors += stderrDecoder.decode(); stdin.fill(0); return { output, randomCalls, randomBytes }; } -async function runProbe(bytes: ArrayBuffer) { +async function runProbe(bytes: ArrayBuffer): Promise<StackChat.Execution> { const result = await execute(bytes, ''); if (!result.output.startsWith('PASS: backend=OpenSSL ') || result.randomCalls === 0) { throw new Error('OpenSSL probe did not pass'); @@ -146,6 +151,6 @@ async function runCommand(bytes: ArrayBuffer, request: StackChat.CryptoRequest): return response; } -globalThis.stackChatWasi = Object.freeze({ runCommand, runProbe }); +Object.defineProperty(globalThis, 'stackChatWasi', { value: Object.freeze({ runCommand, runProbe }), writable: false, configurable: false }); // @license-end |
