UNPKG

8.78 kBJavaScriptView Raw
1/**
2 * Copyright 2019 Google Inc. All Rights Reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13const proxyMarker = Symbol("Comlink.proxy");
14const createEndpoint = Symbol("Comlink.endpoint");
15const throwSet = new WeakSet();
16const 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]);
57function 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 /* GET */:
73 {
74 returnValue = await rawValue;
75 }
76 break;
77 case 1 /* SET */:
78 {
79 parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
80 returnValue = true;
81 }
82 break;
83 case 2 /* APPLY */:
84 {
85 returnValue = await rawValue.apply(parent, argumentList);
86 }
87 break;
88 case 3 /* CONSTRUCT */:
89 {
90 const value = await new rawValue(...argumentList);
91 returnValue = proxy(value);
92 }
93 break;
94 case 4 /* ENDPOINT */:
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}
116function wrap(ep) {
117 return createProxy(ep);
118}
119function 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 /* GET */,
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 // FIXME: ES6 Proxy Handler `set` methods are supposed to return a
136 // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
137 const [value, transferables] = toWireValue(rawValue);
138 return requestResponseMessage(ep, {
139 type: 1 /* SET */,
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 /* ENDPOINT */
149 }).then(fromWireValue);
150 }
151 // We just pretend that `bind()` didn’t happen.
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 /* APPLY */,
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 /* CONSTRUCT */,
166 path: path.map(p => p.toString()),
167 argumentList
168 }, transferables).then(fromWireValue);
169 }
170 });
171 return proxy;
172}
173function myFlat(arr) {
174 return Array.prototype.concat.apply([], arr);
175}
176function processArguments(argumentList) {
177 const processed = argumentList.map(toWireValue);
178 return [processed.map(v => v[0]), myFlat(processed.map(v => v[1]))];
179}
180const transferCache = new WeakMap();
181function transfer(obj, transfers) {
182 transferCache.set(obj, transfers);
183 return obj;
184}
185function proxy(obj) {
186 return Object.assign(obj, { [proxyMarker]: true });
187}
188function 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}
195function 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 /* HANDLER */,
202 name,
203 value: serializedValue
204 },
205 transferables
206 ];
207 }
208 }
209 return [
210 {
211 type: 0 /* RAW */,
212 value
213 },
214 transferCache.get(value) || []
215 ];
216}
217function fromWireValue(value) {
218 switch (value.type) {
219 case 3 /* HANDLER */:
220 return transferHandlers.get(value.name).deserialize(value.value);
221 case 0 /* RAW */:
222 return value.value;
223 }
224}
225function 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}
241function 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
248export { createEndpoint, expose, proxy, proxyMarker, transfer, transferHandlers, windowEndpoint, wrap };
249//# sourceMappingURL=comlink.mjs.map