Fix: Navigating Booklore on a new device requires refreshes

This commit is contained in:
aditya.chandel
2025-08-10 01:32:50 -06:00
committed by Aditya Chandel
parent b1508493cf
commit ea84a9dc6a
6 changed files with 50 additions and 42 deletions

View File

@@ -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 {

View File

@@ -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<string | null>(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();

View File

@@ -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<void> {
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();
}
}

View File

@@ -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<User | null>(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<void> {
const payload = { key, value };
return this.http.put<void>(`${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');
}
}

View File

@@ -1,4 +1,4 @@
<div class="flex flex-col items-center justify-center p-2 md:p-4 rounded-lg border border-neutral-700" style="max-width: 700px; margin: auto;">
<div class="flex flex-col items-center justify-center p-2 md:p-6 rounded-lg border border-neutral-700" style="max-width: 700px; margin: auto;">
<div class="mb-4 w-full text-center">
<span class="inline-block px-3 py-1 rounded-full bg-blue-800/60 text-blue-200 text-xs font-semibold tracking-wide mb-2">
Open Source & Community Driven

View File

@@ -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()),