LP#1640255 Hatch native messaging extension
authorBill Erickson <berickxx@gmail.com>
Mon, 14 Nov 2016 17:58:33 +0000 (12:58 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 14 Nov 2016 17:58:36 +0000 (12:58 -0500)
Replaces Hatch Websockets communication layer with browser extension-
based communication.

Hatch API remains the same with 2 notable exceptions:

1. appendItem() API call has been removed.  It did not work as designed
   and (thus far) has served no purpose.  It was originally intended for
   offline data storage, but that will probably require something a
   little smarter.

2. The printer configuration API is no more.  This will be replaced with
   an in-app configuration page.  Note, this does not prevent use of the
   printer dialog, it only means settings are not collected from the
   printer dialog.  Overhaul still in design...

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/templates/staff/admin/workstation/t_print_config.tt2
Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
Open-ILS/web/js/ui/default/staff/services/hatch.js

index ef5dafb..ce4d670 100644 (file)
                   value="{{printConfig[context].printer}}">
               </div><!-- /input-group -->
             </div><!-- col -->
-            <div class="col-md-6">
-              <div class="input-group">
-                <div class="input-group-btn">
-                  <button type="button" 
-                    ng-click="configurePrinter()"
-                    ng-class="{disabled : actionPending || !printers[0]}"
-                    class="btn btn-default btn-success">
-                      [% l('Configure Printer') %]
-                  </button>
-                  <button type="button" 
-                    ng-click="resetConfig()"
-                    ng-class="{disabled : actionPending}"
-                    class="btn btn-default btn-warning">
-                      [% l('Reset Configuration') %]
-                  </button>
-                </div>
-              </div>
-            </div>
           </div><!-- row -->
           <div class="row" ng-hide="isTestView"> 
             <div class="col-md-12">
index 360f6d7..0a2a267 100644 (file)
       </div>
     </div><!-- row -->
   </div>
-  <div class="row">
-    <div class="col-md-6">
-      <input type='text' class='form-control'  
-        ng-disabled="!hatchRequired || !userHasRegPerm"
-        title="[% l('Hatch URL') %]"
-        placeholder="[% l('Hatch URL') %]"
-        ng-change='updateHatchURL()' ng-model='hatchURL'/>
-    </div>
-  </div>
 
   <div class="row new-entry">
     <div class="col-md-6">
index d31c5fd..c14612d 100644 (file)
@@ -159,7 +159,6 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
 
     // ---------------------
     // Hatch Configs
-    $scope.hatchURL = egCore.hatch.hatchURL();
     $scope.hatchRequired = 
         egCore.hatch.getLocalItem('eg.hatch.required');
 
@@ -168,11 +167,6 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
             'eg.hatch.required', $scope.hatchRequired);
     }
 
-    $scope.updateHatchURL = function() {
-        egCore.hatch.setLocalItem(
-            'eg.hatch.url', $scope.hatchURL);
-    }
-
     egCore.hatch.getItem('eg.audio.disable').then(function(val) {
         $scope.disable_sound = val;
     });
@@ -282,20 +276,6 @@ function($scope , egCore) {
         .finally(function() {$scope.actionPending = false});
     }
 
-    $scope.configurePrinter = function() {
-        $scope.printConfigError = null;
-        $scope.actionPending = true;
-        egCore.hatch.configurePrinter(
-            $scope.context,
-            $scope.printConfig[$scope.context].printer
-        )
-        .then(
-            function(config) {$scope.printConfig = config},
-            function(error) {$scope.printConfigError = error}
-        )
-        .finally(function() {$scope.actionPending = false});
-    }
-
     $scope.setPrinter = function(name) {
         $scope.printConfig[$scope.context].printer = name;
     }
index ff517fc..7115ecc 100644 (file)
  */
 angular.module('egCoreMod')
 
