From be7abd52a3b04d02e304430d0f393744691ff533 Mon Sep 17 00:00:00 2001 From: Dasemu Date: Tue, 25 Aug 2026 19:08:54 +0200 Subject: [PATCH] Fase 3: make the remote transport config-driven, not Electron-only Every PersistentDB dispatch site in persist_db.cljs branched on electron-runtime?, so a web or mobile client could never take the remote path -- only Electron's IPC-managed local daemon could. The remote client itself (persist_db/remote.cljs) was already platform-agnostic (plain js/fetch and js/EventSource), so this is a transport-selection fix, not a new architecture. - frontend.persist-db/remote-runtime?: true for Electron OR a non-Electron client with config/get-server-base-url configured. Replaces every electron-runtime? check at the seven PersistentDB call sites ( 401, login with correct/wrong credentials -> token/401, healthz unauthenticated with :pid/:root-dir absent, authenticated invoke (list-db) -> 200, per-origin CORS reflection, bad Host -> 403, and SSE both without a token (401) and with one as a query param (200). Plus clojure -M:cljs compile app db-worker-node electron (0 warnings) and the full logseq.db-worker.daemon-test, logseq.cli.server-test, frontend.worker.db-worker-node-test, electron.db-worker-manager-test, frontend.state-test, frontend.persist-db.remote-test suites (134 tests, 541 assertions, 0 failures). Co-Authored-By: Claude Sonnet 5 --- src/main/frontend/components/settings.cljs | 100 +++++++++++++++++++++ src/main/frontend/config.cljs | 47 ++++++++++ src/main/frontend/handler/events/ui.cljs | 5 ++ src/main/frontend/persist_db.cljs | 69 +++++++++++--- src/main/frontend/persist_db/remote.cljs | 38 ++++++++ 5 files changed, 247 insertions(+), 12 deletions(-) diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 6256acd..e11c5f6 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -21,6 +21,7 @@ [frontend.mobile.util :as mobile-util] [frontend.modules.instrumentation.core :as instrument] [frontend.modules.shortcut.data-helper :as shortcut-helper] + [frontend.persist-db.remote :as remote] [frontend.rfx :as rfx] [frontend.spec.storage :as storage-spec] [frontend.state :as state] @@ -689,6 +690,104 @@ :on-click (fn [] (reset-url!))} (t :settings.sync-server/reset)))]]])) +(hsx/defc db-worker-server-settings-container + "Connects a web/mobile client to a self-hosted db-worker-node server (see + frontend.persist-db/remote-runtime?). Electron manages its own local + daemon instead and doesn't need this. Reconnecting or disconnecting + requires a reload since frontend.persist-db/ (remote/login! {:base-url trimmed-url + :username username + :password password + :device-label (str (.-platform js/navigator))}) + (p/then (fn [{:keys [token]}] + (config/set-server-base-url! trimmed-url) + (config/set-server-token! token) + (notification/show! "Connected. Reloading..." :success) + (js/setTimeout #(js/location.reload) 500))) + (p/catch (fn [error] + (set-connecting! false) + (notification/show! + (str "Failed to connect: " (or (.-message error) error)) + :error))))))))] + [:div.cp__settings-db-worker-server-cnt + [:h1.mb-2.text-2xl.font-bold "Server"] + [:div.p-2 + [:p.text-sm.opacity-70.mb-4 + "Connect this client to a self-hosted Logseq server (db-worker-node) instead of storing graphs locally in the browser."] + (if connected? + [:div + [:p [:strong "Connected to: "] current-url] + [:p.pt-2 + (shui/button + {:size :sm :variant :outline :on-click disconnect!} + "Disconnect")]] + [:div + [:p + [:label [:strong "Server URL"] + [:input.form-input.is-small + {:value url + :placeholder "https://your-server.example:PORT" + :style {:width "100%"} + :on-change #(set-url! (util/evalue %))}]]] + [:p.pt-2 + [:label [:strong "Username"] + [:input.form-input.is-small + {:value username + :style {:width "100%"} + :on-change #(set-username! (util/evalue %))}]]] + [:p.pt-2 + [:label [:strong "Password"] + [:input.form-input.is-small + {:type "password" + :value password + :style {:width "100%"} + :on-change #(set-password! (util/evalue %))}]]] + [:p.pt-2 + (shui/button + {:size :sm :disabled connecting? :on-click connect!} + (if connecting? "Connecting..." "Connect"))]])]])) + +(hsx/defc db-worker-server-button + [] + (let [current-url (config/get-server-base-url)] + (ui/button [:span.flex.items-center + [:span.pr-1 (if (seq current-url) current-url "Connect to server")] + (ui/icon "edit")] + :class "text-sm" + :on-click #(state/pub-event! [:go/db-worker-server-settings])))) + +(defn db-worker-server-row [] + (row-with-button-action + {:left-label "Server" + :action (db-worker-server-button)})) + (hsx/defc sync-server-url-button [] (let [current-url (config/get-custom-sync-server-url)] @@ -924,6 +1023,7 @@ (when (and (or util/mac? util/win32?) (util/electron?)) (app-auto-update-row t)) (usage-diagnostics-row t instrument-disabled?) (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?)) + (when-not (util/electron?) (db-worker-server-row)) (sync-server-url-row) (publish-server-url-row) (when (util/electron?) (https-user-agent-row https-agent-opts)) diff --git a/src/main/frontend/config.cljs b/src/main/frontend/config.cljs index be0cab1..41830a3 100644 --- a/src/main/frontend/config.cljs +++ b/src/main/frontend/config.cljs @@ -99,6 +99,53 @@ (custom-url->http-base custom) default-db-sync-http-base)) +;; Web/mobile clients connect to a self-hosted db-worker-node daemon by +;; base URL + bearer token, instead of Electron's IPC-managed local runtime +;; (see frontend.persist-db/remote-runtime?). Stored client-side only; the +;; daemon holds the authoritative credential (see +;; frontend.worker.db-worker-node-auth). +(defn get-server-base-url + "Read the configured db-worker-node server base URL from localStorage. + Returns nil when not set or empty." + [] + (when-not util/node-test? + (let [v (.getItem js/localStorage "db-worker-server-url")] + (when (and (string? v) (not (string/blank? v))) + v)))) + +(defn set-server-base-url! + "Persist the db-worker-node server base URL to localStorage. Pass nil or + empty string to clear." + [url] + (when-not util/node-test? + (if (or (nil? url) (string/blank? url)) + (.removeItem js/localStorage "db-worker-server-url") + (.setItem js/localStorage "db-worker-server-url" (string/trim url))))) + +(defn get-server-token + "Read the current db-worker-node session bearer token from localStorage. + Returns nil when not set or empty." + [] + (when-not util/node-test? + (let [v (.getItem js/localStorage "db-worker-server-token")] + (when (and (string? v) (not (string/blank? v))) + v)))) + +(defn set-server-token! + "Persist the db-worker-node session bearer token to localStorage. Pass + nil or empty string to clear (e.g. on logout or a 401)." + [token] + (when-not util/node-test? + (if (or (nil? token) (string/blank? token)) + (.removeItem js/localStorage "db-worker-server-token") + (.setItem js/localStorage "db-worker-server-token" (string/trim token))))) + +(defn clear-server-config! + "Clear both the configured server URL and session token, e.g. on logout." + [] + (set-server-base-url! nil) + (set-server-token! nil)) + (defn get-custom-publish-server-url "Read the user-configured custom publish server URL from localStorage. Returns nil when not set or empty." diff --git a/src/main/frontend/handler/events/ui.cljs b/src/main/frontend/handler/events/ui.cljs index 0a76795..a0891ba 100644 --- a/src/main/frontend/handler/events/ui.cljs +++ b/src/main/frontend/handler/events/ui.cljs @@ -105,6 +105,11 @@ (settings/sync-server-url-settings-container) {:id :sync-server-panel :center? true :class "lg:max-w-2xl"})) +(defevent! :go/db-worker-server-settings [[_]] + (shui/dialog-open! + (settings/db-worker-server-settings-container) + {:id :db-worker-server-panel :center? true :class "lg:max-w-2xl"})) + (defevent! :go/publish-server-settings [[_]] (shui/dialog-open! (settings/publish-server-url-settings-container) diff --git a/src/main/frontend/persist_db.cljs b/src/main/frontend/persist_db.cljs index 1c95f54..80820d3 100644 --- a/src/main/frontend/persist_db.cljs +++ b/src/main/frontend/persist_db.cljs @@ -25,7 +25,7 @@ (defonce remote-repo (atom nil)) (defonce remote-runtime-state (atom nil)) -(declare (remote/invoke! (:client remote-client) "thread-api/close-db" [repo]) @@ -346,12 +388,15 @@ (defn js (cond-> {:method method + :headers (or headers {})} + body (assoc :body body)))) + text (.text res)] + {:status (.-status res) + :body text})) + +(defn login! + "Logs into a db-worker-node daemon's HTTP auth (see + frontend.worker.db-worker-node-auth) and returns {:token :session-id} on + success. Used by the web/mobile server-connection settings UI; Electron + gets its token from the main process instead (see + frontend.persist-db/js {:username username + :password password + :deviceLabel device-label})) + {:keys [status body]} ((or fetch-fn default-fetch-json!) + {:method "POST" + :url (login-url base-url) + :headers {"Content-Type" "application/json"} + :body payload}) + parsed (parse-response-body body)] + (if (<= 200 status 299) + {:token (:token parsed) :session-id (:sessionId parsed)} + (let [error (:error parsed)] + (throw (ex-info (or (:message error) "login failed") + (cond-> {:status status + :code (normalize-code (:code error))} + error (assoc :error error)))))))) + (defn- decode-event [{:keys [type payload]}] (let [decoded (when (some? payload)