@claxedo/workspace-relay 0.8.0 → 0.10.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 +92 -223
- package/dist/auth.d.ts +94 -35
- package/dist/cloudflare.d.ts +17 -57
- package/dist/cors-origins.d.ts +2 -0
- package/dist/directory.d.ts +5 -38
- package/dist/gateway-placement.d.ts +7 -0
- package/dist/host-tunnel-failure.d.ts +7 -0
- package/dist/host-tunnel-forwarding.d.ts +29 -0
- package/dist/http.d.ts +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +940 -870
- package/dist/relay-audit.d.ts +4 -0
- package/dist/relay-cors.d.ts +12 -0
- package/dist/relay-location.d.ts +93 -0
- package/dist/relay-target.d.ts +25 -0
- package/dist/relay-trace.d.ts +54 -0
- package/dist/resolver-clients.d.ts +116 -0
- package/dist/server.d.ts +27 -286
- package/dist/session-host-forwarding.d.ts +22 -0
- package/dist/test-support/unfenced-host-token.d.ts +6 -0
- package/dist/worker.d.ts +3 -3
- package/dist/workerd-fixture/compatibility.d.ts +2 -0
- package/package.json +8 -27
- package/dist/bun.d.ts +0 -225
- package/dist/bun.mjs +0 -2492
- package/dist/main.d.ts +0 -255
- package/dist/main.mjs +0 -23713
- package/dist/synthetic.d.ts +0 -60
- package/dist/upstream-websocket.d.ts +0 -34
- package/dist/user-hosted-forwarding.d.ts +0 -40
package/dist/server.d.ts
CHANGED
|
@@ -1,110 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
3
|
-
import { type HostTunnelTokenClaims, type RelayClaimPair, type RelayJwtAlgorithm, type RelayKey, type RelayRole, type RuntimeAccessTokenClaims } from "./auth";
|
|
1
|
+
import { type RuntimeAccessVerifierClaims, type TokenVerifier } from "@claxedo/workspace-relay-protocol";
|
|
2
|
+
import { type HostTunnelTokenClaims, type RelayJwtAlgorithm, type RelayKey, type RelayRole, type RuntimeAccessTokenClaims } from "./auth";
|
|
4
3
|
import type { WorkspaceRelayDirectory } from "./directory";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
export type WorkspaceRelayTarget = {
|
|
10
|
-
workspaceId: string;
|
|
11
|
-
hostId: string;
|
|
12
|
-
baseUrl: string;
|
|
13
|
-
upstreamHeaders?: Record<string, string>;
|
|
14
|
-
} & RelayClaimPair;
|
|
15
|
-
export type RuntimeAccessTokenActiveResult = {
|
|
16
|
-
active: true;
|
|
17
|
-
} | {
|
|
18
|
-
active: false;
|
|
19
|
-
code: string;
|
|
20
|
-
reason: string;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Both resolver clients — Bun (`main.ts`) and Worker (`worker.ts`) — read the
|
|
24
|
-
* control plane's JSON with these two parsers rather than asserting a type onto
|
|
25
|
-
* `Response.json()`. A half-built target used to travel all the way into
|
|
26
|
-
* routing and fail there as a confusing upstream error; it is now rejected at
|
|
27
|
-
* the one boundary that knows what the payload was supposed to be.
|
|
28
|
-
*/
|
|
29
|
-
export declare function parseWorkspaceRelayTarget(input: unknown): WorkspaceRelayTarget | undefined;
|
|
30
|
-
export declare function parseRuntimeAccessTokenActiveResult(input: unknown): RuntimeAccessTokenActiveResult | undefined;
|
|
31
|
-
/**
|
|
32
|
-
* `GET /internal/relay/host-generation?enrollmentId=` as the control plane
|
|
33
|
-
* answers it: the enrollment's current serving generation, or 404 for an
|
|
34
|
-
* enrollment it does not know (`undefined` here).
|
|
35
|
-
*/
|
|
36
|
-
export type HostGenerationResult = {
|
|
37
|
-
enrollmentId: string;
|
|
38
|
-
generation: number;
|
|
39
|
-
revoked: boolean;
|
|
40
|
-
};
|
|
41
|
-
export declare function parseHostGenerationResult(input: unknown): HostGenerationResult | undefined;
|
|
42
|
-
export type HostGenerationResolverLookupOptions = {
|
|
43
|
-
headers: Record<string, string>;
|
|
44
|
-
fetch?: (url: URL, init: RequestInit) => Promise<Response>;
|
|
45
|
-
/** Deadline per lookup; past it the lookup rejects, which the relay grades as unavailable. */
|
|
46
|
-
timeoutMs?: number;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* The HTTP lookup behind `resolveHostGeneration`, shared by the Bun process and
|
|
50
|
-
* the Worker. Only the control plane's own "enrollment not found" 404 resolves
|
|
51
|
-
* `undefined`; a bare 404 is a control plane without the route, and like every
|
|
52
|
-
* other non-ok status, a malformed body, or a hit deadline it throws so the
|
|
53
|
-
* relay refuses fenced tokens with a retryable 503 instead of grading them as
|
|
54
|
-
* unknown enrollments.
|
|
55
|
-
*/
|
|
56
|
-
export declare function createHostGenerationResolverLookup(url: string, options: HostGenerationResolverLookupOptions): HostGenerationLookup;
|
|
57
|
-
/**
|
|
58
|
-
* The fence between two host tunnels for one identity. An incumbent that
|
|
59
|
-
* carries a generation is displaced only by a candidate at the same or a
|
|
60
|
-
* higher generation — never by a lower one, and never by a token minted
|
|
61
|
-
* without a generation. An incumbent without a generation is displaced by
|
|
62
|
-
* any candidate, which is the pre-fence "newest wins" order.
|
|
63
|
-
*/
|
|
64
|
-
export declare function hostTunnelIncumbentOutranks(incumbentGeneration: number | undefined, candidateGeneration: number | undefined): boolean;
|
|
65
|
-
export type RevocationLookupArgs = {
|
|
66
|
-
jti: string;
|
|
67
|
-
workspaceId: string;
|
|
68
|
-
hostId: string;
|
|
69
|
-
};
|
|
70
|
-
export type RevocationLookup = (args: RevocationLookupArgs) => Promise<RuntimeAccessTokenActiveResult>;
|
|
71
|
-
export type CachedRevocationOptions = {
|
|
72
|
-
/** TTL in milliseconds for cached revocation responses. Defaults to 10_000. */
|
|
73
|
-
ttlMs?: number;
|
|
74
|
-
/** Clock injection for tests. Defaults to `Date.now`. */
|
|
75
|
-
now?: () => number;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* `generation` is the value the caller holds (a token's, a socket's). The
|
|
79
|
-
* cached client uses it to decide whether a cached answer may stand in for a
|
|
80
|
-
* fresh one; an uncached lookup ignores it. Resolves `undefined` for an
|
|
81
|
-
* unknown enrollment and THROWS when the control plane cannot be reached —
|
|
82
|
-
* the throw is the "unavailable" signal every consumer grades separately.
|
|
83
|
-
*/
|
|
84
|
-
export type HostGenerationLookupArgs = {
|
|
85
|
-
enrollmentId: string;
|
|
86
|
-
generation: number;
|
|
87
|
-
};
|
|
88
|
-
export type HostGenerationLookup = (args: HostGenerationLookupArgs) => Promise<HostGenerationResult | undefined>;
|
|
89
|
-
export type CachedHostGenerationOptions = {
|
|
90
|
-
/** TTL in milliseconds for cached host-generation answers. Defaults to 10_000. */
|
|
91
|
-
ttlMs?: number;
|
|
92
|
-
/** Clock injection for tests. Defaults to `Date.now`. */
|
|
93
|
-
now?: () => number;
|
|
94
|
-
};
|
|
95
|
-
export type TargetLookupArgs = {
|
|
96
|
-
workspaceId: string;
|
|
97
|
-
hostId: string;
|
|
98
|
-
};
|
|
99
|
-
export type TargetLookup = (args: TargetLookupArgs) => Promise<WorkspaceRelayTarget | undefined>;
|
|
100
|
-
export type CachedTargetOptions = {
|
|
101
|
-
/** TTL in milliseconds for positive target responses. Defaults to 5_000. */
|
|
102
|
-
ttlMs?: number;
|
|
103
|
-
/** Clock injection for tests. Defaults to `Date.now`. */
|
|
104
|
-
now?: () => number;
|
|
105
|
-
};
|
|
4
|
+
import { type WorkspaceRelayTarget } from "./relay-target";
|
|
5
|
+
import type { HostGenerationLookup, RuntimeAccessTokenActiveResult } from "./resolver-clients";
|
|
6
|
+
export declare function hostTunnelIncumbentOutranks(incumbentGeneration: number, candidateGeneration: number): boolean;
|
|
106
7
|
export type WorkspaceRelayAuditEvent = {
|
|
107
|
-
action: "relay.request.accepted" | "relay.request.denied" | "relay.request.suppressed_summary"
|
|
8
|
+
action: "relay.request.accepted" | "relay.request.denied" | "relay.request.suppressed_summary";
|
|
108
9
|
result: "allow" | "deny";
|
|
109
10
|
reason?: string;
|
|
110
11
|
actorId?: string;
|
|
@@ -125,10 +26,8 @@ export type WorkspaceRelayAuditEvent = {
|
|
|
125
26
|
export type WorkspaceRelayAuthOptions = {
|
|
126
27
|
runtimeAccessKey: RelayKey;
|
|
127
28
|
/**
|
|
128
|
-
* Serving-generation fence for host tunnels.
|
|
129
|
-
*
|
|
130
|
-
* a generation are admitted newest-wins, tokens with one are refused
|
|
131
|
-
* `host_generation_unverifiable`.
|
|
29
|
+
* Serving-generation fence for host tunnels. A relay composed without it
|
|
30
|
+
* refuses every host tunnel `host_generation_unverifiable`.
|
|
132
31
|
*/
|
|
133
32
|
resolveHostGeneration?: HostGenerationLookup;
|
|
134
33
|
/**
|
|
@@ -159,19 +58,11 @@ export type WorkspaceRelayAuthOptions = {
|
|
|
159
58
|
export type WorkspaceRelayHostTokenOptions = {
|
|
160
59
|
relayHostSigningKey: CryptoKey | Uint8Array;
|
|
161
60
|
relayHostAlgorithm: RelayJwtAlgorithm;
|
|
162
|
-
/**
|
|
163
|
-
* Public keys to publish at `/.well-known/jwks.json`. The first entry is the
|
|
164
|
-
* "current" mint key; any further entries are "next" keys (for rolling
|
|
165
|
-
* rotation). Verifiers (workspace-runtime → workspace-host-service) consume
|
|
166
|
-
* this set via `jose.createRemoteJWKSet`. Omit to disable the endpoint
|
|
167
|
-
* (during rollout / for tests that do not need JWKS).
|
|
168
|
-
*/
|
|
169
|
-
relayHostPublicKeys?: RelayHostPublicKey[];
|
|
170
61
|
/**
|
|
171
62
|
* `kid` to embed in the protected header of freshly minted RHTs. When
|
|
172
63
|
* provided, verifiers can dispatch on `kid` to pick the matching key from
|
|
173
|
-
* the published JWKS
|
|
174
|
-
*
|
|
64
|
+
* the published JWKS, so it must be the `kid` of the current key the Worker
|
|
65
|
+
* publishes at `/.well-known/jwks.json`.
|
|
175
66
|
*/
|
|
176
67
|
relayHostMintKid?: string;
|
|
177
68
|
/**
|
|
@@ -187,25 +78,15 @@ export type WorkspaceRelayRoutingOptions = {
|
|
|
187
78
|
fetch?: typeof fetch;
|
|
188
79
|
forwardTimeoutMs?: number;
|
|
189
80
|
directory?: WorkspaceRelayDirectory;
|
|
190
|
-
allowedOrigins?: string[];
|
|
191
|
-
};
|
|
192
|
-
export type WorkspaceRelayDrainOptions = {
|
|
193
|
-
/**
|
|
194
|
-
* When this returns true the relay reports unhealthy via `/health` and
|
|
195
|
-
* fast-paths every workspace request to a 503 (`relay_draining`) before
|
|
196
|
-
* doing any auth or upstream work. Wired by `createWorkspaceRelayBun` to
|
|
197
|
-
* the drain controller it exposes.
|
|
198
|
-
*/
|
|
199
|
-
isDraining?: () => boolean;
|
|
200
81
|
};
|
|
201
82
|
export type WorkspaceRelayTelemetryOptions = {
|
|
202
83
|
audit?: (event: WorkspaceRelayAuditEvent) => void | Promise<void>;
|
|
203
84
|
/**
|
|
204
85
|
* Probability (0.0 – 1.0) at which `relay.request.accepted` audit
|
|
205
|
-
* events are emitted to the user `audit` callback. Denies
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
86
|
+
* events are emitted to the user `audit` callback. Denies are never
|
|
87
|
+
* sampled. When unset, defaults to 1.0 (emit every accept). Suppressed
|
|
88
|
+
* accepts are aggregated into a periodic `relay.request.suppressed_summary`
|
|
89
|
+
* event (see `auditFlushIntervalMs`).
|
|
209
90
|
*/
|
|
210
91
|
auditAcceptSampleRate?: number;
|
|
211
92
|
/**
|
|
@@ -216,74 +97,12 @@ export type WorkspaceRelayTelemetryOptions = {
|
|
|
216
97
|
*/
|
|
217
98
|
auditFlushIntervalMs?: number;
|
|
218
99
|
/**
|
|
219
|
-
* Random source for
|
|
220
|
-
* for deterministic tests.
|
|
100
|
+
* Random source for audit and trace sampling. Defaults to `Math.random`.
|
|
101
|
+
* Override for deterministic tests.
|
|
221
102
|
*/
|
|
222
103
|
random?: () => number;
|
|
223
104
|
};
|
|
224
|
-
export type
|
|
225
|
-
/**
|
|
226
|
-
* Bearer token required to access `GET /metrics` from non-loopback
|
|
227
|
-
* remotes. When unset, the endpoint allows loopback callers only (using
|
|
228
|
-
* `metricsRemoteAddress` to identify the caller). When set, every request
|
|
229
|
-
* must present `Authorization: Bearer <metricsToken>` regardless of origin.
|
|
230
|
-
* Wired from `CLAXEDO_RELAY_METRICS_TOKEN` in main.ts.
|
|
231
|
-
*/
|
|
232
|
-
metricsToken?: string;
|
|
233
|
-
/**
|
|
234
|
-
* Returns the remote IP of the inbound `/metrics` request (used to
|
|
235
|
-
* gate access to the loopback when `metricsToken` is unset). When this is
|
|
236
|
-
* also unset, `/metrics` fails closed. The Bun adapter wires this resolver
|
|
237
|
-
* through `Bun.Server.requestIP`; other callers should set either this or
|
|
238
|
-
* `metricsToken`.
|
|
239
|
-
*/
|
|
240
|
-
metricsRemoteAddress?: (request: Request) => string | undefined;
|
|
241
|
-
/**
|
|
242
|
-
* Optional providers used to assemble the `/metrics` response body. The
|
|
243
|
-
* fragmentation/slow-consumer counters live in the bun adapter's per-handler
|
|
244
|
-
* telemetry, and the in-flight pending count lives on the drain controller.
|
|
245
|
-
* `createWorkspaceRelayBun` wires these to make ops counters visible on
|
|
246
|
-
* `/metrics` without giving `server.ts` a hard dependency on bun-only state.
|
|
247
|
-
*/
|
|
248
|
-
metricsSources?: WorkspaceRelayMetricsSources;
|
|
249
|
-
};
|
|
250
|
-
export type WorkspaceRelayOptions = WorkspaceRelayAuthOptions & WorkspaceRelayHostTokenOptions & WorkspaceRelayRoutingOptions & WorkspaceRelayDrainOptions & WorkspaceRelayTelemetryOptions & WorkspaceRelayMetricsOptions;
|
|
251
|
-
export type WorkspaceRelayMetricsSources = {
|
|
252
|
-
fragmentation?: () => {
|
|
253
|
-
fragmentsBuffered: number;
|
|
254
|
-
oversizedClosed: number;
|
|
255
|
-
};
|
|
256
|
-
slowConsumer?: () => {
|
|
257
|
-
overflowEvents: number;
|
|
258
|
-
timerFired: number;
|
|
259
|
-
droppedRequests: number;
|
|
260
|
-
};
|
|
261
|
-
drainPending?: () => number;
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* Shape of the JSON body returned by `GET /metrics`. Exported so the
|
|
265
|
-
* bun adapter and ops dashboards can type-narrow against it.
|
|
266
|
-
*/
|
|
267
|
-
export type WorkspaceRelayMetrics = {
|
|
268
|
-
fragmentation: {
|
|
269
|
-
fragmentsBuffered: number;
|
|
270
|
-
oversizedClosed: number;
|
|
271
|
-
};
|
|
272
|
-
slowConsumer: {
|
|
273
|
-
overflowEvents: number;
|
|
274
|
-
timerFired: number;
|
|
275
|
-
droppedRequests: number;
|
|
276
|
-
};
|
|
277
|
-
directory: {
|
|
278
|
-
activeHostCount: number;
|
|
279
|
-
};
|
|
280
|
-
audit: {
|
|
281
|
-
suppressedAccepts: number;
|
|
282
|
-
};
|
|
283
|
-
drain?: {
|
|
284
|
-
pendingCount: number;
|
|
285
|
-
};
|
|
286
|
-
};
|
|
105
|
+
export type WorkspaceRelayOptions = WorkspaceRelayAuthOptions & WorkspaceRelayHostTokenOptions & WorkspaceRelayRoutingOptions & WorkspaceRelayTelemetryOptions;
|
|
287
106
|
export type AuthorizedWorkspaceRelayRequest = {
|
|
288
107
|
claims: RuntimeAccessTokenClaims;
|
|
289
108
|
target: WorkspaceRelayTarget;
|
|
@@ -293,29 +112,12 @@ export type AuthorizedWorkspaceRelayRequest = {
|
|
|
293
112
|
export type WorkspaceRelayAuthorizeTrace = {
|
|
294
113
|
span<T>(name: string, run: () => Promise<T>): Promise<T>;
|
|
295
114
|
};
|
|
296
|
-
export
|
|
297
|
-
name: string;
|
|
298
|
-
ms: number;
|
|
299
|
-
};
|
|
300
|
-
export type WorkspaceRelayTrace = WorkspaceRelayAuthorizeTrace & {
|
|
301
|
-
phases: WorkspaceRelayTracePhase[];
|
|
302
|
-
};
|
|
303
|
-
/**
|
|
304
|
-
* Wraps a revocation lookup with a small in-memory cache keyed by `jti`.
|
|
305
|
-
* Both positive and negative results are cached for the full TTL; revocation
|
|
306
|
-
* lag is bounded by the same TTL by design. Concurrent misses share one lookup.
|
|
307
|
-
*/
|
|
308
|
-
export declare function createCachedRevocationClient(inner: RevocationLookup, options?: CachedRevocationOptions): RevocationLookup;
|
|
115
|
+
export declare const RUNTIME_ACCESS_TOKEN_ACTIVE_CHECK_INTERVAL_MS_DEFAULT = 30000;
|
|
309
116
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* holding a HIGHER generation than the cache — or than the answer an in-flight
|
|
314
|
-
* lookup settles on — forces a refresh so a fresh `acquire` is never refused
|
|
315
|
-
* on a stale answer. Unknown enrollments and failures are not cached;
|
|
316
|
-
* concurrent misses share one lookup.
|
|
117
|
+
* `retryable: false` means the control plane refused this generation, so the
|
|
118
|
+
* host must not simply reconnect with it; `retryable: true` means the control
|
|
119
|
+
* plane could not be asked, and a reconnect is the recovery.
|
|
317
120
|
*/
|
|
318
|
-
export declare function createCachedHostGenerationClient(inner: HostGenerationLookup, options?: CachedHostGenerationOptions): HostGenerationLookup;
|
|
319
121
|
export type HostTunnelGenerationDecision = {
|
|
320
122
|
ok: true;
|
|
321
123
|
} | {
|
|
@@ -329,36 +131,16 @@ export type HostTunnelGenerationDecision = {
|
|
|
329
131
|
code: "host_generation_lookup_unavailable";
|
|
330
132
|
reason: string;
|
|
331
133
|
};
|
|
332
|
-
/**
|
|
333
|
-
* The one admission/re-check verdict both relay adapters apply to a host
|
|
334
|
-
* tunnel, on connect and on every registration update. A token without a
|
|
335
|
-
* generation is always `ok` — that is the pre-fence behaviour desktop and
|
|
336
|
-
* self-hosted relays keep. A token WITH a generation asserts a fence, so a
|
|
337
|
-
* relay composed without a resolver refuses it rather than admit what it
|
|
338
|
-
* cannot verify; that refusal is not retryable because the resolver is a
|
|
339
|
-
* property of the composition, not of the moment. `retryable` otherwise
|
|
340
|
-
* separates "the control plane said no" (the host must not simply reconnect)
|
|
341
|
-
* from "the control plane could not be asked" (it should).
|
|
342
|
-
*/
|
|
343
134
|
export declare function checkHostTunnelGeneration(lookup: HostGenerationLookup | undefined, claims: Pick<HostTunnelTokenClaims, "enrollment_id" | "generation">): Promise<HostTunnelGenerationDecision>;
|
|
344
|
-
/**
|
|
345
|
-
* Caches positive workspace target lookups by workspace+host. Missing targets
|
|
346
|
-
* are only coalesced while in flight, not retained, so cold-start polling can
|
|
347
|
-
* see readiness as soon as the control plane records it.
|
|
348
|
-
*/
|
|
349
|
-
export declare function createCachedTargetClient(inner: TargetLookup, options?: CachedTargetOptions): TargetLookup;
|
|
350
135
|
export type WorkspaceRelayForwardHeadersOptions = {
|
|
351
136
|
/**
|
|
352
|
-
* When true, also strip the `Cookie` request header.
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* may legitimately be needed (e.g. workspace dashboards), so the default
|
|
358
|
-
* is to not strip cookies.
|
|
137
|
+
* When true, also strip the `Cookie` request header. A host tunnel ends on a
|
|
138
|
+
* machine somebody uses, whose cookie jar the browser may share (localhost
|
|
139
|
+
* dev tooling); passing cookies verbatim risks leaking session data to it. A
|
|
140
|
+
* cloud-vm sits behind a dedicated network boundary where a session cookie
|
|
141
|
+
* may legitimately be needed, so the default is to not strip cookies.
|
|
359
142
|
*/
|
|
360
|
-
|
|
361
|
-
upstreamHeaders?: Record<string, string>;
|
|
143
|
+
hostTunnel?: boolean;
|
|
362
144
|
};
|
|
363
145
|
export type WorkspaceRelayForwardRequestInitOptions = WorkspaceRelayForwardHeadersOptions & {
|
|
364
146
|
signal?: AbortSignal;
|
|
@@ -366,31 +148,6 @@ export type WorkspaceRelayForwardRequestInitOptions = WorkspaceRelayForwardHeade
|
|
|
366
148
|
export declare function workspaceRelayTargetUrl(target: WorkspaceRelayTarget, path: string, search: string): URL;
|
|
367
149
|
export declare function workspaceRelayForwardHeaders(input: Headers, relayHostToken: string, workspaceId: string, options?: WorkspaceRelayForwardHeadersOptions): Headers;
|
|
368
150
|
export declare function workspaceRelayForwardRequestInit(request: Request, relayHostToken: string, workspaceId: string, options?: WorkspaceRelayForwardRequestInitOptions): RequestInit;
|
|
369
|
-
/**
|
|
370
|
-
* Dispose the sampler's periodic flush timer for a given options object.
|
|
371
|
-
* Tests call this in cleanup so the bun process can exit.
|
|
372
|
-
*/
|
|
373
|
-
export declare function disposeAuditSampler(options: WorkspaceRelayOptions): void;
|
|
374
|
-
export declare function createWorkspaceRelayTrace(): WorkspaceRelayTrace;
|
|
375
|
-
export declare function workspaceRelayServerTiming(trace: WorkspaceRelayTrace): string;
|
|
376
|
-
export declare function workspaceRelayTimingResponse(response: Response, trace: WorkspaceRelayTrace): Response;
|
|
377
|
-
/**
|
|
378
|
-
* Request headers a browser may send to the relay, for every adapter.
|
|
379
|
-
*
|
|
380
|
-
* ONE list, because there were three identical copies — `server.ts`,
|
|
381
|
-
* `cloudflare.ts` and `bun.ts` — and three copies of a list means the next
|
|
382
|
-
* header is added to two of them. A header missing from the copy that serves a
|
|
383
|
-
* given deployment does not degrade anything gracefully: the browser refuses to
|
|
384
|
-
* send the request at all, and the failure surfaces somewhere else entirely.
|
|
385
|
-
* The control plane's equivalent list is `BROWSER_ALLOWED_REQUEST_HEADERS` in
|
|
386
|
-
* `claxedo-server-core`; a header the app sends to BOTH must be in both.
|
|
387
|
-
*
|
|
388
|
-
* `Traceparent`/`Tracestate` are W3C Trace Context, carried from the browser
|
|
389
|
-
* through this relay to the laptop.
|
|
390
|
-
*/
|
|
391
|
-
export declare const RELAY_ALLOWED_REQUEST_HEADER_LIST: readonly ["Accept", "Authorization", "Content-Type", "Last-Event-ID", "Traceparent", "Tracestate", "X-Fetch-Bypass-Throttle", "X-Daytona-Skip-Preview-Warning", "X-Workspace-Id", "X-OpenCode-Directory", "X-Claxedo-Runner", "X-Claxedo-Model", "X-Claxedo-Draft-Id", "X-Claxedo-Binary"];
|
|
392
|
-
/** The same list as a header value, for the adapters that write it directly. */
|
|
393
|
-
export declare const RELAY_ALLOWED_REQUEST_HEADERS: string;
|
|
394
151
|
export declare function authorizeWorkspaceRelayRequest(options: WorkspaceRelayOptions, request: Request, workspaceId: string, trace?: WorkspaceRelayAuthorizeTrace): Promise<{
|
|
395
152
|
ok: true;
|
|
396
153
|
request: AuthorizedWorkspaceRelayRequest;
|
|
@@ -399,19 +156,3 @@ export declare function authorizeWorkspaceRelayRequest(options: WorkspaceRelayOp
|
|
|
399
156
|
response: Response;
|
|
400
157
|
code: string;
|
|
401
158
|
}>;
|
|
402
|
-
export declare function forwardWorkspaceRelayRequest(request: Request, target: WorkspaceRelayTarget, relayHostToken: string, path: string, requestFetch: typeof fetch, timeoutMs: number, trace?: WorkspaceRelayAuthorizeTrace, originAllowed?: (origin: string) => boolean): Promise<Response>;
|
|
403
|
-
export type WorkspaceRelayApp = Hono & {
|
|
404
|
-
/**
|
|
405
|
-
* Clear the sampler's periodic flush timer. Tests must call this so
|
|
406
|
-
* `setInterval` doesn't keep the process alive after the test exits.
|
|
407
|
-
*/
|
|
408
|
-
disposeAuditSampler(): void;
|
|
409
|
-
};
|
|
410
|
-
/**
|
|
411
|
-
* Read the suppressed-accept counter for a given options object. Surfaced
|
|
412
|
-
* for the `/metrics` route handler. Returns 0 if no sampler state has been
|
|
413
|
-
* created yet (no requests have been audit-sampled), which is the same value
|
|
414
|
-
* an external observer would see.
|
|
415
|
-
*/
|
|
416
|
-
export declare function getAuditSuppressedCount(options: WorkspaceRelayOptions): number;
|
|
417
|
-
export declare function createWorkspaceRelay(options: WorkspaceRelayOptions): WorkspaceRelayApp;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RelayBacking } from "./auth";
|
|
2
|
+
import { SESSION_HOST_LOCATION_HINT } from "./relay-location";
|
|
3
|
+
import { type AuthorizedWorkspaceRelayRequest } from "./server";
|
|
4
|
+
/** The `SESSION_HOST` binding: one Durable Object per top-level session, named by that session's id. */
|
|
5
|
+
export type SessionHostNamespace = {
|
|
6
|
+
getByName: (name: string, options?: {
|
|
7
|
+
locationHint?: typeof SESSION_HOST_LOCATION_HINT;
|
|
8
|
+
}) => {
|
|
9
|
+
fetch: (input: string, init?: RequestInit) => Promise<Response>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function isSessionHostTarget(target: {
|
|
13
|
+
backing: RelayBacking;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export declare function isSessionHostNamespace(value: unknown): value is SessionHostNamespace;
|
|
16
|
+
/**
|
|
17
|
+
* Forwards one HTTP request to the session's Durable Object with the Relay
|
|
18
|
+
* Host Token the authorization minted. The browser's cookies never reach it,
|
|
19
|
+
* as they never reach a host tunnel, and the response body streams through
|
|
20
|
+
* untouched so a session's event stream stays open.
|
|
21
|
+
*/
|
|
22
|
+
export declare function forwardSessionHostHttp(namespace: SessionHostNamespace | undefined, request: Request, authorized: AuthorizedWorkspaceRelayRequest): Promise<Response>;
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type WorkspaceRelayDurableObjectNamespace, type WorkspaceRelayDurableObjectRoomOptions, type WorkspaceRelayDurableObjectSocket } from "./cloudflare";
|
|
2
|
-
import
|
|
3
|
-
import { type HostGenerationLookup } from "./
|
|
2
|
+
import { type WorkspaceRelayTarget } from "./relay-target";
|
|
3
|
+
import { type HostGenerationLookup, type RuntimeAccessTokenActiveResult } from "./resolver-clients";
|
|
4
4
|
type WorkspaceRelayWorkerBindings = {
|
|
5
5
|
WORKSPACE_RELAY_ROOM?: WorkspaceRelayDurableObjectNamespace;
|
|
6
6
|
CLAXEDO_RELAY_RESOLVER_URL?: string;
|
|
@@ -31,7 +31,7 @@ type WorkspaceRelayWorkerBindings = {
|
|
|
31
31
|
};
|
|
32
32
|
export type WorkspaceRelayWorkerEnv = Record<string, unknown> & WorkspaceRelayWorkerBindings;
|
|
33
33
|
type ResolverClient = {
|
|
34
|
-
target(workspaceId: string, hostId: string): Promise<WorkspaceRelayTarget | undefined>;
|
|
34
|
+
target(workspaceId: string, hostId: string, routingId?: string): Promise<WorkspaceRelayTarget | undefined>;
|
|
35
35
|
revocation(args: {
|
|
36
36
|
jti: string;
|
|
37
37
|
workspaceId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
|
-
"description": "Relay/tunnel
|
|
3
|
+
"description": "Relay/tunnel that routes authenticated traffic to workspace runtimes on Cloudflare: JWT workspace tokens and a header-stripping trust boundary",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,18 +22,8 @@
|
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"import": "./dist/index.mjs",
|
|
24
24
|
"default": "./dist/index.mjs"
|
|
25
|
-
},
|
|
26
|
-
"./bun": {
|
|
27
|
-
"development": "./src/bun.ts",
|
|
28
|
-
"bun": "./src/bun.ts",
|
|
29
|
-
"types": "./dist/bun.d.ts",
|
|
30
|
-
"import": "./dist/bun.mjs",
|
|
31
|
-
"default": "./dist/bun.mjs"
|
|
32
25
|
}
|
|
33
26
|
},
|
|
34
|
-
"bin": {
|
|
35
|
-
"workspace-relay": "dist/main.mjs"
|
|
36
|
-
},
|
|
37
27
|
"files": [
|
|
38
28
|
"README.md",
|
|
39
29
|
"LICENSE",
|
|
@@ -43,35 +33,26 @@
|
|
|
43
33
|
"access": "public"
|
|
44
34
|
},
|
|
45
35
|
"name": "@claxedo/workspace-relay",
|
|
46
|
-
"version": "0.
|
|
36
|
+
"version": "0.10.1",
|
|
47
37
|
"scripts": {
|
|
48
|
-
"start": "bun run src/main.ts",
|
|
49
|
-
"dev": "bun run --hot src/main.ts",
|
|
50
38
|
"build": "tsx scripts/build.ts",
|
|
51
39
|
"prepublishOnly": "npm run build",
|
|
52
40
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
53
|
-
"
|
|
54
|
-
"test": "mkdir -p .artifacts/unit && node ../../scripts/test-deadline.mjs bun test src bench --path-ignore-patterns='**/relay-workerd-*' --path-ignore-patterns='**/bun.test.ts' --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml && node ../../scripts/test-deadline.mjs bun test src/bun.test.ts --timeout 120000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-bun-adapter.xml && node ../../scripts/test-deadline.mjs bun test src/relay-workerd-backpressure.test.ts src/relay-workerd-binary.test.ts --timeout 240000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-workerd.xml",
|
|
55
|
-
"bench:gate": "bun bench/local-dry-run.ts",
|
|
56
|
-
"bench:gate:cf": "bun bench/cf-dev-smoke.ts"
|
|
41
|
+
"test": "mkdir -p .artifacts/unit && node ../../script/test-deadline.mjs bun test src scripts --path-ignore-patterns='**/relay-workerd-*' --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml && node ../../script/test-deadline.mjs bun test src/relay-workerd-backpressure.test.ts src/relay-workerd-binary.test.ts src/relay-workerd-close.test.ts src/relay-workerd-session-host.test.ts --timeout 240000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-workerd.xml"
|
|
57
42
|
},
|
|
58
43
|
"dependencies": {
|
|
59
|
-
"@claxedo/helpers": "0.1
|
|
60
|
-
"@claxedo/workspace-relay-protocol": "0.
|
|
61
|
-
"
|
|
62
|
-
"jose": "6.0.11",
|
|
63
|
-
"posthog-node": "4.18.0"
|
|
44
|
+
"@claxedo/helpers": "0.2.1",
|
|
45
|
+
"@claxedo/workspace-relay-protocol": "0.10.1",
|
|
46
|
+
"jose": "6.0.11"
|
|
64
47
|
},
|
|
65
48
|
"devDependencies": {
|
|
66
|
-
"@claxedo/sandbox-manager": "0.9.0",
|
|
67
|
-
"@daytona/sdk": "0.192.0",
|
|
68
49
|
"@tsconfig/node-lts": "22.0.4",
|
|
69
50
|
"@types/bun": "1.3.13",
|
|
70
51
|
"esbuild": "0.25.12",
|
|
71
|
-
"miniflare": "4.
|
|
52
|
+
"miniflare": "4.20260730.0",
|
|
72
53
|
"tsx": "4.22.3",
|
|
73
54
|
"typescript": "catalog:",
|
|
74
|
-
"wrangler": "4.
|
|
55
|
+
"wrangler": "4.116.0",
|
|
75
56
|
"@cloudflare/workers-types": "catalog:"
|
|
76
57
|
}
|
|
77
58
|
}
|