faa1e82301
Package mobile/ (Expo SDK 56, expo-router) réutilisant l'API backend : Login (mdp+TOTP), Chat (+ TTS expo-speech), Review (approuver/refuser). JWT en expo-secure-store, thème dark HUD, EXPO_PUBLIC_API_BASE. typecheck vert. STT mobile reporté (lib native), TTS OK. Versions gérées par Expo. Palier de risque : reversible (client mobile, API commune). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
|
import * as SecureStore from "expo-secure-store";
|
|
import { api } from "./api";
|
|
|
|
/**
|
|
* Auth mobile : JWT stocké de façon sécurisée (Keychain/Keystore via
|
|
* expo-secure-store). Owner unique, login fort (mdp + TOTP) côté backend.
|
|
*/
|
|
interface AuthState {
|
|
token: string | null;
|
|
ready: boolean;
|
|
login: (user: string, password: string, totp: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
}
|
|
|
|
const KEY = "chlova.token";
|
|
const Ctx = createContext<AuthState | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [token, setToken] = useState<string | null>(null);
|
|
const [ready, setReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
void SecureStore.getItemAsync(KEY).then((t) => {
|
|
setToken(t);
|
|
setReady(true);
|
|
});
|
|
}, []);
|
|
|
|
const login = async (user: string, password: string, totp: string): Promise<void> => {
|
|
const { token: t } = await api.login(user, password, totp);
|
|
await SecureStore.setItemAsync(KEY, t);
|
|
setToken(t);
|
|
};
|
|
|
|
const logout = async (): Promise<void> => {
|
|
await SecureStore.deleteItemAsync(KEY);
|
|
setToken(null);
|
|
};
|
|
|
|
return <Ctx.Provider value={{ token, ready, login, logout }}>{children}</Ctx.Provider>;
|
|
}
|
|
|
|
export function useAuth(): AuthState {
|
|
const ctx = useContext(Ctx);
|
|
if (!ctx) throw new Error("useAuth hors AuthProvider");
|
|
return ctx;
|
|
}
|