From 461dd0c8b41683a91073b362d100ee5217ec53f6 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Apr 2018 18:44:30 +0200 Subject: [PATCH] js: change in initialization ancd connection handling -- * The Connection will now be started before an object is created, to better account for failures. * index.js: now exposes an init(), which returns a Promise of configurable with an established connection. * TODO: There is currently no way to recover from a "connection lost" * Connection.js offers Connection.isConnected, which toggles on port closing. --- lang/js/src/Connection.js | 41 ++++++++++++-------- lang/js/src/Keyring.js | 30 +++++++-------- lang/js/src/gpgmejs.js | 65 ++++++++++++++----------------- lang/js/src/gpgmejs_openpgpjs.js | 33 +++++++++++----- lang/js/src/index.js | 40 +++++++++++++++++-- lang/js/testapplication.js | 66 ++++++++++++++++---------------- 6 files changed, 159 insertions(+), 116 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 87ec8cf7..e6ff67be 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -35,6 +35,18 @@ export class Connection{ constructor(){ this.connect(); + let me = this; + } + + /** + * (Simple) Connection check. + * @returns {Boolean} true if the onDisconnect event has not been fired. + * Please note that the event listener of the port takes some time + * (5 ms seems enough) to react after the port is created. Then this will + * return undefined + */ + get isConnected(){ + return this._isConnected; } /** @@ -48,28 +60,20 @@ export class Connection{ /** * Opens a nativeMessaging port. - * returns nothing, but triggers errors if not successfull: - * NO_CONNECT: connection not successfull, chrome.runtime.lastError may be - * available - * ALREADY_CONNECTED: There is already a connection present. + * TODO: Error handling ALREADY_CONNECTED */ connect(){ - if (this._connection){ + if (this._isConnected === true){ return new GPGMEJS_Error('ALREADY_CONNECTED'); } + this._isConnected = true; this._connection = chrome.runtime.connectNative('gpgmejson'); - if (!this._connection){ - return new GPGMEJS_Error('NO_CONNECT'); - } - } - - /** - * checks if the connection is established - * TODO: some kind of ping to see if the other side responds as expected? - * @returns {Boolean} - */ - get connected(){ - return this._connection ? true: false; + let me = this; + this._connection.onDisconnect.addListener( + function(){ + me._isConnected = false; + } + ); } /** @@ -79,6 +83,9 @@ export class Connection{ * information. */ post(message){ + if (!this.isConnected){ + return Promise.reject(new GPGMEJS_Error('NO_CONNECT')); + } if (!message || !message instanceof GPGME_Message){ return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d8cb84b2..ef8028ff 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -19,28 +19,28 @@ */ import {GPGME_Message} from './Message' -import {Connection} from './Connection' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; export class GPGME_Keyring { - constructor(){ - this.reconnect(); + constructor(connection){ + this.connection = connection; } - /** - * (Re)-establishes the connection - * TODO TEMP: should we better use the connection of our parent, - * which we do not control? - */ - reconnect(){ - if (!this._connection || ! this._connection instanceof Connection){ - this._connection = new Connection; - } else { - this._connection.disconnect(); - this._connection.connect(); + set connection(connection){ + if (!this._connection && connection instanceof Connection){ + this._connection = connection; } } + get connection(){ + if (this._connection instanceof Connection){ + if (this._connection.isConnected){ + return this._connection; + } + return undefined; //TODO: connection was lost! + } + return undefined; //TODO: no connection there + } /** * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds @@ -57,7 +57,7 @@ export class GPGME_Keyring { msg.setParameter('with-secret', true); } - this._connection.post(msg).then(function(result){ + this.connection.post(msg).then(function(result){ let fpr_list = []; let resultset = []; if (!Array.isArray(result.keys)){ diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index b15477f0..4b2a03a4 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -22,59 +22,51 @@ import {Connection} from "./Connection" import {GPGME_Message} from './Message' import {toKeyIdArray} from "./Helpers" import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors" +import { GPGME_Keyring } from "./Keyring"; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port * TODO: add configuration */ - constructor(configuration = { - null_expire_is_never: false - }){ - this._connection = new Connection; + constructor(connection){ + this.connection = connection; } - /** - * refreshes the nativeApp connection - */ - reconnect(){ - if (!this._connection || ! this._connection instanceof Connection){ - this._connection = new Connection; - } else { - this._connection.disconnect(); - this._connection.connect(); + set connection(connection){ + if (this._connection instanceof Connection){ + //TODO Warning: Connection already established + } + if (connection instanceof Connection){ + this._connection = connection; } } - /** - * inmediately tries to destroy the nativeMessaging connection. - * TODO: may not be included in final API, as it is redundant. - * For now, it just serves paranoia - */ - disconnect(){ - if (this._connection){ - this._connection.disconnect(); - this._connection = null; + get connection(){ + if (this._connection instanceof Connection){ + if (this._connection.isConnected){ + return this._connection; + } + return undefined; //TODO: connection was lost! + } + return undefined; //TODO: no connection there + } + + set Keyring(keyring){ + if (ring && ring instanceof GPGME_Keyring){ + this.Keyring = ring; } } - /** - * tests the nativeApp connection - */ - get connected(){ - if (!this._connection || ! this._connection instanceof Connection){ - return false; - } - return this._connection.connected; + get Keyring(){ } - /** * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message */ - encrypt (data, publicKeys, wildcard=false){ + encrypt(data, publicKeys, wildcard=false){ let msg = new GPGME_Message('encrypt'); @@ -89,7 +81,7 @@ export class GpgME { if (wildcard === true){msg.setParameter('throw-keyids', true); }; - return (this._connection.post(msg)); + return (this.connection.post(msg)); } /** @@ -109,7 +101,7 @@ export class GpgME { } let msg = new GPGME_Message('decrypt'); putData(msg, data); - return this._connection.post(msg); + return this.connection.post(msg); } @@ -128,7 +120,7 @@ export class GpgME { if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? msg.setParameter('delete_force', true); //TBD } - this._connection.post(msg).then(function(success){ + this.connection.post(msg).then(function(success){ //TODO: it seems that there is always errors coming back: }, function(error){ switch (error.msg){ @@ -143,7 +135,6 @@ export class GpgME { } }); } - } /** @@ -171,4 +162,4 @@ function putData(message, data){ } else { return new GPGMEJS_Error('WRONGPARAM'); } -} \ No newline at end of file +} diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index 54b9dd45..f1ddb5d6 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -30,13 +30,29 @@ import { isFingerprint } from "./Helpers" import { GPGMEJS_Error } from './Errors' + export class GpgME_openPGPCompatibility { - constructor(){ - this._gpgme = new GpgME({ - null_expire_is_never: false - }); - this.Keyring = this.initKeyring(); + constructor(connection){ + this.initGpgME(connection); + } + + get Keyring(){ + if (this._keyring){ + return this._keyring; + } + return undefined; + } + + initGpgME(connection){ + this._GpgME = new GpgME(connection); + this._Keyring = new GPGME_Keyring_openPGPCompatibility(connection); + } + + get GpgME(){ + if (this._GpGME){ + return this._GpGME; + } } /** @@ -128,9 +144,6 @@ export class GpgME_openPGPCompatibility { // mime: A Boolean indicating whether the data is a MIME object. // info: An optional object with extra information. } - initKeyring(){ - return new GPGME_Keyring_openPGPCompatibility; - } } /** @@ -138,8 +151,8 @@ export class GpgME_openPGPCompatibility { * It may still be changed/expanded/merged with GPGME_Keyring */ class GPGME_Keyring_openPGPCompatibility { - constructor(){ - this._gpgme_keyring = new GPGME_Keyring; + constructor(connection){ + this._gpgme_keyring = new GPGME_Keyring(connection); } /** diff --git a/lang/js/src/index.js b/lang/js/src/index.js index f70bd2d8..0e2beda4 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -18,6 +18,40 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import { GpgME as gpgmejs } from "./gpgmejs"; -// import { GpgME_openPGPCompatibility as gpgmejs } from "./gpgmejs_openpgpjs"; -export default gpgmejs; +import { GpgME } from "./gpgmejs"; +import { GpgME_openPGPCompatibility } from "./gpgmejs_openpgpjs"; +import { Connection } from "./Connection"; + +/** + * Initializes a nativeMessaging Connection and returns a GPGMEjs object + * @param {*} conf Configuration. TBD + */ +function init( config = { + api_style: 'gpgme', // | gpgme_openpgpjs + null_expire_is_never: true // Boolean + }){ + return new Promise(function(resolve, reject){ + let connection = new Connection; + // TODO: Delayed reaction is ugly. We need to listen to the port's + // event listener in isConnected, but this takes some time (<5ms) to + // disconnect if there is no successfull connection. + let delayedreaction = function(){ + if (connection.isConnected === true){ + let gpgme = null; + if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ + resolve( + new GpgME_openPGPCompatibility(connection)); + } else { + resolve(new GpgME(connection)); + } + } else { + reject('NO_CONNECT'); + } + }; + setTimeout(delayedreaction, 5); + }); +}; + +export default { + init: init +} \ No newline at end of file diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js index 97b35527..f47299e8 100644 --- a/lang/js/testapplication.js +++ b/lang/js/testapplication.js @@ -19,39 +19,37 @@ * */ -function encryptbuttonclicked(event){ - let data = document.getElementById('cleartext').value; - let keyId = document.getElementById('pubkey').value; - let communication = new Gpgmejs; - let enc = communication.encrypt(data, keyId).then( - function(answer){ - console.log(answer); - if (answer.data){ - console.log(answer.data); - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert('Error: '+ errormsg); - }); -}; - -function decryptbuttonclicked(event){ - let data = document.getElementById("ciphertext").value; - let communication = new Gpgmejs; - let enc = communication.decrypt(data).then( - function(answer){ - console.log(answer); - if (answer.data){ - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert('Error: '+ errormsg); - }); -}; - document.addEventListener('DOMContentLoaded', function() { - document.getElementById("buttonencrypt").addEventListener("click", - encryptbuttonclicked); - document.getElementById("buttondecrypt").addEventListener("click", - decryptbuttonclicked); + Gpgmejs.init().then(function(gpgmejs){ + document.getElementById("buttonencrypt").addEventListener("click", + function(){ + let data = document.getElementById('cleartext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.encrypt(data, keyId).then( + function(answer){ + console.log(answer); + if (answer.data){ + console.log(answer.data); + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); + }); + }); + + document.getElementById("buttondecrypt").addEventListener("click", + function(){ + let data = document.getElementById("ciphertext").value; + gpgmejs.decrypt(data).then( + function(answer){ + console.log(answer); + if (answer.data){ + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); + }); + }); + }, + function(error){console.log(error)}); });