Don't attempt to use workers if they fail to load

This commit is contained in:
Daniel Huigens 2019-05-01 22:03:37 +02:00
parent ecc8ae2a09
commit 34e6eacb2f
9 changed files with 69 additions and 32 deletions

View File

@ -119,7 +119,7 @@ Here are some examples of how to use the v2.x+ API. For more elaborate examples
```js ```js
var openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp var openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp
openpgp.initWorker({ path:'openpgp.worker.js' }) // set the relative web worker path await openpgp.initWorker({ path:'openpgp.worker.js' }) // set the relative web worker path
``` ```
#### Encrypt and decrypt *Uint8Array* data with a password #### Encrypt and decrypt *Uint8Array* data with a password
@ -159,7 +159,7 @@ Encryption will use the algorithm preferred by the public key (defaults to aes25
```js ```js
const openpgp = require('openpgp') // use as CommonJS, AMD, ES6 module or via window.openpgp const openpgp = require('openpgp') // use as CommonJS, AMD, ES6 module or via window.openpgp
openpgp.initWorker({ path:'openpgp.worker.js' }) // set the relative web worker path await openpgp.initWorker({ path:'openpgp.worker.js' }) // set the relative web worker path
// put keys in backtick (``) to avoid errors caused by spaces or tabs // put keys in backtick (``) to avoid errors caused by spaces or tabs
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----

View File

@ -63,12 +63,19 @@ let asyncProxy; // instance of the asyncproxy
* @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js' * @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js'
* @param {Number} n number of workers to initialize * @param {Number} n number of workers to initialize
* @param {Array<Object>} workers alternative to path parameter: web workers initialized with 'openpgp.worker.js' * @param {Array<Object>} workers alternative to path parameter: web workers initialized with 'openpgp.worker.js'
* @returns {Promise<Boolean>} returns a promise that resolves to true if all workers have succesfully finished loading
* @async
*/ */
export function initWorker({ path='openpgp.worker.js', n = 1, workers = [] } = {}) { export async function initWorker({ path='openpgp.worker.js', n = 1, workers = [] } = {}) {
if (workers.length || (typeof window !== 'undefined' && window.Worker && window.MessageChannel)) { if (workers.length || (typeof window !== 'undefined' && window.Worker && window.MessageChannel)) {
asyncProxy = new AsyncProxy({ path, n, workers, config }); const proxy = new AsyncProxy({ path, n, workers, config });
return true; const loaded = await proxy.loaded();
if (loaded) {
asyncProxy = proxy;
return true;
}
} }
return false;
} }
/** /**

View File

@ -49,6 +49,9 @@ function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } =
const handleMessage = workerId => event => { const handleMessage = workerId => event => {
const msg = event.data; const msg = event.data;
switch (msg.event) { switch (msg.event) {
case 'loaded':
this.workers[workerId].loadedResolve(true);
break;
case 'method-return': case 'method-return':
if (msg.err) { if (msg.err) {
// fail // fail
@ -83,10 +86,15 @@ function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } =
let workerId = 0; let workerId = 0;
this.workers.forEach(worker => { this.workers.forEach(worker => {
worker.loadedPromise = new Promise(resolve => {
worker.loadedResolve = resolve;
});
worker.requests = 0; worker.requests = 0;
worker.onmessage = handleMessage(workerId++); worker.onmessage = handleMessage(workerId++);
worker.onerror = e => { worker.onerror = e => {
throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'); worker.loadedResolve(false);
console.error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')');
return false;
}; };
if (config) { if (config) {
@ -99,6 +107,15 @@ function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } =
this.currentID = 0; this.currentID = 0;
} }
/**
* Returns a promise that resolves when all workers have finished loading
* @returns {Promise<Boolean>} 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);
};
/** /**
* Get new request ID * Get new request ID
* @returns {integer} New unique request ID * @returns {integer} New unique request ID

View File

@ -135,3 +135,8 @@ function delegate(id, method, options) {
function response(event) { function response(event) {
self.postMessage(event, openpgp.util.getTransferables(event.data, true)); self.postMessage(event, openpgp.util.getTransferables(event.data, true));
} }
/**
* Let the main window know the worker has loaded.
*/
postMessage({ event: 'loaded' });

View File

@ -273,8 +273,8 @@ describe('Brainpool Cryptography', function () {
tryTests('Brainpool Worker Tests', omnibus, { tryTests('Brainpool Worker Tests', omnibus, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: function() { before: async function() {
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;

View File

@ -297,8 +297,8 @@ describe('Elliptic Curve Cryptography', function () {
tryTests('ECC Worker Tests', omnibus, { tryTests('ECC Worker Tests', omnibus, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: function() { before: async function() {
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;

View File

@ -444,13 +444,16 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
openpgp.destroyWorker(); // cleanup worker in case of failure openpgp.destroyWorker(); // cleanup worker in case of failure
}); });
it('should work', function() { it('should work', async function() {
const workerStub = { const workerStub = {
postMessage: function() {} postMessage: function() {}
}; };
openpgp.initWorker({ await Promise.all([
workers: [workerStub] openpgp.initWorker({
}); workers: [workerStub]
}),
workerStub.onmessage({ data: { event: 'loaded' } })
]);
expect(openpgp.getWorker()).to.exist; expect(openpgp.getWorker()).to.exist;
openpgp.destroyWorker(); openpgp.destroyWorker();
expect(openpgp.getWorker()).to.not.exist; expect(openpgp.getWorker()).to.not.exist;
@ -595,13 +598,16 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
}); });
}); });
it('should delegate to async proxy', function() { it('should delegate to async proxy', async function() {
const workerStub = { const workerStub = {
postMessage: function() {} postMessage: function() {}
}; };
openpgp.initWorker({ await Promise.all([
workers: [workerStub] openpgp.initWorker({
}); workers: [workerStub]
}),
workerStub.onmessage({ data: { event: 'loaded' } })
]);
const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); const proxyGenStub = stub(openpgp.getWorker(), 'delegate');
getWebCryptoAllStub.returns(); getWebCryptoAllStub.returns();
@ -643,9 +649,9 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
}); });
}); });
it('should work in JS (with worker)', function() { it('should work in JS (with worker)', async function() {
openpgp.config.use_native = false; openpgp.config.use_native = false;
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
const opt = { const opt = {
userIds: [{ name: 'Test User', email: 'text@example.com' }], userIds: [{ name: 'Test User', email: 'text@example.com' }],
numBits: 512 numBits: 512
@ -727,11 +733,11 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal;
}); });
it('Configuration', function() { it('Configuration', async function() {
openpgp.config.show_version = false; openpgp.config.show_version = false;
openpgp.config.commentstring = 'different'; openpgp.config.commentstring = 'different';
if (openpgp.getWorker()) { // init again to trigger config event if (openpgp.getWorker()) { // init again to trigger config event
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
} }
return openpgp.encrypt({ publicKeys:publicKey.keys, message:openpgp.message.fromText(plaintext) }).then(function(encrypted) { return openpgp.encrypt({ publicKeys:publicKey.keys, message:openpgp.message.fromText(plaintext) }).then(function(encrypted) {
expect(encrypted.data).to.exist; expect(encrypted.data).to.exist;
@ -749,7 +755,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
const { workers } = openpgp.getWorker(); const { workers } = openpgp.getWorker();
try { try {
await privateKey.keys[0].decrypt(passphrase) await privateKey.keys[0].decrypt(passphrase)
openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2}); await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2});
const workerTest = (_, index) => { const workerTest = (_, index) => {
const plaintext = input.createSomeMessage() + index; const plaintext = input.createSomeMessage() + index;
@ -770,7 +776,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
}; };
await Promise.all(Array(10).fill(null).map(workerTest)); await Promise.all(Array(10).fill(null).map(workerTest));
} finally { } finally {
openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 }); await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 });
} }
}); });
@ -828,8 +834,8 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
tryTests('CFB mode (asm.js, worker)', tests, { tryTests('CFB mode (asm.js, worker)', tests, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: function() { before: async function() {
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.aead_protect = false; openpgp.config.aead_protect = false;

View File

@ -319,8 +319,8 @@ describe('X25519 Cryptography', function () {
tryTests('X25519 Worker Tests', omnibus, { tryTests('X25519 Worker Tests', omnibus, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: function() { before: async function() {
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;

View File

@ -37,7 +37,7 @@ let pubKey;
tryTests('Async Proxy', tests, { tryTests('Async Proxy', tests, {
if: typeof window !== 'undefined' && window.Worker && window.MessageChannel, if: typeof window !== 'undefined' && window.Worker && window.MessageChannel,
before: async function() { before: async function() {
openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
}, },
after: function() { after: function() {
@ -48,11 +48,13 @@ tryTests('Async Proxy', tests, {
function tests() { function tests() {
describe('Random number pipeline', function() { describe('Random number pipeline', function() {
it('Random number buffer automatically reseeded', function() { it('Random number buffer automatically reseeded', async function() {
const worker = new Worker('../dist/openpgp.worker.js'); const worker = new Worker('../dist/openpgp.worker.js');
const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] }); const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] });
const loaded = await wProxy.loaded();
return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) }); if (loaded) {
return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) });
}
}); });
}); });