js: Error handling for browser errors
-- * Connection.js - Add some meaningful nativeMessaging feedback for failing communication due to misconfiguration or other browser-originated fails - add an "isDisconnected" property - "isNativeHostUnknown" tries to match browser's feedback string if the browser does not find gpgme-json * init.js - initialization will now reject with a more meaningful error if the configuration is not set up or other browser-based errors (chrome.runtime.lastError) are present. This should speed up the normal initialization (not having to waiting for a timeout any more in case of improper setup) * errors.js - CONN_NATIVEMESSAGE: New error that passes the browser's nativeMessaging error - CONN_NO_CONFIG: native messaging error indicating that the nativeMessaging host was not set up properly * unittests.js: - added the "isDisconnected" property to the startup tests - added tests for proper behavior of connection checks
This commit is contained in:
parent
44cedf9796
commit
f5e27a12d3
@ -40,7 +40,15 @@ import { decode, atobArray, Utf8ArrayToStr } from './Helpers';
|
|||||||
export class Connection{
|
export class Connection{
|
||||||
|
|
||||||
constructor (){
|
constructor (){
|
||||||
|
this._connectionError = null;
|
||||||
this._connection = chrome.runtime.connectNative('gpgmejson');
|
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){
|
if (this._connection){
|
||||||
this._connection.disconnect();
|
this._connection.disconnect();
|
||||||
this._connection = null;
|
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
|
* @typedef {Object} backEndDetails
|
||||||
@ -126,9 +141,17 @@ export class Connection{
|
|||||||
this.disconnect();
|
this.disconnect();
|
||||||
return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
|
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;
|
let chunksize = message.chunksize;
|
||||||
const me = this;
|
const me = this;
|
||||||
return new Promise(function (resolve, reject){
|
const nativeCommunication = new Promise(function (resolve, reject){
|
||||||
let answer = new Answer(message);
|
let answer = new Answer(message);
|
||||||
let listener = function (msg) {
|
let listener = function (msg) {
|
||||||
if (!msg){
|
if (!msg){
|
||||||
@ -161,29 +184,21 @@ export class Connection{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
me._connection.onMessage.addListener(listener);
|
me._connection.onMessage.addListener(listener);
|
||||||
if (permittedOperations[message.operation].pinentry){
|
me._connection.postMessage(message.message);
|
||||||
return me._connection.postMessage(message.message);
|
});
|
||||||
|
if (permittedOperations[message.operation].pinentry === true) {
|
||||||
|
return nativeCommunication;
|
||||||
} else {
|
} else {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
me._connection.postMessage(message.message),
|
nativeCommunication,
|
||||||
function (resolve, reject){
|
new Promise(function (resolve, reject){
|
||||||
setTimeout(function (){
|
setTimeout(function (){
|
||||||
me._connection.disconnect();
|
me._connection.disconnect();
|
||||||
reject(gpgme_error('CONN_TIMEOUT'));
|
reject(gpgme_error('CONN_TIMEOUT'));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
]).then(function (result){
|
|
||||||
return result;
|
|
||||||
}, function (reject){
|
|
||||||
if (!(reject instanceof Error)) {
|
|
||||||
me._connection.disconnect();
|
|
||||||
return gpgme_error('GNUPG_ERROR', reject);
|
|
||||||
} else {
|
|
||||||
return reject;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +227,13 @@ class Answer{
|
|||||||
return this._expected;
|
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
|
* Adds incoming base64 encoded data to the existing response
|
||||||
* @param {*} msg base64 encoded data.
|
* @param {*} msg base64 encoded data.
|
||||||
|
@ -35,6 +35,14 @@ export const err_list = {
|
|||||||
msg: 'The nativeMessaging answer was empty.',
|
msg: 'The nativeMessaging answer was empty.',
|
||||||
type: 'error'
|
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': {
|
'CONN_TIMEOUT': {
|
||||||
msg: 'A connection timeout was exceeded.',
|
msg: 'A connection timeout was exceeded.',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
@ -156,8 +164,8 @@ export function gpgme_error (code = 'GENERIC_ERROR', info){
|
|||||||
*/
|
*/
|
||||||
class GPGME_Error extends Error{
|
class GPGME_Error extends Error{
|
||||||
constructor (code = 'GENERIC_ERROR', msg=''){
|
constructor (code = 'GENERIC_ERROR', msg=''){
|
||||||
|
const verboseErrors = ['GNUPG_ERROR', 'CONN_NATIVEMESSAGE'];
|
||||||
if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){
|
if (verboseErrors.includes(code) && typeof (msg) === 'string'){
|
||||||
super(msg);
|
super(msg);
|
||||||
} else if (err_list.hasOwnProperty(code)){
|
} else if (err_list.hasOwnProperty(code)){
|
||||||
if (msg){
|
if (msg){
|
||||||
|
@ -33,7 +33,7 @@ import { Connection } from './Connection';
|
|||||||
* An unsuccessful attempt will reject as a GPGME_Error.
|
* An unsuccessful attempt will reject as a GPGME_Error.
|
||||||
* @param {Object} config (optional) configuration options
|
* @param {Object} config (optional) configuration options
|
||||||
* @param {Number} config.timeout set the timeout for the initial connection
|
* @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.
|
* too low, so a higher number might be attempted.
|
||||||
* @returns {Promise<GpgME>}
|
* @returns {Promise<GpgME>}
|
||||||
* @async
|
* @async
|
||||||
@ -46,7 +46,17 @@ function init ({ timeout = 1000 } = {}){
|
|||||||
if (result === true) {
|
if (result === true) {
|
||||||
resolve(new GpgME());
|
resolve(new GpgME());
|
||||||
} else {
|
} 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
|
}, function (){ // unspecific connection error. Should not happen
|
||||||
reject(gpgme_error('CONN_NO_CONNECT'));
|
reject(gpgme_error('CONN_NO_CONNECT'));
|
||||||
|
@ -49,11 +49,42 @@ function unittests (){
|
|||||||
expect(answer.info).to.be.an('Array');
|
expect(answer.info).to.be.an('Array');
|
||||||
expect(conn0.disconnect).to.be.a('function');
|
expect(conn0.disconnect).to.be.a('function');
|
||||||
expect(conn0.post).to.be.a('function');
|
expect(conn0.post).to.be.a('function');
|
||||||
|
expect(conn0.isDisconnected).to.be.false;
|
||||||
done();
|
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) {
|
it('Disconnecting', function (done) {
|
||||||
let conn0 = new Connection;
|
let conn0 = new Connection;
|
||||||
conn0.checkConnection(false, connectionTimeout).then(
|
conn0.checkConnection(false, connectionTimeout).then(
|
||||||
@ -63,6 +94,7 @@ function unittests (){
|
|||||||
conn0.checkConnection(false, connectionTimeout).then(
|
conn0.checkConnection(false, connectionTimeout).then(
|
||||||
function (result) {
|
function (result) {
|
||||||
expect(result).to.be.false;
|
expect(result).to.be.false;
|
||||||
|
expect(conn0.isDisconnected).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user