diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8a965479..923698a4 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -40,7 +40,15 @@ import { decode, atobArray, Utf8ArrayToStr } from './Helpers'; export class Connection{ constructor (){ + this._connectionError = null; this._connection = chrome.runtime.connectNative('gpgmejson'); + this._connection.onDisconnect.addListener(() => { + if (chrome.runtime.lastError) { + this._connectionError = chrome.runtime.lastError.message; + } else { + this._connectionError = 'Disconnected without error message'; + } + }); } /** @@ -50,9 +58,16 @@ export class Connection{ if (this._connection){ this._connection.disconnect(); this._connection = null; + this._connectionError = 'Disconnect requested by gpgmejs'; } } + /** + * Checks if the connection terminated with an error state + */ + get isDisconnected (){ + return this._connectionError !== null; + } /** * @typedef {Object} backEndDetails @@ -126,9 +141,17 @@ export class Connection{ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } + if (this.isDisconnected) { + if ( this.isNativeHostUnknown === true) { + return Promise.reject(gpgme_error('CONN_NO_CONFIG')); + } else { + return Promise.reject(gpgme_error( + 'CONN_NO_CONNECT', this._connectionError)); + } + } let chunksize = message.chunksize; const me = this; - return new Promise(function (resolve, reject){ + const nativeCommunication = new Promise(function (resolve, reject){ let answer = new Answer(message); let listener = function (msg) { if (!msg){ @@ -161,29 +184,21 @@ export class Connection{ } }; me._connection.onMessage.addListener(listener); - if (permittedOperations[message.operation].pinentry){ - return me._connection.postMessage(message.message); - } else { - return Promise.race([ - me._connection.postMessage(message.message), - function (resolve, reject){ - setTimeout(function (){ - me._connection.disconnect(); - reject(gpgme_error('CONN_TIMEOUT')); - }, 5000); - } - ]).then(function (result){ - return result; - }, function (reject){ - if (!(reject instanceof Error)) { - me._connection.disconnect(); - return gpgme_error('GNUPG_ERROR', reject); - } else { - return reject; - } - }); - } + me._connection.postMessage(message.message); }); + if (permittedOperations[message.operation].pinentry === true) { + return nativeCommunication; + } else { + return Promise.race([ + nativeCommunication, + new Promise(function (resolve, reject){ + setTimeout(function (){ + me._connection.disconnect(); + reject(gpgme_error('CONN_TIMEOUT')); + }, 5000); + }) + ]); + } } } @@ -212,6 +227,13 @@ class Answer{ return this._expected; } + /** + * Checks if an error matching browsers 'host not known' messages occurred + */ + get isNativeHostUnknown () { + return this._connectionError === 'Specified native messaging host not found.'; + } + /** * Adds incoming base64 encoded data to the existing response * @param {*} msg base64 encoded data. diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index bf52cce7..6189414f 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -35,6 +35,14 @@ export const err_list = { msg: 'The nativeMessaging answer was empty.', type: 'error' }, + 'CONN_NO_CONFIG':{ + msg: 'The browser does not recognize the nativeMessaging host.', + type: 'error' + }, + 'CONN_NATIVEMESSAGE':{ + msg: 'The native messaging was not successful.', + type: 'error' + }, 'CONN_TIMEOUT': { msg: 'A connection timeout was exceeded.', type: 'error' @@ -156,8 +164,8 @@ export function gpgme_error (code = 'GENERIC_ERROR', info){ */ class GPGME_Error extends Error{ constructor (code = 'GENERIC_ERROR', msg=''){ - - if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ + const verboseErrors = ['GNUPG_ERROR', 'CONN_NATIVEMESSAGE']; + if (verboseErrors.includes(code) && typeof (msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 106086fb..3c31a047 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -33,7 +33,7 @@ import { Connection } from './Connection'; * An unsuccessful attempt will reject as a GPGME_Error. * @param {Object} config (optional) configuration options * @param {Number} config.timeout set the timeout for the initial connection - * check. On some machines and operating systems a default timeout of 500 ms is + * check. On some machines and operating systems a default timeout of 1000 ms is * too low, so a higher number might be attempted. * @returns {Promise} * @async @@ -46,7 +46,17 @@ function init ({ timeout = 1000 } = {}){ if (result === true) { resolve(new GpgME()); } else { - reject(gpgme_error('CONN_NO_CONNECT')); + if (connection._connectionError) { + if (connection.isNativeHostUnknown){ + reject(gpgme_error('CONN_NO_CONFIG')); + } else { + reject(gpgme_error('CONN_NATIVEMESSAGE', + connection._connectionError) + ); + } + } else { + reject(gpgme_error('CONN_TIMEOUT')); + } } }, function (){ // unspecific connection error. Should not happen reject(gpgme_error('CONN_NO_CONNECT')); diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 414d18d1..45e2b93c 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -49,11 +49,42 @@ function unittests (){ expect(answer.info).to.be.an('Array'); expect(conn0.disconnect).to.be.a('function'); expect(conn0.post).to.be.a('function'); + expect(conn0.isDisconnected).to.be.false; done(); }); }); + it('Simple connection check', function (done) { + let conn0 = new Connection; + conn0.checkConnection(false, connectionTimeout).then( + function (answer) { + expect(answer).to.be.true; + expect(conn0.isDisconnected).to.be.false; + done(); + }); + }); + + it('Connection check with backend information', function (done) { + let conn0 = new Connection; + conn0.checkConnection(true, connectionTimeout).then( + function (answer) { + expect(answer).to.be.an('Object'); + expect(answer.gpgme).to.be.a('String'); + expect(answer.info).to.be.an('Array'); + expect(answer.info.length).to.be.above(0); + for (const item of answer.info) { + expect(item).to.have.property('protocol'); + expect(item).to.have.property('fname'); + expect(item).to.have.property('version'); + expect(item).to.have.property('req_version'); + expect(item).to.have.property('homedir'); + } + expect(conn0.isDisconnected).to.be.false; + done(); + }); + }); + it('Disconnecting', function (done) { let conn0 = new Connection; conn0.checkConnection(false, connectionTimeout).then( @@ -63,6 +94,7 @@ function unittests (){ conn0.checkConnection(false, connectionTimeout).then( function (result) { expect(result).to.be.false; + expect(conn0.isDisconnected).to.be.true; done(); }); });