mirror of https://github.com/kortix-ai/suna.git
Feat: Updated Typechecks and Error logs trace
This commit is contained in:
parent
c458d84d17
commit
5b2dcf64cf
|
@ -1,80 +1,207 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { createClient } from '@/lib/supabase/client';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
interface AuthMessage {
|
||||||
|
type: 'github-auth-success' | 'github-auth-error';
|
||||||
|
message?: string;
|
||||||
|
returnUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function GitHubOAuthPopup() {
|
export default function GitHubOAuthPopup() {
|
||||||
|
const [status, setStatus] = useState<'loading' | 'processing' | 'error'>(
|
||||||
|
'loading',
|
||||||
|
);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const returnUrl = sessionStorage.getItem('returnUrl') || '/dashboard';
|
|
||||||
|
|
||||||
const finish = (type: 'success' | 'error', message?: string) => {
|
// Get return URL from sessionStorage (set by parent component)
|
||||||
|
const returnUrl =
|
||||||
|
sessionStorage.getItem('github-returnUrl') || '/dashboard';
|
||||||
|
|
||||||
|
const postMessage = (message: AuthMessage) => {
|
||||||
try {
|
try {
|
||||||
if (window.opener) {
|
if (window.opener && !window.opener.closed) {
|
||||||
window.opener.postMessage(
|
window.opener.postMessage(message, window.location.origin);
|
||||||
type === 'success'
|
|
||||||
? { type: 'github-auth-success', returnUrl }
|
|
||||||
: {
|
|
||||||
type: 'github-auth-error',
|
|
||||||
message: message || 'GitHub sign-in failed',
|
|
||||||
},
|
|
||||||
window.opener.origin || '*',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to post message to opener:', err);
|
console.error('Failed to post message to opener:', err);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => window.close(), 150); // Give time for message delivery
|
const handleSuccess = () => {
|
||||||
|
setStatus('processing');
|
||||||
|
postMessage({
|
||||||
|
type: 'github-auth-success',
|
||||||
|
returnUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close popup after short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (message: string) => {
|
||||||
|
setStatus('error');
|
||||||
|
setErrorMessage(message);
|
||||||
|
postMessage({
|
||||||
|
type: 'github-auth-error',
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close popup after delay to show error
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close();
|
||||||
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOAuth = async () => {
|
const handleOAuth = async () => {
|
||||||
const isOAuthCallback = new URLSearchParams(window.location.search).has(
|
try {
|
||||||
'code',
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
);
|
const isCallback = urlParams.has('code');
|
||||||
|
const hasError = urlParams.has('error');
|
||||||
|
|
||||||
if (isOAuthCallback) {
|
// Handle OAuth errors
|
||||||
try {
|
if (hasError) {
|
||||||
const {
|
const error = urlParams.get('error');
|
||||||
data: { session },
|
const errorDescription = urlParams.get('error_description');
|
||||||
} = await supabase.auth.getSession();
|
throw new Error(errorDescription || error || 'GitHub OAuth error');
|
||||||
|
}
|
||||||
|
|
||||||
if (session) {
|
if (isCallback) {
|
||||||
return finish('success');
|
// This is the callback from GitHub
|
||||||
|
setStatus('processing');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Wait a moment for Supabase to process the session
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { session },
|
||||||
|
error,
|
||||||
|
} = await supabase.auth.getSession();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session?.user) {
|
||||||
|
handleSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no session yet, listen for auth state change
|
||||||
|
const {
|
||||||
|
data: { subscription },
|
||||||
|
} = supabase.auth.onAuthStateChange(async (event, session) => {
|
||||||
|
if (event === 'SIGNED_IN' && session?.user) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
handleSuccess();
|
||||||
|
} else if (event === 'SIGNED_OUT') {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
handleError('Authentication failed - please try again');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup subscription after timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
handleError('Authentication timeout - please try again');
|
||||||
|
}, 10000); // 10 second timeout
|
||||||
|
} catch (authError: any) {
|
||||||
|
console.error('Auth processing error:', authError);
|
||||||
|
handleError(authError.message || 'Authentication failed');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Start the OAuth flow
|
||||||
|
setStatus('loading');
|
||||||
|
|
||||||
// Fallback if session is not yet populated
|
const { error } = await supabase.auth.signInWithOAuth({
|
||||||
supabase.auth.onAuthStateChange((_event, session) => {
|
provider: 'github',
|
||||||
if (session) finish('success');
|
options: {
|
||||||
|
redirectTo: `${window.location.origin}/auth/github-popup`,
|
||||||
|
queryParams: {
|
||||||
|
access_type: 'online',
|
||||||
|
prompt: 'select_account',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
if (error) {
|
||||||
} catch (err: any) {
|
throw error;
|
||||||
console.error('Session error:', err);
|
}
|
||||||
return finish('error', err.message);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Start the GitHub OAuth flow
|
|
||||||
try {
|
|
||||||
await supabase.auth.signInWithOAuth({
|
|
||||||
provider: 'github',
|
|
||||||
options: {
|
|
||||||
redirectTo: `${window.location.origin}/auth/github-popup`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('OAuth start error:', err);
|
console.error('OAuth error:', err);
|
||||||
finish('error', err.message);
|
handleError(err.message || 'Failed to authenticate with GitHub');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cleanup sessionStorage when popup closes
|
||||||
|
const handleBeforeUnload = () => {
|
||||||
|
sessionStorage.removeItem('github-returnUrl');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
|
||||||
|
// Start OAuth process
|
||||||
handleOAuth();
|
handleOAuth();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getStatusMessage = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'loading':
|
||||||
|
return 'Starting GitHub authentication...';
|
||||||
|
case 'processing':
|
||||||
|
return 'Completing sign-in...';
|
||||||
|
case 'error':
|
||||||
|
return errorMessage || 'Authentication failed';
|
||||||
|
default:
|
||||||
|
return 'Processing...';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'error':
|
||||||
|
return 'text-red-500';
|
||||||
|
case 'processing':
|
||||||
|
return 'text-green-500';
|
||||||
|
default:
|
||||||
|
return 'text-muted-foreground';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex items-center justify-center h-screen text-muted-foreground text-sm">
|
<main className="flex flex-col items-center justify-center h-screen bg-background p-8">
|
||||||
Completing GitHub sign-in...
|
<div className="flex flex-col items-center gap-4 text-center max-w-sm">
|
||||||
|
{status !== 'error' && (
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-lg font-medium">GitHub Sign-In</h1>
|
||||||
|
<p className={`text-sm ${getStatusColor()}`}>{getStatusMessage()}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<button
|
||||||
|
onClick={() => window.close()}
|
||||||
|
className="mt-4 px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Icons } from './home/icons';
|
import { Icons } from './home/icons';
|
||||||
|
@ -9,82 +9,172 @@ interface GitHubSignInProps {
|
||||||
returnUrl?: string;
|
returnUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuthMessage {
|
||||||
|
type: 'github-auth-success' | 'github-auth-error';
|
||||||
|
message?: string;
|
||||||
|
returnUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function GitHubSignIn({ returnUrl }: GitHubSignInProps) {
|
export default function GitHubSignIn({ returnUrl }: GitHubSignInProps) {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
// Cleanup function to handle auth state
|
||||||
const handler = (event: MessageEvent) => {
|
const cleanupAuthState = useCallback(() => {
|
||||||
if (event.origin !== window.location.origin) return;
|
sessionStorage.removeItem('isGitHubAuthInProgress');
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (event.data?.type === 'github-auth-success') {
|
// Handle success message
|
||||||
sessionStorage.removeItem('isGitHubAuthInProgress');
|
const handleSuccess = useCallback(
|
||||||
setIsLoading(false);
|
(data: AuthMessage) => {
|
||||||
window.location.href =
|
cleanupAuthState();
|
||||||
event.data.returnUrl || returnUrl || '/dashboard';
|
|
||||||
|
// Add a small delay to ensure state is properly cleared
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = data.returnUrl || returnUrl || '/dashboard';
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
[cleanupAuthState, returnUrl],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle error message
|
||||||
|
const handleError = useCallback(
|
||||||
|
(data: AuthMessage) => {
|
||||||
|
cleanupAuthState();
|
||||||
|
toast.error(data.message || 'GitHub sign-in failed. Please try again.');
|
||||||
|
},
|
||||||
|
[cleanupAuthState],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Message event handler
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event: MessageEvent<AuthMessage>) => {
|
||||||
|
// Security: Only accept messages from same origin
|
||||||
|
if (event.origin !== window.location.origin) {
|
||||||
|
console.warn(
|
||||||
|
'Rejected message from unauthorized origin:',
|
||||||
|
event.origin,
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data?.type === 'github-auth-error') {
|
// Validate message structure
|
||||||
sessionStorage.removeItem('isGitHubAuthInProgress');
|
if (!event.data?.type || typeof event.data.type !== 'string') {
|
||||||
setIsLoading(false);
|
return;
|
||||||
toast.error(event.data.message || 'GitHub sign-in failed.');
|
}
|
||||||
|
|
||||||
|
switch (event.data.type) {
|
||||||
|
case 'github-auth-success':
|
||||||
|
handleSuccess(event.data);
|
||||||
|
break;
|
||||||
|
case 'github-auth-error':
|
||||||
|
handleError(event.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Ignore unknown message types
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('message', handler);
|
window.addEventListener('message', handleMessage);
|
||||||
return () => window.removeEventListener('message', handler);
|
|
||||||
}, [returnUrl]);
|
|
||||||
|
|
||||||
const handleGitHubSignIn = () => {
|
return () => {
|
||||||
const popup = window.open(
|
window.removeEventListener('message', handleMessage);
|
||||||
`${window.location.origin}/auth/github-popup`,
|
};
|
||||||
'GitHubOAuth',
|
}, [handleSuccess, handleError]);
|
||||||
'width=500,height=600',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!popup) {
|
// Cleanup on component unmount
|
||||||
toast.error('Popup was blocked. Please enable popups and try again.');
|
useEffect(() => {
|
||||||
return;
|
return () => {
|
||||||
}
|
cleanupAuthState();
|
||||||
|
};
|
||||||
|
}, [cleanupAuthState]);
|
||||||
|
|
||||||
setTimeout(() => {
|
const handleGitHubSignIn = async () => {
|
||||||
sessionStorage.setItem('isGitHubAuthInProgress', '1');
|
if (isLoading) return;
|
||||||
|
|
||||||
|
let popupInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
// Store return URL for the popup
|
||||||
if (popup.closed) {
|
if (returnUrl) {
|
||||||
clearInterval(interval);
|
sessionStorage.setItem('github-returnUrl', returnUrl || '/dashboard');
|
||||||
setIsLoading(false);
|
}
|
||||||
|
|
||||||
// Delay toast to allow success postMessage to clear the flag
|
// Open popup with proper dimensions and features
|
||||||
|
const popup = window.open(
|
||||||
|
`${window.location.origin}/auth/github-popup`,
|
||||||
|
'GitHubOAuth',
|
||||||
|
'width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!popup) {
|
||||||
|
throw new Error(
|
||||||
|
'Popup was blocked. Please enable popups and try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set loading state and track popup
|
||||||
|
sessionStorage.setItem('isGitHubAuthInProgress', '1');
|
||||||
|
|
||||||
|
// Monitor popup closure
|
||||||
|
popupInterval = setInterval(() => {
|
||||||
|
if (popup.closed) {
|
||||||
|
if (popupInterval) {
|
||||||
|
clearInterval(popupInterval);
|
||||||
|
popupInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to allow postMessage to complete
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (sessionStorage.getItem('isGitHubAuthInProgress')) {
|
if (sessionStorage.getItem('isGitHubAuthInProgress')) {
|
||||||
sessionStorage.removeItem('isGitHubAuthInProgress');
|
cleanupAuthState();
|
||||||
toast.error('GitHub sign-in was not completed.');
|
toast.error('GitHub sign-in was cancelled or not completed.');
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 500);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 1000);
|
||||||
}, 0);
|
} catch (error) {
|
||||||
|
console.error('GitHub sign-in error:', error);
|
||||||
|
if (popupInterval) {
|
||||||
|
clearInterval(popupInterval);
|
||||||
|
}
|
||||||
|
cleanupAuthState();
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Failed to start GitHub sign-in',
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// Matched the button with the GoogleSignIn component
|
||||||
<button
|
<button
|
||||||
onClick={handleGitHubSignIn}
|
onClick={handleGitHubSignIn}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full h-[56px] flex items-center justify-center gap-3 rounded-full border border-border bg-background hover:bg-[rgba(255,255,255,0.08)] transition-all text-sm font-normal tracking-normal"
|
className="relative w-full h-12 flex items-center justify-center text-sm font-normal tracking-wide rounded-full bg-background text-foreground border border-border hover:bg-accent/30 transition-all duration-200 disabled:opacity-60 disabled:cursor-not-allowed font-sans"
|
||||||
style={{
|
aria-label={
|
||||||
fontFamily: '"Google Sans", Arial, sans-serif',
|
isLoading ? 'Signing in with GitHub...' : 'Sign in with GitHub'
|
||||||
fontWeight: 400,
|
}
|
||||||
boxShadow: 'none',
|
type="button"
|
||||||
}}
|
|
||||||
aria-label="Sign in with GitHub"
|
|
||||||
>
|
>
|
||||||
<span className="flex items-center justify-center w-6 h-6">
|
<div className="absolute left-0 inset-y-0 flex items-center pl-1 w-10">
|
||||||
<Icons.github className="w-9 h-9" />
|
<div className="w-8 h-8 rounded-full flex items-center justify-center text-foreground dark:bg-foreground dark:text-background">
|
||||||
</span>
|
{isLoading ? (
|
||||||
{isLoading ? 'Waiting for GitHub...' : 'Continue with GitHub'}
|
<div className="w-5 h-5 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||||
</button>
|
) : (
|
||||||
|
<Icons.github className="w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="ml-9 font-light">
|
||||||
|
{isLoading ? 'Signing in...' : 'Continue with GitHub'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue