LP#1755258 Improve network error handling
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / net.js
1 /**
2  * Core Service - egNet
3  *
4  * Promise wrapper for OpenSRF network calls.
5  * http://docs.angularjs.org/api/ng.$q
6  *
7  * promise.notify() is called with each streamed response.
8  *
9  * promise.resolve() is called when the request is complete 
10  * and passes as its value the response received from the 
11  * last call to onresponse().  If no calls to onresponse()
12  * were made (i.e. no responses delivered) no value will
13  * be passed to resolve(), hence any value seen by the client
14  * will be 'undefined'.
15  *
16  * Example: Call with one response and no error checking:
17  *
18  * egNet.request(service, method, param1, param2).then(
19  *    function(data) { 
20  *      // data == undefined if no responses were received
21  *      // data == null if last response was a null value
22  *      console.log(data) 
23  *    });
24  *
25  * Example: capture streaming responses, error checking
26  *
27  * egNet.request(service, method, param1, param2).then(
28  *      function(data) { console.log('all done') },
29  *      function(err)  { console.log('error: ' + err) },
30  *      functoin(data) { console.log('received stream response ' + data) }
31  *  );
32  */
33
34 angular.module('egCoreMod')
35
36 .factory('egNet', 
37        ['$q','$rootScope','egEvent', 
38 function($q,  $rootScope,  egEvent) {
39
40     var net = {};
41
42     // Simple container class for tracking a single request.
43     function NetRequest(kwargs) {
44         var self = this;
45         angular.forEach(kwargs, function(val, key) { self[key] = val });
46     }
47
48     // Relay response object to the caller for typical/successful responses.  
49     // Applies special handling to response events that require global
50     // attention.
51     net.handleResponse = function(request) {
52         request.evt = egEvent.parse(request.last);
53
54         if (request.evt) {
55
56             if (request.evt.textcode == 'NO_SESSION') {
57                 $rootScope.$broadcast('egAuthExpired');
58                 request.deferred.reject();
59                 return;
60
61             } else if (request.evt.textcode == 'PERM_FAILURE') {
62
63                 if (!net.handlePermFailure) {
64                     // nothing we can do, pass the failure up to the caller.
65                     console.debug("egNet has no handlePermFailure()");
66                     request.deferred.notify(request.last);
67                     return;
68                 }
69
70                 // handlePermFailure() starts a new series of promises.
71                 // Tell our in-progress promise to resolve, etc. along
72                 // with the new handlePermFailure promise.
73                 request.superseded = true;
74                 net.handlePermFailure(request).then(
75                     request.deferred.resolve, 
76                     request.deferred.reject, 
77                     request.deferred.notify
78                 );
79             }
80         }
81
82         request.deferred.notify(request.last);
83     };
84
85     net.request = function(service, method) {
86         var params = Array.prototype.slice.call(arguments, 2);
87
88         var request = new NetRequest({
89             service    : service,
90             method     : method,
91             params     : params,
92             deferred   : $q.defer(),
93             superseded : false
94         });
95
96         console.debug('egNet ' + method);
97         new OpenSRF.ClientSession(service).request({
98             async  : true,
99             method : request.method,
100             params : request.params,
101             oncomplete : function() {
102                 if (!request.superseded)
103                     request.deferred.resolve(request.last);
104             },
105             onresponse : function(r) {
106                 request.last = r.recv().content();
107                 net.handleResponse(request);
108             },
109             onerror : function(msg) {
110                 // 'msg' currently tells us very little, so don't 
111                 // bother JSON-ifying it, since there is the off
112                 // chance that JSON-ification could fail, e.g if 
113                 // the object has circular refs.
114                 var note = request.method + 
115                     ' (' + request.params + ')  failed.  See server logs.';
116                 console.error(note, msg);
117                 request.deferred.reject(note);
118             },
119             onmethoderror : function(req, statCode, statMsg) { 
120                 var msg = 'error calling method ' + 
121                     request.method + ' : ' + statCode + ' : ' + statMsg;
122                 console.error(msg);
123                 request.deferred.reject(msg);
124             }
125
126         }).send();
127
128         return request.deferred.promise;
129     }
130
131     // In addition to the service and method names, accepts a single array
132     // as the collection of API call parameters.  This array will get 
133     // expanded to individual arguments in the final server call.
134     // This is useful when the server call expects each param to be
135     // a top-level value, but the set of params is dynamic.
136     net.requestWithParamList = function(service, method, params) {
137         var args = [service, method].concat(params);
138         return net.request.apply(net, args);
139     }
140
141     return net;
142 }]);