You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@bytecodealliance/preview2-shim

Package Overview
Dependencies
Maintainers
4
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bytecodealliance/preview2-shim - npm Package Compare versions

Comparing version
0.17.7
to
0.17.8
+10
-6
lib/browser/clocks.js

@@ -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 @@ };

@@ -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,
};

@@ -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,
};
{
"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>",