@phreshos/client 0.1.28 → 0.1.30

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
@@ -1,224 +1,84 @@
1
1
  # `@phreshos/client`
2
2
 
3
- The Client SDK defines the contextual capabilities available inside a
4
- Program's client endpoint.
3
+ The SDK for a PhreshOS Program's Client Endpoint.
5
4
 
6
- ## Package status
5
+ The Client SDK adapts the sandboxed desktop boundary to the shared Core domain
6
+ model. It exposes the restricted `system` and `context` available inside a
7
+ Client without redefining Program, Process, Endpoint, Server, or Client.
7
8
 
8
- This package is one component of a larger architecture that is still under
9
- active testing. The architecture's components will be released in stages as
10
- their contracts and integrations are verified.
9
+ ## Installation
11
10
 
12
- `@phreshos/client` is not intended to be used on its own. It requires the
13
- shared contracts from `@phreshos/core` and a compatible desktop boundary to
14
- provide its runtime environment.
11
+ | Package manager | Command |
12
+ | --- | --- |
13
+ | npm | `npm install @phreshos/client` |
14
+ | pnpm | `pnpm add @phreshos/client` |
15
+ | Bun | `bun add @phreshos/client` |
16
+ | Yarn | `yarn add @phreshos/client` |
15
17
 
16
- It uses the domain objects and shared contracts from `@phreshos/core` through
17
- a peer dependency. It does not redefine those objects, own server-side
18
- capabilities, or contain System and transport implementations.
18
+ `@phreshos/core` is a peer dependency.
19
19
 
20
- Its `System` contract exposes only desktop-session capabilities: read-only
21
- System Appearance, mutable preferences local to this Desktop, desktop and
22
- pointer state, flat public uploads, and unrestricted server-side Fetch.
23
- `system.appearance.snapshot()` reads complete unresolved authoritative state.
24
- `system.desktopPreferences.snapshot()` reads the complete effective
25
- `{ theme, animations }` state. Its updates may set either preference explicitly
26
- or use `"default"` to resume following the native environment. Both capabilities
27
- expose ordinary live-only `change` subscriptions with no initial replay. A
28
- Client cannot write authoritative Appearance or discover system-wide Programs
29
- and Processes.
30
-
31
- `system.uploads` has the same flat contract as the Server SDK. It writes a
32
- value, then reads or describes it using exactly one opaque generated file key;
33
- it exposes no filesystem path, path segments, listing, deletion, or clearing.
34
-
35
- Desktop and pointer access are independent objects rather than events merged
36
- into System:
20
+ ## Context
37
21
 
38
22
  ```ts
39
- const desktop = await system.desktop.size()
40
- const stopDesktop = system.desktop.subscribe("resize", next => undefined)
23
+ import { context } from "@phreshos/client"
41
24
 
42
- const position = await system.pointer.position()
43
- const stopPointer = system.pointer.subscribe("move", next => undefined)
44
- ```
45
-
46
- The desktop size is the complete desktop area containing this Client,
47
- independent of the Client Window's layer. Client code cannot select a layer,
48
- and the desktop's gutter never enters an endpoint. Pointer position and
49
- movement both require the `pointer` permission. Reads are asynchronous
50
- requests; subscriptions receive only future publications and never replay a
51
- retained value.
25
+ context.subscribe("changed", message => {
26
+ console.log(message)
27
+ })
52
28
 
53
- Permission decisions belong to the current Program, not to the desktop System.
54
- They are available from a Program handle and flattened through `context` as the
55
- same canonical capability:
29
+ context.publish("changed", { value: 1 })
56
30
 
57
- ```ts
58
31
  const program = await context.program()
59
-
60
- await program.permission.granted("pointer")
61
- await context.permission.request("pointer")
62
- await context.permission.timeout(5_000).request("pointer")
63
-
64
- context.permission === program.permission
32
+ const process = await context.process()
33
+ const server = context.server
34
+ const window = context.window
65
35
  ```
66
36
 
67
- `granted()` reads the effective decision without prompting. `request()` asks
68
- only when no known decision can answer immediately and returns `null` if its
69
- deadline expires. The desktop remains the internal enforcement boundary, but
70
- permission ownership does not alter the public shape of `system`.
37
+ `context` belongs to the executing Client. It provides communication,
38
+ navigation to its Program and Process, its paired Server, its Window, and
39
+ Client-owned capabilities.
71
40
 
