1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | (function (factory) {
|
14 | if (typeof module === "object" && typeof module.exports === "object") {
|
15 | var v = factory(require, exports);
|
16 | if (v !== undefined) module.exports = v;
|
17 | }
|
18 | else if (typeof define === "function" && define.amd) {
|
19 | define(["require", "exports"], factory);
|
20 | }
|
21 | else {factory([], self.Comlink={});}
|
22 | })(function (require, exports) {
|
23 | "use strict";
|
24 | Object.defineProperty(exports, "__esModule", { value: true });
|
25 | |
26 |
|
27 |
|
28 | exports.proxyValueSymbol = Symbol("comlinkProxyValue");
|
29 | |
30 |
|
31 |
|
32 | const isProxyValue = (value) => !!value && value[exports.proxyValueSymbol] === true;
|
33 | const TRANSFERABLE_TYPES = ["ArrayBuffer", "MessagePort", "OffscreenCanvas"]
|
34 | .filter(f => f in self)
|
35 | .map(f => self[f]);
|
36 | const uid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
37 | const throwSymbol = Symbol("throw");
|
38 | const proxyTransferHandler = {
|
39 | canHandle: isProxyValue,
|
40 | serialize: (obj) => {
|
41 | const { port1, port2 } = new MessageChannel();
|
42 | expose(obj, port1);
|
43 | return port2;
|
44 | },
|
45 | deserialize: (obj) => {
|
46 | return proxy(obj);
|
47 | }
|
48 | };
|
49 | const throwTransferHandler = {
|
50 | canHandle: (obj) => obj && obj[throwSymbol],
|
51 | serialize: (obj) => {
|
52 | const message = obj && obj.message;
|
53 | const stack = obj && obj.stack;
|
54 | return Object.assign({}, obj, { message, stack });
|
55 | },
|
56 | deserialize: (obj) => {
|
57 | throw Object.assign(Error(), obj);
|
58 | }
|
59 | };
|
60 | exports.transferHandlers = new Map([
|
61 | ["PROXY", proxyTransferHandler],
|
62 | ["THROW", throwTransferHandler]
|
63 | ]);
|
64 | let pingPongMessageCounter = 0;
|
65 | function proxy(endpoint, target) {
|
66 | if (isWindow(endpoint))
|
67 | endpoint = windowEndpoint(endpoint);
|
68 | if (!isEndpoint(endpoint))
|
69 | throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined");
|
70 | activateEndpoint(endpoint);
|
71 | return cbProxy(async (irequest) => {
|
72 | let args = [];
|
73 | if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT")
|
74 | args = irequest.argumentsList.map(wrapValue);
|
75 | const response = await pingPongMessage(endpoint, Object.assign({}, irequest, { argumentsList: args }), transferableProperties(args));
|
76 | const result = response.data;
|
77 | return unwrapValue(result.value);
|
78 | }, [], target);
|
79 | }
|
80 | exports.proxy = proxy;
|
81 | function proxyValue(obj) {
|
82 | const proxyVal = obj;
|
83 | proxyVal[exports.proxyValueSymbol] = true;
|
84 | return proxyVal;
|
85 | }
|
86 | exports.proxyValue = proxyValue;
|
87 | function expose(rootObj, endpoint) {
|
88 | if (isWindow(endpoint))
|
89 | endpoint = windowEndpoint(endpoint);
|
90 | if (!isEndpoint(endpoint))
|
91 | throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined");
|
92 | activateEndpoint(endpoint);
|
93 | attachMessageHandler(endpoint, async function (event) {
|
94 | if (!event.data.id || !event.data.callPath)
|
95 | return;
|
96 | const irequest = event.data;
|
97 | let that = await irequest.callPath
|
98 | .slice(0, -1)
|
99 | .reduce((obj, propName) => obj[propName], rootObj);
|
100 | let obj = await irequest.callPath.reduce((obj, propName) => obj[propName], rootObj);
|
101 | let iresult = obj;
|
102 | let args = [];
|
103 | if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT")
|
104 | args = irequest.argumentsList.map(unwrapValue);
|
105 | if (irequest.type === "APPLY") {
|
106 | try {
|
107 | iresult = await obj.apply(that, args);
|
108 | }
|
109 | catch (e) {
|
110 | iresult = e;
|
111 | iresult[throwSymbol] = true;
|
112 | }
|
113 | }
|
114 | if (irequest.type === "CONSTRUCT") {
|
115 | try {
|
116 | iresult = new obj(...args);
|
117 | iresult = proxyValue(iresult);
|
118 | }
|
119 | catch (e) {
|
120 | iresult = e;
|
121 | iresult[throwSymbol] = true;
|
122 | }
|
123 | }
|
124 | if (irequest.type === "SET") {
|
125 | obj[irequest.property] = irequest.value;
|
126 |
|
127 |
|
128 | iresult = true;
|
129 | }
|
130 | iresult = makeInvocationResult(iresult);
|
131 | iresult.id = irequest.id;
|
132 | return endpoint.postMessage(iresult, transferableProperties([iresult]));
|
133 | });
|
134 | }
|
135 | exports.expose = expose;
|
136 | function wrapValue(arg) {
|
137 |
|
138 | for (const [key, transferHandler] of exports.transferHandlers) {
|
139 | if (transferHandler.canHandle(arg)) {
|
140 | return {
|
141 | type: key,
|
142 | value: transferHandler.serialize(arg)
|
143 | };
|
144 | }
|
145 | }
|
146 |
|
147 | let wrappedChildren = [];
|
148 | for (const item of iterateAllProperties(arg)) {
|
149 | for (const [key, transferHandler] of exports.transferHandlers) {
|
150 | if (transferHandler.canHandle(item.value)) {
|
151 | wrappedChildren.push({
|
152 | path: item.path,
|
153 | wrappedValue: {
|
154 | type: key,
|
155 | value: transferHandler.serialize(item.value)
|
156 | }
|
157 | });
|
158 | }
|
159 | }
|
160 | }
|
161 | for (const wrappedChild of wrappedChildren) {
|
162 | const container = wrappedChild.path
|
163 | .slice(0, -1)
|
164 | .reduce((obj, key) => obj[key], arg);
|
165 | container[wrappedChild.path[wrappedChild.path.length - 1]] = null;
|
166 | }
|
167 | return {
|
168 | type: "RAW",
|
169 | value: arg,
|
170 | wrappedChildren
|
171 | };
|
172 | }
|
173 | function unwrapValue(arg) {
|
174 | if (exports.transferHandlers.has(arg.type)) {
|
175 | const transferHandler = exports.transferHandlers.get(arg.type);
|
176 | return transferHandler.deserialize(arg.value);
|
177 | }
|
178 | else if (isRawWrappedValue(arg)) {
|
179 | for (const wrappedChildValue of arg.wrappedChildren || []) {
|
180 | if (!exports.transferHandlers.has(wrappedChildValue.wrappedValue.type))
|
181 | throw Error(`Unknown value type "${arg.type}" at ${wrappedChildValue.path.join(".")}`);
|
182 | const transferHandler = exports.transferHandlers.get(wrappedChildValue.wrappedValue.type);
|
183 | const newValue = transferHandler.deserialize(wrappedChildValue.wrappedValue.value);
|
184 | replaceValueInObjectAtPath(arg.value, wrappedChildValue.path, newValue);
|
185 | }
|
186 | return arg.value;
|
187 | }
|
188 | else {
|
189 | throw Error(`Unknown value type "${arg.type}"`);
|
190 | }
|
191 | }
|
192 | function replaceValueInObjectAtPath(obj, path, newVal) {
|
193 | const lastKey = path.slice(-1)[0];
|
194 | const lastObj = path
|
195 | .slice(0, -1)
|
196 | .reduce((obj, key) => obj[key], obj);
|
197 | lastObj[lastKey] = newVal;
|
198 | }
|
199 | function isRawWrappedValue(arg) {
|
200 | return arg.type === "RAW";
|
201 | }
|
202 | function windowEndpoint(w) {
|
203 | if (self.constructor.name !== "Window")
|
204 | throw Error("self is not a window");
|
205 | return {
|
206 | addEventListener: self.addEventListener.bind(self),
|
207 | removeEventListener: self.removeEventListener.bind(self),
|
208 | postMessage: (msg, transfer) => w.postMessage(msg, "*", transfer)
|
209 | };
|
210 | }
|
211 | function isEndpoint(endpoint) {
|
212 | return ("addEventListener" in endpoint &&
|
213 | "removeEventListener" in endpoint &&
|
214 | "postMessage" in endpoint);
|
215 | }
|
216 | function activateEndpoint(endpoint) {
|
217 | if (isMessagePort(endpoint))
|
218 | endpoint.start();
|
219 | }
|
220 | function attachMessageHandler(endpoint, f) {
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 | endpoint.addEventListener("message", f);
|
231 | }
|
232 | function detachMessageHandler(endpoint, f) {
|
233 |
|
234 | endpoint.removeEventListener("message", f);
|
235 | }
|
236 | function isMessagePort(endpoint) {
|
237 | return endpoint.constructor.name === "MessagePort";
|
238 | }
|
239 | function isWindow(endpoint) {
|
240 |
|
241 |
|
242 | return ["window", "length", "location", "parent", "opener"].every(prop => prop in endpoint);
|
243 | }
|
244 | |
245 |
|
246 |
|
247 |
|
248 | function pingPongMessage(endpoint, msg, transferables) {
|
249 | const id = `${uid}-${pingPongMessageCounter++}`;
|
250 | return new Promise(resolve => {
|
251 | attachMessageHandler(endpoint, function handler(event) {
|
252 | if (event.data.id !== id)
|
253 | return;
|
254 | detachMessageHandler(endpoint, handler);
|
255 | resolve(event);
|
256 | });
|
257 |
|
258 | msg = Object.assign({}, msg, { id });
|
259 | endpoint.postMessage(msg, transferables);
|
260 | });
|
261 | }
|
262 | function cbProxy(cb, callPath = [], target = function () { }) {
|
263 | return new Proxy(target, {
|
264 | construct(_target, argumentsList, proxy) {
|
265 | return cb({
|
266 | type: "CONSTRUCT",
|
267 | callPath,
|
268 | argumentsList
|
269 | });
|
270 | },
|
271 | apply(_target, _thisArg, argumentsList) {
|
272 |
|
273 |
|
274 | if (callPath[callPath.length - 1] === "bind")
|
275 | return cbProxy(cb, callPath.slice(0, -1));
|
276 | return cb({
|
277 | type: "APPLY",
|
278 | callPath,
|
279 | argumentsList
|
280 | });
|
281 | },
|
282 | get(_target, property, proxy) {
|
283 | if (property === "then" && callPath.length === 0) {
|
284 | return { then: () => proxy };
|
285 | }
|
286 | else if (property === "then") {
|
287 | const r = cb({
|
288 | type: "GET",
|
289 | callPath
|
290 | });
|
291 | return Promise.resolve(r).then.bind(r);
|
292 | }
|
293 | else {
|
294 | return cbProxy(cb, callPath.concat(property), _target[property]);
|
295 | }
|
296 | },
|
297 | set(_target, property, value, _proxy) {
|
298 | return cb({
|
299 | type: "SET",
|
300 | callPath,
|
301 | property,
|
302 | value
|
303 | });
|
304 | }
|
305 | });
|
306 | }
|
307 | function isTransferable(thing) {
|
308 | return TRANSFERABLE_TYPES.some(type => thing instanceof type);
|
309 | }
|
310 | function* iterateAllProperties(value, path = [], visited = null) {
|
311 | if (!value)
|
312 | return;
|
313 | if (!visited)
|
314 | visited = new WeakSet();
|
315 | if (visited.has(value))
|
316 | return;
|
317 | if (typeof value === "string")
|
318 | return;
|
319 | if (typeof value === "object")
|
320 | visited.add(value);
|
321 | if (ArrayBuffer.isView(value))
|
322 | return;
|
323 | yield { value, path };
|
324 | const keys = Object.keys(value);
|
325 | for (const key of keys)
|
326 | yield* iterateAllProperties(value[key], [...path, key], visited);
|
327 | }
|
328 | function transferableProperties(obj) {
|
329 | const r = [];
|
330 | for (const prop of iterateAllProperties(obj)) {
|
331 | if (isTransferable(prop.value))
|
332 | r.push(prop.value);
|
333 | }
|
334 | return r;
|
335 | }
|
336 | function makeInvocationResult(obj) {
|
337 | for (const [type, transferHandler] of exports.transferHandlers) {
|
338 | if (transferHandler.canHandle(obj)) {
|
339 | const value = transferHandler.serialize(obj);
|
340 | return {
|
341 | value: { type, value }
|
342 | };
|
343 | }
|
344 | }
|
345 | return {
|
346 | value: {
|
347 | type: "RAW",
|
348 | value: obj
|
349 | }
|
350 | };
|
351 | }
|
352 | });
|