edee7b9f28
- Auto-Recovery in adapter.js: wenn Cloud-Modus gesetzt + lokale Daten vorhanden + keine Cloud-Session → Cloud-Konfiguration wird beim nächsten App-Start automatisch zurückgenommen. Marker rapport_080_recovery verhindert wiederholte Auslösung. - UpdateNotifier wird in allen Pre-Login-Screens gerendert (BackendChoice, Setup, MigrationScreen, CloudSetup, Login) — so kann ein hängender Wizard sich via Auto-Update selbst befreien. - Tauri-Builds ignorieren VITE_SUPABASE_URL aus dem Build. Desktop-User geben die Server-URL immer aktiv im Login ein, statt eine irrelevante Default-IP vorgesetzt zu bekommen. - Anon-Key bleibt aus dem Build (kein Geheimnis, pro Cloud-Instanz fix). - BackendChoice zeigt nur dann eine vorkonfigurierte URL, wenn nicht in Tauri. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
124 lines
5.2 KiB
JavaScript
124 lines
5.2 KiB
JavaScript
// Storage-Adapter: Auswahl zwischen LocalStorage und Supabase (Cloud).
|
|
//
|
|
// Auswahl-Logik:
|
|
// localStorage["rapport_backend"] === "cloud" → SupabaseAdapter
|
|
// alles andere (default) → LocalStorageAdapter
|
|
//
|
|
// Umschalten: `localStorage.setItem("rapport_backend", "cloud")` (später UI-Toggle).
|
|
// Cloud braucht zusätzlich VITE_SUPABASE_URL und VITE_SUPABASE_ANON_KEY in .env.local.
|
|
// Fallback: wenn Cloud gewählt aber env fehlt, kommt LocalStorage zurück (mit Warning).
|
|
//
|
|
// Bewusst NICHT im Adapter:
|
|
// - UI-State (Dark Mode, Zoom, …) — per-Device, bleibt direkt in localStorage
|
|
// - Session/Auth — sessionStorage / Supabase-Auth-eigenes Storage
|
|
// - Migrations — siehe migrations.js, läuft nach dem load auf den Rohdaten
|
|
|
|
import { STORAGE_KEY } from "../constants.js";
|
|
import { SupabaseAdapter } from "./supabase-adapter.js";
|
|
|
|
export class LocalStorageAdapter {
|
|
async hasExistingData() {
|
|
return !!localStorage.getItem(STORAGE_KEY);
|
|
}
|
|
|
|
async load() {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (!stored) return null;
|
|
return JSON.parse(stored);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async save(data) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
|
} catch (e) {
|
|
console.error("LocalStorage save failed:", e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async clear() {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
}
|
|
|
|
function createAdapter() {
|
|
const isTauri = typeof window !== "undefined" && !!window.__TAURI_INTERNALS__;
|
|
// Build-time-URL nur für Web-Deploy gültig. Tauri-Builds ignorieren den
|
|
// eingebrannten Wert — Desktop-User geben die Server-URL aktiv ein.
|
|
// Der Anon-Key bleibt aus dem Build, weil er pro Cloud-Instanz konstant ist
|
|
// (kein User-Geheimnis, sondern Public-Konfig).
|
|
const envUrl = isTauri ? null : import.meta.env.VITE_SUPABASE_URL;
|
|
const envKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
|
|
if (typeof localStorage !== "undefined") {
|
|
// ── Auto-Recovery für 0.8.0-Upgrade-Bug ──────────────────────────────
|
|
// 0.8.0 hat Cloud-Modus ungewollt gesetzt bei Lokal-Usern (siehe 0.8.1
|
|
// CHANGELOG). 0.8.1 verhindert das nur prospektiv, behebt aber den
|
|
// bestehenden Cloud-State nicht. Hier räumen wir nachträglich auf:
|
|
// Cloud-Modus gesetzt + lokale Daten vorhanden + keine Cloud-Session
|
|
// → Cloud-State löschen, User landet im BackendChoice oder Lokal-Modus.
|
|
const RECOVERY_MARKER = "rapport_080_recovery";
|
|
const backend = localStorage.getItem("rapport_backend");
|
|
const hasLocalData = !!localStorage.getItem(STORAGE_KEY);
|
|
const hasCloudSession = !!localStorage.getItem("rapport_supabase_session");
|
|
if (backend === "cloud" && hasLocalData && !hasCloudSession && !localStorage.getItem(RECOVERY_MARKER)) {
|
|
console.warn("Auto-Recovery: Cloud-Modus gesetzt, aber Lokal-Daten vorhanden und kein Cloud-Login — räume Cloud-State auf.");
|
|
localStorage.removeItem("rapport_backend");
|
|
localStorage.removeItem("rapport_backend_chosen");
|
|
localStorage.removeItem("rapport_cloud_url");
|
|
localStorage.setItem(RECOVERY_MARKER, "1");
|
|
}
|
|
|
|
// ── Auto-Cloud-Default für Web-Deploy ────────────────────────────────
|
|
// Nur wenn keine lokalen Daten existieren und der Browser auf einer
|
|
// konfigurierten Web-Instanz landet (envUrl aus build).
|
|
if (import.meta.env.PROD && envUrl && !localStorage.getItem("rapport_backend_chosen") && !hasLocalData) {
|
|
localStorage.setItem("rapport_backend_chosen", "1");
|
|
localStorage.setItem("rapport_backend", "cloud");
|
|
if (!localStorage.getItem("rapport_cloud_url")) {
|
|
localStorage.setItem("rapport_cloud_url", envUrl.replace(/\/+$/, ""));
|
|
}
|
|
}
|
|
}
|
|
|
|
const backend = (typeof localStorage !== "undefined"
|
|
&& localStorage.getItem("rapport_backend")) || "local";
|
|
if (backend === "cloud") {
|
|
// URL kommt bevorzugt aus localStorage (vom User eingegeben). Tauri hat
|
|
// gar keine env-URL; Web-Build hat sie ggf. als Fallback.
|
|
const url = (typeof localStorage !== "undefined" && localStorage.getItem("rapport_cloud_url")) || envUrl;
|
|
if (!url || !envKey) {
|
|
console.warn("rapport_backend=cloud, aber URL oder ANON_KEY fehlen — Fallback auf LocalStorage.");
|
|
return new LocalStorageAdapter();
|
|
}
|
|
console.info("Storage-Adapter: SupabaseAdapter aktiv (URL:", url + ")");
|
|
return new SupabaseAdapter(url, envKey);
|
|
}
|
|
return new LocalStorageAdapter();
|
|
}
|
|
|
|
// Singleton — wird beim Modul-Load gewählt.
|
|
// Switch zur Laufzeit erfordert (vorerst) einen App-Reload.
|
|
export const storage = createAdapter();
|
|
|
|
export const isCloudBackend = storage instanceof SupabaseAdapter;
|
|
|
|
// Für Dev-Tests im Browser: window.__rapport.storage und ein Helper, um die
|
|
// Cloud-Verbindung ohne UI zu testen.
|
|
if (typeof window !== "undefined") {
|
|
window.__rapport = window.__rapport || {};
|
|
window.__rapport.storage = storage;
|
|
window.__rapport.useCloud = () => {
|
|
localStorage.setItem("rapport_backend", "cloud");
|
|
location.reload();
|
|
};
|
|
window.__rapport.useLocal = () => {
|
|
localStorage.setItem("rapport_backend", "local");
|
|
location.reload();
|
|
};
|
|
}
|