UNPKG

fql-toolkit

Version:
169 lines 6.13 kB
const STORAGE_KEY = 'fql-auth'; const EMPTY_STATE = { user: null, token: null, loggedIn: false }; export class AuthManager { constructor({ baseURL, storage }) { this.listeners = new Set(); this.authErrorListeners = new Set(); this.baseURL = baseURL; this.storage = storage; this.state = this._hydrate(); } // ── State access ───────────────────────────────────────────────────────── getState() { return this.state; } getToken() { return this.state.token; } // ── Observer ────────────────────────────────────────────────────────────── subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; } /** Register a listener fired when a request is rejected for a dead token. */ onAuthError(listener) { this.authErrorListeners.add(listener); return () => { this.authErrorListeners.delete(listener); }; } /** Fired by the HTTP client on a 401/450 response. */ emitAuthError() { this.authErrorListeners.forEach((l) => l()); } async enterAsGuest() { try { const data = await this._post('/auth/register/guest', {}); if (!data.token || !data.id) { return { ok: false, error: data.error ?? 'Failed to get guest token' }; } const user = { id: data.id, token: data.token, role: 'GUEST' }; this._setUser(user); return { ok: true, user }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } async login(email, password) { try { const data = await this._post('/auth/login', { email, password }); if (data.error || !data.token || !data.id) { return { ok: false, error: data.error ?? 'Login failed' }; } const user = { id: data.id, token: data.token, role: (data.role ?? 'USER'), nickname: data.nickname ?? '', email: data.email ?? email, }; this._setUser(user); return { ok: true, user }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } async logout() { this._setUser(null); await this.enterAsGuest(); } async register(email, opts = {}) { try { const body = { email }; if (opts.activationURL) body.activationURL = opts.activationURL; const data = await this._post('/auth/register', body); if (data.error) return { ok: false, error: data.error }; return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } async activateAccount(nickname, activationToken, password) { try { const data = await this._post('/auth/activate', { nickname, token: activationToken, password }); if (data.error) return { ok: false, error: data.error }; return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } async requestPasswordReset(email, opts = {}) { try { const body = { email }; if (opts.resetPasswordURL) body.resetPasswordURL = opts.resetPasswordURL; const data = await this._post('/auth/request-password-reset', body); if (data.error || data.success === false) { return { ok: false, error: data.message ?? data.error ?? 'Request failed' }; } return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } async resetPassword(token, newPassword) { try { const data = await this._post('/auth/reset-password', { token, password: newPassword }); if (data.error) return { ok: false, error: data.error }; return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Network error' }; } } // ── Protected helpers (used by subclasses/later tasks) ─────────────────── _setState(next) { this.state = next; this._persist(); this._notify(); } _setUser(user) { this._setState({ user, token: user?.token ?? null, loggedIn: Boolean(user && user.role !== 'GUEST'), }); } async _post(path, body) { const res = await fetch(`${this.baseURL}/api${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); return res.json(); } _notify() { this.listeners.forEach((l) => l()); } _persist() { if (!this.storage) return; if (!this.state.token) { this.storage.removeItem(STORAGE_KEY); } else { this.storage.setItem(STORAGE_KEY, JSON.stringify(this.state)); } } _hydrate() { if (!this.storage) return { ...EMPTY_STATE }; try { const raw = this.storage.getItem(STORAGE_KEY); if (!raw) return { ...EMPTY_STATE }; return JSON.parse(raw); } catch { return { ...EMPTY_STATE }; } } } //# sourceMappingURL=AuthManager.js.map