Authentication
The app uses Privy for authentication. The entire auth flow
lives in app/page.tsx.
import { PrivyProvider, usePrivy, useIdentityToken } from "@privy-io/react-auth";Privy Provider
The root component wraps the app in a PrivyProvider with the app ID from
environment variables. A mount guard prevents SSR hydration mismatches.
const privyAppId = process.env.NEXT_PUBLIC_PRIVY_APP_ID!;
const privyClientId = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID;
export default function Home() {
const [mounted, setMounted] = useState(false);
// eslint-disable-next-line react-hooks/set-state-in-effect -- hydration guard, runs once on mount
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (
<PrivyProvider
appId={privyAppId}
clientId={privyClientId}
config={{
embeddedWallets: {
ethereum: { createOnLogin: "users-without-wallets" },
},
}}
>
<AuthGate />
</PrivyProvider>
);
}Auth Gate
AuthGate reads Privy’s ready and authenticated state to decide what to
render: a loading indicator, the login screen, or the chat interface.
function AuthGate() {
const { ready, authenticated } = usePrivy();
if (!ready) {
return (
<div className="flex h-screen items-center justify-center">
<span className="animate-pulse-dot inline-block size-2 rounded-full bg-foreground" />
</div>
);
}
if (!authenticated) {
return <LoginScreen />;
}
return <Chat />;
}Getting an Identity Token
Inside the chat component, useIdentityToken provides a JWT that the SDK sends
to the Anuma backend. The token is kept in a ref so the getToken callback
always returns the latest value without causing re-renders.
// Keep token in a ref so getToken always returns the latest value
const tokenRef = useRef(identityToken);
useEffect(() => {
tokenRef.current = identityToken;
}, [identityToken]);
const getToken = useCallback(async () => tokenRef.current, []);Last updated on