import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; import { Location } from '@angular/common'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import * as io from 'socket.io-client'; import { AlertService } from './alert.service'; import { ISdk } from './sdk.service'; // Import RxJs required methods import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/mergeMap'; export interface IXDSConfigProject { id: string; path: string; clientSyncThingID: string; type: number; label?: string; defaultSdkID?: string; } interface IXDSBuilderConfig { ip: string; port: string; syncThingID: string; } export interface IXDSFolderConfig { id: string; label: string; path: string; type: number; status?: string; isInSync?: boolean; defaultSdkID: string; // FIXME better with union but tech pb with go code //data?: IXDSPathMapConfig|IXDSCloudSyncConfig; dataPathMap?: IXDSPathMapConfig; dataCloudSync?: IXDSCloudSyncConfig; } export interface IXDSPathMapConfig { // TODO serverPath: string; } export interface IXDSCloudSyncConfig { syncThingID: string; builderSThgID?: string; } interface IXDSConfig { version: number; builder: IXDSBuilderConfig; folders: IXDSFolderConfig[]; } export interface IXDSAgentTarball { os: string; arch: string; version: string; rawVersion: string; fileUrl: string; } export interface IXDSAgentInfo { tarballs: IXDSAgentTarball[]; } export interface ISdkMessage { wsID: string; msgType: string; data: any; } export interface ICmdOutput { cmdID: string; timestamp: string; stdout: string; stderr: string; } export interface ICmdExit { cmdID: string; timestamp: string; code: number; error: string; } export interface IServerStatus { WS_connected: boolean; } const FOLDER_TYPE_CLOUDSYNC = 2; @Injectable() export class XDSServerService { public CmdOutput$ = >new Subject(); public CmdExit$ = >new Subject(); public FolderStateChange$ = >new Subject(); public Status$: Observable; private baseUrl: string; private wsUrl: string; private _status = { WS_connected: false }; private statusSubject = >new BehaviorSubject(this._status); private socket: SocketIOClient.Socket; constructor(private http: Http, private _window: Window, private alert: AlertService) { this.Status$ = this.statusSubject.asObservable(); this.baseUrl = this._window.location.origin + '/api/v1'; let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/); if (re === null || re.length < 2) { console.error('ERROR: cannot determine Websocket url'); } else { this.wsUrl = 'ws://' + re[1]; this._handleIoSocket(); this._RegisterEvents(); } } private _WSState(sts: boolean) { this._status.WS_connected = sts; this.statusSubject.next(Object.assign({}, this._status)); } private _handleIoSocket() { this.socket = io(this.wsUrl, { transports: ['websocket'] }); this.socket.on('connect_error', (res) => { this._WSState(false); console.error('WS Connect_error ', res); }); this.socket.on('connect', (res) => { this._WSState(true); }); this.socket.on('disconnection', (res) => { this._WSState(false); this.alert.error('WS disconnection: ' + res); }); this.socket.on('error', (err) => { console.error('WS error:', err); }); this.socket.on('make:output', data => { this.CmdOutput$.next(Object.assign({}, data)); }); this.socket.on('make:exit', data => { this.CmdExit$.next(Object.assign({}, data)); }); this.socket.on('exec:output', data => { this.CmdOutput$.next(Object.assign({}, data)); }); this.socket.on('exec:exit', data => { this.CmdExit$.next(Object.assign({}, data)); }); this.socket.on('event:FolderStateChanged', ev => { if (ev && ev.folder) { this.FolderStateChange$.next(Object.assign({}, ev.folder)); } }); } private _RegisterEvents() { let ev = "FolderStateChanged"; this._post('/events/register', { "name": ev }) .subscribe( res => { }, error => { this.alert.error("ERROR while registering events " + ev + ": ", error); } ); } getSdks(): Observable { return this._get('/sdks'); } getXdsAgentInfo(): Observable { return this._get('/xdsagent/info'); } getProjects(): Observable { return this._get('/folders'); } addProject(cfg: IXDSFolderConfig): Observable { return this._post('/folder', cfg); } deleteProject(id: string): Observable { return this._delete('/folder/' + id); } syncProject(id: string): Observable { return this._post('/folder/sync/' + id, {}); } exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable { return this._post('/exec', { id: prjID, rpath: dir, cmd: cmd, sdkid: sdkid || "", args: args || [], env: env || [], }); } make(prjID: string, dir: string, sdkid?: string, args?: string[], env?: string[]): Observable { return this._post('/make', { id: prjID, rpath: dir, sdkid: sdkid, args: args || [], env: env || [], }); } private _attachAuthHeaders(options?: any) { options = options || {}; let headers = options.headers || new Headers(); // headers.append('Authorization', 'Basic ' + btoa('username:password')); headers.append('Accept', 'application/json'); headers.append('Content-Type', 'application/json'); // headers.append('Access-Control-Allow-Origin', '*'); options.headers = headers; return options; } private _get(url: string): Observable { return this.http.get(this.baseUrl + url, this._attachAuthHeaders()) .map((res: Response) => res.json()) .catch(this._decodeError); } private _post(url: string, body: any): Observable { return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders()) .map((res: Response) => res.json()) .catch((error) => { return this._decodeError(error); }); } private _delete(url: string): Observable { return this.http.delete(this.baseUrl + url, this._attachAuthHeaders()) .map((res: Response) => res.json()) .catch(this._decodeError); } private _decodeError(err: any) { let e: string; if (err instanceof Response) { const body = err.json() || 'Server error'; e = body.error || JSON.stringify(body); if (!e || e === "") { e = `${err.status} - ${err.statusText || 'Unknown error'}`; } } else if (typeof err === "object") { if (err.statusText) { e = err.statusText; } else if (err.error) { e = String(err.error); } else { e = JSON.stringify(err); } } else { e = err.message ? err.message : err.toString(); } return Observable.throw(e); } }