ebus to rest + websocket proxy
High-performance HTTP proxy server for cry-ebus2 client requests, built with uWebSockets.js.
``bash`
npm install
Create a .env file with the following variables:
| Variable | Description | Default |
|----------|-------------|---------|
| HOST | Server bind address | 0.0.0.0 |PORT
| | Server port | 3033 |TIMEOUT
| | Default request timeout in ms | 500 |DEBUG
| | Enable debug logging | false |MAX_BODY_SIZE_MB
| | Maximum request body size in MB | 1 |ALLOWED_ORIGINS
| | Comma-separated list of allowed origins (hostname only, port ignored) | - |API_KEYS
| | Comma-separated list of valid API keys | - |
When ALLOWED_ORIGINS or API_KEYS are configured, requests must satisfy at least one of these conditions:Origin
- Origin check: The header hostname matches one in ALLOWED_ORIGINS?apikey=
- API key: A valid key is provided via:
- Query parameter: or ?api_key=X-API-Key:
- Header: or Authorization: Bearer apikey=
- Cookie: (set via /apikey/ endpoint)
If neither ALLOWED_ORIGINS nor API_KEYS are configured, all requests are allowed and a warning is printed on startup.
#### Cookie Authentication
Set an API key as an HttpOnly cookie by visiting /apikey/. Clear the cookie with /apikey/clear.
Example .env:``
ALLOWED_ORIGINS=example.com, localhost, app.example.com
API_KEYS=secret-key-1, secret-key-2
`bash`
npm start
GET or POST to /
All payloads use msgpack binary format with structuredClone: true, base64-encoded for query parameters.
Parameters:
| Parameter | Description | Required |
|-----------|-------------|----------|
| service | Service name to call | Yes |payload
| | Base64-encoded msgpack payload | No |timeout
| | Request timeout in ms | No |
Response: application/msgpack binary
#### JavaScript Client Example
`js
import { Packr, Unpackr } from "msgpackr";
const packr = new Packr({ structuredClone: true });
const unpackr = new Unpackr({ structuredClone: true });
// Encode payload as base64
const payload = Buffer.from(packr.pack({ key: "value" })).toString("base64");
// GET request
const response = await fetch(http://localhost:3033/?service=my-service&payload=${payload}&apikey=secret-key-1);
const result = unpackr.unpack(new Uint8Array(await response.arrayBuffer()));
// POST request
const postResponse = await fetch("http://localhost:3033/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "secret-key-1"
},
body: JSON.stringify({ service: "my-service", payload })
});
const postResult = unpackr.unpack(new Uint8Array(await postResponse.arrayBuffer()));
`
| Endpoint | Auth | Description |
|----------|------|-------------|
| / | Yes | Returns ebus-proxy when no service specified |/ping
| | No | Returns pong for health checks |/doc
| | No | Returns available endpoints |/apikey/:key
| | No | Sets API key as HttpOnly cookie (1 week) |/apikey/clear
| | No | Clears the API key cookie |/spec
| | Yes | Returns OpenAPI specification (YAML) |/stat
| | Yes | Returns server statistics |/debug
| | Yes | Returns debug status |/debug/on
| | Yes | Enables debug logging |/debug/off
| | Yes | Disables debug logging |
Connect to ws://localhost:3033/ for real-time pub/sub messaging. Authentication uses the same origin/API key checks as HTTP (pass API key via query param: ws://localhost:3033/?apikey=secret-key-1).
All messages use msgpack binary format with structuredClone: true for efficient serialization of Maps, Sets, Dates, and RegExps.
#### Message Types
Client → Server:
| Type | Fields | Description |
|------|--------|-------------|
| subscribe | channel, id? | Subscribe to a channel |unsubscribe
| | channel, id? | Unsubscribe from a channel |publish
| | channel, data, id? | Publish message to a channel |ping
| | id? | Keep-alive ping |
Server → Client:
| Type | Fields | Description |
|------|--------|-------------|
| connected | - | Connection established |subscribed
| | channel, id?, already? | Subscription confirmed |unsubscribed
| | channel, id? | Unsubscription confirmed |published
| | channel, id? | Publish confirmed |message
| | channel, data | Incoming message from subscribed channel |pong
| | id? | Ping response |error
| | error, id? | Error message |
The optional id field can be used to correlate requests with responses:
`js
// Client sends request with id
ws.send(packr.pack({ type: "subscribe", channel: "events", id: "req-123" }));
// Server responds with same id
// { type: "subscribed", channel: "events", id: "req-123" }
`
#### JavaScript Client Example
`js
import { Packr, Unpackr } from "msgpackr";
const packr = new Packr({ structuredClone: true });
const unpackr = new Unpackr({ structuredClone: true });
const ws = new WebSocket("ws://localhost:3033/?apikey=secret-key-1");
ws.binaryType = "arraybuffer";
const pending = new Map();
let reqId = 0;
function send(message) {
return new Promise((resolve) => {
const id = String(++reqId);
pending.set(id, resolve);
ws.send(packr.pack({ ...message, id }));
});
}
ws.onmessage = (event) => {
const msg = unpackr.unpack(new Uint8Array(event.data));
if (msg.id && pending.has(msg.id)) {
pending.get(msg.id)(msg);
pending.delete(msg.id);
} else if (msg.type === "message") {
console.log("Channel message:", msg.channel, msg.data);
}
};
// Usage with async/await
await send({ type: "subscribe", channel: "my-channel" });
await send({ type: "publish", channel: "my-channel", data: { hello: "world" } });
await send({ type: "unsubscribe", channel: "my-channel" });
`
Type definitions are available for both REST and WebSocket APIs:
`ts`
import type {
ServiceRequestBody,
ServiceRequestQuery,
WsClientMessage,
WsServerMessage,
WsChannelMessage,
} from "cry-ebus-proxy/contract.types";
For runtime validation with Zod, use the full contract:
`ts`
import { contract, schemas } from "cry-ebus-proxy/contract.zod";
OpenAPI 3.1 specification is available at openapi.yaml.
Compile TypeScript contract files:
`bash`
npm run build
This generates dist/ with compiled JavaScript and type declarations.
`bash`
npm run build
npm publish
The prepublishOnly script automatically runs the build before publishing.
- index.mjs - Main serverdist/contract.types.js
- + .d.ts - Pure TypeScript typesdist/contract.zod.js
- + .d.ts - Zod schemas and ts-rest contractopenapi.yaml` - OpenAPI specification
-