@phreshos/client 0.1.45 → 0.1.47

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/context.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Deadline from "./deadline.js";
2
- import { ClientEndpoint, ServerEndpoint, ServerTrafficHandle, TrafficHandle, endpointLifecycle, endpointEvents, process, program, visibleEndpoint, localWindow, window as windowHandle, } from "./domain.js";
2
+ import { ClientEndpoint, ServerEndpoint, ServerTrafficHandle, TrafficHandle, endpointLifecycle, endpointEvents, process, program, visibleEndpoint, presentation, window as windowHandle, } from "./domain.js";
3
3
  import Events from "./events.js";
4
4
  import wire from "./wire.js";
5
5
  import { contextPermissions } from "./permissions.js";
@@ -18,12 +18,12 @@ class ContextServerHandle extends ServerEndpoint {
18
18
  this.wait = events.wait;
19
19
  this.events = events.events;
20
20
  }
21
- process() { return this.owner(); }
21
+ async process() { await this.running(); return this.owner(); }
22
22
  publish = (event, payload = undefined) => {
23
23
  wire.send("end-end", event, payload);
24
24
  };
25
- async exists() {
26
- const answer = await wire.request(["exists", "server"]);
25
+ async running() {
26
+ const answer = await wire.request(["running", "server"]);
27
27
  return answer[0];
28
28
  }
29
29
  async start(launch = {}) { await wire.request(["start-endpoint", undefined, "server", launch]); }
@@ -76,14 +76,14 @@ class ContextClientHandle extends ClientEndpoint {
76
76
  this.wait = events.wait;
77
77
  this.events = events.events;
78
78
  }
79
- process() { return this.owner(); }
79
+ async process() { await this.running(); return this.owner(); }
80
80
  publish = (event, payload = undefined) => {
81
81
  void wire.identity().then(identity => {
82
82
  wire.send("end-host", "send", { identity: identity.process, reference: identity.reference }, "client", event, payload);
83
83
  });
84
84
  };
85
- async exists() {
86
- const answer = await wire.request(["exists", "client"]);
85
+ async running() {
86
+ const answer = await wire.request(["running", "client"]);
87
87
  return answer[0];
88
88
  }
89
89
  async start(launch = {}) { await wire.request(["start-endpoint", undefined, "client", launch]); }
@@ -95,7 +95,7 @@ contextClient = new ContextClientHandle(owner);
95
95
  class ClientContext extends Events {
96
96
  server = contextServer;
97
97
  window = contextClient.window;
98
- localWindow = localWindow(currentAddress);
98
+ presentation = presentation(currentAddress);
99
99
  permissions = contextPermissions();
100
100
  constructor() {
101
101
  super((event, listener, impossible) => wire.on("end-end", event, value => listener(contextMessage(value)), null, impossible), (listener, impossible) => wire.onAll("end-end", (event, value) => {
@@ -6,6 +6,7 @@ export default class ClientPreferences extends Events<DesktopPreferencesEvents,
6
6
  snapshot(): Promise<Readonly<{
7
7
  theme: import("@phreshos/core").Theme;
8
8
  animations: boolean;
9
+ scale: number;
9
10
  }>>;
10
11
  update(preferences: DesktopPreferencesUpdate): Promise<void>;
11
12
  }
package/dist/domain.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ClientEndpoint as CoreClientEndpoint, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, type AnswerCapture as CoreAnswerCapture, type AnswerMessage as CoreAnswerMessage, type AnswerOutcome, type AnswerSubscriber as CoreAnswerSubscriber, type AskCapture as CoreAskCapture, type AskMessage as CoreAskMessage, type AskSubscriber as CoreAskSubscriber, type Cleanup, type ClientTraffic as CoreClientTraffic, type ClientDeclaration, type EndpointReference as CoreEndpointReference, type EndpointSnapshot, type HandleAddress, type ProcessSnapshot, type ProgramSnapshot, type EndpointTraffic as CoreEndpointTraffic, type EndpointLifecycleEvents, type EndpointDeclaration, type Exit, type EventOptions, type LocalWindow, type ProgramProcessRunEvent as CoreProgramProcessRunEvent, type ProgramProcessRunOptions as CoreProgramProcessRunOptions, type ServerTraffic as CoreServerTraffic, type TrafficMessage as CoreTrafficMessage, type TrafficCapture as CoreTrafficCapture, type TrafficEvents as CoreTrafficEvents, type Window as CoreWindow, type WindowState } from "@phreshos/core";
1
+ import { ClientEndpoint as CoreClientEndpoint, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, type AnswerCapture as CoreAnswerCapture, type AnswerMessage as CoreAnswerMessage, type AnswerOutcome, type AnswerSubscriber as CoreAnswerSubscriber, type AskCapture as CoreAskCapture, type AskMessage as CoreAskMessage, type AskSubscriber as CoreAskSubscriber, type Cleanup, type ClientTraffic as CoreClientTraffic, type ClientDeclaration, type EndpointReference as CoreEndpointReference, type EndpointSnapshot, type HandleAddress, type ProcessSnapshot, type ProgramSnapshot, type EndpointTraffic as CoreEndpointTraffic, type EndpointLifecycleEvents, type EndpointDeclaration, type Exit, type EventOptions, type WindowPresentation, type ProgramProcessRunEvent as CoreProgramProcessRunEvent, type ProgramProcessRunOptions as CoreProgramProcessRunOptions, type ServerTraffic as CoreServerTraffic, type TrafficMessage as CoreTrafficMessage, type TrafficCapture as CoreTrafficCapture, type TrafficEvents as CoreTrafficEvents, type Window as CoreWindow, type WindowState } from "@phreshos/core";
2
2
  import Events from "./events.js";
3
3
  export type { HandleAddress };
4
4
  export type EndpointDeclarationRecord = EndpointDeclaration;
@@ -83,7 +83,7 @@ export declare function process(value: unknown, endpoints?: {
83
83
  export declare function endpoint(value: unknown): Endpoint;
84
84
  export declare function claimEndpoint(reference: string, kind: "server" | "client", endpoint: Endpoint): Endpoint<{}, unknown>;
85
85
  export declare function window(target: WindowTarget): Window;
86
- export declare function localWindow(target: WindowTarget): LocalWindow;
86
+ export declare function presentation(target: WindowTarget): WindowPresentation;
87
87
  /** Resolves an Endpoint reference through the Client Endpoint's global handle registry. */
88
88
  export declare function visibleEndpoint(reference: EndpointReference | null | undefined): Endpoint | null;
89
89
  /** Runtime constructor shared by all client-visible Program handles. */
package/dist/domain.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ClientEndpoint as CoreClientEndpoint, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, parseEndpointReference, parseProcessSnapshot, parseProgramSnapshot } from "@phreshos/core";
1
+ import { ClientEndpoint as CoreClientEndpoint, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, parseEndpointReference, parseProcessSnapshot, parseProgramSnapshot, } from "@phreshos/core";
2
2
  import Events, { stream } from "./events.js";
3
3
  import Deadline from "./deadline.js";
4
4
  import HandleRegistry from "./handle-registry.js";
@@ -236,12 +236,12 @@ class ServerEndpointHandle extends CoreServerEndpoint {
236
236
  this.wait = events.wait;
237
237
  this.events = events.events;
238
238
  }
239
- async process() { return this.owner; }
239
+ async process() { await this.running(); return this.owner; }
240
240
  publish = (event, payload = undefined) => {
241
241
  wire.send("end-host", "send", this.owner.address, "server", event, payload);
242
242
  };
243
- async exists() {
244
- const answer = await wire.request(["exists", "server", this.owner.address]);
243
+ async running() {
244
+ const answer = await wire.request(["running", "server", this.owner.address]);
245
245
  return answer[0];
246
246
  }
247
247
  async start(launch = {}) { await wire.request(["start-endpoint", this.owner.address, "server", launch]); }
@@ -289,12 +289,12 @@ class ClientEndpointHandle extends CoreClientEndpoint {
289
289
  this.wait = events.wait;
290
290
  this.events = events.events;
291
291
  }
292
- async process() { return this.owner; }
292
+ async process() { await this.running(); return this.owner; }
293
293
  publish = (event, payload = undefined) => {
294
294
  wire.send("end-host", "send", this.owner.address, "client", event, payload);
295
295
  };
296
- async exists() {
297
- const answer = await wire.request(["exists", "client", this.owner.address]);
296
+ async running() {
297
+ const answer = await wire.request(["running", "client", this.owner.address]);
298
298
  return answer[0];
299
299
  }
300
300
  async start(launch = {}) { await wire.request(["start-endpoint", this.owner.address, "client", launch]); }
@@ -313,6 +313,9 @@ class WindowHandle extends Events {
313
313
  return answer[0];
314
314
  }
315
315
  async title() { return (await this.state()).title; }
316
+ async header() { return (await this.state()).header; }
317
+ async frame() { return (await this.state()).frame; }
318
+ async transaction() { return (await this.state()).transaction; }
316
319
  async position() { return (await this.state()).position; }
317
320
  async size() { return (await this.state()).size; }
318
321
  async minimized() { return (await this.state()).minimized; }
@@ -324,38 +327,61 @@ class WindowHandle extends Events {
324
327
  async setGeometry(geometry) { await wire.request(["setGeometry", await this.target(), geometry]); }
325
328
  async minimize(minimized = true) { await wire.request(["minimize", await this.target(), minimized]); }
326
329
  async maximize(maximized = true) { await wire.request(["maximize", await this.target(), maximized]); }
327
- async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
330
+ async setTitle(title) { await wire.request(["setTitle", await this.target(), title]); }
331
+ async setHeader(header) { await wire.request(["setHeader", await this.target(), header]); }
332
+ async setFrame(frame) { await wire.request(["setFrame", await this.target(), frame]); }
333
+ async setTransaction(transaction) { await wire.request(["setTransaction", await this.target(), transaction]); }
328
334
  async raise() { await wire.request(["raise", await this.target()]); }
329
335
  }
330
- const windowTargets = new WeakMap();
331
- class LocalWindowHandle {
336
+ class WindowPresentationHandle extends Events {
332
337
  target;
333
338
  selected;
334
339
  constructor(target, selected) {
340
+ super((event, listener, impossible) => {
341
+ if (!windowEvent(event)) {
342
+ impossible?.(new Error(`A Window presentation has no "${event}" event`));
343
+ return () => undefined;
344
+ }
345
+ return wire.on("presentation", event, listener, null, impossible);
346
+ }, (listener, impossible) => wire.onAll("presentation", (event, ...values) => {
347
+ if (typeof event === "string")
348
+ listener(event, values[0]);
349
+ }, null, impossible));
335
350
  this.target = target;
336
351
  this.selected = selected;
337
352
  }
338
353
  transaction(transaction) {
339
- return new LocalWindowHandle(this.target, transaction);
340
- }
341
- async addSurface() { await this.change("windowLocalSurfaceAdd"); }
342
- async removeSurface() { await this.change("windowLocalSurfaceRemove"); }
343
- async move(position) { await this.change("windowLocalMove", position); }
344
- async resize(size) { await this.change("windowLocalResize", size); }
345
- async setGeometry(geometry) { await this.change("windowLocalGeometry", geometry); }
346
- async minimize(minimized = true) { await this.change("windowLocalMinimize", minimized); }
347
- async maximize(maximized = true) { await this.change("windowLocalMaximize", maximized); }
348
- async changeTitle(title) { await wire.request(["windowLocalTitle", await this.target(), title]); }
349
- async follow(window) {
350
- const target = windowTargets.get(window);
351
- if (!target)
352
- throw new Error("Local Window follow requires a Window from this Client SDK");
353
- await wire.request(["windowLocalFollow", await this.target(), await target(), this.selected]);
354
- }
355
- async unfollow() { await wire.request(["windowLocalUnfollow", await this.target(), this.selected]); }
356
- async raise() { await wire.request(["windowLocalRaise", await this.target()]); }
354
+ return new WindowPresentationHandle(this.target, { transaction, wait: false });
355
+ }
356
+ transactionAndWait(transaction) {
357
+ return new WindowPresentationHandle(this.target, { transaction, wait: true });
358
+ }
359
+ async title() { return await this.read("title"); }
360
+ async header() { return await this.read("header"); }
361
+ async frame() { return await this.read("frame"); }
362
+ async position() { return await this.read("position"); }
363
+ async size() { return await this.read("size"); }
364
+ async minimized() { return await this.read("minimized"); }
365
+ async maximized() { return await this.read("maximized"); }
366
+ async front() { return await this.read("front"); }
367
+ async layer() { return await this.read("layer"); }
368
+ async move(position) { await this.change("windowPresentationMove", position); }
369
+ async resize(size) { await this.change("windowPresentationResize", size); }
370
+ async setGeometry(geometry) { await this.change("windowPresentationGeometry", geometry); }
371
+ async minimize(minimized = true) { await this.change("windowPresentationMinimize", minimized); }
372
+ async maximize(maximized = true) { await this.change("windowPresentationMaximize", maximized); }
373
+ async setTitle(title) { await this.change("windowPresentationTitle", title); }
374
+ async setHeader(header) { await this.change("windowPresentationHeader", header); }
375
+ async setFrame(frame) { await this.change("windowPresentationFrame", frame); }
376
+ async follow() { await this.change("windowPresentationFollow"); }
377
+ async unfollow() { await this.change("windowPresentationUnfollow"); }
378
+ async raise() { await this.change("windowPresentationRaise"); }
357
379
  async change(operation, value) {
358
- await wire.request([operation, await this.target(), value, this.selected]);
380
+ await wire.request([operation, await this.target(), value, this.selected?.transaction, this.selected?.wait === true]);
381
+ }
382
+ async read(property) {
383
+ const answer = await wire.request(["windowPresentationRead", await this.target(), property]);
384
+ return answer[0];
359
385
  }
360
386
  }
361
387
  function deferredScoped(route, target, convert) {
@@ -381,7 +407,7 @@ function deferredScoped(route, target, convert) {
381
407
  ];
382
408
  }
383
409
  function windowEvent(event) {
384
- return event === "move" || event === "resize" || event === "geometry" || event === "minimize" || event === "maximize" || event === "changeTitle" || event === "front";
410
+ return event === "move" || event === "resize" || event === "minimize" || event === "maximize" || event === "changeTitle" || event === "changeHeader" || event === "changeFrame" || event === "changeTransaction" || event === "front";
385
411
  }
386
412
  function deferred(target, register, impossible) {
387
413
  let active = true;
@@ -393,8 +419,6 @@ function deferred(target, register, impossible) {
393
419
  const failure = error instanceof Error ? error : new Error(String(error));
394
420
  if (active && impossible)
395
421
  impossible(failure);
396
- else if (active)
397
- queueMicrotask(() => { throw failure; });
398
422
  });
399
423
  return () => {
400
424
  active = false;
@@ -423,11 +447,17 @@ export function endpointEvents(target, half) {
423
447
  }
424
448
  /** Start and stop transitions belonging directly to one permanent Endpoint. */
425
449
  export function endpointLifecycle(target, half) {
426
- return new Events((event, listener, impossible) => deferred(target, subject => wire.on("process-host", endpointLifecycleEvent(event), (...values) => {
450
+ const resolved = () => target();
451
+ const viable = async () => {
452
+ const address = await resolved();
453
+ await wire.request(["running", half, address]);
454
+ return address;
455
+ };
456
+ return new Events((event, listener, impossible) => deferred(impossible ? viable : resolved, subject => wire.on("process-host", endpointLifecycleEvent(event), (...values) => {
427
457
  const message = unscoped(subject, values);
428
458
  if (message?.[1] === half)
429
459
  listener(undefined);
430
- }, subject, impossible), impossible), (listener, impossible) => deferred(target, subject => wire.onAll("process-host", (event, ...values) => {
460
+ }, subject, impossible), impossible), (listener, impossible) => deferred(impossible ? viable : resolved, subject => wire.onAll("process-host", (event, ...values) => {
431
461
  if (event !== "endpointStart" && event !== "endpointStop")
432
462
  return;
433
463
  const message = unscoped(subject, values);
@@ -493,12 +523,10 @@ function endpointHandle(owner, kind, preferred) {
493
523
  return handles.obtain(`endpoint:${owner.reference}:${kind}`, () => preferred ?? (kind === "server" ? new ServerEndpointHandle(owner) : new ClientEndpointHandle(owner)));
494
524
  }
495
525
  export function window(target) {
496
- const handle = new WindowHandle(target);
497
- windowTargets.set(handle, target);
498
- return handle;
526
+ return new WindowHandle(target);
499
527
  }
500
- export function localWindow(target) {
501
- return new LocalWindowHandle(target);
528
+ export function presentation(target) {
529
+ return new WindowPresentationHandle(target);
502
530
  }
503
531
  /** Resolves an Endpoint reference through the Client Endpoint's global handle registry. */
504
532
  export function visibleEndpoint(reference) {
package/dist/service.d.ts CHANGED
@@ -1,8 +1,4 @@
1
- import { type ClientService, type ServerService, type Service, type ServiceKey } from "@phreshos/core";
2
- export declare function prepareService<EventsMap extends object = {}, Fallback = unknown>(key: ServiceKey & {
3
- endpoint: "server";
4
- }): ServerService<EventsMap, Fallback>;
5
- export declare function prepareService<EventsMap extends object = {}, Fallback = unknown>(key: ServiceKey & {
6
- endpoint: "client";
7
- }): ClientService<EventsMap, Fallback>;
8
- export declare function prepareService(key: ServiceKey): Service;
1
+ import { type ClientService, type ServerService, type ServiceAddress } from "@phreshos/core";
2
+ export declare function prepareService<EventsMap extends object = {}, Fallback = unknown>(address: ServiceAddress<"server">): ServerService<EventsMap, Fallback>;
3
+ export declare function prepareService<EventsMap extends object = {}, Fallback = unknown>(address: ServiceAddress<"client">): ClientService<EventsMap, Fallback>;
4
+ export declare function prepareService(address: ServiceAddress): ServerService | ClientService;
package/dist/service.js CHANGED
@@ -1,46 +1,54 @@
1
- import { ClientService as CoreClientService, ServerService as CoreServerService, isServiceKey } from "@phreshos/core";
1
+ import { ClientService as CoreClientService, ServerService as CoreServerService, isServiceAddress } from "@phreshos/core";
2
2
  import Deadline from "./deadline.js";
3
3
  import Events from "./events.js";
4
4
  import HandleRegistry from "./handle-registry.js";
5
5
  import wire from "./wire.js";
6
6
  const handles = new HandleRegistry();
7
7
  class ServiceHandle {
8
- key;
8
+ serviceAddress;
9
9
  lifecycle;
10
- constructor(key) {
11
- this.key = key;
12
- this.lifecycle = new Events(...serviceEvents(key, "lifecycle"));
10
+ constructor(serviceAddress) {
11
+ this.serviceAddress = serviceAddress;
12
+ this.lifecycle = new Events(...serviceEvents(serviceAddress, "lifecycle"));
13
13
  }
14
14
  publish(event, payload = undefined) {
15
- wire.send("end-host", "service-send", this.key, event, payload);
15
+ wire.send("end-host", "service-send", this.serviceAddress, event, payload);
16
16
  }
17
- async exists() {
18
- const answer = await wire.request(["service-exists", this.key]);
17
+ address() { return this.serviceAddress; }
18
+ async available() {
19
+ const answer = await wire.request(["service-available", this.serviceAddress]);
19
20
  return answer[0];
20
21
  }
21
22
  async waitReady(timeout) {
22
- await wire.request(["service-wait-ready", this.key, timeout], timeout);
23
+ await wire.request(["service-wait-ready", this.serviceAddress, timeout], timeout);
24
+ }
25
+ async programMetadata(options = {}) {
26
+ const iconSize = options.icon ?? "medium";
27
+ const [value] = await wire.request(["service-program-metadata", this.serviceAddress, iconSize]);
28
+ return parseServiceProgramMetadata(value);
23
29
  }
24
30
  }
25
31
  class ServerHandler extends CoreServerService {
26
- key;
32
+ serviceAddress;
27
33
  subscribe;
28
34
  wait;
29
35
  events;
30
36
  lifecycle;
31
37
  service;
32
- constructor(key) {
38
+ constructor(serviceAddress) {
33
39
  super();
34
- this.key = key;
35
- this.service = new ServiceHandle(key);
40
+ this.serviceAddress = serviceAddress;
41
+ this.service = new ServiceHandle(serviceAddress);
36
42
  this.lifecycle = this.service.lifecycle;
37
- const events = new Events(...serviceEvents(key, "events"));
43
+ const events = new Events(...serviceEvents(serviceAddress, "events"));
38
44
  this.subscribe = events.subscribe;
39
45
  this.wait = events.wait;
40
46
  this.events = events.events;
41
47
  }
42
48
  publish = (event, payload = undefined) => { this.service.publish(event, payload); };
43
- exists() { return this.service.exists(); }
49
+ address() { return this.serviceAddress; }
50
+ available() { return this.service.available(); }
51
+ programMetadata(options) { return this.service.programMetadata(options); }
44
52
  waitReady(timeout) { return this.service.waitReady(timeout); }
45
53
  async ask(event, payload = undefined) {
46
54
  return await this.askWithin(new Deadline(), event, payload);
@@ -53,7 +61,7 @@ class ServerHandler extends CoreServerService {
53
61
  const address = `client:${identity.process}:${crypto.randomUUID()}`;
54
62
  const question = crypto.randomUUID();
55
63
  const waiting = wire.expectWithin(address, deadline);
56
- wire.send("end-host", "service-ask", this.key, address, question, event, payload);
64
+ wire.send("end-host", "service-ask", this.serviceAddress, address, question, event, payload);
57
65
  try {
58
66
  return await waiting;
59
67
  }
@@ -63,45 +71,59 @@ class ServerHandler extends CoreServerService {
63
71
  }
64
72
  }
65
73
  class ClientHandler extends CoreClientService {
74
+ serviceAddress;
66
75
  subscribe;
67
76
  wait;
68
77
  events;
69
78
  lifecycle;
70
79
  service;
71
- constructor(key) {
80
+ constructor(serviceAddress) {
72
81
  super();
73
- this.service = new ServiceHandle(key);
82
+ this.serviceAddress = serviceAddress;
83
+ this.service = new ServiceHandle(serviceAddress);
74
84
  this.lifecycle = this.service.lifecycle;
75
- const events = new Events(...serviceEvents(key, "events"));
85
+ const events = new Events(...serviceEvents(serviceAddress, "events"));
76
86
  this.subscribe = events.subscribe;
77
87
  this.wait = events.wait;
78
88
  this.events = events.events;
79
89
  }
80
90
  publish = (event, payload = undefined) => { this.service.publish(event, payload); };
81
- exists() { return this.service.exists(); }
91
+ address() { return this.serviceAddress; }
92
+ available() { return this.service.available(); }
93
+ programMetadata(options) { return this.service.programMetadata(options); }
82
94
  waitReady(timeout) { return this.service.waitReady(timeout); }
83
95
  }
84
- export function prepareService(key) {
85
- if (!isServiceKey(key))
86
- throw new Error("A complete service key is required");
87
- const address = Object.freeze({
88
- ...(key.program === undefined ? {} : { program: key.program }),
89
- process: key.process
90
- });
91
- const identity = JSON.stringify([key.program ?? null, key.process, key.endpoint]);
92
- if (key.endpoint === "server") {
93
- const normalized = Object.freeze({ ...address, endpoint: "server" });
96
+ export function prepareService(address) {
97
+ if (!isServiceAddress(address))
98
+ throw new Error("A complete Service address is required");
99
+ const identity = JSON.stringify([address.program, address.process, address.endpoint]);
100
+ if (address.endpoint === "server") {
101
+ const normalized = Object.freeze({ program: address.program, process: address.process, endpoint: "server" });
94
102
  return handles.obtain(`service:${identity}`, () => new ServerHandler(normalized));
95
103
  }
96
- const normalized = Object.freeze({ ...address, endpoint: "client" });
104
+ const normalized = Object.freeze({ program: address.program, process: address.process, endpoint: "client" });
97
105
  return handles.obtain(`service:${identity}`, () => new ClientHandler(normalized));
98
106
  }
99
- function serviceEvents(key, scope) {
107
+ function serviceEvents(address, scope) {
100
108
  return [
101
- (event, listener, impossible) => wire.followService(key, scope, event, listener, impossible),
102
- (listener, impossible) => wire.followService(key, scope, null, (event, payload) => {
109
+ (event, listener, impossible) => wire.followService(address, scope, event, listener, impossible),
110
+ (listener, impossible) => wire.followService(address, scope, null, (event, payload) => {
103
111
  if (typeof event === "string")
104
112
  listener(event, payload);
105
113
  }, impossible)
106
114
  ];
107
115
  }
116
+ function parseServiceProgramMetadata(value) {
117
+ if (!value || typeof value !== "object")
118
+ throw new Error("The System returned invalid Service Program metadata");
119
+ const metadata = value;
120
+ if (typeof metadata.name !== "string" || typeof metadata.version !== "string"
121
+ || !Array.isArray(metadata.icon) || metadata.icon.some(byte => typeof byte !== "number")) {
122
+ throw new Error("The System returned invalid Service Program metadata");
123
+ }
124
+ return Object.freeze({
125
+ name: metadata.name,
126
+ version: metadata.version,
127
+ icon: new Blob([Uint8Array.from(metadata.icon)], { type: "image/png" })
128
+ });
129
+ }
package/dist/system.js CHANGED
@@ -1,4 +1,4 @@
1
- import { parseShellEvent, parseSessionEndSnapshot } from "@phreshos/core";
1
+ import { parseShellEvent, parseSessionEndSnapshot, execute as executeRequest } from "@phreshos/core";
2
2
  import ClientAppearance from "./appearance.js";
3
3
  import wire from "./wire.js";
4
4
  import { prepareService } from "./service.js";
@@ -15,15 +15,35 @@ class ClientSystem {
15
15
  process = new SystemProcessHandle();
16
16
  connection = new SystemConnectionHandle();
17
17
  session = new SystemSessionHandle();
18
+ service = new SystemServiceHandle();
18
19
  uploads = uploads;
19
20
  network = network;
20
- service(key) { return prepareService(key); }
21
+ execute(request) {
22
+ return executeRequest(this, request);
23
+ }
21
24
  async *shell(command, options = {}) {
22
25
  const { signal, ...settings } = options;
23
26
  for await (const event of wire.stream(["shell", command, settings], undefined, signal))
24
27
  yield parseShellEvent(event);
25
28
  }
26
29
  }
30
+ class SystemServiceHandle extends Events {
31
+ constructor() {
32
+ super((event, listener, impossible) => wire.on("host-service", event, (...values) => listener(prepareService(values[1])), null, impossible), observer => wire.onAll("host-service", (event, ...values) => {
33
+ if (typeof event === "string")
34
+ observer(event, prepareService(values[1]));
35
+ }));
36
+ }
37
+ async list() {
38
+ const [addresses] = await wire.request(["host-service-list"]);
39
+ return addresses.map(address => prepareService(address));
40
+ }
41
+ async search(name) {
42
+ const [addresses] = await wire.request(["host-service-search", name]);
43
+ return addresses.map(address => prepareService(address));
44
+ }
45
+ prepare(address) { return prepareService(address); }
46
+ }
27
47
  class SystemProgramHandle extends Events {
28
48
  constructor() {
29
49
  super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(systemProgramEvent(event, values)), null, impossible), observer => wire.onAll("host-program", (event, ...values) => {
package/dist/wire.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Cleanup, ServiceKey } from "@phreshos/core";
1
+ import type { Cleanup, ServiceAddress } from "@phreshos/core";
2
2
  import Deadline from "./deadline.js";
3
3
  import type { HandleAddress } from "./domain.js";
4
4
  type Handler = (...values: unknown[]) => unknown;
@@ -37,7 +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, impossible?: Failure): Cleanup;
40
+ followService(address: ServiceAddress, scope: "lifecycle" | "events", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
41
41
  private register;
42
42
  private unregister;
43
43
  private deliver;
package/dist/wire.js CHANGED
@@ -269,12 +269,12 @@ class Wire {
269
269
  });
270
270
  }
271
271
  /** Follow one exact service lifecycle or application event route. */
272
- followService(key, scope, event, handler, impossible) {
272
+ followService(address, scope, event, handler, impossible) {
273
273
  const subscription = crypto.randomUUID();
274
274
  const stop = this.on("service-event", subscription, handler);
275
275
  if (impossible)
276
276
  this.impossible.set(subscription, impossible);
277
- this.send("end-host", "service-follow", subscription, key, scope, event, impossible !== undefined);
277
+ this.send("end-host", "service-follow", subscription, address, scope, event, impossible !== undefined);
278
278
  return once(() => {
279
279
  stop();
280
280
  this.impossible.delete(subscription);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -46,13 +46,13 @@
46
46
  "test": "vitest run --project default"
47
47
  },
48
48
  "peerDependencies": {
49
- "@phreshos/core": "^0.1.52"
49
+ "@phreshos/core": "^0.1.55"
50
50
  },
51
51
  "dependencies": {
52
52
  "@the-link/messagepack": "^0.1.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@phreshos/core": "^0.1.52",
55
+ "@phreshos/core": "^0.1.55",
56
56
  "typescript": "^6.0.3",
57
57
  "vitest": "^4.1.10"
58
58
  }