@rynx-ai/server 0.1.11-beta.2 → 0.1.11-beta.21
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/dist/control-api.d.ts +10 -3
- package/dist/control-api.js +168 -7
- package/dist/control-web/assets/{highlighted-body-OFNGDK62-DWWsUtHF.js → highlighted-body-OFNGDK62-Ctle6Bpj.js} +1 -1
- package/dist/control-web/assets/index-0noHpRXD.css +32 -0
- package/dist/control-web/assets/index-dOafa0IY.js +709 -0
- package/dist/control-web/assets/{mermaid-GHXKKRXX-C1fxAgiw.js → mermaid-GHXKKRXX-DvISAPzD.js} +3 -3
- package/dist/control-web/index.html +2 -2
- package/dist/control-web-dist.d.ts +1 -1
- package/dist/control-web-dist.js +4 -2
- package/dist/machine-session-service.d.ts +31 -6
- package/dist/machine-session-service.js +124 -7
- package/dist/remote-runtime-dispatcher.d.ts +1 -1
- package/dist/remote-runtime-dispatcher.js +4 -0
- package/dist/server.d.ts +25 -3
- package/dist/server.js +117 -43
- package/dist/session-browser-service.d.ts +8 -0
- package/dist/session-browser-service.js +37 -20
- package/dist/session-portal.d.ts +42 -0
- package/dist/session-portal.js +746 -0
- package/dist/session-runtime-index.d.ts +7 -0
- package/dist/session-runtime-index.js +35 -6
- package/dist/session-terminal-host.d.ts +6 -0
- package/dist/session-terminal-host.js +56 -41
- package/dist/terminal-ws.js +8 -6
- package/package.json +7 -7
- package/dist/control-web/assets/index-BnX7NWJX.js +0 -689
- package/dist/control-web/assets/index-CUoWRX2i.css +0 -32
package/dist/server.js
CHANGED
|
@@ -10,6 +10,8 @@ import { attachBrowserSurfaceWs } from "./browser-surface-ws.js";
|
|
|
10
10
|
import { attachEmulatorSurfaceWs } from "./emulator-surface-ws.js";
|
|
11
11
|
import { attachDesktopBrowserHostServer, } from "./desktop-browser-host.js";
|
|
12
12
|
import { resolveControlWebDist, sendSpaFile } from "./control-web-dist.js";
|
|
13
|
+
import { startSessionPortal, } from "./session-portal.js";
|
|
14
|
+
export { createSessionPortalApp, startSessionPortal } from "./session-portal.js";
|
|
13
15
|
import { SessionRuntimeIndex } from "./session-runtime-index.js";
|
|
14
16
|
import { createRunnerSessionTerminalHost, } from "./session-terminal-host.js";
|
|
15
17
|
import { runtimeWebAccessPolicyForBindHost, } from "./runtime-web-auth.js";
|
|
@@ -59,6 +61,8 @@ export function createSessionRuntimeServices(input) {
|
|
|
59
61
|
...(input.sessionContextProvider
|
|
60
62
|
? { sessionContextProvider: input.sessionContextProvider }
|
|
61
63
|
: {}),
|
|
64
|
+
...(input.admissionOpen ? { admissionOpen: input.admissionOpen } : {}),
|
|
65
|
+
...(input.admissionReserve ? { admissionReserve: input.admissionReserve } : {}),
|
|
62
66
|
});
|
|
63
67
|
const sessionBus = new InMemorySessionBus();
|
|
64
68
|
const sessionRuntimeIndex = new SessionRuntimeIndex();
|
|
@@ -135,7 +139,7 @@ export function createApp({ config = loadConfig(), control } = {}) {
|
|
|
135
139
|
}
|
|
136
140
|
return app;
|
|
137
141
|
}
|
|
138
|
-
export async function startServer({ config = loadConfig(), control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, localMachineSessions, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, } = {}) {
|
|
142
|
+
export async function startServer({ config = loadConfig(), control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, localMachineSessions, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, sessionPortalAuthorization, } = {}) {
|
|
139
143
|
const runtimeWebAccess = runtimeWebAccessPolicyForBindHost(config.HOST);
|
|
140
144
|
const desktopBrowserManagementToken = desktopBrowserHost
|
|
141
145
|
? requireDaemonManagementToken(daemonManagement)
|
|
@@ -158,6 +162,46 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
158
162
|
return { cwd: session.workspace.cwd };
|
|
159
163
|
},
|
|
160
164
|
});
|
|
165
|
+
const controlShutdown = new AbortController();
|
|
166
|
+
const controlSurface = control
|
|
167
|
+
? {
|
|
168
|
+
deps: control,
|
|
169
|
+
runtime: conversationRuntime,
|
|
170
|
+
sessionBus,
|
|
171
|
+
sessionLog,
|
|
172
|
+
runnerManager,
|
|
173
|
+
sessionStore,
|
|
174
|
+
pluginRuntimeStatus: pluginRuntime
|
|
175
|
+
? () => pluginRuntime.statusSnapshot()
|
|
176
|
+
: undefined,
|
|
177
|
+
daemonManagement,
|
|
178
|
+
remoteRuntime,
|
|
179
|
+
remoteRuntimeAdmin,
|
|
180
|
+
runtimeTargetControl,
|
|
181
|
+
runtimeConnectionResolver,
|
|
182
|
+
localSessionResources,
|
|
183
|
+
localMachineSessions,
|
|
184
|
+
sessionEmulators,
|
|
185
|
+
shutdownSignal: controlShutdown.signal,
|
|
186
|
+
sessionRuntimeIndex,
|
|
187
|
+
sessionLifecycle,
|
|
188
|
+
runtimeLocalBrowser,
|
|
189
|
+
providerClis,
|
|
190
|
+
}
|
|
191
|
+
: undefined;
|
|
192
|
+
const sessionPortalConfig = config.RYNX_SESSION_PORTAL_PORT !== undefined
|
|
193
|
+
? (() => {
|
|
194
|
+
if (runtimeWebAccess.mode !== "loopback") {
|
|
195
|
+
throw new Error("Session Portal requires the ordinary Control listener to bind loopback only");
|
|
196
|
+
}
|
|
197
|
+
const controlWebDist = resolveControlWebDist();
|
|
198
|
+
if (!sessionPortalAuthorization || !controlSurface || !controlWebDist) {
|
|
199
|
+
throw new Error("Session Portal requires authorization, Control services, and the native Control Web build");
|
|
200
|
+
}
|
|
201
|
+
return { authorization: sessionPortalAuthorization, controlWebDist };
|
|
202
|
+
})()
|
|
203
|
+
: undefined;
|
|
204
|
+
let sessionPortalServer;
|
|
161
205
|
if (pluginRuntime) {
|
|
162
206
|
if (!sessionRegistry)
|
|
163
207
|
throw new Error("plugin runtime requires a machine session registry");
|
|
@@ -179,7 +223,17 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
179
223
|
}),
|
|
180
224
|
},
|
|
181
225
|
interruptSession: (sessionId) => runnerManager.interruptLiveSession(sessionId),
|
|
226
|
+
terminateSession: (sessionId) => runnerManager.terminateLiveSession(sessionId),
|
|
182
227
|
resolveSessionInteraction: (sessionId, interactionId, resolution) => runnerManager.resolveInteraction(sessionId, interactionId, resolution),
|
|
228
|
+
...(sessionPortalConfig
|
|
229
|
+
? {
|
|
230
|
+
sessionPortal: {
|
|
231
|
+
scheme: "http",
|
|
232
|
+
bindHost: config.RYNX_SESSION_PORTAL_HOST,
|
|
233
|
+
port: config.RYNX_SESSION_PORTAL_PORT,
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
: {}),
|
|
183
237
|
});
|
|
184
238
|
}
|
|
185
239
|
// Provider TUI `/clear`·`/fork` uses the same publication boundary as an
|
|
@@ -195,55 +249,40 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
195
249
|
kind: r.kind,
|
|
196
250
|
});
|
|
197
251
|
});
|
|
198
|
-
const controlShutdown = new AbortController();
|
|
199
252
|
const app = createApp({
|
|
200
253
|
config,
|
|
201
|
-
control:
|
|
202
|
-
? {
|
|
203
|
-
deps: control,
|
|
204
|
-
runtime: conversationRuntime,
|
|
205
|
-
sessionBus,
|
|
206
|
-
sessionLog,
|
|
207
|
-
runnerManager,
|
|
208
|
-
sessionStore,
|
|
209
|
-
pluginRuntimeStatus: pluginRuntime
|
|
210
|
-
? () => pluginRuntime.statusSnapshot()
|
|
211
|
-
: undefined,
|
|
212
|
-
daemonManagement,
|
|
213
|
-
remoteRuntime,
|
|
214
|
-
remoteRuntimeAdmin,
|
|
215
|
-
runtimeTargetControl,
|
|
216
|
-
runtimeConnectionResolver,
|
|
217
|
-
localSessionResources,
|
|
218
|
-
localMachineSessions,
|
|
219
|
-
sessionEmulators,
|
|
220
|
-
shutdownSignal: controlShutdown.signal,
|
|
221
|
-
sessionRuntimeIndex,
|
|
222
|
-
sessionLifecycle,
|
|
223
|
-
runtimeLocalBrowser,
|
|
224
|
-
providerClis,
|
|
225
|
-
}
|
|
226
|
-
: undefined,
|
|
254
|
+
control: controlSurface,
|
|
227
255
|
});
|
|
228
|
-
return new Promise((resolve) => {
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
let startupWebSocketServers = [];
|
|
258
|
+
const rejectBeforeListening = (error) => reject(error);
|
|
229
259
|
const server = app.listen(config.PORT, config.HOST, () => {
|
|
260
|
+
server.off("error", rejectBeforeListening);
|
|
261
|
+
void finishStartup(server).catch(async (error) => {
|
|
262
|
+
controlShutdown.abort(asError(error));
|
|
263
|
+
sessionPortalServer?.stopAccepting();
|
|
264
|
+
server.closeAllConnections?.();
|
|
265
|
+
await Promise.allSettled([
|
|
266
|
+
closeWebSocketServers(startupWebSocketServers),
|
|
267
|
+
sessionPortalServer?.shutdown() ?? Promise.resolve(),
|
|
268
|
+
new Promise((done) => server.close(() => done())),
|
|
269
|
+
Promise.resolve(daemonManagement?.pluginManagement?.dispose?.()),
|
|
270
|
+
Promise.resolve(pluginRuntime?.dispose()),
|
|
271
|
+
runnerManager.stop(),
|
|
272
|
+
]);
|
|
273
|
+
reject(error);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
server.once("error", rejectBeforeListening);
|
|
277
|
+
const finishStartup = async (server) => {
|
|
230
278
|
console.log(JSON.stringify({
|
|
231
279
|
level: config.LOG_LEVEL,
|
|
232
280
|
msg: "Harness agent server listening",
|
|
233
281
|
host: config.HOST,
|
|
234
282
|
port: config.PORT,
|
|
235
|
-
runtime: config.
|
|
236
|
-
model: resolveRuntimeModel(config, config.
|
|
283
|
+
runtime: config.DEFAULT_RUNTIME,
|
|
284
|
+
model: resolveRuntimeModel(config, config.DEFAULT_RUNTIME),
|
|
237
285
|
}));
|
|
238
|
-
const startup = pluginRuntime?.startPluginRuntimes() ?? Promise.resolve();
|
|
239
|
-
void startup.catch((error) => {
|
|
240
|
-
console.error(JSON.stringify({
|
|
241
|
-
level: "error",
|
|
242
|
-
type: "plugin-runtime",
|
|
243
|
-
event: "startup_failed",
|
|
244
|
-
error: error instanceof Error ? error.message : String(error),
|
|
245
|
-
}));
|
|
246
|
-
});
|
|
247
286
|
const webSocketServers = [
|
|
248
287
|
attachTerminalWs(server, {
|
|
249
288
|
runtimeConnectionResolver,
|
|
@@ -262,9 +301,36 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
262
301
|
if (control?.emulator) {
|
|
263
302
|
webSocketServers.push(attachEmulatorTouchWs(server, { emulator: control.emulator }));
|
|
264
303
|
}
|
|
304
|
+
startupWebSocketServers = webSocketServers;
|
|
305
|
+
if (sessionPortalConfig) {
|
|
306
|
+
sessionPortalServer = await startSessionPortal({
|
|
307
|
+
host: config.RYNX_SESSION_PORTAL_HOST,
|
|
308
|
+
port: config.RYNX_SESSION_PORTAL_PORT,
|
|
309
|
+
scheme: "http",
|
|
310
|
+
authorization: sessionPortalConfig.authorization,
|
|
311
|
+
controlOrigin: listeningHttpOrigin(server),
|
|
312
|
+
controlWebDist: sessionPortalConfig.controlWebDist,
|
|
313
|
+
shutdownSignal: controlShutdown.signal,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const startup = pluginRuntime?.startPluginRuntimes() ?? Promise.resolve();
|
|
317
|
+
void startup.catch((error) => {
|
|
318
|
+
console.error(JSON.stringify({
|
|
319
|
+
level: "error",
|
|
320
|
+
type: "plugin-runtime",
|
|
321
|
+
event: "startup_failed",
|
|
322
|
+
error: error instanceof Error ? error.message : String(error),
|
|
323
|
+
}));
|
|
324
|
+
});
|
|
265
325
|
resolve(bindServerLifecycle(server, {
|
|
266
|
-
fence: () =>
|
|
267
|
-
|
|
326
|
+
fence: () => {
|
|
327
|
+
controlShutdown.abort(new Error("Rynx server is shutting down"));
|
|
328
|
+
sessionPortalServer?.stopAccepting();
|
|
329
|
+
},
|
|
330
|
+
closeUpgrades: () => Promise.all([
|
|
331
|
+
closeWebSocketServers(webSocketServers),
|
|
332
|
+
sessionPortalServer?.shutdown() ?? Promise.resolve(),
|
|
333
|
+
]).then(() => undefined),
|
|
268
334
|
cleanup: async () => runCleanupSteps([
|
|
269
335
|
() => startup.catch(() => undefined),
|
|
270
336
|
() => Promise.resolve(daemonManagement?.pluginManagement?.dispose?.()),
|
|
@@ -272,9 +338,17 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
272
338
|
() => runnerManager.stop(),
|
|
273
339
|
]),
|
|
274
340
|
}));
|
|
275
|
-
}
|
|
341
|
+
};
|
|
276
342
|
});
|
|
277
343
|
}
|
|
344
|
+
function listeningHttpOrigin(server) {
|
|
345
|
+
const address = server.address();
|
|
346
|
+
if (!address || typeof address === "string") {
|
|
347
|
+
throw new Error("Control listener did not expose an HTTP address");
|
|
348
|
+
}
|
|
349
|
+
const host = address.address.includes(":") ? `[${address.address}]` : address.address;
|
|
350
|
+
return `http://${host}:${address.port}`;
|
|
351
|
+
}
|
|
278
352
|
function requireDaemonManagementToken(host) {
|
|
279
353
|
const token = host?.managementToken;
|
|
280
354
|
const bytes = typeof token === "string" ? Buffer.byteLength(token, "utf8") : 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdmissionReservation } from "@rynx-ai/core";
|
|
1
2
|
import { type RuntimeBrowserCloseParams, type RuntimeBrowserCloseResult, type RuntimeBrowserExecutionBackend, type RuntimeBrowserOpenParams, type RuntimeBrowserPageCreateParams, type RuntimeBrowserPageMutationParams, type RuntimeBrowserPageNavigateParams, type RuntimeBrowserState, type RuntimeBrowserStateGetParams } from "@rynx-ai/protocol/runtime-browser";
|
|
2
3
|
import type { RuntimeBrowserSurfaceFormat, RuntimeBrowserSurfaceInputEvent } from "@rynx-ai/protocol/runtime-browser-surface";
|
|
3
4
|
import { type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
|
|
@@ -155,6 +156,11 @@ export interface SessionBrowserServiceOptions {
|
|
|
155
156
|
pageIdFactory?: () => string;
|
|
156
157
|
/** Injectable boot nonce; production randomization fences stale pre-restart mutations. */
|
|
157
158
|
bootGenerationSeed?: number;
|
|
159
|
+
/**
|
|
160
|
+
* Synchronous daemon admission fence. A successful reservation is held until
|
|
161
|
+
* Browser open has either failed or registered the physical Host handle.
|
|
162
|
+
*/
|
|
163
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
158
164
|
}
|
|
159
165
|
export type SessionBrowserHostErrorCode = "unavailable" | "page_not_found" | "capacity" | "outcome_unknown";
|
|
160
166
|
/** Expected failure reported by a Browser Host implementation. */
|
|
@@ -181,6 +187,7 @@ export declare class SessionBrowserService {
|
|
|
181
187
|
private readonly observers;
|
|
182
188
|
private readonly pageIdFactory;
|
|
183
189
|
private readonly bootGenerationSeed;
|
|
190
|
+
private readonly admissionReserve?;
|
|
184
191
|
private shuttingDown;
|
|
185
192
|
private closeAllPromise?;
|
|
186
193
|
constructor(ports: SessionBrowserServicePorts, options?: SessionBrowserServiceOptions);
|
|
@@ -225,6 +232,7 @@ export declare class SessionBrowserService {
|
|
|
225
232
|
closeAll(): Promise<void>;
|
|
226
233
|
private closeEveryRecord;
|
|
227
234
|
private createBrowser;
|
|
235
|
+
private reserveOpenAdmission;
|
|
228
236
|
private closeUnavailableForRecreate;
|
|
229
237
|
private pageMutation;
|
|
230
238
|
private pageMutationParsed;
|
|
@@ -42,12 +42,14 @@ export class SessionBrowserService {
|
|
|
42
42
|
observers = new Set();
|
|
43
43
|
pageIdFactory;
|
|
44
44
|
bootGenerationSeed;
|
|
45
|
+
admissionReserve;
|
|
45
46
|
shuttingDown = false;
|
|
46
47
|
closeAllPromise;
|
|
47
48
|
constructor(ports, options = {}) {
|
|
48
49
|
this.ports = ports;
|
|
49
50
|
this.pageIdFactory = options.pageIdFactory ?? (() => `page_${randomUUID()}`);
|
|
50
51
|
this.bootGenerationSeed = options.bootGenerationSeed ?? randomBootGenerationSeed();
|
|
52
|
+
this.admissionReserve = options.admissionReserve;
|
|
51
53
|
if (!Number.isSafeInteger(this.bootGenerationSeed) ||
|
|
52
54
|
this.bootGenerationSeed < 0 ||
|
|
53
55
|
this.bootGenerationSeed >= Number.MAX_SAFE_INTEGER) {
|
|
@@ -224,29 +226,35 @@ export class SessionBrowserService {
|
|
|
224
226
|
});
|
|
225
227
|
}
|
|
226
228
|
async open(input) {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
await this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (existing?.status === "ready") {
|
|
236
|
-
try {
|
|
237
|
-
await this.reconcileForRead(existing);
|
|
229
|
+
const admission = this.reserveOpenAdmission();
|
|
230
|
+
try {
|
|
231
|
+
const params = parseInput(() => parseRuntimeBrowserOpenParams(input), "Browser open request is invalid");
|
|
232
|
+
return await this.exclusive(params.sessionId, async () => {
|
|
233
|
+
this.ensureRunning();
|
|
234
|
+
await this.ensureSessionExists(params.sessionId);
|
|
235
|
+
const existing = this.records.get(params.sessionId);
|
|
236
|
+
if (existing?.status === "opening") {
|
|
238
237
|
return this.project(existing);
|
|
239
238
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
if (existing?.status === "ready") {
|
|
240
|
+
try {
|
|
241
|
+
await this.reconcileForRead(existing);
|
|
242
|
+
return this.project(existing);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// An App Host may have gone away immediately before this open. The
|
|
246
|
+
// failed reconciliation marks the old generation unavailable, so an
|
|
247
|
+
// idempotent open can recreate it on the current preferred Host.
|
|
248
|
+
}
|
|
244
249
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
+
if (existing)
|
|
251
|
+
await this.closeUnavailableForRecreate(existing);
|
|
252
|
+
return this.createBrowser(params);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
admission?.release();
|
|
257
|
+
}
|
|
250
258
|
}
|
|
251
259
|
async close(input) {
|
|
252
260
|
const params = parseInput(() => parseRuntimeBrowserCloseParams(input), "Browser close request is invalid");
|
|
@@ -438,6 +446,15 @@ export class SessionBrowserService {
|
|
|
438
446
|
throw new SessionBrowserServiceError("host_failure", "Browser Host state could not be initialized", { cause: error });
|
|
439
447
|
}
|
|
440
448
|
}
|
|
449
|
+
reserveOpenAdmission() {
|
|
450
|
+
if (!this.admissionReserve)
|
|
451
|
+
return undefined;
|
|
452
|
+
const admission = this.admissionReserve();
|
|
453
|
+
if (!admission) {
|
|
454
|
+
throw new SessionBrowserServiceError("shutting_down", "Browser service is shutting down");
|
|
455
|
+
}
|
|
456
|
+
return admission;
|
|
457
|
+
}
|
|
441
458
|
async closeUnavailableForRecreate(record) {
|
|
442
459
|
record.status = "closing";
|
|
443
460
|
try {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server as HttpServer } from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import Koa from "koa";
|
|
4
|
+
export declare const SESSION_PORTAL_HEADER = "x-rynx-session-portal";
|
|
5
|
+
export declare const SESSION_PORTAL_QUERY = "portal";
|
|
6
|
+
export interface SessionPortalAuthorization {
|
|
7
|
+
portalId: string;
|
|
8
|
+
pluginId: string;
|
|
9
|
+
scope: "plugin_sessions" | "single_session";
|
|
10
|
+
access: "read_only" | "read_write";
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SessionPortalAuthorizationPorts {
|
|
15
|
+
redeem(ticket: string): (SessionPortalAuthorization & {
|
|
16
|
+
secret: string;
|
|
17
|
+
}) | undefined;
|
|
18
|
+
authorize(portalId: string, secret: string): SessionPortalAuthorization | undefined;
|
|
19
|
+
ownsSession(pluginId: string, sessionId: string): boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SessionPortalOptions {
|
|
22
|
+
host: string;
|
|
23
|
+
port: number;
|
|
24
|
+
scheme: "http" | "https";
|
|
25
|
+
authorization: SessionPortalAuthorizationPorts;
|
|
26
|
+
controlOrigin: string;
|
|
27
|
+
controlWebDist: string;
|
|
28
|
+
shutdownSignal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export interface SessionPortalServer {
|
|
31
|
+
server: HttpServer;
|
|
32
|
+
stopAccepting(): void;
|
|
33
|
+
shutdown(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Session Portal is a narrow adapter in front of the one Control service. It
|
|
36
|
+
* serves the native SPA, validates Portal grants, and forwards only authorized
|
|
37
|
+
* Session HTTP calls to Control; it owns no Session or Runtime handlers. */
|
|
38
|
+
export declare function createSessionPortalApp(options: SessionPortalOptions): Koa;
|
|
39
|
+
export declare function startSessionPortal(options: SessionPortalOptions): Promise<SessionPortalServer>;
|
|
40
|
+
export declare function authorizeSessionPortalWebSocket(request: IncomingMessage, ports: SessionPortalAuthorizationPorts, runtimeSelector: string, sessionId: string): SessionPortalAuthorization | undefined;
|
|
41
|
+
export declare function rejectSessionPortalUpgrade(socket: Duplex): void;
|
|
42
|
+
export declare function scheduleSessionPortalExpiry(expiresAt: string, onExpire: () => void): () => void;
|