@nimbus-sh/worker 0.4.0 → 0.5.0
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 +46 -43
- package/dist/_shared/preview-host.d.ts +14 -1
- package/dist/_shared/preview-host.d.ts.map +1 -1
- package/dist/_shared/preview-host.js +28 -3
- package/dist/_shared/session-router.d.ts +43 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +59 -12
- package/dist/facets/durable-images.d.ts +50 -0
- package/dist/facets/durable-images.d.ts.map +1 -0
- package/dist/facets/durable-images.js +75 -0
- package/dist/facets/durable-slots.d.ts +45 -0
- package/dist/facets/durable-slots.d.ts.map +1 -0
- package/dist/facets/durable-slots.js +94 -0
- package/dist/facets/esbuild-transform.d.ts +10 -0
- package/dist/facets/esbuild-transform.d.ts.map +1 -0
- package/dist/facets/esbuild-transform.js +36 -0
- package/dist/facets/manager.d.ts +160 -5
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +554 -97
- package/dist/git/commands.d.ts +22 -0
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +431 -424
- package/dist/git-bundle.generated.d.ts +2 -2
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +59 -2
- package/dist/router/public-directory-do.d.ts +17 -0
- package/dist/router/public-directory-do.d.ts.map +1 -0
- package/dist/router/public-directory-do.js +16 -0
- package/dist/router/public-directory.d.ts +75 -0
- package/dist/router/public-directory.d.ts.map +1 -0
- package/dist/router/public-directory.js +115 -0
- package/dist/router/remote-api.d.ts +9 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +19 -2
- package/dist/runtime/node-shims.d.ts +4 -1
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +324 -6
- package/dist/session/agent.d.ts +11 -0
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/helpers.js +1 -1
- package/dist/session/init-phases.d.ts +17 -23
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +17 -41
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +19 -15
- package/dist/session/keys.d.ts +8 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +8 -0
- package/dist/session/nimbus-session.d.ts +29 -4
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +62 -9
- package/dist/session/port-capability.d.ts +95 -8
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +165 -8
- package/dist/session/programmatic.d.ts +50 -2
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +104 -6
- package/dist/session/routes.d.ts +17 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +92 -25
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +14 -4
- package/dist/session/shell-socket.d.ts +76 -0
- package/dist/session/shell-socket.d.ts.map +1 -0
- package/dist/session/shell-socket.js +172 -0
- package/dist/session/start-real-vite.js +1 -1
- package/dist/session/supervisor-op.d.ts +5 -0
- package/dist/session/supervisor-op.d.ts.map +1 -0
- package/dist/session/supervisor-op.js +74 -0
- package/dist/session/supervisor-rpc.d.ts +6 -10
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +91 -70
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +19 -4
- package/package.json +4 -4
- package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
- package/public/s/index.html +101 -5
|
@@ -11,20 +11,139 @@
|
|
|
11
11
|
* back. Everything else is the security half of the same rule: a capability
|
|
12
12
|
* names ONE registration, so any other registration on that port retires the
|
|
13
13
|
* durable copy. Only a deliberate restore extends a capability's life, which
|
|
14
|
-
* is why `clearPortCapability` sits next to every `portRegistry.register
|
|
14
|
+
* is why `clearPortCapability` sits next to every `portRegistry.register`,
|
|
15
15
|
* `restorePortCapability` sits only at the two places a dev server is
|
|
16
|
-
* deliberately brought back
|
|
16
|
+
* deliberately brought back, and `restoreReservedPortCapability` sits only
|
|
17
|
+
* on the durable spawn's re-adopt path, gated on the stored reservation's
|
|
18
|
+
* owner.
|
|
19
|
+
*
|
|
20
|
+
* The same per-port record also carries a RESERVATION: an owner may hold a
|
|
21
|
+
* port with no capability yet, so a durable application keeps its port across
|
|
22
|
+
* instances whether or not it is currently exposed. Clearing a capability
|
|
23
|
+
* keeps the owner's reservation; only `releasePortReservation` ends it.
|
|
17
24
|
*/
|
|
18
25
|
import { z } from 'zod/v4';
|
|
19
26
|
import { PORT_CAPABILITY_KEY_PREFIX } from './keys.js';
|
|
20
27
|
/** The shape `createPortCapability` mints: 12 random bytes, hex. */
|
|
21
28
|
const PortCapabilitySchema = z.string().regex(/^[a-f0-9]{24}$/);
|
|
22
|
-
|
|
29
|
+
/** 'scoped' is the default: capability-checked but never the public bearer. */
|
|
30
|
+
const PortVisibilitySchema = z.enum(['scoped', 'public']);
|
|
31
|
+
export const PortRecordSchema = z.object({
|
|
32
|
+
capability: PortCapabilitySchema.nullable(),
|
|
33
|
+
owner: z.string().nullable(),
|
|
34
|
+
visibility: PortVisibilitySchema.optional(),
|
|
35
|
+
});
|
|
36
|
+
const RESERVATION_FIRST_PORT = 20000;
|
|
37
|
+
const RESERVATION_LAST_PORT = 65535;
|
|
38
|
+
export function portRecordKey(port) {
|
|
23
39
|
return `${PORT_CAPABILITY_KEY_PREFIX}${Number(port)}`;
|
|
24
40
|
}
|
|
41
|
+
function conflict(detail) {
|
|
42
|
+
return new Error(`port reservation conflict: ${detail}`);
|
|
43
|
+
}
|
|
44
|
+
/** Read the raw per-port record: a reservation, an exposure, or nothing. */
|
|
45
|
+
export async function readPortReservation(ctx, port) {
|
|
46
|
+
const stored = PortRecordSchema.safeParse(await ctx.storage.get(portRecordKey(port)));
|
|
47
|
+
if (!stored.success)
|
|
48
|
+
return null;
|
|
49
|
+
return { ...stored.data, visibility: stored.data.visibility ?? 'scoped' };
|
|
50
|
+
}
|
|
51
|
+
/** Read retained exposure metadata without starting a session or restoring a listener. */
|
|
52
|
+
export async function readPortExposure(ctx, port) {
|
|
53
|
+
const stored = await readPortReservation(ctx, port);
|
|
54
|
+
return stored !== null && stored.capability !== null
|
|
55
|
+
? { capability: stored.capability, owner: stored.owner, visibility: stored.visibility }
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Hold a port for `owner` across instances. The owner's existing port is
|
|
60
|
+
* answered again; otherwise the preferred port is claimed, or the lowest
|
|
61
|
+
* free one. A preferred port that another record or a live listener holds
|
|
62
|
+
* is refused, never silently moved.
|
|
63
|
+
*
|
|
64
|
+
* `capability`/`visibility` give the embedder-durable path its URL material
|
|
65
|
+
* up front: a reservation can carry the capability the application will
|
|
66
|
+
* re-adopt when it binds, and the visibility the public bearer form is
|
|
67
|
+
* gated on. An existing reservation for the same owner is answered again,
|
|
68
|
+
* upgraded in place when the caller supplies values it lacks — an existing
|
|
69
|
+
* capability is never rotated, a URL already handed out stays good.
|
|
70
|
+
*/
|
|
71
|
+
export async function reservePort(ctx, input) {
|
|
72
|
+
const claim = async (txn) => {
|
|
73
|
+
const records = await txn.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
|
|
74
|
+
const taken = new Set(input.occupiedPorts);
|
|
75
|
+
let held = null;
|
|
76
|
+
for (const [storedKey, value] of records) {
|
|
77
|
+
const port = Number(storedKey.slice(PORT_CAPABILITY_KEY_PREFIX.length));
|
|
78
|
+
if (!Number.isInteger(port))
|
|
79
|
+
continue;
|
|
80
|
+
taken.add(port);
|
|
81
|
+
const parsed = PortRecordSchema.safeParse(value);
|
|
82
|
+
if (parsed.success && parsed.data.owner === input.owner) {
|
|
83
|
+
held = { port, stored: parsed.data };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const preferred = input.preferredPort;
|
|
87
|
+
if (held !== null) {
|
|
88
|
+
if (preferred !== undefined && preferred !== held.port) {
|
|
89
|
+
throw conflict(`owner already holds port ${held.port}, cannot reserve ${preferred}`);
|
|
90
|
+
}
|
|
91
|
+
// Upgrade in place: adopt a caller-supplied capability the row lacks
|
|
92
|
+
// and/or apply the caller's visibility — never rotate an existing one.
|
|
93
|
+
const stored = held.stored;
|
|
94
|
+
const capability = stored?.capability ?? input.capability ?? null;
|
|
95
|
+
const visibility = input.visibility ?? stored?.visibility ?? 'scoped';
|
|
96
|
+
if (stored === null || capability !== stored.capability || visibility !== (stored.visibility ?? 'scoped')) {
|
|
97
|
+
await txn.put(portRecordKey(held.port), { owner: input.owner, capability, visibility });
|
|
98
|
+
}
|
|
99
|
+
return held.port;
|
|
100
|
+
}
|
|
101
|
+
let port;
|
|
102
|
+
if (preferred !== undefined) {
|
|
103
|
+
if (!Number.isInteger(preferred) || preferred < 1 || preferred > RESERVATION_LAST_PORT) {
|
|
104
|
+
throw conflict(`port ${preferred} is outside 1..${RESERVATION_LAST_PORT}`);
|
|
105
|
+
}
|
|
106
|
+
if (taken.has(preferred))
|
|
107
|
+
throw conflict(`port ${preferred} is held by another owner or listener`);
|
|
108
|
+
port = preferred;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
port = RESERVATION_FIRST_PORT;
|
|
112
|
+
while (port <= RESERVATION_LAST_PORT && taken.has(port))
|
|
113
|
+
port += 1;
|
|
114
|
+
if (port > RESERVATION_LAST_PORT)
|
|
115
|
+
throw conflict('no free port left to reserve');
|
|
116
|
+
}
|
|
117
|
+
await txn.put(portRecordKey(port), {
|
|
118
|
+
owner: input.owner,
|
|
119
|
+
capability: input.capability ?? null,
|
|
120
|
+
visibility: input.visibility ?? 'scoped',
|
|
121
|
+
});
|
|
122
|
+
return port;
|
|
123
|
+
};
|
|
124
|
+
return ctx.storage.transaction(claim);
|
|
125
|
+
}
|
|
126
|
+
/** End an owner's hold on a port. Another owner's record is left alone and refused. */
|
|
127
|
+
export async function releasePortReservation(ctx, input) {
|
|
128
|
+
// The read, the owner check and the delete are one transaction: a concurrent
|
|
129
|
+
// claim or a foreign release cannot interleave between them.
|
|
130
|
+
return ctx.storage.transaction(async (txn) => {
|
|
131
|
+
const stored = await readPortReservation({ storage: txn }, input.port);
|
|
132
|
+
if (stored === null)
|
|
133
|
+
return false;
|
|
134
|
+
if (stored.owner !== input.owner)
|
|
135
|
+
throw conflict(`port ${input.port} is held by another owner`);
|
|
136
|
+
await txn.delete(portRecordKey(input.port));
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
25
140
|
export async function readPortCapability(self, port) {
|
|
26
|
-
|
|
27
|
-
|
|
141
|
+
// The stored record is the only source of truth: a capability belongs to
|
|
142
|
+
// whoever is registered on the port, and its owner field is preserved by
|
|
143
|
+
// persist, never consulted as a gate. Durable ownership rides the
|
|
144
|
+
// reservation's own restore path (restoreReservedPortCapability).
|
|
145
|
+
const stored = await readPortExposure(self.ctx, port);
|
|
146
|
+
return stored?.capability ?? null;
|
|
28
147
|
}
|
|
29
148
|
/**
|
|
30
149
|
* Re-adopt the persisted capability into whatever the registry holds now.
|
|
@@ -36,14 +155,52 @@ export async function restorePortCapability(self, port) {
|
|
|
36
155
|
return null;
|
|
37
156
|
return self.portRegistry.restoreCapability(Number(port), stored) ? stored : null;
|
|
38
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Re-adopt the persisted capability ONLY for the owner the stored record
|
|
160
|
+
* names. The durable re-drive calls this with its own owner: the reservation
|
|
161
|
+
* is the same source of truth the preflight reads, so a release or a foreign
|
|
162
|
+
* claim mid-boot cannot smuggle an exposure across.
|
|
163
|
+
*/
|
|
164
|
+
export async function restoreReservedPortCapability(self, port, owner) {
|
|
165
|
+
const stored = await readPortReservation(self.ctx, port);
|
|
166
|
+
if (stored === null || stored.owner !== owner || stored.capability === null)
|
|
167
|
+
return null;
|
|
168
|
+
return self.portRegistry.restoreCapability(Number(port), stored.capability) ? stored.capability : null;
|
|
169
|
+
}
|
|
39
170
|
export async function persistPortCapability(self, port, capability) {
|
|
40
|
-
|
|
171
|
+
// Read-modify-write inside the reservation's own transaction, and the stored
|
|
172
|
+
// record is the ONLY source of truth for owner: an SDK ports.list()/expose
|
|
173
|
+
// on a durable application's port must not rewrite the row it is reporting
|
|
174
|
+
// on, or the next durable preflight sees a reservation nobody owns.
|
|
175
|
+
await self.ctx.storage.transaction(async (txn) => {
|
|
176
|
+
const stored = await txn.get(portRecordKey(port));
|
|
177
|
+
const parsed = PortRecordSchema.safeParse(stored);
|
|
178
|
+
await txn.put(portRecordKey(port), {
|
|
179
|
+
capability: PortCapabilitySchema.parse(capability),
|
|
180
|
+
owner: parsed.success ? parsed.data.owner : null,
|
|
181
|
+
...(parsed.success && parsed.data.visibility !== undefined
|
|
182
|
+
? { visibility: parsed.data.visibility }
|
|
183
|
+
: {}),
|
|
184
|
+
});
|
|
185
|
+
});
|
|
41
186
|
}
|
|
42
187
|
/**
|
|
43
188
|
* Retire the durable capability for a port. Called before every registration
|
|
44
189
|
* that is not a restore, so a token handed out for the previous occupant of a
|
|
45
|
-
* port cannot reach the next one.
|
|
190
|
+
* port cannot reach the next one. An owner's reservation survives it.
|
|
46
191
|
*/
|
|
47
192
|
export async function clearPortCapability(self, port) {
|
|
48
|
-
await self.ctx.storage.
|
|
193
|
+
await self.ctx.storage.transaction(async (txn) => {
|
|
194
|
+
const stored = PortRecordSchema.safeParse(await txn.get(portRecordKey(port)));
|
|
195
|
+
const record = stored.success ? stored.data : null;
|
|
196
|
+
if (record !== null && record.owner !== null) {
|
|
197
|
+
await txn.put(portRecordKey(port), {
|
|
198
|
+
owner: record.owner,
|
|
199
|
+
capability: null,
|
|
200
|
+
...(record.visibility !== undefined ? { visibility: record.visibility } : {}),
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
await txn.delete(portRecordKey(port));
|
|
205
|
+
});
|
|
49
206
|
}
|
|
@@ -32,20 +32,36 @@ interface ProgrammaticShellExecuteOptions {
|
|
|
32
32
|
commandContext?: Record<string, unknown>;
|
|
33
33
|
}
|
|
34
34
|
interface ProgrammaticContext {
|
|
35
|
+
/** The session DO's own id — the public-directory binding reads its name. */
|
|
36
|
+
id?: {
|
|
37
|
+
name?: unknown;
|
|
38
|
+
};
|
|
35
39
|
getWebSockets?(tag?: string): WebSocket[];
|
|
36
40
|
/** Holds a background process's work open for the life of the process. */
|
|
37
41
|
waitUntil?(promise: Promise<unknown>): void;
|
|
38
42
|
storage: {
|
|
39
43
|
get(key: string): Promise<unknown>;
|
|
44
|
+
put(key: string, value: unknown): Promise<void>;
|
|
40
45
|
delete(key: string): Promise<void>;
|
|
41
46
|
deleteAll(): Promise<void>;
|
|
42
47
|
deleteAlarm(): Promise<void>;
|
|
43
|
-
|
|
48
|
+
list<T = unknown>(options: {
|
|
49
|
+
prefix: string;
|
|
50
|
+
}): Promise<Map<string, T>>;
|
|
51
|
+
transaction<T>(body: (txn: {
|
|
52
|
+
get(key: string): Promise<unknown>;
|
|
53
|
+
put(key: string, value: unknown): Promise<void>;
|
|
54
|
+
delete(key: string): Promise<unknown>;
|
|
55
|
+
list<T2 = unknown>(options: {
|
|
56
|
+
prefix: string;
|
|
57
|
+
}): Promise<Map<string, T2>>;
|
|
58
|
+
}) => Promise<T>): Promise<T>;
|
|
44
59
|
};
|
|
45
60
|
}
|
|
46
61
|
interface ProgrammaticFacetManager {
|
|
47
62
|
kill(pid: number): boolean;
|
|
48
63
|
hasResidentProcess(pid: number): boolean;
|
|
64
|
+
removeDurableApp(owner: string): Promise<boolean>;
|
|
49
65
|
}
|
|
50
66
|
interface ProgrammaticViteServer {
|
|
51
67
|
isRunning: boolean;
|
|
@@ -249,17 +265,49 @@ export declare function rpcProcessLogs(self: ProgrammaticHost, pid: number, opti
|
|
|
249
265
|
exit: import("@nimbus-sh/core/runtime/process-logs.js").ProcessExitInfo | null;
|
|
250
266
|
}>;
|
|
251
267
|
export declare function rpcListPorts(self: ProgrammaticHost): Promise<SerializedPort[]>;
|
|
252
|
-
export declare function rpcExposePort(self: ProgrammaticHost, port: number
|
|
268
|
+
export declare function rpcExposePort(self: ProgrammaticHost, port: number, options?: {
|
|
269
|
+
visibility?: 'scoped' | 'public';
|
|
270
|
+
}): Promise<{
|
|
253
271
|
port: number;
|
|
254
272
|
listening: boolean;
|
|
255
273
|
pid: number | null;
|
|
256
274
|
registeredAt: number | null;
|
|
257
275
|
capability: string | null;
|
|
276
|
+
visibility: "scoped" | "public";
|
|
277
|
+
}>;
|
|
278
|
+
/**
|
|
279
|
+
* The embedder's durable-application seam: reserve (or re-answer) the port
|
|
280
|
+
* `owner` holds, minting the capability the application's public URL is
|
|
281
|
+
* built on — minted HERE, stored on the reservation, so a URL handed out
|
|
282
|
+
* before the application has ever booted is the one its eventual binding
|
|
283
|
+
* re-adopts, and the one a reset re-adopts again. Answers the port, the
|
|
284
|
+
* capability, and the record's visibility.
|
|
285
|
+
*/
|
|
286
|
+
export declare function rpcEnsureDurableApp(self: ProgrammaticHost, input: {
|
|
287
|
+
owner: string;
|
|
288
|
+
preferredPort?: number;
|
|
289
|
+
visibility?: 'scoped' | 'public';
|
|
290
|
+
}): Promise<{
|
|
291
|
+
port: number;
|
|
292
|
+
capability: string | null;
|
|
293
|
+
visibility: 'scoped' | 'public';
|
|
258
294
|
}>;
|
|
259
295
|
export declare function rpcUnexposePort(self: ProgrammaticHost, port: number): Promise<{
|
|
260
296
|
port: number;
|
|
261
297
|
ok: boolean;
|
|
262
298
|
}>;
|
|
299
|
+
/**
|
|
300
|
+
* End a durable application's contract. The reservation's port is read first
|
|
301
|
+
* so the caller learns which durable address was released; the manager then
|
|
302
|
+
* kills every launch the owner claims, purges its journal rows, releases the
|
|
303
|
+
* port, and frees the durable slot. `removed` is false only when no durable
|
|
304
|
+
* application held that owner at all.
|
|
305
|
+
*/
|
|
306
|
+
export declare function rpcRemoveDurableApp(self: ProgrammaticHost, owner: string): Promise<{
|
|
307
|
+
owner: string;
|
|
308
|
+
removed: boolean;
|
|
309
|
+
port: number | null;
|
|
310
|
+
}>;
|
|
263
311
|
/**
|
|
264
312
|
* Route an embedder request that carries a port capability. The embedder has
|
|
265
313
|
* authenticated the capability at its edge and stripped its own credentials,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"programmatic.d.ts","sourceRoot":"","sources":["../../src/session/programmatic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,+CAA+C,CAAC;AAEvD,OAAO,KAAK,EAAY,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAC/F,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AACjG,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"programmatic.d.ts","sourceRoot":"","sources":["../../src/session/programmatic.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,14 @@ import { listInstalledRuntimes, } from '@nimbus-sh/core/runtime/installed-runtim
|
|
|
10
10
|
import { PID_GEN_STRIDE } from '@nimbus-sh/core/runtime/process-table.js';
|
|
11
11
|
import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
|
|
12
12
|
import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
|
|
13
|
-
import { PortRegistry } from '@nimbus-sh/core/runtime/port-registry.js';
|
|
13
|
+
import { PortRegistry, createPortCapability } from '@nimbus-sh/core/runtime/port-registry.js';
|
|
14
14
|
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
15
15
|
import { endProcessInput, resizeProcess, signalProcess, writeProcessInput, } from '@nimbus-sh/core/runtime/process-input-routing.js';
|
|
16
16
|
import { z } from 'zod/v4';
|
|
17
17
|
import { SESSION_DESTROYED_KEY, SHELL_STATE_KEY_PREFIX, VITE_CONFIG_KEY } from './keys.js';
|
|
18
|
-
import { clearPortCapability, persistPortCapability, restorePortCapability, } from './port-capability.js';
|
|
18
|
+
import { clearPortCapability, persistPortCapability, PortRecordSchema, portRecordKey, readPortReservation, reservePort, restorePortCapability, } from './port-capability.js';
|
|
19
|
+
import { PORT_CAPABILITY_KEY_PREFIX } from './keys.js';
|
|
20
|
+
import { bindPublicPortCapability, unbindPublicPortCapability } from '../router/public-directory.js';
|
|
19
21
|
import { GENERATION_KEY, assumeGeneration, generation } from '@nimbus-sh/fabric/generation.js';
|
|
20
22
|
import { HeadlessTerminal, Shell } from '@nimbus-sh/core/substrate/lifo/index.js';
|
|
21
23
|
const ShellIdSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
@@ -169,8 +171,8 @@ function runtimeDeps(self) {
|
|
|
169
171
|
export async function ensureProgrammaticReady(self, options = {}) {
|
|
170
172
|
if (!self.shell) {
|
|
171
173
|
await self.initSession(makeHeadlessWebSocket());
|
|
172
|
-
//
|
|
173
|
-
// drained
|
|
174
|
+
// A programmatic boot owns no terminal socket, which is what
|
|
175
|
+
// 'drained' reports to the diagnostics and the recovery ring.
|
|
174
176
|
self._b4Phase = 'drained';
|
|
175
177
|
}
|
|
176
178
|
else {
|
|
@@ -522,28 +524,124 @@ export async function rpcListPorts(self) {
|
|
|
522
524
|
await Promise.all(entries.map((entry) => persistPortCapability(self, entry.port, entry.capability)));
|
|
523
525
|
return entries.map(serializePort);
|
|
524
526
|
}
|
|
525
|
-
export async function rpcExposePort(self, port) {
|
|
527
|
+
export async function rpcExposePort(self, port, options) {
|
|
526
528
|
await ensureProgrammaticReady(self);
|
|
527
529
|
const n = Number(port);
|
|
528
530
|
const entry = self.portRegistry.get(n);
|
|
529
531
|
if (entry)
|
|
530
532
|
await persistPortCapability(self, n, entry.capability);
|
|
533
|
+
// The stored record decides visibility: a reservation's mark is sticky,
|
|
534
|
+
// so 'public' survives persists of unrelated fields — and the caller's
|
|
535
|
+
// explicit choice, when given, lands on the row the same way.
|
|
536
|
+
if (options?.visibility !== undefined) {
|
|
537
|
+
// A visibility choice is a write on the stored row, not a reservation
|
|
538
|
+
// claim: owner and capability stay exactly as they were.
|
|
539
|
+
await self.ctx.storage.transaction(async (txn) => {
|
|
540
|
+
const stored = await readPortReservation({ storage: txn }, n);
|
|
541
|
+
if (stored !== null && stored.visibility !== options.visibility) {
|
|
542
|
+
await txn.put(portRecordKey(n), {
|
|
543
|
+
owner: stored.owner,
|
|
544
|
+
capability: stored.capability,
|
|
545
|
+
visibility: options.visibility,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
const record = await readPortReservation(self.ctx, n);
|
|
551
|
+
if (record?.visibility === 'public' && entry) {
|
|
552
|
+
// A port that goes public must be resolvable: the capability the URL
|
|
553
|
+
// carries has to name this session in the directory, and a non-legacy
|
|
554
|
+
// deployment without the binding is refused loudly — never half-public.
|
|
555
|
+
await bindPublicPortCapability(self, entry.capability, n);
|
|
556
|
+
}
|
|
531
557
|
return {
|
|
532
558
|
port: n,
|
|
533
559
|
listening: !!entry,
|
|
534
560
|
pid: entry?.pid ?? null,
|
|
535
561
|
registeredAt: entry?.registeredAt ?? null,
|
|
536
562
|
capability: entry?.capability ?? null,
|
|
563
|
+
visibility: record?.visibility ?? 'scoped',
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The embedder's durable-application seam: reserve (or re-answer) the port
|
|
568
|
+
* `owner` holds, minting the capability the application's public URL is
|
|
569
|
+
* built on — minted HERE, stored on the reservation, so a URL handed out
|
|
570
|
+
* before the application has ever booted is the one its eventual binding
|
|
571
|
+
* re-adopts, and the one a reset re-adopts again. Answers the port, the
|
|
572
|
+
* capability, and the record's visibility.
|
|
573
|
+
*/
|
|
574
|
+
export async function rpcEnsureDurableApp(self, input) {
|
|
575
|
+
await ensureProgrammaticReady(self);
|
|
576
|
+
const owner = input.owner;
|
|
577
|
+
if (typeof owner !== 'string' || owner.length === 0) {
|
|
578
|
+
throw new Error('ensureDurableApp: owner must be a non-empty string');
|
|
579
|
+
}
|
|
580
|
+
const occupied = new Set(self.portRegistry.getAll().map((entry) => entry.port));
|
|
581
|
+
const port = await reservePort(self.ctx, {
|
|
582
|
+
owner,
|
|
583
|
+
preferredPort: input.preferredPort,
|
|
584
|
+
occupiedPorts: occupied,
|
|
585
|
+
// Minted here, not at boot: the URL is built from this capability, and
|
|
586
|
+
// re-drive re-adopts it out of the same row.
|
|
587
|
+
capability: createPortCapability(),
|
|
588
|
+
...(input.visibility !== undefined ? { visibility: input.visibility } : {}),
|
|
589
|
+
});
|
|
590
|
+
const record = await readPortReservation(self.ctx, port);
|
|
591
|
+
if (record?.visibility === 'public' && record.capability !== null) {
|
|
592
|
+
// The URL is built on this capability before the app has ever run:
|
|
593
|
+
// publish it to the directory now, loudly refusing a deployment whose
|
|
594
|
+
// binding is missing rather than minting an unroutable URL.
|
|
595
|
+
await bindPublicPortCapability(self, record.capability, port);
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
port,
|
|
599
|
+
capability: record?.capability ?? null,
|
|
600
|
+
visibility: record?.visibility ?? 'scoped',
|
|
537
601
|
};
|
|
538
602
|
}
|
|
539
603
|
export async function rpcUnexposePort(self, port) {
|
|
540
604
|
await ensureProgrammaticReady(self);
|
|
541
605
|
const n = Number(port);
|
|
542
606
|
// Before the unregister, so a crash between the two leaves a dead token
|
|
543
|
-
// rather than a live one.
|
|
607
|
+
// rather than a live one. A public capability also leaves the directory.
|
|
608
|
+
const record = await readPortReservation(self.ctx, n);
|
|
544
609
|
await clearPortCapability(self, n);
|
|
610
|
+
if (record?.visibility === 'public' && record.capability !== null) {
|
|
611
|
+
await unbindPublicPortCapability(self, record.capability);
|
|
612
|
+
}
|
|
545
613
|
return { port: n, ok: self.portRegistry.unregister(n) };
|
|
546
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* End a durable application's contract. The reservation's port is read first
|
|
617
|
+
* so the caller learns which durable address was released; the manager then
|
|
618
|
+
* kills every launch the owner claims, purges its journal rows, releases the
|
|
619
|
+
* port, and frees the durable slot. `removed` is false only when no durable
|
|
620
|
+
* application held that owner at all.
|
|
621
|
+
*/
|
|
622
|
+
export async function rpcRemoveDurableApp(self, owner) {
|
|
623
|
+
await ensureProgrammaticReady(self);
|
|
624
|
+
if (typeof owner !== 'string' || owner.length === 0) {
|
|
625
|
+
throw new Error('removeDurableApp: owner must be a non-empty string');
|
|
626
|
+
}
|
|
627
|
+
// Which durable address is being released — the reservation's row names it
|
|
628
|
+
// before the manager retires it.
|
|
629
|
+
const rows = await self.ctx.storage.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
|
|
630
|
+
let port = null;
|
|
631
|
+
for (const [key, record] of rows) {
|
|
632
|
+
const parsed = PortRecordSchema.safeParse(record);
|
|
633
|
+
if (!parsed.success || parsed.data.owner !== owner)
|
|
634
|
+
continue;
|
|
635
|
+
const n = Number(key.slice(PORT_CAPABILITY_KEY_PREFIX.length));
|
|
636
|
+
if (Number.isInteger(n) && n > 0) {
|
|
637
|
+
port = n;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
self.ensureFacetManager();
|
|
642
|
+
const removed = await self.facetManager.removeDurableApp(owner);
|
|
643
|
+
return { owner, removed, port };
|
|
644
|
+
}
|
|
547
645
|
/**
|
|
548
646
|
* Route an embedder request that carries a port capability. The embedder has
|
|
549
647
|
* authenticated the capability at its edge and stripped its own credentials,
|
package/dist/session/routes.d.ts
CHANGED
|
@@ -24,6 +24,23 @@
|
|
|
24
24
|
* (~30 sites). RoutesHost = any pragmatic deviation, like InitHost in S6.
|
|
25
25
|
*/
|
|
26
26
|
type RoutesHost = any;
|
|
27
|
+
/**
|
|
28
|
+
* Route a request to whatever is listening on a session port.
|
|
29
|
+
*
|
|
30
|
+
* The one implementation behind every port-addressed surface: `/port/<n>/`,
|
|
31
|
+
* `/preview/?port=N`, and the `<port>--<sid>` preview hostname, which the
|
|
32
|
+
* router forwards as `/port/<n>/`. They differ only in how the port and the
|
|
33
|
+
* inner path are spelled, so they must not differ in what answers.
|
|
34
|
+
*
|
|
35
|
+
* `mountBase` is the public URL prefix the served app is mounted at for THIS
|
|
36
|
+
* request — '' for a root-mounted `<port>--<sid>` host, '/s/<sid>/preview' for
|
|
37
|
+
* the preview path. The in-process Cirrus dev server rewrites base-relative
|
|
38
|
+
* URLs (module URLs, <base href>, BASE_URL, router basename), so it is handed
|
|
39
|
+
* the base directly: the generic port proxy strips the Nimbus base header at
|
|
40
|
+
* the untrusted-code boundary and cannot carry it, and a plain user server on
|
|
41
|
+
* any other port is mounted at root and needs no rewriting.
|
|
42
|
+
*/
|
|
43
|
+
export declare function routeToSessionPort(self: RoutesHost, port: number, request: Request, innerPath: string, mountBase: string, capability?: string): Promise<Response>;
|
|
27
44
|
/** Route a capability-authenticated embedder request to a guest HTTP server. */
|
|
28
45
|
export declare function routeCapabilityPort(self: RoutesHost, port: number, capability: string, request: Request, innerPath: string): Promise<Response>;
|
|
29
46
|
export declare function handleFetch(self: RoutesHost, request: Request): Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAuDH,KAAK,UAAU,GAAG,GAAG,CAAC;AA2HtB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC,CAyDnB;AA2BD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC,CAEnB;AAyGD,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAugCvF"}
|