@claxedo/workspace-relay 0.1.0 → 0.5.1

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/README.md CHANGED
@@ -1,9 +1,82 @@
1
1
  # `@claxedo/workspace-relay`
2
2
 
3
- The Claxedo workspace relay — a single canonical bidirectional tunnel
4
- between cloud-hosted browsers and workspace-runtime hosts (whether the
5
- host is a cloud VM or a user laptop). One package, one process, one
6
- config surface — there is no separate `dev-relay` codepath.
3
+ The Claxedo workspace relay — the single canonical tunnel process between
4
+ cloud-hosted browsers and workspace-runtime hosts, whether the host is a cloud
5
+ VM or a user laptop. One package, one process, one config surface — there is no
6
+ separate `dev-relay` codepath.
7
+
8
+ Production v1 is deliberately single-instance for user-hosted traffic. The
9
+ relay keeps host presence and host-tunnel sockets in process-local maps, and a
10
+ `hostId` has exactly one active tunnel at a time. Deploy one active relay
11
+ process per user-hosted relay fleet; horizontal scaling needs a future routing
12
+ owner for sticky host tunnels, split-brain prevention, and failover. A relay
13
+ process, VM, or region failure drops existing user-hosted HTTP, WebSocket, SSE,
14
+ and PTY sessions until workspace runtimes reconnect.
15
+
16
+ ## Deployment Security
17
+
18
+ The relay is a public edge service for workspace traffic, but it is not the
19
+ identity provider or policy database. It verifies short-lived runtime access
20
+ tokens, calls the configured target resolver/revocation callback, and forwards
21
+ accepted traffic to the selected host with a freshly minted relay-host token.
22
+
23
+ | Token | Default TTL | Issuer | Audience | Purpose |
24
+ | --- | ---: | --- | --- | --- |
25
+ | Runtime Access Token (RAT) | 30 minutes | `claxedo-control-plane` | `workspace-relay` | User/browser authorization to reach one workspace and host through the relay. |
26
+ | Host Tunnel Token (HTT) | 5 minutes | `claxedo-control-plane` | `workspace-relay-host-tunnel` | Workspace runtime authorization to register a user-hosted tunnel. |
27
+ | Relay Host Token (RHT) | 60 seconds | `workspace-relay` | `workspace-host-service` | Per-request relay-to-host authorization minted after RAT validation. |
28
+
29
+ Runtime access tokens and host tunnel tokens bind issuer, audience, subject,
30
+ workspace id, host id, expiry, issue time, and JTI. Runtime access tokens also
31
+ bind role. Relay-host tokens additionally bind the deployment pair:
32
+ `cloud/cloud-vm` or `user-hosted/local-worktree`.
33
+
34
+ ### Revocation And Active Checks
35
+
36
+ `isRuntimeAccessTokenActive` is the revocation/target freshness hook. Production
37
+ deployments should implement it by calling the control plane or an equivalent
38
+ authority on every new HTTP request and WebSocket upgrade. A false result
39
+ rejects before forwarding to the host.
40
+
41
+ Long-lived relayed sockets are authorized at establishment time. Already-open
42
+ SSE, PTY, and WebSocket streams may live until normal close, reconnect, relay
43
+ drain, host disconnect, or process restart. If immediate stream revocation is a
44
+ requirement, the control plane must also close the session/runtime channel.
45
+
46
+ ### Forwarding Boundary
47
+
48
+ The relay strips client-supplied `x-forwarded-for`, `x-forwarded-host`,
49
+ `x-forwarded-proto`, `x-real-ip`, `x-claxedo-internal-*`, and `x-supervisor-*`
50
+ headers. It replaces `Authorization` with an RHT, sets `x-workspace-id`, and
51
+ adds `x-forwarded-by: workspace-relay`.
52
+
53
+ For user-hosted targets, `Cookie` is stripped before forwarding. User-hosted
54
+ workspace processes may run near a developer's local browser cookie jar, so
55
+ browser cookies must not be passed through to the local host service. Cloud VM
56
+ targets may receive cookies when the caller intentionally sends them.
57
+
58
+ ### CORS
59
+
60
+ The relay owns CORS responses for browser-facing workspace requests. Do not
61
+ forward upstream CORS headers as the source of truth. Add allowed product
62
+ origins in the relay/server CORS configuration and keep wildcard origins out of
63
+ credentialed deployments.
64
+
65
+ ### Metrics
66
+
67
+ `GET /metrics` is privileged operational data. In production, set
68
+ `CLAXEDO_RELAY_METRICS_TOKEN` and scrape with `Authorization: Bearer <token>`.
69
+ When no metrics token is configured, the endpoint only allows callers that the
70
+ adapter identifies as loopback; if no remote-address resolver exists, it fails
71
+ closed.
72
+
73
+ ### User-Hosted Topology
74
+
75
+ Production v1 supports one active relay instance for user-hosted traffic, or a
76
+ load balancer with strict stickiness that keeps each `hostId` on the relay
77
+ process that owns its tunnel socket. Non-sticky horizontal scaling needs an
78
+ external directory and tunnel routing owner before it is safe to advertise as
79
+ durable.
7
80
 
