@phreshos/client 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/system.js CHANGED
@@ -1,13 +1,22 @@
1
+ import { parseShellEvent } from "@phreshos/core";
1
2
  import ClientAppearance from "./appearance.js";
2
3
  import wire from "./wire.js";
3
- import ClientDesktop, {} from "./desktop/desktop.js";
4
4
  import { prepareService } from "./service.js";
5
5
  import { uploads } from "./uploads.js";
6
6
  import controlledStream from "./controlled-stream.js";
7
+ import Events from "./events.js";
8
+ import { exit, process, program } from "./domain.js";
9
+ import { systemStorage } from "./storage.js";
7
10
  class ClientSystem {
11
+ storage = systemStorage();
8
12
  appearance = new ClientAppearance();
9
- desktop = new ClientDesktop();
13
+ program = new SystemProgramHandle();
14
+ process = new SystemProcessHandle();
10
15
  uploads = uploads;
16
+ async forceCreateProgram(source) {
17
+ const answer = await wire.request(["host-program-force-create", source]);
18
+ return program(answer[0]);
19
+ }
11
20
  service(key) { return prepareService(key); }
12
21
  async fetch(input, init) {
13
22
  const request = new Request(input, {
@@ -64,6 +73,61 @@ class ClientSystem {
64
73
  });
65
74
  return response;
66
75
  }
76
+ async *shell(command, options = {}) {
77
+ const { signal, ...settings } = options;
78
+ for await (const event of wire.stream(["shell", command, settings], undefined, signal))
79
+ yield parseShellEvent(event);
80
+ }
81
+ }
82
+ class SystemProgramHandle extends Events {
83
+ constructor() {
84
+ super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(systemProgramEvent(event, values)), null, impossible), observer => wire.onAll("host-program", (event, ...values) => {
85
+ if (typeof event === "string")
86
+ observer(event, systemProgramEvent(event, values));
87
+ }));
88
+ }
89
+ async list(onlyInstalled = false) {
90
+ const answer = await wire.request(["host-program-list", onlyInstalled]);
91
+ return answer[0].map(program);
92
+ }
93
+ async find(identity) {
94
+ const answer = await wire.request(["host-program-find", identity]);
95
+ return answer[0] ? program(answer[0]) : null;
96
+ }
97
+ async create(source) {
98
+ const answer = await wire.request(["host-program-create", source]);
99
+ return program(answer[0]);
100
+ }
101
+ }
102
+ class SystemProcessHandle extends Events {
103
+ constructor() {
104
+ super((event, listener, impossible) => wire.on("host-process", event, (...values) => listener(systemProcessEvent(event, values)), null, impossible), observer => wire.onAll("host-process", (event, ...values) => {
105
+ if (typeof event === "string")
106
+ observer(event, systemProcessEvent(event, values));
107
+ }));
108
+ }
109
+ async list() {
110
+ const answer = await wire.request(["host-process-list"]);
111
+ return answer[0].map(record => process(record));
112
+ }
113
+ async find(identity) {
114
+ const answer = await wire.request(["host-process-find", identity]);
115
+ return answer[0] ? process(answer[0]) : null;
116
+ }
117
+ }
118
+ function systemProcessEvent(event, values) {
119
+ if (event === "create")
120
+ return process(values[1]);
121
+ if (event === "exit")
122
+ return { process: process(values[1]), ...exit(values[2], values[3]) };
123
+ return values[0];
124
+ }
125
+ function systemProgramEvent(event, values) {
126
+ if (event === "create" || event === "forget" || event === "install")
127
+ return program(values[1]);
128
+ if (event === "uninstall")
129
+ return { program: program(values[1]), everything: values[2] === true };
130
+ return values[0];
67
131
  }
68
132
  function requestHeaders(normalized, supplied) {
69
133
  if (!supplied)
@@ -80,5 +144,5 @@ function closeControl(signal, port, abort) {
80
144
  signal.removeEventListener("abort", abort);
81
145
  port.close();
82
146
  }
83
- /** Desktop capabilities available to this Client. */
147
+ /** The global System represented through this Client runtime. */
84
148
  export const system = new ClientSystem();
package/dist/uploads.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type SystemUploads, type Upload } from "@phreshos/core";
2
2
  /** Flat upload access transported through the Client's Desktop boundary. */
