*/
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) {
});
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.
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;
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()) ) {
// 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
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();