import { Injectable, DOCUMENT, inject } from '@angular/core' import { HttpService, RPCOptions, isRpcError, RpcError, } from '@start9labs/shared' import { filter, firstValueFrom, Observable } from 'rxjs' import { webSocket } from 'rxjs/webSocket' import { AddForwardReq, ApiService, DeleteDeviceReq, DeleteForwardReq, DeleteSubnetReq, LoginReq, SubscribeRes, UpsertDeviceReq, UpsertSubnetReq, } from './api.service' import { AuthService } from '../auth.service' import { PATCH_CACHE } from '../patch-db/patch-db-source' @Injectable({ providedIn: 'root', }) export class LiveApiService extends ApiService { private readonly http = inject(HttpService) private readonly document = inject(DOCUMENT) private readonly auth = inject(AuthService) private readonly cache$ = inject(PATCH_CACHE) constructor() { super() } openWebsocket$(guid: string): Observable { const { location } = this.document.defaultView! const host = location.host return webSocket({ url: `wss://${host}/ws/rpc/${guid}`, }) } async subscribe(): Promise { return this.rpcRequest({ method: 'db.subscribe', params: {} }) } // auth async login(params: LoginReq): Promise { return this.rpcRequest({ method: 'auth.login', params }) } async logout(): Promise { return this.rpcRequest({ method: 'auth.logout', params: {} }) } async setPassword(params: LoginReq): Promise { return this.rpcRequest({ method: 'auth.set-password', params }) } async addSubnet(params: UpsertSubnetReq): Promise { return this.upsertSubnet(params) } async editSubnet(params: UpsertSubnetReq): Promise { return this.upsertSubnet(params) } async deleteSubnet(params: DeleteSubnetReq): Promise { return this.rpcRequest({ method: 'subnet.remove', params }) } // devices async addDevice(params: UpsertDeviceReq): Promise { return this.upsertDevice(params) } async editDevice(params: UpsertDeviceReq): Promise { return this.upsertDevice(params) } async deleteDevice(params: DeleteDeviceReq): Promise { return this.rpcRequest({ method: 'device.remove', params }) } async showDeviceConfig(params: DeleteDeviceReq): Promise { return this.rpcRequest({ method: 'device.show-config', params }) } // forwards async addForward(params: AddForwardReq): Promise { return this.rpcRequest({ method: 'port-forward.add', params }) } async deleteForward(params: DeleteForwardReq): Promise { return this.rpcRequest({ method: 'port-forward.remove', params }) } // private private async upsertSubnet(params: UpsertSubnetReq): Promise { return this.rpcRequest({ method: 'subnet.add', params }) } private async upsertDevice(params: UpsertDeviceReq): Promise { return this.rpcRequest({ method: 'device.add', params }) } private async rpcRequest( options: RPCOptions, urlOverride?: string, ): Promise { const res = await this.http.rpcRequest(options, urlOverride) const body = res.body if (isRpcError(body)) { if (body.error.code === 34) { console.error('Unauthenticated, logging out') this.auth.authenticated.set(false) } throw new RpcError(body.error) } const patchSequence = res.headers.get('x-patch-sequence') if (patchSequence) await firstValueFrom( this.cache$.pipe(filter(({ id }) => id >= Number(patchSequence))), ) return body.result } }