Fix ssh command when execited with a wget pipe
[src/xds/xds-server.git] / webapp / src / app / services / 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 IXDSDeploy {
25     boardIP: string;
26     file: string;
27 }
28
29 export interface IAgentStatus {
30     baseURL: string;
31     connected: boolean;
32     WS_connected: boolean;
33     connectionRetry: number;
34     version: string;
35 }
36
37 // Default settings
38 const DEFAULT_PORT = 8010;
39 const DEFAULT_API_KEY = "1234abcezam";
40 const API_VERSION = "v1";
41
42 @Injectable()
43 export class XDSAgentService {
44     public Status$: Observable<IAgentStatus>;
45
46     private baseRestUrl: string;
47     private wsUrl: string;
48     private connectionMaxRetry: number;
49     private apikey: string;
50     private _status: IAgentStatus = {
51         baseURL: "",
52         connected: false,
53         WS_connected: false,
54         connectionRetry: 0,
55         version: "",
56     };
57     private statusSubject = <BehaviorSubject<IAgentStatus>>new BehaviorSubject(this._status);
58
59
60     private socket: SocketIOClient.Socket;
61
62     constructor(private http: Http, private _window: Window, private alert: AlertService) {
63
64         this.Status$ = this.statusSubject.asObservable();
65
66         this.apikey = DEFAULT_API_KEY; // FIXME Add dynamic allocated key
67         this._initURLs('http://localhost:' + DEFAULT_PORT);
68     }
69
70     connect(retry: number, url?: string): Observable<IAgentStatus> {
71         if (url) {
72             this._initURLs(url);
73         }
74         this._status.connected = false;
75         this._status.connectionRetry = 0;
76         this.connectionMaxRetry = retry || 3600;   // 1 hour
77
78         // Init IO Socket connection
79         this._handleIoSocket();
80
81         // Get Version in order to check connection via a REST request
82         return this.getVersion()
83             .map((v) => {
84                 this._status.version = v.version;
85                 this.statusSubject.next(Object.assign({}, this._status));
86                 return this._status;
87             });
88     }
89
90     public getVersion(): Observable<IXDSVersion> {
91         return this._get('/version');
92     }
93
94     public deploy(dpy: IXDSDeploy) {
95         return this._post('/deploy', dpy);
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('Accept', 'application/json');
143         headers.append('Content-Type', 'application/json');
144         if (this.apikey !== "") {
145             headers.append('X-API-Key', this.apikey);
146
147         }
148
149         options.headers = headers;
150         return options;
151     }
152
153     private _checkAlive(): Observable<boolean> {
154         if (this._status.connected) {
155             return Observable.of(true);
156         }
157
158         return this.http.get(this.baseRestUrl + "/version", this._attachAuthHeaders())
159             .map((r) => this._status.connected = true)
160             .retryWhen((attempts) => {
161                 this._status.connectionRetry = 0;
162                 return attempts.flatMap(error => {
163                     this._status.connected = false;
164                     if (++this._status.connectionRetry >= this.connectionMaxRetry) {
165                         return Observable.throw("XDS local Agent not responding (url=" + this._status.baseURL + ")");
166                     } else {
167                         return Observable.timer(1000);
168                     }
169                 });
170             });
171     }
172
173     private _get(url: string): Observable<any> {
174         return this._checkAlive()
175             .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders()))
176             .map((res: Response) => res.json())
177             .catch(this._decodeError);
178     }
179     private _post(url: string, body: any): Observable<any> {
180         return this._checkAlive()
181             .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders()))
182             .map((res: Response) => res.json())
183             .catch(this._decodeError);
184     }
185     private _delete(url: string): Observable<any> {
186         return this._checkAlive()
187             .flatMap(() => this.http.delete(this.baseRestUrl + url, this._attachAuthHeaders()))
188             .map((res: Response) => res.json())
189             .catch(this._decodeError);
190     }
191
192     private _decodeError(err: Response | any) {
193         let e: string;
194         if (this._status) {
195             this._status.connected = false;
196         }
197         if (err instanceof Response) {
198             const body = err.json() || 'Server error';
199             e = body.error || JSON.stringify(body);
200             if (!e || e === "") {
201                 e = `${err.status} - ${err.statusText || 'Unknown error'}`;
202             }
203         } else if (typeof err === "object") {
204             if (err.statusText) {
205                 e = err.statusText;
206             } else if (err.error) {
207                 e = String(err.error);
208             } else {
209                 e = JSON.stringify(err);
210             }
211         } else {
212             e = err.message ? err.message : err.toString();
213         }
214         return Observable.throw(e);
215     }
216 }