🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

env-runner

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

env-runner - npm Package Compare versions

Comparing version
0.1.15
to
0.1.16
+1
-0
dist/_chunks/common-base-runner.d.mts

@@ -53,2 +53,3 @@ import { EnvRunner, RunnerMessageListener, WorkerAddress, WorkerHooks } from "./types.mjs";

get ready(): boolean;
get address(): WorkerAddress | undefined;
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;

@@ -55,0 +56,0 @@ upgrade(context: {

@@ -26,2 +26,5 @@ import { resolveVirtualModules } from "./virtual-loader.mjs";

}
get address() {
return this._address;
}
async fetch(input, init) {

@@ -33,3 +36,7 @@ for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));

async upgrade(context) {
if (!this.ready || !this._address) return;
if (!this.ready) await this.waitForReady().catch(() => {});
if (!this.ready || !this._address) {
context.node.socket.destroy();
return;
}
try {

@@ -45,3 +52,3 @@ await proxyUpgrade(this._address, context.node.req, context.node.socket, context.node.head);

}
waitForReady(timeout = 5e3) {
waitForReady(timeout = 15e3) {
if (this.ready) return Promise.resolve();

@@ -48,0 +55,0 @@ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));

import { watch } from "node:fs";
async function createRunnerWSProxyPlugin(getRunner) {
const isBun = "Bun" in globalThis;
const isDeno = "Deno" in globalThis;
if (!isBun && !isDeno) return (server) => {
server.ready().then(() => {
(server.node?.server)?.on("upgrade", (req, socket, head) => {
getRunner()?.upgrade?.({ node: {
req,
socket,
head
} });
});
}).catch(() => {});
};
const { createWebSocketProxy } = await import("crossws");
const { plugin } = isBun ? await import("crossws/server/bun") : await import("crossws/server/deno");
const proxy = createWebSocketProxy({ target: async (peer) => {
await getRunner()?.waitForReady?.().catch(() => {});
return resolveWSProxyTarget(getRunner()?.address, peer.request.url);
} });
return plugin({ resolve: () => proxy });
}
function resolveWSProxyTarget(address, requestUrl) {
if (!address) throw new Error("env runner worker is not ready");
const { pathname, search } = new URL(requestUrl);
if (address.socketPath) return `ws+unix://${address.socketPath}:${pathname}${search}`;
if (!address.port) throw new Error("env runner worker is not ready");
const host = address.host || "127.0.0.1";
return `ws://${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${address.port}${pathname}${search}`;
}
var RunnerManager = class {

@@ -12,2 +42,3 @@ _runner;

_readyListeners = /* @__PURE__ */ new Set();
_readyRejectors = /* @__PURE__ */ new Set();
constructor(runner) {

@@ -22,2 +53,5 @@ if (runner) this._attach(runner);

}
get address() {
return this._runner?.address;
}
async reload(runner) {

@@ -54,5 +88,13 @@ this._reloading = true;

}
upgrade = (context) => {
this._runner?.upgrade?.(context);
upgrade = async (context) => {
const runner = await this._waitForRunner();
if (!runner?.upgrade) {
context.node.socket.destroy();
return;
}
await runner.upgrade(context);
};
wsSrvxPlugin() {
return createRunnerWSProxyPlugin(() => this);
}
sendMessage(message) {

@@ -73,7 +115,13 @@ if (!this._runner || !this._runner.ready) {

}
waitForReady(timeout = 5e3) {
waitForReady(timeout = 15e3) {
if (this.ready) return Promise.resolve();
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
return new Promise((resolve, reject) => {
const cleanup = () => {
clearTimeout(timer);
this.offMessage(listener);
this._readyRejectors.delete(onClose);
};
const timer = setTimeout(() => {
this._messageListeners.delete(listener);
cleanup();
reject(/* @__PURE__ */ new Error("Runner did not become ready in time"));

@@ -83,8 +131,12 @@ }, timeout);

if (message?.address || this.ready) {
clearTimeout(timer);
this._messageListeners.delete(listener);
cleanup();
resolve();
}
};
this._messageListeners.add(listener);
const onClose = () => {
cleanup();
reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
};
this.onMessage(listener);
this._readyRejectors.add(onClose);
});

@@ -132,2 +184,4 @@ }

this._messageQueue.length = 0;
for (const rejectReady of this._readyRejectors) rejectReady();
this._readyRejectors.clear();
const runner = this._runner;

@@ -134,0 +188,0 @@ this._detach();

@@ -65,2 +65,4 @@ import { IncomingMessage } from "node:http";

readonly closed: boolean;
/** Address the worker is listening at, once ready (`undefined` before then). */
readonly address?: WorkerAddress;
/** Proxy an HTTP request to the worker. */

@@ -67,0 +69,0 @@ fetch: FetchHandler;

+3
-1

@@ -47,3 +47,5 @@ import { NodeWorkerEnvRunner } from "./node-worker-runner.mjs";

function generateVercelId() {
return `dev1::${_podId}-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
const timestamp = Date.now().toString(36);
const random = randomBytes(6).toString("hex");
return `dev1::${_podId}-${timestamp}-${random}`;
}

@@ -50,0 +52,0 @@ var VercelEnvRunner = class extends NodeWorkerEnvRunner {

@@ -53,12 +53,6 @@ import { EnvServer } from "./_chunks/server.mjs";

gracefulShutdown: false,
fetch: (request) => envServer.fetch(request)
fetch: (request) => envServer.fetch(request),
plugins: [await envServer.wsSrvxPlugin()]
});
await server.ready();
server.node?.server?.on("upgrade", (req, socket, head) => {
envServer.upgrade({ node: {
req,
socket,
head
} });
});
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, async () => {

@@ -65,0 +59,0 @@ console.log(`\n\x1B[2mShutting down...\x1B[0m`);

@@ -7,3 +7,3 @@ import { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, WorkerAddress, WorkerHooks } from "./_chunks/types.mjs";

import { NetlifyEnvRunner } from "./_chunks/netlify-runner.mjs";
import { ServerOptions } from "srvx";
import { ServerOptions, ServerPlugin } from "srvx";
import { Hooks } from "crossws";

@@ -24,5 +24,7 @@ /**

private _readyListeners;
private _readyRejectors;
constructor(runner?: EnvRunner);
get ready(): boolean;
get closed(): boolean;
get address(): WorkerAddress | undefined;
/**

@@ -46,2 +48,10 @@ * Replace the active runner with a new one. Closes the previous runner.

upgrade: UpgradeHandler;
/**
* Create a runtime-native WebSocket reverse-proxy plugin for the public srvx
* server. Attach it via `serve({ plugins: [await manager.wsSrvxPlugin()] })`:
* on Node it proxies the raw upgrade socket to the worker, and on Bun/Deno it
* bridges the WebSocket with crossws. The plugin reads the active runner
* lazily, so it keeps working across hot-reloads.
*/
wsSrvxPlugin(): Promise<ServerPlugin>;
sendMessage(message: unknown): void;

@@ -48,0 +58,0 @@ onMessage(listener: RunnerMessageListener): void;

{
"name": "env-runner",
"version": "0.1.15",
"version": "0.1.16",
"description": "Generic environment runner for JavaScript runtimes.",

@@ -46,6 +46,6 @@ "license": "MIT",

"dependencies": {
"crossws": "^0.4.6",
"crossws": "^0.4.8",
"exsolve": "^1.1.0",
"httpxy": "^0.5.3",
"srvx": "^0.11.17"
"httpxy": "^0.5.4",
"srvx": "^0.11.19"
},

@@ -52,0 +52,0 @@ "devDependencies": {

@@ -76,5 +76,32 @@ # env-runner

fetch: (request) => envServer.fetch(request),
// Proxy WebSocket upgrades to the worker (see "WebSocket proxying" below)
plugins: [await envServer.wsSrvxPlugin()],
});
```
#### WebSocket proxying
To proxy WebSocket upgrades to the worker, attach the plugin returned by
`wsSrvxPlugin()` (available on both `RunnerManager` and `EnvServer`) to your
[srvx](https://srvx.h3.dev) server:
```ts
const server = serve({
fetch: (request) => envServer.fetch(request),
plugins: [await envServer.wsSrvxPlugin()],
});
```
The plugin picks the proxy strategy by **host** runtime:
- **Node** — proxies the raw upgrade socket to the worker (transparent
passthrough; subprotocol/extension negotiation stays end-to-end).
- **Bun/Deno** — those runtimes serve natively and expose no Node upgrade
socket, so the client WebSocket is terminated with [crossws](https://crossws.h3.dev)
and bridged to the worker over a standard `WebSocket` client.
It reads the active runner lazily, so it keeps working across hot-reloads, and
waits for the worker to become ready before proxying. Your entry module should
expose WebSocket hooks via the `websocket` field (see [Workers](#workers)).
### Manager (`RunnerManager`)

@@ -151,3 +178,4 @@

// Proxy WebSocket upgrades
// Proxy a raw WebSocket upgrade to the worker (Node host only — low-level;
// prefer `manager.wsSrvxPlugin()` for cross-runtime proxying)
runner.upgrade?.({ node: { req, socket, head } });

@@ -154,0 +182,0 @@