mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
fix: add back auth-kit
This commit is contained in:
parent
b9e2087bd8
commit
16c433a13c
@ -1,10 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import '@farcaster/auth-kit/styles.css';
|
||||
import { useSignIn, UseSignInData } from '@farcaster/auth-kit';
|
||||
import { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { cn } from '~/lib/utils';
|
||||
import { Button } from '~/components/ui/Button';
|
||||
import { ProfileButton } from '~/components/ui/NeynarAuthButton/ProfileButton';
|
||||
import { AuthDialog } from '~/components/ui/NeynarAuthButton/AuthDialog';
|
||||
import { getItem, removeItem, setItem } from '~/lib/localStorage';
|
||||
import { useMiniApp } from '@neynar/react';
|
||||
import sdk, { SignIn as SignInCore } from '@farcaster/frame-sdk';
|
||||
import { useQuickAuth } from '~/hooks/useQuickAuth';
|
||||
@ -17,6 +20,7 @@ type User = {
|
||||
// Add other user properties as needed
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'neynar_authenticated_user';
|
||||
const FARCASTER_FID = 9152;
|
||||
|
||||
interface StoredAuthState {
|
||||
@ -110,8 +114,69 @@ export function NeynarAuthButton() {
|
||||
const [backendUserProfile, setBackendUserProfile] = useState<{ username?: string; pfpUrl?: string }>({});
|
||||
|
||||
// Determine which flow to use based on context
|
||||
// - Farcaster clients (when context is not undefined): Use QuickAuth (backend flow)
|
||||
// - Browsers (when context is undefined): Use auth-kit (frontend flow)
|
||||
const useBackendFlow = context !== undefined;
|
||||
|
||||
// Helper function to fetch user data from Neynar API
|
||||
const fetchUserData = useCallback(
|
||||
async (fid: number): Promise<User | null> => {
|
||||
try {
|
||||
const response = await fetch(`/api/users?fids=${fid}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data.users?.[0] || null;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user data:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Auth Kit integration for browser-based authentication (only when not in Farcaster client)
|
||||
const signInState = useSignIn({
|
||||
nonce: !useBackendFlow ? (nonce || undefined) : undefined,
|
||||
onSuccess: useCallback(
|
||||
async (res: UseSignInData) => {
|
||||
if (!useBackendFlow) {
|
||||
// Only handle localStorage for frontend flow
|
||||
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
|
||||
const user = res.fid ? await fetchUserData(res.fid) : null;
|
||||
const authState: StoredAuthState = {
|
||||
...existingAuth,
|
||||
isAuthenticated: true,
|
||||
user: user as StoredAuthState['user'],
|
||||
signers: existingAuth?.signers || [], // Preserve existing signers
|
||||
};
|
||||
setItem<StoredAuthState>(STORAGE_KEY, authState);
|
||||
setStoredAuth(authState);
|
||||
}
|
||||
// For backend flow, the session will be handled by QuickAuth
|
||||
},
|
||||
[useBackendFlow, fetchUserData]
|
||||
),
|
||||
onError: useCallback((error?: Error | null) => {
|
||||
console.error('❌ Sign in error:', error);
|
||||
}, []),
|
||||
});
|
||||
|
||||
const {
|
||||
signIn: frontendSignIn,
|
||||
signOut: frontendSignOut,
|
||||
connect,
|
||||
reconnect,
|
||||
isSuccess,
|
||||
isError,
|
||||
error,
|
||||
channelToken,
|
||||
url,
|
||||
data,
|
||||
validSignature,
|
||||
} = signInState;
|
||||
|
||||
// Helper function to create a signer
|
||||
const createSigner = useCallback(async () => {
|
||||
try {
|
||||
@ -151,24 +216,6 @@ export function NeynarAuthButton() {
|
||||
[useBackendFlow, quickAuthSignIn]
|
||||
);
|
||||
|
||||
// Helper function to fetch user data from Neynar API
|
||||
const fetchUserData = useCallback(
|
||||
async (fid: number): Promise<User | null> => {
|
||||
try {
|
||||
const response = await fetch(`/api/users?fids=${fid}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data.users?.[0] || null;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user data:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Helper function to generate signed key request
|
||||
const generateSignedKeyRequest = useCallback(
|
||||
async (signerUuid: string, publicKey: string) => {
|
||||
@ -239,7 +286,7 @@ export function NeynarAuthButton() {
|
||||
}
|
||||
return signerData.signers;
|
||||
} else {
|
||||
// For frontend flow, store in memory only
|
||||
// For frontend flow, store in localStorage
|
||||
let user: StoredAuthState['user'] | null = null;
|
||||
|
||||
if (signerData.signers && signerData.signers.length > 0) {
|
||||
@ -249,12 +296,13 @@ export function NeynarAuthButton() {
|
||||
user = fetchedUser;
|
||||
}
|
||||
|
||||
// Store signers in memory only
|
||||
// Store signers in localStorage, preserving existing auth data
|
||||
const updatedState: StoredAuthState = {
|
||||
isAuthenticated: !!user,
|
||||
signers: signerData.signers || [],
|
||||
user,
|
||||
};
|
||||
setItem<StoredAuthState>(STORAGE_KEY, updatedState);
|
||||
setStoredAuth(updatedState);
|
||||
|
||||
return signerData.signers;
|
||||
@ -369,6 +417,36 @@ export function NeynarAuthButton() {
|
||||
generateNonce();
|
||||
}, []);
|
||||
|
||||
// Load stored auth state on mount (only for frontend flow)
|
||||
useEffect(() => {
|
||||
if (!useBackendFlow) {
|
||||
const stored = getItem<StoredAuthState>(STORAGE_KEY);
|
||||
if (stored && stored.isAuthenticated) {
|
||||
setStoredAuth(stored);
|
||||
}
|
||||
}
|
||||
}, [useBackendFlow]);
|
||||
|
||||
// Auth Kit data synchronization (only for browser flow)
|
||||
useEffect(() => {
|
||||
if (!useBackendFlow) {
|
||||
setMessage(data?.message || null);
|
||||
setSignature(data?.signature || null);
|
||||
|
||||
// Reset the signer flow flag when message/signature change
|
||||
if (data?.message && data?.signature) {
|
||||
signerFlowStartedRef.current = false;
|
||||
}
|
||||
}
|
||||
}, [useBackendFlow, data?.message, data?.signature]);
|
||||
|
||||
// Connect for frontend flow when nonce is available (only for browser flow)
|
||||
useEffect(() => {
|
||||
if (!useBackendFlow && nonce && !channelToken) {
|
||||
connect();
|
||||
}
|
||||
}, [useBackendFlow, nonce, channelToken, connect]);
|
||||
|
||||
// Backend flow using QuickAuth
|
||||
const handleBackendSignIn = useCallback(async () => {
|
||||
if (!nonce) {
|
||||
@ -414,25 +492,14 @@ export function NeynarAuthButton() {
|
||||
}
|
||||
}, [useBackendFlow, quickAuthUser?.fid, fetchUserData]);
|
||||
|
||||
const handleFrontEndSignIn = useCallback(async () => {
|
||||
try {
|
||||
setSignersLoading(true);
|
||||
const result = await sdk.actions.signIn({ nonce: nonce || '' });
|
||||
|
||||
setMessage(result.message);
|
||||
setSignature(result.signature);
|
||||
|
||||
// For frontend flow, we'll handle the signer flow in the useEffect
|
||||
} catch (e) {
|
||||
if (e instanceof SignInCore.RejectedByUser) {
|
||||
console.log('ℹ️ Sign-in rejected by user');
|
||||
} else {
|
||||
console.error('❌ Frontend sign-in error:', e);
|
||||
}
|
||||
} finally {
|
||||
setSignersLoading(false);
|
||||
const handleFrontEndSignIn = useCallback(() => {
|
||||
if (isError) {
|
||||
reconnect();
|
||||
}
|
||||
}, [nonce]);
|
||||
setDialogStep('signin');
|
||||
setShowDialog(true);
|
||||
frontendSignIn();
|
||||
}, [isError, reconnect, frontendSignIn]);
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
try {
|
||||
@ -443,6 +510,8 @@ export function NeynarAuthButton() {
|
||||
await quickAuthSignOut();
|
||||
} else {
|
||||
// Frontend flow sign out
|
||||
frontendSignOut();
|
||||
removeItem(STORAGE_KEY);
|
||||
setStoredAuth(null);
|
||||
}
|
||||
|
||||
@ -467,7 +536,7 @@ export function NeynarAuthButton() {
|
||||
} finally {
|
||||
setSignersLoading(false);
|
||||
}
|
||||
}, [useBackendFlow, pollingInterval, quickAuthSignOut]);
|
||||
}, [useBackendFlow, frontendSignOut, pollingInterval, quickAuthSignOut]);
|
||||
|
||||
// Handle fetching signers after successful authentication
|
||||
useEffect(() => {
|
||||
@ -545,10 +614,13 @@ export function NeynarAuthButton() {
|
||||
}
|
||||
}, [message, signature]); // Simplified dependencies
|
||||
|
||||
// Authentication check based on flow type
|
||||
const authenticated = useBackendFlow
|
||||
? !!quickAuthUser?.fid
|
||||
: storedAuth?.isAuthenticated && !!(storedAuth?.signers && storedAuth.signers.length > 0);
|
||||
? !!quickAuthUser?.fid // QuickAuth flow: check if user is authenticated via QuickAuth
|
||||
: ((isSuccess && validSignature) || storedAuth?.isAuthenticated) && // Auth-kit flow: check auth-kit success or stored auth
|
||||
!!(storedAuth?.signers && storedAuth.signers.length > 0); // Both flows: ensure signers exist
|
||||
|
||||
// User data based on flow type
|
||||
const userData = useBackendFlow
|
||||
? {
|
||||
fid: quickAuthUser?.fid,
|
||||
@ -582,17 +654,18 @@ export function NeynarAuthButton() {
|
||||
) : (
|
||||
<Button
|
||||
onClick={useBackendFlow ? handleBackendSignIn : handleFrontEndSignIn}
|
||||
disabled={signersLoading}
|
||||
disabled={!useBackendFlow && !url} // Disable button in browser flow if auth-kit URL is not ready
|
||||
className={cn(
|
||||
'btn btn-primary flex items-center gap-3',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
'transform transition-all duration-200 active:scale-[0.98]'
|
||||
'transform transition-all duration-200 active:scale-[0.98]',
|
||||
!url && !useBackendFlow && 'cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{signersLoading ? (
|
||||
{!useBackendFlow && !url ? (
|
||||
<>
|
||||
<div className="spinner-primary w-5 h-5" />
|
||||
<span>Loading...</span>
|
||||
<span>Initializing...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@ -615,9 +688,9 @@ export function NeynarAuthButton() {
|
||||
setPollingInterval(null);
|
||||
}
|
||||
}}
|
||||
url={undefined}
|
||||
isError={false}
|
||||
error={null}
|
||||
url={url}
|
||||
isError={isError}
|
||||
error={error}
|
||||
step={dialogStep}
|
||||
isLoading={signersLoading}
|
||||
signerApprovalUrl={signerApprovalUrl}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user