@phreshos/client 0.1.46 → 0.1.48

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.
@@ -1,4 +1,4 @@
1
- import { type Appearance, type AppearanceEvents, type WritableAppearance } from "@phreshos/core";
1
+ import { type AppearanceEvents, type AppearanceUpdate, type WritableAppearance } from "@phreshos/core";
2
2
  import Events from "./events.js";
3
3
  /** Read-only System Appearance reached through the Desktop boundary. */
4
4
  export default class ClientAppearance extends Events<AppearanceEvents, never> implements WritableAppearance {
@@ -13,5 +13,5 @@ export default class ClientAppearance extends Events<AppearanceEvents, never> im
13
13
  signInWallpaper: import("@phreshos/core").ThemedValue<string | null>;
14
14
  desktopWallpaper: import("@phreshos/core").ThemedValue<string | null>;
15
15
  }>>;
16
- update(appearance: Appearance): Promise<void>;
16
+ update(appearance: AppearanceUpdate): Promise<void>;
17
17
  }
package/dist/context.js CHANGED
@@ -1,8 +1,8 @@
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
- import { contextPermissions } from "./permissions.js";
5
+ import { programPermissions } from "./permissions.js";
6
6
  class ContextServerHandle extends ServerEndpoint {
7
7
  owner;
8
8
  subscribe;
@@ -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,8 +95,8 @@ contextClient = new ContextClientHandle(owner);
95
95
  class ClientContext extends Events {
96
96
  server = contextServer;
97
97
  window = contextClient.window;
98
- localWindow = localWindow(currentAddress);
99
- permissions = contextPermissions();
98
+ presentation = presentation(currentAddress);
99
+ permissions = programPermissions();
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) => {
102
102
  if (typeof event === "string")
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,10 +1,9 @@
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, parseProgramDefinition, 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";
5
5
  import { area, sql, store } from "./storage.js";
6
6
  import startup from "./startup.js";
7
- import launch from "./launch.js";
8
7
  import { programPermissions } from "./permissions.js";
9
8
  import wire from "./wire.js";
10
9
  const handles = new HandleRegistry();
@@ -20,7 +19,6 @@ class ProgramHandle extends CoreProgram {
20
19
  logs;
21
20
  database;
22
21
  startup;
23
- launch;
24
22
  permissions;
25
23
  record;
26
24
  constructor(record) {
@@ -34,7 +32,6 @@ class ProgramHandle extends CoreProgram {
34
32
  this.logs = sql("logs", this.address);
35
33
  this.database = sql("database", this.address);
36
34
  this.startup = startup(this.address);
37
- this.launch = launch(this.address);
38
35
  this.permissions = programPermissions(this.address);
39
36
  const events = scoped("program-host", record.reference, programEvent);
40
37
  this.subscribe = events.subscribe;
@@ -58,6 +55,16 @@ class ProgramHandle extends CoreProgram {
58
55
  const answer = await wire.request(["icon", this.address, size]);
59
56
  return new Blob([Uint8Array.from(answer[0])], { type: "image/png" });
60
57
  }
58
+ async definition() {
59
+ const answer = await wire.request(["program-definition", this.address]);
60
+ return parseProgramDefinition(answer[0]);
61
+ }
62
+ async pinned() {
63
+ const answer = await wire.request(["pinned", this.address, "get"]);
64
+ return answer[0];
65
+ }
66
+ async pin() { await wire.request(["pinned", this.address, "pin"]); }
67
+ async unpin() { await wire.request(["pinned", this.address, "unpin"]); }
61
68
  async agent() {
62
69
  if (!this.hasAgent)
63
70
  return null;
@@ -236,12 +243,12 @@ class ServerEndpointHandle extends CoreServerEndpoint {
236
243
  this.wait = events.wait;
237
244
  this.events = events.events;
238
245
  }
239
- async process() { return this.owner; }
246
+ async process() { await this.running(); return this.owner; }
240
247
  publish = (event, payload = undefined) => {
241
248
  wire.send("end-host", "send", this.owner.address, "server", event, payload);
242
249
  };
243
- async exists() {
244
- const answer = await wire.request(["exists", "server", this.owner.address]);
250
+ async running() {
251
+ const answer = await wire.request(["running", "server", this.owner.address]);
245
252
  return answer[0];
246
253
  }
247
254
  async start(launch = {}) { await wire.request(["start-endpoint", this.owner.address, "server", launch]); }
@@ -289,12 +296,12 @@ class ClientEndpointHandle extends CoreClientEndpoint {
289
296
  this.wait = events.wait;
290
297
  this.events = events.events;
291
298
  }
292
- async process() { return this.owner; }
299
+ async process() { await this.running(); return this.owner; }
293
300
  publish = (event, payload = undefined) => {
294
301
  wire.send("end-host", "send", this.owner.address, "client", event, payload);
295
302
  };
296
- async exists() {
297
- const answer = await wire.request(["exists", "client", this.owner.address]);
303
+ async running() {
304
+ const answer = await wire.request(["running", "client", this.owner.address]);
298
305
  return answer[0];
299
306
  }
300
307
  async start(launch = {}) { await wire.request(["start-endpoint", this.owner.address, "client", launch]); }
@@ -314,6 +321,8 @@ class WindowHandle extends Events {
314
321
  }
315
322
  async title() { return (await this.state()).title; }
316
323
  async header() { return (await this.state()).header; }
324
+ async frame() { return (await this.state()).frame; }
325
+ async transaction() { return (await this.state()).transaction; }
317
326
  async position() { return (await this.state()).position; }
318
327
  async size() { return (await this.state()).size; }
319
328
  async minimized() { return (await this.state()).minimized; }
@@ -325,40 +334,61 @@ class WindowHandle extends Events {
325
334
  async setGeometry(geometry) { await wire.request(["setGeometry", await this.target(), geometry]); }
326
335
  async minimize(minimized = true) { await wire.request(["minimize", await this.target(), minimized]); }
327
336
  async maximize(maximized = true) { await wire.request(["maximize", await this.target(), maximized]); }
328
- async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
329
- async changeHeader(header) { await wire.request(["changeHeader", await this.target(), header]); }
337
+ async setTitle(title) { await wire.request(["setTitle", await this.target(), title]); }
338
+ async setHeader(header) { await wire.request(["setHeader", await this.target(), header]); }
339
+ async setFrame(frame) { await wire.request(["setFrame", await this.target(), frame]); }
340
+ async setTransaction(transaction) { await wire.request(["setTransaction", await this.target(), transaction]); }
330
341
  async raise() { await wire.request(["raise", await this.target()]); }
331
342
  }
332
- const windowTargets = new WeakMap();
333
- class LocalWindowHandle {
343
+ class WindowPresentationHandle extends Events {
334
344
  target;
335
345
  selected;
336
346
  constructor(target, selected) {
347
+ super((event, listener, impossible) => {
348
+ if (!windowEvent(event)) {
349
+ impossible?.(new Error(`A Window presentation has no "${event}" event`));
350
+ return () => undefined;
351
+ }
352
+ return wire.on("presentation", event, listener, null, impossible);
353
+ }, (listener, impossible) => wire.onAll("presentation", (event, ...values) => {
354
+ if (typeof event === "string")
355
+ listener(event, values[0]);
356
+ }, null, impossible));
337
357
  this.target = target;
338
358
  this.selected = selected;
339
359
  }
340
360
  transaction(transaction) {
341
- return new LocalWindowHandle(this.target, transaction);
342
- }
343
- async addSurface() { await this.change("windowLocalSurfaceAdd"); }
344
- async removeSurface() { await this.change("windowLocalSurfaceRemove"); }
345
- async move(position) { await this.change("windowLocalMove", position); }
346
- async resize(size) { await this.change("windowLocalResize", size); }
347
- async setGeometry(geometry) { await this.change("windowLocalGeometry", geometry); }
348
- async minimize(minimized = true) { await this.change("windowLocalMinimize", minimized); }
349
- async maximize(maximized = true) { await this.change("windowLocalMaximize", maximized); }
350
- async changeTitle(title) { await wire.request(["windowLocalTitle", await this.target(), title]); }
351
- async changeHeader(header) { await wire.request(["windowLocalHeader", await this.target(), header]); }
352
- async follow(window) {
353
- const target = windowTargets.get(window);
354
- if (!target)
355
- throw new Error("Local Window follow requires a Window from this Client SDK");
356
- await wire.request(["windowLocalFollow", await this.target(), await target(), this.selected]);
357
- }
358
- async unfollow() { await wire.request(["windowLocalUnfollow", await this.target(), this.selected]); }
359
- async raise() { await wire.request(["windowLocalRaise", await this.target()]); }
361
+ return new WindowPresentationHandle(this.target, { transaction, wait: false });
362
+ }
363
+ transactionAndWait(transaction) {
364
+ return new WindowPresentationHandle(this.target, { transaction, wait: true });
365
+ }
366
+ async title() { return await this.read("title"); }
367
+ async header() { return await this.read("header"); }
368
+ async frame() { return await this.read("frame"); }
369
+ async position() { return await this.read("position"); }
370
+ async size() { return await this.read("size"); }
371
+ async minimized() { return await this.read("minimized"); }
372
+ async maximized() { return await this.read("maximized"); }
373
+ async front() { return await this.read("front"); }
374
+ async layer() { return await this.read("layer"); }
375
+ async move(position) { await this.change("windowPresentationMove", position); }
376
+ async resize(size) { await this.change("windowPresentationResize", size); }
377
+ async setGeometry(geometry) { await this.change("windowPresentationGeometry", geometry); }
378
+ async minimize(minimized = true) { await this.change("windowPresentationMinimize", minimized); }
379
+ async maximize(maximized = true) { await this.change("windowPresentationMaximize", maximized); }
380
+ async setTitle(title) { await this.change("windowPresentationTitle", title); }
381
+ async setHeader(header) { await this.change("windowPresentationHeader", header); }
382
+ async setFrame(frame) { await this.change("windowPresentationFrame", frame); }
383
+ async follow() { await this.change("windowPresentationFollow"); }
384
+ async unfollow() { await this.change("windowPresentationUnfollow"); }
385
+ async raise() { await this.change("windowPresentationRaise"); }
360
386
  async change(operation, value) {
361
- await wire.request([operation, await this.target(), value, this.selected]);
387
+ await wire.request([operation, await this.target(), value, this.selected?.transaction, this.selected?.wait === true]);
388
+ }
389
+ async read(property) {
390
+ const answer = await wire.request(["windowPresentationRead", await this.target(), property]);
391
+ return answer[0];
362
392
  }
363
393
  }
364
394
  function deferredScoped(route, target, convert) {
@@ -384,7 +414,7 @@ function deferredScoped(route, target, convert) {
384
414
  ];
385
415
  }
386
416
  function windowEvent(event) {
387
- return event === "move" || event === "resize" || event === "geometry" || event === "minimize" || event === "maximize" || event === "changeTitle" || event === "changeHeader" || event === "front";
417
+ return event === "move" || event === "resize" || event === "minimize" || event === "maximize" || event === "changeTitle" || event === "changeHeader" || event === "changeFrame" || event === "changeTransaction" || event === "front";
388
418
  }
389
419
  function deferred(target, register, impossible) {
390
420
  let active = true;
@@ -396,8 +426,6 @@ function deferred(target, register, impossible) {
396
426
  const failure = error instanceof Error ? error : new Error(String(error));
397
427
  if (active && impossible)
398
428
  impossible(failure);
399
- else if (active)
400
- queueMicrotask(() => { throw failure; });
401
429
  });
402
430
  return () => {
403
431
  active = false;
@@ -426,11 +454,17 @@ export function endpointEvents(target, half) {
426
454
  }
427
455
  /** Start and stop transitions belonging directly to one permanent Endpoint. */
428
456
  export function endpointLifecycle(target, half) {
429
- return new Events((event, listener, impossible) => deferred(target, subject => wire.on("process-host", endpointLifecycleEvent(event), (...values) => {
457
+ const resolved = () => target();
458
+ const viable = async () => {
459
+ const address = await resolved();
460
+ await wire.request(["running", half, address]);
461
+ return address;
462
+ };
463
+ return new Events((event, listener, impossible) => deferred(impossible ? viable : resolved, subject => wire.on("process-host", endpointLifecycleEvent(event), (...values) => {
430
464
  const message = unscoped(subject, values);
431
465
  if (message?.[1] === half)
432
466
  listener(undefined);
433
- }, subject, impossible), impossible), (listener, impossible) => deferred(target, subject => wire.onAll("process-host", (event, ...values) => {
467
+ }, subject, impossible), impossible), (listener, impossible) => deferred(impossible ? viable : resolved, subject => wire.onAll("process-host", (event, ...values) => {
434
468
  if (event !== "endpointStart" && event !== "endpointStop")
435
469
  return;
436
470
  const message = unscoped(subject, values);
@@ -457,6 +491,8 @@ function programEvent(event, values) {
457
491
  return { process: process(values[0]), ...exit(values[1], values[2]) };
458
492
  if (event === "uninstall")
459
493
  return { purge: values[0] === true };
494
+ if (event === "pinned")
495
+ return values[0] === true;
460
496
  return undefined;
461
497
  }
462
498
  function processEvent(event, values) {
@@ -496,12 +532,10 @@ function endpointHandle(owner, kind, preferred) {
496
532
  return handles.obtain(`endpoint:${owner.reference}:${kind}`, () => preferred ?? (kind === "server" ? new ServerEndpointHandle(owner) : new ClientEndpointHandle(owner)));
497
533
  }
498
534
  export function window(target) {
499
- const handle = new WindowHandle(target);
500
- windowTargets.set(handle, target);
501
- return handle;
535
+ return new WindowHandle(target);
502
536
  }
503
- export function localWindow(target) {
504
- return new LocalWindowHandle(target);
537
+ export function presentation(target) {
538
+ return new WindowPresentationHandle(target);
505
539
  }
506
540
  /** Resolves an Endpoint reference through the Client Endpoint's global handle registry. */
507
541
  export function visibleEndpoint(reference) {
@@ -1,7 +1,5 @@
1
- import type { ContextPermissions, ProgramPermissions } from "@phreshos/core";
1
+ import type { ProgramPermissions } from "@phreshos/core";
2
2
  import type { HandleAddress } from "./domain.js";
3
3
  export declare const defaultPermissionTimeout = 120000;
4
4
  /** Bind authoritative permission state to one exact Program handle. */
5
- export declare function programPermissions(program: HandleAddress): ProgramPermissions;
6
- /** Permission state and owner requests belonging to the current Client Endpoint. */
7
- export declare function contextPermissions(): ContextPermissions;
5
+ export declare function programPermissions(program?: HandleAddress): ProgramPermissions;
@@ -4,30 +4,19 @@ export const defaultPermissionTimeout = 120_000;
4
4
  /** Bind authoritative permission state to one exact Program handle. */
5
5
  export function programPermissions(program) {
6
6
  const operate = (operation, name, permission) => (wire.request(["program-permissions", program, operation, name, permission]));
7
- return {
8
- async get(name) { return parsePermission(name, (await operate("get", name))[0]); },
9
- async all() { return parsePermissions((await operate("all"))[0]); },
10
- async allows(name, permission = true) { return (await operate("allows", name, permission))[0] === true; },
11
- async set(name, permission) { await operate("set", name, permission); },
12
- async delete(name) { await operate("delete", name); }
13
- };
14
- }
15
- /** Permission state and owner requests belonging to the current Client Endpoint. */
16
- export function contextPermissions() {
17
7
  const timed = (timeout) => ({
18
8
  async request(name, permission = true) {
19
9
  const identity = crypto.randomUUID();
20
- const result = await wire.requestOrNull(["context-permission-request", identity, name, permission], timeout);
10
+ const result = await wire.requestOrNull(["program-permissions", program, "request", identity, name, permission], timeout);
21
11
  return result === null ? null : parsePermission(name, result[0]);
22
12
  }
23
13
  });
24
14
  return {
25
- async get(name) {
26
- return parsePermission(name, (await wire.request(["context-permission-get", name]))[0]);
27
- },
28
- async allows(name, permission = true) {
29
- return (await wire.request(["context-permission-allows", name, permission]))[0] === true;
30
- },
15
+ async get(name) { return parsePermission(name, (await operate("get", name))[0]); },
16
+ async all() { return parsePermissions((await operate("all"))[0]); },
17
+ async allows(name, permission = true) { return (await operate("allows", name, permission))[0] === true; },
18
+ async allow(name, permission = true) { await operate("allow", name, permission); },
19
+ async deny(name) { await operate("deny", name); },
31
20
  request: timed(defaultPermissionTimeout).request,
32
21
  timeout(milliseconds) {
33
22
  if (!Number.isFinite(milliseconds) || milliseconds < 0)
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,58 @@
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() {
26
+ const [value] = await wire.request(["service-program-metadata", this.serviceAddress]);
27
+ return parseServiceProgramMetadata(value);
28
+ }
29
+ async programIcon(size = "medium") {
30
+ const [value] = await wire.request(["service-program-icon", this.serviceAddress, size]);
31
+ return parseServiceProgramIcon(value);
23
32
  }
24
33
  }
25
34
  class ServerHandler extends CoreServerService {
26
- key;
35
+ serviceAddress;
27
36
  subscribe;
28
37
  wait;
29
38
  events;
30
39
  lifecycle;
31
40
  service;
32
- constructor(key) {
41
+ constructor(serviceAddress) {
33
42
  super();
34
- this.key = key;
35
- this.service = new ServiceHandle(key);
43
+ this.serviceAddress = serviceAddress;
44
+ this.service = new ServiceHandle(serviceAddress);
36
45
  this.lifecycle = this.service.lifecycle;
37
- const events = new Events(...serviceEvents(key, "events"));
46
+ const events = new Events(...serviceEvents(serviceAddress, "events"));
38
47
  this.subscribe = events.subscribe;
39
48
  this.wait = events.wait;
40
49
  this.events = events.events;
41
50
  }
42
51
  publish = (event, payload = undefined) => { this.service.publish(event, payload); };
43
- exists() { return this.service.exists(); }
52
+ address() { return this.serviceAddress; }
53
+ available() { return this.service.available(); }
54
+ programMetadata() { return this.service.programMetadata(); }
55
+ programIcon(size) { return this.service.programIcon(size); }
44
56
  waitReady(timeout) { return this.service.waitReady(timeout); }
45
57
  async ask(event, payload = undefined) {
46
58
  return await this.askWithin(new Deadline(), event, payload);
@@ -53,7 +65,7 @@ class ServerHandler extends CoreServerService {
53
65
  const address = `client:${identity.process}:${crypto.randomUUID()}`;
54
66
  const question = crypto.randomUUID();
55
67
  const waiting = wire.expectWithin(address, deadline);
56
- wire.send("end-host", "service-ask", this.key, address, question, event, payload);
68
+ wire.send("end-host", "service-ask", this.serviceAddress, address, question, event, payload);
57
69
  try {
58
70
  return await waiting;
59
71
  }
@@ -63,45 +75,61 @@ class ServerHandler extends CoreServerService {
63
75
  }
64
76
  }
65
77
  class ClientHandler extends CoreClientService {
78
+ serviceAddress;
66
79
  subscribe;
67
80
  wait;
68
81
  events;
69
82
  lifecycle;
70
83
  service;
71
- constructor(key) {
84
+ constructor(serviceAddress) {
72
85
  super();
73
- this.service = new ServiceHandle(key);
86
+ this.serviceAddress = serviceAddress;
87
+ this.service = new ServiceHandle(serviceAddress);
74
88
  this.lifecycle = this.service.lifecycle;
75
- const events = new Events(...serviceEvents(key, "events"));
89
+ const events = new Events(...serviceEvents(serviceAddress, "events"));
76
90
  this.subscribe = events.subscribe;
77
91
  this.wait = events.wait;
78
92
  this.events = events.events;
79
93
  }
80
94
  publish = (event, payload = undefined) => { this.service.publish(event, payload); };
81
- exists() { return this.service.exists(); }
95
+ address() { return this.serviceAddress; }
96
+ available() { return this.service.available(); }
97
+ programMetadata() { return this.service.programMetadata(); }
98
+ programIcon(size) { return this.service.programIcon(size); }
82
99
  waitReady(timeout) { return this.service.waitReady(timeout); }
83
100
  }
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" });
101
+ export function prepareService(address) {
102
+ if (!isServiceAddress(address))
103
+ throw new Error("A complete Service address is required");
104
+ const identity = JSON.stringify([address.program, address.process, address.endpoint]);
105
+ if (address.endpoint === "server") {
106
+ const normalized = Object.freeze({ program: address.program, process: address.process, endpoint: "server" });
94
107
  return handles.obtain(`service:${identity}`, () => new ServerHandler(normalized));
95
108
  }
96
- const normalized = Object.freeze({ ...address, endpoint: "client" });
109
+ const normalized = Object.freeze({ program: address.program, process: address.process, endpoint: "client" });
97
110
  return handles.obtain(`service:${identity}`, () => new ClientHandler(normalized));
98
111
  }
99
- function serviceEvents(key, scope) {
112
+ function serviceEvents(address, scope) {
100
113
  return [
101
- (event, listener, impossible) => wire.followService(key, scope, event, listener, impossible),
102
- (listener, impossible) => wire.followService(key, scope, null, (event, payload) => {
114
+ (event, listener, impossible) => wire.followService(address, scope, event, listener, impossible),
115
+ (listener, impossible) => wire.followService(address, scope, null, (event, payload) => {
103
116
  if (typeof event === "string")
104
117
  listener(event, payload);
105
118
  }, impossible)
106
119
  ];
107
120
  }
121
+ function parseServiceProgramMetadata(value) {
122
+ if (!value || typeof value !== "object")
123
+ throw new Error("The System returned invalid Service Program metadata");
124
+ const metadata = value;
125
+ if (typeof metadata.name !== "string" || typeof metadata.version !== "string") {
126
+ throw new Error("The System returned invalid Service Program metadata");
127
+ }
128
+ return Object.freeze({ name: metadata.name, version: metadata.version });
129
+ }
130
+ function parseServiceProgramIcon(value) {
131
+ if (!Array.isArray(value) || value.some(byte => typeof byte !== "number")) {
132
+ throw new Error("The System returned an invalid Service Program icon");
133
+ }
134
+ return new Blob([Uint8Array.from(value)], { type: "image/png" });
135
+ }
package/dist/system.js CHANGED
@@ -15,18 +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
21
  execute(request) {
21
22
  return executeRequest(this, request);
22
23
  }
23
- service(key) { return prepareService(key); }
24
24
  async *shell(command, options = {}) {
25
25
  const { signal, ...settings } = options;
26
26
  for await (const event of wire.stream(["shell", command, settings], undefined, signal))
27
27
  yield parseShellEvent(event);
28
28
  }
29
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
+ }
30
47
  class SystemProgramHandle extends Events {
31
48
  constructor() {
32
49
  super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(systemProgramEvent(event, values)), null, impossible), observer => wire.onAll("host-program", (event, ...values) => {
@@ -111,6 +128,8 @@ function systemProgramEvent(event, values) {
111
128
  return program(values[1]);
112
129
  if (event === "uninstall")
113
130
  return { program: program(values[1]), purge: values[2] === true };
131
+ if (event === "pinned")
132
+ return { program: program(values[1]), pinned: values[2] === true };
114
133
  return values[0];
115
134
  }
116
135
  function systemConnectionEvent(_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.46",
3
+ "version": "0.1.48",
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.53"
49
+ "@phreshos/core": "^0.1.56"
50
50
  },
51
51
  "dependencies": {
52
52
  "@the-link/messagepack": "^0.1.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@phreshos/core": "^0.1.53",
55
+ "@phreshos/core": "^0.1.56",
56
56
  "typescript": "^6.0.3",
57
57
  "vitest": "^4.1.10"
58
58
  }
package/dist/launch.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { type ProgramLaunch } from "@phreshos/core";
2
- import type { HandleAddress } from "./domain.js";
3
- /** Bind saved launch operations to one Program handle. */
4
- export default function launch(program: HandleAddress): ProgramLaunch;
package/dist/launch.js DELETED
@@ -1,14 +0,0 @@
1
- import { parseLaunch } from "@phreshos/core";
2
- import wire from "./wire.js";
3
- /** Bind saved launch operations to one Program handle. */
4
- export default function launch(program) {
5
- return {
6
- async get() {
7
- const [value] = await wire.request(["launch", program, "get"]);
8
- return value === null ? null : parseLaunch(value);
9
- },
10
- async set(value) {
11
- await wire.request(["launch", program, "set", parseLaunch(value)]);
12
- }
13
- };
14
- }