@phreshos/client 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/desktop.d.ts CHANGED
@@ -1,20 +1,25 @@
1
- import type { Size, Subscribable } from "@phreshos/core";
1
+ import type { Subscribable } from "@phreshos/core";
2
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
+ }>;
3
8
  /** Live changes to the desktop area containing this Client. */
4
9
  export type DesktopEvents = {
5
10
  /** The desktop area resized. */
6
- resize: Size;
11
+ resize: DesktopSize;
7
12
  };
8
13
  /** Explicit desktop size reads and future resizes. */
9
14
  export interface HostDesktop extends Subscribable<DesktopEvents, never> {
10
15
  /** Reads the complete current desktop area in CSS pixels. */
11
- size(): Promise<Size>;
16
+ size(): Promise<DesktopSize>;
12
17
  }
13
18
  /** Desktop access bound to the current Client Process boundary. */
14
19
  export default class ClientDesktop extends Events {
15
20
  constructor();
16
21
  size(): Promise<Readonly<{
17
- width: import("@phreshos/core").Value;
18
- height: import("@phreshos/core").Value;
22
+ width: number;
23
+ height: number;
19
24
  }>>;
20
25
  }
package/dist/main.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { host, type Host } from "./host.js";
2
2
  export { type HostPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
3
- export { type DesktopEvents, type HostDesktop } from "./desktop.js";
3
+ export { type DesktopEvents, type DesktopSize, type HostDesktop } from "./desktop.js";
4
4
  export { current, type Current, type CurrentServer } from "./current.js";
5
5
  export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
6
6
  export { ClientServiceHandler, ServerServiceHandler, ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
@@ -0,0 +1,3 @@
1
+ export type Bytes = Uint8Array<ArrayBuffer>;
2
+ export declare const serialize: (value: unknown, attachments?: readonly object[]) => Bytes;
3
+ export declare const deserialize: (bytes: Uint8Array, attachments?: readonly unknown[]) => unknown;
@@ -0,0 +1,130 @@
1
+ import { decode, encode, ExtensionCodec } from "@msgpack/msgpack";
2
+ class Attachment {
3
+ index;
4
+ constructor(index) {
5
+ this.index = index;
6
+ }
7
+ }
8
+ class BigInteger {
9
+ value;
10
+ constructor(value) {
11
+ this.value = value;
12
+ }
13
+ }
14
+ class Undefined {
15
+ }
16
+ const textEncoder = new TextEncoder();
17
+ const textDecoder = new TextDecoder("utf-8", { fatal: true });
18
+ const extensions = new ExtensionCodec();
19
+ const serializePrepared = (value, context) => encode(value, { context, extensionCodec: extensions });
20
+ const deserializePrepared = (bytes, context) => decode(bytes, { context, extensionCodec: extensions });
21
+ extensions.register({
22
+ type: 0,
23
+ encode: value => value instanceof Attachment ? uint32(value.index) : null,
24
+ decode: (bytes, _type, context) => {
25
+ const attachment = context.incoming?.[readUint32(bytes)];
26
+ if (attachment === undefined)
27
+ throw new Error("The MessagePack attachment is absent");
28
+ return attachment;
29
+ }
30
+ });
31
+ extensions.register({
32
+ type: 1,
33
+ encode: (value, context) => value instanceof Map ? serializePrepared([...value], context) : null,
34
+ decode: (bytes, _type, context) => new Map(deserializePrepared(bytes, context))
35
+ });
36
+ extensions.register({
37
+ type: 2,
38
+ encode: (value, context) => value instanceof Set ? serializePrepared([...value], context) : null,
39
+ decode: (bytes, _type, context) => new Set(deserializePrepared(bytes, context))
40
+ });
41
+ extensions.register({
42
+ type: 3,
43
+ encode: (value, context) => value instanceof RegExp ? serializePrepared([value.source, value.flags, value.lastIndex], context) : null,
44
+ decode: (bytes, _type, context) => {
45
+ const [source, flags, lastIndex] = deserializePrepared(bytes, context);
46
+ const expression = new RegExp(source, flags);
47
+ expression.lastIndex = lastIndex;
48
+ return expression;
49
+ }
50
+ });
51
+ extensions.register({
52
+ type: 4,
53
+ encode: value => value instanceof URL ? textEncoder.encode(value.href) : null,
54
+ decode: bytes => new URL(textDecoder.decode(bytes))
55
+ });
56
+ extensions.register({
57
+ type: 5,
58
+ encode: (value, context) => value instanceof Error ? serializePrepared(prepare({
59
+ cause: value.cause,
60
+ message: value.message,
61
+ name: value.name,
62
+ stack: value.stack
63
+ }, context), context) : null,
64
+ decode: (bytes, _type, context) => {
65
+ const value = deserializePrepared(bytes, context);
66
+ const error = new Error(value.message, { cause: value.cause });
67
+ error.name = value.name;
68
+ if (value.stack !== undefined)
69
+ error.stack = value.stack;
70
+ return error;
71
+ }
72
+ });
73
+ extensions.register({
74
+ type: 6,
75
+ encode: value => value instanceof BigInteger ? textEncoder.encode(value.value.toString()) : null,
76
+ decode: bytes => BigInt(textDecoder.decode(bytes))
77
+ });
78
+ extensions.register({
79
+ type: 7,
80
+ encode: value => value instanceof Undefined ? new Uint8Array() : null,
81
+ decode: () => undefined
82
+ });
83
+ export const serialize = (value, attachments = []) => {
84
+ const outgoing = new Map(attachments.map((attachment, index) => [attachment, index]));
85
+ const context = { outgoing };
86
+ return serializePrepared(prepare(value, context), context);
87
+ };
88
+ export const deserialize = (bytes, attachments = []) => {
89
+ return deserializePrepared(bytes, { incoming: attachments });
90
+ };
91
+ const prepare = (value, context) => {
92
+ if (value === undefined)
93
+ return new Undefined();
94
+ if (typeof value === "bigint")
95
+ return new BigInteger(value);
96
+ if (typeof value === "function" || typeof value === "symbol")
97
+ return null;
98
+ if (value === null || typeof value !== "object")
99
+ return value;
100
+ const attachment = context.outgoing?.get(value);
101
+ if (attachment !== undefined)
102
+ return new Attachment(attachment);
103
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL || value instanceof Error)
104
+ return value;
105
+ if (value instanceof ArrayBuffer)
106
+ return new Uint8Array(value);
107
+ if (ArrayBuffer.isView(value))
108
+ return Uint8Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
109
+ if (value instanceof Map)
110
+ return new Map([...value].map(([key, entry]) => [prepare(key, context), prepare(entry, context)]));
111
+ if (value instanceof Set)
112
+ return new Set([...value].map(entry => prepare(entry, context)));
113
+ if (Array.isArray(value))
114
+ return value.map(entry => prepare(entry, context));
115
+ if ("toJSON" in value && typeof value.toJSON === "function")
116
+ return prepare(value.toJSON(), context);
117
+ return Object.fromEntries(Object.entries(value)
118
+ .filter(([, entry]) => typeof entry !== "function")
119
+ .map(([key, entry]) => [key, prepare(entry, context)]));
120
+ };
121
+ const uint32 = (value) => {
122
+ const bytes = new Uint8Array(4);
123
+ new DataView(bytes.buffer).setUint32(0, value);
124
+ return bytes;
125
+ };
126
+ const readUint32 = (bytes) => {
127
+ if (bytes.byteLength !== 4)
128
+ throw new Error("The MessagePack attachment reference is invalid");
129
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
130
+ };
package/dist/wire.d.ts CHANGED
@@ -25,6 +25,7 @@ declare class Wire {
25
25
  reference: string;
26
26
  }>;
27
27
  private question;
28
+ private post;
28
29
  expectWithin(question: string, deadline: Deadline): Promise<unknown>;
29
30
  forget(question: string): void;
30
31
  on(route: string, event: string, handler: Handler, subject?: string | null, impossible?: Failure): Cleanup;
package/dist/wire.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Deadline from "./deadline.js";
2
2
  import { defaultTimeout } from "./events.js";
3
3
  import captureClientOutput from "./log.js";
4
+ import { deserialize, serialize } from "./messagepack.js";
4
5
  /** The client endpoint's sole postMessage adapter. */
5
6
  class Wire {
6
7
  parent = window.parent === window ? null : window.parent;
@@ -14,7 +15,19 @@ class Wire {
14
15
  window.addEventListener("message", event => {
15
16
  if (event.source !== this.parent || !Array.isArray(event.data))
16
17
  return;
17
- const [route, ...values] = event.data;
18
+ const [bytes, ...attachments] = event.data;
19
+ if (!(bytes instanceof Uint8Array))
20
+ return;
21
+ let message;
22
+ try {
23
+ message = deserialize(bytes, attachments);
24
+ }
25
+ catch {
26
+ return;
27
+ }
28
+ if (!Array.isArray(message) || typeof message[0] !== "string")
29
+ return;
30
+ const [route, ...values] = message;
18
31
  if (route === "boundary") {
19
32
  const [operation, ...rest] = values;
20
33
  if (operation === "impossible" && typeof rest[0] === "string" && typeof rest[1] === "string") {
@@ -31,7 +44,7 @@ class Wire {
31
44
  });
32
45
  }
33
46
  send(route, ...values) {
34
- this.parent?.postMessage([route, ...values], "*");
47
+ this.post([route, ...values]);
35
48
  }
36
49
  request(values, timeout = defaultTimeout, transfer = []) {
37
50
  return this.question("end-host", values, timeout, transfer);
@@ -108,7 +121,7 @@ class Wire {
108
121
  ? [route, "wait", question, publicId, ...values]
109
122
  : [route, "wait", question, ...values];
110
123
  try {
111
- this.parent?.postMessage(message, "*", transfer);
124
+ this.post(message, transfer);
112
125
  }
113
126
  catch (error) {
114
127
  this.pending.delete(question);
@@ -126,6 +139,13 @@ class Wire {
126
139
  });
127
140
  });
128
141
  }
142
+ post(message, transfer = []) {
143
+ if (!this.parent)
144
+ return;
145
+ const attachments = nativeAttachments(message, transfer);
146
+ const bytes = serialize(message, attachments);
147
+ this.parent.postMessage([bytes, ...attachments], "*", [bytes.buffer, ...transfer]);
148
+ }
129
149
  expectWithin(question, deadline) {
130
150
  this.send("boundary", "expect", question);
131
151
  return new Promise((resolve, reject) => {
@@ -254,6 +274,37 @@ function once(cleanup) {
254
274
  cleanup();
255
275
  };
256
276
  }
277
+ function nativeAttachments(value, transfer) {
278
+ const attachments = [...transfer];
279
+ const known = new Set(attachments);
280
+ const visit = (entry) => {
281
+ if (entry === null || typeof entry !== "object" || known.has(entry))
282
+ return;
283
+ known.add(entry);
284
+ if (entry instanceof Blob) {
285
+ attachments.push(entry);
286
+ return;
287
+ }
288
+ if (entry instanceof Date || entry instanceof RegExp || entry instanceof URL || entry instanceof Error || entry instanceof ArrayBuffer || ArrayBuffer.isView(entry))
289
+ return;
290
+ if (entry instanceof Map) {
291
+ for (const [key, item] of entry) {
292
+ visit(key);
293
+ visit(item);
294
+ }
295
+ return;
296
+ }
297
+ if (entry instanceof Set) {
298
+ for (const item of entry)
299
+ visit(item);
300
+ return;
301
+ }
302
+ for (const item of Array.isArray(entry) ? entry : Object.values(entry))
303
+ visit(item);
304
+ };
305
+ visit(value);
306
+ return attachments;
307
+ }
257
308
  const wire = new Wire();
258
309
  captureClientOutput(wire);
259
310
  export default wire;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -48,6 +48,9 @@
48
48
  "peerDependencies": {
49
49
  "@phreshos/core": "^0.1.6"
50
50
  },
51
+ "dependencies": {
52
+ "@msgpack/msgpack": "^3.1.3"
53
+ },
51
54
  "devDependencies": {
52
55
  "@phreshos/core": "^0.1.6",
53
56
  "typescript": "^6.0.3"