8
81
  ## Why one package, no parallel impl
9
82
 
@@ -24,7 +97,7 @@ Re-exported from [`src/index.ts`](src/index.ts):
24
97
  | Concern | Module | Notable exports |
25
98
  | --- | --- | --- |
26
99
  | Token issuance / verification | [`src/auth.ts`](src/auth.ts) | `mintRuntimeAccessToken`, `verifyRuntimeAccessToken`, `mintRelayHostToken`, `verifyRelayHostToken`, `mintHostTunnelToken`, `verifyHostTunnelToken`, types `RelayRole`, `RelayAccess`, `RelayBacking`, `RelayJwtAlgorithm`, `RuntimeAccessTokenClaims`, `RelayKey`, `RelayKeyResolver`, error class `WorkspaceRelayAuthError` |
27
- | Hono HTTP surface | [`src/server.ts`](src/server.ts) | `createWorkspaceRelayServer`, types `WorkspaceRelayOptions`, `WorkspaceRelayTarget`, `RuntimeAccessTokenActiveResult`, `WorkspaceRelayAuditEvent`, `WorkspaceRelayMetricsSources`, `RelayHostPublicKey` |
100
+ | Hono HTTP surface | [`src/server.ts`](src/server.ts) | `createWorkspaceRelay`, `authorizeWorkspaceRelayRequest`, types `WorkspaceRelayOptions`, `WorkspaceRelayTarget`, `RuntimeAccessTokenActiveResult`, `WorkspaceRelayAuditEvent`, `WorkspaceRelayMetricsSources`, `RelayHostPublicKey` |
28
101
  | Active-host directory | [`src/directory.ts`](src/directory.ts) | `createWorkspaceRelayDirectory`, `disposeWorkspaceRelayDirectory`, types `WorkspaceRelayDirectory`, `HostTunnelPresence` |
29
102
  | Bun-specific server bootstrap | [`src/bun.ts`](src/bun.ts) | `createWorkspaceRelayBun`, type `WorkspaceRelayBunOptions`, `WorkspaceRelayBunDrainController`, metrics `getFragmentationStats`, `getSlowConsumerStats` |
30
103
 
@@ -50,14 +123,20 @@ production fail-closed gate at `src/main.ts`.
50
123
  | `CLAXEDO_RUNTIME_ACCESS_TOKEN_PUBLIC_KEY_PEM` | Inline public key (alternative to JWKS). |
51
124
  | `CLAXEDO_RELAY_HOST_VERIFY_PEM` | Public PEM the relay uses to verify host-tunnel tokens. |
52
125
  | `CLAXEDO_RELAY_HOST_SIGNING_KEY_PEM` | Private PEM the relay uses to mint relay-host tokens. |
126
+ | `CLAXEDO_RELAY_METRICS_TOKEN` | Optional bearer token for `/metrics`. Without it, `/metrics` requires a trusted loopback remote-address resolver. |
127
+ | `CLAXEDO_RELAY_DRAIN_TIMEOUT_MS` | Graceful shutdown wait before force-closing sockets. Defaults to 30000. |
53
128
  | `NODE_ENV` | `production` / `development` / `test`. Switches the production fail-closed gate. |
54
129
 
55
130
  Token verification is pluggable today via the
56
131
  `RelayKey | RelayKeyResolver` pair on `verifyRuntimeAccessToken` and
57
132
  friends, **and** via the unified
58
133
  [`TokenVerifier`](../workspace-relay-protocol/src/token-verifier.ts)
