multica/apps/mobile/data/auth-store.ts

80 lines
2.5 KiB
TypeScript

/**
* Mobile auth store — Zustand. Logic mirrors packages/core/auth/store.ts:
* - Token written ONLY on successful verifyCode
* - 401 → clear token; non-401 (5xx / network blip) → preserve token so
* the next launch can retry
* - logout = clear token + clear in-memory user + setToken(null)
*
* NOT shared with web/desktop (per Sharing Principles in root CLAUDE.md).
* Storage backend is expo-secure-store (mobile only); web uses HttpOnly
* cookies, desktop uses localStorage via StorageAdapter.
*/
import { create } from "zustand";
import type { User } from "@multica/core/types";
import { api, ApiError } from "./api";
import { clearToken, getToken, setToken } from "./secure-storage";
import { useWorkspaceStore } from "./workspace-store";
interface AuthState {
user: User | null;
isLoading: boolean;
initialize: () => Promise<void>;
sendCode: (email: string) => Promise<void>;
verifyCode: (email: string, code: string) => Promise<User>;
logout: () => Promise<void>;
/** Overwrite the in-memory user — call after PATCH /api/me so name/avatar
* edits land without a refetch. Server response is the source of truth. */
setUser: (user: User) => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isLoading: true,
initialize: async () => {
// Restore the persisted workspace slug alongside the auth token so the
// entry redirect (app/index.tsx) can route directly to the last-used
// workspace without flashing /select-workspace.
await useWorkspaceStore.getState().restoreSlug();
const token = await getToken();
if (!token) {
set({ isLoading: false });
return;
}
api.setToken(token);
try {
const user = await api.getMe();
set({ user, isLoading: false });
} catch (err) {
// Only clear token on a genuine 401. Network blips / 5xx keep the
// token so the next launch (or a manual refresh) can retry.
if (err instanceof ApiError && err.status === 401) {
await clearToken();
api.setToken(null);
}
set({ user: null, isLoading: false });
}
},
sendCode: async (email) => {
await api.sendCode(email);
},
verifyCode: async (email, code) => {
const { token, user } = await api.verifyCode(email, code);
await setToken(token);
api.setToken(token);
set({ user });
return user;
},
logout: async () => {
await clearToken();
api.setToken(null);
set({ user: null });
},
setUser: (user) => set({ user }),
}));