Auto detect XDS-Agent tarballs and fix URL.
[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._initURLs('http://localhost:' + DEFAULT_PORT);
63     }
64
65     connect(retry: number, url?: string): Observable<IAgentStatus> {
66         if (url) {
67             this._initURLs(url);
68         }
69         //FIXME [XDS-Agent]: not implemented yet, set always as connected
70         //this._status.connected = false;
71         this._status.connected = true;
72         this._status.connectionRetry = 0;
73         this.connectionMaxRetry = retry || 3600;   // 1 hour
74
75         // Init IO Socket connection
76         this._handleIoSocket();
77
78         // Get Version in order to check connection via a REST request
79         return this.getVersion()
80             .map((v) => {
81                 this._status.version = v.version;
82                 this.statusSubject.next(Object.assign({}, this._status));
83                 return this._status;
84             });
85     }
86
87     public getVersion(): Observable<IXDSVersion> {
88         /*FIXME [XDS-Agent]: Not implemented for now
89         return this._get('/version');
90         */
91         return Observable.of({
92             version: "NOT_IMPLEMENTED",
93             apiVersion: "NOT_IMPLEMENTED",
94             gitTag: "NOT_IMPLEMENTED"
95         });
96     }
97
98     private _initURLs(url: string) {
99         this._status.baseURL = url;
100         this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION;
101         let re = this._status.baseURL.match(/http[s]?:\/\/([^\/]*)[\/]?/);
102         if (re === null || re.length < 2) {
103             this.wsUrl = '';
104             console.error('ERROR: cannot determine Websocket url');
105             return;
106         }
107         this.wsUrl = 'ws://' + re[1];
108     }
109
110     private _WSState(sts: boolean) {
111         this._status.WS_connected = sts;
112         this.statusSubject.next(Object.assign({}, this._status));
113     }
114
115     private _handleIoSocket() {
116         this.socket = io(this.wsUrl, { transports: ['websocket'] });
117
118         this.socket.on('connect_error', (res) => {
119             this._WSState(false);
120             console.error('WS Connect_error ', res);
121         });
122
123         this.socket.on('connect', (res) => {
124             this._WSState(true);
125         });
126
127         this.socket.on('disconnection', (res) => {
128             this._WSState(false);
129             this.alert.error('WS disconnection: ' + res);
130         });
131
132         this.socket.on('error', (err) => {
133             console.error('WS error:', err);
134         });
135
136     }
137
138     private _attachAuthHeaders(options?: any) {
139         options = options || {};
140         let headers = options.headers || new Headers();
141         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
142         headers.append('Access-Control-Allow-Origin', '*');
143         headers.append('Accept', 'application/json');
144         headers.append('Content-Type', 'application/json');
145         if (this.apikey !== "") {
146             headers.append('X-API-Key', this.apikey);
147
148         }
149
150         options.headers = headers;
151         return options;
152     }
153
154     private _checkAlive(): Observable<boolean> {
155         if (this._status.connected) {
156             return Observable.of(true);
157         }
158
159         return this.http.get(this._status.baseURL, this._attachAuthHeaders())
160             .map((r) => this._status.connected = true)
161             .retryWhen((attempts) => {
162                 this._status.connectionRetry = 0;
163                 return attempts.flatMap(error => {
164                     this._status.connected = false;
165                     if (++this._status.connectionRetry >= this.connectionMaxRetry) {
166                         return Observable.throw("XDS local Agent not responding (url=" + this._status.baseURL + ")");
167                     } else {
168                         return Observable.timer(1000);
169                     }
170                 });
171             });
172     }
173
174     private _get(url: string): Observable<any> {
175         return this._checkAlive()
176             .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders()))
177             .map((res: Response) => res.json())
178             .catch(this._decodeError);
179     }
180     private _post(url: string, body: any): Observable<any> {
181         return this._checkAlive()
182             .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders()))
183             .map((res: Response) => res.json())
184             .catch((error) => {
185                 return this._decodeError(error);
186             });
187     }
188     private _delete(url: string): Observable<any> {
189         return this._checkAlive()
190             .flatMap(() => this.http.delete(this.baseRestUrl + url, this._attachAuthHeaders()))
191             .map((res: Response) => res.json())
192             .catch(this._decodeError);
193     }
194
195     private _decodeError(err: Response | any) {
196         let e: string;
197         if (this._status) {
198             this._status.connected = false;
199         }
200         if (typeof err === "object") {
201             if (err.statusText) {
202                 e = err.statusText;
203             } else if (err.error) {
204                 e = String(err.error);
205             } else {
206                 e = JSON.stringify(err);
207             }
208         } else if (err instanceof Response) {
209             const body = err.json() || 'Server error';
210             const error = body.error || JSON.stringify(body);
211             e = `${err.status} - ${err.statusText || ''} ${error}`;
212         } else {
213             e = err.message ? err.message : err.toString();
214         }
215         return Observable.throw(e);
216     }
217 }