import {Injectable} from '@angular/core'; import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {MatTableDataSource} from '@angular/material'; import {Observable} from 'rxjs/Observable'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {CookieService} from 'ngx-cookie-service'; import {StompConfig, StompRService} from '@stomp/ng2-stompjs'; import {Message} from '@stomp/stompjs'; import {SnackBarService} from './snackbar.service'; import {IdpService} from './idp.service'; import * as t from './types/types.module'; @Injectable() export class UserService { private _loggedIn: boolean = false; public user: t.User; user$ = > new BehaviorSubject(new Object); public userState: t.UserState; userState$ = > new BehaviorSubject(new Object); public messages: string[] = []; // local copy of services public services: t.Service[] = []; public groupMap: Map = new Map(); public groupsParsed: boolean = false; // TODO put in preferences cookie autoLogin = true; constructor( public cookieService: CookieService, public http: HttpClient, public snackBar: SnackBarService, public idpService: IdpService, private _stompService: StompRService, ) { this.update(); // AUTO LOGIN this.subscribeUserState().subscribe( (state: t.UserState) => { console.log(JSON.stringify(state)); if (!state) { this.idpService.subscribeIdps().subscribe( (idps: t.IdP[]) => { if (idps.length === 1 && this.autoLogin) { this.login(idps[0]); } } ); } } ); } // DATA SERVICE API public subscribeUserState(): Observable { return this.userState$.asObservable(); } public subscribeUser(): Observable { return this.user$.asObservable(); } // PRIVATE API private initStomp() { // handle with care let login = this.user.id let passcode = this.cookieService.get('sessionid'); const stompConfig: StompConfig = { // Which server? url: 'wss://'+window.location.host+'/ws', // Headers // Typical keys: login, passcode, host headers: { login: 'webpage-client:' + login, passcode: passcode, }, // How often to heartbeat? // Interval in milliseconds, set to 0 to disable heartbeat_in: 0, // Typical value 0 - disabled heartbeat_out: 20000, // Typical value 20000 - every 20 seconds // Wait in milliseconds before attempting auto reconnect // Set to 0 to disable // Typical value 15000 (15 seconds) reconnect_delay: 15000, // Will log diagnostics on console debug: false, }; this._stompService.config = stompConfig; this._stompService.initAndConnect(); } private connectLiveUpdates() { this.initStomp(); let subscription = this._stompService.subscribe( '/exchange/users/' + this.user.id.toString() ); subscription.subscribe( (message: Message) => { let update : t.StateAPIResult = JSON.parse(message.body); if (update.error && update.error != '') { this.snackBar.open(update.error); this.messages.push(update.error); } if (update.user_state) { this.updateUserState(update.user_state); } }, (error: any) => { console.log(error) } ); } private updateUser(newUser: t.User) { this.user = newUser; this.user$.next(this.user); } private updateServices(newServices: t.Service[]){ if (this.services.length !== newServices.length) { this.services = []; for (let service of newServices) { this.services.push(service); } for (let group of this.user.groups) { this.groupMap.set(group.name, this._getServices(group.name)); } this.groupsParsed = true; } } private updateUserState(newState: t.UserState) { // did a login occur? let login = (!this._loggedIn && newState); // did a logout occur? let logout = (this._loggedIn && !newState); // -- Value updating -- if (login) { this._loggedIn = true; this.snackBar.open('Logged in'); this.connectLiveUpdates(); } if (logout) { this._logout() return } this.userState = newState; this.userState$.next(this.userState); } private _logout() { this._loggedIn = false; this.snackBar.open('Logged out'); this._stompService.disconnect(); this.services = []; this.user = null; this.userState = null; this.userState$.complete(); } private stateAPIUpdate(update: t.StateAPIResult) { if (update) { this.updateUser(update.user) this.updateUserState(update.user_state); this.updateServices(update.services); // report an occured error if (update.error) { this.snackBar.open(update.error); } } else { console.log("Got null update"); } } private _getServices(groupName: string) : t.Service[] { let services: t.Service[] = []; return this.services.filter( (service: t.Service) => { return service.groups.some( (group : t.Group) => { return group.name === groupName; } ); } ); } private getDeploymentByGroup(group: t.Group): t.Deployment | undefined { let dep = this.userState.deployments.find( (d: t.Deployment) => { return d.group === group.id; } ); return dep } // PUBLIC API public serviceDescription(service: t.Service): string { if (service.description != "") { return service.description; } return "No description"; } public getDeployment(service: t.Service): t.Deployment { return this.userState.deployments.find( dep => { if (dep.service) { return dep.service.id === service.id; } return false } ); } public getServices(group: t.Group): t.Service[] { let s = this.groupMap.get(group.name); if (s == undefined) { return [] } return s } public getGroups(): t.Group[] { if (this.user.groups) { return this.user.groups.sort( function(a,b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { return 1; } else if (a.name === b.name) { return 0; } } ); } return [] } public userInfo(): MatTableDataSource { const userInfoList = []; for (const key in this.user.userinfo) { if (this.user.userinfo.hasOwnProperty(key)) { userInfoList.push({name: key, info: this.user.userinfo[key]}); } } return new MatTableDataSource(userInfoList); } public sshKeyTable(): MatTableDataSource { return new MatTableDataSource(this.user.ssh_keys); } public loggedIn(): boolean { if (this.userState) { return true; } return false; } public update(): void { this.http.get('/backend/api/state').subscribe( (data: t.StateAPIResult) => this.stateAPIUpdate(data), (err: HttpErrorResponse) => { console.log('Error', err); this.snackBar.open('Error receiving data from the server'); } ); } public errorHandler(error: any): void { console.log(error); this.snackBar.open('Error'); } public login(idp: t.IdP) { this.idpService.setIdPPreference(idp); if (!this.loggedIn()) { window.location.href = '/backend/auth/v1/request'; } } public logout() { this.http.post('/backend/auth/v1/logout', {}).subscribe( (data: t.StateAPIResult) => this.stateAPIUpdate(data), (err) => { this.errorHandler(err); this._logout(); } ); } public sentQuestionnaire(stateItemID: number, answers: Object) { return this.http.post('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe( (data: t.UserState) => { this.snackBar.open('Uploaded questionnaire'); this.updateUserState(data); }, (err) => { this.snackBar.open('Error uploading questionnaire'); console.log(err); this.update(); } ); } public deleteUser() { return this.http.delete('/backend/api/delete_user').subscribe( (data: {deleted: boolean}) => { if (data && data.deleted) { this._logout(); this.snackBar.open('Deleted user from server'); } }, (err) => { this.snackBar.open('Error deleting user from server'); console.log(err); this.update(); } ); } }