@claxedo/workspace-relay 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/auth.d.ts +1 -0
- package/dist/cloudflare.d.ts +5 -0
- package/dist/cors-origins.d.ts +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +192 -55
- package/dist/main.d.ts +49 -0
- package/dist/main.mjs +40181 -173
- package/dist/server.d.ts +2 -1
- package/dist/worker-h2.d.ts +9 -0
- package/dist/worker.d.ts +2 -0
- package/package.json +19 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Claxedo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,6 +5,11 @@ cloud-hosted browsers and workspace-runtime hosts, whether the host is a cloud
|
|
|
5
5
|
VM or a user laptop. One package, one process, one config surface — there is no
|
|
6
6
|
separate `dev-relay` codepath.
|
|
7
7
|
|
|
8
|
+
Runtime requirement: the packaged `workspace-relay` bin and the default server
|
|
9
|
+
bootstrap run on **Bun** (they use `Bun.*` APIs via the Bun adapter). Node is
|
|
10
|
+
not supported for the standalone process today; the Cloudflare Worker adapter
|
|
11
|
+
is the other supported runtime.
|
|
12
|
+
|
|
8
13
|
Production v1 is deliberately single-instance for user-hosted traffic. The
|
|
9
14
|
relay keeps host presence and host-tunnel sockets in process-local maps, and a
|
|
10
15
|
`hostId` has exactly one active tunnel at a time. Deploy one active relay
|
|
@@ -13,6 +18,74 @@ owner for sticky host tunnels, split-brain prevention, and failover. A relay
|
|
|
13
18
|
process, VM, or region failure drops existing user-hosted HTTP, WebSocket, SSE,
|
|
14
19
|
and PTY sessions until workspace runtimes reconnect.
|
|
15
20
|
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bun add @claxedo/workspace-relay jose
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The smallest runnable relay: mint an EdDSA key pair to stand in for the
|
|
28
|
+
control plane's signing key, point `resolveTarget` at a cloud-VM-style
|
|
29
|
+
workspace host, boot the Bun adapter, then mint a Runtime Access Token the
|
|
30
|
+
way `claxedo-control-plane` would and use it to reach the target through the
|
|
31
|
+
relay.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// relay.ts
|
|
35
|
+
import { generateKeyPair } from "jose"
|
|
36
|
+
import { createWorkspaceRelayBun, mintRuntimeAccessToken } from "@claxedo/workspace-relay"
|
|
37
|
+
|
|
38
|
+
const runtimeAccessKey = await generateKeyPair("EdDSA", { extractable: true })
|
|
39
|
+
const relayHostKey = await generateKeyPair("EdDSA", { extractable: true })
|
|
40
|
+
|
|
41
|
+
// Stand-in workspace host: whatever the relay forwards accepted traffic to.
|
|
42
|
+
const host = Bun.serve({
|
|
43
|
+
port: 0,
|
|
44
|
+
fetch: (request) => new Response(`host saw ${new URL(request.url).pathname}`),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const handler = createWorkspaceRelayBun({
|
|
48
|
+
runtimeAccessKey: runtimeAccessKey.publicKey,
|
|
49
|
+
relayHostSigningKey: relayHostKey.privateKey,
|
|
50
|
+
relayHostAlgorithm: "EdDSA",
|
|
51
|
+
resolveTarget: (claims) => ({
|
|
52
|
+
workspaceId: claims.workspace_id,
|
|
53
|
+
hostId: claims.host_id,
|
|
54
|
+
baseUrl: String(host.url).replace(/\/$/, ""),
|
|
55
|
+
access: "cloud",
|
|
56
|
+
backing: "cloud-vm",
|
|
57
|
+
}),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const relay = Bun.serve({
|
|
61
|
+
port: 7777,
|
|
62
|
+
fetch: handler.fetch,
|
|
63
|
+
websocket: handler.websocket,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const token = await mintRuntimeAccessToken(
|
|
67
|
+
{ subject: "user_1", orgId: "org_1", workspaceId: "ws_1", hostId: "host_1", role: "editor" },
|
|
68
|
+
runtimeAccessKey.privateKey,
|
|
69
|
+
"EdDSA",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
console.log(`relay listening on ${relay.url}`)
|
|
73
|
+
console.log(`curl -H "Authorization: Bearer ${token}" ${relay.url}workspaces/ws_1/hello`)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
bun run relay.ts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run the printed `curl` command in another terminal — the relay verifies the
|
|
81
|
+
Runtime Access Token, calls `resolveTarget`, mints a Relay Host Token, and
|
|
82
|
+
forwards the request; you'll see `host saw /hello` come back. This mirrors
|
|
83
|
+
what [`src/main.ts`](src/main.ts) assembles for production, minus the
|
|
84
|
+
env-driven resolver client, JWKS, and graceful drain — see
|
|
85
|
+
[docs/architecture.md](docs/architecture.md) for how those pieces fit
|
|
86
|
+
together, and the Configuration table below for the env vars that replace
|
|
87
|
+
the hardcoded values above in a real deployment.
|
|
88
|
+
|
|
16
89
|
## Deployment Security
|
|
17
90
|
|
|
18
91
|
The relay is a public edge service for workspace traffic, but it is not the
|
|
@@ -125,8 +198,17 @@ production fail-closed gate at `src/main.ts`.
|
|
|
125
198
|
| `CLAXEDO_RELAY_HOST_SIGNING_KEY_PEM` | Private PEM the relay uses to mint relay-host tokens. |
|
|
126
199
|
| `CLAXEDO_RELAY_METRICS_TOKEN` | Optional bearer token for `/metrics`. Without it, `/metrics` requires a trusted loopback remote-address resolver. |
|
|
127
200
|
| `CLAXEDO_RELAY_DRAIN_TIMEOUT_MS` | Graceful shutdown wait before force-closing sockets. Defaults to 30000. |
|
|
201
|
+
| `CLAXEDO_RELAY_ALLOWED_ORIGINS` | Comma-separated browser-origin allowlist for CORS. **Replaces** the built-in default list (Claxedo/OpenCode app origins plus `http://localhost:*` dev hosts) — self-hosted deployments set their own origins here. Grammar: exact origin, `https://*.example.com`, `http://localhost:*`. Works on both the Bun process and the Cloudflare Worker. |
|
|
128
202
|
| `NODE_ENV` | `production` / `development` / `test`. Switches the production fail-closed gate. |
|
|
129
203
|
|
|
204
|
+
Cloudflare Worker tracing knobs (Durable Object deployment):
|
|
205
|
+
|
|
206
|
+
| Env var | Purpose |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| `CLAXEDO_RELAY_TRACE_SAMPLE_RATE` | 0–1 sampling rate for request traces. Only sampled requests emit `Server-Timing` phase headers and structured trace logs; unsampled responses carry just the `x-claxedo-trace-id` correlation header. Defaults to 0 (off). |
|
|
209
|
+
| `CLAXEDO_RELAY_TRACE_FORCE_SECRET` | Optional operator secret. When set, a request with `x-claxedo-relay-trace: <secret>` forces sampling for that request (targeted debugging). Unset (default) means the force header is ignored — clients can never force tracing or timing emission. |
|
|
210
|
+
| `CLAXEDO_APP_ORIGINS` | Cloudflare-only, **additive** origin entries layered on top of the base allowlist (kept for existing deploys; prefer `CLAXEDO_RELAY_ALLOWED_ORIGINS` for full control). |
|
|
211
|
+
|
|
130
212
|
Token verification is pluggable today via the
|
|
131
213
|
`RelayKey | RelayKeyResolver` pair on `verifyRuntimeAccessToken` and
|
|
132
214
|
friends, **and** via the unified
|
package/dist/auth.d.ts
CHANGED
package/dist/cloudflare.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export type WorkspaceRelayDurableObjectRoomOptions = WorkspaceRelayOptions & {
|
|
|
53
53
|
runtimeAccessTokenActiveCheckIntervalMs?: number;
|
|
54
54
|
workspaceTargetActiveCheckIntervalMs?: number;
|
|
55
55
|
traceSampleRate?: number;
|
|
56
|
+
traceForceHeaderSecret?: string;
|
|
56
57
|
traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
|
|
57
58
|
now?: () => number;
|
|
58
59
|
};
|
|
@@ -98,6 +99,7 @@ export type WorkspaceRelayDurableObjectDrainController = {
|
|
|
98
99
|
export declare function workspaceRelayDurableObjectRoomName(workspaceId: string): string;
|
|
99
100
|
export declare function workspaceRelayDurableObjectWorkspaceId(request: Request): string | undefined;
|
|
100
101
|
export declare function setWorkspaceRelayAppOrigins(raw: string | undefined): void;
|
|
102
|
+
export declare function setWorkspaceRelayAllowedOrigins(raw: string | undefined): void;
|
|
101
103
|
export declare function createWorkspaceRelayDurableObjectGateway(options?: WorkspaceRelayDurableObjectGatewayOptions): {
|
|
102
104
|
fetch(request: Request, env?: WorkspaceRelayDurableObjectEnv): Promise<Response>;
|
|
103
105
|
};
|
|
@@ -110,6 +112,9 @@ export declare function createWorkspaceRelayDurableObjectRoom(options: Workspace
|
|
|
110
112
|
hostTunnelCount: number;
|
|
111
113
|
clientCount: number;
|
|
112
114
|
hostIds: string[];
|
|
115
|
+
hostWorkspaceIds: {
|
|
116
|
+
[k: string]: string[];
|
|
117
|
+
};
|
|
113
118
|
};
|
|
114
119
|
drain: WorkspaceRelayDurableObjectDrainController;
|
|
115
120
|
};
|