@bytecodealliance/preview2-shim
Advanced tools
@@ -0,1 +1,4 @@ | ||
| import { poll } from './io.js'; | ||
| const { Pollable } = poll; | ||
| export const monotonicClock = { | ||
@@ -13,11 +16,12 @@ resolution() { | ||
| instant = BigInt(instant); | ||
| const now = this.now(); | ||
| const now = monotonicClock.now(); | ||
| if (instant <= now) { | ||
| return this.subscribeDuration(0); | ||
| return new Pollable(new Promise(resolve => setTimeout(resolve, 0))); | ||
| } | ||
| return this.subscribeDuration(instant - now); | ||
| return monotonicClock.subscribeDuration(instant - now); | ||
| }, | ||
| subscribeDuration(_duration) { | ||
| _duration = BigInt(_duration); | ||
| console.log(`[monotonic-clock] subscribe`); | ||
| subscribeDuration(duration) { | ||
| duration = BigInt(duration); | ||
| const ms = duration <= 0n ? 0 : Number(duration / 1_000_000n); | ||
| return new Pollable(new Promise(resolve => setTimeout(resolve, ms))); | ||
| }, | ||
@@ -24,0 +28,0 @@ }; |
+677
-140
@@ -1,45 +0,676 @@ | ||
| /** | ||
| * @param {import("../../types/interfaces/wasi-http-types").Request} req | ||
| * @returns {string} | ||
| */ | ||
| export function send(req) { | ||
| console.log(`[http] Send (browser) ${req.uri}`); | ||
| try { | ||
| const xhr = new XMLHttpRequest(); | ||
| xhr.open(req.method.toString(), req.uri, false); | ||
| const requestHeaders = new Headers(req.headers); | ||
| for (let [name, value] of requestHeaders.entries()) { | ||
| if (name !== 'user-agent' && name !== 'host') { | ||
| xhr.setRequestHeader(name, value); | ||
| import { streams, poll } from './io.js'; | ||
| const { InputStream, OutputStream } = streams; | ||
| const { Pollable } = poll; | ||
| const symbolDispose = Symbol.dispose || Symbol.for('dispose'); | ||
| const utf8Decoder = new TextDecoder(); | ||
| const forbiddenHeaders = new Set(['connection', 'keep-alive', 'host']); | ||
| const DEFAULT_HTTP_TIMEOUT_NS = 600_000_000_000n; | ||
| // RFC 9110 compliant header validation | ||
| const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; | ||
| const FIELD_VALUE_RE = /^[\t\x20-\x7E\x80-\xFF]*$/; | ||
| function validateHeaderName(name) { | ||
| if (!TOKEN_RE.test(name)) { | ||
| throw { tag: 'invalid-syntax' }; | ||
| } | ||
| } | ||
| function validateHeaderValue(value) { | ||
| const str = typeof value === 'string' ? value : utf8Decoder.decode(value); | ||
| if (!FIELD_VALUE_RE.test(str)) { | ||
| throw { tag: 'invalid-syntax' }; | ||
| } | ||
| } | ||
| class Fields { | ||
| #immutable = false; | ||
| /** @type {[string, Uint8Array][]} */ #entries = []; | ||
| /** @type {Map<string, [string, Uint8Array][]>} */ #table = new Map(); | ||
| static fromList(entries) { | ||
| const fields = new Fields(); | ||
| for (const [key, value] of entries) { | ||
| fields.append(key, value); | ||
| } | ||
| return fields; | ||
| } | ||
| get(name) { | ||
| const tableEntries = this.#table.get(name.toLowerCase()); | ||
| if (!tableEntries) { | ||
| return []; | ||
| } | ||
| return tableEntries.map(([, v]) => v); | ||
| } | ||
| set(name, values) { | ||
| if (this.#immutable) { | ||
| throw { tag: 'immutable' }; | ||
| } | ||
| validateHeaderName(name); | ||
| for (const value of values) { | ||
| validateHeaderValue(value); | ||
| } | ||
| const lowercased = name.toLowerCase(); | ||
| if (forbiddenHeaders.has(lowercased)) { | ||
| throw { tag: 'forbidden' }; | ||
| } | ||
| const tableEntries = this.#table.get(lowercased); | ||
| if (tableEntries) { | ||
| this.#entries = this.#entries.filter( | ||
| (entry) => !tableEntries.includes(entry) | ||
| ); | ||
| tableEntries.splice(0, tableEntries.length); | ||
| } else { | ||
| this.#table.set(lowercased, []); | ||
| } | ||
| const newTableEntries = this.#table.get(lowercased); | ||
| for (const value of values) { | ||
| const entry = [name, value]; | ||
| this.#entries.push(entry); | ||
| newTableEntries.push(entry); | ||
| } | ||
| } | ||
| has(name) { | ||
| return this.#table.has(name.toLowerCase()); | ||
| } | ||
| delete(name) { | ||
| if (this.#immutable) { | ||
| throw { tag: 'immutable' }; | ||
| } | ||
| const lowercased = name.toLowerCase(); | ||
| const tableEntries = this.#table.get(lowercased); | ||
| if (tableEntries) { | ||
| this.#entries = this.#entries.filter( | ||
| (entry) => !tableEntries.includes(entry) | ||
| ); | ||
| this.#table.delete(lowercased); | ||
| } | ||
| } | ||
| append(name, value) { | ||
| if (this.#immutable) { | ||
| throw { tag: 'immutable' }; | ||
| } | ||
| validateHeaderName(name); | ||
| validateHeaderValue(value); | ||
| const lowercased = name.toLowerCase(); | ||
| if (forbiddenHeaders.has(lowercased)) { | ||
| throw { tag: 'forbidden' }; | ||
| } | ||
| const entry = [name, value]; | ||
| this.#entries.push(entry); | ||
| const tableEntries = this.#table.get(lowercased); | ||
| if (tableEntries) { | ||
| tableEntries.push(entry); | ||
| } else { | ||
| this.#table.set(lowercased, [entry]); | ||
| } | ||
| } | ||
| entries() { | ||
| return this.#entries; | ||
| } | ||
| clone() { | ||
| return fieldsFromEntriesChecked(this.#entries); | ||
| } | ||
| static _lock(fields) { | ||
| fields.#immutable = true; | ||
| return fields; | ||
| } | ||
| static _fromEntriesChecked(entries) { | ||
| const fields = new Fields(); | ||
| fields.#entries = entries; | ||
| for (const entry of entries) { | ||
| const lowercase = entry[0].toLowerCase(); | ||
| const existing = fields.#table.get(lowercase); | ||
| if (existing) { | ||
| existing.push(entry); | ||
| } else { | ||
| fields.#table.set(lowercase, [entry]); | ||
| } | ||
| } | ||
| xhr.send(req.body && req.body.length > 0 ? req.body : null); | ||
| const body = xhr.response | ||
| ? new TextEncoder().encode(xhr.response) | ||
| : undefined; | ||
| const headers = []; | ||
| xhr.getAllResponseHeaders() | ||
| .trim() | ||
| .split(/[\r\n]+/) | ||
| .forEach((line) => { | ||
| var parts = line.split(': '); | ||
| var key = parts.shift(); | ||
| var value = parts.join(': '); | ||
| headers.push([key, value]); | ||
| }); | ||
| return fields; | ||
| } | ||
| } | ||
| const fieldsLock = Fields._lock; | ||
| delete Fields._lock; | ||
| const fieldsFromEntriesChecked = Fields._fromEntriesChecked; | ||
| delete Fields._fromEntriesChecked; | ||
| class RequestOptions { | ||
| #connectTimeout = DEFAULT_HTTP_TIMEOUT_NS; | ||
| #firstByteTimeout = DEFAULT_HTTP_TIMEOUT_NS; | ||
| #betweenBytesTimeout = DEFAULT_HTTP_TIMEOUT_NS; | ||
| connectTimeout() { | ||
| return this.#connectTimeout; | ||
| } | ||
| setConnectTimeout(duration) { | ||
| if (duration < 0n) { | ||
| throw new Error('duration must not be negative'); | ||
| } | ||
| this.#connectTimeout = duration; | ||
| } | ||
| firstByteTimeout() { | ||
| return this.#firstByteTimeout; | ||
| } | ||
| setFirstByteTimeout(duration) { | ||
| if (duration < 0n) { | ||
| throw new Error('duration must not be negative'); | ||
| } | ||
| this.#firstByteTimeout = duration; | ||
| } | ||
| betweenBytesTimeout() { | ||
| return this.#betweenBytesTimeout; | ||
| } | ||
| setBetweenBytesTimeout(duration) { | ||
| if (duration < 0n) { | ||
| throw new Error('duration must not be negative'); | ||
| } | ||
| this.#betweenBytesTimeout = duration; | ||
| } | ||
| } | ||
| class OutgoingBody { | ||
| #outputStream = null; | ||
| #chunks = []; | ||
| #finished = false; | ||
| write() { | ||
| const outputStream = this.#outputStream; | ||
| if (outputStream === null) { | ||
| throw undefined; | ||
| } | ||
| this.#outputStream = null; | ||
| return outputStream; | ||
| } | ||
| static finish(body, trailers) { | ||
| if (trailers) { | ||
| throw { tag: 'internal-error', val: 'trailers unsupported' }; | ||
| } | ||
| if (body.#finished) { | ||
| throw { tag: 'internal-error', val: 'body already finished' }; | ||
| } | ||
| body.#finished = true; | ||
| } | ||
| static _bodyData(outgoingBody) { | ||
| if (outgoingBody.#chunks.length === 0) { | ||
| return null; | ||
| } | ||
| let totalLen = 0; | ||
| for (const chunk of outgoingBody.#chunks) { | ||
| totalLen += chunk.byteLength; | ||
| } | ||
| const result = new Uint8Array(totalLen); | ||
| let offset = 0; | ||
| for (const chunk of outgoingBody.#chunks) { | ||
| result.set(chunk, offset); | ||
| offset += chunk.byteLength; | ||
| } | ||
| return result; | ||
| } | ||
| static _create() { | ||
| const outgoingBody = new OutgoingBody(); | ||
| const chunks = outgoingBody.#chunks; | ||
| outgoingBody.#outputStream = new OutputStream({ | ||
| write(buf) { | ||
| chunks.push(new Uint8Array(buf)); | ||
| }, | ||
| flush() {}, | ||
| blockingFlush() {}, | ||
| subscribe() { | ||
| return new Pollable(); | ||
| }, | ||
| }); | ||
| return outgoingBody; | ||
| } | ||
| [symbolDispose]() {} | ||
| } | ||
| const outgoingBodyCreate = OutgoingBody._create; | ||
| delete OutgoingBody._create; | ||
| const outgoingBodyData = OutgoingBody._bodyData; | ||
| delete OutgoingBody._bodyData; | ||
| class OutgoingRequest { | ||
| /** @type {{ tag: string, val?: string }} */ #method = { tag: 'get' }; | ||
| /** @type {{ tag: string, val?: string } | undefined} */ #scheme = undefined; | ||
| /** @type {string | undefined} */ #pathWithQuery = undefined; | ||
| /** @type {string | undefined} */ #authority = undefined; | ||
| /** @type {Fields} */ #headers; | ||
| /** @type {OutgoingBody} */ #body; | ||
| #bodyRequested = false; | ||
| constructor(headers) { | ||
| fieldsLock(headers); | ||
| this.#headers = headers; | ||
| this.#body = outgoingBodyCreate(); | ||
| } | ||
| body() { | ||
| if (this.#bodyRequested) { | ||
| throw new Error('Body already requested'); | ||
| } | ||
| this.#bodyRequested = true; | ||
| return this.#body; | ||
| } | ||
| method() { | ||
| return this.#method; | ||
| } | ||
| setMethod(method) { | ||
| if (method.tag === 'other' && !method.val.match(/^[a-zA-Z-]+$/)) { | ||
| throw undefined; | ||
| } | ||
| this.#method = method; | ||
| } | ||
| pathWithQuery() { | ||
| return this.#pathWithQuery; | ||
| } | ||
| setPathWithQuery(pathWithQuery) { | ||
| if ( | ||
| pathWithQuery && | ||
| !pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/) | ||
| ) { | ||
| throw undefined; | ||
| } | ||
| this.#pathWithQuery = pathWithQuery; | ||
| } | ||
| scheme() { | ||
| return this.#scheme; | ||
| } | ||
| setScheme(scheme) { | ||
| if (scheme?.tag === 'other' && !scheme.val.match(/^[a-zA-Z]+$/)) { | ||
| throw undefined; | ||
| } | ||
| this.#scheme = scheme; | ||
| } | ||
| authority() { | ||
| return this.#authority; | ||
| } | ||
| setAuthority(authority) { | ||
| if (authority) { | ||
| const [host, port, ...extra] = authority.split(':'); | ||
| const portNum = Number(port); | ||
| if ( | ||
| extra.length || | ||
| (port !== undefined && | ||
| (portNum.toString() !== port || portNum > 65535)) || | ||
| !host.match(/^[a-zA-Z0-9-.]+$/) | ||
| ) { | ||
| throw undefined; | ||
| } | ||
| } | ||
| this.#authority = authority; | ||
| } | ||
| headers() { | ||
| return this.#headers; | ||
| } | ||
| [symbolDispose]() {} | ||
| static _handle(request, options) { | ||
| const scheme = schemeString(request.#scheme); | ||
| const method = request.#method.val || request.#method.tag; | ||
| if (!request.#pathWithQuery) { | ||
| throw { tag: 'HTTP-request-URI-invalid' }; | ||
| } | ||
| const url = `${scheme}//${request.#authority || ''}${request.#pathWithQuery}`; | ||
| const headers = new Headers(); | ||
| for (const [key, value] of request.#headers.entries()) { | ||
| const lowerKey = key.toLowerCase(); | ||
| if (!forbiddenHeaders.has(lowerKey)) { | ||
| headers.set(key, utf8Decoder.decode(value)); | ||
| } | ||
| } | ||
| const bodyData = outgoingBodyData(request.#body); | ||
| let timeoutMs = Number(DEFAULT_HTTP_TIMEOUT_NS / 1_000_000n); | ||
| if (options) { | ||
| const ct = options.connectTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS; | ||
| const fbt = options.firstByteTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS; | ||
| const minTimeout = ct < fbt ? ct : fbt; | ||
| timeoutMs = Number(minTimeout / 1_000_000n); | ||
| } | ||
| return futureIncomingResponseCreate( | ||
| url, method.toUpperCase(), headers, bodyData, timeoutMs | ||
| ); | ||
| } | ||
| } | ||
| const outgoingRequestHandle = OutgoingRequest._handle; | ||
| delete OutgoingRequest._handle; | ||
| class IncomingBody { | ||
| #finished = false; | ||
| #stream = undefined; | ||
| stream() { | ||
| if (!this.#stream) { | ||
| throw undefined; | ||
| } | ||
| const stream = this.#stream; | ||
| this.#stream = null; | ||
| return stream; | ||
| } | ||
| static finish(incomingBody) { | ||
| if (incomingBody.#finished) { | ||
| throw new Error('incoming body already finished'); | ||
| } | ||
| incomingBody.#finished = true; | ||
| return futureTrailersCreate(); | ||
| } | ||
| [symbolDispose]() {} | ||
| static _create(fetchResponse) { | ||
| const incomingBody = new IncomingBody(); | ||
| let buffer = null; | ||
| let bufferOffset = 0; | ||
| let done = false; | ||
| let reader = null; | ||
| let readPromise = null; | ||
| function ensureReader() { | ||
| if (!reader && fetchResponse.body) { | ||
| reader = fetchResponse.body.getReader(); | ||
| } | ||
| } | ||
| function startRead() { | ||
| if (readPromise || done) { return; } | ||
| ensureReader(); | ||
| if (!reader) { | ||
| done = true; | ||
| return; | ||
| } | ||
| readPromise = reader.read().then( | ||
| (result) => { | ||
| readPromise = null; | ||
| if (result.done) { | ||
| done = true; | ||
| } else { | ||
| buffer = result.value; | ||
| bufferOffset = 0; | ||
| } | ||
| }, | ||
| () => { | ||
| readPromise = null; | ||
| done = true; | ||
| } | ||
| ); | ||
| } | ||
| incomingBody.#stream = new InputStream({ | ||
| read(len) { | ||
| if (done && (buffer === null || bufferOffset >= buffer.byteLength)) { | ||
| throw { tag: 'closed' }; | ||
| } | ||
| if (buffer !== null && bufferOffset < buffer.byteLength) { | ||
| const available = buffer.byteLength - bufferOffset; | ||
| const toRead = Math.min(Number(len), available); | ||
| const slice = buffer.slice(bufferOffset, bufferOffset + toRead); | ||
| bufferOffset += toRead; | ||
| if (bufferOffset >= buffer.byteLength) { | ||
| buffer = null; | ||
| bufferOffset = 0; | ||
| if (!done) { startRead(); } | ||
| } | ||
| return slice; | ||
| } | ||
| throw { tag: 'would-block' }; | ||
| }, | ||
| blockingRead(len) { | ||
| if (done && (buffer === null || bufferOffset >= buffer.byteLength)) { | ||
| throw { tag: 'closed' }; | ||
| } | ||
| if (buffer !== null && bufferOffset < buffer.byteLength) { | ||
| const available = buffer.byteLength - bufferOffset; | ||
| const toRead = Math.min(Number(len), available); | ||
| const slice = buffer.slice(bufferOffset, bufferOffset + toRead); | ||
| bufferOffset += toRead; | ||
| if (bufferOffset >= buffer.byteLength) { | ||
| buffer = null; | ||
| bufferOffset = 0; | ||
| if (!done) { startRead(); } | ||
| } | ||
| return slice; | ||
| } | ||
| startRead(); | ||
| const waitFor = readPromise || Promise.resolve(); | ||
| return waitFor.then(() => { | ||
| if (done && (buffer === null || bufferOffset >= buffer.byteLength)) { | ||
| throw { tag: 'closed' }; | ||
| } | ||
| if (buffer !== null && bufferOffset < buffer.byteLength) { | ||
| const available = buffer.byteLength - bufferOffset; | ||
| const toRead = Math.min(Number(len), available); | ||
| const slice = buffer.slice(bufferOffset, bufferOffset + toRead); | ||
| bufferOffset += toRead; | ||
| if (bufferOffset >= buffer.byteLength) { | ||
| buffer = null; | ||
| bufferOffset = 0; | ||
| if (!done) { startRead(); } | ||
| } | ||
| return slice; | ||
| } | ||
| throw { tag: 'closed' }; | ||
| }); | ||
| }, | ||
| subscribe() { | ||
| if (done || (buffer !== null && bufferOffset < buffer.byteLength)) { | ||
| return new Pollable(); | ||
| } | ||
| startRead(); | ||
| if (readPromise) { | ||
| return new Pollable(readPromise); | ||
| } | ||
| return new Pollable(); | ||
| }, | ||
| }); | ||
| startRead(); | ||
| return incomingBody; | ||
| } | ||
| } | ||
| const incomingBodyCreate = IncomingBody._create; | ||
| delete IncomingBody._create; | ||
| class IncomingResponse { | ||
| /** @type {Fields} */ #headers = undefined; | ||
| #status = 0; | ||
| /** @type {IncomingBody} */ #body; | ||
| status() { | ||
| return this.#status; | ||
| } | ||
| headers() { | ||
| return this.#headers; | ||
| } | ||
| consume() { | ||
| if (this.#body === undefined) { | ||
| throw undefined; | ||
| } | ||
| const body = this.#body; | ||
| this.#body = undefined; | ||
| return body; | ||
| } | ||
| [symbolDispose]() {} | ||
| static _create(fetchResponse) { | ||
| const res = new IncomingResponse(); | ||
| res.#status = fetchResponse.status; | ||
| const headerEntries = []; | ||
| const encoder = new TextEncoder(); | ||
| fetchResponse.headers.forEach((value, key) => { | ||
| headerEntries.push([key, encoder.encode(value)]); | ||
| }); | ||
| res.#headers = fieldsLock(fieldsFromEntriesChecked(headerEntries)); | ||
| res.#body = incomingBodyCreate(fetchResponse); | ||
| return res; | ||
| } | ||
| } | ||
| const incomingResponseCreate = IncomingResponse._create; | ||
| delete IncomingResponse._create; | ||
| class FutureTrailers { | ||
| #requested = false; | ||
| subscribe() { | ||
| return new Pollable(); | ||
| } | ||
| get() { | ||
| if (this.#requested) { | ||
| return { tag: 'err' }; | ||
| } | ||
| this.#requested = true; | ||
| return { | ||
| status: xhr.status, | ||
| tag: 'ok', | ||
| val: { | ||
| tag: 'ok', | ||
| val: undefined, | ||
| }, | ||
| }; | ||
| } | ||
| static _create() { | ||
| return new FutureTrailers(); | ||
| } | ||
| } | ||
| const futureTrailersCreate = FutureTrailers._create; | ||
| delete FutureTrailers._create; | ||
| function mapFetchError(err) { | ||
| if (err.name === 'AbortError') { | ||
| return { tag: 'connection-timeout' }; | ||
| } | ||
| if (err.name === 'TypeError') { | ||
| return { tag: 'connection-refused' }; | ||
| } | ||
| return { tag: 'internal-error', val: err.message }; | ||
| } | ||
| class FutureIncomingResponse { | ||
| #result = undefined; | ||
| #promise = null; | ||
| subscribe() { | ||
| return new Pollable(this.#promise); | ||
| } | ||
| get() { | ||
| if (this.#result === undefined) { | ||
| return undefined; | ||
| } | ||
| const result = this.#result; | ||
| this.#result = { tag: 'err' }; | ||
| return result; | ||
| } | ||
| [symbolDispose]() { | ||
| this.#promise = null; | ||
| } | ||
| static _create(url, method, headers, bodyData, timeoutMs) { | ||
| const future = new FutureIncomingResponse(); | ||
| const controller = new AbortController(); | ||
| let timer; | ||
| if (timeoutMs < Infinity) { | ||
| timer = setTimeout(() => controller.abort(), timeoutMs); | ||
| } | ||
| const init = { | ||
| method, | ||
| headers, | ||
| body, | ||
| signal: controller.signal, | ||
| }; | ||
| } catch (err) { | ||
| throw new Error(err.message); | ||
| if (bodyData && method !== 'GET' && method !== 'HEAD') { | ||
| init.body = bodyData; | ||
| } | ||
| future.#promise = fetch(url, init).then( | ||
| (response) => { | ||
| if (timer) { clearTimeout(timer); } | ||
| future.#result = { | ||
| tag: 'ok', | ||
| val: { | ||
| tag: 'ok', | ||
| val: incomingResponseCreate(response), | ||
| }, | ||
| }; | ||
| }, | ||
| (err) => { | ||
| if (timer) { clearTimeout(timer); } | ||
| future.#result = { | ||
| tag: 'ok', | ||
| val: { | ||
| tag: 'err', | ||
| val: mapFetchError(err), | ||
| }, | ||
| }; | ||
| } | ||
| ); | ||
| return future; | ||
| } | ||
| } | ||
| const futureIncomingResponseCreate = FutureIncomingResponse._create; | ||
| delete FutureIncomingResponse._create; | ||
| export const incomingHandler = { | ||
| handle() {}, | ||
| function schemeString(scheme) { | ||
| if (!scheme) { | ||
| return 'https:'; | ||
| } | ||
| switch (scheme.tag) { | ||
| case 'HTTP': | ||
| return 'http:'; | ||
| case 'HTTPS': | ||
| return 'https:'; | ||
| case 'other': | ||
| return scheme.val.toLowerCase() + ':'; | ||
| } | ||
| } | ||
| function httpErrorCode(err) { | ||
| if (err.payload) { | ||
| return err.payload; | ||
| } | ||
| return { | ||
| tag: 'internal-error', | ||
| val: err.message, | ||
| }; | ||
| } | ||
| export const outgoingHandler = { | ||
| handle: outgoingRequestHandle, | ||
| }; | ||
| export const outgoingHandler = { | ||
| export const incomingHandler = { | ||
| handle() {}, | ||
@@ -49,108 +680,14 @@ }; | ||
| export const types = { | ||
| dropFields(_fields) { | ||
| console.log('[types] Drop fields'); | ||
| }, | ||
| newFields(_entries) { | ||
| console.log('[types] New fields'); | ||
| }, | ||
| fieldsGet(_fields, _name) { | ||
| console.log('[types] Fields get'); | ||
| }, | ||
| fieldsSet(_fields, _name, _value) { | ||
| console.log('[types] Fields set'); | ||
| }, | ||
| fieldsDelete(_fields, _name) { | ||
| console.log('[types] Fields delete'); | ||
| }, | ||
| fieldsAppend(_fields, _name, _value) { | ||
| console.log('[types] Fields append'); | ||
| }, | ||
| fieldsEntries(_fields) { | ||
| console.log('[types] Fields entries'); | ||
| }, | ||
| fieldsClone(_fields) { | ||
| console.log('[types] Fields clone'); | ||
| }, | ||
| finishIncomingStream(s) { | ||
| console.log(`[types] Finish incoming stream ${s}`); | ||
| }, | ||
| finishOutgoingStream(s, _trailers) { | ||
| console.log(`[types] Finish outgoing stream ${s}`); | ||
| }, | ||
| dropIncomingRequest(_req) { | ||
| console.log('[types] Drop incoming request'); | ||
| }, | ||
| dropOutgoingRequest(_req) { | ||
| console.log('[types] Drop outgoing request'); | ||
| }, | ||
| incomingRequestMethod(_req) { | ||
| console.log('[types] Incoming request method'); | ||
| }, | ||
| incomingRequestPathWithQuery(_req) { | ||
| console.log('[types] Incoming request path with query'); | ||
| }, | ||
| incomingRequestScheme(_req) { | ||
| console.log('[types] Incoming request scheme'); | ||
| }, | ||
| incomingRequestAuthority(_req) { | ||
| console.log('[types] Incoming request authority'); | ||
| }, | ||
| incomingRequestHeaders(_req) { | ||
| console.log('[types] Incoming request headers'); | ||
| }, | ||
| incomingRequestConsume(_req) { | ||
| console.log('[types] Incoming request consume'); | ||
| }, | ||
| newOutgoingRequest(_method, _pathWithQuery, _scheme, _authority, _headers) { | ||
| console.log('[types] New outgoing request'); | ||
| }, | ||
| outgoingRequestWrite(_req) { | ||
| console.log('[types] Outgoing request write'); | ||
| }, | ||
| dropResponseOutparam(_res) { | ||
| console.log('[types] Drop response outparam'); | ||
| }, | ||
| setResponseOutparam(_response) { | ||
| console.log('[types] Drop fields'); | ||
| }, | ||
| dropIncomingResponse(_res) { | ||
| console.log('[types] Drop incoming response'); | ||
| }, | ||
| dropOutgoingResponse(_res) { | ||
| console.log('[types] Drop outgoing response'); | ||
| }, | ||
| incomingResponseStatus(_res) { | ||
| console.log('[types] Incoming response status'); | ||
| }, | ||
| incomingResponseHeaders(_res) { | ||
| console.log('[types] Incoming response headers'); | ||
| }, | ||
| incomingResponseConsume(_res) { | ||
| console.log('[types] Incoming response consume'); | ||
| }, | ||
| newOutgoingResponse(_statusCode, _headers) { | ||
| console.log('[types] New outgoing response'); | ||
| }, | ||
| outgoingResponseWrite(_res) { | ||
| console.log('[types] Outgoing response write'); | ||
| }, | ||
| dropFutureIncomingResponse(_f) { | ||
| console.log('[types] Drop future incoming response'); | ||
| }, | ||
| futureIncomingResponseGet(_f) { | ||
| console.log('[types] Future incoming response get'); | ||
| }, | ||
| listenToFutureIncomingResponse(_f) { | ||
| console.log('[types] Listen to future incoming response'); | ||
| }, | ||
| Fields: class Fields {}, | ||
| FutureIncomingResponse: new class FutureIncomingResponse {}, | ||
| IncomingBody: new class IncomingBody {}, | ||
| IncomingRequest: new class IncomingRequest {}, | ||
| IncomingResponse: new class IncomingResponse {}, | ||
| OutgoingBody: new class OutgoingBody {}, | ||
| OutgoingRequest: new class OutgoingRequest {}, | ||
| OutgoingResponse: new class OutgoingResponse {}, | ||
| RequestOptions: new class RequestOptions {}, | ||
| ResponseOutparam: new class ResponseOutparam {}, | ||
| Fields, | ||
| FutureIncomingResponse, | ||
| FutureTrailers, | ||
| IncomingBody, | ||
| IncomingRequest: class IncomingRequest {}, | ||
| IncomingResponse, | ||
| OutgoingBody, | ||
| OutgoingRequest, | ||
| OutgoingResponse: class OutgoingResponse {}, | ||
| ResponseOutparam: class ResponseOutparam {}, | ||
| RequestOptions, | ||
| httpErrorCode, | ||
| }; |
+68
-8
@@ -80,3 +80,5 @@ let id = 0; | ||
| subscribe() { | ||
| console.log(`[streams] Subscribe to input stream ${this.id}`); | ||
| if (this.handler.subscribe) { | ||
| return this.handler.subscribe(); | ||
| } | ||
| return new Pollable(); | ||
@@ -172,3 +174,5 @@ } | ||
| subscribe() { | ||
| console.log(`[streams] Subscribe to output stream ${this.id}`); | ||
| if (this.handler.subscribe) { | ||
| return this.handler.subscribe(); | ||
| } | ||
| return new Pollable(); | ||
@@ -183,12 +187,68 @@ } | ||
| class Pollable {} | ||
| class Pollable { | ||
| #ready = false; | ||
| #promise = null; | ||
| function pollList(_list) { | ||
| // TODO | ||
| constructor(promise) { | ||
| if (!promise) { | ||
| this.#ready = true; | ||
| } else { | ||
| this.#promise = promise.then( | ||
| () => { this.#ready = true; }, | ||
| () => { this.#ready = true; } | ||
| ); | ||
| } | ||
| } | ||
| ready() { | ||
| return this.#ready; | ||
| } | ||
| block() { | ||
| if (this.#ready) { | ||
| return Promise.resolve(); | ||
| } | ||
| return this.#promise; | ||
| } | ||
| [symbolDispose]() { | ||
| this.#promise = null; | ||
| } | ||
| } | ||
| function pollOne(_poll) { | ||
| // TODO | ||
| function pollList(list) { | ||
| if (list.length === 0) { | ||
| throw new Error('poll list must not be empty'); | ||
| } | ||
| if (list.length > 0xFFFFFFFF) { | ||
| throw new Error('poll list length exceeds u32 index range'); | ||
| } | ||
| const ready = []; | ||
| for (let i = 0; i < list.length; i++) { | ||
| if (list[i].ready()) { | ||
| ready.push(i); | ||
| } | ||
| } | ||
| if (ready.length > 0) { | ||
| return new Uint32Array(ready); | ||
| } | ||
| // None ready synchronously. Wait for the first to resolve via Promise.race, | ||
| // then sweep for any others that became ready concurrently. | ||
| return Promise.race( | ||
| list.map((p, i) => p.block().then(() => { | ||
| const result = [i]; | ||
| for (let j = 0; j < list.length; j++) { | ||
| if (j !== i && list[j].ready()) { | ||
| result.push(j); | ||
| } | ||
| } | ||
| return new Uint32Array(result); | ||
| })) | ||
| ); | ||
| } | ||
| function pollOne(poll) { | ||
| return poll.block(); | ||
| } | ||
| export const poll = { | ||
@@ -198,3 +258,3 @@ Pollable, | ||
| pollOne, | ||
| poll: pollOne, | ||
| poll: pollList, | ||
| }; |
+1
-1
| { | ||
| "name": "@bytecodealliance/preview2-shim", | ||
| "version": "0.17.7", | ||
| "version": "0.17.8", | ||
| "description": "WASI Preview2 shim for JS environments", | ||
@@ -5,0 +5,0 @@ "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>", |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
378673
4.53%10745
5.13%33
10%3
50%