Throw in openpgp.initWorker if worker failed to load

This commit is contained in:
Daniel Huigens 2020-02-19 14:34:54 +01:00
parent 29f29f6c6e
commit 9394fec1f4
10 changed files with 111 additions and 89 deletions

View File

@ -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;
}
/**

View File

@ -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<Object>} 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<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);
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 };
});
};

View File

@ -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' });

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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) });
});
});