Backend (Rust): - Create proto/auth.proto with Login and RefreshToken RPCs - Implement AuthGrpcService in services/api with JWT issuance - Register as unauthenticated service in main.rs (pre-auth) - Update JWT issuer from foxhunt-api-gateway to foxhunt-api Dashboard (TypeScript): - Install @connectrpc/connect-web + @bufbuild/protobuf - Generate TypeScript proto clients via buf (src/gen/) - Create grpc.ts transport with JWT interceptor - Rewrite all useApi hooks to typed gRPC calls - Replace WebSocket with useGrpcStream server-streaming hooks - Migrate all 6 pages + 6 components to grpc-web - Delete api.ts, websocket.ts, useWebSocket.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
43 lines
1007 B
TypeScript
43 lines
1007 B
TypeScript
import { useCallback, useSyncExternalStore } from "react";
|
|
import {
|
|
clearTokens,
|
|
getToken,
|
|
isAuthenticated,
|
|
setTokens,
|
|
} from "../lib/auth";
|
|
|
|
/** Simple external store for auth state reactivity */
|
|
const authListeners: Set<() => void> = new Set();
|
|
|
|
function notifyAuthChange() {
|
|
authListeners.forEach((l) => l());
|
|
}
|
|
|
|
function subscribeAuth(listener: () => void) {
|
|
authListeners.add(listener);
|
|
return () => {
|
|
authListeners.delete(listener);
|
|
};
|
|
}
|
|
|
|
/** React hook for authentication state and actions */
|
|
export function useAuth() {
|
|
const authenticated = useSyncExternalStore(subscribeAuth, isAuthenticated);
|
|
const token = useSyncExternalStore(subscribeAuth, getToken);
|
|
|
|
const login = useCallback(
|
|
(accessToken: string, refreshToken?: string) => {
|
|
setTokens(accessToken, refreshToken);
|
|
notifyAuthChange();
|
|
},
|
|
[],
|
|
);
|
|
|
|
const logout = useCallback(() => {
|
|
clearTokens();
|
|
notifyAuthChange();
|
|
}, []);
|
|
|
|
return { authenticated, token, login, logout };
|
|
}
|