72
- The Client System exposes only direct, exact Service handles. Creating a handle
73
- does not read or start the Service and exposes no Service registry:
41
+ ## System
74
42
 
75
43
  ```ts
76
- const service = system.service({
77
- program: "counter",
78
- process: "main",
79
- endpoint: "server"
80
- })
81
-
82
- await service.waitReady()
83
- if (await service.exists()) await service.ask("value")
44
+ import { system } from "@phreshos/client"
84
45
 
85
- service.subscribe("changed", message => console.log(message))
86
- service.lifecycle.subscribe("stop", () => console.log("unavailable"))
46
+ const appearance = await system.appearance.snapshot()
47
+ const surface = await system.desktop.surface.snapshot()
48
+ const pointer = await system.desktop.pointer.snapshot()
49
+ const preferences = await system.desktop.preferences.snapshot()
87
50
  ```
88
51
 
89
- The `program` coordinate is required for a Program-local Process name. When
90
- `process` is an exact globally unique identity, use
91
- `system.service({ process, endpoint })`; that handle never retargets a
92
- replacement Process.
93
-
94
- A Client may traverse `Process.parent()` through any number of ancestors in
95
- its own Program. The first parent outside that Program is structurally hidden
96
- and returned as `null`; no cross-Program Process handle enters the client. If
97
- client code fabricates or otherwise obtains an unauthorized handle, the system
98
- responds exactly as it does for a nonexistent Process.
99
-
100
- Its JavaScript entry point adapts the iframe boundary to these contracts. The
101
- SDK owns callbacks, waits, queues, and their cleanup; the boundary owns only
102
- the forwarding registrations requested by the SDK.
103
-
104
- Importing the SDK and establishing the iframe's host lease inject no message
105
- into the endpoint. Identity, Theme, Process, Window, readiness, lifecycle, and
106
- application values enter only in response to an explicit request or a live
107
- registration made by Program code.
108
-
109
- `Context` combines communication with navigation into the executing Client's
110
- Process. The paired Server is explicitly named as
111
- `context.server`; its publishing, asking, existence, readiness, start, and stop
112
- operations never masquerade as properties of `context`. `context.stop()` stops
113
- the executing Client, while complete Process exit remains available only
114
- through `context.process()`. It is the canonical Process-owned handle, so
115
- `context.server === (await context.process()).server`.
116
- Endpoint `process()` navigation is asynchronous; contextual ownership is
117
- requested only when navigation needs it and then retained by the SDK.
118
- `context.name()` returns that retained Process's Program-local name, or `null`
119
- when its launch was unnamed.
120
-
121
- All domain handles are canonical within this iframe's JavaScript realm. Lookup,
122
- navigation, event payloads, and message metadata reuse the same weakly retained
123
- handle. A Client and its synchronous `window` capability remain stable for the
124
- Process lifetime; Window operations always address that Client's current live
125
- presentation state.
126
-
127
- `server.ask()` does not route a question before the current Server incarnation
128
- is ready. The Client SDK owns one deadline across readiness and the answer;
129
- absence or incarnation loss rejects without turning the boundary into a waiter.
130
-
131
- The package provides two contextual runtime entry points:
52
+ The Client System exposes only capabilities allowed by the desktop boundary:
53
+ read-only Appearance, desktop surface and pointer state, writable desktop
54
+ preferences, uploads, Fetch, and exact Service handles. It does not expose
55
+ system-wide Program or Process registries.
132
56
 
133
- ```ts
134
- import { system, context } from "@phreshos/client"
135
- ```
57
+ Requests read current state. Subscriptions observe future publications and do
58
+ not replay an initial value. Importing the SDK performs neither operation.
136
59
 
