Skip to content
Open
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
153 changes: 11 additions & 142 deletions app/components/AuthWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,152 +1,21 @@
"use client";

import { useEffect, useState, useRef, Suspense } from "react";
import { useSolidAuth } from "@ldo/solid-react";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
import LoadingSpinner from "./shared/LoadingSpinner";
import { useEffect, type ReactNode } from "react";
import { useRouter } from "next/navigation";
import { useSolidAuth } from "../lib/hooks/useSolidAuth";
import FullPageLoader from "./shared/FullPageLoader";

interface AuthWrapperProps {
children: React.ReactNode;
}

// Check if there's any indication of a session in storage
function hasSessionInStorage(): boolean {
if (typeof window === "undefined") return false;

try {
const keys = Object.keys(localStorage);
return keys.some(
(key) =>
key.includes("solidClientAuthn") ||
key.includes("solid-auth") ||
key.includes("oidc") ||
key.includes("session"),
);
} catch {
return false;
}
}

function AuthWrapperContent({ children }: AuthWrapperProps) {
const { session } = useSolidAuth();
const searchParams = useSearchParams();
export default function AuthWrapper({ children }: { children: ReactNode }) {
const { status } = useSolidAuth();
const router = useRouter();
const pathname = usePathname();
const [sessionCheckComplete, setSessionCheckComplete] = useState(false);
const [oauthWaitExpired, setOauthWaitExpired] = useState(false);
const [hasSessionIndicator] = useState(() => hasSessionInStorage());
const wasLoggedInRef = useRef(false);

const isOAuthCallback = searchParams.has("code") || searchParams.has("state");
const isLoginPage = pathname === "/login";
const hasSessionData = !!(session.webId || session.sessionId || session.clientAppId);

useEffect(() => {
if (session.isLoggedIn) {
wasLoggedInRef.current = true;
}
}, [session.isLoggedIn]);

useEffect(() => {
if (!isOAuthCallback) {
return;
}

if (session.isLoggedIn) {
const redirectTimer = setTimeout(() => {
if (typeof window !== "undefined") {
window.location.href = "/";
}
}, 200);
return () => clearTimeout(redirectTimer);
}

const maxWaitTimer = setTimeout(() => {
setOauthWaitExpired(true);
setSessionCheckComplete(true);
}, 10000);
return () => clearTimeout(maxWaitTimer);
}, [isOAuthCallback, session.isLoggedIn]);

useEffect(() => {
if (isOAuthCallback) {
return;
}

if (session.isLoggedIn) {
const timer = setTimeout(() => {
setSessionCheckComplete(true);
if (isLoginPage) {
router.replace("/");
}
}, 0);
return () => clearTimeout(timer);
}

if (wasLoggedInRef.current && !session.isLoggedIn) {
const timer = setTimeout(() => {
setSessionCheckComplete(true);
if (!isLoginPage) {
router.replace("/login");
}
}, 0);
return () => clearTimeout(timer);
}

const shouldWaitForRestore = hasSessionData || hasSessionIndicator;
const checkTimer = setTimeout(() => {
setSessionCheckComplete(true);

if (!session.isLoggedIn && !isLoginPage) {
router.replace("/login");
}
}, shouldWaitForRestore ? 2000 : 200);

return () => clearTimeout(checkTimer);
}, [
session.isLoggedIn,
session.webId,
session.sessionId,
isOAuthCallback,
hasSessionIndicator,
hasSessionData,
isLoginPage,
router,
]);
if (status === "anonymous") router.replace("/login");
}, [status, router]);

const showLoading =
(isOAuthCallback && !session.isLoggedIn && !oauthWaitExpired) ||
!sessionCheckComplete;

if (showLoading) {
return (
<div className="flex min-h-screen items-center justify-center bg-white">
<LoadingSpinner size="md" text="Loading..." />
</div>
);
}

if (!session.isLoggedIn && !isLoginPage) {
return null;
}

if (session.isLoggedIn && isLoginPage) {
return null;
}
// Fail closed: children mount only once a session is confirmed, so nothing
// downstream can fetch before there is anything to authenticate with.
if (status !== "authenticated") return <FullPageLoader />;

return <>{children}</>;
}

export default function AuthWrapper({ children }: AuthWrapperProps) {
return (
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center bg-white">
<LoadingSpinner size="md" text="Loading..." />
</div>
}
>
<AuthWrapperContent>{children}</AuthWrapperContent>
</Suspense>
);
}
2 changes: 0 additions & 2 deletions app/components/FileList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import EmptyState from "./shared/EmptyState";

interface FileListProps {
files: FileItemData[];
currentPath: string;
onFileSelect: (file: FileItemData) => void;
onFileDoubleClick: (file: FileItemData) => void;
onFileRename?: (file: FileItemData) => void;
Expand All @@ -25,7 +24,6 @@ const VIEW_STORAGE_KEY = "solid-file-manager-view";

export default function FileList({
files,
currentPath,
onFileSelect,
onFileDoubleClick,
onFileRename,
Expand Down
Loading