import { Component, computed, inject, Injectable, signal } from '@angular/core' import { toObservable } from '@angular/core/rxjs-interop' import { ErrorService } from '@start9labs/shared' import { TuiLoader } from '@taiga-ui/core' import { TuiDialogService } from '@taiga-ui/experimental' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { catchError, EMPTY, filter, from, interval, Subscription, switchMap, takeWhile, } from 'rxjs' import { ApiService, TunnelUpdateResult } from './api/api.service' import { AuthService } from './auth.service' @Component({ template: '', imports: [TuiLoader], }) class UpdatingDialog { protected readonly text = 'StartTunnel is updating...' } @Injectable({ providedIn: 'root', }) export class UpdateService { private readonly api = inject(ApiService) private readonly auth = inject(AuthService) private readonly dialogs = inject(TuiDialogService) private readonly errorService = inject(ErrorService) readonly result = signal(null) readonly hasUpdate = computed( () => this.result()?.status === 'update-available', ) readonly installed = computed(() => this.result()?.installed ?? null) readonly candidate = computed(() => this.result()?.candidate ?? null) private polling = false private updatingDialog: Subscription | null = null constructor() { toObservable(this.auth.authenticated) .pipe(filter(Boolean)) .subscribe(() => this.initCheck()) } async checkUpdate(): Promise { const result = await this.api.checkUpdate() this.setResult(result) } async applyUpdate(): Promise { const result = await this.api.applyUpdate() this.setResult(result) } private setResult(result: TunnelUpdateResult): void { this.result.set(result) if (result.status === 'updating') { this.showUpdatingDialog() this.startPolling() } else { this.hideUpdatingDialog() } } private async initCheck(): Promise { try { await this.checkUpdate() } catch (e: any) { this.errorService.handleError(e) } } private startPolling(): void { if (this.polling) return this.polling = true interval(5000) .pipe( switchMap(() => from(this.api.checkUpdate()).pipe(catchError(() => EMPTY)), ), takeWhile(result => result.status === 'updating', true), ) .subscribe({ next: result => this.result.set(result), complete: () => { this.polling = false this.hideUpdatingDialog() }, error: () => { this.polling = false this.hideUpdatingDialog() }, }) } private showUpdatingDialog(): void { if (this.updatingDialog) return this.updatingDialog = this.dialogs .open(new PolymorpheusComponent(UpdatingDialog), { closable: false, dismissible: false, }) .subscribe({ complete: () => (this.updatingDialog = null) }) } private hideUpdatingDialog(): void { this.updatingDialog?.unsubscribe() this.updatingDialog = null } }