diff --git a/apps/electron/src/main/main.ts b/apps/electron/src/main/main.ts index e3099f2..8fca9a4 100644 --- a/apps/electron/src/main/main.ts +++ b/apps/electron/src/main/main.ts @@ -36,6 +36,13 @@ if (verbose) { app.commandLine.appendSwitch("log-level", "0"); } +if ( + process.platform === "linux" && + !app.commandLine.hasSwitch("password-store") +) { + app.commandLine.appendSwitch("password-store", "gnome-libsecret"); +} + if ( process.platform === "linux" && process.env.XDG_SESSION_TYPE === "wayland" && diff --git a/apps/electron/src/main/secureStorage.ts b/apps/electron/src/main/secureStorage.ts index b09117e..9f1f37f 100644 --- a/apps/electron/src/main/secureStorage.ts +++ b/apps/electron/src/main/secureStorage.ts @@ -34,10 +34,6 @@ function validateValue(value: unknown): asserts value is string { } export function getSecureStorageStatus(): DesktopSecureStorageStatus { - if (!safeStorage.isEncryptionAvailable()) { - return { available: false, backend: null }; - } - const backend = process.platform === "linux" ? safeStorage.getSelectedStorageBackend() @@ -48,7 +44,9 @@ export function getSecureStorageStatus(): DesktopSecureStorageStatus { : null; return { - available: process.platform !== "linux" || backend !== "basic_text", + available: + safeStorage.isEncryptionAvailable() && + (process.platform !== "linux" || backend !== "basic_text"), backend, }; } diff --git a/apps/web/src/features/legal/screen.tsx b/apps/web/src/features/legal/screen.tsx index 9859036..48bbb48 100644 --- a/apps/web/src/features/legal/screen.tsx +++ b/apps/web/src/features/legal/screen.tsx @@ -1,16 +1,18 @@ import { useState, useCallback, useEffect } from "react"; import { useStorage } from "@tensamin/storage/context"; -import { Button } from "@tensamin/ui"; -import { Checkbox } from "@tensamin/ui"; +import { + Button, + Checkbox, + CreateScreen, + ErrorScreen, + Label, + Link, + Spinner, +} from "@tensamin/ui"; import { z } from "zod"; -import { ErrorScreen } from "@tensamin/ui"; - import { legalDocsSchema } from "@tensamin/shared/features/legal/schema"; import { log } from "@tensamin/shared/log"; -import { Link } from "@tensamin/ui"; -import { Label } from "@tensamin/ui"; -import { CreateScreen } from "@tensamin/ui"; // Prevents the user from using Tensamin without accepting the privacy policy and terms of service. export default function Screen(props: { children: React.ReactNode }) { @@ -27,6 +29,7 @@ export default function Screen(props: { children: React.ReactNode }) { >(undefined); const [loading, setLoading] = useState(true); + const [showLoading, setShowLoading] = useState(false); const [acceptedPP, acceptPP] = useState(false); const [acceptedTOS, acceptTOS] = useState(false); @@ -49,6 +52,7 @@ export default function Screen(props: { children: React.ReactNode }) { useEffect(() => { let active = true; + let loadingTimer: ReturnType | undefined; void (async () => { try { @@ -63,22 +67,17 @@ export default function Screen(props: { children: React.ReactNode }) { return; } - const current = await fetch("https://legal.tensamin.net/api/current") - .then((res) => res.json()) - .catch((err) => { - if (!active) { - return undefined; - } + loadingTimer = setTimeout(() => { + if (active) setShowLoading(true); + }, 200); - setError("Failed to load legal documents"); - setErrorDescription( - "An error occurred while fetching the legal documents from the server. Please try again later.", - ); - log(0, "Legal", "red", "Failed to fetch legal documents", err); - return undefined; - }); + const response = await fetch("https://legal.tensamin.net/api/current"); + if (!response.ok) { + throw new Error(`Legal documents request failed: ${response.status}`); + } + const current: unknown = await response.json(); - if (!active || current === undefined) { + if (!active) { return; } @@ -122,7 +121,16 @@ export default function Screen(props: { children: React.ReactNode }) { !!currentLocalDocs && currentLocalDocs.tos.hash === safeCurrent.data.tos.hash, ); + } catch (err) { + if (!active) return; + + setError("Failed to load legal documents"); + setErrorDescription( + "An error occurred while fetching the legal documents from the server. Please try again later.", + ); + log(0, "Legal", "red", "Failed to fetch legal documents", err); } finally { + clearTimeout(loadingTimer); if (active) { setLoading(false); } @@ -131,6 +139,7 @@ export default function Screen(props: { children: React.ReactNode }) { return () => { active = false; + clearTimeout(loadingTimer); }; }, [load]); @@ -139,7 +148,22 @@ export default function Screen(props: { children: React.ReactNode }) { } if (loading || userId === undefined) { - return null; + if (!showLoading) return null; + + return ( + +
+ +

+ Fetching legal documents... +

+ +
+
+ ); } const docsMatch = diff --git a/flake.nix b/flake.nix index 46b11fd..a87b56f 100644 --- a/flake.nix +++ b/flake.nix @@ -53,6 +53,7 @@ libglvnd libnotify libpulseaudio + libsecret libuuid libxkbcommon mesa @@ -104,7 +105,8 @@ cp -r usr/* "$out/" mkdir -p "$out/bin" - makeWrapper "$out/opt/Tensamin/tensamin" "$out/bin/tensamin" + makeWrapper "$out/opt/Tensamin/tensamin" "$out/bin/tensamin" \ + --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath electronRuntimeLibs}" substituteInPlace "$out/share/applications/Tensamin.desktop" \ --replace-fail "Exec=/opt/Tensamin/tensamin" "Exec=tensamin" @@ -172,6 +174,7 @@ libglvnd libnotify libpulseaudio + libsecret libuuid libxkbcommon mesa diff --git a/packages/storage/package.json b/packages/storage/package.json index 416af70..734a2cb 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -14,6 +14,7 @@ "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { + "@tauri-apps/api": "^2", "@tensamin/cache": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/mtp": "workspace:*", diff --git a/packages/storage/src/secure.ts b/packages/storage/src/secure.ts index 06ded7a..618977c 100644 --- a/packages/storage/src/secure.ts +++ b/packages/storage/src/secure.ts @@ -1,8 +1,10 @@ import type {} from "@tensamin/shared/desktopMedia"; +import { isTauri } from "@tauri-apps/api/core"; import { getDatabaseEntry, setDatabaseEntry } from "@tensamin/shared/indexedDb"; export type SecureStorageStatus = { - backend: "electron-keyring" | "webcrypto" | "indexeddb"; + backend: + "electron-keyring" | "application-storage" | "webcrypto" | "indexeddb"; secure: boolean; reason?: string; }; @@ -84,6 +86,7 @@ export function isSecureEnvelope(value: unknown): value is SecureEnvelope { } export async function encodeSecureValue(value: unknown): Promise { + if (isTauri()) return value; const key = await getKey(); if (!key) return value; const iv = crypto.getRandomValues(new Uint8Array(12)); @@ -127,6 +130,7 @@ export async function getSecureStorageStatus(): Promise { : "Electron secure storage is unavailable.", }; } + if (isTauri()) return { backend: "application-storage", secure: true }; return (await getKey()) ? { backend: "webcrypto", secure: true } : { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ec95c0..3f8c78f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -818,6 +818,9 @@ importers: packages/storage: dependencies: + '@tauri-apps/api': + specifier: ^2 + version: 2.11.1 '@tensamin/cache': specifier: workspace:* version: link:../cache