3
3
  declare class ClientUploads implements SystemUploads {
4
+ path(): Promise<string>;
4
5
  write(value: unknown): Promise<Upload>;
5
6
  stream(file: string): Promise<ReadableStream<Uint8Array>>;
6
7
  bytes(file: string): Promise<Uint8Array<ArrayBuffer>>;
package/dist/uploads.js CHANGED
@@ -4,6 +4,12 @@ import controlledStream from "./controlled-stream.js";
4
4
  import wire from "./wire.js";
5
5
  /** Flat upload access transported through the Client's Desktop boundary. */
6
6
  class ClientUploads {
7
+ async path() {
8
+ const answer = await wire.request(["uploads", "path"]);
9
+ if (typeof answer[0] !== "string" || !answer[0])
10
+ throw new Error("The System returned an invalid uploads path");
11
+ return answer[0];
12
+ }
7
13
  async write(value) {
8
14
  const source = content(value);
9
15
  const channel = new MessageChannel();
package/dist/wire.d.ts CHANGED
@@ -17,7 +17,7 @@ declare class Wire {
17
17
  send(route: string, ...values: unknown[]): void;
18
18
  request(values: unknown[], timeout?: number, transfer?: Transferable[]): Promise<unknown>;
19
19
  /** Opens one long-running desktop operation and yields its ordered values. */
20
- stream(values: unknown[], timeout?: number): AsyncIterableIterator<unknown>;
20
+ stream(values: unknown[], timeout?: number, signal?: AbortSignal): AsyncIterableIterator<unknown>;
21
21
  /** Requests a value while treating the caller's deadline as an absent answer. */
22
22
  requestOrNull(values: unknown[], timeout: number): Promise<unknown | null>;
23
23
  requestWithin(values: unknown[], deadline: Deadline, transfer?: Transferable[]): Promise<unknown>;
@@ -37,8 +37,7 @@ declare class Wire {
37
37
  /** Follow destinationless events emitted by one Endpoint. */
38
38
  follow(target: HandleAddress | null, half: "server" | "client", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
39
39
  /** Follow one exact service lifecycle or application event route. */
40
- followService(key: ServiceKey, scope: "lifecycle" | "events", event: string | null, handler: Handler): Cleanup;
41
- samplePointer(movementX: number, movementY: number): void;
40
+ followService(key: ServiceKey, scope: "lifecycle" | "events", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
42
41
  private register;
43
42
  private unregister;
44
43
  private deliver;
package/dist/wire.js CHANGED
@@ -54,7 +54,7 @@ class Wire {
54
54
  return this.question("end-host", values, timeout, transfer);
55
55
  }
56
56
  /** Opens one long-running desktop operation and yields its ordered values. */
57
- stream(values, timeout = defaultTimeout) {
57
+ stream(values, timeout = defaultTimeout, signal) {
58
58
  const wire = this;
59
59
  return (async function* () {
60
60
  const question = `client:${crypto.randomUUID()}:${crypto.randomUUID()}`;
@@ -70,6 +70,15 @@ class Wire {
70
70
  state.wake = null;
71
71
  }, timeout)
72
72
  };
73
+ const abort = () => {
74
+ state.failure = signal?.reason instanceof Error ? signal.reason : new Error("The operation was cancelled");
75
+ state.wake?.();
76
+ state.wake = null;
77
+ };
78
+ if (signal?.aborted)
79
+ abort();
80
+ else
81
+ signal?.addEventListener("abort", abort, { once: true });
73
82
  wire.send("boundary", "expect", question);
74
83
  wire.streams.set(question, state);
75
84
  wire.send("end-host", "stream", question, ...values);
@@ -87,6 +96,7 @@ class Wire {
87
96
  }
88
97
  }
89
98
  finally {
99
+ signal?.removeEventListener("abort", abort);
90
100
  clearTimeout(state.timer);
91
101
  wire.streams.delete(question);
92
102
  wire.send("boundary", "forget", question);
@@ -259,18 +269,18 @@ class Wire {
259
269
  });
260
270
  }
261
271
  /** Follow one exact service lifecycle or application event route. */
262
- followService(key, scope, event, handler) {
272
+ followService(key, scope, event, handler, impossible) {
263
273
  const subscription = crypto.randomUUID();
264
274
  const stop = this.on("service-event", subscription, handler);
265
- this.send("end-host", "service-follow", subscription, key, scope, event);
275
+ if (impossible)
276
+ this.impossible.set(subscription, impossible);
277
+ this.send("end-host", "service-follow", subscription, key, scope, event, impossible !== undefined);
266
278
  return once(() => {
267
279
  stop();
280
+ this.impossible.delete(subscription);
268
281
  this.send("end-host", "service-unfollow", subscription);
269
282
  });
270
283
  }
271
- samplePointer(movementX, movementY) {
272
- this.send("end-host", "pointerSample", movementX, movementY);
273
- }
274
284
  register(kind, route, event, subject, impossible) {
275
285
  const subscription = crypto.randomUUID();
276
286
  if (impossible)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,13 +47,13 @@
47
47
  "prepack": "node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.36"
50
+ "@phreshos/core": "^0.1.37"
51
51
  },
52
52
  "dependencies": {
53
53
  "@msgpack/msgpack": "^3.1.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@phreshos/core": "^0.1.36",
56
+ "@phreshos/core": "^0.1.37",
57
57
  "typescript": "^6.0.3"
58
58
  }
59
59
  }
@@ -1,11 +0,0 @@
1
- import Events from "../events.js";
2
- /** Permission-guarded access to the Desktop pointer visible to this Client. */
3
- export default class ClientPointer extends Events {
4
- private listeners;
5
- private readonly sample;
6
- constructor();
7
- snapshot(): Promise<Readonly<{
8
- position: import("@phreshos/core").DesktopPointerPosition | null;
9
- }>>;
10
- private withSampling;
11
- }
@@ -1,59 +0,0 @@
1
- import Events from "../events.js";
2
- import wire from "../wire.js";
3
- /** Permission-guarded access to the Desktop pointer visible to this Client. */
4
- export default class ClientPointer extends Events {
5
- listeners = 0;
6
- sample = (event) => wire.samplePointer(event.movementX, event.movementY);
7
- constructor() {
8
- super((event, listener, impossible) => this.withSampling(event === "move", wire.on("host-desktop-pointer", event, value => {
9
- const snapshot = createSnapshot(value);
10
- if (snapshot)
11
- listener(snapshot);
12
- }, null, impossible)), observer => this.withSampling(true, wire.onAll("host-desktop-pointer", (event, value) => {
13
- const snapshot = createSnapshot(value);
14
- if (event === "move" && snapshot)
15
- observer(event, snapshot);
16
- })));
17
- }
18
- async snapshot() {
19
- const answer = await wire.request(["desktopPointer"]);
20
- const snapshot = createSnapshot(answer[0]);
21
- if (!snapshot)
22
- throw new Error("The Desktop returned an invalid pointer");
23
- return snapshot;
24
- }
25
- withSampling(sample, stop) {
26
- if (!sample)
27
- return stop;
28
- if (this.listeners++ === 0)
29
- window.addEventListener("pointermove", this.sample);
30
- let active = true;
31
- return () => {
32
- if (!active)
33
- return;
34
- active = false;
35
- stop();
36
- this.listeners--;
37
- if (this.listeners === 0)
38
- window.removeEventListener("pointermove", this.sample);
39
- };
40
- }
41
- }
42
- function createSnapshot(value) {
43
- if (!record(value))
44
- return null;
45
- if (value.position === null)
46
- return Object.freeze({ position: null });
47
- if (!record(value.position))
48
- return null;
49
- const { x, y } = value.position;
50
- if (!finite(x) || !finite(y))
51
- return null;
52
- return Object.freeze({ position: Object.freeze({ x, y }) });
53
- }
54
- function record(value) {
55
- return typeof value === "object" && value !== null && !Array.isArray(value);
56
- }
57
- function finite(value) {
58
- return typeof value === "number" && Number.isFinite(value);
59
- }