/* * Veskslop daemon — auth state for the web UI. * * Owns at most one QR login session. The terminal tool and the web UI share * the session class. The token file stays the single credential store. */ import { existsSync } from "node:fs"; import { isTerminalQrState, QrLoginSession, type QrLoginState, type QrLoginUpdate } from "./qr-session.js"; export interface AuthSessionSnapshot { state: QrLoginState; url?: string; user?: object; error?: string; captcha?: object; } export interface AuthStatus { tokenFile: string; hasToken: boolean; session: AuthSessionSnapshot | null; } export class AuthService { private session?: QrLoginSession; private snapshot: AuthSessionSnapshot | null = null; private freshLogin = false; constructor( private readonly tokenFile: string, private readonly onUpdate?: (update: QrLoginUpdate) => void ) {} status(): AuthStatus { return { tokenFile: this.tokenFile, hasToken: existsSync(this.tokenFile), session: this.snapshot ? structuredClone(this.snapshot) : null }; } startQr(): AuthStatus { if (this.session && !isTerminalQrState(this.session.state)) return this.status(); this.snapshot = { state: "connecting" }; this.session = new QrLoginSession({ tokenFile: this.tokenFile, debug: process.env.DEBUG === "1", onUpdate: update => { this.snapshot = { ...update }; if (update.state === "finished") this.freshLogin = true; this.onUpdate?.(update); } }); this.session.start(); return this.status(); } cancelQr(): AuthStatus { this.session?.cancel(); return this.status(); } submitCaptcha(key: string): boolean { return this.session?.submitCaptcha(key) ?? false; } /** True once after a successful login. Used to force the new token on start. */ consumeFreshLogin(): boolean { const value = this.freshLogin; this.freshLogin = false; return value; } clearFreshLogin(): void { this.freshLogin = false; } }