diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f320b3f..7f41235 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,6 @@ import {Component, inject, OnInit} from '@angular/core'; -import {AuthService} from "./general/service/auth.service"; -import {ActivatedRoute, Router} from "@angular/router"; +import {AuthService} from './general/service/auth.service'; +import {ActivatedRoute, Router} from '@angular/router'; @Component({ selector: 'app-root', @@ -14,14 +14,16 @@ export class AppComponent implements OnInit { private auth: AuthService, private router: Router ) { - console.log("AppComponent.constructor") + console.log('AppComponent.constructor'); } - ngOnInit() { - if (this.auth.userSignedIn) { - this.router.navigate(["/manager"]) - } else { - this.router.navigate(["/login"]) - } + async ngOnInit() { + // console.log("AppComponent.ngOnInit"); + // if (!await this.auth.checkIfUserAuthenticated()) { + // this.router.navigate(['/login']); + // } + // console.log("AppComponent.ngOnInit end"); } + + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 34a1f0e..5c8bc50 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,8 @@ import { LoginHomeComponent } from './login-home/login-home.component'; import {MatGridListModule} from "@angular/material/grid-list"; import {MatCardModule} from "@angular/material/card"; import {MatButtonModule} from "@angular/material/button"; +import {DynamicScriptLoaderService} from './general/service/dynamic-script-loader.service'; +import {MatProgressBarModule} from '@angular/material/progress-bar'; @NgModule({ @@ -34,7 +36,8 @@ import {MatButtonModule} from "@angular/material/button"; HttpClientModule, MatGridListModule, MatCardModule, - MatButtonModule + MatButtonModule, + MatProgressBarModule ], declarations: [ AppComponent, @@ -43,6 +46,7 @@ import {MatButtonModule} from "@angular/material/button"; providers: [ DiagramService, AuthService, + DynamicScriptLoaderService, LoggedInGuard, UserPreferenceService, GoogleDriveService, diff --git a/src/app/data-access/service/google-drive.service.ts b/src/app/data-access/service/google-drive.service.ts index bc00dcd..5b45127 100644 --- a/src/app/data-access/service/google-drive.service.ts +++ b/src/app/data-access/service/google-drive.service.ts @@ -2,6 +2,7 @@ import {Injectable} from '@angular/core'; import {AuthService} from '../../general/service/auth.service'; import {HttpClient} from '@angular/common/http'; import {DiagramMetadata} from '../model/diagram-item.model'; +import {environment} from '../../../environments/environment'; declare let gapi: any; @@ -49,7 +50,11 @@ export class GoogleDriveService { public async init(): Promise { if (!this.initialized) { - // console.log('GoogleDriveService.init'); + console.log('GoogleDriveService.init token', this.auth.accessToken); + gapi.client.setToken({access_token: this.auth.accessToken}); + gapi.client.setApiKey(environment.gapi.api_key); + gapi.client.load('drive', 'v3'); + await gapi.client.load('drive', 'v3'); this.initialized = true; } @@ -99,7 +104,7 @@ export class GoogleDriveService { const endpoint = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=multipart&fields=id'; return this.http.patch(endpoint, form, { headers: { - Authorization: this.auth.getAuthorizationHeader() + Authorization: await this.auth.getAuthorizationHeader() } }).toPromise(); } else { @@ -108,7 +113,7 @@ export class GoogleDriveService { const endpoint = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id'; return this.http.post(endpoint, form, { headers: { - Authorization: this.auth.getAuthorizationHeader() + Authorization: await this.auth.getAuthorizationHeader() } }).toPromise(); } diff --git a/src/app/general/service/auth.service.ts b/src/app/general/service/auth.service.ts index 1b103ec..88d4a7c 100644 --- a/src/app/general/service/auth.service.ts +++ b/src/app/general/service/auth.service.ts @@ -1,137 +1,157 @@ import {EventEmitter, Injectable} from '@angular/core'; import {environment} from '../../../environments/environment'; import TokenClient = google.accounts.oauth2.TokenClient; +import {DynamicScriptLoaderService} from './dynamic-script-loader.service'; +import {GoogleDriveService} from '../../data-access/service/google-drive.service'; + +export type Profile = { + name: string, + email: string, +}; + +const AUTH_KEY = 'vect.AuthService.auth'; +declare let gapi: any; +const GOOGLE_PROFILE_URL = `https://www.googleapis.com/oauth2/v3/userinfo`; @Injectable() export class AuthService { + public authenticatedEvent = new EventEmitter(); + tokenClient!: TokenClient; + accessToken?: string; - authInited = false; - gapiInited = false; - userSignedIn = false; + inited = false; + userAuthenticated = false; - private profile: any; - public name!: string; - public email!: string; - private authenticated!: EventEmitter; + public profile?: Profile; - constructor() { - this.handleAuthResponse = this.handleAuthResponse.bind(this); + constructor( + protected dynamicScriptLoader: DynamicScriptLoaderService + ) { + this.handleTokenResponse = this.handleTokenResponse.bind(this); this.handleProfileResponse = this.handleProfileResponse.bind(this); } - /** - * Callback after Google Identity Services are loaded. - */ - googleOAuthInit() { + public async checkIfUserAuthenticated(): Promise { + console.log('AuthService.checkIfUserAuthenticated before', this.userAuthenticated); + if (!this.userAuthenticated) { + await this.init(); + const storedAccessToken = localStorage.getItem(AUTH_KEY); + if (storedAccessToken) { + await this.requestProfile(storedAccessToken); + this.accessToken = storedAccessToken; + } + } + console.log('AuthService.checkIfUserAuthenticated after', this.userAuthenticated); + return this.userAuthenticated; + } + + + protected async init(): Promise { + if (!this.inited) { + await this.initGoogleScripts(); + console.log('AuthService.initialize google scripts loaded'); + this.initTokenClient(); + } + } + + protected initGoogleScripts(): Promise { + console.log('AuthService.initGoogleScripts'); + const p1 = this.dynamicScriptLoader.loadScript('https://accounts.google.com/gsi/client'); + const p2 = this.dynamicScriptLoader.loadScript('https://apis.google.com/js/api.js'); + const p3 = this.dynamicScriptLoader.loadScript('https://apis.google.com/js/client:plusone.js'); + return Promise.all([p1, p2, p3]); + } + + + protected initTokenClient(): void { + console.log('AuthService.initTokenClient'); this.tokenClient = google.accounts.oauth2.initTokenClient({ client_id: environment.gapi.client_id, scope: environment.gapi.scope, - callback: this.handleAuthResponse + callback: this.handleTokenResponse }); - this.authInited = true; - console.log("GoogleUtils.gisLoaded inited") + this.inited = true; } - - async handleAuthResponse(res: any) { + private async handleTokenResponse(res: any): Promise { + console.log('AuthService.handleTokenResponse', res); if (res.error !== undefined) { - this.userSignedIn = false; + this.userAuthenticated = false; throw (res); } - console.log("GoogleUtils.handleAuthClick resp", res); - this.requestProfile(res.access_token) - if (res && res.access_token) { - gapi.client.setApiKey(environment.gapi.api_key); - gapi.client.load('drive', 'v3'); - // await gapi.client.init({ - // apiKey: environment.gapi.api_key, - // discoveryDocs: environment.gapi.discoveryDocs, - // }); - this.gapiInited = true; - } + await this.requestProfile(res.access_token); - } + // wrong location? + // gapi.client.setApiKey(environment.gapi.api_key); + // gapi.client.load('drive', 'v3'); + this.accessToken = res.access_token; + localStorage.setItem(AUTH_KEY, res.access_token); + } + } - requestProfile(accessToken: any) { - console.log("getUserProfileData", accessToken) - let promise = new Promise(function (resolve, reject) { - let request = new XMLHttpRequest(); - const url = `https://www.googleapis.com/oauth2/v3/userinfo`; - request.addEventListener("loadend", function () { - - const response = JSON.parse(this.responseText); - console.log("getUserProfileData loadend response", response) - - if (this.status === 200) { - resolve(response); - } else { - // @ts-ignore - reject(this, response); - } - }); - request.open("GET", url, true); - request.setRequestHeader('Authorization', `Bearer ${accessToken}`); - request.send(); - }); - console.log("getUserProfileData then"); + async requestProfile(accessToken: string): Promise { + console.log('AuthService.requestProfile', accessToken); - promise.then( - this.handleProfileResponse, function (errorMessage) { - console.error(errorMessage); - }); + const res = await fetch(GOOGLE_PROFILE_URL, { + method: 'get', + headers: new Headers({ + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }) + }); + if (res.ok) { + const profileResponse = await res.json(); + await this.handleProfileResponse(profileResponse); + } + console.log('AuthService.requestProfile end'); } - handleProfileResponse(profileResponse: any) { - this.profile = profileResponse; - this.email = profileResponse.email; - this.name = profileResponse.name; - this.userSignedIn = true; - this.authenticated.emit(this.profile); - console.log("getUserProfileData response", profileResponse); + + private async handleProfileResponse(profileResponse: any) { + console.log('AuthService.handleProfileResponse response', profileResponse); + this.profile = { + name: profileResponse.name, + email: profileResponse.email + }; + this.userAuthenticated = true; + console.log('AuthService.handleProfileResponse authenticatedEvent.emit', this.profile); + this.authenticatedEvent.emit(this.profile); } - /** - * Sign in the user upon button click. - */ - handleAuthClick() { - const token = gapi.client.getToken() + handleAuthClick(): void { + const token = gapi.client.getToken(); if (token) { - console.log("AuthService.handleAuthClick Skip") + console.log('AuthService.handleAuthClick Skip'); // Skip display of account chooser and consent dialog for an existing session. - this.tokenClient.requestAccessToken({prompt: '', login_hint: 'Super Hint!'}); + this.tokenClient.requestAccessToken({prompt: '', state: AUTH_KEY}); } else { - console.log("AuthService.handleAuthClick Prompt the user to select") + console.log('AuthService.handleAuthClick Prompt the user to select'); // Prompt the user to select a Google Account and ask for consent to share their data // when establishing a new session. - this.tokenClient.requestAccessToken({prompt: 'consent'}); + this.tokenClient.requestAccessToken({prompt: 'consent', state: AUTH_KEY}); } } - initialize(authenticated: EventEmitter) { - this.authenticated = authenticated; - this.googleOAuthInit(); - } - - checkIfUserAuthenticated() { - console.log("AuthService.checkIfUserAuthenticated", this.userSignedIn) - - return this.userSignedIn; + public async getAuthorizationHeader(): Promise { + if (await this.checkIfUserAuthenticated()) { + return 'Bearer ' + this.accessToken; + } else { + throw new Error('User is not authenticated'); + } } - public getAuthorizationHeader(): string { - const token = gapi.auth.getToken(); - // @ts-ignore - return token.token_type + ' ' + token.access_token; + public get allowToSignIn(): boolean { + return this.inited; } } diff --git a/src/app/general/service/dynamic-script-loader.service.ts b/src/app/general/service/dynamic-script-loader.service.ts new file mode 100644 index 0000000..9dc2406 --- /dev/null +++ b/src/app/general/service/dynamic-script-loader.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DynamicScriptLoaderService { + + public loadScript(url: string): Promise { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.onload = (): any => { + resolve(); + }; + script.onerror = () => { + reject(new Error(`Script load error for ${url}`)); + }; + document.head.appendChild(script); + }); + } + +} diff --git a/src/app/general/service/logged-in.guard.ts b/src/app/general/service/logged-in.guard.ts index d60d525..a7920eb 100644 --- a/src/app/general/service/logged-in.guard.ts +++ b/src/app/general/service/logged-in.guard.ts @@ -9,13 +9,13 @@ export class LoggedInGuard implements CanActivate { constructor( private authService: AuthService ) { - console.log("LoggedInGuard.constructor") + console.log("LoggedInGuard.constructor"); } public async canActivate(): Promise { - const canActivate = this.authService.checkIfUserAuthenticated() - console.log("LoggedInGuard.canActivate", canActivate); - return canActivate; + const userAuthenticated = await this.authService.checkIfUserAuthenticated(); + console.log("LoggedInGuard.canActivate", userAuthenticated); + return userAuthenticated; } } diff --git a/src/app/login-home/login-home.component.html b/src/app/login-home/login-home.component.html index 2212eb5..4aec442 100644 --- a/src/app/login-home/login-home.component.html +++ b/src/app/login-home/login-home.component.html @@ -12,7 +12,7 @@

- + diff --git a/src/app/login-home/login-home.component.ts b/src/app/login-home/login-home.component.ts index ffe601a..e6e6712 100644 --- a/src/app/login-home/login-home.component.ts +++ b/src/app/login-home/login-home.component.ts @@ -1,5 +1,5 @@ import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {AuthService} from "../general/service/auth.service"; +import {AuthService, Profile} from '../general/service/auth.service'; import {ActivatedRoute, Router} from "@angular/router"; @Component({ @@ -9,33 +9,33 @@ import {ActivatedRoute, Router} from "@angular/router"; }) export class LoginHomeComponent implements OnInit { - @Output() authenticated: EventEmitter = new EventEmitter(); + @Output() authenticatedEvent!: EventEmitter; constructor( - private authService: AuthService, + protected authService: AuthService, private router: Router ) { this.userAuthenticated = this.userAuthenticated.bind(this); - this.authenticated.subscribe({ - next: (value:any) => this.userAuthenticated(value), - error: (err:any) => console.error('Authentication error ' , err) + this.authenticatedEvent = this.authService.authenticatedEvent; + this.authenticatedEvent.subscribe({ + next: (value: any) => this.userAuthenticated(value), + error: (err: any) => console.error('Authentication error ' , err) }); } userAuthenticated(profile: any) { - console.log("LoginHomeComponent.userAuthenticated", profile) + console.log("LoginHomeComponent.userAuthenticated", profile); this.gotoDefault(); } gotoDefault() { + console.log("LoginHomeComponent.gotoDefault"); this.router.navigate(["/manager"]); } - ngOnInit(): void { + async ngOnInit(): Promise { console.log("LoginHomeComponent.ngOnInit") - if (!this.authService.checkIfUserAuthenticated()) { - this.authService.initialize(this.authenticated); - } else { + if (await this.authService.checkIfUserAuthenticated()) { this.gotoDefault(); } } diff --git a/src/app/manager/manager-panel/manager-panel.component.html b/src/app/manager/manager-panel/manager-panel.component.html index 5955449..8377bd6 100644 --- a/src/app/manager/manager-panel/manager-panel.component.html +++ b/src/app/manager/manager-panel/manager-panel.component.html @@ -5,7 +5,7 @@
User Name - +
diff --git a/src/app/manager/manager-panel/manager-panel.component.ts b/src/app/manager/manager-panel/manager-panel.component.ts index 41461cd..d388fdc 100644 --- a/src/app/manager/manager-panel/manager-panel.component.ts +++ b/src/app/manager/manager-panel/manager-panel.component.ts @@ -1,5 +1,5 @@ -import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {AuthService} from '../../general/service/auth.service'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {AuthService, Profile} from '../../general/service/auth.service'; import {MatDialog} from '@angular/material/dialog'; import {NewDiagramDialogComponent, NewDiagramDialogData} from '../new-diagram-dialog/new-diagram-dialog.component'; import {DiagramService} from '../../data-access/service/diagram.service'; @@ -14,20 +14,28 @@ import {DiagramItem} from '../../data-access/model/diagram-item.model'; }) export class ManagerPanelComponent implements OnInit { + @Input() name = ''; + @Output() loadingEvent = new EventEmitter(); constructor( public auth: AuthService, protected dialog: MatDialog, protected diagramService: DiagramService, - private router: Router, - protected templateService: TemplateService + private router: Router ) { } ngOnInit(): void { + if (this.auth.profile) { + this.updateProfileInfo(this.auth.profile); + } } + updateProfileInfo(profile: Profile) { + console.log('ManagerPanelComponent.handleProfileUpdate'); + this.name = profile.name; + } public createNewDiagram(): void { // console.log('ManagerPanelComponent.createNewDiagram'); diff --git a/src/index.html b/src/index.html index 1bb6ea5..10aa13c 100644 --- a/src/index.html +++ b/src/index.html @@ -8,10 +8,6 @@ - - - - diff --git a/tslint.json b/tslint.json index 277c8eb..3a0b637 100644 --- a/tslint.json +++ b/tslint.json @@ -11,6 +11,7 @@ ] }, "array-type": false, + "only-arrow-functions": false, "arrow-return-shorthand": true, "curly": true, "deprecation": { @@ -65,7 +66,7 @@ "as-needed" ], "quotemark": [ - true, + false, "single" ], "semicolon": { @@ -82,10 +83,7 @@ "named": "never" } }, - "typedef": [ - true, - "call-signature" - ], + "typedef": [], "typedef-whitespace": { "options": [ {