1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, throwError, BehaviorSubject, of } from 'rxjs';
import { map, catchError, retry, switchMap, tap, filter } from 'rxjs/operators';
import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError } from './errors';
import {
createTokenUrl, validateTokenUrl, CreateTokenRequest,
CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse
} from './http-entities';
import { UserCredentials, UserInfo } from '../entities';
import { MatSnackBar } from '@angular/material';
import { WINDOW } from '../window-inject-token';
export const snackBarText = {
checkFail: 'Failed to check last login',
noLogin: 'No login before!',
alreadyLogin: 'You have login already!',
invalidLogin: 'Last login is no longer invalid!',
ok: 'ok'
};
export type SnackBarTextKey = Exclude<keyof typeof snackBarText, 'ok'>;
export const TOKEN_STORAGE_KEY = 'token';
/**
* This service is only used internal in user module.
*/
@Injectable({
providedIn: 'root'
})
export class InternalUserService {
private token: string | null = null;
private userInfoSubject = new BehaviorSubject<UserInfo | null | undefined>(undefined);
readonly userInfo$: Observable<UserInfo | null> =
<Observable<UserInfo | null>>this.userInfoSubject.pipe(filter(value => value !== undefined));
get currentUserInfo(): UserInfo | null | undefined {
return this.userInfoSubject.value;
}
private openSnackBar(snackBar: MatSnackBar, textKey: SnackBarTextKey) {
setTimeout(() => snackBar.open(snackBarText[textKey], snackBarText.ok, { duration: 2000 }), 0);
}
constructor(@Inject(WINDOW) private window: Window, private httpClient: HttpClient, private router: Router, snackBar: MatSnackBar) {
const savedToken = this.window.localStorage.getItem(TOKEN_STORAGE_KEY);
if (savedToken === null) {
this.openSnackBar(snackBar, 'noLogin');
this.userInfoSubject.next(null);
} else {
this.validateToken(savedToken).subscribe(result => {
if (result === null) {
this.window.localStorage.removeItem(TOKEN_STORAGE_KEY);
this.openSnackBar(snackBar, 'invalidLogin');
this.userInfoSubject.next(null);
} else {
this.token = savedToken;
this.userInfoSubject.next(result);
this.openSnackBar(snackBar, 'alreadyLogin');
}
}, _ => {
this.openSnackBar(snackBar, 'checkFail');
this.userInfoSubject.next(null);
});
}
}
private validateToken(token: string): Observable<UserInfo | null> {
return this.httpClient.post<ValidateTokenResponse>(validateTokenUrl, <ValidateTokenRequest>{ token: token }).pipe(
retry(3),
switchMap(result => {
if (result.isValid) {
const { userInfo } = result;
if (userInfo) {
return of(userInfo);
} else {
return throwError(new Error('Wrong server response. IsValid is true but UserInfo is null.'));
}
} else {
return of(null);
}
}),
tap({
error: error => {
console.error('Failed to validate token.');
console.error(error);
}
}),
);
}
userRouteNavigate(commands: any[] | null) {
this.router.navigate([{
outlets: {
user: commands
}
}]);
}
tryLogin(credentials: UserCredentials, options: { remember: boolean } = { remember: true }): Observable<UserInfo> {
if (this.token) {
return throwError(new AlreadyLoginError());
}
return this.httpClient.post<CreateTokenResponse>(createTokenUrl, <CreateTokenRequest>credentials).pipe(
catchError((error: HttpErrorResponse) => {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred when login: ' + error.error.message);
return throwError(new BadNetworkError());
} else if (error.status === 400) {
console.error('An error occurred when login: wrong credentials.');
return throwError(new BadCredentialsError());
} else {
console.error('An unknown error occurred when login: ' + error);
return throwError(new UnknownError(error));
}
}),
map(result => {
this.token = result.token;
if (options.remember) {
this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token);
}
this.userInfoSubject.next(result.userInfo);
return result.userInfo;
})
);
}
logout() {
if (this.currentUserInfo === null) {
throw new Error('No login now. You can\'t logout.');
}
this.window.localStorage.removeItem(TOKEN_STORAGE_KEY);
this.token = null;
this.userInfoSubject.next(null);
}
}
|