import { Injectable, OnDestroy } from "@angular/core"; import { BehaviorSubject, Subject, concatMap, from, takeUntil, timer } from "rxjs"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { FeatureFlag } from "../../../enums/feature-flag.enum"; import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction"; import { ServerConfig } from "../../abstractions/config/server-config"; import { EnvironmentService } from "../../abstractions/environment.service"; import { StateService } from "../../abstractions/state.service"; import { ServerConfigData } from "../../models/data/server-config.data"; @Injectable() export class ConfigService implements ConfigServiceAbstraction, OnDestroy { protected _serverConfig = new BehaviorSubject(null); serverConfig$ = this._serverConfig.asObservable(); private destroy$ = new Subject(); constructor( private stateService: StateService, private configApiService: ConfigApiServiceAbstraction, private authService: AuthService, private environmentService: EnvironmentService ) { // Re-fetch the server config every hour timer(0, 1000 * 3600) .pipe(concatMap(() => from(this.fetchServerConfig()))) .subscribe((serverConfig) => { this._serverConfig.next(serverConfig); }); this.environmentService.urls.pipe(takeUntil(this.destroy$)).subscribe(() => { this.fetchServerConfig(); }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } async fetchServerConfig(): Promise { try { const response = await this.configApiService.get(); if (response != null) { const data = new ServerConfigData(response); const serverConfig = new ServerConfig(data); this._serverConfig.next(serverConfig); if ((await this.authService.getAuthStatus()) === AuthenticationStatus.LoggedOut) { return serverConfig; } await this.stateService.setServerConfig(data); } } catch { return null; } } async getFeatureFlagBool(key: FeatureFlag, defaultValue = false): Promise { return await this.getFeatureFlag(key, defaultValue); } async getFeatureFlagString(key: FeatureFlag, defaultValue = ""): Promise { return await this.getFeatureFlag(key, defaultValue); } async getFeatureFlagNumber(key: FeatureFlag, defaultValue = 0): Promise { return await this.getFeatureFlag(key, defaultValue); } private async getFeatureFlag(key: FeatureFlag, defaultValue: T): Promise { const serverConfig = await this.buildServerConfig(); if ( serverConfig == null || serverConfig.featureStates == null || serverConfig.featureStates[key] == null ) { return defaultValue; } return serverConfig.featureStates[key] as T; } private async buildServerConfig(): Promise { const data = await this.stateService.getServerConfig(); const domain = data ? new ServerConfig(data) : this._serverConfig.getValue(); if (domain == null || !domain.isValid() || domain.expiresSoon()) { const value = await this.fetchServerConfig(); return value ?? domain; } return domain; } }