59
- interface in `@claxedo/workspace-relay-protocol`. To swap the relay's
60
- auth backend in a self-hosted deployment:
134
+ interface in `@claxedo/workspace-relay-protocol`. A custom verifier is only the
135
+ crypto/introspection authority; the relay still validates its output into
136
+ `RuntimeAccessTokenClaims`, binds the URL workspace id to the claims, applies
137
+ revocation, and allowlists roles. Missing, unknown, or malformed roles deny.
138
+
139
+ To swap the relay's auth backend in a self-hosted deployment:
61
140
 
62
141
  ```ts
63
142
  import { createStaticTokenVerifier } from "@claxedo/workspace-relay-protocol"
@@ -67,8 +146,7 @@ const verifier = createStaticTokenVerifier({
67
146
  "tok-tenant-1": { subject: "u1", scopes: ["workspace:write"], claims: {} },
68
147
  },
69
148
  })
70
- // Pass `verifier` into a future `WorkspaceRelayOptions.tokenVerifier`
71
- // option when the relay accepts custom verification backends directly.
149
+ // Pass `verifier` as `WorkspaceRelayOptions.tokenVerifier`.
72
150
  ```
73
151
 
74
152
  The `TokenVerifier` interface intentionally contains only `verify(token)`.
@@ -78,6 +156,62 @@ package: `createHttpTokenVerifier` for remote verifiers
78
156
  (Clerk-style introspection, custom OIDC), and `createStaticTokenVerifier`
79
157
  for tests and self-hosted single-tenant setups.
80
158
 
159
+ `createHttpTokenVerifier` is a reference implementation. Its HTTPS endpoint is
160
+ the crypto authority and must enforce issuer, audience, expiry, key selection,
161
+ and replay policy before returning claims. Treat the endpoint as trusted
162
+ operator configuration, not as tenant/user input.
163
+
164
+ Long-lived relayed sockets are authorized at establishment. Revocation is
165
+ checked for new HTTP requests and WebSocket upgrades; already-established
166
+ WebSocket/SSE/PTY streams may live until their normal close, reconnect, relay
167
+ drain, or process restart.
168
+
169
+ ## Tunnel Lifecycle
170
+
171
+ Host-tunnel reconnects replace the old socket deterministically. Replacement
172
+ cleans the old socket's pending HTTP responses, child WebSocket channels,
173
+ heartbeat timer, and buffered work before installing the new socket. Stale close
174
+ events identity-check the current owner before deleting presence, so an old
175
+ socket cannot mark a replacement offline.
176
+
177
+ Relay drain sets `/health` unhealthy, rejects new workspace requests and tunnel
178
+ registrations with `503 relay_draining`, closes active host tunnels so runtimes
179
+ reconnect promptly, waits for pending work up to the configured timeout, then
180
+ stops the server. Truly uncaught exceptions and unhandled rejections are fatal:
181
+ the relay marks itself draining, stops accepting work, disposes timers, and
182
+ exits for supervisor restart.
183
+
184
+ ## External Directory Design
185
+
186
+ The current `WorkspaceRelayDirectory` is an in-memory implementation of the
187
+ directory contract:
188
+
189
+ ```ts
190
+ type WorkspaceRelayDirectory = {
191
+ registerHostTunnel(input: { hostId: string; workspaceIds: string[] }): HostTunnelPresence
192
+ recordPong(hostId: string): HostTunnelPresence | undefined
193
+ disconnectHost(hostId: string): void
194
+ activeHost(input: { hostId: string; workspaceId: string }): HostTunnelPresence | undefined
195
+ sweep(): void
196
+ dispose(): void
197
+ size(): number
198
+ }
199
+ ```
200
+
201
+ A Redis, Durable Object, or equivalent implementation should keep the same
202
+ semantics:
203
+
204
+ - one active owner for a `hostId`;
205
+ - TTL extension on heartbeat pong;
206
+ - immediate removal on disconnect;
207
+ - workspace membership checks before user-hosted forwarding;
208
+ - split-brain prevention when a replacement tunnel connects;
209
+ - observability for active host count and stale owner cleanup.
210
+
211
+ The missing piece for true multi-instance user-hosted relay is not just durable
212
+ presence storage. HTTP/WebSocket/SSE/PTY traffic must also route to the process
213
+ or durable object that owns the live tunnel socket for that `hostId`.
214
+
81
215
  ## Routing
82
216
 
83
217
  The relay has **no** `/w/{workspaceId}/*` URL prefix of its own. The
