diff --git a/src/backend/accounts/progress.ts b/src/backend/accounts/progress.ts index 6b522079..511d1c5d 100644 --- a/src/backend/accounts/progress.ts +++ b/src/backend/accounts/progress.ts @@ -2,7 +2,6 @@ import { ofetch } from "ofetch"; import { getAuthHeaders } from "@/backend/accounts/auth"; import { ProgressResponse } from "@/backend/accounts/user"; -import { BACKEND_URL } from "@/setup/constants"; import { AccountWithToken } from "@/stores/auth"; import { ProgressMediaItem, ProgressUpdateItem } from "@/stores/progress"; @@ -104,7 +103,6 @@ export async function removeProgress( episodeId?: string, seasonId?: string, ) { - if (!BACKEND_URL) return; await ofetch(`/users/${account.userId}/progress/${id}`, { method: "DELETE", headers: getAuthHeaders(account.token), diff --git a/src/hooks/auth/useAuth.ts b/src/hooks/auth/useAuth.ts index f608d148..3ce49163 100644 --- a/src/hooks/auth/useAuth.ts +++ b/src/hooks/auth/useAuth.ts @@ -63,6 +63,7 @@ export function useAuth() { const login = useCallback( async (loginData: LoginData) => { + if (!backendUrl) return; const keys = await keysFromMnemonic(loginData.mnemonic); const publicKeyBase64Url = bytesToBase64Url(keys.publicKey); const { challenge } = await getLoginChallengeToken( @@ -87,7 +88,7 @@ export function useAuth() { ); const logout = useCallback(async () => { - if (!currentAccount) return; + if (!currentAccount || !backendUrl) return; try { await removeSession( backendUrl, @@ -102,6 +103,7 @@ export function useAuth() { const register = useCallback( async (registerData: RegistrationData) => { + if (!backendUrl) return; const { challenge } = await getRegisterChallengeToken( backendUrl, registerData.recaptchaToken, @@ -134,6 +136,7 @@ export function useAuth() { progressItems: Record, bookmarks: Record, ) => { + if (!backendUrl) return; if ( Object.keys(progressItems).length === 0 && Object.keys(bookmarks).length === 0 @@ -159,6 +162,7 @@ export function useAuth() { const restore = useCallback( async (account: AccountWithToken) => { + if (!backendUrl) return; let user: { user: UserResponse; session: SessionResponse }; try { user = await getUser(backendUrl, account.token); diff --git a/src/hooks/auth/useBackendUrl.ts b/src/hooks/auth/useBackendUrl.ts index e417ed06..64545227 100644 --- a/src/hooks/auth/useBackendUrl.ts +++ b/src/hooks/auth/useBackendUrl.ts @@ -1,7 +1,7 @@ import { conf } from "@/setup/config"; import { useAuthStore } from "@/stores/auth"; -export function useBackendUrl() { +export function useBackendUrl(): string | undefined { const backendUrl = useAuthStore((s) => s.backendUrl); return backendUrl ?? conf().BACKEND_URL; } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 18b755ca..cebf1ddd 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -70,6 +70,7 @@ export function AccountSettings(props: { const url = useBackendUrl(); const { account } = props; const [sessionsResult, execSessions] = useAsyncFn(() => { + if (!url) return Promise.resolve([]); return getSessions(url, account); }, [account, url]); useEffect(() => { @@ -144,7 +145,7 @@ export function SettingsPage() { ); const saveChanges = useCallback(async () => { - if (account) { + if (account && backendUrl) { if ( state.appLanguage.changed || state.theme.changed || diff --git a/src/pages/onboarding/OnboardingProxy.tsx b/src/pages/onboarding/OnboardingProxy.tsx index e67c0769..00584eef 100644 --- a/src/pages/onboarding/OnboardingProxy.tsx +++ b/src/pages/onboarding/OnboardingProxy.tsx @@ -43,7 +43,7 @@ export function OnboardingProxyPage() { throw new Error("onboarding.proxy.input.errorNotProxy"); setProxySet([url]); - if (account) { + if (account && backendUrl) { await updateSettings(backendUrl, account, { proxyUrls: [url], }); diff --git a/src/pages/parts/admin/BackendTestPart.tsx b/src/pages/parts/admin/BackendTestPart.tsx index 449e9643..aadd9397 100644 --- a/src/pages/parts/admin/BackendTestPart.tsx +++ b/src/pages/parts/admin/BackendTestPart.tsx @@ -32,13 +32,21 @@ export function BackendTestPart() { value: null, }); + if (!backendUrl) { + return setStatus({ + hasTested: true, + success: false, + errorText: "Backend URL is not set", + value: null, + }); + } + try { const backendData = await getBackendMeta(backendUrl); return setStatus({ hasTested: true, success: true, - errorText: - "Failed to call backend, double check the URL key and your internet connection", + errorText: "", value: backendData, }); } catch (err) { @@ -46,7 +54,7 @@ export function BackendTestPart() { hasTested: true, success: false, errorText: - "Failed to call backend, double check the URL key and your internet connection", + "Failed to call backend, double check the URL, your internet connection, and ensure CORS is properly configured on your backend.", value: null, }); } diff --git a/src/pages/parts/auth/LoginFormPart.tsx b/src/pages/parts/auth/LoginFormPart.tsx index e4bed3ec..ac63fad2 100644 --- a/src/pages/parts/auth/LoginFormPart.tsx +++ b/src/pages/parts/auth/LoginFormPart.tsx @@ -52,6 +52,9 @@ export function LoginFormPart(props: LoginFormPartProps) { throw err; } + if (!account) + throw new Error(t("auth.login.validationError") ?? undefined); + await importData(account, progressItems, bookmarkItems); await restore(account); diff --git a/src/pages/parts/auth/TrustBackendPart.tsx b/src/pages/parts/auth/TrustBackendPart.tsx index 3fa818ef..0d71e403 100644 --- a/src/pages/parts/auth/TrustBackendPart.tsx +++ b/src/pages/parts/auth/TrustBackendPart.tsx @@ -22,8 +22,12 @@ interface TrustBackendPartProps { export function TrustBackendPart(props: TrustBackendPartProps) { const navigate = useNavigate(); const backendUrl = useBackendUrl(); - const hostname = useMemo(() => new URL(backendUrl).hostname, [backendUrl]); + const hostname = useMemo( + () => (backendUrl ? new URL(backendUrl).hostname : ""), + [backendUrl], + ); const result = useAsync(() => { + if (!backendUrl) return Promise.resolve(null); return getBackendMeta(backendUrl); }, [backendUrl]); const { t } = useTranslation(); diff --git a/src/pages/parts/auth/VerifyPassphrasePart.tsx b/src/pages/parts/auth/VerifyPassphrasePart.tsx index 2f040ccb..1d6ea62a 100644 --- a/src/pages/parts/auth/VerifyPassphrasePart.tsx +++ b/src/pages/parts/auth/VerifyPassphrasePart.tsx @@ -47,6 +47,8 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) { const [result, execute] = useAsyncFn( async (inputMnemonic: string) => { + if (!backendUrl) + throw new Error(t("auth.verify.noBackendUrl") ?? undefined); if (!props.mnemonic || !props.userData) throw new Error(t("auth.verify.invalidData") ?? undefined); @@ -68,6 +70,9 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) { recaptchaToken, }); + if (!account) + throw new Error(t("auth.verify.registrationFailed") ?? undefined); + await importData(account, progressItems, bookmarkItems); await updateSettings(backendUrl, account, { diff --git a/src/pages/parts/settings/AccountActionsPart.tsx b/src/pages/parts/settings/AccountActionsPart.tsx index e19aa9e9..f1e50dbf 100644 --- a/src/pages/parts/settings/AccountActionsPart.tsx +++ b/src/pages/parts/settings/AccountActionsPart.tsx @@ -18,7 +18,7 @@ export function AccountActionsPart() { const deleteModal = useModal("account-delete"); const [deleteResult, deleteExec] = useAsyncFn(async () => { - if (!account) return; + if (!account || !url) return; await deleteUser(url, account); await logout(); deleteModal.hide(); diff --git a/src/pages/parts/settings/DeviceListPart.tsx b/src/pages/parts/settings/DeviceListPart.tsx index afaea589..c7326059 100644 --- a/src/pages/parts/settings/DeviceListPart.tsx +++ b/src/pages/parts/settings/DeviceListPart.tsx @@ -24,6 +24,7 @@ export function Device(props: { const token = useAuthStore((s) => s.account?.token); const [result, exec] = useAsyncFn(async () => { if (!token) throw new Error("No token present"); + if (!url) throw new Error("No backend set"); await removeSession(url, token, props.id); props.onRemove?.(); }, [url, token, props.id]); diff --git a/src/pages/parts/settings/SidebarPart.tsx b/src/pages/parts/settings/SidebarPart.tsx index 47469a5e..13db06fe 100644 --- a/src/pages/parts/settings/SidebarPart.tsx +++ b/src/pages/parts/settings/SidebarPart.tsx @@ -14,9 +14,9 @@ import { useAuthStore } from "@/stores/auth"; const rem = 16; -function SecureBadge(props: { url: string }) { +function SecureBadge(props: { url: string | undefined }) { const { t } = useTranslation(); - const secure = props.url.startsWith("https://"); + const secure = props.url ? props.url.startsWith("https://") : false; return (
@@ -68,6 +68,7 @@ export function SidebarPart() { const backendUrl = useBackendUrl(); const backendMeta = useAsync(async () => { + if (!backendUrl) return; return getBackendMeta(backendUrl); }, [backendUrl]); @@ -159,7 +160,7 @@ export function SidebarPart() {

- {backendUrl.replace(/https?:\/\//, "")} + {backendUrl?.replace(/https?:\/\//, "") ?? "—"}

diff --git a/src/stores/bookmarks/BookmarkSyncer.tsx b/src/stores/bookmarks/BookmarkSyncer.tsx index 1f3b705d..7d4d0bc0 100644 --- a/src/stores/bookmarks/BookmarkSyncer.tsx +++ b/src/stores/bookmarks/BookmarkSyncer.tsx @@ -60,6 +60,7 @@ export function BookmarkSyncer() { useEffect(() => { const interval = setInterval(() => { (async () => { + if (!url) return; const state = useBookmarkStore.getState(); const user = useAuthStore.getState(); await syncBookmarks( diff --git a/src/stores/progress/ProgressSyncer.tsx b/src/stores/progress/ProgressSyncer.tsx index c9b4eb58..67aae877 100644 --- a/src/stores/progress/ProgressSyncer.tsx +++ b/src/stores/progress/ProgressSyncer.tsx @@ -62,6 +62,7 @@ export function ProgressSyncer() { useEffect(() => { const interval = setInterval(() => { (async () => { + if (!url) return; const state = useProgressStore.getState(); const user = useAuthStore.getState(); await syncProgress( diff --git a/src/stores/subtitles/SettingsSyncer.tsx b/src/stores/subtitles/SettingsSyncer.tsx index 48b25bbe..ca3c0817 100644 --- a/src/stores/subtitles/SettingsSyncer.tsx +++ b/src/stores/subtitles/SettingsSyncer.tsx @@ -16,6 +16,7 @@ export function SettingsSyncer() { useEffect(() => { const interval = setInterval(() => { (async () => { + if (!url) return; const state = useSubtitleStore.getState(); const user = useAuthStore.getState(); if (state.lastSync.lastSelectedLanguage === state.lastSelectedLanguage)