From 9394fec1f4f3c35ac284687e14d963691bf5ae8e Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 19 Feb 2020 14:34:54 +0100 Subject: [PATCH] Throw in openpgp.initWorker if worker failed to load --- src/openpgp.js | 12 +++--- src/worker/async_proxy.js | 88 ++++++++++++++++---------------------- src/worker/worker.js | 17 +++----- test/general/brainpool.js | 6 ++- test/general/ecc_nist.js | 6 ++- test/general/key.js | 10 +++-- test/general/openpgp.js | 32 ++++++++++---- test/general/signature.js | 6 ++- test/general/x25519.js | 6 ++- test/worker/async_proxy.js | 17 +++++--- 10 files changed, 111 insertions(+), 89 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index 2d6b54af..8d0002fb 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -76,14 +76,12 @@ let asyncProxy; // instance of the asyncproxy */ export async function initWorker({ path = 'openpgp.worker.js', n = 1, workers = [] } = {}) { if (workers.length || (typeof global !== 'undefined' && global.Worker && global.MessageChannel)) { - const proxy = new AsyncProxy({ path, n, workers, config }); - const loaded = await proxy.loaded(); - if (loaded) { - asyncProxy = proxy; - return true; - } + const proxy = new AsyncProxy(); + await proxy.init({ path, n, workers, config }); + asyncProxy = proxy; + } else { + throw new Error('Web Workers are not available'); } - return false; } /** diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 7a01e9da..286919d8 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -35,23 +35,25 @@ import packet from '../packet'; /** - * Initializes a new proxy and loads the web worker + * Creates a new async proxy + * @constructor + */ +function AsyncProxy() {} + +/** + * Initializes the proxy and loads the web worker * @param {String} path The path to the worker or 'openpgp.worker.js' by default * @param {Number} n number of workers to initialize if path given * @param {Object} config config The worker configuration * @param {Array} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' - * @constructor */ -function AsyncProxy({ path = 'openpgp.worker.js', n = 1, workers = [], config } = {}) { +AsyncProxy.prototype.init = async function({ path = 'openpgp.worker.js', n = 1, workers = [], config } = {}) { /** * Message handling */ const handleMessage = workerId => event => { const msg = event.data; switch (msg.event) { - case 'loaded': - this.workers[workerId].loadedResolve(true); - break; case 'method-return': if (msg.err) { // fail @@ -84,37 +86,21 @@ function AsyncProxy({ path = 'openpgp.worker.js', n = 1, workers = [], config } } } - let workerId = 0; - this.workers.forEach(worker => { - worker.loadedPromise = new Promise(resolve => { - worker.loadedResolve = resolve; - }); - worker.requests = 0; - worker.onmessage = handleMessage(workerId++); - worker.onerror = e => { - worker.loadedResolve(false); - // eslint-disable-next-line no-console - console.error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'); - return false; - }; - - if (config) { - worker.postMessage({ event:'configure', config }); - } - }); - // Cannot rely on task order being maintained, use object keyed by request ID to track tasks this.tasks = {}; this.currentID = 0; -} -/** - * Returns a promise that resolves when all workers have finished loading - * @returns {Promise} Resolves to true if all workers have loaded succesfully; false otherwise -*/ -AsyncProxy.prototype.loaded = async function() { - const loaded = await Promise.all(this.workers.map(worker => worker.loadedPromise)); - return loaded.every(Boolean); + let workerId = 0; + await Promise.all(this.workers.map(worker => new Promise(async (resolve, reject) => { + worker.requests = 0; + worker.onmessage = handleMessage(workerId++); + worker.onerror = e => { + reject(new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')')); + }; + + await this.callWorker(worker, 'configure', config); + resolve(); + }))); }; /** @@ -132,7 +118,7 @@ AsyncProxy.prototype.getID = function() { */ AsyncProxy.prototype.seedRandom = async function(workerId, size) { const buf = await crypto.random.getRandomBytes(size); - this.workers[workerId].postMessage({ event:'seed-random', buf }, util.getTransferables(buf, true)); + this.workers[workerId].postMessage({ event: 'seed-random', buf }, util.getTransferables(buf, true)); }; /** @@ -140,13 +126,7 @@ AsyncProxy.prototype.seedRandom = async function(workerId, size) { * @async */ AsyncProxy.prototype.clearKeyCache = async function() { - await Promise.all(this.workers.map(worker => new Promise((resolve, reject) => { - const id = this.getID(); - - worker.postMessage({ id, event: 'clear-key-cache' }); - - this.tasks[id] = { resolve, reject }; - }))); + await Promise.all(this.workers.map(worker => this.callWorker(worker, 'clear-key-cache'))); }; /** @@ -165,9 +145,7 @@ AsyncProxy.prototype.terminate = function() { * @returns {Promise} see the corresponding public api functions for their return types * @async */ -AsyncProxy.prototype.delegate = function(method, options) { - - const id = this.getID(); +AsyncProxy.prototype.delegate = async function (method, options) { const requests = this.workers.map(worker => worker.requests); const minRequests = Math.min(...requests); let workerId = 0; @@ -177,15 +155,21 @@ AsyncProxy.prototype.delegate = function(method, options) { } } - return new Promise((resolve, reject) => { - const data = { id, event: method, options: packet.clone.clonePackets(options) }; - const transferables = util.getTransferables(data, config.zero_copy); - // clone packets (for web worker structured cloning algorithm) - this.workers[workerId].postMessage(data, transferables); - this.workers[workerId].requests++; + const data = { options: packet.clone.clonePackets(options) }; + const transferables = util.getTransferables(data, config.zero_copy); + const worker = this.workers[workerId]; + worker.requests++; + const result = await this.callWorker(worker, method, data.options, transferables); + return packet.clone.parseClonedPackets(util.restoreStreams(result, options.streaming), method); +}; - // remember to handle parsing cloned packets from worker - this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(util.restoreStreams(data, options.streaming), method)), reject }; +AsyncProxy.prototype.callWorker = function(worker, method, options, transferables) { + return new Promise((resolve, reject) => { + const id = this.getID(); + + worker.postMessage({ id, method, options }, transferables); + + this.tasks[id] = { resolve, reject }; }); }; diff --git a/src/worker/worker.js b/src/worker/worker.js index f1a576e0..965db429 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -58,11 +58,7 @@ openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER, randomCallback); self.onmessage = function(event) { var msg = event.data || {}; - switch (msg.event) { - case 'configure': - configure(msg.config); - break; - + switch (msg.method) { case 'seed-random': seedRandom(msg.buf); @@ -75,7 +71,7 @@ self.onmessage = function(event) { break; default: - delegate(msg.id, msg.event, msg.options || {}); + delegate(msg.id, msg.method, msg.options || {}); } }; @@ -117,6 +113,11 @@ function getCachedKey(key) { * @param {Object} options The api function's options */ function delegate(id, method, options) { + if (method === 'configure') { + configure(options); + response({ id, event: 'method-return' }); + return; + } if (method === 'clear-key-cache') { Array.from(keyCache.values()).forEach(key => { if (key.isPrivate()) { @@ -161,7 +162,3 @@ function response(event) { self.postMessage(event, openpgp.util.getTransferables(event.data, openpgp.config.zero_copy)); } -/** - * Let the main window know the worker has loaded. - */ -postMessage({ event: 'loaded' }); diff --git a/test/general/brainpool.js b/test/general/brainpool.js index 9b4ae6b2..4d01a18a 100644 --- a/test/general/brainpool.js +++ b/test/general/brainpool.js @@ -347,7 +347,11 @@ tryTests('Brainpool Omnibus Tests @lightweight', omnibus, { tryTests('Brainpool Omnibus Tests - Worker @lightweight', omnibus, { if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()), before: async function() { - await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, beforeEach: function() { openpgp.config.use_native = true; diff --git a/test/general/ecc_nist.js b/test/general/ecc_nist.js index 533b6786..0fa53544 100644 --- a/test/general/ecc_nist.js +++ b/test/general/ecc_nist.js @@ -101,7 +101,11 @@ describe('Elliptic Curve Cryptography for NIST P-256,P-384,P-521 curves @lightwe tryTests('ECC Worker Tests', omnibus, { if: typeof window !== 'undefined' && window.Worker, before: async function() { - await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, beforeEach: function() { openpgp.config.use_native = true; diff --git a/test/general/key.js b/test/general/key.js index 1f0368f8..12759c81 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2005,7 +2005,7 @@ function versionSpecificTests() { openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm; if (openpgp.getWorker()) { openpgp.getWorker().workers.forEach(worker => { - worker.postMessage({ event: 'configure', config: openpgp.config }); + openpgp.getWorker().callWorker(worker, 'configure', openpgp.config); }); } @@ -2041,7 +2041,7 @@ function versionSpecificTests() { openpgp.config.aead_mode = aead_modeVal; if (openpgp.getWorker()) { openpgp.getWorker().workers.forEach(worker => { - worker.postMessage({ event: 'configure', config: openpgp.config }); + openpgp.getWorker().callWorker(worker, 'configure', openpgp.config); }); } } @@ -2545,7 +2545,11 @@ describe('Key', function() { tryTests('V4 - With Worker', versionSpecificTests, { if: typeof window !== 'undefined' && window.Worker, before: async function() { - await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, after: function() { openpgp.destroyWorker(); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 86a6aeed..52f0238f 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -516,7 +516,7 @@ describe('OpenPGP.js public api tests', function() { openpgp.initWorker({ workers: [workerStub] }), - workerStub.onmessage({ data: { event: 'loaded' } }) + workerStub.onmessage({ data: { id: 0, event: 'method-return' } }) ]); expect(openpgp.getWorker()).to.exist; openpgp.destroyWorker(); @@ -671,7 +671,7 @@ describe('OpenPGP.js public api tests', function() { openpgp.initWorker({ workers: [workerStub] }), - workerStub.onmessage({ data: { event: 'loaded' } }) + workerStub.onmessage({ data: { id: 0, event: 'method-return' } }) ]); const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); getWebCryptoAllStub.returns(); @@ -716,7 +716,11 @@ describe('OpenPGP.js public api tests', function() { it('should work in JS (with worker)', async function() { openpgp.config.use_native = false; - await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } const opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], numBits: 512 @@ -821,7 +825,11 @@ describe('OpenPGP.js public api tests', function() { const { workers } = openpgp.getWorker(); try { await privateKey.keys[0].decrypt(passphrase) - await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2}); + try { + await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2}); + } catch (e) { + openpgp.util.print_debug_error(e); + } const workerTest = (_, index) => { const plaintext = input.createSomeMessage() + index; @@ -842,7 +850,11 @@ describe('OpenPGP.js public api tests', function() { }; await Promise.all(Array(10).fill(null).map(workerTest)); } finally { - await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 }); + try { + await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 }); + } catch (e) { + openpgp.util.print_debug_error(e); + } } }); @@ -900,7 +912,11 @@ describe('OpenPGP.js public api tests', function() { tryTests('CFB mode (asm.js, worker)', tests, { if: typeof window !== 'undefined' && window.Worker, before: async function() { - await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, beforeEach: function() { openpgp.config.aead_protect = false; @@ -1641,7 +1657,7 @@ describe('OpenPGP.js public api tests', function() { openpgp.config.allow_unauthenticated_stream = !!allow_streaming; if (openpgp.getWorker()) { openpgp.getWorker().workers.forEach(worker => { - worker.postMessage({ event: 'configure', config: openpgp.config }); + openpgp.getWorker().callWorker(worker, 'configure', openpgp.config); }); } await Promise.all([badSumEncrypted, badBodyEncrypted].map(async (encrypted, i) => { @@ -1847,7 +1863,7 @@ describe('OpenPGP.js public api tests', function() { openpgp.config.zero_copy = false; if (openpgp.getWorker()) { openpgp.getWorker().workers.forEach(worker => { - worker.postMessage({ event: 'configure', config: openpgp.config }); + openpgp.getWorker().callWorker(worker, 'configure', openpgp.config); }); } return openpgp.decrypt(decOpt); diff --git a/test/general/signature.js b/test/general/signature.js index 734b9eac..9ef6a71a 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -1334,7 +1334,11 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA tryTests('With Worker', tests, { if: typeof window !== 'undefined' && window.Worker, before: async function() { - await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, after: function() { openpgp.destroyWorker(); diff --git a/test/general/x25519.js b/test/general/x25519.js index 670372ff..24fdadd3 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -545,7 +545,11 @@ tryTests('X25519 Omnibus Tests', omnibus, { tryTests('X25519 Omnibus Tests - Worker', omnibus, { if: typeof window !== 'undefined' && window.Worker, before: async function() { - await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } }, beforeEach: function() { openpgp.config.use_native = true; diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js index bedc9739..36990d9a 100644 --- a/test/worker/async_proxy.js +++ b/test/worker/async_proxy.js @@ -37,7 +37,11 @@ let pubKey; tryTests('Async Proxy', tests, { if: typeof window !== 'undefined' && window.Worker && window.MessageChannel, before: async function() { - await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + try { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } catch (e) { + openpgp.util.print_debug_error(e); + } pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; }, after: async function() { @@ -50,11 +54,14 @@ function tests() { describe('Random number pipeline', function() { it('Random number buffer automatically reseeded', async function() { const worker = new Worker('../dist/openpgp.worker.js'); - const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] }); - const loaded = await wProxy.loaded(); - if (loaded) { - return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) }); + const wProxy = new openpgp.AsyncProxy(); + try { + await wProxy.init({ path:'../dist/openpgp.worker.js', workers: [worker] }); + } catch (e) { + openpgp.util.print_debug_error(e); + return; } + return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) }); }); });