@phreshos/node 0.1.12 → 0.1.14
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 +31 -36
- package/dist/development-client.d.ts +20 -0
- package/dist/development-client.js +259 -0
- package/dist/events.d.ts +4 -4
- package/dist/events.js +18 -29
- package/dist/main.d.ts +2 -2
- package/dist/main.js +2 -2
- package/dist/process-tree.d.ts +10 -0
- package/dist/process-tree.js +67 -0
- package/dist/program-resources.d.ts +6 -6
- package/dist/program-resources.js +12 -26
- package/dist/project.d.ts +5 -3
- package/dist/project.js +53 -13
- package/dist/representation.d.ts +100 -0
- package/dist/representation.js +331 -0
- package/dist/shell.d.ts +3 -0
- package/dist/shell.js +125 -0
- package/dist/storage.d.ts +6 -2
- package/dist/storage.js +27 -8
- package/dist/system.d.ts +26 -20
- package/dist/system.js +273 -300
- package/dist/traffic.d.ts +6 -6
- package/dist/traffic.js +15 -33
- package/dist/transport.d.ts +20 -10
- package/dist/transport.js +106 -110
- package/dist/uploads.d.ts +4 -1
- package/dist/uploads.js +20 -5
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +15 -0
- package/package.json +4 -2
package/dist/project.js
CHANGED
|
@@ -6,7 +6,9 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { delimiter, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
8
8
|
import { createJiti } from "jiti";
|
|
9
|
+
import DevelopmentClient from "./development-client.js";
|
|
9
10
|
const configFile = "phresh.config.ts";
|
|
11
|
+
const defaultClientPort = 5200;
|
|
10
12
|
/** One loaded Program authoring project rooted at an absolute directory. */
|
|
11
13
|
export class Project {
|
|
12
14
|
directory;
|
|
@@ -52,10 +54,10 @@ export class Project {
|
|
|
52
54
|
developmentDefinition() {
|
|
53
55
|
return this.definition("development");
|
|
54
56
|
}
|
|
55
|
-
definition(mode) {
|
|
57
|
+
definition(mode, developmentClientUrl) {
|
|
56
58
|
const config = this.config;
|
|
57
59
|
const server = serverHalf(config.server, mode);
|
|
58
|
-
const client = clientHalf(config.client, mode);
|
|
60
|
+
const client = clientHalf(config.client, mode, developmentClientUrl);
|
|
59
61
|
if (mode === "development" && !config.server?.development && !config.client?.development) {
|
|
60
62
|
throw new Error("Nothing here says how this Program is developed");
|
|
61
63
|
}
|
|
@@ -83,7 +85,8 @@ export class Project {
|
|
|
83
85
|
size: config.client?.size,
|
|
84
86
|
position: config.client?.position,
|
|
85
87
|
layer: config.client?.layer,
|
|
86
|
-
minimize: config.client?.minimize
|
|
88
|
+
minimize: config.client?.minimize,
|
|
89
|
+
permissions: config.client?.permissions
|
|
87
90
|
} }
|
|
88
91
|
};
|
|
89
92
|
}
|
|
@@ -115,9 +118,34 @@ export class Project {
|
|
|
115
118
|
await this.build();
|
|
116
119
|
return await this.run(system, this.productionDefinition(), options);
|
|
117
120
|
}
|
|
118
|
-
/**
|
|
121
|
+
/** Prepare this Project's Client development source and return its Process lifecycle. */
|
|
119
122
|
async dev(system, options = {}) {
|
|
120
|
-
|
|
123
|
+
const development = this.config.client?.development;
|
|
124
|
+
const startsClient = Boolean(development && (this.config.client?.start ?? true));
|
|
125
|
+
if (!startsClient || !development)
|
|
126
|
+
return await this.run(system, this.developmentDefinition(), options);
|
|
127
|
+
if (development.startCommand)
|
|
128
|
+
return this.developmentRun(system, development, options);
|
|
129
|
+
const prepared = await this.prepareDevelopment(system, development, options);
|
|
130
|
+
return prepared.client.supervise(prepared.lifecycle);
|
|
131
|
+
}
|
|
132
|
+
async *developmentRun(system, development, options) {
|
|
133
|
+
const prepared = await this.prepareDevelopment(system, development, options);
|
|
134
|
+
yield* prepared.client.supervise(prepared.lifecycle);
|
|
135
|
+
}
|
|
136
|
+
async prepareDevelopment(system, development, options) {
|
|
137
|
+
const client = await DevelopmentClient.prepare(development, this.directory);
|
|
138
|
+
const program = await system.forceCreateProgram(this.definition("development", client.url));
|
|
139
|
+
try {
|
|
140
|
+
await client.start(program.assetId, options.signal);
|
|
141
|
+
const lifecycle = program.process.run({ options: options.options ?? {} }, { signal: client.processSignal(options.signal) });
|
|
142
|
+
return { client, lifecycle };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
await client.dispose(error);
|
|
146
|
+
await program.forget().catch(() => undefined);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
121
149
|
}
|
|
122
150
|
/** Build this Project and return its Program installation generator. */
|
|
123
151
|
async install(system) {
|
|
@@ -171,18 +199,20 @@ function serverHalf(half, mode) {
|
|
|
171
199
|
function serverExecution(server) {
|
|
172
200
|
return server.startCommand !== undefined ? { startCommand: server.startCommand } : { entryFile: server.entryFile };
|
|
173
201
|
}
|
|
174
|
-
function clientHalf(half, mode) {
|
|
202
|
+
function clientHalf(half, mode, developmentUrl) {
|
|
175
203
|
if (!half)
|
|
176
204
|
return null;
|
|
177
205
|
const { development, ...declared } = half;
|
|
178
|
-
return mode === "development" && development
|
|
206
|
+
return mode === "development" && development
|
|
207
|
+
? { ...declared, location: developmentUrl ?? development.url ?? `http://localhost:${defaultClientPort}/` }
|
|
208
|
+
: declared;
|
|
179
209
|
}
|
|
180
210
|
function validateConfig(config) {
|
|
181
211
|
if (typeof config.identity !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.identity)) {
|
|
182
212
|
throw new Error("A Program's identity must be kebab-case");
|
|
183
213
|
}
|
|
184
214
|
if (!config.server && !config.client)
|
|
185
|
-
throw new Error("A Program must have a Server
|
|
215
|
+
throw new Error("A Program must have a Server Endpoint, a Client Endpoint, or both");
|
|
186
216
|
for (const field of ["name", "version", "description", "icon", "agent", "website"]) {
|
|
187
217
|
if (config[field] !== undefined && typeof config[field] !== "string")
|
|
188
218
|
throw new Error(`A Program's ${field} must be text`);
|
|
@@ -212,14 +242,23 @@ function validateConfig(config) {
|
|
|
212
242
|
throw new Error("A Program's default Process must start a Server Endpoint, a Client Endpoint, or both");
|
|
213
243
|
}
|
|
214
244
|
if (config.server)
|
|
215
|
-
execution(config.server, "A Server
|
|
245
|
+
execution(config.server, "A Server Endpoint");
|
|
216
246
|
if (config.server?.development)
|
|
217
247
|
execution(config.server.development, "server.development");
|
|
218
248
|
if (config.client?.layer !== undefined && !layers.includes(config.client.layer)) {
|
|
219
|
-
throw new Error(`A Client
|
|
249
|
+
throw new Error(`A Client Endpoint's layer must be one of ${layers.join(", ")}`);
|
|
220
250
|
}
|
|
221
|
-
if (config.client?.development
|
|
222
|
-
|
|
251
|
+
if (config.client?.development) {
|
|
252
|
+
const development = config.client.development;
|
|
253
|
+
if (development.url !== undefined && !httpUrl(development.url)) {
|
|
254
|
+
throw new Error("client.development.url must be a valid HTTP or HTTPS URL");
|
|
255
|
+
}
|
|
256
|
+
if (development.startCommand !== undefined && (typeof development.startCommand !== "string" || !development.startCommand.trim())) {
|
|
257
|
+
throw new Error("client.development.startCommand must be non-empty text");
|
|
258
|
+
}
|
|
259
|
+
if (development.url === undefined && development.startCommand === undefined) {
|
|
260
|
+
throw new Error("client.development must declare a URL or start command");
|
|
261
|
+
}
|
|
223
262
|
}
|
|
224
263
|
for (const [name, value] of [["size", config.client?.size], ["position", config.client?.position]]) {
|
|
225
264
|
if (value === undefined)
|
|
@@ -286,7 +325,8 @@ function packageDefinition(config, version) {
|
|
|
286
325
|
size: config.client.size,
|
|
287
326
|
position: config.client.position,
|
|
288
327
|
layer: config.client.layer,
|
|
289
|
-
minimize: config.client.minimize
|
|
328
|
+
minimize: config.client.minimize,
|
|
329
|
+
permissions: config.client.permissions
|
|
290
330
|
} }
|
|
291
331
|
};
|
|
292
332
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Appearance, ClientDeclaration, EndpointDeclaration, ServiceKey, WindowGeometry, WindowLayer } from "@phreshos/core";
|
|
2
|
+
import type { GatewayConnection } from "./transport.js";
|
|
3
|
+
export interface ProgramState {
|
|
4
|
+
reference: string;
|
|
5
|
+
identity: string;
|
|
6
|
+
assetId: string;
|
|
7
|
+
installed: boolean;
|
|
8
|
+
name: string;
|
|
9
|
+
version: string | null;
|
|
10
|
+
description: string | null;
|
|
11
|
+
hasAgent: boolean;
|
|
12
|
+
server: EndpointDeclaration | null;
|
|
13
|
+
client: ClientDeclaration | null;
|
|
14
|
+
}
|
|
15
|
+
export interface WindowState {
|
|
16
|
+
title: string;
|
|
17
|
+
position: WindowGeometry["position"];
|
|
18
|
+
size: WindowGeometry["size"];
|
|
19
|
+
depth: number;
|
|
20
|
+
minimized: boolean;
|
|
21
|
+
layer: WindowLayer;
|
|
22
|
+
location: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ProcessState {
|
|
25
|
+
reference: string;
|
|
26
|
+
identity: string;
|
|
27
|
+
name: string | null;
|
|
28
|
+
program: string;
|
|
29
|
+
parent: ProcessAddress | null;
|
|
30
|
+
options: Record<string, string>;
|
|
31
|
+
startedAt: Date;
|
|
32
|
+
server: {
|
|
33
|
+
ready: boolean;
|
|
34
|
+
service: boolean;
|
|
35
|
+
} | null;
|
|
36
|
+
client: {
|
|
37
|
+
service: boolean;
|
|
38
|
+
sameOrigin: boolean;
|
|
39
|
+
window: WindowState;
|
|
40
|
+
} | null;
|
|
41
|
+
}
|
|
42
|
+
export interface ProcessAddress {
|
|
43
|
+
reference: string;
|
|
44
|
+
identity: string;
|
|
45
|
+
}
|
|
46
|
+
export type Observation = Readonly<{
|
|
47
|
+
scope: "endpoint";
|
|
48
|
+
process: string;
|
|
49
|
+
endpoint: "server" | "client";
|
|
50
|
+
event: string | null;
|
|
51
|
+
}> | Readonly<{
|
|
52
|
+
scope: "traffic";
|
|
53
|
+
process: string;
|
|
54
|
+
endpoint: "server" | "client";
|
|
55
|
+
kind: "publish" | "ask" | "answer";
|
|
56
|
+
event: string | null;
|
|
57
|
+
}> | Readonly<{
|
|
58
|
+
scope: "service";
|
|
59
|
+
key: ServiceKey;
|
|
60
|
+
kind: "events" | "lifecycle";
|
|
61
|
+
event: string | null;
|
|
62
|
+
}>;
|
|
63
|
+
type Listener = (...values: unknown[]) => unknown;
|
|
64
|
+
interface CommandEvent {
|
|
65
|
+
event?: string;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
/** A connection-owned, live representation of the authoritative System model. */
|
|
69
|
+
export default class SystemRepresentation {
|
|
70
|
+
private readonly connection;
|
|
71
|
+
readonly authorization: string;
|
|
72
|
+
readonly programs: Map<string, ProgramState>;
|
|
73
|
+
readonly processes: Map<string, ProcessState>;
|
|
74
|
+
appearance: Appearance;
|
|
75
|
+
private readonly listeners;
|
|
76
|
+
private readonly release;
|
|
77
|
+
constructor(connection: GatewayConnection);
|
|
78
|
+
activate(): void;
|
|
79
|
+
close(): void;
|
|
80
|
+
call<Result = unknown>(event: string, ...values: unknown[]): Promise<Result>;
|
|
81
|
+
on(event: string, listener: Listener): () => void;
|
|
82
|
+
follow(observation: Observation, listener: (event: string, value: unknown) => unknown, impossible?: (error: Error) => void): () => void;
|
|
83
|
+
/** Stream one Program command through the live LinkManager connection. */
|
|
84
|
+
command(operation: "install" | "uninstall" | "run", subject: ProgramAddress, value: unknown, signal?: AbortSignal): AsyncGenerator<CommandEvent, void, unknown>;
|
|
85
|
+
private followModel;
|
|
86
|
+
private arriveProgram;
|
|
87
|
+
private uninstallProgram;
|
|
88
|
+
private forgetProgram;
|
|
89
|
+
private createProcess;
|
|
90
|
+
private serverReady;
|
|
91
|
+
private changeEndpoint;
|
|
92
|
+
private exitProcess;
|
|
93
|
+
private changeWindow;
|
|
94
|
+
private emit;
|
|
95
|
+
}
|
|
96
|
+
export type ProgramAddress = Readonly<{
|
|
97
|
+
identity: string;
|
|
98
|
+
reference: string;
|
|
99
|
+
}>;
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const maximumStreamQueue = 256;
|
|
3
|
+
/** A connection-owned, live representation of the authoritative System model. */
|
|
4
|
+
export default class SystemRepresentation {
|
|
5
|
+
connection;
|
|
6
|
+
authorization;
|
|
7
|
+
programs = new Map();
|
|
8
|
+
processes = new Map();
|
|
9
|
+
appearance;
|
|
10
|
+
listeners = new Map();
|
|
11
|
+
release = [];
|
|
12
|
+
constructor(connection) {
|
|
13
|
+
this.connection = connection;
|
|
14
|
+
const session = ownerSession(connection.session);
|
|
15
|
+
this.authorization = session.authorization;
|
|
16
|
+
this.appearance = session.linkManager.appearance.value;
|
|
17
|
+
for (const [, value] of session.authManager.programManager.programs) {
|
|
18
|
+
const program = programState(value);
|
|
19
|
+
this.programs.set(program.identity, program);
|
|
20
|
+
}
|
|
21
|
+
for (const [, value] of session.authManager.processManager.processes) {
|
|
22
|
+
const process = processState(value);
|
|
23
|
+
this.processes.set(process.identity, process);
|
|
24
|
+
}
|
|
25
|
+
this.followModel(session.linkManager.appearance.key);
|
|
26
|
+
}
|
|
27
|
+
activate() { this.connection.activate(); }
|
|
28
|
+
close() {
|
|
29
|
+
for (const stop of this.release.splice(0))
|
|
30
|
+
stop();
|
|
31
|
+
this.listeners.clear();
|
|
32
|
+
}
|
|
33
|
+
call(event, ...values) {
|
|
34
|
+
return this.connection.call(`/auth${event}`, this.authorization, ...values);
|
|
35
|
+
}
|
|
36
|
+
on(event, listener) {
|
|
37
|
+
const listeners = this.listeners.get(event) ?? new Set();
|
|
38
|
+
listeners.add(listener);
|
|
39
|
+
this.listeners.set(event, listeners);
|
|
40
|
+
return () => {
|
|
41
|
+
listeners.delete(listener);
|
|
42
|
+
if (!listeners.size)
|
|
43
|
+
this.listeners.delete(event);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
follow(observation, listener, impossible) {
|
|
47
|
+
const subscription = randomUUID();
|
|
48
|
+
let active = true;
|
|
49
|
+
const stopEvent = this.connection.subscribe("/auth/process/followed", (received, event, value) => {
|
|
50
|
+
if (active && received === subscription && typeof event === "string")
|
|
51
|
+
listener(event, value);
|
|
52
|
+
});
|
|
53
|
+
const stopImpossible = this.connection.subscribe("/auth/process/impossible", (received, reason) => {
|
|
54
|
+
if (!active || received !== subscription)
|
|
55
|
+
return;
|
|
56
|
+
stop();
|
|
57
|
+
impossible?.(new Error(String(reason)));
|
|
58
|
+
});
|
|
59
|
+
const stop = () => {
|
|
60
|
+
if (!active)
|
|
61
|
+
return;
|
|
62
|
+
active = false;
|
|
63
|
+
stopEvent();
|
|
64
|
+
stopImpossible();
|
|
65
|
+
void this.call("/process/unfollow", subscription).catch(() => undefined);
|
|
66
|
+
};
|
|
67
|
+
void this.call("/process/follow", subscription, observation).catch(error => {
|
|
68
|
+
if (!active)
|
|
69
|
+
return;
|
|
70
|
+
stop();
|
|
71
|
+
impossible?.(exception(error));
|
|
72
|
+
});
|
|
73
|
+
return stop;
|
|
74
|
+
}
|
|
75
|
+
/** Stream one Program command through the live LinkManager connection. */
|
|
76
|
+
command(operation, subject, value, signal) {
|
|
77
|
+
const stream = randomUUID();
|
|
78
|
+
const queue = [];
|
|
79
|
+
let wake = null;
|
|
80
|
+
let ended = false;
|
|
81
|
+
let failure = null;
|
|
82
|
+
const stopOutput = this.connection.subscribe("/auth/program/command-output", (received, output) => {
|
|
83
|
+
if (received !== stream || ended)
|
|
84
|
+
return;
|
|
85
|
+
if (!record(output))
|
|
86
|
+
failure = new Error("The System returned an invalid Program command event");
|
|
87
|
+
else if (queue.length >= maximumStreamQueue)
|
|
88
|
+
failure = new Error(`System stream queue exceeded its capacity of ${maximumStreamQueue}`);
|
|
89
|
+
else
|
|
90
|
+
queue.push(output);
|
|
91
|
+
wake?.();
|
|
92
|
+
wake = null;
|
|
93
|
+
});
|
|
94
|
+
const cancel = () => { void this.call("/program/command-cancel", stream).catch(() => undefined); };
|
|
95
|
+
const abort = () => {
|
|
96
|
+
failure = abortReason(signal);
|
|
97
|
+
cancel();
|
|
98
|
+
wake?.();
|
|
99
|
+
wake = null;
|
|
100
|
+
};
|
|
101
|
+
if (signal?.aborted) {
|
|
102
|
+
failure = abortReason(signal);
|
|
103
|
+
ended = true;
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
107
|
+
const running = ended
|
|
108
|
+
? Promise.resolve()
|
|
109
|
+
: this.call("/program/command", stream, operation, subject, value, "").then(() => { ended = true; wake?.(); wake = null; }, error => { failure = signal?.aborted ? abortReason(signal) : exception(error); ended = true; wake?.(); wake = null; });
|
|
110
|
+
return (async function* () {
|
|
111
|
+
try {
|
|
112
|
+
while (true) {
|
|
113
|
+
if (queue.length) {
|
|
114
|
+
yield queue.shift();
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (failure)
|
|
118
|
+
throw failure;
|
|
119
|
+
if (ended)
|
|
120
|
+
return;
|
|
121
|
+
await new Promise(resolve => { wake = resolve; });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
stopOutput();
|
|
126
|
+
signal?.removeEventListener("abort", abort);
|
|
127
|
+
if (!ended)
|
|
128
|
+
cancel();
|
|
129
|
+
await running.catch(() => undefined);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
}
|
|
133
|
+
followModel(appearance) {
|
|
134
|
+
const subscribe = (event, listener) => this.release.push(this.connection.subscribe(event, listener));
|
|
135
|
+
subscribe(`property-update:${appearance}`, value => {
|
|
136
|
+
this.appearance = value;
|
|
137
|
+
this.emit("appearance", this.appearance);
|
|
138
|
+
});
|
|
139
|
+
subscribe("/auth/program/create", value => this.arriveProgram("create", value));
|
|
140
|
+
subscribe("/auth/program/install", value => this.arriveProgram("install", value));
|
|
141
|
+
subscribe("/auth/program/uninstall", (value, everything) => this.uninstallProgram(value, everything === true));
|
|
142
|
+
subscribe("/auth/program/forget", value => this.forgetProgram(value));
|
|
143
|
+
subscribe("/auth/process/created", value => this.createProcess(value));
|
|
144
|
+
subscribe("/auth/process/server-ready", identity => this.serverReady(identity));
|
|
145
|
+
subscribe("/auth/process/server-start", (identity, value) => this.changeEndpoint(identity, "server", value));
|
|
146
|
+
subscribe("/auth/process/server-stop", (identity, value) => this.changeEndpoint(identity, "server", value, false));
|
|
147
|
+
subscribe("/auth/process/client-start", (identity, value) => this.changeEndpoint(identity, "client", value));
|
|
148
|
+
subscribe("/auth/process/client-stop", (identity, value) => this.changeEndpoint(identity, "client", value, false));
|
|
149
|
+
subscribe("/auth/process/client-access", (identity, value) => this.changeEndpoint(identity, "client", value));
|
|
150
|
+
subscribe("/auth/process/exited", (value, code, signal) => this.exitProcess(value, code, signal));
|
|
151
|
+
for (const event of ["move", "resize", "geometry", "change-title", "raise", "minimize"]) {
|
|
152
|
+
subscribe(`/auth/process/${event}`, value => this.changeWindow(event, value));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
arriveProgram(event, value) {
|
|
156
|
+
const program = programState(value);
|
|
157
|
+
this.programs.set(program.identity, program);
|
|
158
|
+
this.emit(`program:${program.reference}:change`, program);
|
|
159
|
+
this.emit(`program:${event}`, program);
|
|
160
|
+
}
|
|
161
|
+
uninstallProgram(value, everything) {
|
|
162
|
+
const program = programState(value);
|
|
163
|
+
this.programs.set(program.identity, program);
|
|
164
|
+
this.emit(`program:${program.reference}:change`, program);
|
|
165
|
+
this.emit(`program:${program.reference}:uninstall`, everything);
|
|
166
|
+
this.emit("program:uninstall", program, everything);
|
|
167
|
+
}
|
|
168
|
+
forgetProgram(value) {
|
|
169
|
+
if (typeof value !== "string")
|
|
170
|
+
return;
|
|
171
|
+
const program = this.programs.get(value);
|
|
172
|
+
if (!program)
|
|
173
|
+
return;
|
|
174
|
+
this.programs.delete(value);
|
|
175
|
+
this.emit(`program:${program.reference}:forget`);
|
|
176
|
+
this.emit("program:forget", program);
|
|
177
|
+
}
|
|
178
|
+
createProcess(value) {
|
|
179
|
+
const process = processState(value);
|
|
180
|
+
this.processes.set(process.identity, process);
|
|
181
|
+
this.emit("process:create", process);
|
|
182
|
+
this.emit(`program:${process.program}:process:create`, process);
|
|
183
|
+
}
|
|
184
|
+
serverReady(value) {
|
|
185
|
+
if (typeof value !== "string")
|
|
186
|
+
return;
|
|
187
|
+
const process = this.processes.get(value);
|
|
188
|
+
if (!process?.server)
|
|
189
|
+
return;
|
|
190
|
+
process.server.ready = true;
|
|
191
|
+
this.emit(`endpoint:${process.reference}:server:ready`);
|
|
192
|
+
}
|
|
193
|
+
changeEndpoint(identity, endpoint, value, running = true) {
|
|
194
|
+
if (typeof identity !== "string")
|
|
195
|
+
return;
|
|
196
|
+
const current = this.processes.get(identity);
|
|
197
|
+
if (!current)
|
|
198
|
+
return;
|
|
199
|
+
const incoming = processState(value);
|
|
200
|
+
current.server = incoming.server;
|
|
201
|
+
current.client = incoming.client;
|
|
202
|
+
this.emit(`endpoint:${current.reference}:${endpoint}:${running ? "start" : "stop"}`);
|
|
203
|
+
this.emit(`process:${current.reference}:change`, current);
|
|
204
|
+
}
|
|
205
|
+
exitProcess(value, code, signal) {
|
|
206
|
+
const received = processState(value);
|
|
207
|
+
const process = this.processes.get(received.identity) ?? received;
|
|
208
|
+
this.processes.delete(process.identity);
|
|
209
|
+
const exit = {
|
|
210
|
+
status: typeof signal === "string" ? "signaled" : "exited",
|
|
211
|
+
code: typeof code === "number" ? code : null,
|
|
212
|
+
signal: typeof signal === "string" ? signal : null
|
|
213
|
+
};
|
|
214
|
+
this.emit(`process:${process.reference}:exit`, exit);
|
|
215
|
+
this.emit("process:exit", process, exit);
|
|
216
|
+
this.emit(`program:${process.program}:process:exit`, process, exit);
|
|
217
|
+
}
|
|
218
|
+
changeWindow(event, value) {
|
|
219
|
+
if (!record(value) || typeof value.identity !== "string" || !record(value.window))
|
|
220
|
+
return;
|
|
221
|
+
const process = this.processes.get(value.identity);
|
|
222
|
+
if (!process?.client)
|
|
223
|
+
return;
|
|
224
|
+
process.client.window = windowState(value.window);
|
|
225
|
+
this.emit(`window:${process.identity}:${camel(event)}`, windowMessage(event, process));
|
|
226
|
+
this.emit(`process:${process.reference}:change`, process);
|
|
227
|
+
}
|
|
228
|
+
emit(event, ...values) {
|
|
229
|
+
for (const listener of this.listeners.get(event) ?? [])
|
|
230
|
+
listener(...values);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function ownerSession(value) {
|
|
234
|
+
if (!record(value) || typeof value.authorization !== "string")
|
|
235
|
+
throw new Error("The System Gateway returned an invalid owner session");
|
|
236
|
+
const linkManager = value.linkManager;
|
|
237
|
+
const authManager = value.authManager;
|
|
238
|
+
if (!record(linkManager) || !record(linkManager.appearance) || typeof linkManager.appearance.key !== "string")
|
|
239
|
+
throw new Error("The System Gateway returned invalid Appearance state");
|
|
240
|
+
if (!record(authManager) || !record(authManager.programManager) || !record(authManager.processManager))
|
|
241
|
+
throw new Error("The System Gateway returned an invalid System state");
|
|
242
|
+
const programs = authManager.programManager.programs;
|
|
243
|
+
const processes = authManager.processManager.processes;
|
|
244
|
+
if (!Array.isArray(programs) || !Array.isArray(processes))
|
|
245
|
+
throw new Error("The System Gateway returned invalid domain collections");
|
|
246
|
+
return {
|
|
247
|
+
authorization: value.authorization,
|
|
248
|
+
linkManager: { appearance: { key: linkManager.appearance.key, value: linkManager.appearance.value } },
|
|
249
|
+
authManager: {
|
|
250
|
+
programManager: { programs: programs },
|
|
251
|
+
processManager: { processes: processes }
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function programState(value) {
|
|
256
|
+
if (!record(value) || typeof value.reference !== "string" || typeof value.identity !== "string" || typeof value.assetId !== "string" || typeof value.name !== "string") {
|
|
257
|
+
throw new Error("The System returned an invalid Program");
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
reference: value.reference,
|
|
261
|
+
identity: value.identity,
|
|
262
|
+
assetId: value.assetId,
|
|
263
|
+
installed: value.installed === true,
|
|
264
|
+
name: value.name,
|
|
265
|
+
version: typeof value.version === "string" ? value.version : null,
|
|
266
|
+
description: typeof value.description === "string" ? value.description : null,
|
|
267
|
+
hasAgent: value.hasAgent === true,
|
|
268
|
+
server: value.server,
|
|
269
|
+
client: value.client
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function processState(value) {
|
|
273
|
+
if (!record(value) || typeof value.reference !== "string" || typeof value.identity !== "string" || typeof value.program !== "string" || !record(value.options)) {
|
|
274
|
+
throw new Error("The System returned an invalid Process");
|
|
275
|
+
}
|
|
276
|
+
const startedAt = value.startedAt instanceof Date ? value.startedAt : new Date(String(value.startedAt));
|
|
277
|
+
if (Number.isNaN(startedAt.getTime()))
|
|
278
|
+
throw new Error("The System returned an invalid Process start time");
|
|
279
|
+
return {
|
|
280
|
+
reference: value.reference,
|
|
281
|
+
identity: value.identity,
|
|
282
|
+
name: typeof value.name === "string" ? value.name : null,
|
|
283
|
+
program: value.program,
|
|
284
|
+
parent: address(value.parent),
|
|
285
|
+
options: value.options,
|
|
286
|
+
startedAt,
|
|
287
|
+
server: record(value.server) ? { ready: value.server.ready === true, service: value.server.service === true } : null,
|
|
288
|
+
client: record(value.client) ? {
|
|
289
|
+
service: value.client.service === true,
|
|
290
|
+
sameOrigin: value.client.sameOrigin === true,
|
|
291
|
+
window: windowState(value.client.window)
|
|
292
|
+
} : null
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function windowState(value) {
|
|
296
|
+
if (!record(value) || typeof value.title !== "string" || typeof value.location !== "string" || typeof value.depth !== "number" || typeof value.minimized !== "boolean") {
|
|
297
|
+
throw new Error("The System returned an invalid Window");
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
title: value.title,
|
|
301
|
+
position: value.position,
|
|
302
|
+
size: value.size,
|
|
303
|
+
depth: value.depth,
|
|
304
|
+
minimized: value.minimized,
|
|
305
|
+
layer: value.layer,
|
|
306
|
+
location: value.location
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function address(value) {
|
|
310
|
+
if (!record(value) || typeof value.reference !== "string" || typeof value.identity !== "string")
|
|
311
|
+
return null;
|
|
312
|
+
return { reference: value.reference, identity: value.identity };
|
|
313
|
+
}
|
|
314
|
+
function windowMessage(event, process) {
|
|
315
|
+
const window = process.client.window;
|
|
316
|
+
if (event === "move")
|
|
317
|
+
return window.position;
|
|
318
|
+
if (event === "resize")
|
|
319
|
+
return window.size;
|
|
320
|
+
if (event === "geometry")
|
|
321
|
+
return { position: window.position, size: window.size };
|
|
322
|
+
if (event === "change-title")
|
|
323
|
+
return window.title;
|
|
324
|
+
if (event === "minimize")
|
|
325
|
+
return window.minimized;
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
function camel(value) { return value === "change-title" ? "changeTitle" : value; }
|
|
329
|
+
function record(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
330
|
+
function exception(error) { return error instanceof Error ? error : new Error(String(error)); }
|
|
331
|
+
function abortReason(signal) { return signal.reason instanceof Error ? signal.reason : new Error("The operation was cancelled"); }
|
package/dist/shell.d.ts
ADDED