@phreshos/server 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +99 -8
- package/dist/channel.d.ts +5 -2
- package/dist/channel.js +3 -0
- package/dist/current.d.ts +13 -10
- package/dist/current.js +37 -9
- package/dist/domain.d.ts +71 -28
- package/dist/domain.js +226 -92
- package/dist/handle-registry.d.ts +7 -0
- package/dist/handle-registry.js +24 -0
- package/dist/host.d.ts +58 -10
- package/dist/host.js +12 -9
- package/dist/main.d.ts +4 -3
- package/dist/main.js +2 -1
- package/dist/permissions.d.ts +3 -0
- package/dist/permissions.js +20 -0
- package/dist/startup.d.ts +13 -0
- package/dist/startup.js +16 -0
- package/dist/storage.d.ts +4 -3
- package/dist/theme.d.ts +15 -0
- package/dist/theme.js +32 -0
- package/dist/wallpaper.d.ts +18 -0
- package/dist/wallpaper.js +25 -0
- package/dist/wire.d.ts +10 -5
- package/dist/wire.js +33 -8
- package/package.json +29 -4
package/dist/domain.js
CHANGED
|
@@ -1,142 +1,176 @@
|
|
|
1
|
-
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer
|
|
1
|
+
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer } from "@phreshos/core";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import Events from "./events.js";
|
|
4
4
|
import Deadline from "./deadline.js";
|
|
5
|
+
import HandleRegistry from "./handle-registry.js";
|
|
5
6
|
import { area, sql, store } from "./storage.js";
|
|
7
|
+
import startup, {} from "./startup.js";
|
|
8
|
+
import permissions from "./permissions.js";
|
|
6
9
|
import wire from "./wire.js";
|
|
10
|
+
const handles = new HandleRegistry();
|
|
7
11
|
const ProgramBase = CoreProgram;
|
|
8
12
|
const ProcessBase = CoreProcess;
|
|
9
13
|
const ServerBase = CoreServer;
|
|
10
14
|
const ClientBase = CoreClient;
|
|
11
|
-
const WindowBase = CoreWindow;
|
|
12
15
|
class ProgramHandle extends ProgramBase {
|
|
13
16
|
identity;
|
|
14
|
-
|
|
15
|
-
version;
|
|
16
|
-
description;
|
|
17
|
-
server;
|
|
18
|
-
client;
|
|
17
|
+
reference;
|
|
19
18
|
data;
|
|
20
19
|
cache;
|
|
21
20
|
store;
|
|
22
21
|
logs;
|
|
23
22
|
database;
|
|
23
|
+
startup;
|
|
24
|
+
permissions;
|
|
25
|
+
record;
|
|
24
26
|
constructor(record) {
|
|
25
27
|
super();
|
|
26
28
|
this.identity = record.identity;
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this
|
|
37
|
-
|
|
29
|
+
this.reference = record.reference;
|
|
30
|
+
this.record = record;
|
|
31
|
+
this.data = area(this.address, "data");
|
|
32
|
+
this.cache = area(this.address, "cache");
|
|
33
|
+
this.store = store(this.address);
|
|
34
|
+
this.logs = sql("logs", this.address);
|
|
35
|
+
this.database = sql("database", this.address);
|
|
36
|
+
this.startup = startup(this.address);
|
|
37
|
+
this.permissions = permissions(this.address);
|
|
38
|
+
bindEvents(this, scoped("host-end", record.reference, programEvent));
|
|
39
|
+
}
|
|
40
|
+
get name() { return this.record.name; }
|
|
41
|
+
get version() { return this.record.version; }
|
|
42
|
+
get description() { return this.record.description; }
|
|
43
|
+
get server() { return this.record.server; }
|
|
44
|
+
get client() { return this.record.client; }
|
|
45
|
+
get address() { return { identity: this.identity, reference: this.reference }; }
|
|
46
|
+
update(record) {
|
|
47
|
+
if (record.reference !== this.reference)
|
|
48
|
+
throw new Error("A Program handle cannot become another Program");
|
|
49
|
+
this.record = record;
|
|
38
50
|
}
|
|
39
51
|
async processes() {
|
|
40
|
-
const answer = await wire.request(["processes", this.
|
|
41
|
-
return answer[0].map(process);
|
|
52
|
+
const answer = await wire.request(["processes", this.address]);
|
|
53
|
+
return answer[0].map(record => process(record));
|
|
54
|
+
}
|
|
55
|
+
async firstProcess() {
|
|
56
|
+
return chronological(await this.processes())[0] ?? null;
|
|
57
|
+
}
|
|
58
|
+
async lastProcess() {
|
|
59
|
+
return chronological(await this.processes()).at(-1) ?? null;
|
|
42
60
|
}
|
|
43
61
|
async getProcess(identityOrName) {
|
|
44
|
-
const answer = await wire.request(["program-process", this.
|
|
62
|
+
const answer = await wire.request(["program-process", this.address, identityOrName]);
|
|
45
63
|
return answer[0] ? process(answer[0]) : null;
|
|
46
64
|
}
|
|
47
65
|
async createProcess(launch = {}) {
|
|
48
|
-
const answer = await wire.request(["create-process", this.
|
|
66
|
+
const answer = await wire.request(["create-process", this.address, launch]);
|
|
49
67
|
return process(answer[0]);
|
|
50
68
|
}
|
|
51
69
|
async apiDocs() {
|
|
52
|
-
const answer = await wire.request(["api-docs", this.
|
|
70
|
+
const answer = await wire.request(["api-docs", this.address]);
|
|
53
71
|
return answer[0];
|
|
54
72
|
}
|
|
73
|
+
async icon(size = "medium") {
|
|
74
|
+
const answer = await wire.request(["icon", this.address, size]);
|
|
75
|
+
return new Blob([Uint8Array.from(answer[0])], { type: "image/png" });
|
|
76
|
+
}
|
|
55
77
|
async installed() {
|
|
56
|
-
const answer = await wire.request(["installed", this.
|
|
78
|
+
const answer = await wire.request(["installed", this.address]);
|
|
57
79
|
return answer[0];
|
|
58
80
|
}
|
|
59
81
|
async install() {
|
|
60
|
-
await wire.request(["install", this.
|
|
82
|
+
await wire.request(["install", this.address]);
|
|
61
83
|
return this;
|
|
62
84
|
}
|
|
63
85
|
async fork(identity) {
|
|
64
|
-
const answer = await wire.request(["fork", this.
|
|
86
|
+
const answer = await wire.request(["fork", this.address, identity]);
|
|
65
87
|
return program(answer[0]);
|
|
66
88
|
}
|
|
67
89
|
async uninstall(everything = false) {
|
|
68
|
-
await wire.request(["uninstall", this.
|
|
90
|
+
await wire.request(["uninstall", this.address, everything]);
|
|
69
91
|
}
|
|
70
92
|
async forget() {
|
|
71
|
-
await wire.request(["forget", this.
|
|
93
|
+
await wire.request(["forget", this.address]);
|
|
72
94
|
}
|
|
73
95
|
async exitAll() {
|
|
74
|
-
const answer = await wire.request(["exit-all", this.
|
|
96
|
+
const answer = await wire.request(["exit-all", this.address]);
|
|
75
97
|
return answer[0];
|
|
76
98
|
}
|
|
77
99
|
}
|
|
100
|
+
/** Internal transport address for a Program handle created by this SDK. */
|
|
101
|
+
export function programAddress(value) {
|
|
102
|
+
if (!(value instanceof ProgramHandle))
|
|
103
|
+
throw new Error("A Program handle is required");
|
|
104
|
+
return value.address;
|
|
105
|
+
}
|
|
106
|
+
function chronological(processes) {
|
|
107
|
+
return processes.sort((left, right) => left.startedAt.getTime() - right.startedAt.getTime());
|
|
108
|
+
}
|
|
78
109
|
class ProcessHandle extends ProcessBase {
|
|
79
110
|
identity;
|
|
111
|
+
reference;
|
|
80
112
|
name;
|
|
81
113
|
startedAt;
|
|
82
114
|
server;
|
|
83
115
|
client;
|
|
84
116
|
ownerProgram;
|
|
85
117
|
options;
|
|
86
|
-
constructor(record) {
|
|
118
|
+
constructor(record, endpoints = {}) {
|
|
87
119
|
super();
|
|
88
120
|
this.identity = record.identity;
|
|
121
|
+
this.reference = record.reference;
|
|
89
122
|
this.name = record.name;
|
|
90
123
|
this.startedAt = new Date(record.startedAt);
|
|
91
124
|
this.ownerProgram = program(record.program);
|
|
92
125
|
this.options = record.options;
|
|
93
|
-
this.server =
|
|
94
|
-
this.client =
|
|
95
|
-
bindEvents(this, scoped("process-host", record.
|
|
126
|
+
this.server = endpointHandle(this, "server", endpoints.server);
|
|
127
|
+
this.client = endpointHandle(this, "client", endpoints.client);
|
|
128
|
+
bindEvents(this, scoped("process-host", record.reference, processEvent));
|
|
96
129
|
}
|
|
97
130
|
program() { return this.ownerProgram; }
|
|
131
|
+
get address() { return { identity: this.identity, reference: this.reference }; }
|
|
98
132
|
async parent() {
|
|
99
|
-
const answer = await wire.request(["parent", this.
|
|
133
|
+
const answer = await wire.request(["parent", this.address]);
|
|
100
134
|
return answer[0] ? process(answer[0]) : null;
|
|
101
135
|
}
|
|
102
136
|
async option(name) {
|
|
103
137
|
if (name in this.options)
|
|
104
138
|
return this.options[name];
|
|
105
|
-
const answer = await wire.request(["option", this.
|
|
139
|
+
const answer = await wire.request(["option", this.address, name]);
|
|
106
140
|
return answer[0];
|
|
107
141
|
}
|
|
108
142
|
async exit() {
|
|
109
|
-
await wire.request(["exit", this.
|
|
143
|
+
await wire.request(["exit", this.address]);
|
|
110
144
|
}
|
|
111
145
|
async exited() {
|
|
112
|
-
const answer = await wire.request(["exited", this.
|
|
146
|
+
const answer = await wire.request(["exited", this.address]);
|
|
113
147
|
return answer[0];
|
|
114
148
|
}
|
|
115
149
|
}
|
|
116
|
-
class TrafficHandle extends Events {
|
|
117
|
-
|
|
150
|
+
export class TrafficHandle extends Events {
|
|
151
|
+
target;
|
|
118
152
|
kind;
|
|
119
|
-
constructor(
|
|
120
|
-
super((event, listener, impossible) => wire.observe(
|
|
153
|
+
constructor(target, kind) {
|
|
154
|
+
super((event, listener, impossible) => wire.observe(target, kind, "publish", event, value => {
|
|
121
155
|
listener(trafficMessage(value));
|
|
122
|
-
}, impossible), observer => wire.observe(
|
|
156
|
+
}, impossible), observer => wire.observe(target, kind, "publish", null, (event, value) => {
|
|
123
157
|
if (typeof event === "string")
|
|
124
158
|
observer(event, trafficMessage(value));
|
|
125
159
|
}));
|
|
126
|
-
this.
|
|
160
|
+
this.target = target;
|
|
127
161
|
this.kind = kind;
|
|
128
162
|
}
|
|
129
163
|
observeAsks(observer) {
|
|
130
|
-
return wire.observe(this.
|
|
164
|
+
return wire.observe(this.target, this.kind, "ask", null, (event, questionId, message) => {
|
|
131
165
|
if (typeof event !== "string" || typeof questionId !== "string")
|
|
132
166
|
return;
|
|
133
167
|
observer({ event, questionId, message: trafficMessage(message) });
|
|
134
168
|
});
|
|
135
169
|
}
|
|
136
170
|
}
|
|
137
|
-
class ServerTrafficHandle extends TrafficHandle {
|
|
171
|
+
export class ServerTrafficHandle extends TrafficHandle {
|
|
138
172
|
observeAnswers(observer) {
|
|
139
|
-
return wire.observe(this.
|
|
173
|
+
return wire.observe(this.target, "server", "answer", null, (event, questionId, message) => {
|
|
140
174
|
if (typeof event !== "string" || typeof questionId !== "string")
|
|
141
175
|
return;
|
|
142
176
|
const raw = message;
|
|
@@ -154,35 +188,36 @@ class ServerHandle extends ServerBase {
|
|
|
154
188
|
constructor(owner) {
|
|
155
189
|
super();
|
|
156
190
|
this.owner = owner;
|
|
157
|
-
this.traffic = new ServerTrafficHandle(owner, "server");
|
|
191
|
+
this.traffic = new ServerTrafficHandle(owner.address, "server");
|
|
192
|
+
bindEvents(this, endpointEvents(owner.address, "server"));
|
|
158
193
|
}
|
|
159
|
-
process() { return this.owner; }
|
|
160
|
-
publish(event, payload) {
|
|
161
|
-
wire.send("end-host", "send", this.owner.
|
|
194
|
+
async process() { return this.owner; }
|
|
195
|
+
publish(event, payload = undefined) {
|
|
196
|
+
wire.send("end-host", "send", this.owner.address, "server", event, payload);
|
|
162
197
|
}
|
|
163
198
|
async exists() {
|
|
164
|
-
const answer = await wire.request(["exists", "server", this.owner.
|
|
199
|
+
const answer = await wire.request(["exists", "server", this.owner.address]);
|
|
165
200
|
return answer[0];
|
|
166
201
|
}
|
|
167
|
-
async start() { await wire.request(["start-endpoint", this.owner.
|
|
168
|
-
async stop() { await wire.request(["stop-endpoint", this.owner.
|
|
202
|
+
async start() { await wire.request(["start-endpoint", this.owner.address, "server"]); }
|
|
203
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.address, "server"]); }
|
|
169
204
|
async waitReady(timeout) {
|
|
170
|
-
await wire.request(["wait-ready", this.owner.
|
|
205
|
+
await wire.request(["wait-ready", this.owner.address], timeout);
|
|
171
206
|
}
|
|
172
|
-
async ask(event, payload) {
|
|
207
|
+
async ask(event, payload = undefined) {
|
|
173
208
|
return this.askWithin(undefined, event, payload);
|
|
174
209
|
}
|
|
175
210
|
timeout(milliseconds) {
|
|
176
|
-
return { ask: (event, payload) => this.askWithin(milliseconds, event, payload) };
|
|
211
|
+
return { ask: (event, payload = undefined) => this.askWithin(milliseconds, event, payload) };
|
|
177
212
|
}
|
|
178
213
|
async askWithin(timeout, event, payload) {
|
|
179
214
|
const deadline = new Deadline(timeout);
|
|
180
|
-
await wire.requestWithin(["wait-ready", this.owner.
|
|
181
|
-
const identity = await wire.identity;
|
|
215
|
+
await wire.requestWithin(["wait-ready", this.owner.address, true], deadline);
|
|
216
|
+
const identity = await wire.identity();
|
|
182
217
|
const address = `server:${identity.process}:${randomUUID()}`;
|
|
183
218
|
const questionId = randomUUID();
|
|
184
219
|
const waiting = wire.expectWithin(address, deadline);
|
|
185
|
-
wire.send("end-host", "ask", this.owner.
|
|
220
|
+
wire.send("end-host", "ask", this.owner.address, "server", address, questionId, event, payload);
|
|
186
221
|
try {
|
|
187
222
|
return await waiting;
|
|
188
223
|
}
|
|
@@ -194,36 +229,35 @@ class ServerHandle extends ServerBase {
|
|
|
194
229
|
class ClientHandle extends ClientBase {
|
|
195
230
|
owner;
|
|
196
231
|
traffic;
|
|
232
|
+
window;
|
|
197
233
|
constructor(owner) {
|
|
198
234
|
super();
|
|
199
235
|
this.owner = owner;
|
|
200
|
-
this.traffic = new TrafficHandle(owner, "client");
|
|
236
|
+
this.traffic = new TrafficHandle(owner.address, "client");
|
|
237
|
+
this.window = window(async () => owner.address);
|
|
238
|
+
bindEvents(this, endpointEvents(owner.address, "client"));
|
|
201
239
|
}
|
|
202
|
-
process() { return this.owner; }
|
|
203
|
-
publish(event, payload) { wire.send("end-host", "send", this.owner.
|
|
240
|
+
async process() { return this.owner; }
|
|
241
|
+
publish(event, payload = undefined) { wire.send("end-host", "send", this.owner.address, "client", event, payload); }
|
|
204
242
|
async exists() {
|
|
205
|
-
const answer = await wire.request(["exists", "client", this.owner.
|
|
243
|
+
const answer = await wire.request(["exists", "client", this.owner.address]);
|
|
206
244
|
return answer[0];
|
|
207
245
|
}
|
|
208
246
|
async start(overrides = {}) {
|
|
209
|
-
await wire.request(["start-endpoint", this.owner.
|
|
210
|
-
}
|
|
211
|
-
async stop() { await wire.request(["stop-endpoint", this.owner.identity, "client"]); }
|
|
212
|
-
async window() {
|
|
213
|
-
await wire.request(["window", this.owner.identity]);
|
|
214
|
-
return new WindowHandle(this);
|
|
247
|
+
await wire.request(["start-endpoint", this.owner.address, "client", overrides]);
|
|
215
248
|
}
|
|
249
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.address, "client"]); }
|
|
216
250
|
}
|
|
217
|
-
class WindowHandle extends
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
251
|
+
class WindowHandle extends Events {
|
|
252
|
+
target;
|
|
253
|
+
surface;
|
|
254
|
+
constructor(target) {
|
|
255
|
+
super(...deferredScoped("host-end", target, (_event, values) => values[0]));
|
|
256
|
+
this.target = target;
|
|
257
|
+
this.surface = new WindowSurfaceHandle(target);
|
|
223
258
|
}
|
|
224
|
-
client() { return this.owner; }
|
|
225
259
|
async state() {
|
|
226
|
-
const answer = await wire.request(["window", this.
|
|
260
|
+
const answer = await wire.request(["window", await this.target()]);
|
|
227
261
|
return answer[0];
|
|
228
262
|
}
|
|
229
263
|
async title() { return (await this.state()).title; }
|
|
@@ -233,11 +267,81 @@ class WindowHandle extends WindowBase {
|
|
|
233
267
|
async front() { return (await this.state()).front; }
|
|
234
268
|
async layer() { return (await this.state()).layer; }
|
|
235
269
|
async location() { return (await this.state()).location; }
|
|
236
|
-
async move(position) { await wire.request(["move", this.
|
|
237
|
-
async resize(size) { await wire.request(["resize", this.
|
|
238
|
-
async minimize(minimized = true) { await wire.request(["minimize", this.
|
|
239
|
-
async changeTitle(title) { await wire.request(["changeTitle", this.
|
|
240
|
-
async raise() { await wire.request(["raise", this.
|
|
270
|
+
async move(position) { await wire.request(["move", await this.target(), position]); }
|
|
271
|
+
async resize(size) { await wire.request(["resize", await this.target(), size]); }
|
|
272
|
+
async minimize(minimized = true) { await wire.request(["minimize", await this.target(), minimized]); }
|
|
273
|
+
async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
|
|
274
|
+
async raise() { await wire.request(["raise", await this.target()]); }
|
|
275
|
+
}
|
|
276
|
+
class WindowSurfaceHandle extends Events {
|
|
277
|
+
target;
|
|
278
|
+
constructor(target) {
|
|
279
|
+
super((event, listener, impossible) => {
|
|
280
|
+
if (event !== "change") {
|
|
281
|
+
impossible?.(new Error(`A Window Surface has no "${String(event)}" event`));
|
|
282
|
+
return () => undefined;
|
|
283
|
+
}
|
|
284
|
+
return deferred(target, subject => wire.on("host-end", "surface", (...values) => {
|
|
285
|
+
const message = unscoped(subject, values);
|
|
286
|
+
if (message)
|
|
287
|
+
listener(message[0]);
|
|
288
|
+
}, subject, impossible), impossible);
|
|
289
|
+
}, observer => deferred(target, subject => wire.on("host-end", "surface", (...values) => {
|
|
290
|
+
const message = unscoped(subject, values);
|
|
291
|
+
if (message)
|
|
292
|
+
observer("change", message[0]);
|
|
293
|
+
}, subject)));
|
|
294
|
+
this.target = target;
|
|
295
|
+
}
|
|
296
|
+
async snapshot() {
|
|
297
|
+
const answer = await wire.request(["window", await this.target()]);
|
|
298
|
+
return answer[0].surface;
|
|
299
|
+
}
|
|
300
|
+
async set(settings = {}) { await wire.request(["surfaceSet", await this.target(), settings]); }
|
|
301
|
+
async remove() { await wire.request(["surfaceRemove", await this.target()]); }
|
|
302
|
+
}
|
|
303
|
+
function deferredScoped(route, target, convert) {
|
|
304
|
+
return [
|
|
305
|
+
(event, listener, impossible) => {
|
|
306
|
+
if (!windowEvent(event)) {
|
|
307
|
+
impossible?.(new Error(`A Window has no "${event}" event`));
|
|
308
|
+
return () => undefined;
|
|
309
|
+
}
|
|
310
|
+
return deferred(target, subject => wire.on(route, event, (...values) => {
|
|
311
|
+
const message = unscoped(subject, values);
|
|
312
|
+
if (message)
|
|
313
|
+
listener(convert(event, message));
|
|
314
|
+
}, subject, impossible), impossible);
|
|
315
|
+
},
|
|
316
|
+
observer => deferred(target, subject => wire.onAll(route, (event, ...values) => {
|
|
317
|
+
if (typeof event !== "string" || !windowEvent(event))
|
|
318
|
+
return;
|
|
319
|
+
const message = unscoped(subject, values);
|
|
320
|
+
if (message)
|
|
321
|
+
observer(event, convert(event, message));
|
|
322
|
+
}, subject))
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
function windowEvent(event) {
|
|
326
|
+
return event === "move" || event === "resize" || event === "minimize" || event === "changeTitle" || event === "front";
|
|
327
|
+
}
|
|
328
|
+
function deferred(target, register, impossible) {
|
|
329
|
+
let active = true;
|
|
330
|
+
let stop = () => undefined;
|
|
331
|
+
void target().then(address => {
|
|
332
|
+
if (active)
|
|
333
|
+
stop = register(address.reference);
|
|
334
|
+
}, error => {
|
|
335
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
336
|
+
if (active && impossible)
|
|
337
|
+
impossible(failure);
|
|
338
|
+
else if (active)
|
|
339
|
+
queueMicrotask(() => { throw failure; });
|
|
340
|
+
});
|
|
341
|
+
return () => {
|
|
342
|
+
active = false;
|
|
343
|
+
stop();
|
|
344
|
+
};
|
|
241
345
|
}
|
|
242
346
|
export function scoped(route, subject, convert) {
|
|
243
347
|
return new Events((event, listener, impossible) => wire.on(route, event, (...values) => {
|
|
@@ -252,16 +356,23 @@ export function scoped(route, subject, convert) {
|
|
|
252
356
|
observer(event, convert(event, message));
|
|
253
357
|
}, subject));
|
|
254
358
|
}
|
|
359
|
+
/** Destinationless events originating from one Endpoint handle. */
|
|
360
|
+
export function endpointEvents(target, half) {
|
|
361
|
+
return new Events((event, listener, impossible) => wire.follow(target, half, event, listener, impossible), observer => wire.follow(target, half, null, (event, payload) => {
|
|
362
|
+
if (typeof event === "string")
|
|
363
|
+
observer(event, payload);
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
255
366
|
function unscoped(subject, values) {
|
|
256
367
|
if (subject === null)
|
|
257
368
|
return values;
|
|
258
369
|
return values[0] === subject ? values.slice(1) : null;
|
|
259
370
|
}
|
|
260
371
|
function programEvent(event, values) {
|
|
261
|
-
if (event === "
|
|
372
|
+
if (event === "endpointStart" || event === "endpointStop")
|
|
373
|
+
return lifecycleEndpoint(values[0], values[1]);
|
|
374
|
+
if (event === "processCreate")
|
|
262
375
|
return process(values[0]);
|
|
263
|
-
if (event === "serverStop")
|
|
264
|
-
return { process: process(values[0]), code: numberOrNull(values[1]), signal: stringOrNull(values[2]) };
|
|
265
376
|
if (event === "processExit")
|
|
266
377
|
return { process: process(values[0]), ...exit(values[1], values[2]) };
|
|
267
378
|
if (event === "uninstall")
|
|
@@ -269,12 +380,20 @@ function programEvent(event, values) {
|
|
|
269
380
|
return undefined;
|
|
270
381
|
}
|
|
271
382
|
function processEvent(event, values) {
|
|
272
|
-
if (event === "
|
|
273
|
-
return
|
|
383
|
+
if (event === "endpointStart" || event === "endpointStop")
|
|
384
|
+
return lifecycleEndpoint(values[0], values[1]);
|
|
274
385
|
if (event === "exit")
|
|
275
386
|
return exit(values[0], values[1]);
|
|
276
387
|
return undefined;
|
|
277
388
|
}
|
|
389
|
+
export function lifecycleEndpoint(record, kind) {
|
|
390
|
+
const owner = process(record);
|
|
391
|
+
if (kind === "server")
|
|
392
|
+
return owner.server;
|
|
393
|
+
if (kind === "client")
|
|
394
|
+
return owner.client;
|
|
395
|
+
throw new Error("The host returned an invalid Endpoint lifecycle event");
|
|
396
|
+
}
|
|
278
397
|
export function exit(code, signal) {
|
|
279
398
|
const namedSignal = stringOrNull(signal);
|
|
280
399
|
return { status: namedSignal === null ? "exited" : "signaled", code: numberOrNull(code), signal: namedSignal };
|
|
@@ -294,20 +413,35 @@ export function bindEvents(target, events) {
|
|
|
294
413
|
});
|
|
295
414
|
}
|
|
296
415
|
export function program(record) {
|
|
297
|
-
|
|
416
|
+
const handle = handles.obtain(`program:${record.reference}`, () => new ProgramHandle(record));
|
|
417
|
+
handle.update(record);
|
|
418
|
+
return handle;
|
|
298
419
|
}
|
|
299
|
-
export function process(record) {
|
|
300
|
-
return new ProcessHandle(record);
|
|
420
|
+
export function process(record, endpoints = {}) {
|
|
421
|
+
return handles.obtain(`process:${record.reference}`, () => new ProcessHandle(record, endpoints));
|
|
301
422
|
}
|
|
302
423
|
export function endpoint(reference) {
|
|
303
424
|
if (!reference)
|
|
304
425
|
throw new Error("The boundary returned an invalid Endpoint reference");
|
|
305
|
-
const owner =
|
|
426
|
+
const owner = process(reference.process);
|
|
306
427
|
return reference.kind === "server" ? owner.server : owner.client;
|
|
307
428
|
}
|
|
429
|
+
export function claimEndpoint(reference, kind, endpoint) {
|
|
430
|
+
return handles.adopt(`endpoint:${reference}:${kind}`, endpoint);
|
|
431
|
+
}
|
|
432
|
+
function endpointHandle(owner, kind, preferred) {
|
|
433
|
+
return handles.obtain(`endpoint:${owner.reference}:${kind}`, () => preferred ?? (kind === "server" ? new ServerHandle(owner) : new ClientHandle(owner)));
|
|
434
|
+
}
|
|
435
|
+
export function window(target) {
|
|
436
|
+
return new WindowHandle(target);
|
|
437
|
+
}
|
|
438
|
+
/** Runtime constructor used to identify and type Server-visible Program handles. */
|
|
308
439
|
export const Program = CoreProgram;
|
|
440
|
+
/** Runtime constructor used to identify and type Server-visible Process handles. */
|
|
309
441
|
export const Process = CoreProcess;
|
|
442
|
+
/** Runtime constructor used to identify and type Server-visible Endpoint handles. */
|
|
310
443
|
export const Endpoint = CoreEndpoint;
|
|
444
|
+
/** Runtime constructor used to identify and type Server-visible Server handles. */
|
|
311
445
|
export const Server = CoreServer;
|
|
446
|
+
/** Runtime constructor used to identify and type Server-visible Client handles. */
|
|
312
447
|
export const Client = CoreClient;
|
|
313
|
-
export const Window = CoreWindow;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Weak canonical references for system-backed domain handles in this SDK realm. */
|
|
2
|
+
export default class HandleRegistry {
|
|
3
|
+
private readonly handles;
|
|
4
|
+
private readonly released;
|
|
5
|
+
obtain<Value extends object>(key: string, create: () => Value): Value;
|
|
6
|
+
adopt<Value extends object>(key: string, value: Value): Value;
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Weak canonical references for system-backed domain handles in this SDK realm. */
|
|
2
|
+
export default class HandleRegistry {
|
|
3
|
+
handles = new Map();
|
|
4
|
+
released = new FinalizationRegistry(({ key, reference }) => {
|
|
5
|
+
if (this.handles.get(key) === reference)
|
|
6
|
+
this.handles.delete(key);
|
|
7
|
+
});
|
|
8
|
+
obtain(key, create) {
|
|
9
|
+
const existing = this.handles.get(key)?.deref();
|
|
10
|
+
if (existing)
|
|
11
|
+
return existing;
|
|
12
|
+
const value = create();
|
|
13
|
+
const reference = new WeakRef(value);
|
|
14
|
+
this.handles.set(key, reference);
|
|
15
|
+
this.released.register(value, { key, reference });
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
adopt(key, value) {
|
|
19
|
+
const existing = this.handles.get(key)?.deref();
|
|
20
|
+
if (existing && existing !== value)
|
|
21
|
+
throw new Error(`The canonical handle for "${key}" already exists`);
|
|
22
|
+
return existing ?? this.obtain(key, () => value);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,65 +1,113 @@
|
|
|
1
|
-
import type { Exit, Layer, Position, ServedFile, Size, Subscribable } from "@phreshos/core";
|
|
2
|
-
import { type Process, type Program } from "./domain.js";
|
|
1
|
+
import type { DesktopWallpaper, Exit, FileWallpaper, Layer, Position, ServedFile, Size, Subscribable, ThemeProperties, WritableTheme } from "@phreshos/core";
|
|
2
|
+
import { type Client, type Process, type Program, type Server } from "./domain.js";
|
|
3
|
+
/** Resolved production description for a Program's Server. */
|
|
3
4
|
export type ServerDescription = Readonly<{
|
|
5
|
+
/** Absolute directory containing the production Server files. */
|
|
4
6
|
location: string;
|
|
7
|
+
/** Whether newly created Processes start this Server by default. */
|
|
5
8
|
start?: boolean;
|
|
9
|
+
/** Command used to install the Server's production dependencies. */
|
|
6
10
|
installCommand?: string;
|
|
11
|
+
/** Command used to start the Server from its production directory. */
|
|
7
12
|
startCommand: string;
|
|
8
13
|
}>;
|
|
14
|
+
/** Resolved production description for a Program's Client and initial Window. */
|
|
9
15
|
export type ClientDescription = Readonly<{
|
|
16
|
+
/** Absolute directory containing the production Client files. */
|
|
10
17
|
location: string;
|
|
18
|
+
/** Whether newly created Processes start this Client by default. */
|
|
11
19
|
start?: boolean;
|
|
20
|
+
/** Default Window title. */
|
|
12
21
|
title?: string;
|
|
22
|
+
/** Default Window size. */
|
|
13
23
|
size?: Size;
|
|
24
|
+
/** Default Window position. */
|
|
14
25
|
position?: Position;
|
|
26
|
+
/** Default Window layer. */
|
|
15
27
|
layer?: Layer;
|
|
28
|
+
/** Whether the Window starts minimized. */
|
|
16
29
|
minimize?: boolean;
|
|
17
30
|
}>;
|
|
18
31
|
type Description = Readonly<{
|
|
32
|
+
/** Stable identity assigned to the Program. */
|
|
19
33
|
identity: string;
|
|
34
|
+
/** Human-readable Program name. */
|
|
20
35
|
name?: string;
|
|
36
|
+
/** Declared Program version. */
|
|
21
37
|
version?: string;
|
|
38
|
+
/** Short human-readable Program description. */
|
|
22
39
|
description?: string;
|
|
40
|
+
/** Path to the Program-authored API entry document. */
|
|
23
41
|
apiDocs?: string;
|
|
24
|
-
|
|
42
|
+
/** Absolute validated PNG source used to derive the Program's hosted icon sizes. */
|
|
43
|
+
icon?: string;
|
|
44
|
+
/** Absolute directory used for the Program's persistent storage. */
|
|
25
45
|
storage: string;
|
|
26
46
|
}>;
|
|
47
|
+
/** Complete runtime description used to create a Program. */
|
|
27
48
|
export type ProgramDescription = Description & (Readonly<{
|
|
49
|
+
/** Required Server description when no Client is described. */
|
|
28
50
|
server: ServerDescription;
|
|
51
|
+
/** Optional Client description. */
|
|
29
52
|
client?: ClientDescription;
|
|
30
53
|
}> | Readonly<{
|
|
54
|
+
/** Optional Server description. */
|
|
31
55
|
server?: ServerDescription;
|
|
56
|
+
/** Required Client description when no Server is described. */
|
|
32
57
|
client: ClientDescription;
|
|
33
58
|
}>);
|
|
34
|
-
|
|
35
|
-
process: Process;
|
|
36
|
-
}>;
|
|
59
|
+
/** An uninstall reported with the affected Program and removal scope. */
|
|
37
60
|
export type HostProgramUninstall = Readonly<{
|
|
61
|
+
/** Program that left the installed state. */
|
|
38
62
|
program: Program;
|
|
63
|
+
/** Whether all installed resources, including storage, were removed. */
|
|
39
64
|
everythingRemoved: boolean;
|
|
40
65
|
}>;
|
|
66
|
+
/** A Process exit reported with the Process that ended. */
|
|
41
67
|
export type HostProcessExit = Exit & Readonly<{
|
|
68
|
+
/** Process that ended. */
|
|
42
69
|
process: Process;
|
|
43
70
|
}>;
|
|
71
|
+
/** Authoritative lifecycle events visible to the Server host. */
|
|
44
72
|
export type HostEvents = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
/** One Process Endpoint entered a new live incarnation. */
|
|
74
|
+
endpointStart: Server | Client;
|
|
75
|
+
/** One Process Endpoint incarnation ended. */
|
|
76
|
+
endpointStop: Server | Client;
|
|
77
|
+
/** A Program entered the runtime registry. */
|
|
49
78
|
programCreate: Program;
|
|
79
|
+
/** A Program left the runtime registry. */
|
|
50
80
|
programForget: Program;
|
|
81
|
+
/** A Program entered the installed state. */
|
|
51
82
|
programInstall: Program;
|
|
83
|
+
/** A Program left the installed state. */
|
|
52
84
|
programUninstall: HostProgramUninstall;
|
|
85
|
+
/** A Process entered the runtime set. */
|
|
53
86
|
processCreate: Process;
|
|
87
|
+
/** A Process left the runtime set. */
|
|
54
88
|
processExit: HostProcessExit;
|
|
55
89
|
};
|
|
90
|
+
/** Authoritative system capabilities available to a Server endpoint. */
|
|
56
91
|
export interface Host<Events extends object = {}> extends Subscribable<HostEvents & Events, never> {
|
|
92
|
+
/** Observable system Theme authority. */
|
|
93
|
+
readonly theme: WritableTheme<ThemeProperties>;
|
|
94
|
+
/** Authoritative wallpaper visible before authentication. */
|
|
95
|
+
readonly signInWallpaper: FileWallpaper;
|
|
96
|
+
/** Authoritative wallpaper visible within authenticated desktops. */
|
|
97
|
+
readonly desktopWallpaper: DesktopWallpaper;
|
|
98
|
+
/** Publishes a value through the host and returns its public file metadata. */
|
|
57
99
|
serve(value: unknown): Promise<ServedFile>;
|
|
100
|
+
/** Returns all known Programs, optionally restricted to installed Programs. */
|
|
58
101
|
programs(onlyInstalled?: boolean): Promise<Program[]>;
|
|
102
|
+
/** Returns the Program with the given stable identity. */
|
|
59
103
|
getProgram(identity: string): Promise<Program>;
|
|
104
|
+
/** Creates a runtime Program from a description or description file path. */
|
|
60
105
|
createProgram(source: ProgramDescription | string): Promise<Program>;
|
|
106
|
+
/** Returns every live Process known to the authoritative host. */
|
|
61
107
|
processes(): Promise<Process[]>;
|
|
108
|
+
/** Returns the live Process with the given runtime identity. */
|
|
62
109
|
getProcess(identity: string): Promise<Process>;
|
|
63
110
|
}
|
|
111
|
+
/** Authoritative system capabilities for the currently executing Server. */
|
|
64
112
|
export declare const host: Host;
|
|
65
113
|
export {};
|