C
Look, in this context, everything depends very much on the task. For example, we have internal systems that are tied to Google scout and the user in principle cannot see the annex until it is authorised.I'll show you how it's done to me, you can just take the code and redo it to your needs, because there's no best practice here, just trade-offs.First, we need definitions of types for easy work with Google libraries:yarn add -D @types/{gapi,gapi.auth2,gapi.plus}
Unfortunately, the compiler sees it as a global type, but not as a characteristic. window♪ to make a file ♪ typings.d.ts in the file. src equal to main.ts:declare interface Window {
gapi: typeof gapi;
}
Everyone, gapi We're available on our own. window♪I have GapiService who initiates Google objects:import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { mergeMapTo, shareReplay, mergeMap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class GapiService {
private config: gapi.auth2.ClientConfig = {
scope: 'profile',
ux_mode: 'redirect',
redirect_uri: location.origin
};
private googleAuthObject$ = this.loadGapiAuth2Library().pipe(
mergeMapTo(this.initializeAuth2()),
mergeMapTo(this.loadGapiPlusLibrary()),
mergeMapTo(this.waitForTheGoogleAuthObjectInitializationComplete()),
shareReplay(1)
);
public getGoogleAuthObject(): Observable<gapi.auth2.GoogleAuth> {
return this.googleAuthObject$;
}
public signIn(): Observable<unknown> {
return this.googleAuthObject$.pipe(mergeMap(googleAuth => googleAuth.signIn()));
}
private loadGapiAuth2Library() {
return new Observable<void>(observer => {
window.gapi.load('auth2', () => {
observer.next();
observer.complete();
});
});
}
private loadGapiPlusLibrary() {
return new Observable<void>(observer => {
window.gapi.client
.load('plus', 'v1')
.then(() => {
observer.next();
observer.complete();
})
.catch(error => {
observer.error(error);
});
});
}
private initializeAuth2() {
return new Observable<void>(observer => {
window.gapi.auth2.init(this.config).then(
() => {
observer.next();
observer.complete();
},
error => {
observer.error(error);
}
);
});
}
private waitForTheGoogleAuthObjectInitializationComplete() {
return new Observable<gapi.auth2.GoogleAuth>(observer => {
window.gapi.auth2.getAuthInstance().then(
googleAuth => {
observer.next(googleAuth);
observer.complete();
},
error => {
observer.error(error);
}
);
});
}
}
All right, let's through the code phase:loadGapiAuth2Library - loaded. auth2 LibraryinitializeAuth2 - Initiate the library with our configuration, initialize asynchronous.loadGapiPlusLibrary We're downloading the library to work with Google Plus.waitForTheGoogleAuthObjectInitializationComplete - Waiting for the initialization of GoogleAuth, too.Some uses then/catchBut it's a pseudo-promis. Promise(c) async/await It won't work.shareReplay(1) is the result of the operator performance, i.e. if 10 times sign googleAuthObject$, there will be no 10 licks.I also have GapiVerifierServicewhich, before initializing the application, checks whether the user is authorised:import { Injectable } from '@angular/core';
import { Observable, EMPTY } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { GapiService } from './gapi.service';
import { AuthService } from './auth.service';
@Injectable({ providedIn: 'root' })
export class GapiVerifierService {
constructor(private gapiService: GapiService, private authService: AuthService) {}
public verify(): Observable<unknown> {
return this.gapiService.getGoogleAuthObject().pipe(
mergeMap(googleAuth => {
const isSignedIn = googleAuth.currentUser.get().isSignedIn();
if (isSignedIn) {
this.authService.setUser(googleAuth);
return EMPTY;
}
return this.gapiService.signIn();
})
);
}
}
What's inside? AuthService:import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { User } from '~app/shared/types';
import { GapiService } from './gapi.service';
@Injectable({ providedIn: 'root' })
export class AuthService {
public user$ = new BehaviorSubject<User | null>(null);
constructor(private gapiService: GapiService) {}
public signOut(): void {
this.gapiService
.getGoogleAuthObject()
.pipe(mergeMap(googleAuth => this.signOutFromAllAccounts(googleAuth)))
.subscribe(() => {
window.location.href = window.config.landingAppHost;
});
}
public setUser(googleAuth: gapi.auth2.GoogleAuth): void {
this.user$.next(new User(googleAuth));
}
private signOutFromAllAccounts(googleAuth: gapi.auth2.GoogleAuth) {
return new Observable<void>(observer => {
googleAuth
.signOut()
.then(() => {
observer.next();
observer.complete();
})
.catch(error => {
observer.error(error);
});
});
}
}
I've got a user redesign on the Lening page after I'm out of the system, you can miss it. What's in class? User?export class User {
public readonly id: string;
public readonly name: string;
public readonly email: string;
public readonly imageUrl: string;
public readonly givenName: string;
public readonly familyName: string;
private readonly profile: gapi.auth2.BasicProfile;
constructor(googleAuth: gapi.auth2.GoogleAuth) {
this.profile = googleAuth.currentUser.get().getBasicProfile();
this.id = this.profile.getId();
this.name = this.profile.getName();
this.email = this.profile.getEmail();
this.imageUrl = this.profile.getImageUrl();
this.givenName = this.profile.getGivenName();
this.familyName = this.profile.getFamilyName();
}
}
How do I use this when I initialize the application? It's easy, I'll go. APP_INITIALIZER:export function appInitializerFactory(
gapiVerifierService: GapiVerifierService
): () => Promise<unknown> {
return () => gapiVerifierService.verify().toPromise();
}
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
multi: true,
deps: [GapiVerifierService]
}
]
})
export class AppModule {}
If you've only got a click on the button or some other case in the annex, you've got to do almost the same thing, but in the back, you're gonna have to create some kind of guard. AuthGuard to certain URLs that will verify whether or not the user has been identified.