@phreshos/client 0.1.0 → 0.1.2
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 +95 -13
- package/dist/channel.d.ts +4 -0
- package/dist/channel.js +3 -0
- package/dist/current.d.ts +13 -10
- package/dist/current.js +67 -14
- package/dist/domain.d.ts +83 -30
- package/dist/domain.js +183 -77
- package/dist/handle-registry.d.ts +7 -0
- package/dist/handle-registry.js +24 -0
- package/dist/host.d.ts +15 -20
- package/dist/host.js +10 -35
- package/dist/main.d.ts +5 -3
- package/dist/main.js +3 -1
- package/dist/permissions.d.ts +8 -0
- package/dist/permissions.js +21 -0
- package/dist/pointer.d.ts +35 -0
- package/dist/pointer.js +54 -0
- package/dist/surface.d.ts +29 -0
- package/dist/surface.js +34 -0
- package/dist/theme.d.ts +13 -0
- package/dist/theme.js +29 -0
- package/dist/wire.d.ts +12 -4
- package/dist/wire.js +52 -29
- package/package.json +29 -4
package/dist/domain.js
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
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 Events from "./events.js";
|
|
3
3
|
import Deadline from "./deadline.js";
|
|
4
|
+
import HandleRegistry from "./handle-registry.js";
|
|
4
5
|
import { area, sql, store } from "./storage.js";
|
|
5
6
|
import wire from "./wire.js";
|
|
7
|
+
const handles = new HandleRegistry();
|
|
6
8
|
const ProgramBase = CoreProgram;
|
|
7
9
|
const ProcessBase = CoreProcess;
|
|
8
10
|
const ServerBase = CoreServer;
|
|
9
11
|
const ClientBase = CoreClient;
|
|
10
|
-
const WindowBase = CoreWindow;
|
|
11
12
|
class ProgramHandle extends ProgramBase {
|
|
12
13
|
identity;
|
|
13
|
-
|
|
14
|
-
version;
|
|
15
|
-
description;
|
|
16
|
-
server;
|
|
17
|
-
client;
|
|
14
|
+
reference;
|
|
18
15
|
data = area("data");
|
|
19
16
|
cache = area("cache");
|
|
20
17
|
store = store();
|
|
21
18
|
logs = sql("logs");
|
|
22
19
|
database = sql("database");
|
|
20
|
+
record;
|
|
23
21
|
constructor(record) {
|
|
24
22
|
super();
|
|
25
23
|
this.identity = record.identity;
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
this.reference = record.reference;
|
|
25
|
+
this.record = record;
|
|
26
|
+
bindEvents(this, scoped("host-end", record.reference, programEvent));
|
|
27
|
+
}
|
|
28
|
+
get name() { return this.record.name; }
|
|
29
|
+
get version() { return this.record.version; }
|
|
30
|
+
get description() { return this.record.description; }
|
|
31
|
+
get server() { return this.record.server; }
|
|
32
|
+
get client() { return this.record.client; }
|
|
33
|
+
update(record) {
|
|
34
|
+
if (record.reference !== this.reference)
|
|
35
|
+
throw new Error("A Program handle cannot become another Program");
|
|
36
|
+
this.record = record;
|
|
32
37
|
}
|
|
33
38
|
async processes() {
|
|
34
39
|
const answer = await wire.request(["processes"]);
|
|
35
|
-
return answer[0].map(process);
|
|
40
|
+
return answer[0].map(record => process(record));
|
|
41
|
+
}
|
|
42
|
+
async firstProcess() {
|
|
43
|
+
return chronological(await this.processes())[0] ?? null;
|
|
44
|
+
}
|
|
45
|
+
async lastProcess() {
|
|
46
|
+
return chronological(await this.processes()).at(-1) ?? null;
|
|
36
47
|
}
|
|
37
48
|
async getProcess(identityOrName) {
|
|
38
49
|
const answer = await wire.request(["program-process", undefined, identityOrName]);
|
|
@@ -46,6 +57,10 @@ class ProgramHandle extends ProgramBase {
|
|
|
46
57
|
const answer = await wire.request(["api-docs"]);
|
|
47
58
|
return answer[0];
|
|
48
59
|
}
|
|
60
|
+
async icon(size = "medium") {
|
|
61
|
+
const answer = await wire.request(["icon", size]);
|
|
62
|
+
return new Blob([Uint8Array.from(answer[0])], { type: "image/png" });
|
|
63
|
+
}
|
|
49
64
|
async installed() {
|
|
50
65
|
const answer = await wire.request(["installed"]);
|
|
51
66
|
return answer[0];
|
|
@@ -57,64 +72,70 @@ class ProgramHandle extends ProgramBase {
|
|
|
57
72
|
return answer[0];
|
|
58
73
|
}
|
|
59
74
|
}
|
|
75
|
+
function chronological(processes) {
|
|
76
|
+
return processes.sort((left, right) => left.startedAt.getTime() - right.startedAt.getTime());
|
|
77
|
+
}
|
|
60
78
|
class ProcessHandle extends ProcessBase {
|
|
61
79
|
identity;
|
|
80
|
+
reference;
|
|
62
81
|
name;
|
|
63
82
|
startedAt;
|
|
64
83
|
server;
|
|
65
84
|
client;
|
|
66
85
|
ownerProgram;
|
|
67
86
|
options;
|
|
68
|
-
constructor(record) {
|
|
87
|
+
constructor(record, endpoints = {}) {
|
|
69
88
|
super();
|
|
70
89
|
this.identity = record.identity;
|
|
90
|
+
this.reference = record.reference;
|
|
71
91
|
this.name = record.name;
|
|
72
92
|
this.startedAt = new Date(record.startedAt);
|
|
73
93
|
this.ownerProgram = program(record.program);
|
|
74
94
|
this.options = record.options;
|
|
75
|
-
this.server =
|
|
76
|
-
this.client =
|
|
77
|
-
bindEvents(this, scoped("process-host", record.
|
|
95
|
+
this.server = endpointHandle(this, "server", endpoints.server);
|
|
96
|
+
this.client = endpointHandle(this, "client", endpoints.client);
|
|
97
|
+
bindEvents(this, scoped("process-host", record.reference, processEvent));
|
|
78
98
|
}
|
|
79
99
|
program() { return this.ownerProgram; }
|
|
100
|
+
get address() { return { identity: this.identity, reference: this.reference }; }
|
|
80
101
|
async parent() {
|
|
81
|
-
const answer = await wire.request(["parent", this.
|
|
102
|
+
const answer = await wire.request(["parent", this.address]);
|
|
82
103
|
return answer[0] ? process(answer[0]) : null;
|
|
83
104
|
}
|
|
84
105
|
async option(name) {
|
|
85
106
|
if (name in this.options)
|
|
86
107
|
return this.options[name];
|
|
87
|
-
const answer = await wire.request(["option", this.
|
|
108
|
+
const answer = await wire.request(["option", this.address, name]);
|
|
88
109
|
return answer[0];
|
|
89
110
|
}
|
|
90
|
-
async exit() { await wire.request(["exit", this.
|
|
111
|
+
async exit() { await wire.request(["exit", this.address]); }
|
|
91
112
|
async exited() {
|
|
92
|
-
const answer = await wire.request(["exited", this.
|
|
113
|
+
const answer = await wire.request(["exited", this.address]);
|
|
93
114
|
return answer[0];
|
|
94
115
|
}
|
|
95
116
|
}
|
|
96
|
-
class TrafficHandle extends Events {
|
|
97
|
-
|
|
117
|
+
export class TrafficHandle extends Events {
|
|
118
|
+
target;
|
|
98
119
|
kind;
|
|
99
|
-
constructor(
|
|
100
|
-
super((event, listener, impossible) => wire.observe(
|
|
120
|
+
constructor(target, kind) {
|
|
121
|
+
super((event, listener, impossible) => wire.observe(target, kind, "publish", event, value => listener(trafficMessage(value)), impossible), observer => wire.observe(target, kind, "publish", null, (event, value) => {
|
|
101
122
|
if (typeof event === "string")
|
|
102
123
|
observer(event, trafficMessage(value));
|
|
103
124
|
}));
|
|
104
|
-
this.
|
|
125
|
+
this.target = target;
|
|
105
126
|
this.kind = kind;
|
|
106
127
|
}
|
|
107
128
|
observeAsks(observer) {
|
|
108
|
-
return wire.observe(this.
|
|
129
|
+
return wire.observe(this.target, this.kind, "ask", null, (event, questionId, value) => {
|
|
109
130
|
if (typeof event !== "string" || typeof questionId !== "string")
|
|
110
131
|
return;
|
|
111
132
|
observer({ event, questionId, message: trafficMessage(value) });
|
|
112
133
|
});
|
|
113
134
|
}
|
|
114
135
|
}
|
|
115
|
-
class ServerTrafficHandle extends TrafficHandle {
|
|
136
|
+
export class ServerTrafficHandle extends TrafficHandle {
|
|
116
137
|
observeAnswers(observer) {
|
|
117
|
-
return wire.observe(this.
|
|
138
|
+
return wire.observe(this.target, "server", "answer", null, (event, questionId, value) => {
|
|
118
139
|
if (typeof event !== "string" || typeof questionId !== "string")
|
|
119
140
|
return;
|
|
120
141
|
const raw = value;
|
|
@@ -128,31 +149,32 @@ class ServerHandle extends ServerBase {
|
|
|
128
149
|
constructor(owner) {
|
|
129
150
|
super();
|
|
130
151
|
this.owner = owner;
|
|
131
|
-
this.traffic = new ServerTrafficHandle(owner, "server");
|
|
152
|
+
this.traffic = new ServerTrafficHandle(owner.address, "server");
|
|
153
|
+
bindEvents(this, endpointEvents(owner.address, "server"));
|
|
132
154
|
}
|
|
133
|
-
process() { return this.owner; }
|
|
134
|
-
publish(event, payload) { wire.send("end-host", "send", this.owner.
|
|
155
|
+
async process() { return this.owner; }
|
|
156
|
+
publish(event, payload = undefined) { wire.send("end-host", "send", this.owner.address, "server", event, payload); }
|
|
135
157
|
async exists() {
|
|
136
|
-
const answer = await wire.request(["exists", "server", this.owner.
|
|
158
|
+
const answer = await wire.request(["exists", "server", this.owner.address]);
|
|
137
159
|
return answer[0];
|
|
138
160
|
}
|
|
139
|
-
async start() { await wire.request(["start-endpoint", this.owner.
|
|
140
|
-
async stop() { await wire.request(["stop-endpoint", this.owner.
|
|
141
|
-
async waitReady(timeout) { await wire.request(["wait-ready", this.owner.
|
|
142
|
-
async ask(event, payload) {
|
|
161
|
+
async start() { await wire.request(["start-endpoint", this.owner.address, "server"]); }
|
|
162
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.address, "server"]); }
|
|
163
|
+
async waitReady(timeout) { await wire.request(["wait-ready", this.owner.address], timeout); }
|
|
164
|
+
async ask(event, payload = undefined) {
|
|
143
165
|
return this.askWithin(undefined, event, payload);
|
|
144
166
|
}
|
|
145
167
|
timeout(milliseconds) {
|
|
146
|
-
return { ask: (event, payload) => this.askWithin(milliseconds, event, payload) };
|
|
168
|
+
return { ask: (event, payload = undefined) => this.askWithin(milliseconds, event, payload) };
|
|
147
169
|
}
|
|
148
170
|
async askWithin(timeout, event, payload) {
|
|
149
171
|
const deadline = new Deadline(timeout);
|
|
150
|
-
await wire.requestWithin(["wait-ready", this.owner.
|
|
151
|
-
const source = await wire.identity;
|
|
152
|
-
const address = `client:${source}:${crypto.randomUUID()}`;
|
|
172
|
+
await wire.requestWithin(["wait-ready", this.owner.address, true], deadline);
|
|
173
|
+
const source = await wire.identity();
|
|
174
|
+
const address = `client:${source.process}:${crypto.randomUUID()}`;
|
|
153
175
|
const questionId = crypto.randomUUID();
|
|
154
176
|
const waiting = wire.expectWithin(address, deadline);
|
|
155
|
-
wire.send("end-host", "ask", this.owner.
|
|
177
|
+
wire.send("end-host", "ask", this.owner.address, "server", address, questionId, event, payload);
|
|
156
178
|
try {
|
|
157
179
|
return await waiting;
|
|
158
180
|
}
|
|
@@ -164,34 +186,33 @@ class ServerHandle extends ServerBase {
|
|
|
164
186
|
class ClientHandle extends ClientBase {
|
|
165
187
|
owner;
|
|
166
188
|
traffic;
|
|
189
|
+
window;
|
|
167
190
|
constructor(owner) {
|
|
168
191
|
super();
|
|
169
192
|
this.owner = owner;
|
|
170
|
-
this.traffic = new TrafficHandle(owner, "client");
|
|
193
|
+
this.traffic = new TrafficHandle(owner.address, "client");
|
|
194
|
+
this.window = window(async () => owner.address);
|
|
195
|
+
bindEvents(this, endpointEvents(owner.address, "client"));
|
|
171
196
|
}
|
|
172
|
-
process() { return this.owner; }
|
|
173
|
-
publish(event, payload) { wire.send("end-host", "send", this.owner.
|
|
197
|
+
async process() { return this.owner; }
|
|
198
|
+
publish(event, payload = undefined) { wire.send("end-host", "send", this.owner.address, "client", event, payload); }
|
|
174
199
|
async exists() {
|
|
175
|
-
const answer = await wire.request(["exists", "client", this.owner.
|
|
200
|
+
const answer = await wire.request(["exists", "client", this.owner.address]);
|
|
176
201
|
return answer[0];
|
|
177
202
|
}
|
|
178
|
-
async start(overrides = {}) { await wire.request(["start-endpoint", this.owner.
|
|
179
|
-
async stop() { await wire.request(["stop-endpoint", this.owner.
|
|
180
|
-
async window() {
|
|
181
|
-
await wire.request(["window", this.owner.identity]);
|
|
182
|
-
return new WindowHandle(this);
|
|
183
|
-
}
|
|
203
|
+
async start(overrides = {}) { await wire.request(["start-endpoint", this.owner.address, "client", overrides]); }
|
|
204
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.address, "client"]); }
|
|
184
205
|
}
|
|
185
|
-
class WindowHandle extends
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
206
|
+
class WindowHandle extends Events {
|
|
207
|
+
target;
|
|
208
|
+
surface;
|
|
209
|
+
constructor(target) {
|
|
210
|
+
super(...deferredScoped("host-end", target, (_event, values) => values[0]));
|
|
211
|
+
this.target = target;
|
|
212
|
+
this.surface = new WindowSurfaceHandle(target);
|
|
191
213
|
}
|
|
192
|
-
client() { return this.owner; }
|
|
193
214
|
async state() {
|
|
194
|
-
const answer = await wire.request(["window", this.
|
|
215
|
+
const answer = await wire.request(["window", await this.target()]);
|
|
195
216
|
return answer[0];
|
|
196
217
|
}
|
|
197
218
|
async title() { return (await this.state()).title; }
|
|
@@ -201,13 +222,64 @@ class WindowHandle extends WindowBase {
|
|
|
201
222
|
async front() { return (await this.state()).front; }
|
|
202
223
|
async layer() { return (await this.state()).layer; }
|
|
203
224
|
async location() { return (await this.state()).location; }
|
|
204
|
-
async move(position) { await wire.request(["move", this.
|
|
205
|
-
localMove(position) { wire.
|
|
206
|
-
async resize(size) { await wire.request(["resize", this.
|
|
207
|
-
localResize(size) { wire.
|
|
208
|
-
async minimize(minimized = true) { await wire.request(["minimize", this.
|
|
209
|
-
async changeTitle(title) { await wire.request(["changeTitle", this.
|
|
210
|
-
async raise() { await wire.request(["raise", this.
|
|
225
|
+
async move(position) { await wire.request(["move", await this.target(), position]); }
|
|
226
|
+
async localMove(position) { await wire.request(["localMove", await this.target(), position]); }
|
|
227
|
+
async resize(size) { await wire.request(["resize", await this.target(), size]); }
|
|
228
|
+
async localResize(size) { await wire.request(["localResize", await this.target(), size]); }
|
|
229
|
+
async minimize(minimized = true) { await wire.request(["minimize", await this.target(), minimized]); }
|
|
230
|
+
async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
|
|
231
|
+
async raise() { await wire.request(["raise", await this.target()]); }
|
|
232
|
+
}
|
|
233
|
+
class WindowSurfaceHandle {
|
|
234
|
+
target;
|
|
235
|
+
constructor(target) {
|
|
236
|
+
this.target = target;
|
|
237
|
+
}
|
|
238
|
+
async set(settings = {}) { await wire.request(["surfaceSet", await this.target(), settings]); }
|
|
239
|
+
async remove() { await wire.request(["surfaceRemove", await this.target()]); }
|
|
240
|
+
}
|
|
241
|
+
function deferredScoped(route, target, convert) {
|
|
242
|
+
return [
|
|
243
|
+
(event, listener, impossible) => {
|
|
244
|
+
if (!windowEvent(event)) {
|
|
245
|
+
impossible?.(new Error(`A Window has no "${event}" event`));
|
|
246
|
+
return () => undefined;
|
|
247
|
+
}
|
|
248
|
+
return deferred(target, subject => wire.on(route, event, (...values) => {
|
|
249
|
+
const message = unscoped(subject, values);
|
|
250
|
+
if (message)
|
|
251
|
+
listener(convert(event, message));
|
|
252
|
+
}, subject, impossible), impossible);
|
|
253
|
+
},
|
|
254
|
+
observer => deferred(target, subject => wire.onAll(route, (event, ...values) => {
|
|
255
|
+
if (typeof event !== "string" || !windowEvent(event))
|
|
256
|
+
return;
|
|
257
|
+
const message = unscoped(subject, values);
|
|
258
|
+
if (message)
|
|
259
|
+
observer(event, convert(event, message));
|
|
260
|
+
}, subject))
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
function windowEvent(event) {
|
|
264
|
+
return event === "move" || event === "resize" || event === "minimize" || event === "changeTitle" || event === "front";
|
|
265
|
+
}
|
|
266
|
+
function deferred(target, register, impossible) {
|
|
267
|
+
let active = true;
|
|
268
|
+
let stop = () => undefined;
|
|
269
|
+
void target().then(address => {
|
|
270
|
+
if (active)
|
|
271
|
+
stop = register(address.reference);
|
|
272
|
+
}, error => {
|
|
273
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
274
|
+
if (active && impossible)
|
|
275
|
+
impossible(failure);
|
|
276
|
+
else if (active)
|
|
277
|
+
queueMicrotask(() => { throw failure; });
|
|
278
|
+
});
|
|
279
|
+
return () => {
|
|
280
|
+
active = false;
|
|
281
|
+
stop();
|
|
282
|
+
};
|
|
211
283
|
}
|
|
212
284
|
export function scoped(route, subject, convert) {
|
|
213
285
|
return new Events((event, listener, impossible) => wire.on(route, event, (...values) => {
|
|
@@ -222,16 +294,23 @@ export function scoped(route, subject, convert) {
|
|
|
222
294
|
observer(event, convert(event, message));
|
|
223
295
|
}, subject));
|
|
224
296
|
}
|
|
297
|
+
/** Destinationless events originating from one Endpoint handle. */
|
|
298
|
+
export function endpointEvents(target, half) {
|
|
299
|
+
return new Events((event, listener, impossible) => wire.follow(target, half, event, listener, impossible), observer => wire.follow(target, half, null, (event, payload) => {
|
|
300
|
+
if (typeof event === "string")
|
|
301
|
+
observer(event, payload);
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
225
304
|
function unscoped(subject, values) {
|
|
226
305
|
if (subject === null)
|
|
227
306
|
return values;
|
|
228
307
|
return values[0] === subject ? values.slice(1) : null;
|
|
229
308
|
}
|
|
230
309
|
function programEvent(event, values) {
|
|
231
|
-
if (event === "
|
|
310
|
+
if (event === "endpointStart" || event === "endpointStop")
|
|
311
|
+
return lifecycleEndpoint(values[0], values[1]);
|
|
312
|
+
if (event === "processCreate")
|
|
232
313
|
return process(values[0]);
|
|
233
|
-
if (event === "serverStop")
|
|
234
|
-
return { process: process(values[0]), code: numberOrNull(values[1]), signal: stringOrNull(values[2]) };
|
|
235
314
|
if (event === "processExit")
|
|
236
315
|
return { process: process(values[0]), ...exit(values[1], values[2]) };
|
|
237
316
|
if (event === "uninstall")
|
|
@@ -239,12 +318,20 @@ function programEvent(event, values) {
|
|
|
239
318
|
return undefined;
|
|
240
319
|
}
|
|
241
320
|
function processEvent(event, values) {
|
|
242
|
-
if (event === "
|
|
243
|
-
return
|
|
321
|
+
if (event === "endpointStart" || event === "endpointStop")
|
|
322
|
+
return lifecycleEndpoint(values[0], values[1]);
|
|
244
323
|
if (event === "exit")
|
|
245
324
|
return exit(values[0], values[1]);
|
|
246
325
|
return undefined;
|
|
247
326
|
}
|
|
327
|
+
function lifecycleEndpoint(record, kind) {
|
|
328
|
+
const owner = process(record);
|
|
329
|
+
if (kind === "server")
|
|
330
|
+
return owner.server;
|
|
331
|
+
if (kind === "client")
|
|
332
|
+
return owner.client;
|
|
333
|
+
throw new Error("The host returned an invalid Endpoint lifecycle event");
|
|
334
|
+
}
|
|
248
335
|
export function exit(code, signal) {
|
|
249
336
|
const namedSignal = stringOrNull(signal);
|
|
250
337
|
return { status: namedSignal === null ? "exited" : "signaled", code: numberOrNull(code), signal: namedSignal };
|
|
@@ -263,23 +350,42 @@ export function bindEvents(target, events) {
|
|
|
263
350
|
observe: events.observe.bind(events)
|
|
264
351
|
});
|
|
265
352
|
}
|
|
266
|
-
export function program(record) {
|
|
267
|
-
|
|
353
|
+
export function program(record) {
|
|
354
|
+
const handle = handles.obtain(`program:${record.reference}`, () => new ProgramHandle(record));
|
|
355
|
+
handle.update(record);
|
|
356
|
+
return handle;
|
|
357
|
+
}
|
|
358
|
+
export function process(record, endpoints = {}) {
|
|
359
|
+
return handles.obtain(`process:${record.reference}`, () => new ProcessHandle(record, endpoints));
|
|
360
|
+
}
|
|
268
361
|
export function endpoint(reference) {
|
|
269
362
|
if (!reference)
|
|
270
363
|
throw new Error("The boundary returned an invalid Endpoint reference");
|
|
271
|
-
const owner =
|
|
364
|
+
const owner = process(reference.process);
|
|
272
365
|
return reference.kind === "server" ? owner.server : owner.client;
|
|
273
366
|
}
|
|
367
|
+
export function claimEndpoint(reference, kind, endpoint) {
|
|
368
|
+
return handles.adopt(`endpoint:${reference}:${kind}`, endpoint);
|
|
369
|
+
}
|
|
370
|
+
function endpointHandle(owner, kind, preferred) {
|
|
371
|
+
return handles.obtain(`endpoint:${owner.reference}:${kind}`, () => preferred ?? (kind === "server" ? new ServerHandle(owner) : new ClientHandle(owner)));
|
|
372
|
+
}
|
|
373
|
+
export function window(target) {
|
|
374
|
+
return new WindowHandle(target);
|
|
375
|
+
}
|
|
274
376
|
/** Resolves only endpoint identities intentionally visible to this Client. */
|
|
275
377
|
export function visibleEndpoint(reference) {
|
|
276
378
|
if (reference === null)
|
|
277
379
|
return null;
|
|
278
380
|
return endpoint(reference);
|
|
279
381
|
}
|
|
382
|
+
/** Runtime constructor shared by all client-visible Program handles. */
|
|
280
383
|
export const Program = CoreProgram;
|
|
384
|
+
/** Runtime constructor shared by all client-visible Process handles. */
|
|
281
385
|
export const Process = CoreProcess;
|
|
386
|
+
/** Runtime constructor shared by all client-visible Endpoint handles. */
|
|
282
387
|
export const Endpoint = CoreEndpoint;
|
|
388
|
+
/** Runtime constructor shared by all client-visible Server handles. */
|
|
283
389
|
export const Server = CoreServer;
|
|
390
|
+
/** Runtime constructor shared by all client-visible Client handles. */
|
|
284
391
|
export const Client = CoreClient;
|
|
285
|
-
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,25 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
x: number;
|
|
5
|
-
y: number;
|
|
6
|
-
}>;
|
|
7
|
-
/** One desktop layer's available workspace. */
|
|
8
|
-
export type Surface = Readonly<{
|
|
9
|
-
width: number;
|
|
10
|
-
height: number;
|
|
11
|
-
gutter: number;
|
|
12
|
-
}>;
|
|
13
|
-
/** Session-local desktop events available to a Client endpoint. */
|
|
14
|
-
export type HostEvents = {
|
|
15
|
-
surface: Surface;
|
|
16
|
-
pointerMove: PointerPosition;
|
|
17
|
-
};
|
|
1
|
+
import type { Permissions, ServedFile, Theme, ThemeProperties } from "@phreshos/core";
|
|
2
|
+
import { type HostPointer } from "./pointer.js";
|
|
3
|
+
import { type HostSurface } from "./surface.js";
|
|
18
4
|
/** Desktop capabilities structurally available to a Client endpoint. */
|
|
19
|
-
export interface Host
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
export interface Host {
|
|
6
|
+
/** Read-only system Theme explicitly read from and observed through the desktop host. */
|
|
7
|
+
readonly theme: Theme<ThemeProperties>;
|
|
8
|
+
/** This Client Window's asynchronous surface read and live updates. */
|
|
9
|
+
readonly surface: HostSurface;
|
|
10
|
+
/** Permission-guarded desktop pointer reads and live movement. */
|
|
11
|
+
readonly pointer: HostPointer;
|
|
12
|
+
/** Permission decisions for capabilities guarded by the desktop. */
|
|
13
|
+
readonly permissions: Permissions;
|
|
14
|
+
/** Stores one value as a publicly reachable file. */
|
|
22
15
|
serve(value: unknown): Promise<ServedFile>;
|
|
16
|
+
/** Performs an unrestricted server-side fetch on behalf of this Client. */
|
|
23
17
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
24
18
|
}
|
|
19
|
+
/** Desktop capabilities available to this Client. */
|
|
25
20
|
export declare const host: Host;
|
package/dist/host.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { content } from "./content.js";
|
|
2
|
-
import
|
|
2
|
+
import ClientTheme from "./theme.js";
|
|
3
3
|
import wire from "./wire.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return answer[0];
|
|
13
|
-
}
|
|
14
|
-
async surface(layer) {
|
|
15
|
-
const answer = await wire.request(["surface", undefined, layer]);
|
|
16
|
-
return answer[0];
|
|
17
|
-
}
|
|
4
|
+
import ClientPermissions from "./permissions.js";
|
|
5
|
+
import ClientPointer, {} from "./pointer.js";
|
|
6
|
+
import ClientSurface, {} from "./surface.js";
|
|
7
|
+
class ClientHost {
|
|
8
|
+
theme = new ClientTheme();
|
|
9
|
+
surface = new ClientSurface();
|
|
10
|
+
pointer = new ClientPointer();
|
|
11
|
+
permissions = new ClientPermissions();
|
|
18
12
|
async serve(value) {
|
|
19
13
|
const source = content(value);
|
|
20
14
|
const channel = new MessageChannel();
|
|
@@ -85,22 +79,6 @@ class ClientHost extends Events {
|
|
|
85
79
|
});
|
|
86
80
|
return response;
|
|
87
81
|
}
|
|
88
|
-
withPointerSampling(sample, stop) {
|
|
89
|
-
if (!sample)
|
|
90
|
-
return stop;
|
|
91
|
-
if (this.pointerListeners++ === 0)
|
|
92
|
-
window.addEventListener("pointermove", this.samplePointer);
|
|
93
|
-
let active = true;
|
|
94
|
-
return () => {
|
|
95
|
-
if (!active)
|
|
96
|
-
return;
|
|
97
|
-
active = false;
|
|
98
|
-
stop();
|
|
99
|
-
this.pointerListeners--;
|
|
100
|
-
if (this.pointerListeners === 0)
|
|
101
|
-
window.removeEventListener("pointermove", this.samplePointer);
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
82
|
}
|
|
105
83
|
function requestHeaders(normalized, supplied) {
|
|
106
84
|
if (!supplied)
|
|
@@ -142,8 +120,5 @@ function closeControl(signal, port, abort) {
|
|
|
142
120
|
signal.removeEventListener("abort", abort);
|
|
143
121
|
port.close();
|
|
144
122
|
}
|
|
145
|
-
|
|
146
|
-
return () => { for (const cleanup of cleanups)
|
|
147
|
-
cleanup(); };
|
|
148
|
-
}
|
|
123
|
+
/** Desktop capabilities available to this Client. */
|
|
149
124
|
export const host = new ClientHost();
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { host, type Host
|
|
1
|
+
export { host, type Host } from "./host.js";
|
|
2
|
+
export { type HostPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
|
|
3
|
+
export { type HostSurface, type Surface, type SurfaceEvents } from "./surface.js";
|
|
2
4
|
export { current, type Current, type CurrentServer } from "./current.js";
|
|
3
5
|
export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
|
|
4
|
-
export { Client, Endpoint, Process, Program, Server, Window, type AnswerCapture, type AnswerMessage, type AnswerObserver, type AskCapture, type AskMessage, type AskObserver, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
|
|
5
|
-
export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, Position, ProgramArea, ProgramEvents, ProgramProcessExit,
|
|
6
|
+
export { Client, Endpoint, Process, Program, Server, type Window, type AnswerCapture, type AnswerMessage, type AnswerObserver, type AskCapture, type AskMessage, type AskObserver, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
|
|
7
|
+
export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permissions, Position, ProgramArea, ProgramEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermissions, Timeoutable, Theme, ThemeEvents, ThemeProperties, Value, WritableTheme, WindowEvents, WindowLayer, WindowState, WindowSurface, WindowSurfaceEasing, WindowSurfaceSettings, WindowSurfaceTransaction } from "@phreshos/core";
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { host } from "./host.js";
|
|
2
|
+
export {} from "./pointer.js";
|
|
3
|
+
export {} from "./surface.js";
|
|
2
4
|
export { current } from "./current.js";
|
|
3
5
|
export {} from "./channel.js";
|
|
4
|
-
export { Client, Endpoint, Process, Program, Server
|
|
6
|
+
export { Client, Endpoint, Process, Program, Server } from "./domain.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PermissionDecision, PermissionName, Permissions, TimedPermissions } from "@phreshos/core";
|
|
2
|
+
/** Client permission access bound to the current Process boundary. */
|
|
3
|
+
export default class ClientPermissions implements Permissions {
|
|
4
|
+
granted(name: PermissionName): Promise<PermissionDecision>;
|
|
5
|
+
request(name: PermissionName): Promise<PermissionDecision>;
|
|
6
|
+
timeout(milliseconds: number): TimedPermissions;
|
|
7
|
+
private requestWithin;
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import wire from "./wire.js";
|
|
2
|
+
const defaultPermissionTimeout = 30_000;
|
|
3
|
+
/** Client permission access bound to the current Process boundary. */
|
|
4
|
+
export default class ClientPermissions {
|
|
5
|
+
async granted(name) {
|
|
6
|
+
const answer = await wire.request(["permission-granted", name]);
|
|
7
|
+
return answer[0];
|
|
8
|
+
}
|
|
9
|
+
request(name) {
|
|
10
|
+
return this.requestWithin(name, defaultPermissionTimeout);
|
|
11
|
+
}
|
|
12
|
+
timeout(milliseconds) {
|
|
13
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0)
|
|
14
|
+
throw new Error("A permission timeout must be a non-negative finite number");
|
|
15
|
+
return Object.freeze({ request: (name) => this.requestWithin(name, milliseconds) });
|
|
16
|
+
}
|
|
17
|
+
async requestWithin(name, milliseconds) {
|
|
18
|
+
const answer = await wire.requestOrNull(["permission-request", name], milliseconds);
|
|
19
|
+
return answer?.[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Subscribable } from "@phreshos/core";
|
|
2
|
+
import Events from "./events.js";
|
|
3
|
+
/** Pointer coordinates relative to the desktop display core. */
|
|
4
|
+
export type PointerPosition = Readonly<{
|
|
5
|
+
/** Horizontal coordinate in CSS pixels. */
|
|
6
|
+
x: number;
|
|
7
|
+
/** Vertical coordinate in CSS pixels. */
|
|
8
|
+
y: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** Live pointer events visible to this Client. */
|
|
11
|
+
export type PointerEvents = {
|
|
12
|
+
/** The pointer moved over the desktop display core. */
|
|
13
|
+
move: PointerPosition;
|
|
14
|
+
};
|
|
15
|
+
/** Permission-guarded pointer positions and future movement. */
|
|
16
|
+
export interface HostPointer extends Subscribable<PointerEvents, never> {
|
|
17
|
+
/**
|
|
18
|
+
* Reads the current desktop pointer position, or `null` before one is known.
|
|
19
|
+
* Rejects unless the `pointer` permission is currently granted.
|
|
20
|
+
*/
|
|
21
|
+
position(): Promise<PointerPosition | null>;
|
|
22
|
+
}
|
|
23
|
+
/** Client pointer access bound to the current Process boundary. */
|
|
24
|
+
export default class ClientPointer extends Events {
|
|
25
|
+
private listeners;
|
|
26
|
+
private readonly sample;
|
|
27
|
+
constructor();
|
|
28
|
+
position(): Promise<Readonly<{
|
|
29
|
+
/** Horizontal coordinate in CSS pixels. */
|
|
30
|
+
x: number;
|
|
31
|
+
/** Vertical coordinate in CSS pixels. */
|
|
32
|
+
y: number;
|
|
33
|
+
}> | null>;
|
|
34
|
+
private withSampling;
|
|
35
|
+
}
|