4d9aadcf570de8ccc0d283d80346b033c77c7d31
[src/xds/xds-server.git] / webapp / src / app / common / xdsagent.service.ts
1 import { Injectable } from '@angular/core';
2 import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http';
3 import { Location } from '@angular/common';
4 import { Observable } from 'rxjs/Observable';
5 import { Subject } from 'rxjs/Subject';
6 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
7 import * as io from 'socket.io-client';
8
9 import { AlertService } from './alert.service';
10
11
12 // Import RxJs required methods
13 import 'rxjs/add/operator/map';
14 import 'rxjs/add/operator/catch';
15 import 'rxjs/add/observable/throw';
16
17 export interface IXDSVersion {
18     version: string;
19     apiVersion: string;
20     gitTag: string;
21
22 }
23
24 export interface IAgentStatus {
25     baseURL: string;
26     connected: boolean;
27     WS_connected: boolean;
28     connectionRetry: number;
29     version: string;
30 }
31
32 // Default settings
33 const DEFAULT_PORT = 8010;
34 const DEFAULT_API_KEY = "1234abcezam";
35 const API_VERSION = "v1";
36
37 @Injectable()
38 export class XDSAgentService {
39     public Status$: Observable<IAgentStatus>;
40
41     private baseRestUrl: string;
42     private wsUrl: string;
43     private connectionMaxRetry: number;
44     private apikey: string;
45     private _status: IAgentStatus = {
46         baseURL: "",
47         connected: false,
48         WS_connected: false,
49         connectionRetry: 0,
50         version: "",
51     };
52     private statusSubject = <BehaviorSubject<IAgentStatus>>new BehaviorSubject(this._status);
53
54
55     private socket: SocketIOClient.Socket;
56
57     constructor(private http: Http, private _window: Window, private alert: AlertService) {
58
59         this.Status$ = this.statusSubject.asObservable();
60
61         this.apikey = DEFAULT_API_KEY; // FIXME Add dynamic allocated key
62         this._status.baseURL = 'http://localhost:' + DEFAULT_PORT;
63         this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION;
64         let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/);
65         if (re === null || re.length < 2) {
66             console.error('ERROR: cannot determine Websocket url');
67         } else {
68             this.wsUrl = 'ws://' + re[1];
69         }
70     }
71
72     connect(retry: number, url?: string): Observable<IAgentStatus> {
73         if (url) {
74             this._status.baseURL = url;
75             this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION;
76         }
77         //FIXME [XDS-Agent]: not implemented yet, set always as connected
78         //this._status.connected = false;
79         this._status.connected = true;
80         this._status.connectionRetry = 0;
81         this.connectionMaxRetry = retry || 3600;   // 1 hour
82
83         // Init IO Socket connection
84         this._handleIoSocket();
85
86         // Get Version in order to check connection via a REST request
87         return this.getVersion()
88             .map((v) => {
89                 this._status.version = v.version;
90                 this.statusSubject.next(Object.assign({}, this._status));
91                 return this._status;
92             });
93     }
94
95     public getVersion(): Observable<IXDSVersion> {
96         /*FIXME [XDS-Agent]: Not implemented for now
97         return this._get('/version');
98         */
99         return Observable.of({
100             version: "NOT_IMPLEMENTED",
101             apiVersion: "NOT_IMPLEMENTED",
102             gitTag: "NOT_IMPLEMENTED"
103         });
104     }
105
106     private _WSState(sts: boolean) {
107         this._status.WS_connected = sts;
108         this.statusSubject.next(Object.assign({}, this._status));
109     }
110
111     private _handleIoSocket() {
112         this.socket = io(this.wsUrl, { transports: ['websocket'] });
113
114         this.socket.on('connect_error', (res) => {
115             this._WSState(false);
116             console.error('WS Connect_error ', res);
117         });
118
119         this.socket.on('connect', (res) => {
120             this._WSState(true);
121         });
122
123         this.socket.on('disconnection', (res) => {
124             this._WSState(false);
125             this.alert.error('WS disconnection: ' + res);
126         });
127
128         this.socket.on('error', (err) => {
129             console.error('WS error:', err);
130         });
131
132     }
133
134     private _attachAuthHeaders(options?: any) {
135         options = options || {};
136         let headers = options.headers || new Headers();
137         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
138         headers.append('Access-Control-Allow-Origin', '*');
139         headers.append('Accept', 'application/json');
140         headers.append('Content-Type', 'application/json');
141         if (this.apikey !== "") {
142             headers.append('X-API-Key', this.apikey);
143
144         }
145
146         options.headers = headers;
147         return options;
148     }
149
150     private _checkAlive(): Observable<boolean> {
151         if (this._status.connected) {
152             return Observable.of(true);
153         }
154
155         return this.http.get(this._status.baseURL, this._attachAuthHeaders())
156             .map((r) => this._status.connected = true)
157             .retryWhen((attempts) => {
158                 this._status.connectionRetry = 0;
159                 return attempts.flatMap(error => {
160                     this._status.connected = false;
161                     if (++this._status.connectionRetry >= this.connectionMaxRetry) {
162                         return Observable.throw("XDS local Agent not responding (url=" + this._status.baseURL + ")");
163                     } else {
164                         return Observable.timer(1000);
165                     }
166                 });
167             });
168     }
169
170     private _get(url: string): Observable<any> {
171         return this._checkAlive()
172             .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders()))
173             .map((res: Response) => res.json())
174             .catch(this._decodeError);
175     }
176     private _post(url: string, body: any): Observable<any> {
177         return this._checkAlive()
178             .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders()))
179             .map((res: Response) => res.json())
180             .catch((error) => {
181                 return this._decodeError(error);
182             });
183     }
184     private _delete(url: string): Observable<any> {
185         return this._checkAlive()
186             .flatMap(() => this.http.delete(this.baseRestUrl + url, this._attachAuthHeaders()))
187             .map((res: Response) => res.json())
188             .catch(this._decodeError);
189     }
190
191     private _decodeError(err: Response | any) {
192         let e: string;
193         if (this._status) {
194             this._status.connected = false;
195         }
196         if (typeof err === "object") {
197             if (err.statusText) {
198                 e = err.statusText;
199             } else if (err.error) {
200                 e = String(err.error);
201             } else {
202                 e = JSON.stringify(err);
203             }
204         } else if (err instanceof Response) {
205             const body = err.json() || 'Server error';
206             const error = body.error || JSON.stringify(body);
207             e = `${err.status} - ${err.statusText || ''} ${error}`;
208         } else {
209             e = err.message ? err.message : err.toString();
210         }
211         return Observable.throw(e);
212     }
213 }