Approvement of reconnection.
This commit is contained in:
parent
a348f28831
commit
bed3c705cb
@ -1,8 +1,8 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<widget id="io.ionic.starter" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
<widget id="io.ionic.starter" version="1.3.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
<name>METAsocket</name>
|
<name>METAsocket</name>
|
||||||
<description>WowApp's awesome instant messenger</description>
|
<description>WowApp's awesome instant messenger</description>
|
||||||
<author email="hi@ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</author>
|
<author email="webmaster@sabolli.de" href="https://sabolli.de/metasocket/">sabolli</author>
|
||||||
<content src="index.html" />
|
<content src="index.html" />
|
||||||
<access origin="*" />
|
<access origin="*" />
|
||||||
<allow-intent href="http://*/*" />
|
<allow-intent href="http://*/*" />
|
||||||
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -1581,6 +1581,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ionic-native/app-minimize": {
|
||||||
|
"version": "5.31.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ionic-native/app-minimize/-/app-minimize-5.31.1.tgz",
|
||||||
|
"integrity": "sha512-tDr8iLCPr+MK/MJVTgyqolt5iSSpHpFHm/VPfhi0lIBAvbIbF/pRmNiQFpNUdOVKcqDxGda8zVeKaDSbnTbzQg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/cordova": "^0.0.34"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@ionic-native/background-mode": {
|
"@ionic-native/background-mode": {
|
||||||
"version": "5.31.1",
|
"version": "5.31.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ionic-native/background-mode/-/background-mode-5.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic-native/background-mode/-/background-mode-5.31.1.tgz",
|
||||||
@ -4181,6 +4189,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cordova-plugin-app-exit": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cordova-plugin-app-exit/-/cordova-plugin-app-exit-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-2gnV5Y97JBrU2f5Y/n7RDIpsUP6O+dyn6duG7JcqYcarK9QYnKUsD2ETZ9EQ6oreM8/LH6ymyP06jJTjmnWQYQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"cordova-plugin-appminimize": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cordova-plugin-appminimize/-/cordova-plugin-appminimize-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UJZ5g8iFBP42EplS0gKwAQhwr9cUfur95o6w+2NW21pjbgioj1RVZddngy7dO++ABDpkd4HMPYnJw7DqMp5rww==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cordova-plugin-background-mode": {
|
"cordova-plugin-background-mode": {
|
||||||
"version": "0.7.3",
|
"version": "0.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-background-mode/-/cordova-plugin-background-mode-0.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/cordova-plugin-background-mode/-/cordova-plugin-background-mode-0.7.3.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@angular/platform-browser-dynamic": "~11.2.0",
|
"@angular/platform-browser-dynamic": "~11.2.0",
|
||||||
"@angular/router": "~11.2.0",
|
"@angular/router": "~11.2.0",
|
||||||
"@capacitor/core": "^2.4.6",
|
"@capacitor/core": "^2.4.6",
|
||||||
|
"@ionic-native/app-minimize": "^5.31.1",
|
||||||
"@ionic-native/background-mode": "^5.31.1",
|
"@ionic-native/background-mode": "^5.31.1",
|
||||||
"@ionic-native/core": "^5.31.1",
|
"@ionic-native/core": "^5.31.1",
|
||||||
"@ionic-native/foreground-service": "^5.31.1",
|
"@ionic-native/foreground-service": "^5.31.1",
|
||||||
@ -41,6 +42,8 @@
|
|||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"cordova-android": "^9.0.0",
|
"cordova-android": "^9.0.0",
|
||||||
|
"cordova-plugin-app-exit": "0.0.2",
|
||||||
|
"cordova-plugin-appminimize": "^1.0.1",
|
||||||
"cordova-plugin-background-mode": "^0.7.3",
|
"cordova-plugin-background-mode": "^0.7.3",
|
||||||
"cordova-plugin-badge": "^0.8.8",
|
"cordova-plugin-badge": "^0.8.8",
|
||||||
"cordova-plugin-device": "^2.0.3",
|
"cordova-plugin-device": "^2.0.3",
|
||||||
@ -77,7 +80,9 @@
|
|||||||
},
|
},
|
||||||
"cordova-plugin-ionic-keyboard": {},
|
"cordova-plugin-ionic-keyboard": {},
|
||||||
"cordova-plugin-background-mode": {},
|
"cordova-plugin-background-mode": {},
|
||||||
"cordova-plugin-foreground-service": {}
|
"cordova-plugin-foreground-service": {},
|
||||||
|
"cordova-plugin-app-exit": {},
|
||||||
|
"cordova-plugin-appminimize": {}
|
||||||
},
|
},
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"android"
|
"android"
|
||||||
|
@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
|
|||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Token} from './token';
|
import {Token} from './token';
|
||||||
import {Host} from './host';
|
import {Setting} from './setting';
|
||||||
import {ChatMessage} from './chat.message';
|
import {ChatMessage} from './chat.message';
|
||||||
import {ChatTokenResponse} from './chat.token';
|
import {ChatTokenResponse} from './chat.token';
|
||||||
|
|
||||||
@ -25,19 +25,26 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAuthToken(username: string, password: string): Observable<Token> {
|
getAuthToken(username: string, password: string): Observable<Token> {
|
||||||
return this.client.post<Token>(Host.URL + '/token', {username, password});
|
return this.client.post<Token>(Setting.URL + '/token', {username, password});
|
||||||
}
|
}
|
||||||
|
|
||||||
getChatToken(authToken: string): Observable<ChatTokenResponse> {
|
getChatToken(authToken: string): Observable<ChatTokenResponse> {
|
||||||
return this.client.get<ChatTokenResponse>(
|
return this.client.get<ChatTokenResponse>(
|
||||||
Host.URL + '/session/chat',
|
Setting.URL + '/session/chat',
|
||||||
{headers: {Authorization: 'Bearer ' + authToken}}
|
{headers: {Authorization: 'Bearer ' + authToken}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChatHistory(token: string, offset: number, limit: number): Observable<ChatMessage[]> {
|
getChatHistory(token: string, offset: number, limit: number): Observable<ChatMessage[]> {
|
||||||
return this.client.get<ChatMessage[]>(
|
return this.client.get<ChatMessage[]>(
|
||||||
Host.URL + '/session/chat/history?limit=' + limit + '&offset=' + offset,
|
Setting.URL + '/session/chat/history?limit=' + limit + '&offset=' + offset + (Setting.DEBUG ? '&debug=true' : ''),
|
||||||
|
{headers: {Authorization: 'Bearer ' + token}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getChatMessagesMissed(token: string, lastMessageId: number): Observable<ChatMessage[]> {
|
||||||
|
return this.client.get<ChatMessage[]>(
|
||||||
|
Setting.URL + '/session/chat/history/missed?lastMessageId=' + lastMessageId,
|
||||||
{headers: {Authorization: 'Bearer ' + token}}
|
{headers: {Authorization: 'Bearer ' + token}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -45,7 +52,7 @@ export class ApiService {
|
|||||||
deleteAuthToken(token: string): Observable<string>
|
deleteAuthToken(token: string): Observable<string>
|
||||||
{
|
{
|
||||||
return this.client.delete<string>(
|
return this.client.delete<string>(
|
||||||
Host.URL + '/token/' + token,
|
Setting.URL + '/token/' + token,
|
||||||
{headers: {Authorization: 'Bearer ' + token}}
|
{headers: {Authorization: 'Bearer ' + token}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<app-chat></app-chat>
|
<app-chat *ngIf="getToken() !== null"></app-chat>
|
||||||
<app-login *ngIf="getToken() === null"></app-login>
|
<app-login *ngIf="getToken() === null"></app-login>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
|
@ -14,14 +14,14 @@ import {LoginComponent} from './login/login.component';
|
|||||||
import {TopbarComponent} from './topbar/topbar.component';
|
import {TopbarComponent} from './topbar/topbar.component';
|
||||||
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
||||||
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
||||||
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
|
import {AppMinimize} from '@ionic-native/app-minimize/ngx';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent, ChatComponent, LoginComponent, TopbarComponent],
|
declarations: [AppComponent, ChatComponent, LoginComponent, TopbarComponent],
|
||||||
entryComponents: [],
|
entryComponents: [],
|
||||||
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, FormsModule, HttpClientModule],
|
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, FormsModule, HttpClientModule],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, LocalNotifications, BackgroundMode, ForegroundService
|
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, LocalNotifications, BackgroundMode, AppMinimize
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export interface ChatMessage
|
export interface ChatMessage
|
||||||
{
|
{
|
||||||
|
id: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
username: string;
|
username: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<div id="chat">
|
<div id="chat">
|
||||||
<app-topbar></app-topbar>
|
<div #errorMessage id="error-message">Konnte keine Verbindung herstellen!</div>
|
||||||
|
|
||||||
<div #chatPostArea id="chat-post-area" (scroll)="onScroll()">
|
<div #chatPostArea id="chat-post-area" (scroll)="onScroll()">
|
||||||
<div *ngFor="let message of messages" class="chat-post" [class.chat-own-post]="userId === message.userId">
|
<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}}">
|
<img class="chat-avatar" src="{{url}}/user/{{message.userId}}/avatar?token={{userToken}}">
|
||||||
@ -12,6 +13,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="chat-type-area">
|
<div id="chat-type-area">
|
||||||
<textarea [(ngModel)]="chatText" id="chat-textarea" (keydown)="onTextInput($event)" autofocus></textarea>
|
<textarea [(ngModel)]="chatText" id="chat-textarea" (keydown)="onTextInput($event)" autofocus maxlength="2000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import {Plugins, AppState} from '@capacitor/core';
|
import {Plugins, AppState} from '@capacitor/core';
|
||||||
import {AfterViewChecked, AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
import {AfterViewChecked, AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||||
import {ChatMessage} from '../chat.message';
|
import {ChatMessage} from '../chat.message';
|
||||||
import {Host} from '../host';
|
import {Setting} from '../setting';
|
||||||
import {ApiService} from '../api.service';
|
import {ApiService} from '../api.service';
|
||||||
import {WebsocketListener} from '../websocket.listener';
|
import {WebsocketListener} from '../websocket.listener';
|
||||||
import {WebsocketService} from '../websocket.service';
|
import {WebsocketService} from '../websocket.service';
|
||||||
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
|
||||||
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
|
||||||
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
|
import {AppComponent} from '../app.component';
|
||||||
|
import {Platform} from '@ionic/angular';
|
||||||
|
|
||||||
const {App} = Plugins;
|
const {App} = Plugins;
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
url: string;
|
url: string;
|
||||||
|
|
||||||
@ViewChild('chatPostArea') chatPostArea: ElementRef;
|
@ViewChild('chatPostArea') chatPostArea: ElementRef;
|
||||||
|
@ViewChild('errorMessage') errorMessage: ElementRef;
|
||||||
chatText: string;
|
chatText: string;
|
||||||
|
|
||||||
private oldScrollHeight = 0;
|
private oldScrollHeight = 0;
|
||||||
@ -30,17 +32,18 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
private messageLimit = 10;
|
private messageLimit = 10;
|
||||||
private hasBeenReloaded = false;
|
private hasBeenReloaded = false;
|
||||||
private hasFocus = true;
|
private hasFocus = true;
|
||||||
|
private isReconnection = false;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private localNotifications: LocalNotifications,
|
private localNotifications: LocalNotifications,
|
||||||
private backgroundMode: BackgroundMode,
|
private backgroundMode: BackgroundMode,
|
||||||
private foregroundService: ForegroundService
|
private platform: Platform
|
||||||
) {
|
) {
|
||||||
this.userToken = this.apiService.getFromStorage('token');
|
this.userToken = this.apiService.getFromStorage('token');
|
||||||
this.userId = Number(this.apiService.getFromStorage('userId'));
|
this.userId = Number(this.apiService.getFromStorage('userId'));
|
||||||
this.url = Host.URL;
|
this.url = Setting.URL;
|
||||||
this.websocketService.setListener(this);
|
this.websocketService.setListener(this);
|
||||||
this.websocketService.initializeSocket(this.apiService.getFromStorage('chatToken'));
|
this.websocketService.initializeSocket(this.apiService.getFromStorage('chatToken'));
|
||||||
this.backgroundMode.disableBatteryOptimizations();
|
this.backgroundMode.disableBatteryOptimizations();
|
||||||
@ -74,12 +77,19 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
|
|
||||||
this.localNotifications.requestPermission();
|
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).toPromise()
|
||||||
|
.then(
|
||||||
this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).subscribe(
|
|
||||||
(response) => {
|
(response) => {
|
||||||
this.messages = response;
|
response.forEach(
|
||||||
this.messageOffset += this.messageLimit;
|
(message: ChatMessage) => {
|
||||||
|
this.messages.push(message);
|
||||||
|
this.messageOffset++;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).catch(
|
||||||
|
(error) => {
|
||||||
|
window.alert('Fehler ' + error.status + ': Verbindung zur Web-API gescheitert!');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -87,6 +97,14 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
this.hasFocus = state.isActive;
|
this.hasFocus = state.isActive;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.platform.backButton.subscribe(
|
||||||
|
() => {
|
||||||
|
if (window.confirm('Möchtest du wirklich ausloggen?')) {
|
||||||
|
this.onLogout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
setInterval(
|
setInterval(
|
||||||
() => {
|
() => {
|
||||||
this.websocketService.sendKeepAliveMessage();
|
this.websocketService.sendKeepAliveMessage();
|
||||||
@ -129,6 +147,12 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLogout() {
|
||||||
|
this.apiService.storeData('token', null);
|
||||||
|
|
||||||
|
AppComponent.token = null;
|
||||||
|
}
|
||||||
|
|
||||||
onChatMessage(message: ChatMessage): void {
|
onChatMessage(message: ChatMessage): void {
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
this.messageOffset++;
|
this.messageOffset++;
|
||||||
@ -140,6 +164,40 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
this.triggerNotification(message);
|
this.triggerNotification(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onConnection(): void
|
||||||
|
{
|
||||||
|
this.errorMessage.nativeElement.style.display = 'none';
|
||||||
|
|
||||||
|
this.apiService.getChatMessagesMissed(this.userToken, this.messages[this.messages.length - 1].id).toPromise()
|
||||||
|
.then(
|
||||||
|
(messagesMissed) => {
|
||||||
|
if (messagesMissed.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.concat(messagesMissed);
|
||||||
|
this.messageOffset += messagesMissed.length;
|
||||||
|
}
|
||||||
|
).catch(
|
||||||
|
(error) => {
|
||||||
|
console.log('Failed to load messages missed after reconnect!', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.isReconnection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReconnect(): void
|
||||||
|
{
|
||||||
|
this.isReconnection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(message: string): void
|
||||||
|
{
|
||||||
|
this.errorMessage.nativeElement.style.display = 'block';
|
||||||
|
this.errorMessage.nativeElement.innerText = message;
|
||||||
|
}
|
||||||
|
|
||||||
triggerNotification(message: ChatMessage): void
|
triggerNotification(message: ChatMessage): void
|
||||||
{
|
{
|
||||||
this.localNotifications.schedule(
|
this.localNotifications.schedule(
|
||||||
@ -150,13 +208,24 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
|
|||||||
priority: 2,
|
priority: 2,
|
||||||
lockscreen: true,
|
lockscreen: true,
|
||||||
autoClear: true,
|
autoClear: true,
|
||||||
icon: Host.URL + '/user/' + message.userId + '/avatar?token=' + this.userToken,
|
icon: Setting.URL + '/user/' + message.userId + '/avatar?token=' + this.userToken,
|
||||||
smallIcon: 'ic_stat_notification_icon_enabled',
|
smallIcon: 'ic_stat_notification_icon_enabled',
|
||||||
led: {color: '#ff00ff', on: 500, off: 500},
|
led: '#ff00ff',
|
||||||
trigger: { at: new Date(new Date().getTime() + 1000) },
|
trigger: { at: new Date(new Date().getTime() + 1000) },
|
||||||
sound: 'file://assets/audio/murloc.wav',
|
sound: 'file://assets/audio/murloc.wav',
|
||||||
vibrate: true
|
vibrate: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hasChatMessage(message: ChatMessage): boolean
|
||||||
|
{
|
||||||
|
for (const messageStored of this.messages) {
|
||||||
|
if (messageStored.id === message.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export class Host
|
|
||||||
{
|
|
||||||
public static readonly URL: string = 'https://sabolli.de/wow/api/v1';
|
|
||||||
public static readonly WEBSOCKET: string = 'wss://sabolli.de/metasocket';
|
|
||||||
}
|
|
@ -6,12 +6,12 @@
|
|||||||
<form>
|
<form>
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input name="username" [(ngModel)] = "username">
|
<input name="username" [(ngModel)] = "username" required>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input type="password" name="password" [(ngModel)] = "password">
|
<input type="password" name="password" [(ngModel)] = "password" required>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<input type="submit" (click)="login($event)" value="Anmelden">
|
<input type="submit" (click)="login($event)" value="Anmelden">
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ApiService } from '../api.service';
|
import { ApiService } from '../api.service';
|
||||||
|
import {Platform} from '@ionic/angular';
|
||||||
|
import {AppMinimize} from '@ionic-native/app-minimize/ngx';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -11,9 +13,15 @@ export class LoginComponent implements OnInit {
|
|||||||
password = '';
|
password = '';
|
||||||
error: string = null;
|
error: string = null;
|
||||||
|
|
||||||
constructor(private apiService: ApiService) { }
|
constructor(private apiService: ApiService, private platform: Platform, private appMinimize: AppMinimize) { }
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {
|
||||||
|
this.platform.backButton.subscribe(
|
||||||
|
(t) => {
|
||||||
|
this.appMinimize.minimize();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
login(event): void
|
login(event): void
|
||||||
{
|
{
|
||||||
|
6
src/app/setting.ts
Normal file
6
src/app/setting.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export class Setting
|
||||||
|
{
|
||||||
|
public static readonly DEBUG: boolean = false;
|
||||||
|
public static readonly URL: string = 'https://sabolli.de/wow/api/v1';
|
||||||
|
public static readonly WEBSOCKET: string = Setting.DEBUG ? 'wss://sabolli.de/metasocket-debug' : 'wss://sabolli.de/metasocket';
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
export interface SocketReceivedMessage {
|
export interface SocketReceivedChatMessage {
|
||||||
type: number;
|
type: number;
|
||||||
|
id: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
message: string;
|
message: string;
|
||||||
datetime: string;
|
datetime: string;
|
@ -3,4 +3,12 @@ import {ChatMessage} from './chat.message';
|
|||||||
export interface WebsocketListener
|
export interface WebsocketListener
|
||||||
{
|
{
|
||||||
onChatMessage(message: ChatMessage): void;
|
onChatMessage(message: ChatMessage): void;
|
||||||
|
|
||||||
|
onLogout(): void;
|
||||||
|
|
||||||
|
onConnection(): void;
|
||||||
|
|
||||||
|
onReconnect(): void;
|
||||||
|
|
||||||
|
onError(message: string): void;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Host} from './host';
|
import {Setting} from './setting';
|
||||||
import {ChatMessage} from './chat.message';
|
import {ChatMessage} from './chat.message';
|
||||||
import {SocketRegistrationMessage} from './socket.registration.message';
|
import {SocketRegistrationMessage} from './socket.registration.message';
|
||||||
import {SocketReceivedMessage} from './socket.received.message';
|
import {SocketReceivedChatMessage} from './socketReceivedChatMessage';
|
||||||
import {WebsocketListener} from './websocket.listener';
|
import {WebsocketListener} from './websocket.listener';
|
||||||
import {SocketSendMessage} from './socket.send.message';
|
import {SocketSendMessage} from './socket.send.message';
|
||||||
import {SocketKeepaliveMessage} from './socket.keepalive.message';
|
import {SocketKeepaliveMessage} from './socket.keepalive.message';
|
||||||
@ -11,28 +11,21 @@ import {SocketKeepaliveMessage} from './socket.keepalive.message';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class WebsocketService {
|
export class WebsocketService {
|
||||||
private socket: WebSocket = new WebSocket(Host.WEBSOCKET);
|
private socket: WebSocket = new WebSocket(Setting.WEBSOCKET);
|
||||||
private userList: Map<number, string> = new Map<number, string>();
|
private userList: Map<number, string> = new Map<number, string>();
|
||||||
private listener: WebsocketListener;
|
private listener: WebsocketListener;
|
||||||
private chatToken: string;
|
private chatToken: string;
|
||||||
|
private isReconnectDesired = true;
|
||||||
|
private lastReconnectAttempts: Date[] = [];
|
||||||
|
|
||||||
initializeSocket(chatToken: string): void {
|
initializeSocket(chatToken: string): void {
|
||||||
this.chatToken = chatToken;
|
this.chatToken = chatToken;
|
||||||
|
|
||||||
this.socket = new WebSocket(Host.WEBSOCKET);
|
this.socket = new WebSocket(Setting.WEBSOCKET);
|
||||||
this.socket.addEventListener('open', () => {
|
this.socket.addEventListener('open', () => {this.authorize(); this.listener.onConnection(); });
|
||||||
this.authorize();
|
this.socket.addEventListener('message', (transmission: MessageEvent) => {this.onMessage(transmission); });
|
||||||
});
|
this.socket.addEventListener('close', () => {this.onClose(); });
|
||||||
this.socket.addEventListener('message', (transmission: MessageEvent) => {
|
this.socket.addEventListener('error', () => {this.onError(); });
|
||||||
this.handleIncomingTransmission(transmission);
|
|
||||||
});
|
|
||||||
this.socket.addEventListener(
|
|
||||||
'close',
|
|
||||||
() => {
|
|
||||||
this.initializeSocket(this.chatToken);
|
|
||||||
this.authorize();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setListener(listener: WebsocketListener): void {
|
setListener(listener: WebsocketListener): void {
|
||||||
@ -48,6 +41,10 @@ export class WebsocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendKeepAliveMessage(): void {
|
sendKeepAliveMessage(): void {
|
||||||
|
if (this.socket === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const socketMessage: SocketKeepaliveMessage = {
|
const socketMessage: SocketKeepaliveMessage = {
|
||||||
type: Response.KEEP_ALIVE
|
type: Response.KEEP_ALIVE
|
||||||
};
|
};
|
||||||
@ -60,14 +57,15 @@ export class WebsocketService {
|
|||||||
this.socket.send(JSON.stringify(message));
|
this.socket.send(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleIncomingTransmission(transmission: MessageEvent): void {
|
private onMessage(transmission: MessageEvent): void {
|
||||||
const response = JSON.parse(transmission.data);
|
const response = JSON.parse(transmission.data);
|
||||||
|
|
||||||
switch (response.type) {
|
switch (response.type) {
|
||||||
case Response.CHAT_MESSAGE:
|
case Response.CHAT_MESSAGE:
|
||||||
const messageReceived: SocketReceivedMessage = response;
|
const messageReceived: SocketReceivedChatMessage = response;
|
||||||
|
|
||||||
const message: ChatMessage = {
|
const message: ChatMessage = {
|
||||||
|
id: messageReceived.id,
|
||||||
userId: messageReceived.userId,
|
userId: messageReceived.userId,
|
||||||
username: this.userList.get(messageReceived.userId),
|
username: this.userList.get(messageReceived.userId),
|
||||||
datetime: messageReceived.datetime,
|
datetime: messageReceived.datetime,
|
||||||
@ -94,6 +92,59 @@ export class WebsocketService {
|
|||||||
throw new Error('Unknown message type: ' + response.type);
|
throw new Error('Unknown message type: ' + response.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reconnect(): void
|
||||||
|
{
|
||||||
|
this.initializeSocket(this.chatToken);
|
||||||
|
this.authorize();
|
||||||
|
this.listener.onReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private needsAuthorizationForFurtherReconnectAttempts(): boolean {
|
||||||
|
if (this.lastReconnectAttempts.length < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date().getTime();
|
||||||
|
let recentAttempts = 0;
|
||||||
|
|
||||||
|
for (const attempt of this.lastReconnectAttempts) {
|
||||||
|
if (now - attempt.getTime() < 20000) {
|
||||||
|
recentAttempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recentAttempts >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onError(): void {
|
||||||
|
if (this.needsAuthorizationForFurtherReconnectAttempts()) {
|
||||||
|
this.lastReconnectAttempts = [];
|
||||||
|
this.isReconnectDesired = false;
|
||||||
|
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
this.isReconnectDesired = true;
|
||||||
|
this.onClose();
|
||||||
|
}, 10000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isReconnectDesired) {
|
||||||
|
this.lastReconnectAttempts.push(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listener.onError('Die Verbindung konnte nicht hergestellt werden!');
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClose(): void
|
||||||
|
{
|
||||||
|
this.listener.onError('Verbindung unterbrochen!');
|
||||||
|
|
||||||
|
if (this.isReconnectDesired) {
|
||||||
|
this.reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Response {
|
enum Response {
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
|
@keyframes error-message-entrance {
|
||||||
|
from { top: -1000px}
|
||||||
|
to { top: 0}
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: #251a25;
|
||||||
|
}
|
||||||
|
|
||||||
#chat {
|
#chat {
|
||||||
background: rgb(27, 47, 114) url("assets/graphics/bg_responsive.jpg") repeat;
|
background: rgb(27, 47, 114) url("assets/graphics/bg_responsive.jpg") repeat;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -19,7 +28,7 @@
|
|||||||
|
|
||||||
#chat-post-area {
|
#chat-post-area {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 40px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
@ -173,3 +182,17 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#error-message {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background-color: #550000;
|
||||||
|
color: white;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.9);
|
||||||
|
animation-name: error-message-entrance;
|
||||||
|
animation-duration: 1s;
|
||||||
|
display: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user