mirror of
https://git.leinelab.org/Mal/metasocket-cordova.git
synced 2026-03-20 14:22:26 +01:00
Release
This commit is contained in:
52
src/app/api.service.ts
Normal file
52
src/app/api.service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Token} from './token';
|
||||
import {Host} from './host';
|
||||
import {ChatMessage} from './chat.message';
|
||||
import {ChatTokenResponse} from './chat.token';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
public static userToken: string;
|
||||
|
||||
constructor(private client: HttpClient) {
|
||||
}
|
||||
|
||||
storeData(key: string, value: string): void
|
||||
{
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
getFromStorage(key: string): string {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
|
||||
getAuthToken(username: string, password: string): Observable<Token> {
|
||||
return this.client.post<Token>(Host.URL + '/token', {username, password});
|
||||
}
|
||||
|
||||
getChatToken(authToken: string): Observable<ChatTokenResponse> {
|
||||
return this.client.get<ChatTokenResponse>(
|
||||
Host.URL + '/session/chat',
|
||||
{headers: {Authorization: 'Bearer ' + authToken}}
|
||||
);
|
||||
}
|
||||
|
||||
getChatHistory(token: string, offset: number, limit: number): Observable<ChatMessage[]> {
|
||||
return this.client.get<ChatMessage[]>(
|
||||
Host.URL + '/session/chat/history?limit=' + limit + '&offset=' + offset,
|
||||
{headers: {Authorization: 'Bearer ' + token}}
|
||||
);
|
||||
}
|
||||
|
||||
deleteAuthToken(token: string): Observable<string>
|
||||
{
|
||||
return this.client.delete<string>(
|
||||
Host.URL + '/token/' + token,
|
||||
{headers: {Authorization: 'Bearer ' + token}}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,6 @@ import { NgModule } from '@angular/core';
|
||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'home',
|
||||
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'home',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<ion-app>
|
||||
<ion-router-outlet></ion-router-outlet>
|
||||
<app-chat></app-chat>
|
||||
<app-login *ngIf="getToken() === null"></app-login>
|
||||
</ion-app>
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ApiService } from './api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.scss'],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor() {}
|
||||
public static token: string = null;
|
||||
|
||||
title = 'METAsocket';
|
||||
|
||||
public constructor(private apiService: ApiService)
|
||||
{
|
||||
AppComponent.token = this.apiService.getFromStorage('token');
|
||||
}
|
||||
|
||||
public getToken(): string
|
||||
{
|
||||
return AppComponent.token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,25 @@ import { RouteReuseStrategy } from '@angular/router';
|
||||
|
||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import {ChatComponent} from './chat/chat.component';
|
||||
import {LoginComponent} from './login/login.component';
|
||||
import {TopbarComponent} from './topbar/topbar.component';
|
||||
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
||||
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
||||
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
declarations: [AppComponent, ChatComponent, LoginComponent, TopbarComponent],
|
||||
entryComponents: [],
|
||||
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
|
||||
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
|
||||
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, FormsModule, HttpClientModule],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, LocalNotifications, BackgroundMode, ForegroundService
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
7
src/app/chat.message.ts
Normal file
7
src/app/chat.message.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ChatMessage
|
||||
{
|
||||
userId: number;
|
||||
username: string;
|
||||
message: string;
|
||||
datetime: string;
|
||||
}
|
||||
5
src/app/chat.token.ts
Normal file
5
src/app/chat.token.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface ChatTokenResponse
|
||||
{
|
||||
userId: number;
|
||||
token: string;
|
||||
}
|
||||
17
src/app/chat/chat.component.html
Normal file
17
src/app/chat/chat.component.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<div id="chat">
|
||||
<app-topbar></app-topbar>
|
||||
<div #chatPostArea id="chat-post-area" (scroll)="onScroll()">
|
||||
<div *ngFor="let message of messages" class="chat-post" [class.chat-own-post]="userId === message.userId">
|
||||
<img class="chat-avatar" src="{{url}}/user/{{message.userId}}/avatar?token={{userToken}}">
|
||||
<div class="chat-message-area">
|
||||
<div class="chat-username">{{message.username}}</div>
|
||||
<div class="chat-post-message">{{message.message}}</div>
|
||||
<div class="chat-datetime">{{message.datetime}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-type-area">
|
||||
<textarea [(ngModel)]="chatText" id="chat-textarea" (keydown)="onTextInput($event)" autofocus></textarea>
|
||||
</div>
|
||||
</div>
|
||||
0
src/app/chat/chat.component.scss
Normal file
0
src/app/chat/chat.component.scss
Normal file
24
src/app/chat/chat.component.spec.ts
Normal file
24
src/app/chat/chat.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { ChatComponent } from './chat.component';
|
||||
|
||||
describe('ChatComponent', () => {
|
||||
let component: ChatComponent;
|
||||
let fixture: ComponentFixture<ChatComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChatComponent ],
|
||||
imports: [IonicModule.forRoot()]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ChatComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
162
src/app/chat/chat.component.ts
Normal file
162
src/app/chat/chat.component.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import {Plugins, AppState} from '@capacitor/core';
|
||||
import {AfterViewChecked, AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {ChatMessage} from '../chat.message';
|
||||
import {Host} from '../host';
|
||||
import {ApiService} from '../api.service';
|
||||
import {WebsocketListener} from '../websocket.listener';
|
||||
import {WebsocketService} from '../websocket.service';
|
||||
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
||||
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
||||
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
|
||||
|
||||
const {App} = Plugins;
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat',
|
||||
templateUrl: './chat.component.html',
|
||||
styleUrls: ['./chat.component.scss'],
|
||||
})
|
||||
export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, WebsocketListener {
|
||||
messages: ChatMessage[] = [];
|
||||
userToken: string;
|
||||
userId: number;
|
||||
url: string;
|
||||
|
||||
@ViewChild('chatPostArea') chatPostArea: ElementRef;
|
||||
chatText: string;
|
||||
|
||||
private oldScrollHeight = 0;
|
||||
private messageOffset = 0;
|
||||
private messageLimit = 10;
|
||||
private hasBeenReloaded = false;
|
||||
private hasFocus = true;
|
||||
|
||||
public constructor(
|
||||
private apiService: ApiService,
|
||||
private websocketService: WebsocketService,
|
||||
private localNotifications: LocalNotifications,
|
||||
private backgroundMode: BackgroundMode,
|
||||
private foregroundService: ForegroundService
|
||||
) {
|
||||
this.userToken = this.apiService.getFromStorage('token');
|
||||
this.userId = Number(this.apiService.getFromStorage('userId'));
|
||||
this.url = Host.URL;
|
||||
this.websocketService.setListener(this);
|
||||
this.websocketService.initializeSocket(this.apiService.getFromStorage('chatToken'));
|
||||
this.backgroundMode.disableBatteryOptimizations();
|
||||
this.backgroundMode.disableWebViewOptimizations();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.chatPostArea.nativeElement.scroll(0, this.chatPostArea.nativeElement.scrollHeight);
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.oldScrollHeight !== this.chatPostArea.nativeElement.scrollHeight) {
|
||||
const scrollTop = this.chatPostArea.nativeElement.scrollTop;
|
||||
const clientHeight = this.chatPostArea.nativeElement.clientHeight;
|
||||
|
||||
if (this.hasBeenReloaded) {
|
||||
this.chatPostArea.nativeElement.scroll(0, this.chatPostArea.nativeElement.scrollHeight - this.oldScrollHeight);
|
||||
this.hasBeenReloaded = false;
|
||||
} else if (scrollTop + clientHeight > this.oldScrollHeight - 10) {
|
||||
this.chatPostArea.nativeElement.scroll(0, this.oldScrollHeight);
|
||||
}
|
||||
|
||||
this.oldScrollHeight = this.chatPostArea.nativeElement.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.userToken === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.localNotifications.requestPermission();
|
||||
|
||||
this.foregroundService.start('METAsocket', 'The chat for WowApp', 'ic_stat_notification_icon_enabled');
|
||||
|
||||
this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).subscribe(
|
||||
(response) => {
|
||||
this.messages = response;
|
||||
this.messageOffset += this.messageLimit;
|
||||
}
|
||||
);
|
||||
|
||||
App.addListener('appStateChange', (state: AppState) => {
|
||||
this.hasFocus = state.isActive;
|
||||
});
|
||||
|
||||
setInterval(
|
||||
() => {
|
||||
this.websocketService.sendKeepAliveMessage();
|
||||
}, 1000 * 60 // every minute
|
||||
);
|
||||
}
|
||||
|
||||
onScroll(): void {
|
||||
if (this.chatPostArea.nativeElement.scrollTop === 0) {
|
||||
this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).subscribe(
|
||||
(response) => {
|
||||
this.messages = response.concat(this.messages);
|
||||
this.messageOffset += this.messageLimit;
|
||||
this.hasBeenReloaded = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onTextInput(event: Event): void {
|
||||
if (!(event instanceof KeyboardEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
|
||||
if (this.chatText.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.websocketService.sendChatMessage(this.chatText);
|
||||
this.chatText = '';
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onChatMessage(message: ChatMessage): void {
|
||||
this.messages.push(message);
|
||||
this.messageOffset++;
|
||||
|
||||
if (message.userId === this.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerNotification(message);
|
||||
}
|
||||
|
||||
triggerNotification(message: ChatMessage): void
|
||||
{
|
||||
this.localNotifications.schedule(
|
||||
{
|
||||
title: message.username,
|
||||
text: message.message,
|
||||
id: 1,
|
||||
priority: 2,
|
||||
lockscreen: true,
|
||||
autoClear: true,
|
||||
icon: Host.URL + '/user/' + message.userId + '/avatar?token=' + this.userToken,
|
||||
smallIcon: 'ic_stat_notification_icon_enabled',
|
||||
led: {color: '#ff00ff', on: 500, off: 500},
|
||||
trigger: { at: new Date(new Date().getTime() + 1000) },
|
||||
sound: 'file://assets/audio/murloc.wav',
|
||||
vibrate: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
5
src/app/host.ts
Normal file
5
src/app/host.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class Host
|
||||
{
|
||||
public static readonly URL: string = 'https://sabolli.de/wow/api/v1';
|
||||
public static readonly WEBSOCKET: string = 'wss://sabolli.de/metasocket';
|
||||
}
|
||||
20
src/app/login/login.component.html
Normal file
20
src/app/login/login.component.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<div id="login">
|
||||
<div id="login-form">
|
||||
<img id="metasocket-banner" src="assets/graphics/metasocket-banner.svg">
|
||||
<p *ngIf="error !== null" class="error-message">{{error}}</p>
|
||||
|
||||
<form>
|
||||
<label>
|
||||
Username
|
||||
<input name="username" [(ngModel)] = "username">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Password
|
||||
<input type="password" name="password" [(ngModel)] = "password">
|
||||
</label>
|
||||
|
||||
<input type="submit" (click)="login($event)" value="Anmelden">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
0
src/app/login/login.component.scss
Normal file
0
src/app/login/login.component.scss
Normal file
24
src/app/login/login.component.spec.ts
Normal file
24
src/app/login/login.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ],
|
||||
imports: [IonicModule.forRoot()]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
71
src/app/login/login.component.ts
Normal file
71
src/app/login/login.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ApiService } from '../api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
username = '';
|
||||
password = '';
|
||||
error: string = null;
|
||||
|
||||
constructor(private apiService: ApiService) { }
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
login(event): void
|
||||
{
|
||||
event.target.disabled = true;
|
||||
event.target.style.visibility = 'hidden';
|
||||
|
||||
this.apiService.getAuthToken(this.username, this.password).toPromise()
|
||||
.then(
|
||||
(response) => {
|
||||
this.apiService.storeData('token', response.token);
|
||||
|
||||
this.apiService.getChatToken(response.token).toPromise()
|
||||
.then(
|
||||
(chatTokenResponse) => {
|
||||
this.apiService.storeData('chatToken', chatTokenResponse.token);
|
||||
this.apiService.storeData('userId', chatTokenResponse.userId.toString());
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
this.apiService.storeData('token', null);
|
||||
this.error = error.name + ': ' + error.message;
|
||||
|
||||
event.target.disabled = false;
|
||||
event.target.style.visibility = 'visible';
|
||||
}
|
||||
);
|
||||
}
|
||||
).catch (
|
||||
(error) => {
|
||||
this.apiService.storeData('token', null);
|
||||
|
||||
switch (error.status) {
|
||||
case 401:
|
||||
this.error = 'Login fehlgeschlagen: Die Zugangsdaten sind falsch!';
|
||||
break;
|
||||
|
||||
case 0:
|
||||
this.error = 'Verbindungsfehler: Konnte keine Verbindung zum Server herstellen!';
|
||||
break;
|
||||
|
||||
default:
|
||||
this.error = 'Fehler: Etwas unfassbar schlimmes ist passiert!';
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(error);
|
||||
|
||||
event.target.disabled = false;
|
||||
event.target.style.visibility = 'visible';
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
4
src/app/socket.keepalive.message.ts
Normal file
4
src/app/socket.keepalive.message.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface SocketKeepaliveMessage
|
||||
{
|
||||
type: number;
|
||||
}
|
||||
6
src/app/socket.received.message.ts
Normal file
6
src/app/socket.received.message.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface SocketReceivedMessage {
|
||||
type: number;
|
||||
userId: number;
|
||||
message: string;
|
||||
datetime: string;
|
||||
}
|
||||
5
src/app/socket.registration.message.ts
Normal file
5
src/app/socket.registration.message.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface SocketRegistrationMessage {
|
||||
type: number;
|
||||
token: string;
|
||||
}
|
||||
|
||||
4
src/app/socket.send.message.ts
Normal file
4
src/app/socket.send.message.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface SocketSendMessage {
|
||||
type: number;
|
||||
message: string;
|
||||
}
|
||||
3
src/app/token.ts
Normal file
3
src/app/token.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface Token {
|
||||
token: string;
|
||||
}
|
||||
4
src/app/topbar/topbar.component.html
Normal file
4
src/app/topbar/topbar.component.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div id="topbar">
|
||||
<img src="assets/graphics/metasocket-banner.svg" id="metasocket-logo">
|
||||
<button id="logout-button" (click)="logout()">X</button>
|
||||
</div>
|
||||
0
src/app/topbar/topbar.component.scss
Normal file
0
src/app/topbar/topbar.component.scss
Normal file
24
src/app/topbar/topbar.component.spec.ts
Normal file
24
src/app/topbar/topbar.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { TopbarComponent } from './topbar.component';
|
||||
|
||||
describe('TopbarComponent', () => {
|
||||
let component: TopbarComponent;
|
||||
let fixture: ComponentFixture<TopbarComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TopbarComponent ],
|
||||
imports: [IonicModule.forRoot()]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TopbarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
31
src/app/topbar/topbar.component.ts
Normal file
31
src/app/topbar/topbar.component.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../api.service';
|
||||
import {AppComponent} from '../app.component';
|
||||
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
|
||||
|
||||
@Component({
|
||||
selector: 'app-topbar',
|
||||
templateUrl: './topbar.component.html',
|
||||
styleUrls: ['./topbar.component.scss']
|
||||
})
|
||||
export class TopbarComponent implements OnInit {
|
||||
@Input() token: string;
|
||||
|
||||
constructor(private apiService: ApiService, private foregroundService: ForegroundService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.apiService.deleteAuthToken(this.apiService.getFromStorage('token')).toPromise()
|
||||
.then()
|
||||
.catch(
|
||||
(error) => {console.log(error); }
|
||||
);
|
||||
this.apiService.storeData('token', null);
|
||||
this.foregroundService.stop();
|
||||
|
||||
AppComponent.token = null;
|
||||
}
|
||||
}
|
||||
6
src/app/websocket.listener.ts
Normal file
6
src/app/websocket.listener.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {ChatMessage} from './chat.message';
|
||||
|
||||
export interface WebsocketListener
|
||||
{
|
||||
onChatMessage(message: ChatMessage): void;
|
||||
}
|
||||
104
src/app/websocket.service.ts
Normal file
104
src/app/websocket.service.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Host} from './host';
|
||||
import {ChatMessage} from './chat.message';
|
||||
import {SocketRegistrationMessage} from './socket.registration.message';
|
||||
import {SocketReceivedMessage} from './socket.received.message';
|
||||
import {WebsocketListener} from './websocket.listener';
|
||||
import {SocketSendMessage} from './socket.send.message';
|
||||
import {SocketKeepaliveMessage} from './socket.keepalive.message';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebsocketService {
|
||||
private socket: WebSocket = new WebSocket(Host.WEBSOCKET);
|
||||
private userList: Map<number, string> = new Map<number, string>();
|
||||
private listener: WebsocketListener;
|
||||
private chatToken: string;
|
||||
|
||||
initializeSocket(chatToken: string): void {
|
||||
this.chatToken = chatToken;
|
||||
|
||||
this.socket = new WebSocket(Host.WEBSOCKET);
|
||||
this.socket.addEventListener('open', () => {
|
||||
this.authorize();
|
||||
});
|
||||
this.socket.addEventListener('message', (transmission: MessageEvent) => {
|
||||
this.handleIncomingTransmission(transmission);
|
||||
});
|
||||
this.socket.addEventListener(
|
||||
'close',
|
||||
() => {
|
||||
this.initializeSocket(this.chatToken);
|
||||
this.authorize();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setListener(listener: WebsocketListener): void {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
sendChatMessage(message: string): void {
|
||||
const socketMessage: SocketSendMessage = {
|
||||
type: Response.CHAT_MESSAGE,
|
||||
message
|
||||
};
|
||||
this.socket.send(JSON.stringify(socketMessage));
|
||||
}
|
||||
|
||||
sendKeepAliveMessage(): void {
|
||||
const socketMessage: SocketKeepaliveMessage = {
|
||||
type: Response.KEEP_ALIVE
|
||||
};
|
||||
|
||||
this.socket.send(JSON.stringify(socketMessage));
|
||||
}
|
||||
|
||||
private authorize(): void {
|
||||
const message: SocketRegistrationMessage = {type: Response.REGISTRATION_MESSAGE, token: this.chatToken};
|
||||
this.socket.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
private handleIncomingTransmission(transmission: MessageEvent): void {
|
||||
const response = JSON.parse(transmission.data);
|
||||
|
||||
switch (response.type) {
|
||||
case Response.CHAT_MESSAGE:
|
||||
const messageReceived: SocketReceivedMessage = response;
|
||||
|
||||
const message: ChatMessage = {
|
||||
userId: messageReceived.userId,
|
||||
username: this.userList.get(messageReceived.userId),
|
||||
datetime: messageReceived.datetime,
|
||||
message: messageReceived.message
|
||||
};
|
||||
|
||||
this.listener.onChatMessage(message);
|
||||
|
||||
break;
|
||||
|
||||
case Response.REGISTRATION_MESSAGE:
|
||||
this.userList.set(response.userId, response.username);
|
||||
break;
|
||||
|
||||
case Response.USERLIST:
|
||||
response.users.forEach(
|
||||
(user) => {
|
||||
this.userList.set(user.userId, user.username);
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Unknown message type: ' + response.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Response {
|
||||
CHAT_MESSAGE = 1,
|
||||
REGISTRATION_MESSAGE,
|
||||
USERLIST,
|
||||
KEEP_ALIVE
|
||||
}
|
||||
Reference in New Issue
Block a user