package/dist/auth.d.ts CHANGED
@@ -5,8 +5,15 @@ export declare const runtimeAccessTokenAudience = "workspace-relay";
5
5
  export declare const hostTunnelTokenAudience = "workspace-relay-host-tunnel";
6
6
  export declare const relayHostTokenIssuer = "workspace-relay";
7
7
  export declare const relayHostTokenAudience = "workspace-host-service";
8
- export type RelayAccess = "cloud" | "user-hosted";
9
- export type RelayBacking = "local-worktree" | "cloud-vm";
8
+ export type RelayClaimPair = {
9
+ access: "cloud";
10
+ backing: "cloud-vm";
11
+ } | {
12
+ access: "user-hosted";
13
+ backing: "local-worktree";
14
+ };
15
+ export type RelayAccess = RelayClaimPair["access"];
16
+ export type RelayBacking = RelayClaimPair["backing"];
10
17
  export type RelayJwtAlgorithm = (typeof algorithms)[number];
11
18
  export type RelayRole = "viewer" | "editor" | "admin" | "owner";
12
19
  export type RuntimeAccessTokenClaims = {
@@ -28,12 +35,10 @@ export type RelayHostTokenClaims = {
28
35
  org_id: string;
29
36
  workspace_id: string;
30
37
  host_id: string;
31
- access: RelayAccess;
32
- backing: RelayBacking;
33
38
  exp: number;
34
39
  iat: number;
35
40
  jti: string;
36
- };
41
+ } & RelayClaimPair;
37
42
  export type HostTunnelTokenClaims = {
38
43
  iss: typeof runtimeAccessTokenIssuer;
39
44
  aud: typeof hostTunnelTokenAudience;
@@ -58,9 +63,7 @@ type RuntimeInput = {
58
63
  jti?: string;
59
64
  now?: number;
60
65
  };
61
- type RelayHostInput = RuntimeInput & {
62
- access: RelayAccess;
63
- backing: RelayBacking;
66
+ type RelayHostInput = RuntimeInput & RelayClaimPair & {
64
67
  /**
65
68
  * Optional `kid` to embed in the JWT protected header. Verifiers using a
66
69
  * JWKS resolver dispatch on this to pick the matching key, so a freshly
@@ -86,6 +89,10 @@ type ExpectedHostTunnel = {
86
89
  hostId: string;
87
90
  workspaceIds: string[];
88
91
  };
92
+ export declare function isRelayClaimPair(input: {
93
+ access?: unknown;
94
+ backing?: unknown;
95
+ }): input is RelayClaimPair;
89
96
  /**
90
97
  * Resolver function returned by `createRemoteJWKSet` / `createLocalJWKSet`.
91
98
  * Accepted by `jwtVerify` directly — jose dispatches based on whether the
@@ -100,6 +107,7 @@ export type RelayKey = CryptoKey | Uint8Array | RelayKeyResolver;
100
107
  type RelaySigningKey = CryptoKey | Uint8Array;
101
108
  export declare function mintRuntimeAccessToken(input: RuntimeInput, key: RelaySigningKey, alg: RelayJwtAlgorithm): Promise<string>;
102
109
  export declare function verifyRuntimeAccessToken(token: string, key: RelayKey, expected: ExpectedTarget): Promise<RuntimeAccessTokenClaims>;
110
+ export declare function validateRuntimeAccessTokenClaims(input: Record<string, unknown>, expected: ExpectedTarget): RuntimeAccessTokenClaims;
103
111
  export declare function mintHostTunnelToken(input: HostTunnelInput, key: RelaySigningKey, alg: RelayJwtAlgorithm): Promise<string>;
104
112
  export declare function verifyHostTunnelToken(token: string, key: RelayKey, expected: ExpectedHostTunnel): Promise<HostTunnelTokenClaims>;
105
113
  export declare function mintRelayHostToken(input: RelayHostInput, key: RelaySigningKey, alg: RelayJwtAlgorithm): Promise<string>;
package/dist/bun.d.ts CHANGED
@@ -3,8 +3,21 @@ type RelayClientWebSocketData = {
3
3
  kind: "client";
4
4
  upstreamUrl: string;
5
5
  headers: Record<string, string>;
6
- queue: Array<string | Buffer<ArrayBuffer>>;
6
+ queue: Array<{
7
+ payload: string | Buffer<ArrayBuffer>;
8
+ queuedAt: number;
9
+ }>;
7
10
  upstream?: WebSocket;
11
+ upstreamOpenTimer?: ReturnType<typeof setTimeout>;
12
+ trace?: RelayClientWebSocketTrace;
13
+ };
14
+ type RelayClientWebSocketTrace = {
15
+ acceptedAt: number;
16
+ upstreamStartedAt?: number;
17
+ upstreamOpenMs?: number;
18
+ queuedFrames: number;
19
+ maxQueuedDelayMs: number;
20
+ emitted: boolean;
8
21
  };
9
22
  type RelayHostTunnelWebSocketData = {
10
23
  kind: "host-tunnel";
@@ -13,6 +26,7 @@ type RelayHostTunnelWebSocketData = {
13
26
  pending: Map<string, PendingTunnelHttpResponse>;
14
27
  channels: Map<string, Bun.ServerWebSocket<RelayUserHostedClientWebSocketData>>;
15
28
  heartbeat?: ReturnType<typeof setInterval>;
29
+ missedPongs: number;
16
30
  messageBuffer: string;
17
31
  };
18
32
  type RelayUserHostedClientWebSocketData = {
@@ -24,6 +38,9 @@ type RelayUserHostedClientWebSocketData = {
24
38
  relayHostToken: string;
25
39
  };
26
40
  type RelayWebSocketData = RelayClientWebSocketData | RelayHostTunnelWebSocketData | RelayUserHostedClientWebSocketData;
41
+ type UpstreamWebSocketConstructor = {
42
+ new (url: string, options: Bun.WebSocketOptions): WebSocket;
43
+ };
27
44
  type PendingTunnelHttpResponse = {
28
45
  controller: ReadableStreamDefaultController<Uint8Array>;
29
46
  stream: ReadableStream<Uint8Array>;
@@ -41,13 +58,28 @@ export type WorkspaceRelayHostTunnelOptions = {
41
58
  workspaceIds: string[];
42
59
  }) => boolean | Promise<boolean>;
43
60
  hostTunnelPingIntervalMs?: number;
61
+ hostTunnelMaxMissedPongs?: number;
44
62
  hostTunnelStateDebounceMs?: number;
45
63
  };
46
64
  export type WorkspaceRelayBackpressureOptions = {
47
65
  slowConsumerHighWaterMarkBytes?: number;
48
66
  slowConsumerTimeoutMs?: number;
67
+ tunnelRequestBodyMaxBytes?: number;
68
+ tunnelHttpResponseTimeoutMs?: number;
69
+ directHttpTimeoutMs?: number;
70
+ directHttpConcurrency?: number;
71
+ upstreamWebSocketOpenTimeoutMs?: number;
72
+ upstreamWebSocketPreOpenQueueMaxFrames?: number;
73
+ upstreamWebSocket?: UpstreamWebSocketConstructor;
74
+ webSocketBufferedAmountMaxBytes?: number;
49
75
  };
50
76
  export type WorkspaceRelayBunOptions = WorkspaceRelayHostTunnelOptions & WorkspaceRelayBackpressureOptions;
77
+ export type WorkspaceRelayBunTelemetry = {
78
+ getFragmentationStats(): FragmentationStats;
79
+ resetFragmentationStats(): void;
80
+ getSlowConsumerStats(): SlowConsumerStats;
81
+ resetSlowConsumerStats(): void;
82
+ };
51
83
  /**
52
84
  * T9: drain controller exposed by `createWorkspaceRelayBun`.
53
85
  *
@@ -56,8 +88,7 @@ export type WorkspaceRelayBunOptions = WorkspaceRelayHostTunnelOptions & Workspa
56
88
  * - returns 503 from `/health` so Fly removes the instance from routing
57
89
  * - rejects new HTTP requests with 503 (`relay_draining`) before doing auth
58
90
  * - rejects new host-tunnel WebSocket upgrades with 503
59
- * - leaves all existing in-flight HTTP requests, host tunnels, and
60
- * multiplexed WS channels alone so they can complete naturally
91
+ * - closes active host tunnels with 1012 so host clients reconnect promptly
61
92
  *
62
93
  * `pendingCount()` reports the total number of in-flight tunnel HTTP
63
94
  * responses across every connected host tunnel — this is what the operator
@@ -72,27 +103,27 @@ export type WorkspaceRelayBunDrainController = {
72
103
  remaining: number;
73
104
  }>;
74
105
  };
75
- export declare function getFragmentationStats(): {
106
+ export type FragmentationStats = {
76
107
  fragmentsBuffered: number;
77
108
  oversizedClosed: number;
78
109
  };
79
- export declare function resetFragmentationStats(): void;
80
- export declare function getSlowConsumerStats(): {
110
+ export type SlowConsumerStats = {
81
111
  overflowEvents: number;
82
112
  timerFired: number;
83
113
  droppedRequests: number;
84
114
  };
85
- export declare function resetSlowConsumerStats(): void;
86
- export declare function __resetHostTunnelStateDebounce(): void;
115
+ declare function createSlowConsumerStats(): SlowConsumerStats;
87
116
  declare function enqueueChunkWithBackpressure(input: {
88
117
  ws: Bun.ServerWebSocket<RelayHostTunnelWebSocketData>;
89
118
  requestId: string;
90
119
  entry: PendingTunnelHttpResponse;
91
120
  chunk: Uint8Array;
92
121
  slowConsumerTimeoutMs: number;
122
+ slowConsumerStats: SlowConsumerStats;
93
123
  }): void;
94
124
  export declare const __slowConsumerInternalsForTest: {
95
125
  enqueueChunkWithBackpressure: typeof enqueueChunkWithBackpressure;
126
+ createSlowConsumerStats: typeof createSlowConsumerStats;
96
127
  };
97
128
  export declare function createWorkspaceRelayBun(options: WorkspaceRelayOptions, bunOptions?: WorkspaceRelayBunOptions): {
98
129
  fetch(request: Request, server: Bun.Server<RelayWebSocketData>): Promise<Response | undefined>;
@@ -103,5 +134,6 @@ export declare function createWorkspaceRelayBun(options: WorkspaceRelayOptions,
103
134
  close(ws: Bun.ServerWebSocket<RelayWebSocketData>, code: number, reason: string): void;
104
135
  };
105
136
  drain: WorkspaceRelayBunDrainController;
137
+ telemetry: WorkspaceRelayBunTelemetry;
106
138
  };
107
139
  export {};
@@ -0,0 +1,116 @@
1
+ import { type AuthorizedWorkspaceRelayRequest, type WorkspaceRelayOptions } from "./server";
2
+ export type WorkspaceRelayDurableObjectId = unknown;
3
+ export type WorkspaceRelayDurableObjectStub = {
4
+ fetch: (request: Request) => Promise<Response>;
5
+ };
6
+ export type WorkspaceRelayDurableObjectGetOptions = {
7
+ locationHint?: string;
8
+ };
9
+ export type WorkspaceRelayDurableObjectNamespace = {
10
+ idFromName: (name: string) => WorkspaceRelayDurableObjectId;
11
+ get: (id: WorkspaceRelayDurableObjectId, options?: WorkspaceRelayDurableObjectGetOptions) => WorkspaceRelayDurableObjectStub;
12
+ };
13
+ export declare const DEFAULT_RELAY_LOCATION_HINT = "apac";
14
+ export type WorkspaceRelayDurableObjectEnv = Record<string, unknown>;
15
+ export type WorkspaceRelayDurableObjectGatewayOptions = {
16
+ bindingName?: string;
17
+ namespace?: WorkspaceRelayDurableObjectNamespace;
18
+ };
19
+ export type WorkspaceRelayDurableObjectSocketEvent = {
20
+ data?: unknown;
21
+ code?: number;
22
+ reason?: string;
23
+ };
24
+ export type WorkspaceRelayDurableObjectSocket = {
25
+ accept?: () => void;
26
+ readyState?: number;
27
+ binaryType?: string;
28
+ send?: (message: string | ArrayBuffer | Uint8Array) => void;
29
+ addEventListener?: (type: "open" | "message" | "close" | "error", listener: (event?: WorkspaceRelayDurableObjectSocketEvent) => void) => void;
30
+ close?: (code?: number, reason?: string) => void;
31
+ serializeAttachment?: (attachment: WorkspaceRelaySocketAttachment) => void;
32
+ deserializeAttachment?: () => WorkspaceRelaySocketAttachment | undefined;
33
+ };
34
+ export type WorkspaceRelayDurableObjectSocketPair = {
35
+ client: WorkspaceRelayDurableObjectSocket;
36
+ server: WorkspaceRelayDurableObjectSocket;
37
+ };
38
+ export type WorkspaceRelayDurableObjectConnectWebSocket = (url: string, init: {
39
+ headers: Record<string, string>;
40
+ }) => WorkspaceRelayDurableObjectSocket | Promise<WorkspaceRelayDurableObjectSocket>;
41
+ export type WorkspaceRelayDurableObjectRoomOptions = WorkspaceRelayOptions & {
42
+ createWebSocketPair?: () => WorkspaceRelayDurableObjectSocketPair;
43
+ connectWebSocket?: WorkspaceRelayDurableObjectConnectWebSocket;
44
+ hibernation?: WorkspaceRelayDurableObjectHibernation;
45
+ upstreamWebSocketPreOpenQueueMaxFrames?: number;
46
+ tunnelPendingHttpCap?: number;
47
+ tunnelStartedStreamCap?: number;
48
+ tunnelChannelCap?: number;
49
+ tunnelRequestBodyMaxBytes?: number;
50
+ tunnelResponseBodyMaxBytes?: number;
51
+ slowConsumerHighWaterMarkBytes?: number;
52
+ slowConsumerTimeoutMs?: number;
53
+ runtimeAccessTokenActiveCheckIntervalMs?: number;
54
+ workspaceTargetActiveCheckIntervalMs?: number;
55
+ traceSampleRate?: number;
56
+ traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
57
+ now?: () => number;
58
+ };
59
+ export type WorkspaceRelayTraceLogEvent = {
60
+ event: "workspace_relay_request";
61
+ traceId: string;
62
+ workspaceId?: string;
63
+ routeKind: string;
64
+ method: string;
65
+ path: string;
66
+ status: number;
67
+ totalMs: number;
68
+ phases: Record<string, number>;
69
+ };
70
+ export type WorkspaceRelayDurableObjectHibernation = {
71
+ acceptWebSocket: (socket: WorkspaceRelayDurableObjectSocket) => void;
72
+ getWebSockets: () => WorkspaceRelayDurableObjectSocket[];
73
+ };
74
+ export type WorkspaceRelaySocketAttachment = {
75
+ kind: "host-tunnel";
76
+ hostId: string;
77
+ workspaceIds: string[];
78
+ connectedAt: number;
79
+ } | {
80
+ kind: "user-hosted-client";
81
+ channelId: string;
82
+ claims: RuntimeAccessAttachmentClaims;
83
+ target: WorkspaceRelayTargetAttachment;
84
+ path: string;
85
+ connectedAt: number;
86
+ };
87
+ type RuntimeAccessAttachmentClaims = AuthorizedWorkspaceRelayRequest["claims"];
88
+ type WorkspaceRelayTargetAttachment = AuthorizedWorkspaceRelayRequest["target"];
89
+ export type WorkspaceRelayDurableObjectDrainController = {
90
+ isDraining(): boolean;
91
+ setDraining(value: boolean): void;
92
+ pendingCount(): number;
93
+ waitForDrain(timeoutMs: number): Promise<{
94
+ drained: boolean;
95
+ remaining: number;
96
+ }>;
97
+ };
98
+ export declare function workspaceRelayDurableObjectRoomName(workspaceId: string): string;
99
+ export declare function workspaceRelayDurableObjectWorkspaceId(request: Request): string | undefined;
100
+ export declare function setWorkspaceRelayAppOrigins(raw: string | undefined): void;
101
+ export declare function createWorkspaceRelayDurableObjectGateway(options?: WorkspaceRelayDurableObjectGatewayOptions): {
102
+ fetch(request: Request, env?: WorkspaceRelayDurableObjectEnv): Promise<Response>;
103
+ };
104
+ export declare function createWorkspaceRelayDurableObjectRoom(options: WorkspaceRelayDurableObjectRoomOptions): {
105
+ fetch(request: Request): Promise<Response>;
106
+ webSocketMessage(socket: WorkspaceRelayDurableObjectSocket, message: string | ArrayBuffer): Promise<void>;
107
+ webSocketClose(socket: WorkspaceRelayDurableObjectSocket, code: number, reason: string): void;
108
+ webSocketError(socket: WorkspaceRelayDurableObjectSocket): void;
109
+ state(): {
110
+ hostTunnelCount: number;
111
+ clientCount: number;
112
+ hostIds: string[];
113
+ };
114
+ drain: WorkspaceRelayDurableObjectDrainController;
115
+ };
116
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./auth";
2
2
  export * from "./bun";
3
+ export * from "./cloudflare";
3
4
  export * from "./directory";
4
5
  export * from "./server";