@claxedo/workspace-relay 0.10.2 → 0.10.3
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 +68 -19
- package/dist/auth.d.ts +10 -12
- package/dist/cloudflare.d.ts +11 -30
- package/dist/host-tunnel-forwarding.d.ts +0 -2
- package/dist/http.d.ts +0 -2
- package/dist/index.mjs +533 -364
- package/dist/relay-bytes.d.ts +10 -0
- package/dist/relay-location.d.ts +19 -10
- package/dist/relay-log.d.ts +61 -0
- package/dist/relay-trace.d.ts +5 -0
- package/dist/resolver-clients.d.ts +19 -4
- package/dist/server.d.ts +6 -14
- package/dist/socket-frames.d.ts +47 -0
- package/dist/stream-standing.d.ts +24 -0
- package/dist/worker.d.ts +35 -15
- package/package.json +3 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exported for `relay-base64.test.ts`, which proves byte-equality against a
|
|
3
|
+
* per-byte reference across the chunk boundaries. This sits on the path every
|
|
4
|
+
* frame pays, so its equivalence is worth asserting directly rather than
|
|
5
|
+
* inferring from a round-trip.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
8
|
+
export declare function base64ToBytes(input: string): Uint8Array<ArrayBuffer>;
|
|
9
|
+
export declare function concatBytes(chunks: Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
10
|
+
export declare function readRequestBodyCapped(request: Request, maxBytes: number): Promise<"too_large" | Uint8Array<ArrayBuffer>>;
|
package/dist/relay-location.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare const DEFAULT_RELAY_LOCATION_HINT = "apac";
|
|
|
12
12
|
*
|
|
13
13
|
* The room sits on the path of every request to the object and asks the
|
|
14
14
|
* control plane (IAD) for the token's revocation and the target on a cold
|
|
15
|
-
* start and every
|
|
15
|
+
* start and every 30 s: from apac each lookup is ~220 ms, from enam ~10 ms,
|
|
16
16
|
* while the browser's path through the room is the same ~115 ms either way
|
|
17
17
|
* (docs/perf/pi-durable-latency-2026-10-18/README.md). A machine's room stays
|
|
18
18
|
* beside its machine, where those lookups would cost a browser in apac
|
|
@@ -28,7 +28,6 @@ export declare const SESSION_HOST_LOCATION_HINT = "enam";
|
|
|
28
28
|
*/
|
|
29
29
|
export declare const RELAY_LOCATION_HINTS: readonly ["wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me"];
|
|
30
30
|
export type RelayLocationHint = typeof RELAY_LOCATION_HINTS[number];
|
|
31
|
-
/** A workspace region carried on the request, if the caller supplied one. */
|
|
32
31
|
export declare const RELAY_REGION_HEADER = "x-claxedo-workspace-region";
|
|
33
32
|
/**
|
|
34
33
|
* Chooses the location hint for a workspace's Durable Object.
|
|
@@ -40,11 +39,13 @@ export declare const RELAY_REGION_HEADER = "x-claxedo-workspace-region";
|
|
|
40
39
|
* passed through or guessed at.
|
|
41
40
|
*
|
|
42
41
|
* Precedence, most authoritative first:
|
|
43
|
-
* 1. the workspace's own region (header or `?region=`)
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* 2. the requesting user's Cloudflare edge country
|
|
47
|
-
*
|
|
42
|
+
* 1. the workspace's own region (header or `?region=`). The caller passes it
|
|
43
|
+
* only where the region is a fact about the runtime: see
|
|
44
|
+
* `RelayPlacementCredential.declaresRegion`;
|
|
45
|
+
* 2. the requesting user's Cloudflare edge country. A control-plane Worker
|
|
46
|
+
* calling on a person's behalf carries that person's country, not its own
|
|
47
|
+
* (measured: a Worker placed in IAD, called from India, reaches the next
|
|
48
|
+
* Worker with `cf.country` IN);
|
|
48
49
|
* 3. the deployment-wide configured hint (`CLAXEDO_RELAY_LOCATION_HINT`), then
|
|
49
50
|
* `DEFAULT_RELAY_LOCATION_HINT`.
|
|
50
51
|
*/
|
|
@@ -53,7 +54,6 @@ export declare function relayLocationHint(input: {
|
|
|
53
54
|
region?: string;
|
|
54
55
|
country?: string;
|
|
55
56
|
}): string;
|
|
56
|
-
/** The workspace region a request declares, if any. */
|
|
57
57
|
export declare function relayRequestRegion(request: Request): string | undefined;
|
|
58
58
|
export type RelayRoomPlacement = {
|
|
59
59
|
locationHint: string;
|
|
@@ -66,9 +66,18 @@ export type RelayRoomPlacement = {
|
|
|
66
66
|
* room's workspace: the host it reaches. A `session-do:` host is the control
|
|
67
67
|
* plane's signed statement that the workspace's sessions run on their own
|
|
68
68
|
* Durable Objects.
|
|
69
|
+
*
|
|
70
|
+
* `declaresRegion` says whether a region on the request is a fact about the
|
|
71
|
+
* runtime. It is for a cloud runtime, whose token carries the routing id the
|
|
72
|
+
* control plane assigned with its sandbox, and for a machine's own tunnel
|
|
73
|
+
* dial. It is not for anyone else's request to a machine: a machine workspace
|
|
74
|
+
* has no home region, the control plane fills the column with the deployment
|
|
75
|
+
* default (`us-east`), and a room placed by that default sits 228 ms from a
|
|
76
|
+
* machine and owner who are both in India, for good.
|
|
69
77
|
*/
|
|
70
78
|
export type RelayPlacementCredential = {
|
|
71
79
|
hostId: string;
|
|
80
|
+
declaresRegion: boolean;
|
|
72
81
|
};
|
|
73
82
|
/**
|
|
74
83
|
* Where a workspace's room is created, decided from what the request declares
|
|
@@ -83,11 +92,11 @@ export type RelayPlacementCredential = {
|
|
|
83
92
|
* drag the room elsewhere, and a forged token verifies for no workspace.
|
|
84
93
|
* Request-declared inputs (region, edge country) move the hint off the
|
|
85
94
|
* configured floor only when the credential verifies, so anonymous first-touch
|
|
86
|
-
* traffic cannot pin a workspace's room placement.
|
|
95
|
+
* traffic cannot pin a workspace's room placement. The region counts only from
|
|
96
|
+
* a credential that `declaresRegion`; the edge country counts from any.
|
|
87
97
|
*/
|
|
88
98
|
export declare function relayRoomPlacement(request: Request, input: {
|
|
89
99
|
configured?: string;
|
|
90
100
|
credential: () => Promise<RelayPlacementCredential | undefined>;
|
|
91
101
|
}): Promise<RelayRoomPlacement>;
|
|
92
|
-
/** The Cloudflare edge country for the request, when running on Cloudflare. */
|
|
93
102
|
export declare function relayRequestCountry(request: Request): string | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the relay records about a machine's tunnel. A relayed request that
|
|
3
|
+
* succeeds quickly writes nothing; these are the changes and failures that
|
|
4
|
+
* explain why a machine could not be reached.
|
|
5
|
+
*/
|
|
6
|
+
export type RelayLogEvent = {
|
|
7
|
+
event: "tunnel_admitted";
|
|
8
|
+
hostId: string;
|
|
9
|
+
workspaceIds: string[];
|
|
10
|
+
generation: number;
|
|
11
|
+
replacedAgeMs?: number;
|
|
12
|
+
} | {
|
|
13
|
+
event: "tunnel_refused";
|
|
14
|
+
hostId?: string;
|
|
15
|
+
code: string;
|
|
16
|
+
} | {
|
|
17
|
+
event: "tunnel_ended";
|
|
18
|
+
hostId: string;
|
|
19
|
+
generation: number;
|
|
20
|
+
reason: string;
|
|
21
|
+
ageMs: number;
|
|
22
|
+
pendingFailed: number;
|
|
23
|
+
channelsClosed: number;
|
|
24
|
+
closeCode?: number;
|
|
25
|
+
closeReason?: string;
|
|
26
|
+
socketError?: string;
|
|
27
|
+
} | {
|
|
28
|
+
event: "tunnel_timeout";
|
|
29
|
+
hostId: string;
|
|
30
|
+
traceId?: string;
|
|
31
|
+
requestId: string;
|
|
32
|
+
method: string;
|
|
33
|
+
path: string;
|
|
34
|
+
bodyBytes: number;
|
|
35
|
+
timeoutMs: number;
|
|
36
|
+
tunnelAgeMs: number;
|
|
37
|
+
pending: number;
|
|
38
|
+
}
|
|
39
|
+
/** The machine answered a request the relay had already timed out: it was slow, not gone. */
|
|
40
|
+
| {
|
|
41
|
+
event: "tunnel_late_response";
|
|
42
|
+
hostId: string;
|
|
43
|
+
requestId: string;
|
|
44
|
+
lateMs: number;
|
|
45
|
+
} | {
|
|
46
|
+
event: "tunnel_frame_dropped";
|
|
47
|
+
hostId: string;
|
|
48
|
+
reason: string;
|
|
49
|
+
dropped: number;
|
|
50
|
+
type?: string;
|
|
51
|
+
protocol?: string;
|
|
52
|
+
};
|
|
53
|
+
export type RelayLog = (event: RelayLogEvent) => void;
|
|
54
|
+
export declare function relayLogOf(options: {
|
|
55
|
+
relayLog?: RelayLog;
|
|
56
|
+
}): RelayLog;
|
|
57
|
+
/** A machine speaking another protocol drops every frame, so only the first few and then each hundredth are written. */
|
|
58
|
+
export declare function frameDropWorthLogging(dropped: number): boolean;
|
|
59
|
+
/** How many timed-out request ids a tunnel remembers so a late answer can be told from an unknown one. */
|
|
60
|
+
export declare const TIMED_OUT_REQUESTS_REMEMBERED = 32;
|
|
61
|
+
export declare function rememberTimedOut(timedOut: Map<string, number>, requestId: string, at: number): void;
|
package/dist/relay-trace.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type WorkspaceRelayTraceLogEvent = {
|
|
|
19
19
|
export type RelayTraceSampling = {
|
|
20
20
|
traceSampleRate?: number;
|
|
21
21
|
traceForceHeaderSecret?: string;
|
|
22
|
+
/** The data center the room runs in once it is known; asked on a sampled trace only. */
|
|
23
|
+
roomColo?: () => string | undefined;
|
|
22
24
|
random?: () => number;
|
|
23
25
|
};
|
|
24
26
|
export type RelayTrace = {
|
|
@@ -30,6 +32,7 @@ export type RelayTrace = {
|
|
|
30
32
|
name: string;
|
|
31
33
|
ms: number;
|
|
32
34
|
}>;
|
|
35
|
+
roomColo?: string;
|
|
33
36
|
};
|
|
34
37
|
export declare function traceRequest(request: Request, routeKind: string, options: RelayTraceSampling): RelayTrace;
|
|
35
38
|
export declare function traceSpan<T>(trace: RelayTrace | undefined, name: string, fn: () => Promise<T>): Promise<T>;
|
|
@@ -49,6 +52,8 @@ export declare function webSocketTraceFrame(upstreamStartedAt: number, queueTime
|
|
|
49
52
|
maxQueuedDelayMs: number;
|
|
50
53
|
};
|
|
51
54
|
export declare function withTraceHeaders(response: Response, trace: RelayTrace): Response;
|
|
55
|
+
/** A request that fails or takes longer than this is written whether or not its trace was sampled. */
|
|
56
|
+
export declare const SLOW_REQUEST_MS = 5000;
|
|
52
57
|
export declare function logTrace(options: {
|
|
53
58
|
traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
|
|
54
59
|
}, request: Request, workspaceId: string | undefined, trace: RelayTrace, response: Response): void;
|
|
@@ -31,9 +31,8 @@ export type RevocationLookupArgs = {
|
|
|
31
31
|
};
|
|
32
32
|
export type RevocationLookup = (args: RevocationLookupArgs) => Promise<RuntimeAccessTokenActiveResult>;
|
|
33
33
|
export type CachedRevocationOptions = {
|
|
34
|
-
/** TTL in milliseconds for cached revocation responses. Defaults to
|
|
34
|
+
/** TTL in milliseconds for cached revocation responses. Defaults to 30_000. */
|
|
35
35
|
ttlMs?: number;
|
|
36
|
-
/** Clock injection for tests. Defaults to `Date.now`. */
|
|
37
36
|
now?: () => number;
|
|
38
37
|
};
|
|
39
38
|
/**
|
|
@@ -51,7 +50,6 @@ export type HostGenerationLookup = (args: HostGenerationLookupArgs) => Promise<H
|
|
|
51
50
|
export type CachedHostGenerationOptions = {
|
|
52
51
|
/** TTL in milliseconds for cached host-generation answers. Defaults to 10_000. */
|
|
53
52
|
ttlMs?: number;
|
|
54
|
-
/** Clock injection for tests. Defaults to `Date.now`. */
|
|
55
53
|
now?: () => number;
|
|
56
54
|
};
|
|
57
55
|
export type TargetLookupArgs = {
|
|
@@ -63,12 +61,29 @@ export type TargetLookup = (args: TargetLookupArgs) => Promise<WorkspaceRelayTar
|
|
|
63
61
|
export declare function pruneExpiringCache<T extends {
|
|
64
62
|
expiresAt: number;
|
|
65
63
|
}>(cache: Map<string, T>, now: number, maxEntries: number): void;
|
|
64
|
+
/** The code of an answer that says the authority could not be asked, as opposed to one that says no. */
|
|
65
|
+
export declare const REVOCATION_RESOLVER_UNAVAILABLE = "relay_revocation_resolver_unavailable";
|
|
66
|
+
export type RuntimeAccessTokenStanding = {
|
|
67
|
+
result: RuntimeAccessTokenActiveResult;
|
|
68
|
+
checkedAt: number;
|
|
69
|
+
};
|
|
70
|
+
export type CachedRevocationLookup = RevocationLookup & {
|
|
71
|
+
/**
|
|
72
|
+
* The token's standing as the authority gave it no more than `maxAgeMs`
|
|
73
|
+
* ago, asked now when the held answer is older. A request that arrives
|
|
74
|
+
* while it is being asked still gets the held answer until that expires, and
|
|
75
|
+
* an authority that cannot be reached leaves the held answer in place.
|
|
76
|
+
*/
|
|
77
|
+
within(args: RevocationLookupArgs, maxAgeMs: number): Promise<RuntimeAccessTokenStanding>;
|
|
78
|
+
/** How long an answer is held, which is the bound on how late a revocation is seen. */
|
|
79
|
+
ttlMs: number;
|
|
80
|
+
};
|
|
66
81
|
/**
|
|
67
82
|
* Wraps a revocation lookup with a small in-memory cache keyed by `jti`.
|
|
68
83
|
* Both positive and negative results are cached for the full TTL; revocation
|
|
69
84
|
* lag is bounded by the same TTL by design. Concurrent misses share one lookup.
|
|
70
85
|
*/
|
|
71
|
-
export declare function createCachedRevocationClient(inner: RevocationLookup, options?: CachedRevocationOptions):
|
|
86
|
+
export declare function createCachedRevocationClient(inner: RevocationLookup, options?: CachedRevocationOptions): CachedRevocationLookup;
|
|
72
87
|
/**
|
|
73
88
|
* The longest a revoked Runtime Access Token can keep working after the
|
|
74
89
|
* authority records the revocation, derived from the configured TTLs rather
|
package/dist/server.d.ts
CHANGED
|
@@ -34,23 +34,15 @@ export type WorkspaceRelayAuthOptions = {
|
|
|
34
34
|
* Cache signature/introspection-verified Runtime Access Token claims by the
|
|
35
35
|
* full token string. Revocation/active checks, role enforcement, and target
|
|
36
36
|
* resolution still run for every request after this cache hit. Defaults to
|
|
37
|
-
*
|
|
37
|
+
* 30s and is always capped by the token's exp claim minus skew.
|
|
38
38
|
*/
|
|
39
39
|
runtimeAccessTokenCacheTtlMs?: number;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `resolveTarget` and the workspace/host audit fields. `runtimeAccessKey`
|
|
47
|
-
* is then only used for legacy token paths and tests that exercise the
|
|
48
|
-
* raw JWT codepath.
|
|
49
|
-
*
|
|
50
|
-
* This is the seam downstream users plug their own auth backend
|
|
51
|
-
* through (issuer-signed tokens, OIDC introspection, custom keys, etc.).
|
|
52
|
-
* See `@claxedo/workspace-relay-protocol/token-verifier` for the
|
|
53
|
-
* `HttpTokenVerifier` and `StaticTokenVerifier` reference impls.
|
|
41
|
+
* Verifies Runtime Access Tokens in place of decoding the JWT with
|
|
42
|
+
* `runtimeAccessKey`, for a deployment with its own auth backend (issuer
|
|
43
|
+
* tokens, OIDC introspection). Its `claims` drive `resolveTarget` and the
|
|
44
|
+
* audit fields. `@claxedo/workspace-relay-protocol/token-verifier` has HTTP,
|
|
45
|
+
* OIDC and static implementations.
|
|
54
46
|
*/
|
|
55
47
|
tokenVerifier?: TokenVerifier<RuntimeAccessVerifierClaims>;
|
|
56
48
|
isRuntimeAccessTokenActive?: (claims: RuntimeAccessTokenClaims) => RuntimeAccessTokenActiveResult | Promise<RuntimeAccessTokenActiveResult>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { TunnelWsFrame } from "@claxedo/workspace-relay-protocol";
|
|
2
|
+
/** What a frame awaiting the upstream socket costs the pre-open byte bound. */
|
|
3
|
+
export declare function preOpenFrameBytes(input: string | ArrayBuffer | Uint8Array): number;
|
|
4
|
+
/**
|
|
5
|
+
* Socket `data` → a tunnel `ws.frame` payload. Returns a promise only for
|
|
6
|
+
* `Blob` data, which a listener-path socket delivers for a binary frame at
|
|
7
|
+
* `compatibility_date >= 2026-03-17`, the relay's deployed date among them;
|
|
8
|
+
* string and ArrayBuffer frames stay synchronous and pay no extra microtask.
|
|
9
|
+
*/
|
|
10
|
+
export declare function socketFrame(input: unknown): Promise<{
|
|
11
|
+
binary: boolean;
|
|
12
|
+
data_base64: string;
|
|
13
|
+
}> | {
|
|
14
|
+
binary: boolean;
|
|
15
|
+
data_base64: string;
|
|
16
|
+
} | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Wraps a per-socket frame handler so frames are delivered in ARRIVAL order
|
|
19
|
+
* even when some of them resolve asynchronously.
|
|
20
|
+
*
|
|
21
|
+
* `socketPayload` is synchronous for string/ArrayBuffer/view data and only
|
|
22
|
+
* returns a promise for `Blob`. Awaiting that promise inline inside the
|
|
23
|
+
* listener would let the next frame — resolved synchronously — overtake it,
|
|
24
|
+
* silently reordering an ordered byte stream: a terminal would render its
|
|
25
|
+
* output scrambled. So the first pending Blob installs a tail promise and every
|
|
26
|
+
* later frame chains onto it until the chain drains, at which point the
|
|
27
|
+
* synchronous fast path resumes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function orderedFrameDelivery(deliver: (payload: string | ArrayBuffer | Uint8Array) => void): (data: unknown) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Runs the two frame decoders over one runtime-delivered `event.data` and
|
|
32
|
+
* reports what they produced.
|
|
33
|
+
*
|
|
34
|
+
* Exists so `relay-workerd-binary.test.ts` can assert the decoders against what
|
|
35
|
+
* REAL workerd delivers. The rest of the suite drives hand-rolled socket
|
|
36
|
+
* doubles, which by construction hand us whatever we chose to hand ourselves —
|
|
37
|
+
* so they cannot catch the hazard that binary `event.data` becomes a `Blob` at
|
|
38
|
+
* `compatibility_date >= 2026-03-17`. Test-support surface, not a relay path.
|
|
39
|
+
*/
|
|
40
|
+
export declare function relayFrameDiagnostics(data: unknown): Promise<{
|
|
41
|
+
frameDropped: boolean;
|
|
42
|
+
payloadDropped: boolean;
|
|
43
|
+
binary?: boolean | undefined;
|
|
44
|
+
dataBase64?: string | undefined;
|
|
45
|
+
payloadBase64: string | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function tunnelFrame(input: TunnelWsFrame): string | Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RuntimeAccessTokenClaims } from "./auth";
|
|
2
|
+
import { type RuntimeAccessTokenStanding } from "./resolver-clients";
|
|
3
|
+
export type StreamStandingOptions = {
|
|
4
|
+
/** The token's standing no older than `maxAgeMs`; absent, a deployment has no revocation authority and streams are left alone. */
|
|
5
|
+
standing?: (claims: RuntimeAccessTokenClaims, maxAgeMs: number) => Promise<RuntimeAccessTokenStanding>;
|
|
6
|
+
/** The bound a request's own check keeps: no answer older than this lets bytes through. */
|
|
7
|
+
boundMs: number;
|
|
8
|
+
/** Consecutive failures to reach the authority a stream survives; `exp` still ends it. */
|
|
9
|
+
graceAttempts: number;
|
|
10
|
+
/** How long before the bound the authority is asked again; `STANDING_REFRESH_LEAD_MS` unless a test shortens the bound. */
|
|
11
|
+
leadMs?: number;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A response whose body stops when its token does.
|
|
16
|
+
*
|
|
17
|
+
* A request is authorized once, when it arrives, but an event stream stays
|
|
18
|
+
* open for hours on that one check. This keeps the answer it flows on inside
|
|
19
|
+
* the bound a new request would get, by asking the authority again shortly
|
|
20
|
+
* before the held answer ages out, and ends the body when the token is
|
|
21
|
+
* revoked or expires. Ending cancels the source, which frees a tunnel
|
|
22
|
+
* stream's slot and tells its host to stop.
|
|
23
|
+
*/
|
|
24
|
+
export declare function guardStreamStanding(response: Response, claims: RuntimeAccessTokenClaims, options: StreamStandingOptions): Response;
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type WorkspaceRelayDurableObjectNamespace, type WorkspaceRelayDurableObjectRoomOptions, type WorkspaceRelayDurableObjectSocket } from "./cloudflare";
|
|
2
2
|
import { type WorkspaceRelayTarget } from "./relay-target";
|
|
3
|
-
import { type
|
|
3
|
+
import { type CachedRevocationLookup, type HostGenerationLookup } from "./resolver-clients";
|
|
4
4
|
type WorkspaceRelayWorkerBindings = {
|
|
5
5
|
WORKSPACE_RELAY_ROOM?: WorkspaceRelayDurableObjectNamespace;
|
|
6
6
|
CLAXEDO_RELAY_RESOLVER_URL?: string;
|
|
@@ -32,29 +32,28 @@ type WorkspaceRelayWorkerBindings = {
|
|
|
32
32
|
export type WorkspaceRelayWorkerEnv = Record<string, unknown> & WorkspaceRelayWorkerBindings;
|
|
33
33
|
type ResolverClient = {
|
|
34
34
|
target(workspaceId: string, hostId: string, routingId?: string): Promise<WorkspaceRelayTarget | undefined>;
|
|
35
|
-
revocation
|
|
36
|
-
jti: string;
|
|
37
|
-
workspaceId: string;
|
|
38
|
-
hostId: string;
|
|
39
|
-
}): Promise<RuntimeAccessTokenActiveResult>;
|
|
35
|
+
revocation: CachedRevocationLookup;
|
|
40
36
|
hostGeneration: HostGenerationLookup;
|
|
41
37
|
};
|
|
42
38
|
type ResolverFetch = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
43
39
|
export declare function workspaceRelayWorkerResolverUrl(env: WorkspaceRelayWorkerEnv): string;
|
|
44
40
|
export declare function workspaceRelayWorkerResolverClient(env: WorkspaceRelayWorkerEnv, fetcher?: ResolverFetch): ResolverClient;
|
|
45
|
-
|
|
41
|
+
/** Everything the room needs from the environment; the Worker adds `report`, which is the object's own. */
|
|
42
|
+
export declare function workspaceRelayDurableObjectOptions(env: WorkspaceRelayWorkerEnv): Promise<Omit<WorkspaceRelayDurableObjectRoomOptions, "report">>;
|
|
46
43
|
/**
|
|
47
|
-
* The `DurableObjectState` workerd hands a room
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Everything is optional because `cloudflare.ts` treats hibernation and alarms
|
|
52
|
-
* as capabilities to DETECT: an older runtime, or a harness standing in for
|
|
53
|
-
* one, may provide neither, and the room degrades rather than failing.
|
|
44
|
+
* The `DurableObjectState` workerd hands a room. Everything is optional because
|
|
45
|
+
* `cloudflare.ts` detects hibernation and alarms: an older runtime, or a
|
|
46
|
+
* harness standing in for one, may provide neither, and the room degrades
|
|
47
|
+
* rather than failing.
|
|
54
48
|
*/
|
|
55
49
|
export type WorkspaceRelayRoomState = {
|
|
50
|
+
id?: {
|
|
51
|
+
name?: string;
|
|
52
|
+
};
|
|
56
53
|
acceptWebSocket?: (socket: WorkspaceRelayDurableObjectSocket) => void;
|
|
57
54
|
getWebSockets?: () => WorkspaceRelayDurableObjectSocket[];
|
|
55
|
+
/** Keeps a report's POST alive past the step that made it; absent in tests that construct the room directly. */
|
|
56
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
58
57
|
storage?: {
|
|
59
58
|
getAlarm?: () => Promise<number | null>;
|
|
60
59
|
setAlarm?: (scheduledTime: number) => Promise<void>;
|
|
@@ -66,7 +65,18 @@ export declare class WorkspaceRelayRoom {
|
|
|
66
65
|
private env;
|
|
67
66
|
private room?;
|
|
68
67
|
private loading?;
|
|
68
|
+
private readonly telemetry;
|
|
69
|
+
private colo?;
|
|
70
|
+
private coloLookup?;
|
|
69
71
|
constructor(state: WorkspaceRelayRoomState, env: WorkspaceRelayWorkerEnv);
|
|
72
|
+
/** A fault of this room's code, under `system`; the room's own name is a workspace id and is digested by the sink. */
|
|
73
|
+
private report;
|
|
74
|
+
/**
|
|
75
|
+
* A Durable Object has no API for its own location. Cloudflare's trace page
|
|
76
|
+
* names the data center a request left from, which for a subrequest is the
|
|
77
|
+
* object's. The first sampled trace starts the lookup and goes without it.
|
|
78
|
+
*/
|
|
79
|
+
private roomColo;
|
|
70
80
|
private hibernation;
|
|
71
81
|
/**
|
|
72
82
|
* The DO alarm surface, used for the hibernation-safe revocation re-check.
|
|
@@ -85,8 +95,18 @@ export declare class WorkspaceRelayRoom {
|
|
|
85
95
|
* it, so adding another alarm user here stays safe.
|
|
86
96
|
*/
|
|
87
97
|
alarm(): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* The gateway reaches a workspace's room by one name. A room under any other
|
|
100
|
+
* name keeps the sockets it accepted, and a machine whose tunnel is healthy
|
|
101
|
+
* never dials again, so the workspace reads as offline for as long as this
|
|
102
|
+
* room holds on. Closed, the machine and its browsers dial through the
|
|
103
|
+
* gateway and meet in the room it names.
|
|
104
|
+
*/
|
|
105
|
+
private unreachable;
|
|
88
106
|
}
|
|
89
107
|
declare const _default: {
|
|
90
|
-
fetch(request: Request, env: WorkspaceRelayWorkerEnv
|
|
108
|
+
fetch(request: Request, env: WorkspaceRelayWorkerEnv, context?: {
|
|
109
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
110
|
+
}): Promise<Response>;
|
|
91
111
|
};
|
|
92
112
|
export default _default;
|
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"name": "@claxedo/workspace-relay",
|
|
36
|
-
"version": "0.10.
|
|
36
|
+
"version": "0.10.3",
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsx scripts/build.ts",
|
|
39
39
|
"prepublishOnly": "npm run build",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
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 node scripts/test-workerd.mjs"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@claxedo/helpers": "0.2.
|
|
45
|
-
"@claxedo/workspace-relay-protocol": "0.10.
|
|
44
|
+
"@claxedo/helpers": "0.2.3",
|
|
45
|
+
"@claxedo/workspace-relay-protocol": "0.10.3",
|
|
46
46
|
"jose": "6.0.11"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|