pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/

fix: change useBackendUrl to possibly be undefined, add checks to avoid useless requests to nonexistent backend

qtchaos fcf42a4e b18269b4

+42 -14
-2
src/backend/accounts/progress.ts
··· 2 2 3 3 import { getAuthHeaders } from "@/backend/accounts/auth"; 4 4 import { ProgressResponse } from "@/backend/accounts/user"; 5 - import { BACKEND_URL } from "@/setup/constants"; 6 5 import { AccountWithToken } from "@/stores/auth"; 7 6 import { ProgressMediaItem, ProgressUpdateItem } from "@/stores/progress"; 8 7 ··· 104 103 episodeId?: string, 105 104 seasonId?: string, 106 105 ) { 107 - if (!BACKEND_URL) return; 108 106 await ofetch(`/users/${account.userId}/progress/${id}`, { 109 107 method: "DELETE", 110 108 headers: getAuthHeaders(account.token),
+5 -1
src/hooks/auth/useAuth.ts
··· 63 63 64 64 const login = useCallback( 65 65 async (loginData: LoginData) => { 66 + if (!backendUrl) return; 66 67 const keys = await keysFromMnemonic(loginData.mnemonic); 67 68 const publicKeyBase64Url = bytesToBase64Url(keys.publicKey); 68 69 const { challenge } = await getLoginChallengeToken( ··· 87 88 ); 88 89 89 90 const logout = useCallback(async () => { 90 - if (!currentAccount) return; 91 + if (!currentAccount || !backendUrl) return; 91 92 try { 92 93 await removeSession( 93 94 backendUrl, ··· 102 103 103 104 const register = useCallback( 104 105 async (registerData: RegistrationData) => { 106 + if (!backendUrl) return; 105 107 const { challenge } = await getRegisterChallengeToken( 106 108 backendUrl, 107 109 registerData.recaptchaToken, ··· 134 136 progressItems: Record<string, ProgressMediaItem>, 135 137 bookmarks: Record<string, BookmarkMediaItem>, 136 138 ) => { 139 + if (!backendUrl) return; 137 140 if ( 138 141 Object.keys(progressItems).length === 0 && 139 142 Object.keys(bookmarks).length === 0 ··· 159 162 160 163 const restore = useCallback( 161 164 async (account: AccountWithToken) => { 165 + if (!backendUrl) return; 162 166 let user: { user: UserResponse; session: SessionResponse }; 163 167 try { 164 168 user = await getUser(backendUrl, account.token);
+1 -1
src/hooks/auth/useBackendUrl.ts
··· 1 1 import { conf } from "@/setup/config"; 2 2 import { useAuthStore } from "@/stores/auth"; 3 3 4 - export function useBackendUrl() { 4 + export function useBackendUrl(): string | undefined { 5 5 const backendUrl = useAuthStore((s) => s.backendUrl); 6 6 return backendUrl ?? conf().BACKEND_URL; 7 7 }
+2 -1
src/pages/Settings.tsx
··· 70 70 const url = useBackendUrl(); 71 71 const { account } = props; 72 72 const [sessionsResult, execSessions] = useAsyncFn(() => { 73 + if (!url) return Promise.resolve([]); 73 74 return getSessions(url, account); 74 75 }, [account, url]); 75 76 useEffect(() => { ··· 144 145 ); 145 146 146 147 const saveChanges = useCallback(async () => { 147 - if (account) { 148 + if (account && backendUrl) { 148 149 if ( 149 150 state.appLanguage.changed || 150 151 state.theme.changed ||
+1 -1
src/pages/onboarding/OnboardingProxy.tsx
··· 43 43 throw new Error("onboarding.proxy.input.errorNotProxy"); 44 44 setProxySet([url]); 45 45 46 - if (account) { 46 + if (account && backendUrl) { 47 47 await updateSettings(backendUrl, account, { 48 48 proxyUrls: [url], 49 49 });
+11 -3
src/pages/parts/admin/BackendTestPart.tsx
··· 32 32 value: null, 33 33 }); 34 34 35 + if (!backendUrl) { 36 + return setStatus({ 37 + hasTested: true, 38 + success: false, 39 + errorText: "Backend URL is not set", 40 + value: null, 41 + }); 42 + } 43 + 35 44 try { 36 45 const backendData = await getBackendMeta(backendUrl); 37 46 return setStatus({ 38 47 hasTested: true, 39 48 success: true, 40 - errorText: 41 - "Failed to call backend, double check the URL key and your internet connection", 49 + errorText: "", 42 50 value: backendData, 43 51 }); 44 52 } catch (err) { ··· 46 54 hasTested: true, 47 55 success: false, 48 56 errorText: 49 - "Failed to call backend, double check the URL key and your internet connection", 57 + "Failed to call backend, double check the URL, your internet connection, and ensure CORS is properly configured on your backend.", 50 58 value: null, 51 59 }); 52 60 }
+3
src/pages/parts/auth/LoginFormPart.tsx
··· 52 52 throw err; 53 53 } 54 54 55 + if (!account) 56 + throw new Error(t("auth.login.validationError") ?? undefined); 57 + 55 58 await importData(account, progressItems, bookmarkItems); 56 59 57 60 await restore(account);
+5 -1
src/pages/parts/auth/TrustBackendPart.tsx
··· 22 22 export function TrustBackendPart(props: TrustBackendPartProps) { 23 23 const navigate = useNavigate(); 24 24 const backendUrl = useBackendUrl(); 25 - const hostname = useMemo(() => new URL(backendUrl).hostname, [backendUrl]); 25 + const hostname = useMemo( 26 + () => (backendUrl ? new URL(backendUrl).hostname : ""), 27 + [backendUrl], 28 + ); 26 29 const result = useAsync(() => { 30 + if (!backendUrl) return Promise.resolve(null); 27 31 return getBackendMeta(backendUrl); 28 32 }, [backendUrl]); 29 33 const { t } = useTranslation();
+5
src/pages/parts/auth/VerifyPassphrasePart.tsx
··· 47 47 48 48 const [result, execute] = useAsyncFn( 49 49 async (inputMnemonic: string) => { 50 + if (!backendUrl) 51 + throw new Error(t("auth.verify.noBackendUrl") ?? undefined); 50 52 if (!props.mnemonic || !props.userData) 51 53 throw new Error(t("auth.verify.invalidData") ?? undefined); 52 54 ··· 67 69 userData: props.userData, 68 70 recaptchaToken, 69 71 }); 72 + 73 + if (!account) 74 + throw new Error(t("auth.verify.registrationFailed") ?? undefined); 70 75 71 76 await importData(account, progressItems, bookmarkItems); 72 77
+1 -1
src/pages/parts/settings/AccountActionsPart.tsx
··· 18 18 const deleteModal = useModal("account-delete"); 19 19 20 20 const [deleteResult, deleteExec] = useAsyncFn(async () => { 21 - if (!account) return; 21 + if (!account || !url) return; 22 22 await deleteUser(url, account); 23 23 await logout(); 24 24 deleteModal.hide();
+1
src/pages/parts/settings/DeviceListPart.tsx
··· 24 24 const token = useAuthStore((s) => s.account?.token); 25 25 const [result, exec] = useAsyncFn(async () => { 26 26 if (!token) throw new Error("No token present"); 27 + if (!url) throw new Error("No backend set"); 27 28 await removeSession(url, token, props.id); 28 29 props.onRemove?.(); 29 30 }, [url, token, props.id]);
+4 -3
src/pages/parts/settings/SidebarPart.tsx
··· 14 14 15 15 const rem = 16; 16 16 17 - function SecureBadge(props: { url: string }) { 17 + function SecureBadge(props: { url: string | undefined }) { 18 18 const { t } = useTranslation(); 19 - const secure = props.url.startsWith("https://"); 19 + const secure = props.url ? props.url.startsWith("https://") : false; 20 20 return ( 21 21 <div className="flex items-center gap-1 -mx-1 ml-3 px-1 rounded bg-largeCard-background font-bold"> 22 22 <Icon icon={secure ? Icons.LOCK : Icons.UNLOCK} /> ··· 68 68 const backendUrl = useBackendUrl(); 69 69 70 70 const backendMeta = useAsync(async () => { 71 + if (!backendUrl) return; 71 72 return getBackendMeta(backendUrl); 72 73 }, [backendUrl]); 73 74 ··· 159 160 <SecureBadge url={backendUrl} /> 160 161 </div> 161 162 <p className="text-white"> 162 - {backendUrl.replace(/https?:\/\//, "")} 163 + {backendUrl?.replace(/https?:\/\//, "") ?? "—"} 163 164 </p> 164 165 </div> 165 166
+1
src/stores/bookmarks/BookmarkSyncer.tsx
··· 60 60 useEffect(() => { 61 61 const interval = setInterval(() => { 62 62 (async () => { 63 + if (!url) return; 63 64 const state = useBookmarkStore.getState(); 64 65 const user = useAuthStore.getState(); 65 66 await syncBookmarks(
+1
src/stores/progress/ProgressSyncer.tsx
··· 62 62 useEffect(() => { 63 63 const interval = setInterval(() => { 64 64 (async () => { 65 + if (!url) return; 65 66 const state = useProgressStore.getState(); 66 67 const user = useAuthStore.getState(); 67 68 await syncProgress(
+1
src/stores/subtitles/SettingsSyncer.tsx
··· 16 16 useEffect(() => { 17 17 const interval = setInterval(() => { 18 18 (async () => { 19 + if (!url) return; 19 20 const state = useSubtitleStore.getState(); 20 21 const user = useAuthStore.getState(); 21 22 if (state.lastSync.lastSelectedLanguage === state.lastSelectedLanguage)