import serializeError from 'serialize-error'; import { parse, stringify } from './serializeBinary'; const SUBTLE_METHODS = [ 'encrypt', 'decrypt', 'sign', 'verify', 'digest', 'generateKey', 'deriveKey', 'deriveBits', 'importKey', 'exportKey', 'wrapKey', 'unwrapKey', ]; /* MainWorker provides a `crypto` attribute that proxies method calls to the webview. It sends strings to the webview in the format: { id: , method: getRandomValues | subtle., args: [] } When the webview succeeds in completeing that method, it gets backs: { id: , value: } And when it fails: { id: , reason: , } */ export default class MainWorker { // sendToWebView should take a string and send that message to the webview constructor(sendToWebView, debug = false) { this.sendToWebView = sendToWebView; this.debug = debug; // hold a queue of messages to send, in case someone calls crypto // before the webview is initialized this.toSend = []; this.readyToSend = false; // Holds the `resolve` and `reject` function for all the promises // we are working on this.messages = {}; } get crypto() { const callMethod = this.callMethod; return { subtle: this.subtle, getRandomValues: this.getRandomValues.bind(this), fake: true, }; } get subtle() { const s = {}; for (const m of SUBTLE_METHODS) { s[m] = (...args) => { return this.callMethod(`subtle.${m}`, args, true); }; } return s; } // http://stackoverflow.com/a/105074/907060 static uuid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return `${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}`; } onWebViewMessage(message) { // first message just tells us the webview is ready if (!this.readyToSend) { if (this.debug) { console.log('[webview-crypto] Got first message; ready to send'); } this.readyToSend = true; for (const m of this.toSend) { this.sendToWebView(m); } return; } parse(message) .then(({ id, value, reason }) => { if (this.debug) { console.log('[webview-crypto] Received message:', JSON.stringify({ id, value, reason, })); } if (!id) { console.warn('[webview-crypto] no ID passed back from message:', JSON.stringify(serializeError(reason))); return; } const { resolve, reject } = this.messages[id]; if (value) { resolve(value); } else { reject(reason); } delete this.messages[id]; }) .catch((reason) => { console.warn('[webview-crypto] error in `parse` of message:', JSON.stringify(message), 'reason:', JSON.stringify(serializeError(reason))); }); } getRandomValues(array) { const promise = this.callMethod('getRandomValues', [array], false); // make the _promise not enumerable so it isn't JSON stringified, // which could lead to an infinite loop with Angular's zone promises Object.defineProperty(array, '_promise', { value: promise, configurable: true, enumerable: false, writable: true, }); promise.then((updatedArray) => { array.set(updatedArray); }); return array; } callMethod(method, args, waitForArrayBufferView) { const id = MainWorker.uuid(); // store this promise, so we can resolve it when we get a message // back from the web view const promise = new Promise((resolve, reject) => { this.messages[id] = { resolve, reject }; }); const payloadObject = { method, id, args }; if (this.debug) { console.log('[webview-crypto] Sending message:', JSON.stringify({ method, args, payloadObject, })); } stringify(payloadObject, waitForArrayBufferView) .then((message) => { if (this.readyToSend) { this.sendToWebView(message); } else { this.toSend.push(message); } }) .catch((reason) => { this.messages[id].reject({ message: `exception in stringify-ing message: ${method} ${id}`, reason, }); delete this.messages[id]; }); return promise; } } //# sourceMappingURL=MainWorker.js.map