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; hostSyncThingID: string; label?: string; defaultSdkID?: string; } interface IXDSBuilderConfig { ip: string; port: string; syncThingID: string; } interface IXDSFolderConfig { id: string; label: string; path: string; type: number; syncThingID: string; builderSThgID?: string; status?: string; defaultSdkID: string; } interface IXDSConfig { version: number; builder: IXDSBuilderConfig; folders: IXDSFolderConfig[]; } export interface IXDSAgentTarball { os: 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 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(); } } 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)); }); } getSdks(): Observable { return this._get('/sdks'); } getXdsAgentInfo(): Observable { return this._get('/xdsagent/info'); } getProjects(): Observable { return this._get('/folders'); } addProject(cfg: IXDSConfigProject): Observable { let folder: IXDSFolderConfig = { id: cfg.id || null, label: cfg.label || "", path: cfg.path, type: FOLDER_TYPE_CLOUDSYNC, syncThingID: cfg.hostSyncThingID, defaultSdkID: cfg.defaultSdkID || "", }; return this._post('/folder', folder); } deleteProject(id: string): Observable { return this._delete('/folder/' + id); } exec(cmd: string, args?: string[], options?: any): Observable { return this._post('/exec', { cmd: cmd, args: args || [] }); } make(prjID: string, dir: string, args: string, sdkid?: string): Observable { return this._post('/make', { id: prjID, rpath: dir, args: args, sdkid: sdkid }); } 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 (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.json().error || 'Server error'; } return Observable.throw(e); } }