From 58f799315f8dd7b4d7ac35f61dee8b537301cf78 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 23 Nov 2016 16:41:35 -0500 Subject: [PATCH] LP#1640255 Hatch declarativeContent plugin Signed-off-by: Bill Erickson --- Open-ILS/web/js/ui/default/staff/services/hatch.js | 203 ++++++++------------- 1 file changed, 72 insertions(+), 131 deletions(-) diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js index 2983f4c5f6..c38920e0f6 100644 --- a/Open-ILS/web/js/ui/default/staff/services/hatch.js +++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js @@ -24,25 +24,16 @@ */ 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','HATCH_CONFIG', - function($q , $window , $timeout , $interpolate , $http , $cookies , HATCH_CONFIG) { + ['$q','$window','$timeout','$interpolate','$http','$cookies', + function($q , $window , $timeout , $interpolate , $http , $cookies) { var service = {}; - service.port = null; // Hatch extension connection service.msgId = 1; service.messages = {}; service.pending = []; service.hatchAvailable = null; + service.state = 'IDLE'; // IDLE, INIT, CONNECTED, NO_CONNECTION // write a message to the Hatch port service.sendToHatch = function(msg) { @@ -55,7 +46,9 @@ angular.module('egCoreMod') }); console.debug("sending to Hatch: " + JSON.stringify(msg2,null,2)); - service.port.postMessage(msg2); + + msg2.from = 'page'; + $window.postMessage(msg2, $window.location.origin); } // Send the request to Hatch if it's available. @@ -65,27 +58,18 @@ angular.module('egCoreMod') msg.msgid = service.msgId++; msg.deferred = $q.defer(); - if (service.hatchAvailable === false) { // Hatch is closed + if (service.state == 'NO_CONNECTION') { msg.deferred.reject(msg); - } else if (service.hatchAvailable === true) { // Hatch is open + } else if (service.state.match(/CONNECTED|INIT/)) { // Hatch is known to be open service.messages[msg.msgid] = msg; service.sendToHatch(msg); - } else { // Hatch status unknown; attempt to connect + } else if (service.state == 'IDLE') { service.messages[msg.msgid] = msg; service.pending.push(msg); - // Connect to Hatch asynchronously. Othwerise, results - // in a $rootScope:inprog error in Firefox, possibly - // because hatchConnect() does not yet work in FF. - // - // Firefox does not yet support runtime.connect(..) - // from the browser to "externally_connectable" extensions. - // http://stackoverflow.com/questions/38487552/externally-connectable-and-firefox-webextensions - // http://stackoverflow.com/questions/10526995/can-a-site-invoke-a-browser-extension/10527809#10527809 - // https://bugzilla.mozilla.org/show_bug.cgi?id=1204583 - $timeout(service.hatchConnect); + $timeout(service.openHatch); } return msg.deferred.promise; @@ -107,18 +91,71 @@ angular.module('egCoreMod') msg.deferred = service.messages[msg.msgid].deferred; delete service.messages[msg.msgid]; // un-cache - if (msg.status != 200) { - msg.deferred.reject(); - throw new Error("Hatch command failed with status=" - + msg.status + " and message=" + msg.message); + switch (service.state) { + + case 'CONNECTED': // received a standard Hatch response + if (msg.status == 200) { + msg.deferred.resolve(msg.content); + } else { + msg.deferred.reject(); + console.warn("Hatch command failed with status=" + + msg.status + " and message=" + msg.message); + } + break; + + case 'INIT': + if (msg.status == 200) { + service.hatchAvailable = true; // public flag + service.state = 'CONNECTED'; + service.hatchOpened(); + } else { + msg.deferred.reject(); + service.hatchWontOpen(msg.message); + } + break; + + default: + console.warn( + "Received message in unexpected state: " + service.state); + } + } + + service.openHatch = function() { + + // When the Hatch extension loads, it tacks an attribute onto + // the page body to indicate it's available. + + if (!$window.document.body.getAttribute('hatch-is-open')) { + service.hatchWontOpen('Hatch is not available'); + return; + } + + $window.addEventListener("message", function(event) { + // We only accept messages from our own content script. + if (event.source != window) return; + + // We only care about messages from the Hatch extension. + if (event.data && event.data.from == 'extension') { + + console.debug('Hatch says: ' + + JSON.stringify(event.data, null, 2)); + + service.resolveRequest(event.data); + } + }); + + service.state = 'INIT'; + service.attemptHatchDelivery({action : 'init'}); + } - } else { - msg.deferred.resolve(msg.content); - } + service.hatchWontOpen = function(err) { + console.debug("Hatch connection failed: " + err); + service.state = 'NO_CONNECTION'; + service.hatchAvailable = false; + service.hatchClosed(); } service.hatchClosed = function() { - service.port = null; service.printers = []; service.printConfig = {}; while ( (msg = service.pending.shift()) ) { @@ -132,7 +169,7 @@ angular.module('egCoreMod') // Returns true if Hatch is required or if we are currently // communicating with the Hatch service. service.usingHatch = function() { - return service.hatchAvailable || service.hatchRequired(); + return service.state == 'CONNECTED' || service.hatchRequired(); } // Returns true if this browser (via localStorage) is @@ -141,102 +178,6 @@ angular.module('egCoreMod') return service.getLocalItem('eg.hatch.required'); } - /** - * See if the Hatch extension is registered before we try to - * connect. - * - * Since chrome.runtime.connect() will return a valid Port object - * regardless of whether the requested extenion exists, first attempt - * a simple call/request that will loudly fail when the extension - * is not available. - * - * Returns a promise. - */ - service.checkHatchExtension = function() { - var deferred = $q.defer(); - try { - console.debug("Sending 'ping' request to Hatch extension"); - chrome.runtime.sendMessage( - HATCH_CONFIG.EXT_NAME_CHROME, {ping : true}, - function (reply) { - if (reply && reply.pong) { - deferred.resolve(); - } else { - deferred.reject( - "Extension failed to reply to 'ping' request"); - } - } - ); - } catch (E) { - deferred.reject(E); - } - return deferred.promise; - } - - service.hatchConnect = function() { - service.checkHatchExtension().then( - service.openHatchPort, // extension is available - service.extConnectFailed // extension is not available - ); - } - - service.extConnectFailed = function(err) { - console.debug("Hatch connection failed: " + err); - service.hatchAvailable = false; - service.hatchClosed(); - } - - service.openHatchPort = function() { - - if (service.port) return; - - service.initting = true; - - try { - service.port = - chrome.runtime.connect(HATCH_CONFIG.EXT_NAME_CHROME); - } catch(e) { - service.extConnectFailed(e); - return; - } - - if (!service.port) { - service.extConnectFailed( - 'runtime.connect() did not return a Port'); - return; - } - - service.port.onDisconnect.addListener(function() { - if (service.hatchAvailable === false) return; // already noted - service.hatchAvailable = null; // reset - service.hatchClosed(); - }); - - service.port.onMessage.addListener(function(msg) { - console.debug('Hatch says: ' + JSON.stringify(msg, null, 2)); - - if (service.initting) { - service.initting = false; - console.debug("Hatch init completed with " + msg.message); - - if (msg.status == 200) { - service.hatchOpened(); - } else { - console.warn("Hatch init failed"); - } - - } else { - service.resolveRequest(msg); - } - }); - - console.debug('Hatch port open, sending "init" message'); - 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(); -- 2.11.0