Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 9 additions & 88 deletions app/(landing)/_components/landing-client.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';

import { authenticateWithSealos } from '@/lib/actions/sealos-auth';
import { useSealos } from '@/provider/sealos';

import { HeroSection } from './hero-section';
import { LandingHeader } from './landing-header';
Expand All @@ -29,103 +25,28 @@ interface LandingClientProps {
*/
export function LandingClient({ starCount }: LandingClientProps) {
const router = useRouter();
const { status } = useSession();
const { isInitialized, isLoading, isSealos, sealosToken, sealosKubeconfig } = useSealos();

const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authError, setAuthError] = useState<string | null>(null);
const hasAttemptedAuth = useRef(false); // Prevent duplicate auth attempts

// Auto-trigger authentication in Sealos environment
useEffect(() => {
// Wait for Sealos initialization
if (!isInitialized || isLoading) return;

// Already authenticated, no need to auth again
if (status === 'authenticated') return;

// Not in Sealos, don't auto-authenticate
if (!isSealos) return;

// Prevent duplicate attempts
if (hasAttemptedAuth.current) return;
hasAttemptedAuth.current = true;

// Check credentials
if (!sealosToken || !sealosKubeconfig) {
queueMicrotask(() => {
setAuthError('Missing Sealos credentials');
});
return;
}

// Trigger authentication
queueMicrotask(() => {
setIsAuthenticating(true);
});

authenticateWithSealos(sealosToken, sealosKubeconfig)
.then((result) => {
if (result.success) {
// Authentication successful - don't auto-redirect, let user click
setIsAuthenticating(false);
router.refresh(); // Refresh session
} else {
setAuthError(result.error || 'Authentication failed');
setIsAuthenticating(false);
hasAttemptedAuth.current = false; // Allow retry
}
})
.catch((error) => {
setAuthError(error instanceof Error ? error.message : 'Unknown error');
setIsAuthenticating(false);
hasAttemptedAuth.current = false; // Allow retry
});
}, [isInitialized, isLoading, status, isSealos, sealosToken, sealosKubeconfig, router]);

// Handle Get Started button click
const handleGetStarted = useCallback(() => {
setAuthError(null);

// Authenticated users go to projects
if (status === 'authenticated') {
router.push('/projects');
return;
}

// Non-Sealos environment - go to login
if (!isSealos) {
router.push('/login');
return;
}

// Sealos environment - retry authentication
hasAttemptedAuth.current = false;
}, [status, isSealos, router]);
router.push('/login');
}, [router]);

// Handle Sign In button click
const handleSignIn = useCallback(() => {
if (status === 'authenticated') {
router.push('/projects');
} else if (isSealos) {
// Sealos environment - retry auth
hasAttemptedAuth.current = false;
setAuthError(null);
} else {
router.push('/login');
}
}, [status, isSealos, router]);
router.push('/login');
}, [router]);

// Button state and text logic
const isInitializing = !isInitialized || isLoading;
const isButtonLoading = isInitializing || isAuthenticating;
const shouldShowGoToProjects = isSealos || status === 'authenticated';
const isButtonLoading = false;
const shouldShowGoToProjects = false;

return (
<div className="h-screen overflow-hidden flex flex-col">
<LandingHeader
isAuthenticated={status === 'authenticated'}
isSealos={isSealos}
isAuthenticated={false}
isSealos={false}
onSignIn={handleSignIn}
starCount={starCount}
isLoading={isButtonLoading}
Expand Down
3 changes: 1 addition & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Metadata } from 'next';
import { Noto_Sans, Space_Grotesk } from 'next/font/google';

import { Toaster } from '@/components/ui/sonner';
import { Providers } from '@/provider/providers';

import './globals.css';

Expand Down Expand Up @@ -32,7 +31,7 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className={`${spaceGrotesk.variable} ${notoSans.variable} bg-background antialiased`}>
<Providers>{children}</Providers>
{children}
<Toaster
position="bottom-right"
theme="dark"
Expand Down
3 changes: 1 addition & 2 deletions components/dialog/settings-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { Textarea } from '@/components/ui/textarea';
import { getInstallations, type GitHubInstallation } from '@/lib/actions/github';
import { env } from '@/lib/env';
import * as fetchClient from '@/lib/fetch-client';
import { useSealos } from '@/provider/sealos';

