Skip to content

Commit e1e822f

Browse files
refactor: modified repeated codes
1 parent 16d11d0 commit e1e822f

11 files changed

Lines changed: 99 additions & 70 deletions

app/components/AuthWrapper.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
"use client";
22

33
import { useEffect, useState } from "react";
4-
import {
5-
getDefaultSession,
6-
handleIncomingRedirect,
7-
} from "@inrupt/solid-client-authn-browser";
4+
import { handleIncomingRedirect } from "@inrupt/solid-client-authn-browser";
5+
import { getSession } from "../lib/helpers";
86
import LoginPage from "./LoginPage";
97
import LoadingSpinner from "./shared/LoadingSpinner";
108
import ErrorDisplay from "./shared/ErrorDisplay";
@@ -29,7 +27,7 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
2927
});
3028

3129
// Get the session instance after handling redirect
32-
const session = getDefaultSession();
30+
const session = getSession();
3331

3432
let isLoggedIn = session.info.isLoggedIn && !!session.info.webId;
3533

@@ -66,7 +64,7 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
6664
const redirectInfo = await handleIncomingRedirect({
6765
restorePreviousSession: true,
6866
});
69-
const session = getDefaultSession();
67+
const session = getSession();
7068
if (session.info.isLoggedIn) {
7169
setIsAuthenticated(true);
7270
setError(null);
@@ -87,7 +85,7 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
8785

8886
handleIncomingRedirect({ restorePreviousSession: true })
8987
.then(() => {
90-
const session = getDefaultSession();
88+
const session = getSession();
9189
setIsAuthenticated(session.info.isLoggedIn);
9290
})
9391
.catch((err) => {

app/components/FileUploadHandler.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"use client";
22

33
import { useRef, useEffect } from "react";
4-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
54
import { overwriteFile, UrlString } from "@inrupt/solid-client";
65
import toast from "react-hot-toast";
6+
import { getAuthenticatedSession } from "../lib/helpers";
77

88
interface FileUploadHandlerProps {
99
currentContainerUrl: string | null;
@@ -34,14 +34,14 @@ export default function FileUploadHandler({
3434
return;
3535
}
3636

37-
const session = getDefaultSession();
38-
if (!session.info.isLoggedIn) {
37+
let fetchFn: typeof fetch;
38+
try {
39+
({ fetch: fetchFn } = getAuthenticatedSession());
40+
} catch (error) {
3941
toast.error("Not authenticated");
4042
e.target.value = "";
4143
return;
4244
}
43-
44-
const fetchFn = session.fetch || fetch;
4545
const uploadPromises: Promise<void>[] = [];
4646
const uploadedFiles: string[] = [];
4747
const failedFiles: string[] = [];

app/components/NewFolderDialog.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { useState, useEffect, useRef } from "react";
44
import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
66
import Input from "./shared/Input";
7-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
87
import { createContainerAt, getSolidDataset, UrlString } from "@inrupt/solid-client";
98
import toast from "react-hot-toast";
9+
import { getAuthenticatedSession } from "../lib/helpers";
1010

1111
interface NewFolderDialogProps {
1212
isOpen: boolean;
@@ -53,12 +53,7 @@ export default function NewFolderDialog({
5353
setIsCreating(true);
5454

5555
try {
56-
const session = getDefaultSession();
57-
if (!session.info.isLoggedIn) {
58-
throw new Error("Not authenticated");
59-
}
60-
61-
const fetchFn = session.fetch || fetch;
56+
const { fetch: fetchFn } = getAuthenticatedSession();
6257

6358
// Ensure the current container exists
6459
await getSolidDataset(currentContainerUrl, { fetch: fetchFn });

app/components/PreviewModal.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import { useState, useEffect, useRef } from "react";
44
import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
6-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
76
import { getFile, UrlString } from "@inrupt/solid-client";
7+
import { getAuthenticatedSession } from "../lib/helpers";
88
import { FileItemData } from "./FileItem";
99
import LoadingSpinner from "./shared/LoadingSpinner";
1010
import { getFileType } from "../lib/helpers";
@@ -56,12 +56,7 @@ export default function PreviewModal({
5656
setError(null);
5757

5858
try {
59-
const session = getDefaultSession();
60-
if (!session.info.isLoggedIn) {
61-
throw new Error("Not authenticated");
62-
}
63-
64-
const fetchFn = session.fetch || fetch;
59+
const { fetch: fetchFn } = getAuthenticatedSession();
6560
const fileTypeDetected = getFileType(file.url, file.mimeType, file.name);
6661
setFileType(fileTypeDetected);
6762

app/components/ProfileIcon.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useState, useEffect, useRef } from "react";
4-
import { getDefaultSession, logout } from "@inrupt/solid-client-authn-browser";
4+
import { logout } from "@inrupt/solid-client-authn-browser";
5+
import { getSession } from "../lib/helpers";
56
import { useUserProfile, useClickOutside } from "../lib/hooks";
67
import { UserCircleIcon, ArrowRightStartOnRectangleIcon, PhoneIcon, BuildingOfficeIcon, BriefcaseIcon, GlobeAltIcon, ClipboardIcon } from "@heroicons/react/24/outline";
78
import toast from "react-hot-toast";
@@ -16,7 +17,7 @@ export default function ProfileIcon() {
1617
const buttonRef = useRef<HTMLButtonElement>(null);
1718

1819
useEffect(() => {
19-
const session = getDefaultSession();
20+
const session = getSession();
2021
if (session.info.webId) {
2122
setWebId(session.info.webId);
2223
}

app/components/RenameDialog.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import { useState, useEffect, useRef } from "react";
44
import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
66
import Input from "./shared/Input";
7-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
87
import { UrlString } from "@inrupt/solid-client";
98
import toast from "react-hot-toast";
109
import { FileItemData } from "./FileItem";
11-
import { updateMetaFile } from "../lib/helpers/metaFileUtils";
10+
import { updateMetaFile, getAuthenticatedSession } from "../lib/helpers";
1211

1312
interface RenameDialogProps {
1413
isOpen: boolean;
@@ -60,12 +59,7 @@ export default function RenameDialog({
6059
const resourceUrlString = resourceUrl as UrlString;
6160

6261
try {
63-
const session = getDefaultSession();
64-
if (!session.info.isLoggedIn) {
65-
throw new Error("Not authenticated");
66-
}
67-
68-
const fetchFn = session.fetch || fetch;
62+
const { fetch: fetchFn } = getAuthenticatedSession();
6963

7064
// Update the .meta file for this resource
7165
// This is the standard Solid approach for storing metadata about resources

app/lib/helpers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ export * from "./urlUtils";
66
export * from "./breadcrumbUtils";
77
export * from "./fileTypeUtils";
88
export * from "./profileUtils";
9+
export * from "./sessionUtils";
10+
export * from "./metaFileUtils";
911

app/lib/helpers/profileUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
21
import { Parser, Store, NamedNode } from "n3";
2+
import { getSession } from "./sessionUtils";
33

44
// Cache for parsed profile documents
55
const profileCache = new Map<string, { store: Store; baseUrl: string; mainSubject: NamedNode }>();
@@ -17,7 +17,7 @@ export async function fetchAndParseProfile(
1717
return profileCache.get(webId)!;
1818
}
1919

20-
const session = getDefaultSession();
20+
const session = getSession();
2121
const fetchFn = session.fetch || fetch;
2222

2323
// Try different Accept headers to get the profile

app/lib/helpers/sessionUtils.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { getDefaultSession, Session } from "@inrupt/solid-client-authn-browser";
2+
3+
/**
4+
* Gets the current Solid session and optionally validates authentication.
5+
*
6+
* @param requireAuth - If true, throws an error if the user is not authenticated. Defaults to true.
7+
* @returns An object containing the session and authenticated fetch function.
8+
* @throws Error if requireAuth is true and the user is not authenticated.
9+
*/
10+
export function getAuthenticatedSession(requireAuth: boolean = true): {
11+
session: Session;
12+
fetch: typeof fetch;
13+
} {
14+
const session = getDefaultSession();
15+
16+
if (requireAuth && !session.info.isLoggedIn) {
17+
throw new Error("Not authenticated");
18+
}
19+
20+
return {
21+
session,
22+
fetch: session.fetch || fetch,
23+
};
24+
}
25+
26+
/**
27+
* Gets the current Solid session without requiring authentication.
28+
* Useful for checking session state without throwing errors.
29+
*
30+
* @returns The current session.
31+
*/
32+
export function getSession(): Session {
33+
return getDefaultSession();
34+
}
35+

app/lib/hooks/useBrowseStorage.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
4-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
3+
import { useEffect, useState, useRef } from "react";
4+
import { getAuthenticatedSession } from "../helpers";
55
import {
66
getSolidDataset,
77
getContainedResourceUrlAll,
@@ -23,6 +23,10 @@ interface UseBrowseStorageResult {
2323
error: Error | null;
2424
}
2525

26+
// In-memory cache for container contents
27+
// Key: normalized URL (with trailing slash), Value: cached files
28+
const containerCache = new Map<string, FileItemData[]>();
29+
2630
/**
2731
* Hook to browse/list the contents of a Solid storage container
2832
* Uses LDP to fetch and parse container contents
@@ -31,36 +35,58 @@ export function useBrowseStorage(containerUrl: string | null, refreshKey?: numbe
3135
const [files, setFiles] = useState<FileItemData[]>([]);
3236
const [isLoading, setIsLoading] = useState(false);
3337
const [error, setError] = useState<Error | null>(null);
38+
const lastUrlRef = useRef<string | null>(null);
39+
const lastRefreshKeyRef = useRef<number | undefined>(undefined);
3440

3541
useEffect(() => {
3642
if (!containerUrl) {
3743
setFiles([]);
3844
setIsLoading(false);
45+
lastUrlRef.current = null;
46+
return;
47+
}
48+
49+
// Normalize URL (ensure trailing slash for consistency)
50+
const normalizedUrl = containerUrl.endsWith("/") ? containerUrl : containerUrl + "/";
51+
52+
// Check if we should use cached data
53+
// Use cache if: no explicit refresh requested and cache exists for this URL
54+
const shouldUseCache =
55+
refreshKey === undefined &&
56+
containerCache.has(normalizedUrl);
57+
58+
if (shouldUseCache) {
59+
// Use cached data immediately
60+
const cachedFiles = containerCache.get(normalizedUrl)!;
61+
setFiles(cachedFiles);
62+
setIsLoading(false);
63+
setError(null);
64+
// Update refs to track current URL
65+
lastUrlRef.current = normalizedUrl;
66+
lastRefreshKeyRef.current = refreshKey;
3967
return;
4068
}
4169

42-
const urlToBrowse = containerUrl;
70+
// Update refs
71+
lastUrlRef.current = normalizedUrl;
72+
lastRefreshKeyRef.current = refreshKey;
4373

4474
async function browseContainer() {
4575
try {
4676
setIsLoading(true);
4777
setError(null);
4878

49-
const session = getDefaultSession();
50-
if (!session.info.isLoggedIn) {
51-
throw new Error("Not authenticated");
52-
}
79+
const { fetch: fetchFn } = getAuthenticatedSession();
5380

54-
const url = urlToBrowse.endsWith("/") ? urlToBrowse : urlToBrowse + "/";
55-
const sessionFetch = session.fetch || fetch;
81+
const url = normalizedUrl;
5682

5783
// Create a fetch function that bypasses cache when refreshKey is provided
5884
const fetchWithCacheBust = refreshKey !== undefined
5985
? async (input: RequestInfo | URL, init?: RequestInit) => {
6086
const urlWithCacheBust = typeof input === 'string'
6187
? `${input}${input.includes('?') ? '&' : '?'}_t=${Date.now()}`
6288
: input;
63-
return sessionFetch(urlWithCacheBust, {
89+
return fetchFn(urlWithCacheBust, {
6490
...init,
6591
cache: 'no-store',
6692
headers: {
@@ -69,7 +95,7 @@ export function useBrowseStorage(containerUrl: string | null, refreshKey?: numbe
6995
},
7096
});
7197
}
72-
: sessionFetch;
98+
: fetchFn;
7399

74100
// Use @inrupt/solid-client to fetch the container dataset
75101
const containerDataset = await getSolidDataset(url, {
@@ -170,6 +196,8 @@ export function useBrowseStorage(containerUrl: string | null, refreshKey?: numbe
170196
url: item.url
171197
})));
172198

199+
// Cache the results
200+
containerCache.set(normalizedUrl, fileItems);
173201
setFiles(fileItems);
174202
} catch (err) {
175203
const errorMessage =

0 commit comments

Comments
 (0)