137
- It also re-exports the shared Core runtime classes—`Program`, `Process`,
138
- `Endpoint`, `Server`, and `Client`—and refines the handles returned through
139
- them. These are the same domain classes used by the Server SDK, so
140
- `instanceof Server` and `instanceof Client` retain one meaning. `Window`, like
141
- `ClientTraffic`, is a type-only capability owned by Client and has no
142
- independent `instanceof` identity.
143
-
144
- The executing Client's communication belongs directly to `context`. Its
145
- subscription tools receive events addressed to this Client, while `publish()`
146
- emits outward from this Client without choosing a destination. Existence and
147
- readiness belong to `context.server`; the current Client stops through
148
- `context.stop()`, and explicit Endpoint handles expose their own
149
- `endpoint.lifecycle` subscriptions.
150
-
151
- An Endpoint handle is also a selective source: `endpoint.subscribe()` follows
152
- destinationless events emitted by that Endpoint. Its `traffic` property remains
153
- reserved for directed publications, questions, and answers.
154
-
155
- Messages sent by an Endpoint in the same Program contain that real Endpoint in
156
- `from`. If the sender belongs to another Program, `from` is `null`; the foreign
157
- identity is removed by the authoritative System before the message reaches the desktop.
158
- The same rule applies to Client-visible traffic destinations.
159
-
160
- Client-visible Program handles can operate only within their own Program.
161
- They deliberately omit `install()` and `fork()`, and their `Storage` values never
162
- expose native filesystem paths. Every Client-side Window handle exposes the same
163
- authoritative, subscribable Window capability and its `window.local` physical
164
- representation on this desktop. Local reads and updates have no events and do
165
- not change Server state. `context.window` is only convenient access to the
166
- executing Client's canonical Window; it has no additional authority.
167
-
168
- A Client may uninstall only its own Program. The operation is an async
169
- generator so a declared Server cleanup command remains observable without a
170
- fixed answer timeout:
60
+ ## Development
171
61
 
172
- ```ts
173
- for await (const chunk of program.uninstall()) {
174
- console.log(chunk.stream, chunk.text)
175
- }
62
+ ```sh
63
+ bun install --frozen-lockfile
64
+ bun run verify
176
65
  ```
177
66
 
178
- A Program may converge its Clients on one named Process without a manual
179
- `find()`/`create()` race:
67
+ `verify` checks the source, completion surface, build, and published package
68
+ shape.
180
69
 
181
- ```ts
182
- const shared = await program.process.findOrCreate({
183
- name: "shared-server",
184
- server: true,
185
- client: false
186
- })
187
- ```
188
-
189
- The authoritative Core returns the existing Process only when its normalized
190
- launch is equivalent; a conflicting launch rejects without changing it.
70
+ See the [Client and Server documentation](https://github.com/PhreshOS/docs/blob/main/content/docs/sdks/client-and-server.mdx)
71
+ for the shared model and authority boundary.
191
72
 
192
- When both dimensions must change, `setGeometry({ position, size })` commits
193
- them through one authoritative request and produces one `geometry` event.
194
- Calling `move()` and `resize()` sequentially or through `Promise.all()` remains
195
- two independent operations and can expose an intermediate state remotely.
73
+ ## Repository boundary
196
74
 
197
- An `under` or `over` representation may request one local system-rendered Surface:
75
+ This repository owns the Client runtime adapter. Core owns the domain model, the
76
+ System owns enforcement and forwarding, React owns framework adaptation, and
77
+ React UI owns visual interpretation.
198
78
 
199
- ```ts
200
- await context.window.local.surface.set({ duration: 240, easing: "ease-out", wait: true })
79
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository workflow and
80
+ [SECURITY.md](SECURITY.md) for private vulnerability reporting.
201
81
 
