From ea84a9dc6aa0b1561ab2cc264c95d4dc3438678a Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Sun, 10 Aug 2025 01:32:50 -0600 Subject: [PATCH] Fix: Navigating Booklore on a new device requires refreshes --- .../src/app/bookdrop/bookdrop-file.service.ts | 12 +++--- .../src/app/core/service/auth.service.ts | 8 +++- .../src/app/core/service/startup.service.ts | 26 +++++++++++++ .../settings/user-management/user.service.ts | 37 ++----------------- .../github-support-dialog.html | 2 +- booklore-ui/src/main.ts | 7 ++++ 6 files changed, 50 insertions(+), 42 deletions(-) create mode 100644 booklore-ui/src/app/core/service/startup.service.ts diff --git a/booklore-ui/src/app/bookdrop/bookdrop-file.service.ts b/booklore-ui/src/app/bookdrop/bookdrop-file.service.ts index 85153586d..4873d050c 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-file.service.ts +++ b/booklore-ui/src/app/bookdrop/bookdrop-file.service.ts @@ -1,7 +1,8 @@ import {inject, Injectable, OnDestroy} from '@angular/core'; import {BehaviorSubject, Subscription} from 'rxjs'; -import {map} from 'rxjs/operators'; +import {map, filter, take} from 'rxjs/operators'; import {BookdropFileApiService} from './bookdrop-file-api.service'; +import {AuthService} from '../core/service/auth.service'; export interface BookdropFileNotification { pendingCount: number; @@ -25,14 +26,13 @@ export class BookdropFileService implements OnDestroy { ); private apiService = inject(BookdropFileApiService); + private authService = inject(AuthService); private subscriptions = new Subscription(); constructor() { - const sub = this.apiService.getNotification().subscribe({ - next: summary => this.summarySubject.next(summary), - error: err => console.warn('Failed to fetch bookdrop file summary:', err) - }); - this.subscriptions.add(sub); + this.authService.token$ + .pipe(filter(t => !!t), take(1)) + .subscribe(() => this.refresh()); } handleIncomingFile(summary: BookdropFileNotification): void { diff --git a/booklore-ui/src/app/core/service/auth.service.ts b/booklore-ui/src/app/core/service/auth.service.ts index fd23eb219..4bcc8f2ed 100644 --- a/booklore-ui/src/app/core/service/auth.service.ts +++ b/booklore-ui/src/app/core/service/auth.service.ts @@ -1,11 +1,10 @@ import {inject, Injectable, Injector} from '@angular/core'; import {HttpClient} from '@angular/common/http'; -import {Observable, tap} from 'rxjs'; +import {BehaviorSubject, Observable, tap} from 'rxjs'; import {RxStompService} from '../../shared/websocket/rx-stomp.service'; import {API_CONFIG} from '../../config/api-config'; import {createRxStompConfig} from '../../shared/websocket/rx-stomp.config'; import {OAuthService} from 'angular-oauth2-oidc'; -import {AppSettingsService} from './app-settings.service'; import {Router} from '@angular/router'; @Injectable({ @@ -21,6 +20,9 @@ export class AuthService { private oAuthService = inject(OAuthService); private router = inject(Router); + private tokenSubject = new BehaviorSubject(this.getOidcAccessToken() || this.getInternalAccessToken()); + public token$ = this.tokenSubject.asObservable(); + internalLogin(credentials: { username: string; password: string }): Observable<{ accessToken: string; refreshToken: string, isDefaultPassword: string }> { return this.http.post<{ accessToken: string; refreshToken: string, isDefaultPassword: string }>(`${this.apiUrl}/login`, credentials).pipe( tap((response) => { @@ -57,6 +59,7 @@ export class AuthService { saveInternalTokens(accessToken: string, refreshToken: string): void { localStorage.setItem('accessToken_Internal', accessToken); localStorage.setItem('refreshToken_Internal', refreshToken); + this.tokenSubject.next(accessToken); } getInternalAccessToken(): string | null { @@ -74,6 +77,7 @@ export class AuthService { logout(): void { localStorage.removeItem('accessToken_Internal'); localStorage.removeItem('refreshToken_Internal'); + this.tokenSubject.next(null); this.getRxStompService().deactivate(); if (this.oAuthService.clientId) { this.oAuthService.logOut(); diff --git a/booklore-ui/src/app/core/service/startup.service.ts b/booklore-ui/src/app/core/service/startup.service.ts new file mode 100644 index 000000000..a05a7dbb2 --- /dev/null +++ b/booklore-ui/src/app/core/service/startup.service.ts @@ -0,0 +1,26 @@ +import { Injectable, inject } from '@angular/core'; +import { AuthService } from './auth.service'; +import { UserService } from '../../settings/user-management/user.service'; +import { filter, catchError } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class StartupService { + private auth = inject(AuthService); + private userSvc = inject(UserService); + + load(): Promise { + this.auth.token$ + .pipe(filter(t => !!t)) + .subscribe(() => { + this.userSvc.getMyself() + .pipe(catchError(() => of(null))) + .subscribe(user => { + if (user) { + this.userSvc.setInitialUser(user); + } + }); + }); + return Promise.resolve(); + } +} diff --git a/booklore-ui/src/app/settings/user-management/user.service.ts b/booklore-ui/src/app/settings/user-management/user.service.ts index f56dadf83..fcc5dfc91 100644 --- a/booklore-ui/src/app/settings/user-management/user.service.ts +++ b/booklore-ui/src/app/settings/user-management/user.service.ts @@ -6,6 +6,8 @@ import {RxStompService} from '../../shared/websocket/rx-stomp.service'; import {Library} from '../../book/model/library.model'; import {catchError} from 'rxjs/operators'; import {CbxPageSpread, CbxPageViewMode, PdfPageSpread, PdfPageViewMode} from '../../book/model/book.model'; +import {filter, take} from 'rxjs/operators'; +import {AuthService} from '../../core/service/auth.service'; export interface EntityViewPreferences { global: EntityViewPreference; @@ -119,18 +121,12 @@ export class UserService { private readonly userUrl = `${API_CONFIG.BASE_URL}/api/v1/users`; private http = inject(HttpClient); - private injector = inject(Injector); - - private rxStompService?: RxStompService; private userStateSubject = new BehaviorSubject(null); userState$ = this.userStateSubject.asObservable(); - constructor() { - this.getMyself().subscribe(user => { - this.userStateSubject.next(user); - this.startWebSocket(); - }); + public setInitialUser(user: User): void { + this.userStateSubject.next(user); } getCurrentUser(): User | null { @@ -200,29 +196,4 @@ export class UserService { } }); } - - - updateUserSettingV2(userId: number, key: string, value: any): Observable { - const payload = { key, value }; - return this.http.put(`${this.userUrl}/${userId}/settings`, payload); - } - - private startWebSocket(): void { - const token = this.getToken(); - if (token) { - const rxStompService = this.getRxStompService(); - rxStompService.activate(); - } - } - - private getRxStompService(): RxStompService { - if (!this.rxStompService) { - this.rxStompService = this.injector.get(RxStompService); - } - return this.rxStompService; - } - - getToken(): string | null { - return localStorage.getItem('accessToken'); - } } diff --git a/booklore-ui/src/app/utilities/component/github-support-dialog/github-support-dialog.html b/booklore-ui/src/app/utilities/component/github-support-dialog/github-support-dialog.html index 4e6b2935a..93e2bfff4 100644 --- a/booklore-ui/src/app/utilities/component/github-support-dialog/github-support-dialog.html +++ b/booklore-ui/src/app/utilities/component/github-support-dialog/github-support-dialog.html @@ -1,4 +1,4 @@ -
+
Open Source & Community Driven diff --git a/booklore-ui/src/main.ts b/booklore-ui/src/main.ts index a356efb92..6575e2f3b 100644 --- a/booklore-ui/src/main.ts +++ b/booklore-ui/src/main.ts @@ -16,6 +16,7 @@ import {AuthService, websocketInitializer} from './app/core/service/auth.service import {provideOAuthClient} from 'angular-oauth2-oidc'; import {APP_INITIALIZER, provideAppInitializer} from '@angular/core'; import {initializeAuthFactory} from './app/auth-initializer'; +import {StartupService} from './app/core/service/startup.service'; bootstrapApplication(AppComponent, { providers: [ @@ -25,6 +26,12 @@ bootstrapApplication(AppComponent, { deps: [AuthService], multi: true }, + { + provide: APP_INITIALIZER, + useFactory: (startup: StartupService) => () => startup.load(), + deps: [StartupService], + multi: true + }, provideHttpClient(withInterceptors([AuthInterceptorService])), provideOAuthClient(), provideAppInitializer(initializeAuthFactory()),