import { defineStore } from "pinia"; import { ref } from "vue"; import { repo } from "../adapters"; // One client build this server can hand out. Platforms it holds nothing for are // ABSENT from the map rather than present-and-null, so a key test is the whole // question; see client_dist.py. // // Named for its twin in `core/src/sync/client.rs`, which deserializes the same // payload. That one is deliberately NARROWER — it only ever reads // `/api/client/android`, so its `version_code` is an `i64` and it declares none of // the fields below that Android does not use. Widening it to match this is not a // tidy-up: every phone in the field runs the current shape. export interface ClientRelease { // The table row's id — "android", "linux-deb", "linux-appimage", "windows". platform: string; // What a person calls it, named for the DISTRO rather than the package format // ("Debian / Ubuntu", not ".deb"). The server owns this wording so the five // labels cannot drift apart across the surfaces that show them. label: string; version: string; // What decides "is this newer". The name is for people and sorts like a string. // // Not one type across platforms, deliberately: Android's is an integer because // Android's own install gate compares one, and the desktop's is Tauri's semver // key `1.0.`. Nothing in this app compares them — the union is here so // the shape is honest rather than to be read. version_code: number | string; size: number; sha256: string; // A PATH, never an absolute URL — the client joins it to the server it is // already talking to. url: string; // Present only for the AppImage: the minisign signature the desktop updater // checks before replacing the running binary. signature?: string; } export interface PublicConfig { site_name: string; allow_registration: boolean; version: string; enable_url_unfurl: boolean; // How many days a note survives in Trash before the server purges it. 0 = forever. trash_retention_days: number; // Every client this server holds, keyed by platform id. Absent on a server that // holds none, and absent on the desktop's own offline config — the Tauri build // answers `config_get` locally and has no clients to hand out. // // `/api/config` also carries `android_client`, which is NOT declared here: it // exists for phones in the field polling for their own update, not for this app, // and reading it here would be a second path to the same fact. clients?: Record; } // Public, unauthenticated app config (site name, whether signups are open). export const useConfigStore = defineStore("config", () => { const siteName = ref("ThoughtSync"); const allowRegistration = ref(true); const version = ref(""); const enableUrlUnfurl = ref(true); // Mirrors the server default (settings.REGISTRY). Only used if /api/config is // unreachable — and 30 is a safer stand-in than 0, since claiming "kept forever" // when the server is actually purging is the wrong way to be wrong. const trashRetentionDays = ref(30); // Empty until proven otherwise: a server with no clients, and an older server // that never had the field, both correctly offer no downloads. const clients = ref>({}); const loaded = ref(false); async function load(): Promise { if (loaded.value) return; try { const cfg = await repo.config.get(); siteName.value = cfg.site_name; allowRegistration.value = cfg.allow_registration; version.value = cfg.version; enableUrlUnfurl.value = cfg.enable_url_unfurl ?? true; trashRetentionDays.value = cfg.trash_retention_days ?? 30; clients.value = cfg.clients ?? {}; } catch { // Keep defaults if the config endpoint is unreachable. } finally { loaded.value = true; } } async function reload(): Promise { loaded.value = false; await load(); } return { siteName, allowRegistration, version, enableUrlUnfurl, trashRetentionDays, clients, loaded, load, reload, }; });