@phreshos/client 0.1.0
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 +65 -0
- package/dist/channel.d.ts +8 -0
- package/dist/channel.js +16 -0
- package/dist/content.d.ts +7 -0
- package/dist/content.js +51 -0
- package/dist/current.d.ts +20 -0
- package/dist/current.js +58 -0
- package/dist/deadline.d.ts +7 -0
- package/dist/deadline.js +13 -0
- package/dist/domain.d.ts +93 -0
- package/dist/domain.js +285 -0
- package/dist/events.d.ts +16 -0
- package/dist/events.js +83 -0
- package/dist/host.d.ts +25 -0
- package/dist/host.js +149 -0
- package/dist/log.d.ts +6 -0
- package/dist/log.js +64 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +4 -0
- package/dist/storage.d.ts +5 -0
- package/dist/storage.js +98 -0
- package/dist/wire.d.ts +34 -0
- package/dist/wire.js +226 -0
- package/package.json +30 -0
package/dist/wire.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import Deadline from "./deadline.js";
|
|
2
|
+
import { defaultTimeout } from "./events.js";
|
|
3
|
+
import captureClientOutput from "./log.js";
|
|
4
|
+
/** The client endpoint's sole postMessage adapter. */
|
|
5
|
+
class Wire {
|
|
6
|
+
parent = window.parent === window ? null : window.parent;
|
|
7
|
+
pending = new Map();
|
|
8
|
+
subscribers = new Map();
|
|
9
|
+
every = new Map();
|
|
10
|
+
impossible = new Map();
|
|
11
|
+
boundarySubscriptions = new Map();
|
|
12
|
+
observations = new Map();
|
|
13
|
+
identity;
|
|
14
|
+
constructor() {
|
|
15
|
+
let identify = () => undefined;
|
|
16
|
+
this.identity = new Promise(resolve => { identify = resolve; });
|
|
17
|
+
this.send("boundary", "document", crypto.randomUUID());
|
|
18
|
+
this.send("boundary", "identity");
|
|
19
|
+
window.addEventListener("message", event => {
|
|
20
|
+
if (event.source !== this.parent || !Array.isArray(event.data))
|
|
21
|
+
return;
|
|
22
|
+
const [route, ...values] = event.data;
|
|
23
|
+
if (route === "boundary") {
|
|
24
|
+
const [operation, ...rest] = values;
|
|
25
|
+
if (operation === "identity" && typeof rest[0] === "string")
|
|
26
|
+
identify(rest[0]);
|
|
27
|
+
if (operation === "impossible" && typeof rest[0] === "string" && typeof rest[1] === "string") {
|
|
28
|
+
this.impossible.get(rest[0])?.(new Error(rest[1]));
|
|
29
|
+
this.impossible.delete(rest[0]);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (route === "host-end" && values[0] === "observations-ready") {
|
|
34
|
+
for (const [subscription, description] of this.boundarySubscriptions) {
|
|
35
|
+
this.send("boundary", "subscribe", subscription, ...description);
|
|
36
|
+
}
|
|
37
|
+
for (const [subscription, description] of this.observations) {
|
|
38
|
+
this.send("end-host", "observe", subscription, ...description);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (values[0] === "answer" && typeof values[1] === "string") {
|
|
43
|
+
this.settle(values[1], values.at(-1));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.deliver(route, values);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
send(route, ...values) {
|
|
50
|
+
this.parent?.postMessage([route, ...values], "*");
|
|
51
|
+
}
|
|
52
|
+
request(values, timeout = defaultTimeout, transfer = []) {
|
|
53
|
+
return this.question("end-host", values, timeout, transfer);
|
|
54
|
+
}
|
|
55
|
+
requestWithin(values, deadline, transfer = []) {
|
|
56
|
+
return this.question("end-host", values, deadline.remaining(), transfer, deadline.milliseconds);
|
|
57
|
+
}
|
|
58
|
+
askServerWithin(event, payload, deadline) {
|
|
59
|
+
return this.question("end-end", [event, payload], deadline.remaining(), [], deadline.milliseconds);
|
|
60
|
+
}
|
|
61
|
+
question(route, values, timeout, transfer = [], reportedTimeout = timeout) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
let active = true;
|
|
64
|
+
let question = null;
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
if (!active)
|
|
67
|
+
return;
|
|
68
|
+
active = false;
|
|
69
|
+
if (question) {
|
|
70
|
+
this.pending.delete(question);
|
|
71
|
+
this.send("boundary", "forget", question);
|
|
72
|
+
}
|
|
73
|
+
reject(new Error(`Answer timeout ${reportedTimeout}ms`));
|
|
74
|
+
}, timeout);
|
|
75
|
+
this.identity.then(identity => {
|
|
76
|
+
if (!active)
|
|
77
|
+
return;
|
|
78
|
+
question = `client:${identity}:${crypto.randomUUID()}`;
|
|
79
|
+
const publicId = crypto.randomUUID();
|
|
80
|
+
this.send("boundary", "expect", question);
|
|
81
|
+
this.pending.set(question, {
|
|
82
|
+
timer,
|
|
83
|
+
resolve: value => {
|
|
84
|
+
if (!active)
|
|
85
|
+
return;
|
|
86
|
+
active = false;
|
|
87
|
+
resolve(value);
|
|
88
|
+
},
|
|
89
|
+
reject: error => {
|
|
90
|
+
if (!active)
|
|
91
|
+
return;
|
|
92
|
+
active = false;
|
|
93
|
+
reject(error);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const message = route === "end-end"
|
|
97
|
+
? [route, "wait", question, publicId, ...values]
|
|
98
|
+
: [route, "wait", question, ...values];
|
|
99
|
+
try {
|
|
100
|
+
this.parent?.postMessage(message, "*", transfer);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.pending.delete(question);
|
|
104
|
+
this.send("boundary", "forget", question);
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
active = false;
|
|
107
|
+
reject(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
expectWithin(question, deadline) {
|
|
113
|
+
this.send("boundary", "expect", question);
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
this.pending.delete(question);
|
|
117
|
+
this.send("boundary", "forget", question);
|
|
118
|
+
reject(new Error(`Answer timeout ${deadline.milliseconds}ms`));
|
|
119
|
+
}, deadline.remaining());
|
|
120
|
+
this.pending.set(question, { resolve, reject, timer });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
forget(question) {
|
|
124
|
+
const pending = this.pending.get(question);
|
|
125
|
+
if (pending)
|
|
126
|
+
clearTimeout(pending.timer);
|
|
127
|
+
this.pending.delete(question);
|
|
128
|
+
this.send("boundary", "forget", question);
|
|
129
|
+
}
|
|
130
|
+
on(route, event, handler, subject = null, impossible) {
|
|
131
|
+
const key = `${route}:${event}`;
|
|
132
|
+
const handlers = this.subscribers.get(key) ?? new Set();
|
|
133
|
+
handlers.add(handler);
|
|
134
|
+
this.subscribers.set(key, handlers);
|
|
135
|
+
const subscription = this.register("publish", route, event, subject, impossible);
|
|
136
|
+
return once(() => {
|
|
137
|
+
handlers.delete(handler);
|
|
138
|
+
if (!handlers.size)
|
|
139
|
+
this.subscribers.delete(key);
|
|
140
|
+
this.unregister(subscription);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
onAll(route, handler, subject = null) {
|
|
144
|
+
const handlers = this.every.get(route) ?? new Set();
|
|
145
|
+
handlers.add(handler);
|
|
146
|
+
this.every.set(route, handlers);
|
|
147
|
+
const subscription = this.register("publish", route, null, subject);
|
|
148
|
+
return once(() => {
|
|
149
|
+
handlers.delete(handler);
|
|
150
|
+
if (!handlers.size)
|
|
151
|
+
this.every.delete(route);
|
|
152
|
+
this.unregister(subscription);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
observe(target, half, kind, event, handler, impossible) {
|
|
156
|
+
const subscription = crypto.randomUUID();
|
|
157
|
+
const stop = this.on("observed", subscription, handler);
|
|
158
|
+
const description = [target, half, kind, event, impossible !== undefined];
|
|
159
|
+
this.observations.set(subscription, description);
|
|
160
|
+
if (impossible)
|
|
161
|
+
this.impossible.set(subscription, impossible);
|
|
162
|
+
this.send("end-host", "observe", subscription, ...description);
|
|
163
|
+
return once(() => {
|
|
164
|
+
stop();
|
|
165
|
+
this.observations.delete(subscription);
|
|
166
|
+
this.impossible.delete(subscription);
|
|
167
|
+
this.send("end-host", "unobserve", subscription);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
samplePointer(movementX, movementY) {
|
|
171
|
+
this.send("end-host", "pointerSample", movementX, movementY);
|
|
172
|
+
}
|
|
173
|
+
register(kind, route, event, subject, impossible) {
|
|
174
|
+
const subscription = crypto.randomUUID();
|
|
175
|
+
const description = [kind, route, event, subject, impossible !== undefined];
|
|
176
|
+
this.boundarySubscriptions.set(subscription, description);
|
|
177
|
+
if (impossible)
|
|
178
|
+
this.impossible.set(subscription, impossible);
|
|
179
|
+
this.send("boundary", "subscribe", subscription, ...description);
|
|
180
|
+
return subscription;
|
|
181
|
+
}
|
|
182
|
+
unregister(subscription) {
|
|
183
|
+
if (!this.boundarySubscriptions.delete(subscription))
|
|
184
|
+
return;
|
|
185
|
+
this.impossible.delete(subscription);
|
|
186
|
+
this.send("boundary", "unsubscribe", subscription);
|
|
187
|
+
}
|
|
188
|
+
deliver(route, values) {
|
|
189
|
+
const [event, ...message] = values;
|
|
190
|
+
if (typeof event !== "string")
|
|
191
|
+
return;
|
|
192
|
+
for (const handler of [...this.subscribers.get(`${route}:${event}`) ?? []])
|
|
193
|
+
invoke(handler, message);
|
|
194
|
+
for (const observer of [...this.every.get(route) ?? []])
|
|
195
|
+
invoke(observer, [event, ...message]);
|
|
196
|
+
}
|
|
197
|
+
settle(question, outcome) {
|
|
198
|
+
const pending = this.pending.get(question);
|
|
199
|
+
if (!pending)
|
|
200
|
+
return;
|
|
201
|
+
this.pending.delete(question);
|
|
202
|
+
clearTimeout(pending.timer);
|
|
203
|
+
this.send("boundary", "forget", question);
|
|
204
|
+
if (outcome?.success === true)
|
|
205
|
+
pending.resolve(outcome.result);
|
|
206
|
+
else if (outcome?.success === false && typeof outcome.error === "string")
|
|
207
|
+
pending.reject(new Error(outcome.error));
|
|
208
|
+
else
|
|
209
|
+
pending.reject(new Error("The boundary returned an invalid outcome"));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function invoke(handler, values) {
|
|
213
|
+
Promise.resolve().then(() => handler(...values)).catch(error => queueMicrotask(() => { throw error; }));
|
|
214
|
+
}
|
|
215
|
+
function once(cleanup) {
|
|
216
|
+
let active = true;
|
|
217
|
+
return () => {
|
|
218
|
+
if (!active)
|
|
219
|
+
return;
|
|
220
|
+
active = false;
|
|
221
|
+
cleanup();
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const wire = new Wire();
|
|
225
|
+
captureClientOutput(wire);
|
|
226
|
+
export default wire;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phreshos/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The SDK used by a Program's client endpoint.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/main.js",
|
|
7
|
+
"types": "dist/main.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/main.d.ts",
|
|
11
|
+
"default": "./dist/main.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"check": "tsc --noEmit",
|
|
20
|
+
"build": "tsc --noEmit false --outDir dist --rootDir source",
|
|
21
|
+
"prepack": "node --run build"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@phreshos/core": "^0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@phreshos/core": "0.1.0",
|
|
28
|
+
"typescript": "^6.0.3"
|
|
29
|
+
}
|
|
30
|
+
}
|