Overlay until log status received

This commit is contained in:
Eneko Nieto
2021-02-13 00:37:10 +01:00
parent e9d5ce4d8e
commit 77497a642d
12 changed files with 172 additions and 99 deletions

View File

@@ -0,0 +1,24 @@
.overlay {
position: fixed;
background-color: firebrick;
left: 0;
right: 0;
top: 0;
bottom: 0;
text-align: center;
}
.logo {
width: 50%;
padding-top: 100px;
}
.spinner-container {
height: 360px;
width: 390px;
margin: auto;
}
.spinner-container mat-spinner {
margin: 130px auto 0 auto;
}

View File

@@ -4,8 +4,11 @@
<div class="row">
<router-outlet></router-outlet>
</div>
<div class="row">
<router-outlet name="aux"></router-outlet>
</div>
</div>
<div *ngIf="showOverlay" class="overlay">
<img class="logo" src="/assets/img/logo.png" />
<div class="spinner-container">
<mat-spinner></mat-spinner>
</div>
</div>

View File

@@ -1,35 +1,21 @@
import { Component } from '@angular/core';
import { OAuthService, NullValidationHandler } from 'angular-oauth2-oidc';
import { Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import { authConfig } from './auth.config';
import { useHash } from '../flags';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { AuthService } from './services/auth.service';
@Component({
// tslint:disable-next-line:component-selector
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(private router: Router, private oauthService: OAuthService) {
this.configureCodeFlow();
export class AppComponent implements OnInit {
showOverlay = true;
// Automatically load user profile
this.oauthService.events
.pipe(filter((e) => e.type === 'token_received'))
.subscribe((_) => {
console.log('state', this.oauthService.state);
this.oauthService.loadUserProfile();
});
}
constructor(
private authService: AuthService
) { }
private configureCodeFlow(): void {
this.oauthService.configure(authConfig);
this.oauthService.loadDiscoveryDocumentAndTryLogin().then((_) => {
if (useHash) {
this.router.navigate(['/']);
}
});
this.oauthService.setupAutomaticSilentRefresh();
async ngOnInit(): Promise<void> {
await this.authService.isLogged;
this.showOverlay = false;
}
}

View File

@@ -1,10 +1,15 @@
<button *ngIf="logged" mat-raised-button class="btn btn-default" (click)="newTravel()">New travel</button>
<button *ngIf="isLogged" mat-raised-button class="btn btn-default" (click)="newTravel()">New travel</button>
<mat-card>
<mat-card-title>My travels</mat-card-title>
<app-list *ngIf="logged" (rowClick)="travelClicked($event)" [displayedColumns]="displayedColumns"
<div *ngIf="isLogged | async; then thenBlock; else elseBlock"></div>
<ng-template #thenBlock>
<app-list (rowClick)="travelClicked($event)" [displayedColumns]="displayedColumns"
[apiCall]="myTravelsApiCall"></app-list>
<mat-card-content *ngIf="!logged">Logueate para ver tus viajes</mat-card-content>
</ng-template>
<ng-template #elseBlock>
<mat-card-content>Logueate para ver tus viajes</mat-card-content>
</ng-template>
</mat-card>
<mat-card>

View File

@@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../../services/api.service';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import { Travel } from '../../entities/travel';
import { ApiService } from '../../services/api.service';
@Component({
templateUrl: './home.component.html',
@@ -26,16 +26,16 @@ export class HomeComponent implements OnInit {
myTravelsApiCall = this.apiService.getMyTravels;
constructor(
private route: ActivatedRoute,
// private route: ActivatedRoute,
private router: Router,
private oauthService: OAuthService,
private authService: AuthService,
private apiService: ApiService
) {}
) { }
ngOnInit(): void {
this.route.params.subscribe((p) => {
this.login = p['login'];
});
// this.route.params.subscribe((p) => {
// this.login = p['login'];
// });
}
newTravel(): void {
@@ -47,12 +47,8 @@ export class HomeComponent implements OnInit {
this.router.navigateByUrl('/travel/' + travel.id);
}
loadUserProfile(): void {
this.oauthService.loadUserProfile().then((userProfile) => (this.userProfile = userProfile));
}
get logged(): boolean {
return this.oauthService.getIdentityClaims() != null;
// return this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken();
get isLogged(): Promise<boolean> {
return this.authService.isLogged;
// return this.authService.isLogged();
}
}

View File

@@ -7,6 +7,7 @@ import { Travel, TravelId } from '../entities/travel';
import { PUBLIC_API_URL, TRAVEL_API_URL, USER_API_URL } from '../app.config';
import { ListDto } from '../entities/list-dto';
import { User } from '../entities/user';
import { AuthService } from './auth.service';
export type ApiCall<T> = (params?: { [param: string]: any }) => (Observable<ApiResponse<ListDto<T>>>);
@@ -14,7 +15,10 @@ export type ApiCall<T> = (params?: { [param: string]: any }) => (Observable<ApiR
providedIn: 'root',
})
export class ApiService {
constructor(private http: HttpClient) { }
constructor(
private authService: AuthService,
private http: HttpClient
) { }
// Las llamadas al API son arrow-functions para capturar el parámetro this en la definición
// y no en la ejecución.
@@ -63,11 +67,32 @@ export class ApiService {
this.http.get<ApiResponse<T>>(url, { params });
return response$.pipe(
retry(0), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error
);
retry(0), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error
);
}
// private callProtectedApi<T>(
// url: string,
// params?: { [param: string]: any },
// body?: any
// ): Observable<ApiResponse<T>> {
// const observable = new Observable<ApiResponse<T>>(observer => {
// this.authService.configurePromise.then(logged => {
// if (logged) {
// this.callApi<T>(url, params, body).subscribe(res => {
// observer.next(res);
// });
// }
// else {
// console.error('callProtectedApi: NOT LOGGED');
// observer.error('NOT LOGGED');
// }
// });
// });
// return observable;
// }
private handleError(error: HttpErrorResponse): Observable<never> {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { authConfig } from '../auth.config';
import { Travel } from '../entities/travel';
import { User } from '../entities/user';
@@ -7,8 +8,26 @@ import { User } from '../entities/user';
providedIn: 'root'
})
export class AuthService {
private configurePromise = null;
constructor(private oauthService: OAuthService) { }
constructor(
private oauthService: OAuthService
) { }
get isLogged(): Promise<boolean> {
if (this.configurePromise == null) {
this.configurePromise = this.configureAndTryLogin();
}
return this.configurePromise;
}
login(): void {
this.oauthService.initLoginFlow();
}
logout(): void {
this.oauthService.revokeTokenAndLogout();
}
currentUser(): User {
const claims = this.oauthService.getIdentityClaims();
@@ -16,6 +35,9 @@ export class AuthService {
}
canEditTravel(travel: Travel): boolean {
if (travel == null) {
return false;
}
const user = this.currentUser();
if (user == null) {
return false;
@@ -23,4 +45,47 @@ export class AuthService {
return user.admin || (user.id === travel.driverInfo.id);
}
private async configureAndTryLogin(): Promise<boolean> {
this.oauthService.configure(authConfig);
this.oauthService.setupAutomaticSilentRefresh();
return new Promise((resolve) => {
this.oauthService.loadDiscoveryDocumentAndTryLogin().then((_) => {
if (this.oauthService.getIdentityClaims() != null) {
if (this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken()) {
console.log('NO NEED FOR TOKEN REFRESH');
resolve(true);
}
else {
this.oauthService.refreshToken().then(() => {
console.log('TOKEN REFRESHED');
resolve(true);
}).catch(e => {
console.error('ERROR REFRESHING TOKEN');
console.error(e);
resolve(false);
});
}
}
else {
console.error('NOT LOGGED!');
resolve(false);
}
});
});
// Automatically load user profile
// this.oauthService.events
// .pipe(filter((e) => e.type === 'token_received'))
// .subscribe((_) => {
// console.log('state', this.oauthService.state);
// this.oauthService.loadUserProfile();
// this.apiService.getUser().subscribe(user => {
// console.log('USER');
// console.log(user);
// });
// });
}
}

View File

@@ -3,7 +3,7 @@
<img width="100%" src="/assets/img/logo.png" />
</mat-grid-tile>
<mat-grid-tile colspan="1" rowspan="2">
<div *ngIf="logged; then thenBlock; else elseBlock"></div>
<div *ngIf="isLogged | async; then thenBlock; else elseBlock"></div>
<ng-template #thenBlock>
{{ name }}
<button mat-raised-button class="btn btn-default" (click)="logout()">Logout</button>

View File

@@ -1,57 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { ApiService } from 'src/app/services/api.service';
import { authConfig } from '../../auth.config';
import { Component } from '@angular/core';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit {
export class HeaderComponent {
constructor(
private oauthService: OAuthService,
private apiService: ApiService
private authService: AuthService
) { }
ngOnInit(): void {
console.log('INIT logged=' + String(this.logged));
this.oauthService.configure(authConfig);
this.oauthService.loadDiscoveryDocumentAndTryLogin().then(success => {
console.log('Autologin success=' + success + ' logged=' + String(this.logged));
console.log('USER autologin');
if (this.logged) {
this.apiService.getUser().subscribe(user => {
console.log(user);
});
}
});
}
async login(): Promise<void> {
this.oauthService.loadDiscoveryDocumentAndLogin().then(success => {
console.log('USER login');
if (this.logged) {
this.apiService.getUser().subscribe(user => {
console.log(user);
});
}
});
this.authService.login();
}
logout(): void {
this.oauthService.revokeTokenAndLogout();
this.authService.logout();
}
get logged(): boolean {
return this.oauthService.getIdentityClaims() != null;
get isLogged(): Promise<boolean> {
return this.authService.isLogged;
}
get name(): any {
const claims = this.oauthService.getIdentityClaims();
if (!claims) {
return null;
}
return claims['name'];
get name(): string {
return this.authService.currentUser()?.name;
}
}

View File

@@ -19,7 +19,7 @@ import { ListDataSource } from './list.datasource';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
styleUrls: ['./list.component.css']
})
export class ListComponent<T> implements OnInit, AfterViewInit {
dataSource: ListDataSource<T>;

View File

@@ -1,9 +1,9 @@
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of, throwError } from 'rxjs';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { ApiResponse } from '../../entities/api-response';
import { ListDto } from '../../entities/list-dto';
import { ApiCall, ApiService } from '../../services/api.service';
import { ApiCall } from '../../services/api.service';
export class ListDataSource<T> implements DataSource<T> {
private listSubject = new BehaviorSubject<T[]>([]);

View File

@@ -1,6 +1,3 @@
// Use HashLocationStrategy for routing?
export const useHash = false;
// Set this to true, to use silent refresh; otherwise the example
// uses the refresh_token via an AJAX coll to get new tokens.
export const useSilentRefreshForCodeFlow = false;
export const useSilentRefreshForCodeFlow = true;