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
  (<start-runtime!, <list-db, <unsafe-delete, <close-db, <export-db,
  <import-db, <open-and-fetch-schema, <new).
- Fixes the known <export-db bug: it always went through the local OPFS
  impl regardless of transport, so exporting a remote graph silently
  exported nothing. It now follows the same remote-runtime? dispatch as
  every sibling function.
- <resolve-remote-runtime!/<release-remote-runtime-slot! split the
  Electron-IPC path from a new web path that just reads the locally
  configured base-url/token -- there's no daemon to spawn from a browser,
  only one to log into.
- config.cljs: get/set-server-base-url!, get/set-server-token!,
  clear-server-config!, mirroring the existing custom-sync-server-url
  localStorage pattern.
- remote.cljs: login! POSTs to the daemon's /v1/auth/login (added in Fase
  1) and returns a bearer token.
- A minimal settings panel (components/settings.cljs, non-Electron only):
  server URL + username + password, calls login!, saves the result, and
  reloads. Functional but not visually verified -- no browser available in
  this environment; the request/response wiring is confirmed below against
  a real daemon, not the rendered UI itself.

Verified by running a real db-worker-node daemon and exercising the exact
sequence the new settings panel and remote.cljs drive: unauthenticated
invoke -> 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 19:08:54 +02:00
parent 49d9f7da84
commit be7abd52a3
5 changed files with 247 additions and 12 deletions

View File

@@ -57,6 +57,44 @@
(string? code) (keyword code)
:else :invoke-failed))
(defn- login-url
[base-url]
(str (normalize-base-url base-url) "/v1/auth/login"))
(defn- default-fetch-json!
[{:keys [method url headers body]}]
(p/let [^js res (js/fetch url (clj->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/<resolve-remote-runtime!)."
[{:keys [base-url username password device-label fetch-fn]}]
(p/let [payload (js/JSON.stringify
(clj->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)