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