Switch from RabbitMQ to server-sent events
(not fully working yet)
This commit is contained in:
17692
frontend/package-lock.json
generated
17692
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,6 @@
|
||||
"moment": "^2.29.1",
|
||||
"ng2-charts": "^2.4.2",
|
||||
"ngx-mapbox-gl": "^4.8.1",
|
||||
"ngx-mqtt": "^7.0.14",
|
||||
"rxjs": "~6.6.0",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.2"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { resolve } from 'dns';
|
||||
import { APIService, UserType } from '../api.service';
|
||||
import { AlertService } from '../_alert/alert.service';
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MqttService } from 'ngx-mqtt';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import * as moment from 'moment';
|
||||
import { error } from 'protractor';
|
||||
import { AlertService } from './_alert/alert.service';
|
||||
import { AlertService, AlertType } from './_alert/alert.service';
|
||||
|
||||
/*
|
||||
* ==========================
|
||||
* DEFINITION OF TYPE
|
||||
*/
|
||||
export interface ILogin {
|
||||
@@ -44,7 +43,7 @@ export enum UserType {
|
||||
export interface IUser {
|
||||
_id: string;
|
||||
name: string;
|
||||
brokerToken: string;
|
||||
eventToken: string;
|
||||
type: UserType;
|
||||
lastLogin: Date;
|
||||
twoFASecret?: string;
|
||||
@@ -84,8 +83,13 @@ export interface INotification extends Document {
|
||||
user: IUser;
|
||||
}
|
||||
|
||||
interface CustomEvent extends Event {
|
||||
data: any;
|
||||
}
|
||||
|
||||
/*
|
||||
* END OF THE DEFINITION OF TYPE
|
||||
* END OF THE TYPE DEFINITION
|
||||
* ==========================
|
||||
*/
|
||||
|
||||
@Injectable({
|
||||
@@ -94,9 +98,9 @@ export interface INotification extends Document {
|
||||
export class APIService {
|
||||
|
||||
private token: string;
|
||||
private events: EventSource | undefined;
|
||||
|
||||
username: string;
|
||||
rabbitmq: any;
|
||||
|
||||
// Passthough data (not useful for api but a way for components to share data)
|
||||
showFilter = true;
|
||||
@@ -112,7 +116,7 @@ export class APIService {
|
||||
user: IUser = {
|
||||
_id: '',
|
||||
name: '',
|
||||
brokerToken: '',
|
||||
eventToken: '',
|
||||
lastLogin: new Date(2020, 3, 1),
|
||||
type: UserType.GUEST,
|
||||
createdAt: new Date(),
|
||||
@@ -128,42 +132,66 @@ export class APIService {
|
||||
|
||||
API_ENDPOINT = 'http://192.168.178.26:8040';
|
||||
|
||||
constructor(private httpClient: HttpClient, private mqtt: MqttService, private alert: AlertService) { }
|
||||
constructor(private httpClient: HttpClient, private alert: AlertService) { }
|
||||
|
||||
private mqttInit(): void {
|
||||
// Connect with RabbitMQ after we received our user information
|
||||
this.mqtt.connect({
|
||||
hostname: '192.168.178.26',
|
||||
port: 15675,
|
||||
protocol: 'ws',
|
||||
path: '/ws',
|
||||
username: this.user.name,
|
||||
password: this.user.brokerToken
|
||||
});
|
||||
/**
|
||||
* This functions opens a new http connection that stays open, forever, to receive events from the backend.
|
||||
*/
|
||||
async subscribeToEvents() {
|
||||
let shownError = false;
|
||||
|
||||
this.mqtt.observe(this.user._id).subscribe(async message => {
|
||||
if (message !== undefined || message !== null) {
|
||||
const obj = JSON.parse(message.payload.toString());
|
||||
console.log('Received message:', obj);
|
||||
// If there is already a event, close it.
|
||||
if (this.events !== undefined) {
|
||||
this.events.close();
|
||||
}
|
||||
|
||||
if (obj.type === 'beat') {
|
||||
this.events = new EventSource(`${this.API_ENDPOINT}/user/events?token=${this.user.eventToken}`);
|
||||
|
||||
this.events.onopen = event => {
|
||||
console.info('Connection to event stream is open. Awaiting incoming events ...');
|
||||
shownError = false;
|
||||
};
|
||||
|
||||
this.events.onerror = error => {
|
||||
if (shownError) return;
|
||||
console.error('Connection to event stream has failed! Error: ' + error);
|
||||
this.alert.error('Could not subscribe to events', 'Events');
|
||||
|
||||
shownError = true;
|
||||
}
|
||||
|
||||
this.events.onmessage = async event => {
|
||||
const jsonData = JSON.parse(event.data);
|
||||
console.debug(`[SSE] ${event.type}: ${event.data}`);
|
||||
|
||||
switch (event.type) {
|
||||
case 'beat':
|
||||
if (this.beats !== undefined) {
|
||||
this.beats.push(obj);
|
||||
this.beatsEvent.next([obj]); // We just push one, so the map doesn't has to rebuild everything from scratch.
|
||||
this.beats.push(jsonData);
|
||||
this.beatsEvent.next([jsonData]); // We just push one, so the map doesn't has to rebuild everything from scratch.
|
||||
this.beatStats.totalBeats++;
|
||||
}
|
||||
} else if (obj.type === 'phone_available') {
|
||||
this.alert.dynamic(`Device ${obj.displayName} is now online`, obj.severity, 'Device');
|
||||
} else if (obj.type === 'phone_register') {
|
||||
|
||||
console.debug('Received count:', jsonData);
|
||||
break;
|
||||
case 'message':
|
||||
this.alert.info(event.data.message, 'SSE');
|
||||
break;
|
||||
case 'phone_available':
|
||||
this.alert.dynamic(`Device ${jsonData.displayName} is now online`, jsonData.severity, 'Device');
|
||||
break;
|
||||
case 'phone_register':
|
||||
await this.getPhones();
|
||||
this.alert.dynamic(`New device "${obj.displayName}"`, obj.severity, 'New device');
|
||||
} else if (obj.type === 'phone_alive') {
|
||||
this.alert.dynamic('Device is now active', obj.severity, obj.displayName);
|
||||
} else if (obj.type === 'phone_dead') {
|
||||
this.alert.dynamic('Device is now offline', obj.severity, obj.displayName);
|
||||
}
|
||||
this.alert.dynamic(`New device "${jsonData.displayName}"`, jsonData.severity, 'New device');
|
||||
break;
|
||||
case 'phone_alive':
|
||||
this.alert.dynamic('Device is now active', jsonData.severity, jsonData.displayName);
|
||||
break;
|
||||
case 'phone_dead':
|
||||
this.alert.dynamic('Device is now offline', jsonData.severity, jsonData.displayName);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -171,6 +199,7 @@ export class APIService {
|
||||
*/
|
||||
async login(username: string, password: string): Promise<ILogin> {
|
||||
return new Promise<ILogin>(async (resolve, reject) => {
|
||||
if (this.token !== undefined) reject('User is already logged in.');
|
||||
this.httpClient.post(this.API_ENDPOINT + '/user/login', { username, password }, { responseType: 'json' })
|
||||
.subscribe(async token => {
|
||||
console.log(token);
|
||||
@@ -181,9 +210,9 @@ export class APIService {
|
||||
await this.getUserInfo();
|
||||
await this.getNotifications();
|
||||
|
||||
this.mqttInit();
|
||||
this.subscribeToEvents();
|
||||
|
||||
await this.getBeats();
|
||||
await this.getBeats({ from: moment().startOf('day').unix(), to: moment().unix() });
|
||||
await this.getBeatStats();
|
||||
this.loginEvent.next(true);
|
||||
this.alert.success('Login successful', 'Login', { duration: 2 });
|
||||
@@ -385,7 +414,7 @@ export class APIService {
|
||||
});
|
||||
}
|
||||
|
||||
/* HELPER CLASSES */
|
||||
/* HELPER FUNCTIONS */
|
||||
degreesToRadians(degrees: number): number {
|
||||
return degrees * Math.PI / 180;
|
||||
}
|
||||
@@ -429,7 +458,14 @@ export class APIService {
|
||||
return this.beats[this.beats.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if user is logged in and `false` if not.
|
||||
*/
|
||||
hasSession(): boolean {
|
||||
return this.token !== undefined;
|
||||
}
|
||||
|
||||
getToken(): string {
|
||||
return this.token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export class AppComponent implements OnInit{
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.api.login('admin', '$1KDaNCDlyXAOg');
|
||||
this.alert.error('Audio test');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@@ -14,13 +14,13 @@ import { LoginComponent } from './login/login.component';
|
||||
import { MapComponent } from './map/map.component';
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { DashboardWidgetComponent } from './dashboard-widget/dashboard-widget.component';
|
||||
import { MqttModule } from 'ngx-mqtt';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
import { AdminComponent } from './admin/admin.component';
|
||||
import { AlertComponent } from './_alert/alert/alert.component';
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||
import { NotificationsComponent } from './notifications/notifications.component';
|
||||
import { BackendInterceptor } from './interceptor';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -42,7 +42,6 @@ import { NotificationsComponent } from './notifications/notifications.component'
|
||||
FontAwesomeModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
MqttModule.forRoot({}),
|
||||
NgxMapboxGLModule.withConfig({
|
||||
accessToken: 'pk.eyJ1IjoibW9uZGVpMSIsImEiOiJja2dsY2ZtaG0xZ2o5MnR0ZWs0Mm82OTBpIn0.NzDWN3P6jJLmci_v3MM1tA'
|
||||
}),
|
||||
@@ -50,7 +49,13 @@ import { NotificationsComponent } from './notifications/notifications.component'
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||
FontAwesomeModule
|
||||
],
|
||||
providers: [],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: BackendInterceptor,
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
19
frontend/src/app/interceptor.ts
Normal file
19
frontend/src/app/interceptor.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpEvent, HttpResponse, HttpRequest, HttpHandler } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { APIService } from './api.service';
|
||||
|
||||
@Injectable()
|
||||
export class BackendInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor (private api: APIService) {}
|
||||
|
||||
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (this.api.hasSession()) {
|
||||
console.debug('Inject token for', httpRequest.url);
|
||||
return next.handle(httpRequest.clone({ setHeaders: { token: this.api.getToken() } }));
|
||||
}
|
||||
|
||||
return next.handle(httpRequest);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ addEventListener('message', ({ data }) => {
|
||||
const isNearPoint = isPointInRadius(
|
||||
{ lat: beat2.coordinate[0], lng: beat2.coordinate[1] },
|
||||
{ lat: beat.coordinate[0], lng: beat.coordinate[1] },
|
||||
0.025
|
||||
0.005
|
||||
);
|
||||
|
||||
if (isNearPoint) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<mgl-map [style]="'mapbox://styles/mapbox/dark-v10'" [zoom]="[15]" [center]="[this.lastLocation[0], this.lastLocation[1]]" *ngIf="showMap">
|
||||
<mgl-map [style]="'mapbox://styles/mapbox/outdoors-v11'" [zoom]="[15]" [center]="[this.lastLocation[0], this.lastLocation[1]]" *ngIf="showMap">
|
||||
<mgl-geojson-source id="locHistory" [data]="data"></mgl-geojson-source>
|
||||
<mgl-geojson-source id="locHistoryFiltered" [data]="mostVisitData"></mgl-geojson-source>
|
||||
<mgl-geojson-source id="lastLoc" [data]="lastLocationData"></mgl-geojson-source>
|
||||
|
||||
Reference in New Issue
Block a user