202
- await context.window.local.surface.remove({ duration: 180, easing: "ease-in" })
203
- ```
82
+ ## License
204
83
 
205
- The desktop holds local state only for the lifetime of that iframe
206
- representation. Reloading or destroying it resets the representation from
207
- authoritative truth, while other desktops remain unaffected. Program code may
208
- synchronize desired presence through its Server and explicitly apply it
209
- again. `set()` and `remove()` accept only a required `VisibilityTransition`;
210
- Programs cannot configure the System Surface's material, opacity, or radius.
211
- The transition uses milliseconds and a stable named or cubic Bézier easing;
212
- the desktop performs the motion, honors reduced motion, and removes the Surface
213
- only after its exit transition completes. `wait: true` makes the request settle
214
- with that transition. A new iframe representation restores nothing. The
215
- container follows the iframe geometry while the independently rounded Surface
216
- neither clips nor masks Client content. The ordinary `window` layer rejects
217
- the capability.
218
-
219
- `program.icon()` requests the current Program's guaranteed PNG `Blob` on
220
- demand. The desktop derives the Program from the calling frame; no identity,
221
- private asset address, or filesystem path crosses from Client code.
222
-
223
- Persistent startup is deliberately absent. Only a Server endpoint may change
224
- whether an installed Program creates a Process when the system starts.
84
+ Licensed under the [MIT License](LICENSE). Copyright © 2026 Zohayr SLILEH.
@@ -0,0 +1,13 @@
1
+ import type { DesktopPointerSource, DesktopSurfaceSource, WritableDesktopPreferencesSource } from "@phreshos/core";
2
+ /** Every capability owned by the Desktop containing this Client. */
3
+ export interface SystemDesktop {
4
+ readonly surface: DesktopSurfaceSource;
5
+ readonly pointer: DesktopPointerSource;
6
+ readonly preferences: WritableDesktopPreferencesSource;
7
+ }
8
+ /** Desktop access bound to the current Client Process boundary. */
9
+ export default class ClientDesktop implements SystemDesktop {
10
+ readonly surface: DesktopSurfaceSource;
11
+ readonly pointer: DesktopPointerSource;
12
+ readonly preferences: WritableDesktopPreferencesSource;
13
+ }
@@ -0,0 +1,9 @@
1
+ import ClientPointer from "./pointer.js";
2
+ import ClientPreferences from "./preferences.js";
3
+ import ClientSurface from "./surface.js";
4
+ /** Desktop access bound to the current Client Process boundary. */
5
+ export default class ClientDesktop {
6
+ surface = new ClientSurface();
7
+ pointer = new ClientPointer();
8
+ preferences = new ClientPreferences();
9
+ }
@@ -0,0 +1,11 @@
1
+ import Events from "../events.js";
2
+ /** Permission-guarded access to the Desktop pointer visible to this Client. */
3
+ export default class ClientPointer extends Events {
4
+ private listeners;
5
+ private readonly sample;
6
+ constructor();
7
+ snapshot(): Promise<Readonly<{
8
+ position: import("@phreshos/core").DesktopPointerPosition | null;
9
+ }>>;
10
+ private withSampling;
11
+ }
@@ -0,0 +1,59 @@
1
+ import Events from "../events.js";
2
+ import wire from "../wire.js";
3
+ /** Permission-guarded access to the Desktop pointer visible to this Client. */
4
+ export default class ClientPointer extends Events {
5
+ listeners = 0;
6
+ sample = (event) => wire.samplePointer(event.movementX, event.movementY);
7
+ constructor() {
8
+ super((event, listener, impossible) => this.withSampling(event === "move", wire.on("host-desktop-pointer", event, value => {
9
+ const snapshot = createSnapshot(value);
10
+ if (snapshot)
11
+ listener(snapshot);
12
+ }, null, impossible)), observer => this.withSampling(true, wire.onAll("host-desktop-pointer", (event, value) => {
13
+ const snapshot = createSnapshot(value);
14
+ if (event === "move" && snapshot)
15
+ observer(event, snapshot);
16
+ })));
17
+ }
18
+ async snapshot() {
19
+ const answer = await wire.request(["desktopPointer"]);
20
+ const snapshot = createSnapshot(answer[0]);
21
+ if (!snapshot)
22
+ throw new Error("The Desktop returned an invalid pointer");
23
+ return snapshot;
24
+ }
25
+ withSampling(sample, stop) {
26
+ if (!sample)
27
+ return stop;
28
+ if (this.listeners++ === 0)
29
+ window.addEventListener("pointermove", this.sample);
30
+ let active = true;
31
+ return () => {
32
+ if (!active)
33
+ return;
34
+ active = false;
35
+ stop();
36
+ this.listeners--;
37
+ if (this.listeners === 0)
38
+ window.removeEventListener("pointermove", this.sample);
39
+ };
40
+ }
41
+ }
42
+ function createSnapshot(value) {
43
+ if (!record(value))
44
+ return null;
45
+ if (value.position === null)
46
+ return Object.freeze({ position: null });
47
+ if (!record(value.position))
48
+ return null;
49
+ const { x, y } = value.position;
50
+ if (!finite(x) || !finite(y))
51
+ return null;
52
+ return Object.freeze({ position: Object.freeze({ x, y }) });
53
+ }
54
+ function record(value) {
55
+ return typeof value === "object" && value !== null && !Array.isArray(value);
56
+ }
57
+ function finite(value) {
58
+ return typeof value === "number" && Number.isFinite(value);
59
+ }
@@ -0,0 +1,11 @@
1
+ import type { DesktopPreferencesUpdate } from "@phreshos/core";
2
+ import Events from "../events.js";
3
+ /** Effective preferences owned by the Desktop containing this Client. */
4
+ export default class ClientPreferences extends Events {
5
+ constructor();
6
+ snapshot(): Promise<Readonly<{
7
+ theme: import("@phreshos/core").Theme;
8
+ animations: boolean;
9
+ }>>;
10
+ update(preferences: DesktopPreferencesUpdate): Promise<void>;
11
+ }
@@ -1,8 +1,7 @@
1
- import {} from "@phreshos/core";
2
- import Events from "./events.js";
3
- import wire from "./wire.js";
4
- /** Mutable effective preferences local to the current Desktop. */
5
- export default class ClientDesktopPreferences extends Events {
1
+ import Events from "../events.js";
2
+ import wire from "../wire.js";
3
+ /** Effective preferences owned by the Desktop containing this Client. */
4
+ export default class ClientPreferences extends Events {
6
5
  constructor() {
7
6
  super((event, listener, impossible) => wire.on("host-desktop-preferences", event, value => {
8
7
  listener(value);
@@ -0,0 +1,8 @@
1
+ import Events from "../events.js";
2
+ /** Read-only access to the Desktop surface containing this Client. */
3
+ export default class ClientSurface extends Events {
4
+ constructor();
5
+ snapshot(): Promise<Readonly<{
6
+ size: import("@phreshos/core").DesktopSize;
7
+ }>>;
8
+ }
@@ -0,0 +1,37 @@
1
+ import Events from "../events.js";
2
+ import wire from "../wire.js";
3
+ /** Read-only access to the Desktop surface containing this Client. */
4
+ export default class ClientSurface extends Events {
5
+ constructor() {
6
+ super((event, listener, impossible) => wire.on("host-desktop-surface", event, value => {
7
+ const snapshot = createSnapshot(value);
8
+ if (snapshot)
9
+ listener(snapshot);
10
+ }, null, impossible), observer => wire.onAll("host-desktop-surface", (event, value) => {
11
+ const snapshot = createSnapshot(value);
12
+ if (typeof event === "string" && snapshot)
13
+ observer(event, snapshot);
14
+ }));
15
+ }
16
+ async snapshot() {
17
+ const answer = await wire.request(["desktopSurface"]);
18
+ const snapshot = createSnapshot(answer[0]);
19
+ if (!snapshot)
20
+ throw new Error("The System returned an invalid Desktop surface");
21
+ return snapshot;
22
+ }
23
+ }
24
+ function createSnapshot(value) {
25
+ if (!record(value) || !record(value.size))
26
+ return null;
27
+ const { width, height } = value.size;
28
+ if (!finite(width) || !finite(height))
29
+ return null;
30
+ return Object.freeze({ size: Object.freeze({ width, height }) });
31
+ }
32
+ function record(value) {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
+ }
35
+ function finite(value) {
36
+ return typeof value === "number" && Number.isFinite(value);
37
+ }
package/dist/main.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export { system, type System } from "./system.js";
2
- export { type SystemPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
3
- export { type DesktopEvents, type DesktopSize, type SystemDesktop } from "./desktop.js";
2
+ export { type SystemDesktop } from "./desktop/desktop.js";
4
3
  export { context, type Context, type ContextCapture, type ContextEvents, type ContextMessage, type ContextServer } from "./context.js";
5
4
  export { ClientService, ServerService, Service, type ServiceKey, } from "@phreshos/core";
6
5
  export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess, type AnswerCapture, type AnswerMessage, type AnswerSubscriber, type AskCapture, type AskMessage, type AskSubscriber, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
7
- export type { Askable, Capture, Captures, CaptureSubscriber, ClientLaunch, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointLifecycle, EndpointLifecycleEvents, EntryStat, EventMessage, EventName, EventOptions, EventSubscriber, Exit, FileStat, Launch, ServerLaunch, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, AnimationsPreference, DesktopPreferences, DesktopPreferencesEvents, DesktopPreferencesUpdate, SystemDesktopPreferences, ThemedValue, Theme, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } from "@phreshos/core";
6
+ export type { Askable, Capture, Captures, CaptureSubscriber, ClientLaunch, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointLifecycle, EndpointLifecycleEvents, EntryStat, EventMessage, EventName, EventOptions, EventSubscriber, Exit, FileStat, Launch, ServerLaunch, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, AnimationsPreference, DesktopPreferences, DesktopPreferencesEvents, DesktopPreferencesSource, DesktopPreferencesUpdate, DesktopPointerEvents, DesktopPointerPosition, DesktopPointerSnapshot, DesktopPointerSource, DesktopSize, DesktopSurfaceEvents, DesktopSurfaceSnapshot, DesktopSurfaceSource, WritableDesktopPreferencesSource, ThemedValue, Theme, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } from "@phreshos/core";
package/dist/main.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export { system } from "./system.js";
2
- export {} from "./pointer.js";
3
- export {} from "./desktop.js";
2
+ export {} from "./desktop/desktop.js";
4
3
  export { context } from "./context.js";
5
4
  export { ClientService, ServerService, Service, } from "@phreshos/core";
6
5
  export { Client, Endpoint, Process, Program, Server } from "./domain.js";
package/dist/service.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { type ClientService, type ServerService, type Service, type ServiceKey } from "@phreshos/core";
2
- export declare function prepareService<EventsMap extends object = {}>(key: ServiceKey & {
2
+ export declare function prepareService<EventsMap extends object = {}, Fallback = never>(key: ServiceKey & {
3
3
  endpoint: "server";
4
- }): ServerService<EventsMap>;
5
- export declare function prepareService<EventsMap extends object = {}>(key: ServiceKey & {
4
+ }): ServerService<EventsMap, Fallback>;
5
+ export declare function prepareService<EventsMap extends object = {}, Fallback = never>(key: ServiceKey & {
6
6
  endpoint: "client";
7
- }): ClientService<EventsMap>;
7
+ }): ClientService<EventsMap, Fallback>;
8
8
  export declare function prepareService(key: ServiceKey): Service;
package/dist/system.d.ts CHANGED
@@ -1,21 +1,16 @@
1
- import type { AppearanceSource, ClientService, SystemDesktopPreferences, ServerService, ServiceKey, SystemUploads } from "@phreshos/core";
2
- import { type SystemPointer } from "./pointer.js";
3
- import { type SystemDesktop } from "./desktop.js";
1
+ import type { AppearanceSource, ClientService, ServerService, ServiceKey, SystemUploads } from "@phreshos/core";
2
+ import { type SystemDesktop } from "./desktop/desktop.js";
4
3
  type ServiceEndpoint = ServiceKey["endpoint"];
5
4
  type ServiceAddress<Endpoint extends ServiceEndpoint> = Omit<ServiceKey, "endpoint"> & Readonly<{
6
5
  endpoint: Endpoint;
7
6
  }>;
8
- type ServiceHandle<Endpoint extends ServiceEndpoint, Events extends object> = Endpoint extends "server" ? ServerService<Events> : ClientService<Events>;
7
+ type ServiceHandle<Endpoint extends ServiceEndpoint, Events extends object, Fallback = never> = Endpoint extends "server" ? ServerService<Events, Fallback> : ClientService<Events, Fallback>;
9
8
  /** Desktop capabilities structurally available to a Client endpoint. */
10
9
  export interface System {
11
10
  /** Complete unresolved Appearance read from the System authority. */
12
11
  readonly appearance: AppearanceSource;
13
- /** Mutable effective preferences local to this Desktop. */
14
- readonly desktopPreferences: SystemDesktopPreferences;
15
- /** Layer-independent desktop size reads and live updates. */
12
+ /** Capabilities owned by the Desktop containing this Client. */
16
13
  readonly desktop: SystemDesktop;
17
- /** Permission-guarded desktop pointer reads and live movement. */
18
- readonly pointer: SystemPointer;
19
14
  /** Flat System-owned public uploads capability. */
20
15
  readonly uploads: SystemUploads;
21
16
  /** Performs an unrestricted server-side fetch on behalf of this Client. */
@@ -23,9 +18,9 @@ export interface System {
23
18
  /** Returns a precisely typed stable handle for either service endpoint. */
24
19
  service<Endpoint extends ServiceEndpoint>(key: ServiceAddress<Endpoint>): ServiceHandle<Endpoint, {}>;
25
20
  /** Returns a typed stable handle for one exact Server service identity. */
26
- service<ServiceEvents extends object>(key: ServiceAddress<"server">): ServerService<ServiceEvents>;
21
+ service<ServiceEvents extends object, Fallback = never>(key: ServiceAddress<"server">): ServerService<ServiceEvents, Fallback>;
27
22
  /** Returns a typed stable handle for one exact Client service identity. */
28
- service<ServiceEvents extends object>(key: ServiceAddress<"client">): ClientService<ServiceEvents>;
23
+ service<ServiceEvents extends object, Fallback = never>(key: ServiceAddress<"client">): ClientService<ServiceEvents, Fallback>;
29
24
  }
30
25
  /** Desktop capabilities available to this Client. */
31
26
  export declare const system: System;
package/dist/system.js CHANGED
@@ -1,16 +1,12 @@
1
1
  import ClientAppearance from "./appearance.js";
2
- import ClientDesktopPreferences from "./desktop-preferences.js";
3
2
  import wire from "./wire.js";
4
- import ClientPointer, {} from "./pointer.js";
5
- import ClientDesktop, {} from "./desktop.js";
3
+ import ClientDesktop, {} from "./desktop/desktop.js";
6
4
  import { prepareService } from "./service.js";
7
5
  import { uploads } from "./uploads.js";
8
6
  import controlledStream from "./controlled-stream.js";
9
7
  class ClientSystem {
10
8
  appearance = new ClientAppearance();
11
- desktopPreferences = new ClientDesktopPreferences();
12
9
  desktop = new ClientDesktop();
13
- pointer = new ClientPointer();
14
10
  uploads = uploads;
15
11
  service(key) { return prepareService(key); }
16
12
  async fetch(input, init) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,13 +47,13 @@
47
47
  "prepack": "node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.29"
50
+ "@phreshos/core": "^0.1.33"
51
51
  },
52
52
  "dependencies": {
53
53
  "@msgpack/msgpack": "^3.1.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@phreshos/core": "^0.1.29",
56
+ "@phreshos/core": "^0.1.33",
57
57
  "typescript": "^6.0.3"
58
58
  }
59
59
  }
@@ -1,11 +0,0 @@
1
- import { type DesktopPreferencesUpdate } from "@phreshos/core";
2
- import Events from "./events.js";
3
- /** Mutable effective preferences local to the current Desktop. */
4
- export default class ClientDesktopPreferences extends Events {
5
- constructor();
6
- snapshot(): Promise<Readonly<{
7
- theme: import("@phreshos/core").Theme;
8
- animations: boolean;
9
- }>>;
10
- update(preferences: DesktopPreferencesUpdate): Promise<void>;
11
- }
package/dist/desktop.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { Subscribable } from "@phreshos/core";
2
- import Events from "./events.js";
3
- /** The complete measured desktop area in CSS pixels. */
4
- export type DesktopSize = Readonly<{
5
- width: number;
6
- height: number;
7
- }>;
8
- /** Live changes to the desktop area containing this Client. */
9
- export type DesktopEvents = {
10
- /** The desktop area resized. */
11
- resize: DesktopSize;
12
- };
13
- /** Explicit desktop size reads and future resizes. */
14
- export interface SystemDesktop extends Subscribable<DesktopEvents, never> {
15
- /** Reads the complete current desktop area in CSS pixels. */
16
- size(): Promise<DesktopSize>;
17
- }
18
- /** Desktop access bound to the current Client Process boundary. */
19
- export default class ClientDesktop extends Events {
20
- constructor();
21
- size(): Promise<Readonly<{
22
- width: number;
23
- height: number;
24
- }>>;
25
- }
package/dist/desktop.js DELETED
@@ -1,34 +0,0 @@
1
- import Events from "./events.js";
2
- import wire from "./wire.js";
3
- /** Desktop access bound to the current Client Process boundary. */
4
- export default class ClientDesktop extends Events {
5
- constructor() {
6
- super((event, listener, impossible) => wire.on("host-desktop", event, value => {
7
- const size = createSize(value);
8
- if (size)
9
- listener(size);
10
- }, null, impossible), observer => wire.onAll("host-desktop", (event, value) => {
11
- const size = createSize(value);
12
- if (typeof event === "string" && size)
13
- observer(event, size);
14
- }));
15
- }
16
- async size() {
17
- const answer = await wire.request(["desktop"]);
18
- const size = createSize(answer[0]);
19
- if (!size)
20
- throw new Error("The system returned an invalid desktop size");
21
- return size;
22
- }
23
- }
24
- function createSize(value) {
25
- if (typeof value !== "object" || value === null || Array.isArray(value))
26
- return null;
27
- const size = value;
28
- if (!finite(size.width) || !finite(size.height))
29
- return null;
30
- return Object.freeze({ width: size.width, height: size.height });
31
- }
32
- function finite(value) {
33
- return typeof value === "number" && Number.isFinite(value);
34
- }
package/dist/pointer.d.ts DELETED
@@ -1,35 +0,0 @@
1
- import type { Subscribable } from "@phreshos/core";
2
- import Events from "./events.js";
3
- /** Pointer coordinates relative to the desktop display core. */
4
- export type PointerPosition = Readonly<{
5
- /** Horizontal coordinate in CSS pixels. */
6
- x: number;
7
- /** Vertical coordinate in CSS pixels. */
8
- y: number;
9
- }>;
10
- /** Live pointer events visible to this Client. */
11
- export type PointerEvents = {
12
- /** The pointer moved over the desktop display core. */
13
- move: PointerPosition;
14
- };
15
- /** Permission-guarded pointer positions and future movement. */
16
- export interface SystemPointer extends Subscribable<PointerEvents, never> {
17
- /**
18
- * Reads the current desktop pointer position, or `null` before one is known.
19
- * Rejects unless the `pointer` permission is currently granted.
20
- */
21
- position(): Promise<PointerPosition | null>;
22
- }
23
- /** Client pointer access bound to the current Process boundary. */
24
- export default class ClientPointer extends Events {
25
- private listeners;
26
- private readonly sample;
27
- constructor();
28
- position(): Promise<Readonly<{
29
- /** Horizontal coordinate in CSS pixels. */
30
- x: number;
31
- /** Vertical coordinate in CSS pixels. */
32
- y: number;
33
- }> | null>;
34
- private withSampling;
35
- }
package/dist/pointer.js DELETED
@@ -1,54 +0,0 @@
1
- import Events from "./events.js";
2
- import wire from "./wire.js";
3
- /** Client pointer access bound to the current Process boundary. */
4
- export default class ClientPointer extends Events {
5
- listeners = 0;
6
- sample = (event) => wire.samplePointer(event.movementX, event.movementY);
7
- constructor() {
8
- super((event, listener, impossible) => this.withSampling(event === "move", wire.on("host-pointer", event, value => {
9
- const position = createPosition(value);
10
- if (position)
11
- listener(position);
12
- }, null, impossible)), observer => this.withSampling(true, wire.onAll("host-pointer", (event, value) => {
13
- const position = createPosition(value);
14
- if (event === "move" && position)
15
- observer(event, position);
16
- })));
17
- }
18
- async position() {
19
- const answer = await wire.request(["pointer"]);
20
- if (answer[0] === null)
21
- return null;
22
- const position = createPosition(answer[0]);
23
- if (!position)
24
- throw new Error("The desktop returned an invalid Pointer position");
25
- return position;
26
- }
27
- withSampling(sample, stop) {
28
- if (!sample)
29
- return stop;
30
- if (this.listeners++ === 0)
31
- window.addEventListener("pointermove", this.sample);
32
- let active = true;
33
- return () => {
34
- if (!active)
35
- return;
36
- active = false;
37
- stop();
38
- this.listeners--;
39
- if (this.listeners === 0)
40
- window.removeEventListener("pointermove", this.sample);
41
- };
42
- }
43
- }
44
- function createPosition(value) {
45
- if (typeof value !== "object" || value === null || Array.isArray(value))
46
- return null;
47
- const position = value;
48
- if (!finite(position.x) || !finite(position.y))
49
- return null;
50
- return Object.freeze({ x: position.x, y: position.y });
51
- }
52
- function finite(value) {
53
- return typeof value === "number" && Number.isFinite(value);
54
- }