@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 +10 -5
- package/dist/main.d.ts +1 -1
- package/dist/messagepack.d.ts +3 -0
- package/dist/messagepack.js +130 -0
- package/dist/wire.d.ts +1 -0
- package/dist/wire.js +54 -3
- package/package.json +4 -1
package/dist/desktop.d.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import type {
|
|
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:
|
|
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<
|
|
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:
|
|
18
|
-
height:
|
|
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,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 [
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|