cry-ebus-proxy
v1.0.3
Published
ebus to rest + websocket proxy
Readme
ebus-proxy
High-performance HTTP proxy server for cry-ebus2 client requests, built with uWebSockets.js.
Installation
npm installConfiguration
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 | - |
Security
When ALLOWED_ORIGINS or API_KEYS are configured, requests must satisfy at least one of these conditions:
- Origin check: The
Originheader hostname matches one inALLOWED_ORIGINS - API key: A valid key is provided via:
- Query parameter:
?apikey=<key>or?api_key=<key> - Header:
X-API-Key: <key>orAuthorization: Bearer <key> - Cookie:
apikey=<key>(set via/apikey/<key>endpoint)
- Query parameter:
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/<key>. Clear the cookie with /apikey/clear.
Example .env:
ALLOWED_ORIGINS=example.com, localhost, app.example.com
API_KEYS=secret-key-1, secret-key-2Usage
npm startREST API
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
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()));Utility Endpoints
| Endpoint | Auth | Description |
|----------|------|-------------|
| / | Yes | Returns ebus-proxy when no service specified |
| /ping | No | Returns pong <ISO-date> 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 |
WebSocket API
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:
// 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
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" });TypeScript Support
Type definitions are available for both REST and WebSocket APIs:
import type {
ServiceRequestBody,
ServiceRequestQuery,
WsClientMessage,
WsServerMessage,
WsChannelMessage,
} from "cry-ebus-proxy/contract.types";For runtime validation with Zod, use the full contract:
import { contract, schemas } from "cry-ebus-proxy/contract.zod";API Documentation
OpenAPI 3.1 specification is available at openapi.yaml.
Development
Build
Compile TypeScript contract files:
npm run buildThis generates dist/ with compiled JavaScript and type declarations.
Publish
npm run build
npm publishThe prepublishOnly script automatically runs the build before publishing.
Published Files
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
