Within my application, I have implemented third-party authentication to facilitate user login and store a token in their local storage. To enhance the user experience, I am developing a service to cache profile information. This service utilizes the user's authentication token to invoke a backend method getUser()
, which retrieves the user's profile data.
The challenge arises due to a slight delay between setting the token in local storage and the application relying on it for making backend calls during initialization.
export class UserService {
private userProfileSubject = new BehaviorSubject<Enduser>(new Enduser());
userProfile$ = this.userProfileSubject.asObservable();
constructor(
private _adService: AdService,
private _authService: AuthnService) { }
setUserProfile() {
const username = this._authService.getUser();
this.userProfile$ = this._adService.getUser(username).pipe(
first(),
map(result => result[0]),
publishReplay(1),
refCount()
);
return this.userProfile$;
}
}
This synchronous method validates the local storage token and returns the respective username.
public getUser(): string {
const jwtHelper = new JwtHelperService()
const token = localStorage.getItem(environment.JWT_TOKEN_NAME);
if (!token || jwtHelper.isTokenExpired(token)) {
return null;
} else {
const t = jwtHelper.decodeToken(token);
return t.username;
}
}
Hence, this._authService.getUser();
must complete before being utilized in
this._adService.getUser(username)
.
To resolve this issue, it seems like making the getUser()
method return an Observable and utilizing techniques like takeWhile
or timer
could be beneficial. Despite attempting these solutions diligently, challenges persist.
Any assistance would be greatly appreciated.
__
Edit:
The implementation below appears functional, but the utilization of timer
feels somewhat inelegant. Therefore, exploring alternative approaches is desirable:
In user.service.ts
:
setUserProfile() {
timer(100).pipe(
concatMap(() => {
const username = this._authService.getUser();
return this._adService.getUser(username)
}),
map(res => res[0])
).subscribe(profile => {
this.userProfileSubject.next(profile);
});
}
In app.component.ts
ngOnInit
this._userService.setUserProfile();
this._userService.userProfile$.pipe(
map((user: Enduser) => this._userService.setUserPermissions(user)),
takeUntil(this.ngUnsubscribe)
).subscribe();
Edit 2: Working Solution
The method isLoggedIn()
pertains to setting local storage. Here, there is a deliberate wait for its completion before proceeding to fetch user profile details.
this._authService.isLoggedIn().pipe(
concatMap(() => {
const username = this._authService.getUser();
return this._adService.getUser(username)
}),
map(res => res[0])
).subscribe(profile => {
this.userProfileSubject.next(profile);
});
}
isLoggedIn:
isLoggedIn(state): Observable<boolean> {
...
return this.http.get(url, {withCredentials: true}).pipe(
map((res: any) => {
const token = res.mdoc.token;
if (token) {
localStorage.setItem(environment.JWT_TOKEN_NAME, token);
return true;
} else {
return false;
}
})
}