@phreshos/client 0.1.38 → 0.1.39

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/README.md CHANGED
@@ -49,6 +49,12 @@ bun run verify
49
49
  `verify` checks the types and completions, builds the package, and validates its
50
50
  published shape.
51
51
 
52
+ `check` performs static checks, `build` creates distributable output, and `test`
53
+ runs Vitest assertions from `tests/`. Run `build` before testing built artifacts.
54
+ `verify` runs `check`, `build`, and `test` in order. Operational tooling belongs
55
+ in `scripts/`; tests and their fixtures belong in `tests/`. Verification uses
56
+ the committed dependency graph without local package substitutions.
57
+
52
58
  ## Related repositories
53
59
 
54
60
  - [`@phreshos/core`](https://github.com/PhreshOS/core) is the one public import
@@ -4,13 +4,14 @@ import Events from "./events.js";
4
4
  export default class ClientAppearance extends Events<AppearanceEvents, never> implements WritableAppearance {
5
5
  constructor();
6
6
  snapshot(): Promise<Readonly<{
7
- colors: import("@phreshos/core").AppearanceColors;
8
- spacing: import("@phreshos/core").ThemedValue<number>;
9
- radius: import("@phreshos/core").ThemedValue<number>;
10
- shadow: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceShadow, import("@phreshos/core").AppearanceShadow>;
11
- material: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceMaterial, import("@phreshos/core").AppearanceMaterial>;
12
- signInWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
13
- desktopWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
7
+ colors: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceColors>;
8
+ spacing: number;
9
+ radius: number;
10
+ shadow: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceShadow>;
11
+ material: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceMaterial>;
12
+ transaction: import("@phreshos/core").AppearanceTransaction;
13
+ signInWallpaper: import("@phreshos/core").ThemedValue<string | null>;
14
+ desktopWallpaper: import("@phreshos/core").ThemedValue<string | null>;
14
15
  }>>;
15
16
  update(appearance: Appearance): Promise<void>;
16
17
  }
@@ -1,19 +1,19 @@
1
- import { createAppearanceSnapshot } from "@phreshos/core";
1
+ import { parseAppearance } from "@phreshos/core";
2
2
  import Events from "./events.js";
3
3
  import wire from "./wire.js";
4
4
  /** Read-only System Appearance reached through the Desktop boundary. */
5
5
  export default class ClientAppearance extends Events {
6
6
  constructor() {
7
7
  super((event, listener, impossible) => wire.on("host-appearance", event, value => {
8
- listener(createAppearanceSnapshot(value));
8
+ listener(parseAppearance(value));
9
9
  }, null, impossible), observer => wire.onAll("host-appearance", (event, value) => {
10
10
  if (typeof event === "string")
11
- observer(event, createAppearanceSnapshot(value));
11
+ observer(event, parseAppearance(value));
12
12
  }));
13
13
  }
14
14
  async snapshot() {
15
15
  const [appearance] = await wire.request(["appearance"]);
16
- return createAppearanceSnapshot(appearance);
16
+ return parseAppearance(appearance);
17
17
  }
18
18
  async update(appearance) {
19
19
  await wire.request(["updateAppearance", appearance]);
package/dist/context.js CHANGED
@@ -1,21 +1,27 @@
1
1
  import Deadline from "./deadline.js";
2
- import { ClientEndpoint, ServerEndpoint, ServerTrafficHandle, TrafficHandle, bindEvents, endpointLifecycle, endpointEvents, process, program, visibleEndpoint, localWindow, window as windowHandle, } from "./domain.js";
2
+ import { ClientEndpoint, ServerEndpoint, ServerTrafficHandle, TrafficHandle, endpointLifecycle, endpointEvents, process, program, visibleEndpoint, localWindow, 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";
6
- const ServerEndpointBase = ServerEndpoint;
7
- const ClientEndpointBase = ClientEndpoint;
8
- class ContextServerHandle extends ServerEndpointBase {
6
+ class ContextServerHandle extends ServerEndpoint {
9
7
  owner;
8
+ subscribe;
9
+ wait;
10
+ events;
10
11
  traffic = new ServerTrafficHandle(null, "server");
11
12
  lifecycle = endpointLifecycle(currentAddress, "server");
12
13
  constructor(owner) {
13
14
  super();
14
15
  this.owner = owner;
15
- bindEvents(this, endpointEvents(null, "server"));
16
+ const events = endpointEvents(null, "server");
17
+ this.subscribe = events.subscribe;
18
+ this.wait = events.wait;
19
+ this.events = events.events;
16
20
  }
17
21
  process() { return this.owner(); }
18
- publish(event, payload = undefined) { wire.send("end-end", event, payload); }
22
+ publish = (event, payload = undefined) => {
23
+ wire.send("end-end", event, payload);
24
+ };
19
25
  async exists() {
20
26
  const answer = await wire.request(["exists", "server"]);
21
27
  return answer[0];
@@ -54,22 +60,28 @@ function owner() {
54
60
  return ownerPromise;
55
61
  }
56
62
  contextServer = new ContextServerHandle(owner);
57
- class ContextClientHandle extends ClientEndpointBase {
63
+ class ContextClientHandle extends ClientEndpoint {
58
64
  owner;
65
+ subscribe;
66
+ wait;
67
+ events;
59
68
  traffic = new TrafficHandle(null, "client");
60
69
  lifecycle = endpointLifecycle(currentAddress, "client");
61
70
  window = windowHandle(currentAddress);
62
71
  constructor(owner) {
63
72
  super();
64
73
  this.owner = owner;
65
- bindEvents(this, endpointEvents(null, "client"));
74
+ const events = endpointEvents(null, "client");
75
+ this.subscribe = events.subscribe;
76
+ this.wait = events.wait;
77
+ this.events = events.events;
66
78
  }
67
79
  process() { return this.owner(); }
68
- publish(event, payload = undefined) {
80
+ publish = (event, payload = undefined) => {
69
81
  void wire.identity().then(identity => {
70
82
  wire.send("end-host", "send", { identity: identity.process, reference: identity.reference }, "client", event, payload);
71
83
  });
72
- }
84
+ };
73
85
  async exists() {
74
86
  const answer = await wire.request(["exists", "client"]);
75
87
  return answer[0];
@@ -101,9 +113,9 @@ class ClientContext extends Events {
101
113
  const answer = await wire.request(["current-program"]);
102
114
  return program(answer[0]);
103
115
  }
104
- async option(name) {
105
- const answer = await wire.request(["option", undefined, name]);
106
- return answer[0];
116
+ async options(name) {
117
+ const process = await owner();
118
+ return name === undefined ? process.options() : process.options(name);
107
119
  }
108
120
  async stop() { await wire.request(["stop-current"]); }
109
121
  async isService() { return contextClient.isService(); }
@@ -1,8 +1,8 @@
1
1
  import ClientPreferences from "./preferences.js";
2
- import ClientSurface from "./surface.js";
2
+ import ClientViewport from "./viewport.js";
3
3
  /** Desktop access bound to the current Client Endpoint's Process boundary. */
4
4
  class ClientDesktop {
5
- surface = new ClientSurface();
5
+ viewport = new ClientViewport();
6
6
  preferences = new ClientPreferences();
7
7
  }
8
8
  /** The Desktop environment containing this Client Endpoint. */
@@ -0,0 +1,9 @@
1
+ import type { DesktopViewportEvents, DesktopViewportSource } from "@phreshos/core";
2
+ import Events from "../events.js";
3
+ /** Read-only access to the Desktop viewport containing this Client Endpoint. */
4
+ export default class ClientViewport extends Events<DesktopViewportEvents, never> implements DesktopViewportSource {
5
+ constructor();
6
+ snapshot(): Promise<Readonly<{
7
+ size: import("@phreshos/core").DesktopSize;
8
+ }>>;
9
+ }
@@ -1,23 +1,23 @@
1
1
  import Events from "../events.js";
2
2
  import wire from "../wire.js";
3
- /** Read-only access to the Desktop surface containing this Client Endpoint. */
4
- export default class ClientSurface extends Events {
3
+ /** Read-only access to the Desktop viewport containing this Client Endpoint. */
4
+ export default class ClientViewport extends Events {
5
5
  constructor() {
6
- super((event, listener, impossible) => wire.on("host-desktop-surface", event, value => {
6
+ super((event, listener, impossible) => wire.on("host-desktop-viewport", event, value => {
7
7
  const snapshot = createSnapshot(value);
8
8
  if (snapshot)
9
9
  listener(snapshot);
10
- }, null, impossible), observer => wire.onAll("host-desktop-surface", (event, value) => {
10
+ }, null, impossible), observer => wire.onAll("host-desktop-viewport", (event, value) => {
11
11
  const snapshot = createSnapshot(value);
12
12
  if (typeof event === "string" && snapshot)
13
13
  observer(event, snapshot);
14
14
  }));
15
15
  }
16
16
  async snapshot() {
17
- const answer = await wire.request(["desktopSurface"]);
17
+ const answer = await wire.request(["desktopViewport"]);
18
18
  const snapshot = createSnapshot(answer[0]);
19
19
  if (!snapshot)
20
- throw new Error("The System returned an invalid Desktop surface");
20
+ throw new Error("The System returned an invalid Desktop viewport");
21
21
  return snapshot;
22
22
  }
23
23
  }
package/dist/domain.d.ts CHANGED
@@ -1,50 +1,12 @@
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 EndpointTraffic as CoreEndpointTraffic, type Exit, type EventOptions, type LocalWindow, type Position, type ProgramProcess as CoreProgramProcess, type ProgramProcessRunEvent as CoreProgramProcessRunEvent, type ProgramProcessRunOptions as CoreProgramProcessRunOptions, type ServerTraffic as CoreServerTraffic, type Size, 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 LocalWindow, type ProgramProcess as CoreProgramProcess, 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
- export interface HandleAddress {
4
- identity: string;
5
- reference: string;
6
- }
7
- export interface EndpointDeclarationRecord {
8
- start: boolean;
9
- service: boolean;
10
- }
11
- export interface ClientDeclarationRecord extends EndpointDeclarationRecord {
12
- title: string | null;
13
- size: Size | null;
14
- position: Position | null;
15
- layer: ClientDeclaration["layer"];
16
- minimize: boolean | null;
17
- permissions: ClientDeclaration["permissions"];
18
- }
19
- export interface ProgramRecord {
20
- reference: string;
21
- identity: string;
22
- assetId: string;
23
- installed?: boolean;
24
- name: string;
25
- version: string | null;
26
- description: string | null;
27
- hasAgent: boolean;
28
- server: EndpointDeclarationRecord | null;
29
- client: ClientDeclarationRecord | null;
30
- }
31
- export interface ProcessRecord {
32
- reference: string;
33
- identity: string;
34
- name: string | null;
35
- program: ProgramRecord;
36
- options: Record<string, string>;
37
- startedAt: string | Date;
38
- server: EndpointRecord | null;
39
- client: EndpointRecord | null;
40
- }
41
- export interface EndpointRecord {
42
- service: boolean;
43
- }
44
- export interface EndpointReference {
45
- kind: "server" | "client";
46
- process: ProcessRecord;
47
- }
3
+ export type { HandleAddress };
4
+ export type EndpointDeclarationRecord = EndpointDeclaration;
5
+ export type ClientDeclarationRecord = ClientDeclaration;
6
+ export type ProgramRecord = ProgramSnapshot;
7
+ export type ProcessRecord = ProcessSnapshot;
8
+ export type EndpointRecord = EndpointSnapshot;
9
+ export type EndpointReference = CoreEndpointReference;
48
10
  export type WindowRecord = WindowState;
49
11
  export type Program = CoreProgram;
50
12
  export type ProgramProcess = CoreProgramProcess;
@@ -79,48 +41,47 @@ export type Endpoint<Events extends object = {}, Fallback = unknown> = CoreEndpo
79
41
  export type ServerEndpoint<Events extends object = {}, Fallback = unknown> = CoreServerEndpoint<Events, Fallback>;
80
42
  export type ClientEndpoint<Events extends object = {}, Fallback = unknown> = CoreClientEndpoint<Events, Fallback>;
81
43
  export type Window = CoreWindow;
82
- export declare class TrafficHandle extends Events {
44
+ export declare class TrafficHandle<EventsMap extends object = {}, Fallback = unknown> extends Events<TrafficEvents<EventsMap>, TrafficMessage<Fallback>> {
83
45
  protected readonly target: HandleAddress | null;
84
46
  protected readonly kind: "server" | "client";
85
47
  constructor(target: HandleAddress | null, kind: "server" | "client");
86
- subscribeAsks(subscriber: (capture: AskCapture) => unknown): Cleanup;
87
- asks(options?: EventOptions): AsyncIterableIterator<Readonly<{
48
+ subscribeAsks<Payload = unknown>(subscriber: (capture: AskCapture<Payload>) => unknown): Cleanup;
49
+ asks<Payload = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
88
50
  event: string;
89
51
  questionId: string;
90
52
  message: Readonly<{
91
53
  to: ServerEndpoint<{}, unknown> | null;
92
- payload: unknown;
54
+ payload: Payload;
93
55
  }>;
94
56
  }>>;
95
57
  private followAsks;
96
58
  }
97
- export declare class ServerTrafficHandle extends TrafficHandle {
98
- subscribeAnswers(subscriber: (capture: AnswerCapture) => unknown): Cleanup;
99
- answers(options?: EventOptions): AsyncIterableIterator<Readonly<{
59
+ export declare class ServerTrafficHandle<EventsMap extends object = {}, Fallback = unknown> extends TrafficHandle<EventsMap, Fallback> {
60
+ subscribeAnswers<Result = unknown>(subscriber: (capture: AnswerCapture<Result>) => unknown): Cleanup;
61
+ answers<Result = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
100
62
  event: string;
101
63
  questionId: string;
102
64
  message: Readonly<{
103
65
  to: Endpoint<{}, unknown> | null;
104
- outcome: AnswerOutcome<unknown>;
66
+ outcome: AnswerOutcome<Result>;
105
67
  }>;
106
68
  }>>;
107
69
  private followAnswers;
108
70
  }
109
71
  type WindowTarget = () => Promise<HandleAddress>;
110
- export declare function scoped(route: string, subject: string | null, convert: (event: string, values: unknown[]) => unknown): Events<{}, unknown>;
72
+ export declare function scoped<Definitions extends object = {}, Fallback = unknown>(route: string, subject: string | null, convert: (event: string, values: unknown[]) => unknown): Events<Definitions, Fallback>;
111
73
  /** Destinationless events originating from one Endpoint handle. */
112
74
  export declare function endpointEvents(target: HandleAddress | null, half: "server" | "client"): Events<{}, unknown>;
113
75
  /** Start and stop transitions belonging directly to one permanent Endpoint. */
114
- export declare function endpointLifecycle(target: WindowTarget, half: "server" | "client"): Events<{}, unknown>;
76
+ export declare function endpointLifecycle(target: WindowTarget, half: "server" | "client"): Events<EndpointLifecycleEvents, never>;
115
77
  export declare function exit(code: unknown, signal: unknown): Exit;
116
78
  export declare function trafficMessage(value: unknown): TrafficMessage;
117
- export declare function bindEvents(target: object, events: Events): void;
118
- export declare function program(record: ProgramRecord): Program;
119
- export declare function process(record: ProcessRecord, endpoints?: {
79
+ export declare function program(value: unknown): Program;
80
+ export declare function process(value: unknown, endpoints?: {
120
81
  server?: ServerEndpoint;
121
82
  client?: ClientEndpoint;
122
83
  }): Process;
123
- export declare function endpoint(reference: EndpointReference | undefined): Endpoint;
84
+ export declare function endpoint(value: unknown): Endpoint;
124
85
  export declare function claimEndpoint(reference: string, kind: "server" | "client", endpoint: Endpoint): Endpoint<{}, unknown>;
125
86
  export declare function window(target: WindowTarget): Window;
126
87
  export declare function localWindow(target: WindowTarget): LocalWindow;
@@ -136,4 +97,3 @@ export declare const Endpoint: typeof CoreEndpoint;
136
97
  export declare const ServerEndpoint: typeof CoreServerEndpoint;
137
98
  /** Runtime constructor shared by all client-visible Client Endpoint handles. */
138
99
  export declare const ClientEndpoint: typeof CoreClientEndpoint;
139
- export {};
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, parseClientPermissions } 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";
@@ -7,11 +7,10 @@ import startup from "./startup.js";
7
7
  import { programPermissions } from "./permissions.js";
8
8
  import wire from "./wire.js";
9
9
  const handles = new HandleRegistry();
10
- const ProgramBase = CoreProgram;
11
- const ProcessBase = CoreProcess;
12
- const ServerEndpointBase = CoreServerEndpoint;
13
- const ClientEndpointBase = CoreClientEndpoint;
14
- class ProgramHandle extends ProgramBase {
10
+ class ProgramHandle extends CoreProgram {
11
+ subscribe;
12
+ wait;
13
+ events;
15
14
  identity;
16
15
  reference;
17
16
  data;
@@ -36,19 +35,18 @@ class ProgramHandle extends ProgramBase {
36
35
  this.startup = startup(this.address);
37
36
  this.permissions = programPermissions(this.address);
38
37
  this.process = new ProgramProcessHandle(this.address, record.reference);
39
- bindEvents(this, scoped("program-host", record.reference, programEvent));
38
+ const events = scoped("program-host", record.reference, programEvent);
39
+ this.subscribe = events.subscribe;
40
+ this.wait = events.wait;
41
+ this.events = events.events;
40
42
  }
41
43
  get name() { return this.record.name; }
42
44
  get assetId() { return this.record.assetId; }
43
45
  get version() { return this.record.version; }
44
46
  get description() { return this.record.description; }
45
47
  get hasAgent() { return this.record.hasAgent; }
46
- get server() {
47
- return this.record.server ? declaration(this.record.server) : null;
48
- }
49
- get client() {
50
- return this.record.client ? clientDeclaration(this.record.client) : null;
51
- }
48
+ get server() { return this.record.server; }
49
+ get client() { return this.record.client; }
52
50
  get address() { return { identity: this.identity, reference: this.reference }; }
53
51
  update(record) {
54
52
  if (record.reference !== this.reference)
@@ -92,28 +90,17 @@ function programCommandChunk(value) {
92
90
  }
93
91
  return Object.freeze({ stream: chunk.stream, text: chunk.text });
94
92
  }
95
- function declaration(record) {
96
- return Object.freeze({
97
- start: record.start,
98
- service: record.service
99
- });
100
- }
101
- function clientDeclaration(record) {
102
- return Object.freeze({
103
- ...declaration(record),
104
- title: record.title,
105
- size: record.size,
106
- position: record.position,
107
- layer: record.layer,
108
- minimize: record.minimize,
109
- permissions: parseClientPermissions(record.permissions)
110
- });
111
- }
112
93
  class ProgramProcessHandle {
113
94
  address;
95
+ subscribe;
96
+ wait;
97
+ events;
114
98
  constructor(address, reference) {
115
99
  this.address = address;
116
- bindEvents(this, scoped("program-process", reference, programProcessEvent));
100
+ const events = scoped("program-process", reference, programProcessEvent);
101
+ this.subscribe = events.subscribe;
102
+ this.wait = events.wait;
103
+ this.events = events.events;
117
104
  }
118
105
  async list() {
119
106
  const answer = await wire.request(["program-process-list", this.address]);
@@ -165,7 +152,10 @@ function processRunEvent(value) {
165
152
  function chronological(processes) {
166
153
  return processes.sort((left, right) => left.startedAt.getTime() - right.startedAt.getTime());
167
154
  }
168
- class ProcessHandle extends ProcessBase {
155
+ class ProcessHandle extends CoreProcess {
156
+ subscribe;
157
+ wait;
158
+ events;
169
159
  identity;
170
160
  reference;
171
161
  name;
@@ -173,7 +163,7 @@ class ProcessHandle extends ProcessBase {
173
163
  server;
174
164
  client;
175
165
  ownerProgram;
176
- options;
166
+ launchOptions;
177
167
  constructor(record, endpoints = {}) {
178
168
  super();
179
169
  this.identity = record.identity;
@@ -181,10 +171,13 @@ class ProcessHandle extends ProcessBase {
181
171
  this.name = record.name;
182
172
  this.startedAt = new Date(record.startedAt);
183
173
  this.ownerProgram = program(record.program);
184
- this.options = record.options;
174
+ this.launchOptions = Object.freeze({ ...record.options });
185
175
  this.server = endpointHandle(this, "server", endpoints.server);
186
176
  this.client = endpointHandle(this, "client", endpoints.client);
187
- bindEvents(this, scoped("process-host", record.reference, processEvent));
177
+ const events = scoped("process-host", record.reference, processEvent);
178
+ this.subscribe = events.subscribe;
179
+ this.wait = events.wait;
180
+ this.events = events.events;
188
181
  }
189
182
  program() { return this.ownerProgram; }
190
183
  get address() { return { identity: this.identity, reference: this.reference }; }
@@ -192,11 +185,8 @@ class ProcessHandle extends ProcessBase {
192
185
  const answer = await wire.request(["parent", this.address]);
193
186
  return answer[0] ? process(answer[0]) : null;
194
187
  }
195
- async option(name) {
196
- if (name in this.options)
197
- return this.options[name];
198
- const answer = await wire.request(["option", this.address, name]);
199
- return answer[0];
188
+ async options(name) {
189
+ return name === undefined ? this.launchOptions : this.launchOptions[name];
200
190
  }
201
191
  async exit() { await wire.request(["exit", this.address]); }
202
192
  async exited() {
@@ -245,8 +235,11 @@ export class ServerTrafficHandle extends TrafficHandle {
245
235
  }, impossible);
246
236
  }
247
237
  }
248
- class ServerEndpointHandle extends ServerEndpointBase {
238
+ class ServerEndpointHandle extends CoreServerEndpoint {
249
239
  owner;
240
+ subscribe;
241
+ wait;
242
+ events;
250
243
  traffic;
251
244
  lifecycle;
252
245
  constructor(owner) {
@@ -254,10 +247,15 @@ class ServerEndpointHandle extends ServerEndpointBase {
254
247
  this.owner = owner;
255
248
  this.traffic = new ServerTrafficHandle(owner.address, "server");
256
249
  this.lifecycle = endpointLifecycle(() => Promise.resolve(owner.address), "server");
257
- bindEvents(this, endpointEvents(owner.address, "server"));
250
+ const events = endpointEvents(owner.address, "server");
251
+ this.subscribe = events.subscribe;
252
+ this.wait = events.wait;
253
+ this.events = events.events;
258
254
  }
259
255
  async process() { return this.owner; }
260
- publish(event, payload = undefined) { wire.send("end-host", "send", this.owner.address, "server", event, payload); }
256
+ publish = (event, payload = undefined) => {
257
+ wire.send("end-host", "send", this.owner.address, "server", event, payload);
258
+ };
261
259
  async exists() {
262
260
  const answer = await wire.request(["exists", "server", this.owner.address]);
263
261
  return answer[0];
@@ -288,8 +286,11 @@ class ServerEndpointHandle extends ServerEndpointBase {
288
286
  }
289
287
  }
290
288
  }
291
- class ClientEndpointHandle extends ClientEndpointBase {
289
+ class ClientEndpointHandle extends CoreClientEndpoint {
292
290
  owner;
291
+ subscribe;
292
+ wait;
293
+ events;
293
294
  traffic;
294
295
  lifecycle;
295
296
  window;
@@ -299,10 +300,15 @@ class ClientEndpointHandle extends ClientEndpointBase {
299
300
  this.traffic = new TrafficHandle(owner.address, "client");
300
301
  this.lifecycle = endpointLifecycle(() => Promise.resolve(owner.address), "client");
301
302
  this.window = window(async () => owner.address);
302
- bindEvents(this, endpointEvents(owner.address, "client"));
303
+ const events = endpointEvents(owner.address, "client");
304
+ this.subscribe = events.subscribe;
305
+ this.wait = events.wait;
306
+ this.events = events.events;
303
307
  }
304
308
  async process() { return this.owner; }
305
- publish(event, payload = undefined) { wire.send("end-host", "send", this.owner.address, "client", event, payload); }
309
+ publish = (event, payload = undefined) => {
310
+ wire.send("end-host", "send", this.owner.address, "client", event, payload);
311
+ };
306
312
  async exists() {
307
313
  const answer = await wire.request(["exists", "client", this.owner.address]);
308
314
  return answer[0];
@@ -326,13 +332,14 @@ class WindowHandle extends Events {
326
332
  async position() { return (await this.state()).position; }
327
333
  async size() { return (await this.state()).size; }
328
334
  async minimized() { return (await this.state()).minimized; }
335
+ async maximized() { return (await this.state()).maximized; }
329
336
  async front() { return (await this.state()).front; }
330
337
  async layer() { return (await this.state()).layer; }
331
- async location() { return (await this.state()).location; }
332
338
  async move(position) { await wire.request(["move", await this.target(), position]); }
333
339
  async resize(size) { await wire.request(["resize", await this.target(), size]); }
334
340
  async setGeometry(geometry) { await wire.request(["setGeometry", await this.target(), geometry]); }
335
341
  async minimize(minimized = true) { await wire.request(["minimize", await this.target(), minimized]); }
342
+ async maximize(maximized = true) { await wire.request(["maximize", await this.target(), maximized]); }
336
343
  async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
337
344
  async raise() { await wire.request(["raise", await this.target()]); }
338
345
  }
@@ -353,6 +360,8 @@ class LocalWindowHandle {
353
360
  async resize(size) { await this.change("windowLocalResize", size); }
354
361
  async setGeometry(geometry) { await this.change("windowLocalGeometry", geometry); }
355
362
  async minimize(minimized = true) { await this.change("windowLocalMinimize", minimized); }
363
+ async maximize(maximized = true) { await this.change("windowLocalMaximize", maximized); }
364
+ async changeTitle(title) { await wire.request(["windowLocalTitle", await this.target(), title]); }
356
365
  async follow(window) {
357
366
  const target = windowTargets.get(window);
358
367
  if (!target)
@@ -388,7 +397,7 @@ function deferredScoped(route, target, convert) {
388
397
  ];
389
398
  }
390
399
  function windowEvent(event) {
391
- return event === "move" || event === "resize" || event === "geometry" || event === "minimize" || event === "changeTitle" || event === "front";
400
+ return event === "move" || event === "resize" || event === "geometry" || event === "minimize" || event === "maximize" || event === "changeTitle" || event === "front";
392
401
  }
393
402
  function deferred(target, register, impossible) {
394
403
  let active = true;
@@ -481,24 +490,18 @@ export function trafficMessage(value) {
481
490
  const raw = value;
482
491
  return { to: visibleEndpoint(raw.to), payload: raw.payload };
483
492
  }
484
- export function bindEvents(target, events) {
485
- Object.assign(target, {
486
- subscribe: events.subscribe.bind(events),
487
- waitFor: events.waitFor.bind(events),
488
- events: events.events.bind(events)
489
- });
490
- }
491
- export function program(record) {
493
+ export function program(value) {
494
+ const record = parseProgramSnapshot(value);
492
495
  const handle = handles.obtain(`program:${record.reference}`, () => new ProgramHandle(record));
493
496
  handle.update(record);
494
497
  return handle;
495
498
  }
496
- export function process(record, endpoints = {}) {
499
+ export function process(value, endpoints = {}) {
500
+ const record = parseProcessSnapshot(value);
497
501
  return handles.obtain(`process:${record.reference}`, () => new ProcessHandle(record, endpoints));
498
502
  }
499
- export function endpoint(reference) {
500
- if (!reference)
501
- throw new Error("The boundary returned an invalid Endpoint reference");
503
+ export function endpoint(value) {
504
+ const reference = parseEndpointReference(value);
502
505
  const owner = process(reference.process);
503
506
  return reference.kind === "server" ? owner.server : owner.client;
504
507
  }
package/dist/events.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Cleanup, EventOptions, Subscribable } from "@phreshos/core";
1
+ import { subscribableDefinition, type Cleanup, type EventOptions, type Subscribable, type SubscribableDefinition } from "@phreshos/core";
2
2
  export declare const defaultTimeout = 10000;
3
3
  type Failure = (error: Error) => void;
4
4
  type Listener = (message: unknown) => unknown;
@@ -8,9 +8,10 @@ type Register<Message> = (listener: (message: Message) => unknown, impossible?:
8
8
  export default class Events<Vocabulary extends object = {}, Fallback = unknown> implements Subscribable<Vocabulary, Fallback> {
9
9
  private readonly listen;
10
10
  private readonly listenAll;
11
+ readonly [subscribableDefinition]?: SubscribableDefinition<Vocabulary, Fallback>;
11
12
  constructor(listen: (event: string, listener: Listener, impossible?: Failure) => Cleanup, listenAll: (listener: EveryListener, impossible?: Failure) => Cleanup);
12
13
  readonly subscribe: Subscribable<Vocabulary, Fallback>["subscribe"];
13
- readonly waitFor: Subscribable<Vocabulary, Fallback>["waitFor"];
14
+ readonly wait: Subscribable<Vocabulary, Fallback>["wait"];
14
15
  readonly events: Subscribable<Vocabulary, Fallback>["events"];
15
16
  }
16
17
  /** Converts one persistent registration into a bounded asynchronous iterator. */
package/dist/events.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { subscribableDefinition } from "@phreshos/core";
1
2
  export const defaultTimeout = 10_000;
2
3
  /** SDK-owned waits, queues and callbacks over boundary forwarding state. */
3
4
  export default class Events {
@@ -12,7 +13,7 @@ export default class Events {
12
13
  return this.listen(eventOrSubscriber, subscriber);
13
14
  return this.listenAll((event, message) => eventOrSubscriber({ event, message }));
14
15
  });
15
- waitFor = ((event, timeout = defaultTimeout) => {
16
+ wait = ((event, timeout = defaultTimeout) => {
16
17
  return new Promise((resolve, reject) => {
17
18
  let settled = false;
18
19
  let stop = () => undefined;
@@ -0,0 +1,7 @@
1
+ import type { Network } from "@phreshos/core";
2
+ declare class ClientNetwork implements Network {
3
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
4
+ websocket(url: string | URL, protocols?: string | string[]): Promise<WebSocket>;
5
+ }
6
+ declare const _default: ClientNetwork;
7
+ export default _default;
@@ -0,0 +1,79 @@
1
+ import wire from "./wire.js";
2
+ import controlledStream from "./controlled-stream.js";
3
+ import websocket from "./websocket.js";
4
+ class ClientNetwork {
5
+ async fetch(input, init) {
6
+ const request = new Request(input, {
7
+ ...init,
8
+ ...init?.body instanceof ReadableStream ? { duplex: "half" } : {}
9
+ });
10
+ const body = request.body;
11
+ const control = new MessageChannel();
12
+ const abort = () => control.port1.postMessage("abort");
13
+ if (request.signal.aborted) {
14
+ control.port1.close();
15
+ control.port2.close();
16
+ throw request.signal.reason;
17
+ }
18
+ request.signal.addEventListener("abort", abort, { once: true });
19
+ const description = {
20
+ body: body !== null,
21
+ cache: request.cache,
22
+ credentials: request.credentials,
23
+ headers: requestHeaders(request.headers, init?.headers),
24
+ integrity: request.integrity,
25
+ keepalive: request.keepalive,
26
+ method: request.method,
27
+ mode: request.mode,
28
+ redirect: request.redirect,
29
+ referrer: request.referrer,
30
+ referrerPolicy: request.referrerPolicy,
31
+ url: request.url
32
+ };
33
+ let result;
34
+ try {
35
+ const answer = await wire.request(["fetch", description, body, control.port2], undefined, body ? [body, control.port2] : [control.port2]);
36
+ result = answer[0];
37
+ }
38
+ catch (error) {
39
+ abort();
40
+ closeControl(request.signal, control.port1, abort);
41
+ throw request.signal.aborted ? request.signal.reason : error;
42
+ }
43
+ const responseBody = result.body
44
+ ? controlledStream(result.body, abort, () => closeControl(request.signal, control.port1, abort))
45
+ : null;
46
+ if (!responseBody)
47
+ closeControl(request.signal, control.port1, abort);
48
+ const response = new Response(responseBody, {
49
+ headers: result.headers,
50
+ status: result.status,
51
+ statusText: result.statusText
52
+ });
53
+ Object.defineProperties(response, {
54
+ redirected: { configurable: true, enumerable: true, value: result.redirected },
55
+ type: { configurable: true, enumerable: true, value: result.type },
56
+ url: { configurable: true, enumerable: true, value: result.url }
57
+ });
58
+ return response;
59
+ }
60
+ websocket(url, protocols) {
61
+ return websocket(url, protocols);
62
+ }
63
+ }
64
+ function requestHeaders(normalized, supplied) {
65
+ if (!supplied)
66
+ return [...normalized.entries()];
67
+ const explicit = supplied instanceof Headers
68
+ ? [...supplied.entries()]
69
+ : Array.isArray(supplied)
70
+ ? supplied.map(([name, value]) => [name, value])
71
+ : Object.entries(supplied);
72
+ const names = new Set(explicit.map(([name]) => name.toLowerCase()));
73
+ return [...normalized.entries()].filter(([name]) => !names.has(name.toLowerCase())).concat(explicit);
74
+ }
75
+ function closeControl(signal, port, abort) {
76
+ signal.removeEventListener("abort", abort);
77
+ port.close();
78
+ }
79
+ export default new ClientNetwork();
@@ -1,7 +1,7 @@
1
1
  import type { ContextPermissions, ProgramPermissions } from "@phreshos/core";
2
2
  import type { HandleAddress } from "./domain.js";
3
3
  export declare const defaultPermissionTimeout = 120000;
4
- /** Bind authoritative stored grants to one exact Program handle. */
4
+ /** Bind authoritative permission state to one exact Program handle. */
5
5
  export declare function programPermissions(program: HandleAddress): ProgramPermissions;
6
- /** Stored grants and owner requests belonging to the current Client Endpoint. */
6
+ /** Permission state and owner requests belonging to the current Client Endpoint. */
7
7
  export declare function contextPermissions(): ContextPermissions;
@@ -1,7 +1,7 @@
1
1
  import { parsePermission, parsePermissions } from "@phreshos/core";
2
2
  import wire from "./wire.js";
3
3
  export const defaultPermissionTimeout = 120_000;
4
- /** Bind authoritative stored grants to one exact Program handle. */
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
7
  return {
@@ -12,7 +12,7 @@ export function programPermissions(program) {
12
12
  async delete(name) { await operate("delete", name); }
13
13
  };
14
14
  }
15
- /** Stored grants and owner requests belonging to the current Client Endpoint. */
15
+ /** Permission state and owner requests belonging to the current Client Endpoint. */
16
16
  export function contextPermissions() {
17
17
  const timed = (timeout) => ({
18
18
  async request(name, permission = true) {
package/dist/service.js CHANGED
@@ -4,8 +4,6 @@ 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
- const ServerServiceBase = CoreServerService;
8
- const ClientServiceBase = CoreClientService;
9
7
  class ServiceHandle {
10
8
  key;
11
9
  lifecycle;
@@ -24,8 +22,11 @@ class ServiceHandle {
24
22
  await wire.request(["service-wait-ready", this.key, timeout], timeout);
25
23
  }
26
24
  }
27
- class ServerHandler extends ServerServiceBase {
25
+ class ServerHandler extends CoreServerService {
28
26
  key;
27
+ subscribe;
28
+ wait;
29
+ events;
29
30
  lifecycle;
30
31
  service;
31
32
  constructor(key) {
@@ -33,9 +34,12 @@ class ServerHandler extends ServerServiceBase {
33
34
  this.key = key;
34
35
  this.service = new ServiceHandle(key);
35
36
  this.lifecycle = this.service.lifecycle;
36
- bindEvents(this, new Events(...serviceEvents(key, "events")));
37
+ const events = new Events(...serviceEvents(key, "events"));
38
+ this.subscribe = events.subscribe;
39
+ this.wait = events.wait;
40
+ this.events = events.events;
37
41
  }
38
- publish(event, payload = undefined) { this.service.publish(event, payload); }
42
+ publish = (event, payload = undefined) => { this.service.publish(event, payload); };
39
43
  exists() { return this.service.exists(); }
40
44
  waitReady(timeout) { return this.service.waitReady(timeout); }
41
45
  async ask(event, payload = undefined) {
@@ -58,31 +62,39 @@ class ServerHandler extends ServerServiceBase {
58
62
  }
59
63
  }
60
64
  }
61
- class ClientHandler extends ClientServiceBase {
65
+ class ClientHandler extends CoreClientService {
66
+ subscribe;
67
+ wait;
68
+ events;
62
69
  lifecycle;
63
70
  service;
64
71
  constructor(key) {
65
72
  super();
66
73
  this.service = new ServiceHandle(key);
67
74
  this.lifecycle = this.service.lifecycle;
68
- bindEvents(this, new Events(...serviceEvents(key, "events")));
75
+ const events = new Events(...serviceEvents(key, "events"));
76
+ this.subscribe = events.subscribe;
77
+ this.wait = events.wait;
78
+ this.events = events.events;
69
79
  }
70
- publish(event, payload = undefined) { this.service.publish(event, payload); }
80
+ publish = (event, payload = undefined) => { this.service.publish(event, payload); };
71
81
  exists() { return this.service.exists(); }
72
82
  waitReady(timeout) { return this.service.waitReady(timeout); }
73
83
  }
74
84
  export function prepareService(key) {
75
85
  if (!isServiceKey(key))
76
86
  throw new Error("A complete service key is required");
77
- const normalized = Object.freeze({
87
+ const address = Object.freeze({
78
88
  ...(key.program === undefined ? {} : { program: key.program }),
79
- process: key.process,
80
- endpoint: key.endpoint
89
+ process: key.process
81
90
  });
82
91
  const identity = JSON.stringify([key.program ?? null, key.process, key.endpoint]);
83
- return handles.obtain(`service:${identity}`, () => normalized.endpoint === "server"
84
- ? new ServerHandler(normalized)
85
- : new ClientHandler(normalized));
92
+ if (key.endpoint === "server") {
93
+ const normalized = Object.freeze({ ...address, endpoint: "server" });
94
+ return handles.obtain(`service:${identity}`, () => new ServerHandler(normalized));
95
+ }
96
+ const normalized = Object.freeze({ ...address, endpoint: "client" });
97
+ return handles.obtain(`service:${identity}`, () => new ClientHandler(normalized));
86
98
  }
87
99
  function serviceEvents(key, scope) {
88
100
  return [
@@ -93,10 +105,3 @@ function serviceEvents(key, scope) {
93
105
  }, impossible)
94
106
  ];
95
107
  }
96
- function bindEvents(target, events) {
97
- Object.assign(target, {
98
- subscribe: events.subscribe.bind(events),
99
- waitFor: events.waitFor.bind(events),
100
- events: events.events.bind(events)
101
- });
102
- }
package/dist/system.js CHANGED
@@ -3,76 +3,18 @@ import ClientAppearance from "./appearance.js";
3
3
  import wire from "./wire.js";
4
4
  import { prepareService } from "./service.js";
5
5
  import { uploads } from "./uploads.js";
6
- import controlledStream from "./controlled-stream.js";
7
6
  import Events from "./events.js";
8
7
  import { exit, process, program } from "./domain.js";
9
8
  import { systemStorage } from "./storage.js";
10
- import websocket from "./websocket.js";
9
+ import network from "./network.js";
11
10
  class ClientSystem {
12
11
  storage = systemStorage();
13
12
  appearance = new ClientAppearance();
14
13
  program = new SystemProgramHandle();
15
14
  process = new SystemProcessHandle();
16
15
  uploads = uploads;
16
+ network = network;
17
17
  service(key) { return prepareService(key); }
18
- async fetch(input, init) {
19
- const request = new Request(input, {
20
- ...init,
21
- ...init?.body instanceof ReadableStream ? { duplex: "half" } : {}
22
- });
23
- const body = request.body;
24
- const control = new MessageChannel();
25
- const abort = () => control.port1.postMessage("abort");
26
- if (request.signal.aborted) {
27
- control.port1.close();
28
- control.port2.close();
29
- throw request.signal.reason;
30
- }
31
- request.signal.addEventListener("abort", abort, { once: true });
32
- const description = {
33
- body: body !== null,
34
- cache: request.cache,
35
- credentials: request.credentials,
36
- headers: requestHeaders(request.headers, init?.headers),
37
- integrity: request.integrity,
38
- keepalive: request.keepalive,
39
- method: request.method,
40
- mode: request.mode,
41
- redirect: request.redirect,
42
- referrer: request.referrer,
43
- referrerPolicy: request.referrerPolicy,
44
- url: request.url
45
- };
46
- let result;
47
- try {
48
- const answer = await wire.request(["fetch", description, body, control.port2], undefined, body ? [body, control.port2] : [control.port2]);
49
- result = answer[0];
50
- }
51
- catch (error) {
52
- abort();
53
- closeControl(request.signal, control.port1, abort);
54
- throw request.signal.aborted ? request.signal.reason : error;
55
- }
56
- const responseBody = result.body
57
- ? controlledStream(result.body, abort, () => closeControl(request.signal, control.port1, abort))
58
- : null;
59
- if (!responseBody)
60
- closeControl(request.signal, control.port1, abort);
61
- const response = new Response(responseBody, {
62
- headers: result.headers,
63
- status: result.status,
64
- statusText: result.statusText
65
- });
66
- Object.defineProperties(response, {
67
- redirected: { configurable: true, enumerable: true, value: result.redirected },
68
- type: { configurable: true, enumerable: true, value: result.type },
69
- url: { configurable: true, enumerable: true, value: result.url }
70
- });
71
- return response;
72
- }
73
- websocket(url, protocols) {
74
- return websocket(url, protocols);
75
- }
76
18
  async *shell(command, options = {}) {
77
19
  const { signal, ...settings } = options;
78
20
  for await (const event of wire.stream(["shell", command, settings], undefined, signal))
@@ -133,20 +75,5 @@ function systemProgramEvent(event, values) {
133
75
  return { program: program(values[1]), everything: values[2] === true };
134
76
  return values[0];
135
77
  }
136
- function requestHeaders(normalized, supplied) {
137
- if (!supplied)
138
- return [...normalized.entries()];
139
- const explicit = supplied instanceof Headers
140
- ? [...supplied.entries()]
141
- : Array.isArray(supplied)
142
- ? supplied.map(([name, value]) => [name, value])
143
- : Object.entries(supplied);
144
- const names = new Set(explicit.map(([name]) => name.toLowerCase()));
145
- return [...normalized.entries()].filter(([name]) => !names.has(name.toLowerCase())).concat(explicit);
146
- }
147
- function closeControl(signal, port, abort) {
148
- signal.removeEventListener("abort", abort);
149
- port.close();
150
- }
151
78
  /** The global System represented through this Client runtime. */
152
79
  export const system = new ClientSystem();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -41,19 +41,19 @@
41
41
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
42
42
  "check": "tsc --noEmit && tsc -p tsconfig.test.json",
43
43
  "build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
44
- "verify:completions": "node scripts/verify-completions.mjs",
45
- "verify:package": "node scripts/verify-package.mjs",
46
- "verify": "node --run check && node --run verify:completions && node --run build && node --run verify:package",
47
- "prepack": "node --run build"
44
+ "verify": "node --run check && node --run build && node --run test",
45
+ "prepack": "node --run build",
46
+ "test": "vitest run --project default"
48
47
  },
49
48
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.42"
49
+ "@phreshos/core": "^0.1.43"
51
50
  },
52
51
  "dependencies": {
53
52
  "@the-link/messagepack": "^0.1.0"
54
53
  },
55
54
  "devDependencies": {
56
- "@phreshos/core": "^0.1.42",
57
- "typescript": "^6.0.3"
55
+ "@phreshos/core": "^0.1.43",
56
+ "typescript": "^6.0.3",
57
+ "vitest": "^4.1.10"
58
58
  }
59
59
  }
@@ -1,9 +0,0 @@
1
- import type { DesktopSurfaceEvents, DesktopSurfaceSource } from "@phreshos/core";
2
- import Events from "../events.js";
3
- /** Read-only access to the Desktop surface containing this Client Endpoint. */
4
- export default class ClientSurface extends Events<DesktopSurfaceEvents, never> implements DesktopSurfaceSource {
5
- constructor();
6
- snapshot(): Promise<Readonly<{
7
- size: import("@phreshos/core").DesktopSize;
8
- }>>;
9
- }