+.constant("HATCH_CONFIG", {
+    /* TODO: Extension name will change once the extension is 
+     * registered and will presumably be different per browser.
+     * Current extension name is borrowed from:
+     * https://chromium.googlesource.com/chromium/src/+/master/chrome/common/extensions/docs/examples/api/nativeMessaging
+     */
+    EXT_NAME_CHROME : "knldjmfmopnpolahpmmgbagdohdnhkik"
+})
+
 .factory('egHatch',
-           ['$q','$window','$timeout','$interpolate','$http','$cookies',
-    function($q , $window , $timeout , $interpolate , $http , $cookies) {
+           ['$q','$window','$timeout','$interpolate','$http','$cookies','HATCH_CONFIG',
+    function($q , $window , $timeout , $interpolate , $http , $cookies , HATCH_CONFIG) {
 
     var service = {};
-    service.msgId = 0;
+    service.port = null; // Hatch extension connection
+    service.msgId = 1;
     service.messages = {};
     service.pending = [];
-    service.socket = null;
     service.hatchAvailable = null;
-    service.defaultHatchURL = 'wss://localhost:8443/hatch'; 
 
-    // write a message to the Hatch websocket
+    // write a message to the Hatch port
     service.sendToHatch = function(msg) {
         var msg2 = {};
 
@@ -47,7 +55,7 @@ angular.module('egCoreMod')
         });
 
         console.debug("sending to Hatch: " + JSON.stringify(msg2,null,2));
-        service.socket.send(JSON.stringify(msg2));
+        service.port.postMessage(msg2);
     }
 
     // Send the request to Hatch if it's available.  
@@ -90,18 +98,18 @@ angular.module('egCoreMod')
         msg.deferred = service.messages[msg.msgid].deferred;
         delete service.messages[msg.msgid]; // un-cache
 
-        // resolve / reject
-        if (msg.error) {
-            throw new Error(
-            "egHatch command failed : " 
-                + JSON.stringify(msg.error, null, 2));
+        if (msg.status != 200) {
+            msg.deferred.reject();
+            throw new Error("Hatch command failed with status=" 
+                + msg.status + " and message=" + msg.message);
+
         } else {
             msg.deferred.resolve(msg.content);
         } 
     }
 
     service.hatchClosed = function() {
-        service.socket = null;
+        service.port = null;
         service.printers = [];
         service.printConfig = {};
         while ( (msg = service.pending.shift()) ) {
@@ -112,11 +120,6 @@ angular.module('egCoreMod')
             service.onHatchClose();
     }
 
-    service.hatchURL = function() {
-        return service.getLocalItem('eg.hatch.url') 
-            || service.defaultHatchURL;
-    }
-
     // Returns true if Hatch is required or if we are currently
     // communicating with the Hatch service. 
     service.usingHatch = function() {
@@ -131,56 +134,59 @@ angular.module('egCoreMod')
 
     service.hatchConnect = function() {
 
-        if (service.socket && 
-            service.socket.readyState == service.socket.CONNECTING) {
-            // connection in progress.  Nothing to do.  Our queued
-            // message will be delivered when onopen() fires
-            return;
-        }
+        if (service.port) return;
+
+        service.initting = true;
 
         try {
-            service.socket = new WebSocket(service.hatchURL());
+            service.port = 
+                chrome.runtime.connect(HATCH_CONFIG.EXT_NAME_CHROME);
         } catch(e) {
             service.hatchAvailable = false;
             service.hatchClosed();
+            console.debug("Hatch connection failed: " + e);
             return;
         }
 
-        service.socket.onopen = function() {
-            console.debug('connected to Hatch');
-            service.hatchAvailable = true;
-            if (service.onHatchOpen) 
-                service.onHatchOpen();
-            while ( (msg = service.pending.shift()) ) {
-                service.sendToHatch(msg);
-            };
-        }
+        service.port.onDisconnect.addListener(function() {
+            if (service.hatchAvailable === false) return; // already noted
+            service.hatchAvailable = null; // reset
+            service.hatchClosed();
+        });
 
-        service.socket.onclose = function() {
-            if (service.hatchAvailable === false) return; // already registered
+        service.port.onMessage.addListener(function(msg) {
+            console.debug('Hatch says: ' + JSON.stringify(msg, null, 2));
 
-            // onclose() will be called regularly as we disconnect from
-            // Hatch via timeouts.  Return hatchAvailable to its unknow state
-            service.hatchAvailable = null;
-            service.hatchClosed();
-        }
+            if (service.initting) {
+                service.initting = false;
+                console.debug("Hatch init completed with " + msg.message);
 
-        service.socket.onerror = function() {
-            if (service.hatchAvailable === false) return; // already registered
-            service.hatchAvailable = false;
-            console.debug(
-                "unable to connect to Hatch server at " + service.hatchURL());
-            service.hatchClosed();
-        }
+                if (msg.status == 200) {
+                    service.hatchOpened();
+                } else {
+                    console.warn("Hatch init failed");
+                }
 
-        service.socket.onmessage = function(evt) {
-            var msgStr = evt.data;
-            if (!msgStr) throw new Error("Hatch returned empty message");
+            } else {
+                service.resolveRequest(msg); 
+            }
+        });
 
-            var msgObj = JSON.parse(msgStr);
-            console.debug('Hatch says ' + JSON.stringify(msgObj, null, 2));
-            service.resolveRequest(msgObj); 
-        }
+        console.debug('Connected to Hatch');
+        service.hatchAvailable = true;
+
+        // The first message to Hatch must be "init"
+        service.attemptHatchDelivery({action : 'init'});
+    }
+
+    service.hatchOpened = function() {
+        // let others know we're connected
+        if (service.onHatchOpen) service.onHatchOpen();
+
+        // Deliver any previously queued requests 
+        while ( (msg = service.pending.shift()) ) {
+            service.sendToHatch(msg);
+        };
     }
 
     service.getPrintConfig = function() {
@@ -291,8 +297,8 @@ angular.module('egCoreMod')
     service.getRemoteItem = function(key) {
         return service.attemptHatchDelivery({
             key : key,
-            action : 'get'
-        });
+            action : 'get'
+        })
     }
 
     service.getLocalItem = function(key) {
@@ -318,16 +324,14 @@ angular.module('egCoreMod')
      * tmp values are removed during logout or browser close.
      */
     service.setItem = function(key, value) {
-        var str = JSON.stringify(value);
-
-        return service.setRemoteItem(key, str)['catch'](
+        return service.setRemoteItem(key, value)['catch'](
             function(msg) {
                 if (service.hatchRequired()) {
                     console.error("Unable to setItem: " + key
                      + "; hatchRequired=true, but hatch is not connected");
                      return null;
                 }
-                return service.setLocalItem(msg.key, null, str);
+                return service.setLocalItem(msg.key, value);
             }
         );
     }
@@ -336,7 +340,7 @@ angular.module('egCoreMod')
     service.setRemoteItem = function(key, value) {
         return service.attemptHatchDelivery({
             key : key, 
-            value : value, 
+            content : value, 
             action : 'set',
         });
     }
@@ -374,29 +378,6 @@ angular.module('egCoreMod')
         $window.sessionStorage.setItem(key, jsonified);
     }
 
-    // appends the value to the existing item stored at key.
-    // If not item is found at key, this behaves just like setItem()
-    service.appendItem = function(key, value) {
-        return service.appendRemoteItem(key, value)['catch'](
-            function(msg) {
-                if (service.hatchRequired()) {
-                    console.error("Unable to appendItem: " + key
-                     + "; hatchRequired=true, but hatch is not connected");
-                     return null;
-                }
-                service.appendLocalItem(msg.key, msg.value);
-            }
-        );
-    }
-
-    service.appendRemoteItem = function(key, value) {
-        return service.attemptHatchDelivery({
-            key : key, 
-            value : value, 
-            action : 'append',
-        });
-    }
-
     // assumes the appender and appendee are both strings
     // TODO: support arrays as well
     service.appendLocalItem = function(key, value) {