diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 723d42f68..760ff9dca 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -1,5 +1,5 @@ import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; -import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ServiceWorkerModule } from '@angular/service-worker'; @@ -11,6 +11,7 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { CoreModule } from './core/core.module'; import { environment } from '../environments/environment'; +import { ErrorService } from './core/core-services/error.service'; import { httpInterceptorProviders } from './core/core-services/http-interceptors'; import { LoginModule } from './site/login/login.module'; import { OpenSlidesTranslateModule } from './core/translate/openslides-translate-module'; @@ -47,7 +48,8 @@ export function AppLoaderFactory(appLoadService: AppLoadService): () => Promise< ], providers: [ { provide: APP_INITIALIZER, useFactory: AppLoaderFactory, deps: [AppLoadService], multi: true }, - httpInterceptorProviders + httpInterceptorProviders, + { provide: ErrorHandler, useClass: ErrorService } ], bootstrap: [AppComponent] }) diff --git a/client/src/app/core/core-services/data-store.service.ts b/client/src/app/core/core-services/data-store.service.ts index 1f4d5cf89..eda7851b7 100644 --- a/client/src/app/core/core-services/data-store.service.ts +++ b/client/src/app/core/core-services/data-store.service.ts @@ -5,6 +5,7 @@ import { Observable, Subject } from 'rxjs'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { CollectionStringMapperService } from './collection-string-mapper.service'; import { Deferred } from '../promises/deferred'; +import { OpenSlidesStatusService } from './openslides-status.service'; import { RelationCacheService } from './relation-cache.service'; import { StorageService } from './storage.service'; @@ -338,7 +339,8 @@ export class DataStoreService { public constructor( private storageService: StorageService, private modelMapper: CollectionStringMapperService, - private DSUpdateManager: DataStoreUpdateManagerService + private DSUpdateManager: DataStoreUpdateManagerService, + private statusService: OpenSlidesStatusService ) {} /** @@ -662,8 +664,16 @@ export class DataStoreService { */ public async flushToStorage(changeId: number): Promise { this._maxChangeId = changeId; - await this.storageService.set(DataStoreService.cachePrefix + 'DS', this.jsonStore); - await this.storageService.set(DataStoreService.cachePrefix + 'maxChangeId', changeId); + try { + await this.storageService.set(DataStoreService.cachePrefix + 'DS', this.jsonStore); + await this.storageService.set(DataStoreService.cachePrefix + 'maxChangeId', changeId); + } catch (e) { + if (e?.name === 'QuotaExceededError') { + this.statusService.setTooLessLocalStorage(); + } else { + throw e; + } + } } public print(): void { diff --git a/client/src/app/core/core-services/error.service.ts b/client/src/app/core/core-services/error.service.ts new file mode 100644 index 000000000..052b5d1d8 --- /dev/null +++ b/client/src/app/core/core-services/error.service.ts @@ -0,0 +1,36 @@ +import { ErrorHandler, Injectable } from '@angular/core'; + +import { OpenSlidesStatusService } from './openslides-status.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ErrorService extends ErrorHandler { + // TODO: This service cannot be injected into other services since it is constructed twice. + public constructor(private statusService: OpenSlidesStatusService) { + super(); + } + + public handleError(error: any): void { + const errorInformation = { + error, + name: this.guessName(error) + }; + this.statusService.currentError.next(errorInformation); + super.handleError(error); + } + + private guessName(error: any): string | null { + if (!error) { + return; + } + + if (error.rejection?.name) { + return error.rejection.name; + } + + if (error.name) { + return error.name; + } + } +} diff --git a/client/src/app/core/core-services/openslides-status.service.ts b/client/src/app/core/core-services/openslides-status.service.ts index 056ea21c8..ef4be9176 100644 --- a/client/src/app/core/core-services/openslides-status.service.ts +++ b/client/src/app/core/core-services/openslides-status.service.ts @@ -6,6 +6,11 @@ import { History } from 'app/shared/models/core/history'; import { BannerDefinition, BannerService } from '../ui-services/banner.service'; import { Deferred } from '../promises/deferred'; +export interface ErrorInformation { + error: any; + name?: string; +} + /** * Holds information about OpenSlides. This is not included into other services to * avoid circular dependencies. @@ -18,10 +23,12 @@ export class OpenSlidesStatusService { * in History mode, saves the history point. */ private history: History = null; - private bannerDefinition: BannerDefinition = { + private historyBanner: BannerDefinition = { type: 'history' }; + private tooLessLocalStorage = false; + /** * Returns, if OpenSlides is in the history mode. */ @@ -35,6 +42,8 @@ export class OpenSlidesStatusService { public isPrioritizedClient = false; + public readonly currentError = new BehaviorSubject(null); + private _stable = new Deferred(); private _bootedSubject = new BehaviorSubject(false); @@ -63,7 +72,7 @@ export class OpenSlidesStatusService { */ public enterHistoryMode(history: History): void { this.history = history; - this.banner.addBanner(this.bannerDefinition); + this.banner.addBanner(this.historyBanner); } /** @@ -71,6 +80,13 @@ export class OpenSlidesStatusService { */ public leaveHistoryMode(): void { this.history = null; - this.banner.removeBanner(this.bannerDefinition); + this.banner.removeBanner(this.historyBanner); + } + + public setTooLessLocalStorage(): void { + if (!this.tooLessLocalStorage) { + this.tooLessLocalStorage = true; + this.banner.addBanner({ type: 'tooLessLocalStorage' }); + } } } diff --git a/client/src/app/core/ui-services/banner.service.ts b/client/src/app/core/ui-services/banner.service.ts index 06f5699e5..708d4beb9 100644 --- a/client/src/app/core/ui-services/banner.service.ts +++ b/client/src/app/core/ui-services/banner.service.ts @@ -30,10 +30,7 @@ export class BannerService { public activeBanners: BehaviorSubject = new BehaviorSubject([]); - public constructor(/*translate: TranslateService, */ offlineBroadcastService: OfflineBroadcastService) { - /*translate.onLangChange.subscribe(() => { - this.offlineBannerDefinition.text = translate.instant(this.offlineBannerDefinition.text); - });*/ + public constructor(offlineBroadcastService: OfflineBroadcastService) { offlineBroadcastService.isOfflineObservable.subscribe(offline => { if (offline) { this.addBanner(this.offlineBannerDefinition); diff --git a/client/src/app/shared/components/banner/banner.component.html b/client/src/app/shared/components/banner/banner.component.html index 80db9aba3..f2fc5ec52 100644 --- a/client/src/app/shared/components/banner/banner.component.html +++ b/client/src/app/shared/components/banner/banner.component.html @@ -3,6 +3,7 @@ class="banner" [ngClass]="[ banner.type === 'history' ? 'history-mode-indicator' : '', + banner.type === 'tooLessLocalStorage' ? 'too-less-local-storage-indicator' : '', banner.class ? banner.class : '', banner.largerOnMobileView ? 'larger-on-mobile' : '' ]" @@ -13,6 +14,10 @@ ({{ getHistoryTimestamp() }}) {{ 'Exit' | translate }} + + {{ "QuotaExceededError: The local storage's quota is too low" | translate }} + {{ 'Click here for more information' | translate }} +