mirror of
https://github.com/logseq/logseq.git
synced 2026-05-28 14:39:48 +00:00
Custom Sync Server URL (#12459)
* Add custom sync server Co-authored-by: Tienson Qin <tiensonqin@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -451,8 +451,8 @@
|
||||
(-> (p/do!
|
||||
(state/<invoke-db-worker :thread-api/set-db-sync-config
|
||||
{:enabled? true
|
||||
:ws-url config/db-sync-ws-url
|
||||
:http-base config/db-sync-http-base})
|
||||
:ws-url (config/db-sync-ws-url)
|
||||
:http-base (config/db-sync-http-base)})
|
||||
(p/let [rsa-key-pair (state/<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys)]
|
||||
(set-e2ee-rsa-key-ensured? (some? rsa-key-pair))))
|
||||
(p/catch (fn [e]
|
||||
|
||||
@@ -547,6 +547,76 @@
|
||||
(config-handler/set-config! :feature/enable-flashcards? value)))
|
||||
true))
|
||||
|
||||
(defn- push-sync-config-to-worker!
|
||||
"Push the current sync URL config to the db worker so changes take effect
|
||||
without restarting the app."
|
||||
[]
|
||||
(state/<invoke-db-worker :thread-api/set-db-sync-config
|
||||
{:enabled? true
|
||||
:ws-url (config/db-sync-ws-url)
|
||||
:http-base (config/db-sync-http-base)}))
|
||||
|
||||
(rum/defc sync-server-url-settings-container
|
||||
[]
|
||||
(let [current-url (config/get-custom-sync-server-url)
|
||||
[url set-url!] (rum/use-state (or current-url ""))
|
||||
reset-url! (fn []
|
||||
(config/set-custom-sync-server-url! nil)
|
||||
(set-url! "")
|
||||
(-> (push-sync-config-to-worker!)
|
||||
(p/then #(notification/show! (t :settings-page/sync-server-url-cleared) :success))
|
||||
(p/catch #(notification/show! (str "Failed to update worker: " %) :error))))]
|
||||
[:div.cp__settings-sync-server-cnt
|
||||
[:h1.mb-2.text-2xl.font-bold (t :settings-page/sync-server-url)]
|
||||
[:div.p-2
|
||||
[:p.text-sm.opacity-70.mb-4 (t :settings-page/sync-server-url-desc)]
|
||||
[:p
|
||||
[:label
|
||||
[:strong "URL"]
|
||||
[:input.form-input.is-small
|
||||
{:value url
|
||||
:placeholder config/default-db-sync-http-base
|
||||
:style {:width "100%"}
|
||||
:on-change #(set-url! (util/evalue %))}]]]
|
||||
[:p.pt-2.flex.gap-2
|
||||
(shui/button
|
||||
{:size :sm
|
||||
:on-click (fn []
|
||||
(let [trimmed (string/trim url)]
|
||||
(if (string/blank? trimmed)
|
||||
(reset-url!)
|
||||
(if-not (config/valid-sync-server-url? trimmed)
|
||||
(notification/show! "URL must start with https:// or http://" :error)
|
||||
(do
|
||||
(config/set-custom-sync-server-url! trimmed)
|
||||
(-> (push-sync-config-to-worker!)
|
||||
(p/then #(notification/show! (t :settings-page/sync-server-url-saved) :success))
|
||||
(p/catch #(notification/show! (str "Failed to update worker: " %) :error))))))))}
|
||||
(t :save))
|
||||
(when (seq url)
|
||||
(shui/button
|
||||
{:size :sm
|
||||
:variant :outline
|
||||
:on-click (fn [] (reset-url!))}
|
||||
(t :settings-page/sync-server-url-reset)))]]]))
|
||||
|
||||
(rum/defc sync-server-url-button
|
||||
[]
|
||||
(let [current-url (config/get-custom-sync-server-url)]
|
||||
(ui/button [:span.flex.items-center
|
||||
[:span.pr-1
|
||||
(if (seq current-url)
|
||||
current-url
|
||||
(t :settings-page/sync-server-url-default))]
|
||||
(ui/icon "edit")]
|
||||
:class "text-sm"
|
||||
:on-click #(state/pub-event! [:go/sync-server-settings]))))
|
||||
|
||||
(defn sync-server-url-row []
|
||||
(row-with-button-action
|
||||
{:left-label (t :settings-page/sync-server-url)
|
||||
:action (sync-server-url-button)}))
|
||||
|
||||
(rum/defc user-proxy-settings
|
||||
[{:keys [type protocol host port] :as agent-opts}]
|
||||
(ui/button [:span.flex.items-center
|
||||
@@ -661,6 +731,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?))
|
||||
(sync-server-url-row)
|
||||
(when (util/electron?) (https-user-agent-row https-agent-opts))
|
||||
(when (util/electron?) (auto-chmod-row t))
|
||||
;; (clear-cache-row t)
|
||||
|
||||
@@ -50,19 +50,70 @@
|
||||
(goog-define ENABLE-DB-SYNC-LOCAL false)
|
||||
(defonce db-sync-local? ENABLE-DB-SYNC-LOCAL)
|
||||
|
||||
(defonce db-sync-ws-url
|
||||
(defonce default-db-sync-ws-url
|
||||
(if db-sync-local?
|
||||
"ws://127.0.0.1:8787/sync/%s"
|
||||
;; "wss://api-staging.logseq.io/sync/%s"
|
||||
"wss://api.logseq.io/sync/%s"))
|
||||
|
||||
(defonce db-sync-http-base
|
||||
(defonce default-db-sync-http-base
|
||||
(if db-sync-local?
|
||||
"http://127.0.0.1:8787"
|
||||
;; "https://api-staging.logseq.io"
|
||||
"https://api.logseq.io"
|
||||
))
|
||||
|
||||
(defn get-custom-sync-server-url
|
||||
"Read the user-configured custom sync server URL from localStorage.
|
||||
Returns nil when not set or empty."
|
||||
[]
|
||||
(when-not util/node-test?
|
||||
(let [v (.getItem js/localStorage "sync-server-url")]
|
||||
(when (and (string? v) (not (string/blank? v)))
|
||||
v))))
|
||||
|
||||
(defn set-custom-sync-server-url!
|
||||
"Persist the custom sync server 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 "sync-server-url")
|
||||
(.setItem js/localStorage "sync-server-url" (string/trim url)))))
|
||||
|
||||
(defn valid-sync-server-url?
|
||||
"Return true when `url` looks like a valid HTTP(S) base URL."
|
||||
[url]
|
||||
(and (string? url)
|
||||
(re-find #"^https?://" url)))
|
||||
|
||||
(defn custom-url->ws-url
|
||||
"Derive a WebSocket sync URL from a custom HTTP base URL. Pure function."
|
||||
[custom-url]
|
||||
(let [scheme (if (string/starts-with? custom-url "https") "wss" "ws")
|
||||
base (-> custom-url
|
||||
(string/replace #"^https?://" "")
|
||||
(string/replace #"/+$" ""))]
|
||||
(str scheme "://" base "/sync/%s")))
|
||||
|
||||
(defn custom-url->http-base
|
||||
"Normalize a custom HTTP base URL by stripping trailing slashes. Pure function."
|
||||
[custom-url]
|
||||
(string/replace custom-url #"/+$" ""))
|
||||
|
||||
(defn db-sync-ws-url
|
||||
"Return the WebSocket sync URL. Uses custom server when configured, otherwise the default."
|
||||
[]
|
||||
(if-let [custom (get-custom-sync-server-url)]
|
||||
(custom-url->ws-url custom)
|
||||
default-db-sync-ws-url))
|
||||
|
||||
(defn db-sync-http-base
|
||||
"Return the HTTP base URL for sync. Uses custom server when configured, otherwise the default."
|
||||
[]
|
||||
(if-let [custom (get-custom-sync-server-url)]
|
||||
(custom-url->http-base custom)
|
||||
default-db-sync-http-base))
|
||||
|
||||
;; Feature flags
|
||||
;; =============
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
base)))
|
||||
|
||||
(defn http-base []
|
||||
(or config/db-sync-http-base
|
||||
(ws->http-base config/db-sync-ws-url)))
|
||||
(or (config/db-sync-http-base)
|
||||
(ws->http-base (config/db-sync-ws-url))))
|
||||
|
||||
(defn- auth-headers []
|
||||
(when-let [token (state/get-auth-id-token)]
|
||||
|
||||
@@ -81,6 +81,11 @@
|
||||
(plugin/user-proxy-settings-container agent-opts)
|
||||
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
|
||||
|
||||
(defmethod events/handle :go/sync-server-settings [[_]]
|
||||
(shui/dialog-open!
|
||||
(settings/sync-server-url-settings-container)
|
||||
{:id :sync-server-panel :center? true :class "lg:max-w-2xl"}))
|
||||
|
||||
(defmethod events/handle :redirect-to-home [_]
|
||||
(page-handler/create-today-journal!)
|
||||
(when (util/capacitor?)
|
||||
@@ -330,8 +335,8 @@
|
||||
(state/pub-event! [:rtc/sync-app-state])
|
||||
(state/<invoke-db-worker :thread-api/set-db-sync-config
|
||||
{:enabled? true
|
||||
:ws-url config/db-sync-ws-url
|
||||
:http-base config/db-sync-http-base})
|
||||
:ws-url (config/db-sync-ws-url)
|
||||
:http-base (config/db-sync-http-base)})
|
||||
(state/<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys))
|
||||
(p/catch (fn [error]
|
||||
(log/error :db-sync/ensure-user-rsa-keys-failed error)
|
||||
|
||||
@@ -322,7 +322,8 @@
|
||||
|
||||
(defn rtc-group?
|
||||
[]
|
||||
(boolean (seq (set/intersection (state/user-groups) #{"team" "rtc_2025_07_10"}))))
|
||||
(boolean (or (some? (config/get-custom-sync-server-url))
|
||||
(seq (set/intersection (state/user-groups) #{"team" "rtc_2025_07_10"})))))
|
||||
|
||||
(defn alpha-user?
|
||||
[]
|
||||
|
||||
@@ -129,8 +129,8 @@
|
||||
(-> (p/let [_ (state/<invoke-db-worker :thread-api/init)
|
||||
_ (state/<invoke-db-worker :thread-api/set-db-sync-config
|
||||
{:enabled? true
|
||||
:ws-url config/db-sync-ws-url
|
||||
:http-base config/db-sync-http-base})
|
||||
:ws-url (config/db-sync-ws-url)
|
||||
:http-base (config/db-sync-http-base)})
|
||||
_ (state/pub-event! [:rtc/sync-app-state])
|
||||
_ (log/info "init worker spent" (str (- (util/time-ms) t1) "ms"))
|
||||
_ (sync-ui-state!)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
(s/def :copy/export-block-text-indent-style string?)
|
||||
(s/def :copy/export-block-text-remove-options set?)
|
||||
(s/def :copy/export-block-text-other-options map?)
|
||||
(s/def ::sync-server-url string?)
|
||||
;; Dynamic keys which aren't as easily validated:
|
||||
;; :ls-pdf-last-page-*
|
||||
;; :ls-js-allowed-*
|
||||
@@ -66,4 +67,5 @@
|
||||
:ui/shortcut-tooltip?
|
||||
:copy/export-block-text-indent-style
|
||||
:copy/export-block-text-remove-options
|
||||
:copy/export-block-text-other-options]))
|
||||
:copy/export-block-text-other-options
|
||||
::sync-server-url]))
|
||||
|
||||
@@ -142,6 +142,14 @@
|
||||
(shui/popup-show! nil (fn [] (log)) {}))}
|
||||
[:span.text-base "Check log"]]
|
||||
|
||||
[:div.mobile-setting-item
|
||||
{:on-click #(state/pub-event! [:go/sync-server-settings])}
|
||||
[:span.text-base "Sync server URL"]
|
||||
[:span.text-sm.opacity-70
|
||||
(if-let [custom (config/get-custom-sync-server-url)]
|
||||
custom
|
||||
"Logseq Cloud (default)")]]
|
||||
|
||||
(when login?
|
||||
[:div.mobile-setting-item
|
||||
{:on-click (fn []
|
||||
|
||||
@@ -222,6 +222,12 @@
|
||||
:settings-page/plugin-system "Plugins"
|
||||
:settings-page/enable-flashcards "Flashcards"
|
||||
:settings-page/network-proxy "Network proxy"
|
||||
:settings-page/sync-server-url "Sync server URL"
|
||||
:settings-page/sync-server-url-desc "Set a custom HTTPS sync server URL for self-hosted sync. Your Logseq authentication tokens will be sent to this server, so only use a trusted URL. Leave empty to use the official Logseq cloud."
|
||||
:settings-page/sync-server-url-saved "Sync server URL saved."
|
||||
:settings-page/sync-server-url-cleared "Sync server URL cleared. Using official Logseq cloud."
|
||||
:settings-page/sync-server-url-default "Logseq Cloud (default)"
|
||||
:settings-page/sync-server-url-reset "Reset to default"
|
||||
:settings-page/login-prompt "To access new features before anyone else you must be an Open Collective Sponsor or Backer of Logseq and therefore log in first."
|
||||
:settings-page/native-titlebar "Native title bar"
|
||||
:settings-page/native-titlebar-desc "Enables the native window title bar on Windows and Linux."
|
||||
|
||||
67
src/test/frontend/config_test.cljs
Normal file
67
src/test/frontend/config_test.cljs
Normal file
@@ -0,0 +1,67 @@
|
||||
(ns frontend.config-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[frontend.config :as config]))
|
||||
|
||||
(deftest custom-url->ws-url-test
|
||||
(testing "https URL becomes wss"
|
||||
(is (= "wss://my-server.example.com/sync/%s"
|
||||
(config/custom-url->ws-url "https://my-server.example.com"))))
|
||||
|
||||
(testing "http URL becomes ws"
|
||||
(is (= "ws://localhost:8787/sync/%s"
|
||||
(config/custom-url->ws-url "http://localhost:8787"))))
|
||||
|
||||
(testing "trailing slashes are stripped"
|
||||
(is (= "wss://my-server.example.com/sync/%s"
|
||||
(config/custom-url->ws-url "https://my-server.example.com/")))
|
||||
(is (= "wss://my-server.example.com/sync/%s"
|
||||
(config/custom-url->ws-url "https://my-server.example.com///"))))
|
||||
|
||||
(testing "preserves port in URL"
|
||||
(is (= "wss://example.com:3000/sync/%s"
|
||||
(config/custom-url->ws-url "https://example.com:3000"))))
|
||||
|
||||
(testing "preserves subpath in host"
|
||||
;; Users should only provide a base URL, but verify trailing path doesn't break things
|
||||
(is (= "wss://example.com/api/sync/%s"
|
||||
(config/custom-url->ws-url "https://example.com/api")))))
|
||||
|
||||
(deftest custom-url->http-base-test
|
||||
(testing "returns URL as-is when no trailing slash"
|
||||
(is (= "https://my-server.example.com"
|
||||
(config/custom-url->http-base "https://my-server.example.com"))))
|
||||
|
||||
(testing "strips trailing slashes"
|
||||
(is (= "https://my-server.example.com"
|
||||
(config/custom-url->http-base "https://my-server.example.com/")))
|
||||
(is (= "https://my-server.example.com"
|
||||
(config/custom-url->http-base "https://my-server.example.com///"))))
|
||||
|
||||
(testing "preserves http scheme"
|
||||
(is (= "http://localhost:8787"
|
||||
(config/custom-url->http-base "http://localhost:8787"))))
|
||||
|
||||
(testing "preserves port"
|
||||
(is (= "https://example.com:3000"
|
||||
(config/custom-url->http-base "https://example.com:3000/")))))
|
||||
|
||||
(deftest default-urls-are-returned-when-no-custom-url
|
||||
(testing "db-sync-ws-url returns default when no custom URL is set"
|
||||
;; In test environment, node-test? is true so get-custom-sync-server-url
|
||||
;; always returns nil, meaning we always get the default
|
||||
(is (string? (config/db-sync-ws-url)))
|
||||
(is (= config/default-db-sync-ws-url (config/db-sync-ws-url))))
|
||||
|
||||
(testing "db-sync-http-base returns default when no custom URL is set"
|
||||
(is (string? (config/db-sync-http-base)))
|
||||
(is (= config/default-db-sync-http-base (config/db-sync-http-base)))))
|
||||
|
||||
(deftest valid-sync-server-url?-test
|
||||
(testing "accepts http and https URLs"
|
||||
(is (config/valid-sync-server-url? "https://my-server.example.com"))
|
||||
(is (config/valid-sync-server-url? "http://localhost:8787")))
|
||||
|
||||
(testing "rejects non-URL strings"
|
||||
(is (not (config/valid-sync-server-url? "not a url")))
|
||||
(is (not (config/valid-sync-server-url? "")))
|
||||
(is (not (config/valid-sync-server-url? nil)))))
|
||||
Reference in New Issue
Block a user