1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | const proxyMarker = Symbol("Comlink.proxy");
|
14 | const createEndpoint = Symbol("Comlink.endpoint");
|
15 | const throwSet = new WeakSet();
|
16 | const transferHandlers = new Map([
|
17 | [
|
18 | "proxy",
|
19 | {
|
20 | canHandle: obj => obj && obj[proxyMarker],
|
21 | serialize(obj) {
|
22 | const { port1, port2 } = new MessageChannel();
|
23 | expose(obj, port1);
|
24 | return [port2, [port2]];
|
25 | },
|
26 | deserialize: (port) => {
|
27 | port.start();
|
28 | return wrap(port);
|
29 | }
|
30 | }
|
31 | ],
|
32 | [
|
33 | "throw",
|
34 | {
|
35 | canHandle: obj => throwSet.has(obj),
|
36 | serialize(obj) {
|
37 | const isError = obj instanceof Error;
|
38 | let serialized = obj;
|
39 | if (isError) {
|
40 | serialized = {
|
41 | isError,
|
42 | message: obj.message,
|
43 | stack: obj.stack
|
44 | };
|
45 | }
|
46 | return [serialized, []];
|
47 | },
|
48 | deserialize(obj) {
|
49 | if (obj.isError) {
|
50 | throw Object.assign(new Error(), obj);
|
51 | }
|
52 | throw obj;
|
53 | }
|
54 | }
|
55 | ]
|
56 | ]);
|
57 | function expose(obj, ep = self) {
|
58 | ep.addEventListener("message", (async (ev) => {
|
59 | if (!ev || !ev.data) {
|
60 | return;
|
61 | }
|
62 | const { id, type, path } = {
|
63 | path: [],
|
64 | ...ev.data
|
65 | };
|
66 | const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
67 | let returnValue;
|
68 | try {
|
69 | const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
|
70 | const rawValue = path.reduce((obj, prop) => obj[prop], obj);
|
71 | switch (type) {
|
72 | case 0 :
|
73 | {
|
74 | returnValue = await rawValue;
|
75 | }
|
76 | break;
|
77 | case 1 :
|
78 | {
|
79 | parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
|
80 | returnValue = true;
|
81 | }
|
82 | break;
|
83 | case 2 :
|
84 | {
|
85 | returnValue = await rawValue.apply(parent, argumentList);
|
86 | }
|
87 | break;
|
88 | case 3 :
|
89 | {
|
90 | const value = await new rawValue(...argumentList);
|
91 | returnValue = proxy(value);
|
92 | }
|
93 | break;
|
94 | case 4 :
|
95 | {
|
96 | const { port1, port2 } = new MessageChannel();
|
97 | expose(obj, port2);
|
98 | returnValue = transfer(port1, [port1]);
|
99 | }
|
100 | break;
|
101 | default:
|
102 | console.warn("Unrecognized message", ev.data);
|
103 | }
|
104 | }
|
105 | catch (e) {
|
106 | returnValue = e;
|
107 | throwSet.add(e);
|
108 | }
|
109 | const [wireValue, transferables] = toWireValue(returnValue);
|
110 | ep.postMessage({ ...wireValue, id }, transferables);
|
111 | }));
|
112 | if (ep.start) {
|
113 | ep.start();
|
114 | }
|
115 | }
|
116 | function wrap(ep) {
|
117 | return createProxy(ep);
|
118 | }
|
119 | function createProxy(ep, path = []) {
|
120 | const proxy = new Proxy(function () { }, {
|
121 | get(_target, prop) {
|
122 | if (prop === "then") {
|
123 | if (path.length === 0) {
|
124 | return { then: () => proxy };
|
125 | }
|
126 | const r = requestResponseMessage(ep, {
|
127 | type: 0 ,
|
128 | path: path.map(p => p.toString())
|
129 | }).then(fromWireValue);
|
130 | return r.then.bind(r);
|
131 | }
|
132 | return createProxy(ep, [...path, prop]);
|
133 | },
|
134 | set(_target, prop, rawValue) {
|
135 |
|
136 |
|
137 | const [value, transferables] = toWireValue(rawValue);
|
138 | return requestResponseMessage(ep, {
|
139 | type: 1 ,
|
140 | path: [...path, prop].map(p => p.toString()),
|
141 | value
|
142 | }, transferables).then(fromWireValue);
|
143 | },
|
144 | apply(_target, _thisArg, rawArgumentList) {
|
145 | const last = path[path.length - 1];
|
146 | if (last === createEndpoint) {
|
147 | return requestResponseMessage(ep, {
|
148 | type: 4
|
149 | }).then(fromWireValue);
|
150 | }
|
151 |
|
152 | if (last === "bind") {
|
153 | return createProxy(ep, path.slice(0, -1));
|
154 | }
|
155 | const [argumentList, transferables] = processArguments(rawArgumentList);
|
156 | return requestResponseMessage(ep, {
|
157 | type: 2 ,
|
158 | path: path.map(p => p.toString()),
|
159 | argumentList
|
160 | }, transferables).then(fromWireValue);
|
161 | },
|
162 | construct(_target, rawArgumentList) {
|
163 | const [argumentList, transferables] = processArguments(rawArgumentList);
|
164 | return requestResponseMessage(ep, {
|
165 | type: 3 ,
|
166 | path: path.map(p => p.toString()),
|
167 | argumentList
|
168 | }, transferables).then(fromWireValue);
|
169 | }
|
170 | });
|
171 | return proxy;
|
172 | }
|
173 | function myFlat(arr) {
|
174 | return Array.prototype.concat.apply([], arr);
|
175 | }
|
176 | function processArguments(argumentList) {
|
177 | const processed = argumentList.map(toWireValue);
|
178 | return [processed.map(v => v[0]), myFlat(processed.map(v => v[1]))];
|
179 | }
|
180 | const transferCache = new WeakMap();
|
181 | function transfer(obj, transfers) {
|
182 | transferCache.set(obj, transfers);
|
183 | return obj;
|
184 | }
|
185 | function proxy(obj) {
|
186 | return Object.assign(obj, { [proxyMarker]: true });
|
187 | }
|
188 | function windowEndpoint(w, context = self) {
|
189 | return {
|
190 | postMessage: (msg, transferables) => w.postMessage(msg, "*", transferables),
|
191 | addEventListener: context.addEventListener.bind(context),
|
192 | removeEventListener: context.removeEventListener.bind(context)
|
193 | };
|
194 | }
|
195 | function toWireValue(value) {
|
196 | for (const [name, handler] of transferHandlers) {
|
197 | if (handler.canHandle(value)) {
|
198 | const [serializedValue, transferables] = handler.serialize(value);
|
199 | return [
|
200 | {
|
201 | type: 3 ,
|
202 | name,
|
203 | value: serializedValue
|
204 | },
|
205 | transferables
|
206 | ];
|
207 | }
|
208 | }
|
209 | return [
|
210 | {
|
211 | type: 0 ,
|
212 | value
|
213 | },
|
214 | transferCache.get(value) || []
|
215 | ];
|
216 | }
|
217 | function fromWireValue(value) {
|
218 | switch (value.type) {
|
219 | case 3 :
|
220 | return transferHandlers.get(value.name).deserialize(value.value);
|
221 | case 0 :
|
222 | return value.value;
|
223 | }
|
224 | }
|
225 | function requestResponseMessage(ep, msg, transfers) {
|
226 | return new Promise(resolve => {
|
227 | const id = generateUUID();
|
228 | ep.addEventListener("message", function l(ev) {
|
229 | if (!ev.data || !ev.data.id || ev.data.id !== id) {
|
230 | return;
|
231 | }
|
232 | ep.removeEventListener("message", l);
|
233 | resolve(ev.data);
|
234 | });
|
235 | if (ep.start) {
|
236 | ep.start();
|
237 | }
|
238 | ep.postMessage({ id, ...msg }, transfers);
|
239 | });
|
240 | }
|
241 | function generateUUID() {
|
242 | return new Array(4)
|
243 | .fill(0)
|
244 | .map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
|
245 | .join("-");
|
246 | }
|
247 |
|
248 | export { createEndpoint, expose, proxy, proxyMarker, transfer, transferHandlers, windowEndpoint, wrap };
|
249 |
|