]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/core/net.service.ts
LP#1775466 Angular(6) base application
[Evergreen.git] / Open-ILS / src / eg2 / src / app / core / net.service.ts
1 /**
2  *
3  * constructor(private net : NetService) {
4  *   ...
5  *   this.net.request(service, method, param1 [, param2, ...])
6  *     .subscribe(
7  *       (res) => console.log('received one resopnse: ' + res),
8  *       (err) => console.error('recived request error: ' + err),
9  *       ()    => console.log('request complete')
10  *     )
11  *   );
12  *   ...
13  *
14  *  // Example translating a net request into a promise.
15  *  this.net.request(service, method, param1)
16  *  .toPromise().then(result => console.log(result));
17  *
18  * }
19  *
20  * Each response is relayed via Observable.next().  The interface is
21  * the same for streaming and atomic requests.
22  */
23 import {Injectable, EventEmitter} from '@angular/core';
24 import {Observable} from 'rxjs/Observable';
25 import {Observer} from 'rxjs/Observer';
26 import {EventService, EgEvent} from './event.service';
27
28 // Global vars from opensrf.js
29 // These are availavble at runtime, but are not exported.
30 declare var OpenSRF, OSRF_TRANSPORT_TYPE_WS;
31
32 export class NetRequest {
33     service: string;
34     method: string;
35     params: any[];
36     observer: Observer<any>;
37     superseded = false;
38     // If set, this will be used instead of a one-off OpenSRF.ClientSession.
39     session?: any;
40     // True if we're using a single-use local session
41     localSession = true;
42
43     // Last Event encountered by this request.
44     // Most callers will not need to import Event since the parsed
45     // event will be available here.
46     evt: EgEvent;
47
48     constructor(service: string, method: string, params: any[], session?: any) {
49         this.service = service;
50         this.method = method;
51         this.params = params;
52         if (session) {
53             this.session = session;
54             this.localSession = false;
55         } else {
56             this.session = new OpenSRF.ClientSession(service);
57         }
58     }
59 }
60
61 export interface AuthExpiredEvent {
62     // request is set when the auth expiration was determined as a
63     // by-product of making an API call.
64     request?: NetRequest;
65
66     // True if this environment (e.g. browser tab) was notified of the
67     // expired auth token from an external source (e.g. another browser tab).
68     viaExternal?: boolean;
69 }
70
71 @Injectable({providedIn: 'root'})
72 export class NetService {
73
74     permFailed$: EventEmitter<NetRequest>;
75     authExpired$: EventEmitter<AuthExpiredEvent>;
76
77     // If true, permission failures are emitted via permFailed$
78     // and the active request is marked as superseded.
79     permFailedHasHandler: Boolean = false;
80
81     constructor(
82         private egEvt: EventService
83     ) {
84         this.permFailed$ = new EventEmitter<NetRequest>();
85         this.authExpired$ = new EventEmitter<AuthExpiredEvent>();
86     }
87
88     // Standard request call -- Variadic params version
89     request(service: string, method: string, ...params: any[]): Observable<any> {
90         return this.requestWithParamList(service, method, params);
91     }
92
93     // Array params version
94     requestWithParamList(service: string,
95         method: string, params: any[]): Observable<any> {
96         return this.requestCompiled(
97             new NetRequest(service, method, params));
98     }
99
100     // Request with pre-compiled NetRequest
101     requestCompiled(request: NetRequest): Observable<any> {
102         return Observable.create(
103             observer => {
104                 request.observer = observer;
105                 this.sendCompiledRequest(request);
106             }
107         );
108     }
109
110     // Send the compiled request to the server via WebSockets
111     sendCompiledRequest(request: NetRequest): void {
112         OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
113         console.debug(`Net: request ${request.method}`);
114
115         request.session.request({
116             async  : true, // WS only operates in async mode
117             method : request.method,
118             params : request.params,
119             oncomplete : () => {
120
121                 // TODO: teach opensrf.js to call cleanup() inside
122                 // disconnect() and teach Pcrud to call cleanup()
123                 // as needed to avoid long-lived session data bloat.
124                 if (request.localSession) {
125                     request.session.cleanup();
126                 }
127
128                 // A superseded request will be complete()'ed by the
129                 // superseder at a later time.
130                 if (!request.superseded) {
131                     request.observer.complete();
132                 }
133             },
134             onresponse : r => {
135                 this.dispatchResponse(request, r.recv().content());
136             },
137             onerror : errmsg => {
138                 const msg = `${request.method} failed! See server logs. ${errmsg}`;
139                 console.error(msg);
140                 request.observer.error(msg);
141             },
142             onmethoderror : (req, statCode, statMsg) => {
143                 const msg =
144                     `${request.method} failed! stat=${statCode} msg=${statMsg}`;
145                 console.error(msg);
146
147                 if (request.service === 'open-ils.pcrud'
148                     && Number(statCode) === 401) {
149                     // 401 is the PCRUD equivalent of a NO_SESSION event
150                     this.authExpired$.emit({request: request});
151                 }
152
153                 request.observer.error(msg);
154             }
155
156         }).send();
157     }
158
159     // Relay response object to the caller for typical/successful
160     // responses.  Applies special handling to response events that
161     // require global attention.
162     private dispatchResponse(request, response): void {
163         request.evt = this.egEvt.parse(response);
164
165         if (request.evt) {
166             switch (request.evt.textcode) {
167
168                 case 'NO_SESSION':
169                     console.debug(`Net emitting event: ${request.evt}`);
170                     request.observer.error(request.evt.toString());
171                     this.authExpired$.emit({request: request});
172                     return;
173
174                 case 'PERM_FAILURE':
175                     if (this.permFailedHasHandler) {
176                         console.debug(`Net emitting event: ${request.evt}`);
177                         request.superseded = true;
178                         this.permFailed$.emit(request);
179                         return;
180                     }
181             }
182         }
183
184         // Pass the response to the caller.
185         request.observer.next(response);
186     }
187 }