interface SettingsDialogProps {
open: boolean;
Expand Down Expand Up @@ -68,7 +67,7 @@ export default function SettingsDialog({
onOpenChange,
defaultTab = 'kubeconfig',
}: SettingsDialogProps) {
const { isSealos } = useSealos();
const isSealos = false;
const [activeTab, setActiveTab] = useState<TabType>(defaultTab);

// System Prompt state
Expand Down
71 changes: 4 additions & 67 deletions components/home-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import { useCallback, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';

import { Button } from '@/components/ui/button';
import { authenticateWithSealos } from '@/lib/actions/sealos-auth';
import { useSealos } from '@/provider/sealos';

/**
* Home page client component with unified rendering.
Expand All @@ -21,64 +18,19 @@ import { useSealos } from '@/provider/sealos';
*/
export function HomePage() {
const router = useRouter();
const { status } = useSession();
const { isInitialized, isLoading, isSealos, sealosToken, sealosKubeconfig } = useSealos();

const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authError, setAuthError] = useState<string | null>(null);

// Determine button action based on environment and auth status
const handleGetStarted = async () => {
// Clear previous errors on retry
setAuthError(null);

// Already authenticated - go to projects
if (status === 'authenticated') {
router.push('/projects');
return;
}

// Non-Sealos environment - go to login
if (!isSealos) {
router.push('/login');
return;
}

// Sealos environment + unauthenticated - trigger Sealos auth
if (!sealosToken || !sealosKubeconfig) {
setAuthError('Missing Sealos credentials');
return;
}

setIsAuthenticating(true);

try {
const result = await authenticateWithSealos(sealosToken, sealosKubeconfig);

if (result.success) {
// Authentication successful - redirect to projects
router.push('/projects');
router.refresh();
} else {
setAuthError(result.error || 'Authentication failed');
setIsAuthenticating(false);
}
} catch (error) {
setAuthError(error instanceof Error ? error.message : 'Unknown error');
setIsAuthenticating(false);
}
router.push('/login');
};

const getButtonText = useCallback(() => {
if (status === 'authenticated') {
return 'Go to Projects';
}
return 'Get Started';
}, [status]);
const getButtonText = useCallback(() => 'Get Started', []);

// Show minimal loading during initialization
const isInitializing = !isInitialized || isLoading;
const isButtonDisabled = isInitializing || isAuthenticating;
const isButtonDisabled = false;

return (
<>
Expand Down Expand Up @@ -135,7 +87,7 @@ export function HomePage() {
size="lg"
onClick={handleGetStarted}
disabled={isButtonDisabled}
aria-busy={isAuthenticating}
aria-busy={false}
className="w-48"
>
{getButtonText()}
Expand All @@ -153,21 +105,6 @@ export function HomePage() {
</div>
</div>
</div>

{/* Authentication overlay - shown during Sealos auth process */}
{isAuthenticating && (
<div
className="fixed inset-0 bg-background/90 flex items-center justify-center z-50"
role="dialog"
aria-label="Authentication in progress"
aria-modal="true"
>
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-foreground mx-auto mb-4"></div>
<p className="text-muted-foreground text-sm">Authenticating with Sealos...</p>
</div>
</div>
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest'

import { detectSealosIframe } from '@/lib/platform/integrations/sealos/auth/detect-sealos-iframe'
import { detectSealosIframe } from '@/integrations/sealos/desktop-sdk/detect-sealos-iframe'

describe('detectSealosIframe', () => {
afterEach(() => {
Expand All @@ -15,9 +15,23 @@ describe('detectSealosIframe', () => {
expect(detectSealosIframe({ location: {} })).toBe(false)
})

it('returns false for Sealos origins when the window is not framed', () => {
const browserWindow = {
self: 'same-frame',
top: 'same-frame',
location: {
ancestorOrigins: ['https://cloud.sealos.io'],
},
}

expect(detectSealosIframe(browserWindow)).toBe(false)
})

it('returns true for sealos.io ancestor origins', () => {
expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://cloud.sealos.io'],
},
Expand All @@ -28,6 +42,8 @@ describe('detectSealosIframe', () => {
it('returns true for sealos.run ancestor origins', () => {
expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://workspace.sealos.run'],
},
Expand All @@ -38,6 +54,8 @@ describe('detectSealosIframe', () => {
it('returns false for non-Sealos ancestor origins', () => {
expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://example.com'],
},
Expand All @@ -48,6 +66,8 @@ describe('detectSealosIframe', () => {
it('returns false for spoofed Sealos-like ancestor hosts', () => {
expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://sealos.io.evil.com'],
},
Expand All @@ -56,6 +76,8 @@ describe('detectSealosIframe', () => {

expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://evil-sealos.run.example'],
},
Expand All @@ -64,6 +86,8 @@ describe('detectSealosIframe', () => {

expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['https://example.com?next=sealos.io'],
},
Expand All @@ -74,6 +98,8 @@ describe('detectSealosIframe', () => {
it('returns false for malformed ancestor origin strings', () => {
expect(
detectSealosIframe({
self: 'child-frame',
top: 'parent-frame',
location: {
ancestorOrigins: ['not a url with sealos.io'],
},
Expand All @@ -83,12 +109,14 @@ describe('detectSealosIframe', () => {

it('returns false when ancestor origin access throws', () => {
const browserWindow = {
self: 'child-frame',
top: 'parent-frame',
location: {
get ancestorOrigins() {
throw new Error('blocked')
},
},
}
} as Parameters<typeof detectSealosIframe>[0]

expect(detectSealosIframe(browserWindow)).toBe(false)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
type BrowserWindowLike = {
self?: unknown
top?: unknown
location?: {
ancestorOrigins?: ArrayLike<string>
}
Expand All @@ -21,6 +23,8 @@ export function detectSealosIframe(browserWindow?: BrowserWindowLike): boolean {
if (!browserWindow) return false

try {
if (browserWindow.self === browserWindow.top) return false

const ancestorOrigin = browserWindow.location?.ancestorOrigins?.[0]
if (!ancestorOrigin) return false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ vi.mock('@zjy365/sealos-desktop-sdk/app', () => ({
sealosApp,
}))

import { getSealosSession } from '@/lib/platform/integrations/sealos/auth/get-sealos-session'
import { getSealosSession } from '@/integrations/sealos/desktop-sdk/get-sealos-session'

describe('getSealosSession', () => {
beforeEach(() => {
Expand Down
Loading
Loading