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 = [] } = {}) { export async function initWorker({ path = 'openpgp.worker.js', n = 1, workers = [] } = {}) {
if (workers.length || (typeof global !== 'undefined' && global.Worker && global.MessageChannel)) { if (workers.length || (typeof global !== 'undefined' && global.Worker && global.MessageChannel)) {
const proxy = new AsyncProxy({ path, n, workers, config }); const proxy = new AsyncProxy();
const loaded = await proxy.loaded(); await proxy.init({ path, n, workers, config });
if (loaded) {
asyncProxy = proxy; asyncProxy = proxy;
return true; } 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 {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 {Number} n number of workers to initialize if path given
* @param {Object} config config The worker configuration * @param {Object} config config The worker configuration
* @param {Array<Object>} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' * @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 * Message handling
*/ */
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
@ -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 // Cannot rely on task order being maintained, use object keyed by request ID to track tasks
this.tasks = {}; this.tasks = {};
this.currentID = 0; this.currentID = 0;
}
/** let workerId = 0;
* Returns a promise that resolves when all workers have finished loading await Promise.all(this.workers.map(worker => new Promise(async (resolve, reject) => {
* @returns {Promise<Boolean>} Resolves to true if all workers have loaded succesfully; false otherwise worker.requests = 0;
*/ worker.onmessage = handleMessage(workerId++);
AsyncProxy.prototype.loaded = async function() { worker.onerror = e => {
const loaded = await Promise.all(this.workers.map(worker => worker.loadedPromise)); reject(new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'));
return loaded.every(Boolean); };
await this.callWorker(worker, 'configure', config);
resolve();
})));
}; };
/** /**
@ -132,7 +118,7 @@ AsyncProxy.prototype.getID = function() {
*/ */
AsyncProxy.prototype.seedRandom = async function(workerId, size) { AsyncProxy.prototype.seedRandom = async function(workerId, size) {
const buf = await crypto.random.getRandomBytes(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 * @async
*/ */
AsyncProxy.prototype.clearKeyCache = async function() { AsyncProxy.prototype.clearKeyCache = async function() {
await Promise.all(this.workers.map(worker => new Promise((resolve, reject) => { await Promise.all(this.workers.map(worker => this.callWorker(worker, 'clear-key-cache')));
const id = this.getID();
worker.postMessage({ id, event: 'clear-key-cache' });
this.tasks[id] = { resolve, reject };
})));
}; };
/** /**
@ -165,9 +145,7 @@ AsyncProxy.prototype.terminate = function() {
* @returns {Promise} see the corresponding public api functions for their return types * @returns {Promise} see the corresponding public api functions for their return types
* @async * @async
*/ */
AsyncProxy.prototype.delegate = function(method, options) { AsyncProxy.prototype.delegate = async function (method, options) {
const id = this.getID();
const requests = this.workers.map(worker => worker.requests); const requests = this.workers.map(worker => worker.requests);
const minRequests = Math.min(...requests); const minRequests = Math.min(...requests);
let workerId = 0; let workerId = 0;
@ -177,15 +155,21 @@ AsyncProxy.prototype.delegate = function(method, options) {
} }
} }
return new Promise((resolve, reject) => { const data = { options: packet.clone.clonePackets(options) };
const data = { id, event: method, options: packet.clone.clonePackets(options) };
const transferables = util.getTransferables(data, config.zero_copy); const transferables = util.getTransferables(data, config.zero_copy);
// clone packets (for web worker structured cloning algorithm) const worker = this.workers[workerId];
this.workers[workerId].postMessage(data, transferables); worker.requests++;
this.workers[workerId].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 AsyncProxy.prototype.callWorker = function(worker, method, options, transferables) {
this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(util.restoreStreams(data, options.streaming), method)), reject }; 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) { self.onmessage = function(event) {
var msg = event.data || {}; var msg = event.data || {};
switch (msg.event) { switch (msg.method) {
case 'configure':
configure(msg.config);
break;
case 'seed-random': case 'seed-random':
seedRandom(msg.buf); seedRandom(msg.buf);
@ -75,7 +71,7 @@ self.onmessage = function(event) {
break; break;
default: 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 * @param {Object} options The api function's options
*/ */
function delegate(id, method, options) { function delegate(id, method, options) {
if (method === 'configure') {
configure(options);
response({ id, event: 'method-return' });
return;
}
if (method === 'clear-key-cache') { if (method === 'clear-key-cache') {
Array.from(keyCache.values()).forEach(key => { Array.from(keyCache.values()).forEach(key => {
if (key.isPrivate()) { if (key.isPrivate()) {
@ -161,7 +162,3 @@ function response(event) {
self.postMessage(event, openpgp.util.getTransferables(event.data, openpgp.config.zero_copy)); 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, { tryTests('Brainpool Omnibus Tests - Worker @lightweight', omnibus, {
if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()), if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()),
before: async function() { before: async function() {
try {
await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); await openpgp.initWorker({ path: '../dist/openpgp.worker.js' });
} catch (e) {
openpgp.util.print_debug_error(e);
}
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.use_native = true; 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, { tryTests('ECC Worker Tests', omnibus, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: async function() { before: async function() {
try {
await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
} catch (e) {
openpgp.util.print_debug_error(e);
}
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;

View File

@ -2005,7 +2005,7 @@ function versionSpecificTests() {
openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm; openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm;
if (openpgp.getWorker()) { if (openpgp.getWorker()) {
openpgp.getWorker().workers.forEach(worker => { 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; openpgp.config.aead_mode = aead_modeVal;
if (openpgp.getWorker()) { if (openpgp.getWorker()) {
openpgp.getWorker().workers.forEach(worker => { 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, { tryTests('V4 - With Worker', versionSpecificTests, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: async function() { before: async function() {
try {
await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); await openpgp.initWorker({ path: '../dist/openpgp.worker.js' });
} catch (e) {
openpgp.util.print_debug_error(e);
}
}, },
after: function() { after: function() {
openpgp.destroyWorker(); openpgp.destroyWorker();

View File

@ -516,7 +516,7 @@ describe('OpenPGP.js public api tests', function() {
openpgp.initWorker({ openpgp.initWorker({
workers: [workerStub] workers: [workerStub]
}), }),
workerStub.onmessage({ data: { event: 'loaded' } }) workerStub.onmessage({ data: { id: 0, event: 'method-return' } })
]); ]);
expect(openpgp.getWorker()).to.exist; expect(openpgp.getWorker()).to.exist;
openpgp.destroyWorker(); openpgp.destroyWorker();
@ -671,7 +671,7 @@ describe('OpenPGP.js public api tests', function() {
openpgp.initWorker({ openpgp.initWorker({
workers: [workerStub] workers: [workerStub]
}), }),
workerStub.onmessage({ data: { event: 'loaded' } }) workerStub.onmessage({ data: { id: 0, event: 'method-return' } })
]); ]);
const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); const proxyGenStub = stub(openpgp.getWorker(), 'delegate');
getWebCryptoAllStub.returns(); getWebCryptoAllStub.returns();
@ -716,7 +716,11 @@ describe('OpenPGP.js public api tests', function() {
it('should work in JS (with worker)', async function() { it('should work in JS (with worker)', async function() {
openpgp.config.use_native = false; openpgp.config.use_native = false;
try {
await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
} catch (e) {
openpgp.util.print_debug_error(e);
}
const opt = { const opt = {
userIds: [{ name: 'Test User', email: 'text@example.com' }], userIds: [{ name: 'Test User', email: 'text@example.com' }],
numBits: 512 numBits: 512
@ -821,7 +825,11 @@ describe('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)
try {
await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2}); await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2});
} catch (e) {
openpgp.util.print_debug_error(e);
}
const workerTest = (_, index) => { const workerTest = (_, index) => {
const plaintext = input.createSomeMessage() + 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)); await Promise.all(Array(10).fill(null).map(workerTest));
} finally { } finally {
try {
await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 }); 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, { tryTests('CFB mode (asm.js, worker)', tests, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: async function() { before: async function() {
try {
await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); await openpgp.initWorker({ path:'../dist/openpgp.worker.js' });
} catch (e) {
openpgp.util.print_debug_error(e);
}
}, },
beforeEach: function() { beforeEach: function() {
openpgp.config.aead_protect = false; openpgp.config.aead_protect = false;
@ -1641,7 +1657,7 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.allow_unauthenticated_stream = !!allow_streaming; openpgp.config.allow_unauthenticated_stream = !!allow_streaming;
if (openpgp.getWorker()) { if (openpgp.getWorker()) {
openpgp.getWorker().workers.forEach(worker => { 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) => { 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; openpgp.config.zero_copy = false;
if (openpgp.getWorker()) { if (openpgp.getWorker()) {
openpgp.getWorker().workers.forEach(worker => { openpgp.getWorker().workers.forEach(worker => {
worker.postMessage({ event: 'configure', config: openpgp.config }); openpgp.getWorker().callWorker(worker, 'configure', openpgp.config);
}); });
} }
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);

View File

@ -1334,7 +1334,11 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
tryTests('With Worker', tests, { tryTests('With Worker', tests, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: async function() { 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() { after: function() {
openpgp.destroyWorker(); openpgp.destroyWorker();

View File

@ -545,7 +545,11 @@ tryTests('X25519 Omnibus Tests', omnibus, {
tryTests('X25519 Omnibus Tests - Worker', omnibus, { tryTests('X25519 Omnibus Tests - Worker', omnibus, {
if: typeof window !== 'undefined' && window.Worker, if: typeof window !== 'undefined' && window.Worker,
before: async function() { 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() { beforeEach: function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;

View File

@ -37,7 +37,11 @@ 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() {
try {
await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); 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]; pubKey = (await openpgp.key.readArmored(pub_key)).keys[0];
}, },
after: async function() { after: async function() {
@ -50,11 +54,14 @@ function tests() {
describe('Random number pipeline', function() { describe('Random number pipeline', function() {
it('Random number buffer automatically reseeded', async 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();
const loaded = await wProxy.loaded(); try {
if (loaded) { await wProxy.init({ path:'../dist/openpgp.worker.js', workers: [worker] });
return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) }); } catch (e) {
openpgp.util.print_debug_error(e);
return;
} }
return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) });
}); });
}); });