Merge pull request #27 from neynarxyz/shreyas/neyn-7098-move-siwn-to-be-fully-embeddable-within-the-host-app

Fix terminology for siwn
This commit is contained in:
Shreyaschorge 2025-09-30 18:59:47 +05:30 committed by GitHub
commit 6a8fa017e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,5 +1,12 @@
'use client'; 'use client';
/**
* This authentication system is designed to work both in a regular web browser and inside a miniapp.
* In other words, it supports authentication when the miniapp context is not present (web browser) as well as when the app is running inside the miniapp.
* If you only need authentication for a web application, follow the Webapp flow;
* if you only need authentication inside a miniapp, follow the Miniapp flow.
*/
import '@farcaster/auth-kit/styles.css'; import '@farcaster/auth-kit/styles.css';
import { useSignIn, UseSignInData } from '@farcaster/auth-kit'; import { useSignIn, UseSignInData } from '@farcaster/auth-kit';
import { useCallback, useEffect, useState, useRef } from 'react'; import { useCallback, useEffect, useState, useRef } from 'react';
@ -10,8 +17,8 @@ import { AuthDialog } from '~/components/ui/NeynarAuthButton/AuthDialog';
import { getItem, removeItem, setItem } from '~/lib/localStorage'; import { getItem, removeItem, setItem } from '~/lib/localStorage';
import { useMiniApp } from '@neynar/react'; import { useMiniApp } from '@neynar/react';
import { import {
signIn as backendSignIn, signIn as miniappSignIn,
signOut as backendSignOut, signOut as miniappSignOut,
useSession, useSession,
} from 'next-auth/react'; } from 'next-auth/react';
import sdk, { SignIn as SignInCore } from '@farcaster/miniapp-sdk'; import sdk, { SignIn as SignInCore } from '@farcaster/miniapp-sdk';
@ -116,7 +123,7 @@ export function NeynarAuthButton() {
const signerFlowStartedRef = useRef(false); const signerFlowStartedRef = useRef(false);
// Determine which flow to use based on context // Determine which flow to use based on context
const useBackendFlow = context !== undefined; const useMiniappFlow = context !== undefined;
// Helper function to create a signer // Helper function to create a signer
const createSigner = useCallback(async () => { const createSigner = useCallback(async () => {
@ -137,16 +144,16 @@ export function NeynarAuthButton() {
} }
}, []); }, []);
// Helper function to update session with signers (backend flow only) // Helper function to update session with signers (miniapp flow only)
const updateSessionWithSigners = useCallback( const updateSessionWithSigners = useCallback(
async ( async (
signers: StoredAuthState['signers'], signers: StoredAuthState['signers'],
user: StoredAuthState['user'] user: StoredAuthState['user']
) => { ) => {
if (!useBackendFlow) return; if (!useMiniappFlow) return;
try { try {
// For backend flow, we need to sign in again with the additional data // For miniapp flow, we need to sign in again with the additional data
if (message && signature) { if (message && signature) {
const signInData = { const signInData = {
message, message,
@ -158,13 +165,13 @@ export function NeynarAuthButton() {
user: JSON.stringify(user), user: JSON.stringify(user),
}; };
await backendSignIn('neynar', signInData); await miniappSignIn('neynar', signInData);
} }
} catch (error) { } catch (error) {
console.error('❌ Error updating session with signers:', error); console.error('❌ Error updating session with signers:', error);
} }
}, },
[useBackendFlow, message, signature, nonce] [useMiniappFlow, message, signature, nonce]
); );
// Helper function to fetch user data from Neynar API // Helper function to fetch user data from Neynar API
@ -231,7 +238,7 @@ export function NeynarAuthButton() {
try { try {
setSignersLoading(true); setSignersLoading(true);
const endpoint = useBackendFlow const endpoint = useMiniappFlow
? `/api/auth/session-signers?message=${encodeURIComponent( ? `/api/auth/session-signers?message=${encodeURIComponent(
message message
)}&signature=${signature}` )}&signature=${signature}`
@ -243,8 +250,8 @@ export function NeynarAuthButton() {
const signerData = await response.json(); const signerData = await response.json();
if (response.ok) { if (response.ok) {
if (useBackendFlow) { if (useMiniappFlow) {
// For backend flow, update session with signers // For miniapp flow, update session with signers
if (signerData.signers && signerData.signers.length > 0) { if (signerData.signers && signerData.signers.length > 0) {
const user = const user =
signerData.user || signerData.user ||
@ -253,7 +260,7 @@ export function NeynarAuthButton() {
} }
return signerData.signers; return signerData.signers;
} else { } else {
// For frontend flow, store in localStorage // For webapp flow, store in localStorage
let user: StoredAuthState['user'] | null = null; let user: StoredAuthState['user'] | null = null;
if (signerData.signers && signerData.signers.length > 0) { if (signerData.signers && signerData.signers.length > 0) {
@ -285,7 +292,7 @@ export function NeynarAuthButton() {
setSignersLoading(false); setSignersLoading(false);
} }
}, },
[useBackendFlow, fetchUserData, updateSessionWithSigners] [useMiniappFlow, fetchUserData, updateSessionWithSigners]
); );
// Helper function to poll signer status // Helper function to poll signer status
@ -384,21 +391,21 @@ export function NeynarAuthButton() {
generateNonce(); generateNonce();
}, []); }, []);
// Load stored auth state on mount (only for frontend flow) // Load stored auth state on mount (only for webapp flow)
useEffect(() => { useEffect(() => {
if (!useBackendFlow) { if (!useMiniappFlow) {
const stored = getItem<StoredAuthState>(STORAGE_KEY); const stored = getItem<StoredAuthState>(STORAGE_KEY);
if (stored && stored.isAuthenticated) { if (stored && stored.isAuthenticated) {
setStoredAuth(stored); setStoredAuth(stored);
} }
} }
}, [useBackendFlow]); }, [useMiniappFlow]);
// Success callback - this is critical! // Success callback - this is critical!
const onSuccessCallback = useCallback( const onSuccessCallback = useCallback(
async (res: UseSignInData) => { async (res: UseSignInData) => {
if (!useBackendFlow) { if (!useMiniappFlow) {
// Only handle localStorage for frontend flow // Only handle localStorage for webapp flow
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY); const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
const user = res.fid ? await fetchUserData(res.fid) : null; const user = res.fid ? await fetchUserData(res.fid) : null;
const authState: StoredAuthState = { const authState: StoredAuthState = {
@ -410,9 +417,9 @@ export function NeynarAuthButton() {
setItem<StoredAuthState>(STORAGE_KEY, authState); setItem<StoredAuthState>(STORAGE_KEY, authState);
setStoredAuth(authState); setStoredAuth(authState);
} }
// For backend flow, the session will be handled by NextAuth // For miniapp flow, the session will be handled by NextAuth
}, },
[useBackendFlow, fetchUserData] [useMiniappFlow, fetchUserData]
); );
// Error callback // Error callback
@ -427,8 +434,8 @@ export function NeynarAuthButton() {
}); });
const { const {
signIn: frontendSignIn, signIn: webappSignIn,
signOut: frontendSignOut, signOut: webappSignOut,
connect, connect,
reconnect, reconnect,
isSuccess, isSuccess,
@ -450,12 +457,12 @@ export function NeynarAuthButton() {
} }
}, [data?.message, data?.signature]); }, [data?.message, data?.signature]);
// Connect for frontend flow when nonce is available // Connect for webapp flow when nonce is available
useEffect(() => { useEffect(() => {
if (!useBackendFlow && nonce && !channelToken) { if (!useMiniappFlow && nonce && !channelToken) {
connect(); connect();
} }
}, [useBackendFlow, nonce, channelToken, connect]); }, [useMiniappFlow, nonce, channelToken, connect]);
// Handle fetching signers after successful authentication // Handle fetching signers after successful authentication
useEffect(() => { useEffect(() => {
@ -478,14 +485,14 @@ export function NeynarAuthButton() {
// Step 1: Change to loading state // Step 1: Change to loading state
setDialogStep('loading'); setDialogStep('loading');
// Show dialog if not using backend flow or in browser farcaster // Show dialog if not using miniapp flow or in browser farcaster
if ((useBackendFlow && !isMobileContext) || !useBackendFlow) if ((useMiniappFlow && !isMobileContext) || !useMiniappFlow)
setShowDialog(true); setShowDialog(true);
// First, fetch existing signers // First, fetch existing signers
const signers = await fetchAllSigners(message, signature); const signers = await fetchAllSigners(message, signature);
if (useBackendFlow && isMobileContext) setSignersLoading(true); if (useMiniappFlow && isMobileContext) setSignersLoading(true);
// Check if no signers exist or if we have empty signers // Check if no signers exist or if we have empty signers
if (!signers || signers.length === 0) { if (!signers || signers.length === 0) {
@ -538,10 +545,10 @@ export function NeynarAuthButton() {
} }
}, [message, signature]); // Simplified dependencies }, [message, signature]); // Simplified dependencies
// Backend flow using NextAuth // Miniapp flow using NextAuth
const handleBackendSignIn = useCallback(async () => { const handleMiniappSignIn = useCallback(async () => {
if (!nonce) { if (!nonce) {
console.error('❌ No nonce available for backend sign-in'); console.error('❌ No nonce available for miniapp sign-in');
return; return;
} }
@ -556,7 +563,7 @@ export function NeynarAuthButton() {
nonce: nonce, nonce: nonce,
}; };
const nextAuthResult = await backendSignIn('neynar', signInData); const nextAuthResult = await miniappSignIn('neynar', signInData);
if (nextAuthResult?.ok) { if (nextAuthResult?.ok) {
setMessage(result.message); setMessage(result.message);
setSignature(result.signature); setSignature(result.signature);
@ -567,34 +574,34 @@ export function NeynarAuthButton() {
if (e instanceof SignInCore.RejectedByUser) { if (e instanceof SignInCore.RejectedByUser) {
console.log(' Sign-in rejected by user'); console.log(' Sign-in rejected by user');
} else { } else {
console.error('❌ Backend sign-in error:', e); console.error('❌ Miniapp sign-in error:', e);
} }
} finally { } finally {
setSignersLoading(false); setSignersLoading(false);
} }
}, [nonce]); }, [nonce]);
const handleFrontEndSignIn = useCallback(() => { const handleWebappSignIn = useCallback(() => {
if (isError) { if (isError) {
reconnect(); reconnect();
} }
setDialogStep('signin'); setDialogStep('signin');
setShowDialog(true); setShowDialog(true);
frontendSignIn(); webappSignIn();
}, [isError, reconnect, frontendSignIn]); }, [isError, reconnect, webappSignIn]);
const handleSignOut = useCallback(async () => { const handleSignOut = useCallback(async () => {
try { try {
setSignersLoading(true); setSignersLoading(true);
if (useBackendFlow) { if (useMiniappFlow) {
// Only sign out from NextAuth if the current session is from Neynar provider // Only sign out from NextAuth if the current session is from Neynar provider
if (session?.provider === 'neynar') { if (session?.provider === 'neynar') {
await backendSignOut({ redirect: false }); await miniappSignOut({ redirect: false });
} }
} else { } else {
// Frontend flow sign out // Webapp flow sign out
frontendSignOut(); webappSignOut();
removeItem(STORAGE_KEY); removeItem(STORAGE_KEY);
setStoredAuth(null); setStoredAuth(null);
} }
@ -620,9 +627,9 @@ export function NeynarAuthButton() {
} finally { } finally {
setSignersLoading(false); setSignersLoading(false);
} }
}, [useBackendFlow, frontendSignOut, pollingInterval, session]); }, [useMiniappFlow, webappSignOut, pollingInterval, session]);
const authenticated = useBackendFlow const authenticated = useMiniappFlow
? !!( ? !!(
session?.provider === 'neynar' && session?.provider === 'neynar' &&
session?.user?.fid && session?.user?.fid &&
@ -632,7 +639,7 @@ export function NeynarAuthButton() {
: ((isSuccess && validSignature) || storedAuth?.isAuthenticated) && : ((isSuccess && validSignature) || storedAuth?.isAuthenticated) &&
!!(storedAuth?.signers && storedAuth.signers.length > 0); !!(storedAuth?.signers && storedAuth.signers.length > 0);
const userData = useBackendFlow const userData = useMiniappFlow
? { ? {
fid: session?.user?.fid, fid: session?.user?.fid,
username: session?.user?.username || '', username: session?.user?.username || '',
@ -664,16 +671,16 @@ export function NeynarAuthButton() {
<ProfileButton userData={userData} onSignOut={handleSignOut} /> <ProfileButton userData={userData} onSignOut={handleSignOut} />
) : ( ) : (
<Button <Button
onClick={useBackendFlow ? handleBackendSignIn : handleFrontEndSignIn} onClick={useMiniappFlow ? handleMiniappSignIn : handleWebappSignIn}
disabled={!useBackendFlow && !url} disabled={!useMiniappFlow && !url}
className={cn( className={cn(
'btn btn-primary flex items-center gap-3', 'btn btn-primary flex items-center gap-3',
'disabled:opacity-50 disabled:cursor-not-allowed', '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' !url && !useMiniappFlow && 'cursor-not-allowed'
)} )}
> >
{!useBackendFlow && !url ? ( {!useMiniappFlow && !url ? (
<> <>
<div className="spinner-primary w-5 h-5" /> <div className="spinner-primary w-5 h-5" />
<span>Initializing...</span> <span>Initializing...</span>