2 * Core Service - egHatch
4 * Dispatches print and data storage requests to the appropriate handler.
6 * With each top-level request, if a connection to Hatch is established,
7 * the request is relayed. If a connection has not been attempted, an
8 * attempt is made then the request is handled. If Hatch is known to be
9 * inaccessible, requests are routed to local handlers.
11 * Most handlers also provide direct remote and local variants to the
12 * application can decide to which to use as needed.
14 * Local storage requests are handled by $window.localStorage.
16 * Note that all top-level and remote requests return promises. All
17 * local requests return immediate values, since local requests are
20 * BEWARE: never store "fieldmapper" objects, since their structure
21 * may change over time as the IDL changes. Always flatten objects
22 * into key/value pairs before calling set*Item()
25 angular.module('egCoreMod')
28 ['$q','$window','$timeout','$interpolate','$http','$cookies',
29 function($q , $window , $timeout , $interpolate , $http , $cookies) {
33 service.messages = {};
35 service.hatchAvailable = null;
37 // key/value cache -- avoid unnecessary Hatch extension requests.
38 // Only affects *RemoteItem calls.
39 service.keyCache = {};
41 // write a message to the Hatch port
42 service.sendToHatch = function(msg) {
45 // shallow copy and scrub msg before sending
46 angular.forEach(msg, function(val, key) {
47 if (key.match(/deferred/)) return;
51 console.debug("sending to Hatch: " + JSON.stringify(msg2));
54 $window.postMessage(msg2, $window.location.origin);
57 // Send the request to Hatch if it's available.
58 // Otherwise handle the request locally.
59 service.attemptHatchDelivery = function(msg) {
61 msg.msgid = service.msgId++;
62 msg.deferred = $q.defer();
64 if (service.hatchAvailable === false) { // Hatch is closed.
65 msg.deferred.reject(msg);
67 } else if (service.hatchAvailable === true) { // Hatch is open.
68 service.messages[msg.msgid] = msg;
69 service.sendToHatch(msg);
71 } else { // Hatch state unknown
72 service.messages[msg.msgid] = msg;
73 service.pending.push(msg);
74 $timeout(service.openHatch);
79 Object.keys(service.messages).length + " pending Hatch requests");
82 return msg.deferred.promise;
86 // resolve the promise on the given request and remove
87 // it from our tracked requests.
88 service.resolveRequest = function(msg) {
90 if (!service.messages[msg.msgid]) {
91 console.error('no cached message for id = ' + msg.msgid);
95 // for requests sent through Hatch, only the cached
96 // request will have the original promise attached
97 msg.deferred = service.messages[msg.msgid].deferred;
98 delete service.messages[msg.msgid]; // un-cache
100 if (msg.status == 200) {
101 msg.deferred.resolve(msg.content);
103 console.warn("Hatch command failed with status="
104 + msg.status + " and message=" + msg.message);
105 msg.deferred.reject();
109 service.openHatch = function() {
111 // When the Hatch extension loads, it tacks an attribute onto
112 // the page body to indicate it's available.
114 if (!$window.document.body.getAttribute('hatch-is-open')) {
115 service.hatchWontOpen('Hatch is not available');
119 $window.addEventListener("message", function(event) {
120 // We only accept messages from our own content script.
121 if (event.source != window) return;
123 // We only care about messages from the Hatch extension.
124 if (event.data && event.data.from == 'extension') {
126 // Avoid logging full Hatch responses. they can get large.
128 'Hatch responded to message ID ' + event.data.msgid);
130 service.resolveRequest(event.data);
134 service.hatchAvailable = true; // public flag
135 service.hatchOpened();
138 service.hatchWontOpen = function(err) {
139 console.debug("Hatch connection failed: " + err);
140 service.hatchAvailable = false;
141 while ( (msg = service.pending.shift()) ) {
142 msg.deferred.reject(msg);
143 delete service.messages[msg.msgid];
147 // Returns true if Hatch is required or if we are currently
148 // communicating with the Hatch service.
149 service.usingHatch = function() {
150 return service.hatchAvailable || service.hatchRequired();
153 // Returns true if this browser (via localStorage) is
154 // configured to require Hatch.
155 service.hatchRequired = function() {
156 return service.getLocalItem('eg.hatch.required');
159 service.hatchOpened = function() {
160 // let others know we're connected
161 if (service.onHatchOpen) service.onHatchOpen();
163 // Deliver any previously queued requests
164 while ( (msg = service.pending.shift()) ) {
165 service.sendToHatch(msg);
169 service.remotePrint = function(
170 context, contentType, content, withDialog) {
172 return service.getPrintConfig(context).then(
174 // print configuration retrieved; print
175 return service.attemptHatchDelivery({
179 contentType : contentType,
180 showDialog : withDialog,
186 service.getPrintConfig = function(context) {
187 return service.getRemoteItem('eg.print.config.' + context);
190 service.setPrintConfig = function(context, config) {
191 return service.setRemoteItem('eg.print.config.' + context, config);
194 service.getPrinterOptions = function(name) {
195 return service.attemptHatchDelivery({
196 action : 'printer-options',
201 service.getPrinters = function() {
202 if (service.printers) // cached printers
203 return $q.when(service.printers);
205 return service.attemptHatchDelivery({action : 'printers'}).then(
207 // we have remote printers; sort by name and return
209 service.printers = printers.sort(
210 function(a,b) {return a.name < b.name ? -1 : 1});
211 return service.printers;
214 // remote call failed and there is no such thing as local
215 // printers; return empty set.
216 function() { return [] }
220 // get the value for a stored item
221 service.getItem = function(key) {
222 return service.getRemoteItem(key)['catch'](
224 if (service.hatchRequired()) {
225 console.error("getRemoteItem() failed for key " + key);
228 return service.getLocalItem(msg.key);
233 service.getRemoteItem = function(key) {
235 if (service.keyCache[key] != undefined)
236 return $q.when(service.keyCache[key])
238 return service.attemptHatchDelivery({
241 }).then(function(content) {
242 return service.keyCache[key] = content;
246 service.getLocalItem = function(key) {
247 var val = $window.localStorage.getItem(key);
248 if (val == null) return;
249 return JSON.parse(val);
252 service.getLoginSessionItem = function(key) {
253 var val = $cookies.get(key);
254 if (val == null) return;
255 return JSON.parse(val);
258 service.getSessionItem = function(key) {
259 var val = $window.sessionStorage.getItem(key);
260 if (val == null) return;
261 return JSON.parse(val);
265 * @param tmp bool Store the value as a session cookie only.
266 * tmp values are removed during logout or browser close.
268 service.setItem = function(key, value) {
269 return service.setRemoteItem(key, value)['catch'](
271 if (service.hatchRequired()) {
272 console.error("setRemoteItem() failed for key " + key);
275 return service.setLocalItem(msg.key, value);
280 // set the value for a stored or new item
281 service.setRemoteItem = function(key, value) {
282 service.keyCache[key] = value;
283 return service.attemptHatchDelivery({
290 // Set the value for the given key.
291 // "Local" items persist indefinitely.
292 // If the value is raw, pass it as 'value'. If it was
293 // externally JSONified, pass it via jsonified.
294 service.setLocalItem = function(key, value, jsonified) {
295 if (jsonified === undefined )
296 jsonified = JSON.stringify(value);
297 $window.localStorage.setItem(key, jsonified);
300 // Set the value for the given key.
301 // "LoginSession" items are removed when the user logs out or the
302 // browser is closed.
303 // If the value is raw, pass it as 'value'. If it was
304 // externally JSONified, pass it via jsonified.
305 service.setLoginSessionItem = function(key, value, jsonified) {
306 service.addLoginSessionKey(key);
307 if (jsonified === undefined )
308 jsonified = JSON.stringify(value);
309 $cookies.put(key, jsonified);
312 // Set the value for the given key.
313 // "Session" items are browser tab-specific and are removed when the
315 // If the value is raw, pass it as 'value'. If it was
316 // externally JSONified, pass it via jsonified.
317 service.setSessionItem = function(key, value, jsonified) {
318 if (jsonified === undefined )
319 jsonified = JSON.stringify(value);
320 $window.sessionStorage.setItem(key, jsonified);
323 // assumes the appender and appendee are both strings
324 // TODO: support arrays as well
325 service.appendLocalItem = function(key, value) {
326 var item = service.getLocalItem(key);
328 if (typeof item != 'string') {
329 logger.warn("egHatch.appendLocalItem => "
330 + "cannot append to a non-string item: " + key);
333 value = item + value; // concatenate our value
335 service.setLocalitem(key, value);
338 // remove a stored item
339 service.removeItem = function(key) {
340 return service.removeRemoteItem(key)['catch'](
342 return service.removeLocalItem(msg.key)
347 service.removeRemoteItem = function(key) {
348 delete service.keyCache[key];
349 return service.attemptHatchDelivery({
355 service.removeLocalItem = function(key) {
356 $window.localStorage.removeItem(key);
359 service.removeLoginSessionItem = function(key) {
360 service.removeLoginSessionKey(key);
361 $cookies.remove(key);
364 service.removeSessionItem = function(key) {
365 $window.sessionStorage.removeItem(key);
369 * Remove all "LoginSession" items.
371 service.clearLoginSessionItems = function() {
372 angular.forEach(service.getLoginSessionKeys(), function(key) {
373 service.removeLoginSessionItem(key);
376 // remove the keys cache.
377 service.removeLocalItem('eg.hatch.login_keys');
380 // if set, prefix limits the return set to keys starting with 'prefix'
381 service.getKeys = function(prefix) {
382 return service.getRemoteKeys(prefix)['catch'](
384 if (service.hatchRequired()) {
385 console.error("getRemoteKeys() failed");
388 return service.getLocalKeys(prefix)
393 service.getRemoteKeys = function(prefix) {
394 return service.attemptHatchDelivery({
400 service.getLocalKeys = function(prefix) {
403 while ( (k = $window.localStorage.key(idx++)) !== null) {
404 // key prefix match test
405 if (prefix && k.substr(0, prefix.length) != prefix) continue;
413 * Array of "LoginSession" keys.
414 * Note we have to store these as "Local" items so browser tabs can
415 * share them. We could store them as cookies, but it's more data
416 * that has to go back/forth to the server. A "LoginSession" key name is
417 * not private, though, so it's OK if they are left in localStorage
418 * until the next login.
420 service.getLoginSessionKeys = function(prefix) {
423 var login_keys = service.getLocalItem('eg.hatch.login_keys') || [];
424 angular.forEach(login_keys, function(k) {
425 // key prefix match test
426 if (prefix && k.substr(0, prefix.length) != prefix) return;
432 service.addLoginSessionKey = function(key) {
433 var keys = service.getLoginSessionKeys();
434 if (keys.indexOf(key) < 0) {
436 service.setLocalItem('eg.hatch.login_keys', keys);
440 service.removeLoginSessionKey = function(key) {
441 var keys = service.getLoginSessionKeys().filter(function(k) {
444 service.setLocalItem('eg.hatch.login_keys', keys);