fql-toolkit
Version:
169 lines • 6.13 kB
JavaScript
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