Fase 8 (2/N): delete the client-side RTC/E2EE/sync UI and finish the teardown

Removes the client-side half of the remote CRDT sync protocol left after
the worker-side deletion in 565c211: RTC/remote-graph UI (components/rtc/*,
db/rtc/debug_ui.cljs, cloud-graph upload/download/leave/delete flows in
components/repo.cljs and mobile/components/app.cljs), E2EE UI and handlers
(components/e2ee.cljs, handler/e2ee.cljs, handler/events/rtc*.cljs), the
db-worker-node ui-request RPC (handler/worker.cljs's :db-worker/ui-request,
which existed only to serve E2EE password prompts), settings panels for the
legacy custom sync-server URL, and the now-orphaned :rtc/* client state
(presence UI, upload/download progress, remote-graph lists) — presence.cljs
was already deleted in the worker-side pass, contrary to the plan's "keep"
list, so all of this had zero producers left and was fully inert.

Also fixes several dead thread-api invokes discovered on LIVE paths that
would throw at runtime rather than being merely dead UI:
- db/restore.cljs's restore-graph! (called on every graph load)
- persist_db.cljs's <ensure-remote! (the Fase 3/4 daemon connect path)
- persist_db/browser.cljs's worker init and shutdown
- handler/user.cljs's logout

Verified via full compile of all 5 shadow-cljs targets (app, electron,
mobile, db-worker-node, test — 0 warnings) and a full bb dev:test run:
every namespace passes except the same 7 frontend.persist-db-test failures
that reproduce identically on unmodified HEAD (confirmed via git stash),
pre-existing network-sandbox flakiness unrelated to this change.

Known follow-up, not yet done: a handful of harmless-but-vestigial :rtc/*
references remain (handler/user.cljs's storage/graph-count exceed-limit
events, handler/events.cljs's :rtc-download? opt, worker/state.cljs's
*db-sync-config atom, worker/handler/graph.cljs's get-rtc-graph-uuid reused
by handler/editor.cljs's quick-add filter) — none throw or affect behavior,
left for a future pass rather than risk removing something still load-bearing
without deeper tracing. deps/db-sync/ (the Cloudflare Worker) is also still
untouched, per the plan's explicit note that it needs separate, larger surgery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 15:59:39 +02:00
parent 565c2114be
commit 1e7e9a3c13
52 changed files with 162 additions and 4904 deletions

View File

@@ -38,8 +38,7 @@
goog.debug.LOGGING_ENABLED true
frontend.modules.instrumentation.sentry/SENTRY-DSN #shadow/env "LOGSEQ_SENTRY_DSN"
frontend.modules.instrumentation.posthog/POSTHOG-TOKEN #shadow/env "LOGSEQ_POSTHOG_TOKEN"
frontend.config/ENABLE-PLUGINS #shadow/env ["ENABLE_PLUGINS" :as :bool :default true]
frontend.config/ENABLE-DB-SYNC-LOCAL #shadow/env ["ENABLE_DB_SYNC_LOCAL" :as :bool :default false]}
frontend.config/ENABLE-PLUGINS #shadow/env ["ENABLE_PLUGINS" :as :bool :default true]}
:devtools {:before-load frontend.core/stop ;; before live-reloading any code call this function
:after-load frontend.core/start ;; after live-reloading finishes call this function

View File

@@ -9,7 +9,6 @@
[clojure.string :as string]
[dommy.core :as dom]
[electron.ipc :as ipc]
[frontend.components.avatar :as avatar]
[frontend.components.block.breadcrumb-model :as breadcrumb-model]
[frontend.components.block.asset :as block-asset]
[frontend.components.block.comments :as block-comments]
@@ -1094,25 +1093,11 @@
[:a.asset-ref {:target "_blank" :href real-path-url}
title-or-path])]))
(defn- maybe-request-asset-download!
[file-exists? requested? block]
(let [repo (state/get-current-repo)
asset-file-write-finish (state/get-state :assets/asset-file-write-finish)
asset-file-write-finished? (get-in asset-file-write-finish [repo (str (:block/uuid block))])
file-ready? (or file-exists? asset-file-write-finished?)]
(when (and (true? @requested?) file-ready?)
(reset! requested? false))
(when (and (not @requested?)
(assets-handler/maybe-request-remote-asset-download! repo block file-ready?))
(reset! requested? true))))
(defn- retry-missing-asset-upload!
[repo block file-exists?*]
(let [asset-rpath (block-asset/asset-relative-path block)]
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo) asset-rpath)]
(reset! file-exists?* exists?)
(when exists?
(state/<invoke-db-worker :thread-api/db-sync-retry-asset-upload repo)))))
(reset! file-exists?* exists?))))
(hsx/defc missing-asset-file
[block on-reload]
@@ -1132,25 +1117,10 @@
(let [asset-type (:logseq.property.asset/type block)
file (block-asset/asset-file-name block)
file-exists?* (hooks/use-memo #(atom nil) [(:block/uuid block) asset-type])
requested?* (hooks/use-memo #(atom false) [(:block/uuid block)])
[file-exists?] (hooks/use-atom file-exists?*)
repo (state/get-current-repo)
asset-file-write-finished? (rfx/use-sub [:assets/asset-file-write-finish repo (str (:block/uuid block))])
file-ready? (or file-exists? asset-file-write-finished?)
progress-entry (rfx/use-sub [:rtc/asset-upload-download-progress repo (str (:block/uuid block))])
{:keys [direction loaded total]} progress-entry
in-progress? (and (number? loaded) (number? total) (pos? total) (not= loaded total))
percent (when in-progress?
(int (* 100 (/ loaded total))))
label (case direction
:upload (t :asset/uploading)
:download (t :asset/downloading)
(t :asset/syncing))
progress-view (when in-progress?
[:div.asset-transfer-progress
[:div.asset-transfer-progress-label (str label " " percent "%")]
[:div.asset-transfer-progress-bar
[:span {:style {:width (str percent "%")}}]]])
image? (contains? (common-config/img-formats) (keyword asset-type))
gallery-image? (and (:gallery-view? config) image?)
width (get-in block [:logseq.property.asset/resize-metadata :width])
@@ -1184,8 +1154,6 @@
(fs/file-exists? (config/get-repo-dir (state/get-current-repo)) path))]
(reset! file-exists?* result))))
[file])
_ (hooks/use-effect!
#(maybe-request-asset-download! file-exists? requested?* block))
content (cond
(or file-ready? gallery-image?)
(asset-link (cond-> (assoc config :asset-block block)
@@ -1202,12 +1170,7 @@
#(retry-missing-asset-upload! repo block file-exists?*))
(block-asset/show-image-placeholder? block file-ready? gallery-image?)
img-placeholder)]
(if progress-view
[:div.asset-transfer-shell
(or content
[:div.asset-transfer-placeholder (t :asset/transfer-placeholder label)])
progress-view]
content)))
content))
(defn- img-audio-video?
[block]
@@ -2227,24 +2190,11 @@
(open-comment-thread! block-uuid comment-thread))}
(shui/tabler-icon "message-circle" {:size 15})))))))
(defn- editing-user-avatar
[{:user/keys [name uuid]}]
(let [user-name (or name uuid)]
(when (avatar/initials user-name)
[:span.block-editing-avatar-wrap
(avatar/user-avatar
{:class "block-editing-avatar w-4 h-4 flex-none"
:title user-name
:name user-name
:uuid uuid
:fallback-props {:style {:font-size 9}}})])))
(hsx/defc ^:large-vars/cleanup-todo block-control
[config block {:keys [uuid block-id collapsed? has-children? *control-show? edit? selected? top? bottom?]}]
(let [*bullet-dragging? (hooks/use-memo #(atom false) [])
doc-mode? (rfx/use-sub [:document/mode?])
[control-show?] (hooks/use-atom *control-show?)
editing-user (rfx/use-sub [:rtc/editing-users-by-block (str uuid)])
ref? (:ref? config)
container-id (:container-id config)
empty-content? (block-content-empty? block)
@@ -2276,8 +2226,6 @@
:is-with-icon with-icon?
:bullet-closed collapsed?
:bullet-hidden (:hide-bullet? config)}])}
(when (and (not page-title?) editing-user)
(editing-user-avatar editing-user))
(when (and (or (not fold-button-right?) collapsable? collapsed?)
(not (:table? config)))
[:a.block-control
@@ -3296,78 +3244,6 @@
{:align :end}))}
(clock/seconds->days:hours:minutes:seconds seconds))])))
(defn- sync-conflict-attr-label
[attr]
(case attr
:block/title (t :property.built-in/title)
(name attr)))
(defn- visible-sync-conflicts
[block conflicts]
(->> conflicts
(remove (fn [{:keys [attr value]}]
(= value (get block attr))))
vec))
(hsx/defc sync-conflict-item
[{:keys [id attr value created-at]}]
[:div.border.rounded.p-3 {:key id}
[:div.flex.flex-row.items-center.justify-between.gap-3.mb-2.text-xs.text-muted-foreground
[:span (sync-conflict-attr-label attr)]
[:span (date/int->local-time-2 created-at)]]
[:pre.whitespace-pre-wrap.text-sm.bg-muted.p-2.rounded.max-h-64.overflow-auto value]
[:div.flex.justify-end.mt-2
(shui/button
{:variant :secondary
:size :sm
:on-click (fn []
(util/copy-to-clipboard! value)
(notification/show! (t :notification/copied) :success))}
(t :ui/copy))]])
(hsx/defc sync-conflicts-popup
[conflicts on-mark-resolved]
[:div.p-3.w-96
{:style {:max-width "90vw"}}
[:h2.text-lg.font-medium.mb-2 (t :sync/conflicts-title)]
[:p.text-sm.text-muted-foreground.mb-3
(t :sync/conflicts-description)]
[:div.flex.flex-col.gap-3
(for [conflict conflicts]
(sync-conflict-item conflict))]
[:div.flex.justify-end.mt-3
(ui/button (t :sync/mark-conflicts-resolved)
:on-click on-mark-resolved)]])
(hsx/defc sync-conflicts-warning-button
[block]
(let [repo (state/get-current-repo)
block-id (:block/uuid block)
conflicts (rfx/use-sub [:sync/block-conflicts repo (str block-id)])
visible-conflicts (visible-sync-conflicts block conflicts)]
(when (seq visible-conflicts)
(ui/tooltip
(shui/button
{:variant :secondary
:size :sm
:title (t :sync/show-conflicts)
:class "ls-sync-conflict-warning ls-small-icon px-1 !py-0 h-5"
:on-click (fn [e]
(util/stop e)
(shui/popup-show! (.-target e)
(fn []
(sync-conflicts-popup
visible-conflicts
(fn []
(p/let [_ (state/<invoke-db-worker
:thread-api/db-sync-clear-block-conflicts
repo
block-id)]
(shui/popup-hide!)))))
{:align :end}))}
(ui/icon "alert-triangle" {:size 14}))
[:div (t :sync/show-conflicts)]))))
(hsx/defc ^:large-vars/cleanup-todo block-content
[config {:block/keys [uuid] :as block} edit-input-id block-id *show-query?]
(let [repo (state/get-current-repo)
@@ -3619,10 +3495,6 @@
(when-not (:table-block-title? config)
[:div.ls-block-right.flex.flex-row.items-center.self-start.gap-1
(when-not (or (:block-ref? config) (:table? config) (:gallery-view? config)
(:property? config))
(sync-conflicts-warning-button block))
(when-not table?
[:div.opacity-70.hover:opacity-100
(block-positioned-properties config block :block-right)])

View File

@@ -467,8 +467,7 @@
logged? (user-handler/logged-in?)
fold-button-on-right? (state/use-enable-fold-button-right?)
show-action-bar? (rfx/use-sub [:mobile/show-action-bar?])
preferred-language (rfx/use-sub [:preferred-language])
uploading? (rfx/use-sub [:rtc/uploading?])]
preferred-language (rfx/use-sub [:preferred-language])]
(hooks/use-effect!
(fn []
(let [keydown-handler (fn [e]
@@ -554,18 +553,15 @@
(when (util/electron?)
(find-in-page/search))
(if uploading?
[:div.flex.items-center.justify-center.full-height-without-header
(ui/loading (t :sync/creating-remote-graph))]
(main {:route-match route-match
:margin-less-pages? margin-less-pages?
:logged? logged?
:home? home?
:route-name route-name
:light? light?
:db-restoring? db-restoring?
:main-content main-content'
:show-action-bar? show-action-bar?}))]
(main {:route-match route-match
:margin-less-pages? margin-less-pages?
:logged? logged?
:home? home?
:route-name route-name
:light? light?
:db-restoring? db-restoring?
:main-content main-content'
:show-action-bar? show-action-bar?})]
(when window-controls?
(window-controls/container))

View File

@@ -1,126 +0,0 @@
(ns frontend.components.e2ee
(:require [clojure.string :as string]
[frontend.common.crypt :as crypt]
[frontend.context.i18n :refer [t]]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[io.factorhouse.hsx.core :as hsx]))
(hsx/defc e2ee-request-new-password
[password-promise]
(let [[password set-password!] (hooks/use-state "")
[password-confirm set-password-confirm!] (hooks/use-state "")
[matched? set-matched!] (hooks/use-state nil)
on-submit (fn []
(p/resolve! password-promise password)
(shui/dialog-close!))]
[:div.e2ee-password-modal-overlay
[:div.encryption-password.max-w-2xl.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
[:div.text-2xl.font-medium (t :encryption/set-password-title)]
[:div.init-remote-pw-tips.space-x-4.hidden.sm:flex
[:div.flex-1.flex.items-center
[:span.px-3.flex (ui/icon "key")]
[:p (t :encryption/remember-password-rich)]]
[:div.flex-1.flex.items-center
[:span.px-3.flex (ui/icon "lock")]
[:p (t :encryption/cloud-password-rich)]]]
[:div.flex.flex-col.gap-4
(shui/toggle-password
{:placeholder (t :encryption/enter-password)
:value password
:on-change (fn [e] (set-password! (-> e .-target .-value)))
:on-blur (fn []
(when-not (string/blank? password-confirm)
(set-matched! (= password-confirm password))))})
[:div.flex.flex-col.gap-2
(shui/toggle-password
{:placeholder (t :encryption/enter-password-again)
:value password-confirm
:on-change (fn [e] (set-password-confirm! (-> e .-target .-value)))
:on-blur (fn [] (set-matched! (= password-confirm password)))})
(when (false? matched?)
[:div.text-warning.text-sm
(t :encryption/password-not-matched)])]
(shui/button
{:on-click on-submit
:disabled (or (string/blank? password)
(false? matched?))}
(t :ui/submit))]]]))
(hsx/defc e2ee-request-password
[password-promise]
(let [*password-input (hooks/use-ref nil)
[password set-password!] (hooks/use-state "")
on-submit (fn []
(p/resolve! password-promise
(-> (hooks/deref *password-input)
.-value))
(shui/dialog-close!))]
[:div.e2ee-password-modal-overlay
[:div.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
[:div.text-2xl.font-medium (t :encryption/enter-password-title)]
[:div.flex.flex-col.gap-4
(shui/toggle-password
{:ref *password-input
:value password
:on-key-press (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))
:on-change (fn [e]
(set-password! (-> e .-target .-value)))})
(shui/button
{:on-click on-submit
:disabled (string/blank? password)
:on-key-press (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))}
(t :ui/submit))]]]))
(hsx/defc e2ee-password-to-decrypt-private-key
[encrypted-private-key private-key-promise]
(let [*password-input (hooks/use-ref nil)
[password set-password!] (hooks/use-state "")
[decrypt-fail? set-decrypt-fail!] (hooks/use-state false)
on-submit (fn []
(->
(p/let [entered-password (-> (hooks/deref *password-input)
.-value)
private-key (crypt/<decrypt-private-key entered-password encrypted-private-key)]
(state/<invoke-db-worker :thread-api/save-e2ee-password entered-password)
(p/resolve! private-key-promise private-key)
(shui/dialog-close!))
(p/catch (fn [e]
(when (= "decrypt-private-key" (ex-message e))
(set-decrypt-fail! true))))))]
[:div.e2ee-password-modal-overlay
[:div.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
[:div.text-2xl.font-medium (t :encryption/enter-password-title)]
[:div.flex.flex-col.gap-4
[:div.flex.flex-col.gap-1
(shui/toggle-password
{:ref *password-input
:value password
:on-key-press (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))
:on-change (fn [e]
(set-decrypt-fail! false)
(set-password! (-> e .-target .-value)))})
(when decrypt-fail? [:p.text-warning.text-sm (t :encryption/wrong-password)])]
(shui/button
{:on-click on-submit
:disabled (string/blank? password)
:on-key-press (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))}
(t :ui/submit))]]]))

View File

@@ -6,17 +6,13 @@
[clojure.string :as string]
[dommy.core :as d]
[electron.ipc :as ipc]
[frontend.components.avatar :as avatar]
[frontend.components.block :as component-block]
[frontend.components.email :as email-component]
[frontend.components.export :as export]
[frontend.components.page-menu :as page-menu]
[frontend.components.plugins :as plugins]
[frontend.components.repo :as repo]
[frontend.components.right-sidebar :as sidebar]
[frontend.components.rtc.indicator :as rtc-indicator]
[frontend.components.server :as server]
[frontend.components.settings :as settings]
[frontend.components.svg :as svg]
[frontend.config :as config]
[frontend.context.i18n :as i18n :refer [t]]
@@ -32,7 +28,6 @@
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.util.email :as email-util]
[frontend.util.entity :as entity]
[frontend.version :refer [version]]
[logseq.common.config :as common-config]
@@ -55,86 +50,6 @@
(t :nav/home)
{:trigger-props {:as-child true}}))
(defn current-local-uploadable-graph
[db-rtc-uuid]
(let [current-repo (state/get-current-repo)]
(some (fn [{:keys [url] :as graph}]
(when (and (= current-repo url)
(repo/local-uploadable-graph?
(assoc graph :rtc-graph?
(boolean db-rtc-uuid))))
graph))
(state/get-repos))))
(defn- use-db-rtc-uuid
[repo]
(let [[db-rtc-uuid set-db-rtc-uuid!] (hooks/use-state nil)]
(hooks/use-effect!
(fn []
(when repo
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
(set-db-rtc-uuid! graph-uuid)))
nil)
[repo])
db-rtc-uuid))
(defn- current-remote-rtc-graph
[current-repo rtc-graphs]
(some #(when (= current-repo (:url %)) %) rtc-graphs))
(defn- rtc-indicator-visible?
[{:keys [current-repo rtc-graphs db-rtc-uuid rtc-state logged-in? rtc-group?]}]
(let [remote-graph (current-remote-rtc-graph current-repo rtc-graphs)
remote-graph-uuid (some-> (:GraphUUID remote-graph) str)
state-graph-uuid (some-> (:graph-uuid rtc-state) str)]
(and current-repo
logged-in?
rtc-group?
remote-graph
(or db-rtc-uuid
(and (seq state-graph-uuid)
(= state-graph-uuid remote-graph-uuid))))))
(defn local-graph-sync-button
[graph]
(ui/tooltip
(shui/button-ghost-icon :cloud
{:class "local-graph-sync-btn"
:on-click #(repo/upload-local-graph-with-confirm! graph)})
(t :graph/use-sync-beta)
{:trigger-props {:as-child true}}))
(hsx/defc rtc-collaborators
[]
(let [rtc-graph-id (use-db-rtc-uuid (state/get-current-repo))
config (rfx/use-sub [:config])
online-users (rfx/use-sub [:rtc/state :online-users])]
(when rtc-graph-id
[:div.rtc-collaborators.flex.gap-1.text-sm.bg-gray-01.items-center
(shui/button-ghost-icon :user-plus
{:on-click #(shui/dialog-open!
(fn []
[:div.p-2.-mb-8
[:h1.text-3xl.-mt-2.-ml-2 (t :collaboration/members)]
(settings/settings-collaboration)])
{:id :rtc-collaborators})})
(when (seq online-users)
(for [{user-email :user/email
user-name :user/name
user-uuid :user/uuid} online-users
:when user-name
:let [key (str "rtc-user-" (or user-uuid user-email user-name))]]
^{:key key}
[:<>
(avatar/user-avatar
{:class "w-5 h-5"
:style {:app-region "no-drag"}
:title (email-util/display-email user-email config)
:name user-name
:uuid user-uuid
:fallback-props {:style {:font-size 11}}})]))])))
(hsx/defc left-menu-button
[{:keys [on-click]}]
(ui/with-shortcut :ui/toggle-left-sidebar "bottom"
@@ -502,9 +417,6 @@
(hsx/defc ^:large-vars/cleanup-todo header-aux
[{:keys [current-repo default-home new-block-mode]}]
(let [electron-mac? (and util/mac? (util/electron?))
rtc-graphs (rfx/use-sub [:rtc/graphs])
rtc-state (rfx/use-sub [:rtc/state])
db-rtc-uuid (use-db-rtc-uuid current-repo)
default-home-page (get-in (state/config-for-repo (rfx/use-sub [:config])
(state/get-current-repo))
[:default-home :page] "")
@@ -554,28 +466,9 @@
[:div.flex.flex-1
(block-breadcrumb (state/get-current-page))]
[:div.flex.items-center
(when (rtc-indicator-visible?
{:current-repo current-repo
:rtc-graphs rtc-graphs
:db-rtc-uuid db-rtc-uuid
:rtc-state rtc-state
:logged-in? (user-handler/logged-in?)
:rtc-group? (user-handler/rtc-group?)})
[:<>
(recent-slider)
^{:key (str "collab-" current-repo)}
[rtc-collaborators]
(rtc-indicator/indicator)])
(when (user-handler/logged-in?)
(rtc-indicator/downloading-detail))
(when (user-handler/logged-in?)
(rtc-indicator/uploading-detail))
(recent-slider)
(search-index-progress)
(when-let [graph (current-local-uploadable-graph db-rtc-uuid)]
(local-graph-sync-button graph))
(when (and (not= (state/get-current-route) :home)
(not custom-home-page?))
(home-button))
@@ -608,6 +501,5 @@
(hsx/defc header
[opts]
(let [_user-groups (rfx/use-sub [:user/info :UserGroups])
_rtc-running? (rfx/use-sub [:rtc/state :rtc-lock])]
(let [_user-groups (rfx/use-sub [:user/info :UserGroups])]
(header-aux opts)))

View File

@@ -1,23 +1,17 @@
(ns frontend.components.repo
(:require [clojure.string :as string]
[frontend.components.rtc.indicator :as rtc-indicator]
[frontend.config :as config]
[frontend.context.i18n :as i18n :refer [t]]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.events.rtc-error :as rtc-error]
[frontend.handler.graph :as graph]
[frontend.handler.notification :as notification]
[frontend.handler.repo :as repo-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.util :as mobile-util]
[frontend.rfx :as rfx]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.util.text :as text-util]
[goog.object :as gobj]
[lambdaisland.glogi :as log]
[logseq.common.util :as common-util]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
@@ -25,11 +19,6 @@
[promesa.core :as p]
[io.factorhouse.hsx.core :as hsx]))
(defn graph-sync-icon-name
[{:keys [remote? graph-e2ee?]}]
(when remote?
(if graph-e2ee? "lock" "cloud")))
(defn- repo-display-name
[repo-url]
(let [repo-name (text-util/get-graph-name-from-path repo-url)]
@@ -37,85 +26,15 @@
(config/db-graph-name repo-name)
repo-name)))
(defn local-uploadable-graph?
[{:keys [root remote? rtc-graph?]}]
(and (or root
(mobile-util/native-platform?))
(not remote?)
(not rtc-graph?)
(user-handler/logged-in?)
(user-handler/rtc-group?)))
(defn <invoke-db-worker
[op & args]
(apply state/<invoke-db-worker op args))
(defn- handle-cloud-graph-error!
[error]
(log/error :db-sync/cloud-graph-failed error)
(when (rtc-error/e2ee-decrypt-failed? error)
(notification/show! (t :encryption/wrong-password) :error false)))
(defn- graph-e2ee-enabled?
[{:keys [url graph-e2ee?] :as graph}]
(cond
(contains? graph :graph-e2ee?)
(p/resolved (true? graph-e2ee?))
(= url (state/get-current-repo))
(p/let [e2ee? (<invoke-db-worker :thread-api/get-key-value url :logseq.kv/graph-rtc-e2ee?)]
(if (nil? e2ee?) true (true? e2ee?)))
:else
(p/resolved true)))
(defn- <ensure-current-graph-for-upload!
[repo]
(if (= repo (state/get-current-repo))
(p/resolved nil)
(state/pub-event! [:graph/switch repo])))
(defn upload-local-graph-with-confirm!
[{:keys [url] :as graph}]
(let [graph-name (config/db-graph-name url)
dialog-config {:cancel-label (t :ui/cancel)
:ok-label (t :ui/confirm)}]
(-> (shui/dialog-confirm!
[:p.font-medium.mb-6 (t :graph/upload-local-confirm-desc graph-name)]
dialog-config)
(p/then
(fn []
(p/let [_ (<ensure-current-graph-for-upload! url)
graph-e2ee? (graph-e2ee-enabled? graph)]
(let [mobile? (util/mobile?)
hide-upload-log! (fn []
(when mobile?
(shui/popup-hide! :rtc-graph-upload-log)))]
(when mobile?
(shui/popup-show! nil
(fn []
(rtc-indicator/uploading-logs))
{:id :rtc-graph-upload-log}))
(rtc-indicator/on-upload-finished-task
hide-upload-log!)
(-> (rtc-handler/<rtc-upload-graph! url graph-e2ee?)
(p/catch (fn [error]
(handle-cloud-graph-error! error)
(p/rejected error)))
(p/finally hide-upload-log!)))))))))
(hsx/defc normalized-graph-label
[{:keys [url remote?] :as graph} on-click]
[{:keys [url] :as graph} on-click]
(when graph
[:span.flex.items-center
(let [local-dir (config/get-local-dir url)
graph-name (text-util/get-graph-name-from-path url)]
[:<>
[:a.flex.items-center {:title local-dir
:on-click #(on-click graph)}
[:span graph-name]
(when remote?
[:strong.px-1.flex.items-center (ui/icon (graph-sync-icon-name graph))])]])]))
[:a.flex.items-center {:title local-dir
:on-click #(on-click graph)}
[:span graph-name]])]))
(defn sort-repos-with-metadata-local
[repos]
@@ -183,28 +102,19 @@
(when target
(state/pub-event! [:graph/open-new-window target])))))
(hsx/defc ^:large-vars/cleanup-todo repos-inner
(hsx/defc repos-inner
"Graph list in `All graphs` page"
[repos]
(for [{:keys [root url remote? graph-e2ee? GraphUUID GraphSchemaVersion GraphName created-at last-seen-at] :as repo}
(sort-repos-with-metadata-local repos)
:let [graph-name (config/db-graph-name url)]]
[:div.flex.justify-between.mb-2.items-center.group {:key (or url GraphUUID)
(for [{:keys [root url created-at last-seen-at] :as repo}
(sort-repos-with-metadata-local repos)]
[:div.flex.justify-between.mb-2.items-center.group {:key url
"data-testid" url}
[:div
[:span.flex.items-center.gap-1
(normalized-graph-label repo
(fn []
(when-not (state/get-state :rtc/downloading-graph-uuid)
(cond
root ; exists locally
(state/pub-event! [:graph/switch url])
remote?
(state/pub-event! [:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion graph-e2ee?])
:else
nil))))]
(when root
(state/pub-event! [:graph/switch url]))))]
(when-let [time (some-> (or last-seen-at created-at) (safe-locale-date))]
[:small.text-muted-foreground (t :graph/last-opened-at-label time)])]
@@ -215,120 +125,42 @@
{:on-click #(open-repo-folder! repo)}
(shui/tabler-icon "folder-pin") [:span.pl-1 root]])
(let [manager? (user-handler/manager? url)
dialog-config {:cancel-label (t :ui/cancel)
:ok-label (t :ui/confirm)}]
(shui/dropdown-menu
(shui/dropdown-menu-trigger
{:asChild true}
(shui/button
{:variant "ghost"
:class "graph-action-btn !px-1"
:size :sm}
(ui/icon "dots" {:size 15})))
(shui/dropdown-menu-content
{:align "end"}
(when (open-in-another-tab-action? repo)
(shui/dropdown-menu-item
{:key "open-in-another-tab"
:class "open-in-another-tab-menu-item"
:on-click #(open-graph-in-another-tab! repo)}
(t :graph/open-in-another-tab-action)))
(shui/dropdown-menu
(shui/dropdown-menu-trigger
{:asChild true}
(shui/button
{:variant "ghost"
:class "graph-action-btn !px-1"
:size :sm}
(ui/icon "dots" {:size 15})))
(shui/dropdown-menu-content
{:align "end"}
(when (open-in-another-tab-action? repo)
(shui/dropdown-menu-item
{:key "open-in-another-tab"
:class "open-in-another-tab-menu-item"
:on-click #(open-graph-in-another-tab! repo)}
(t :graph/open-in-another-tab-action)))
(when root
(let [disabled? (not (can-delete-local-graph? repo))]
(shui/dropdown-menu-item
{:key "delete-locally"
:class "delete-local-graph-menu-item"
:disabled disabled?
:on-click #(when-not disabled?
(delete-local-graph! repo))}
(t :graph/delete-local-action))))
(when (and root
(user-handler/logged-in?)
(user-handler/rtc-group?)
(not remote?)
(= url (state/get-current-repo)))
(when root
(let [disabled? (not (can-delete-local-graph? repo))]
(shui/dropdown-menu-item
{:key "logseq-sync"
:class "use-logseq-sync-menu-item"
:on-click #(upload-local-graph-with-confirm! repo)}
(t :graph/use-sync-beta)))
(when (and remote?
manager?)
(shui/dropdown-menu-item
{:key "delete-remotely"
:class "delete-remote-graph-menu-item"
:on-click (fn []
(let [prompt-str (t :graph/delete-server-confirm-desc graph-name)]
(-> (shui/dialog-confirm!
(assoc dialog-config
:title prompt-str
:description (t :graph/delete-warning)))
(p/then
(fn []
(state/set-state! :rtc/loading-graphs? true)
(when (= (state/get-current-repo) repo)
(rtc-handler/<rtc-stop!))
(p/do! (rtc-handler/<rtc-delete-graph! GraphUUID GraphSchemaVersion)
(state/set-state! :rtc/loading-graphs? false)
(rtc-handler/<get-remote-graphs)))))))}
(t :graph/delete-server-action)))
(when (and remote? (not manager?))
(shui/dropdown-menu-item
{:key "leave-shared-graph"
:class "leave-shared-graph-menu-item"
:on-click (fn []
(let [prompt-str (t :graph/leave-confirm-desc)]
(-> (shui/dialog-confirm!
[:p.font-medium.-my-4 prompt-str]
dialog-config)
(p/then
(fn []
(state/set-state! :rtc/loading-graphs? true)
(when (= (state/get-current-repo) repo)
(rtc-handler/<rtc-stop!))
(-> (rtc-handler/<rtc-leave-graph! GraphUUID)
(p/then (fn []
(notification/show! (t :graph/left) :success)
(rtc-handler/<get-remote-graphs)))
(p/catch (fn [e]
(notification/show! (t :graph/leave-error) :error)
(log/error :db-sync/leave-graph-failed
{:error e
:graph-uuid GraphUUID})))
(p/finally (fn []
(state/set-state! :rtc/loading-graphs? false)))))))))}
(t :graph/leave-action))))))]]]))
{:key "delete-locally"
:class "delete-local-graph-menu-item"
:disabled disabled?
:on-click #(when-not disabled?
(delete-local-graph! repo))}
(t :graph/delete-local-action))))))]]]))
(hsx/defc repos-cp
[]
(let [login? (boolean (rfx/use-sub [:auth/id-token]))
repos (rfx/use-sub [:me :repos])
(let [repos (rfx/use-sub [:me :repos])
repos (util/distinct-by :url repos)
remotes (rfx/use-sub [:rtc/graphs])
remotes-loading? (rfx/use-sub [:rtc/loading-graphs?])
repos (->> (if (and login? (seq remotes))
(repo-handler/combine-local-&-remote-graphs repos remotes)
repos)
(util/distinct-by :url))
repos (cond->>
(remove #(= (:url %) config/demo-repo) repos)
true
(filter (fn [item]
;; use `config/db-based-graph?` to avoid loading old file graphs
(config/db-based-graph? (:url item)))))
{remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)
{own-graphs true shared-graphs false}
(group-by (fn [graph] (= "manager" (:graph<->user-user-type graph))) remote-graphs)]
(hooks/use-effect!
(fn []
(when (and login? (user-handler/rtc-group?))
(rtc-handler/<get-remote-graphs)))
[login?])
repos (remove #(= (:url %) config/demo-repo) repos)
repos (filter (fn [item]
;; use `config/db-based-graph?` to avoid loading old file graphs
(config/db-based-graph? (:url item)))
repos)]
[:div#graphs
(when-not (util/capacitor?)
[:h1.title (t :graph/all-graphs)])
@@ -344,78 +176,32 @@
[:div
[:h2.text-lg.font-medium.mb-4 (t :graph/local-graphs)]
(when (seq local-graphs)
(repos-inner local-graphs))]
(when (seq repos)
(repos-inner repos))]]]))
(when (and (user-handler/rtc-group?)
(seq remote-graphs)
login?)
[:<>
(when (seq own-graphs)
[:div
[:hr.mt-8]
[:div.flex.align-items.justify-between
[:h2.text-lg.font-medium.mb-4 (t :graph/remote-graphs)]
[:div
(ui/button
[:span.flex.items-center (t :ui/refresh)
(when remotes-loading? [:small.pl-2 (ui/loading nil)])]
:background "gray"
:disabled remotes-loading?
:on-click (fn [] (rtc-handler/<get-remote-graphs)))]]
(repos-inner own-graphs)])
(when (seq shared-graphs)
[:div
[:hr.mt-8]
[:div.flex.align-items.justify-between
[:h2.text-lg.font-medium.mb-4 (t :graph/shared-graphs)]]
(repos-inner shared-graphs)])])]]))
(defn- repos-dropdown-links [repos current-repo downloading-graph-id & {:as opts}]
(defn- repos-dropdown-links [repos current-repo & {:as opts}]
(let [switch-repos (if-not (nil? current-repo)
(remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
repo-links (mapv
(fn [{:keys [url remote? graph-e2ee? rtc-graph? GraphName GraphSchemaVersion GraphUUID graph-ready-for-use?] :as graph}]
(fn [{:keys [url] :as graph}]
(let [repo-url url
short-repo-name (text-util/get-graph-name-from-path repo-url)
downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))
ready-for-use? (not= false graph-ready-for-use?)
title (str "<" GraphName "> #" GraphUUID)]
short-repo-name (text-util/get-graph-name-from-path repo-url)]
(when short-repo-name
{:title [:span.flex.items-center.title-wrap short-repo-name
(when remote? [:span.pl-1.flex.items-center
{:title title}
(ui/icon (if graph-e2ee? "lock" "cloud") {:size 18})
(when-not ready-for-use?
[:span.opacity.text-sm.pl-1 (t :graph/preparing)])
(when downloading?
[:span.opacity.text-sm.pl-1 (t :graph/downloading)])])]
{:title [:span.flex.items-center.title-wrap short-repo-name]
:hover-detail repo-url ;; show full path on hover
:options {:on-click
(fn [e]
(when (and ready-for-use? (not downloading?))
(when-let [on-click (:on-click opts)]
(on-click e))
(if (and (gobj/get e "shiftKey") (:root graph))
(if (util/electron?)
(state/pub-event! [:graph/open-new-window url])
(if-let [target (graph-open-new-window-target graph)]
(state/pub-event! [:graph/open-new-window target])
(p/let [target (<graph-open-new-window-target graph)]
(when target
(state/pub-event! [:graph/open-new-window target])))))
(cond
;; exists locally?
(or (:root graph) (not rtc-graph?))
(state/pub-event! [:graph/switch url])
(and rtc-graph? remote?)
(state/pub-event!
[:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion graph-e2ee?])
:else
nil))))}})))
(when-let [on-click (:on-click opts)]
(on-click e))
(if (and (gobj/get e "shiftKey") (:root graph))
(if (util/electron?)
(state/pub-event! [:graph/open-new-window url])
(if-let [target (graph-open-new-window-target graph)]
(state/pub-event! [:graph/open-new-window target])
(p/let [target (<graph-open-new-window-target graph)]
(when target
(state/pub-event! [:graph/open-new-window target])))))
(state/pub-event! [:graph/switch url])))}})))
switch-repos)]
(->> repo-links (remove nil?))))
@@ -458,35 +244,13 @@
[& {:keys [contentid footer?] :as opts
:or {footer? true}}]
(let [current-repo (rfx/use-sub [:git/current-repo])
login? (boolean (rfx/use-sub [:auth/id-token]))
repos (rfx/use-sub [:me :repos])
rtc-graphs (rfx/use-sub [:rtc/graphs])
downloading-graph-id (rfx/use-sub [:rtc/downloading-graph-uuid])
remotes-loading? (rfx/use-sub [:rtc/loading-graphs?])
repos (sort-repos-with-metadata-local repos)
repos (->>
(if (and (seq rtc-graphs) login?)
(repo-handler/combine-local-&-remote-graphs repos rtc-graphs)
repos)
(util/distinct-by :url))
items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
repos (util/distinct-by :url repos)
items-fn #(repos-dropdown-links repos current-repo opts)
header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
[:div.font-medium.md:text-sm.md:opacity-50.p-2.flex.flex-row.justify-between.items-center
[:h4.pb-1 (t :graph.switch/prompt)]
(when login?
(if remotes-loading?
(ui/loading "")
(shui/button
{:variant :ghost
:size :sm
:title (t :graph/refresh-remote-graphs)
:class "!h-6 !px-1 relative right-[-4px]"
:on-click (fn []
(rtc-handler/<get-remote-graphs))}
(ui/icon "refresh" {:size 15}))))])
_remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))]
[:h4.pb-1 (t :graph.switch/prompt)]])]
[:div
{:class (when (<= (count repos) 1) "no-repos")}
@@ -546,7 +310,6 @@
(let [current-repo (rfx/use-sub [:git/current-repo])
user-repos (rfx/use-sub [:me :repos])
current-repo' (some->> user-repos (medley/find-first #(= current-repo (:url %))))
remote? (:remote? current-repo')
short-repo-name (if current-repo
(repo-display-name current-repo)
(t :graph.switch/select-prompt))
@@ -577,7 +340,7 @@
[:a.item.flex.items-center.gap-1.select-none
selector-opts
[:span.thumb
(shui/tabler-icon (if remote? "cloud" "topology-star") {:size 16})]
(shui/tabler-icon "topology-star" {:size 16})]
[:strong short-repo-name]
(shui/tabler-icon "selector" {:size 18})]]))
@@ -623,28 +386,9 @@
(string/includes? graph-name "+")
(string/includes? graph-name "/")))
(defn ensure-e2ee-rsa-key-for-cloud!
[{:keys [cloud? graph-e2ee? refresh-token token user-uuid e2ee-rsa-key-ensured?]} set-e2ee-rsa-key-ensured?]
(if (and cloud? graph-e2ee? refresh-token token user-uuid (not e2ee-rsa-key-ensured?))
(-> (p/do!
(state/pub-event! [:rtc/sync-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)})
(p/let [rsa-key-pair (<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys)]
(set-e2ee-rsa-key-ensured? (some? rsa-key-pair))))
(p/catch (fn [e]
(log/error :db-sync/ensure-user-rsa-keys-failed e)
e)))
(p/resolved nil)))
(hsx/defc new-db-graph-inner
[rtc-group?]
[]
(let [[creating-db? set-creating-db?] (hooks/use-state false)
[cloud? set-cloud?] (hooks/use-state false)
[graph-e2ee? set-graph-e2ee?] (hooks/use-state true)
[e2ee-rsa-key-ensured? set-e2ee-rsa-key-ensured?] (hooks/use-state nil)
input-ref (hooks/create-ref)
new-db-f (fn new-db-f
[graph-name]
@@ -654,15 +398,7 @@
(invalid-graph-name-warning)
(do
(set-creating-db? true)
(p/let [repo (repo-handler/new-db! graph-name
{:creating-remote-graph? cloud?})]
(when cloud?
(->
(p/do
(rtc-handler/<rtc-create-graph-and-start-sync! repo graph-e2ee?))
(p/catch handle-cloud-graph-error!)
(p/finally (fn []
(set-creating-db? false)))))
(p/let [_repo (repo-handler/new-db! graph-name)]
(shui/dialog-close!))))))
submit! (fn submit!
[^js e click?]
@@ -675,21 +411,6 @@
(js/setTimeout #(.focus input) 32)))
[])
(hooks/use-effect!
(fn []
(let [token (state/get-auth-id-token)
user-uuid (user-handler/user-uuid)
refresh-token (state/get-auth-refresh-token)]
(ensure-e2ee-rsa-key-for-cloud!
{:cloud? cloud?
:graph-e2ee? graph-e2ee?
:refresh-token refresh-token
:token token
:user-uuid user-uuid
:e2ee-rsa-key-ensured? e2ee-rsa-key-ensured?}
set-e2ee-rsa-key-ensured?)))
[cloud? graph-e2ee?])
[:div.new-graph.flex.flex-col.gap-4.p-1.pt-2
(shui/input
{:disabled creating-db?
@@ -697,32 +418,8 @@
:placeholder (t :graph/name-placeholder)
:on-key-down submit!
:autoComplete "off"})
(when rtc-group?
[:div.flex.flex-col
[:div.flex.flex-row.items-center.gap-1
(shui/checkbox
{:id "rtc-sync"
:checked cloud?
:on-checked-change
(fn []
(let [v (not cloud?)]
(set-cloud? v)))})
[:label.opacity-70.text-sm
{:for "rtc-sync"}
(t :graph/use-sync-label)]
(when cloud?
[:div.flex.flex-row.items-center.gap-1.ml-3
(shui/checkbox
{:id "rtc-graph-e2ee"
:checked graph-e2ee?
:on-checked-change
(fn []
(set-graph-e2ee? (not graph-e2ee?)))})
[:label.opacity-70.text-sm
{:for "rtc-graph-e2ee"}
(t :graph/encrypt-data-label)]])]])
(shui/button
{:disabled (and cloud? graph-e2ee? (not e2ee-rsa-key-ensured?))
{:disabled creating-db?
:on-click #(submit! % true)
:on-key-down submit!}
(if creating-db?
@@ -731,5 +428,4 @@
(hsx/defc new-db-graph
[]
(let [rtc-group? (user-handler/rtc-group?)]
(new-db-graph-inner rtc-group?)))
(new-db-graph-inner))

View File

@@ -15,7 +15,6 @@
[frontend.date :as date]
[frontend.db.async :as db-async]
[frontend.db.hooks :as db-hooks]
[frontend.db.rtc.debug-ui :as rtc-debug-ui]
[frontend.handler.editor :as editor-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.ui :as ui-handler]
@@ -155,10 +154,6 @@
[[:.flex.items-center (ui/icon "command" {:class "text-md mr-2"}) (t :help.shortcuts/label)]
(shortcut-settings)]
:rtc
[[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) RTC"]
(rtc-debug-ui/rtc-debug-ui)]
:undo-redo
[[:.flex.items-center (ui/icon "rotate-clockwise" {:class "text-md mr-2"}) "(Dev) Undo/Redo"]
(undo-redo-debug-ui/undo-redo-debug-ui)]
@@ -189,9 +184,6 @@
(defn dev-sidebar-items
[developer-mode?]
(cond-> []
(and developer-mode? (not config/publishing?))
(conj {:db-id "rtc" :block-type :rtc :label "(Dev) RTC"})
developer-mode?
(conj {:db-id "undo-redo" :block-type :undo-redo :label "(Dev) Undo/Redo"})

View File

@@ -1,31 +0,0 @@
(ns frontend.components.rtc.download-progress
(:require [frontend.components.rtc.indicator :as indicator]
[frontend.context.i18n :refer [t]]
[frontend.mobile.util :as mobile-util]
[logseq.shui.ui :as shui]))
(defn- content
[graph-name]
[:div.flex.flex-col.items-center.justify-center.gap-4
[:div (t :sync/downloading-graph graph-name)]
(indicator/downloading-logs)])
(defn show!
[graph-name]
(if (mobile-util/native-platform?)
(shui/popup-show!
nil
#(content graph-name)
{:id :download-rtc-graph})
(shui/dialog-open!
#(content graph-name)
{:id :download-rtc-graph
:content-props
{:onPointerDownOutside #(.preventDefault %)
:onOpenAutoFocus #(.preventDefault %)}})))
(defn hide!
[]
(if (mobile-util/native-platform?)
(shui/popup-hide! :download-rtc-graph)
(shui/dialog-close! :download-rtc-graph)))

View File

@@ -1,360 +0,0 @@
(ns frontend.components.rtc.indicator
"RTC state indicator"
(:require [clojure.pprint :as pprint]
[clojure.string :as string]
[frontend.config :as config]
[frontend.db.async :as db-async]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.context.i18n :refer [locale-format-date t]]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.rfx :as rfx]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[io.factorhouse.hsx.core :as hsx]))
(comment
(def rtc-state-schema
[:enum :open :close]))
(defonce ^:private *detail-logs
(atom {:download-logs nil
:upload-logs nil
:misc-logs nil}))
(defn rtc-state->detail-info
[state]
{:pending-local-ops (:unpushed-block-update-count state)
:pending-asset-ops (:pending-asset-ops-count state)
:missing-asset-upload-files (:missing-asset-upload-files state)
:pending-server-ops (or (:pending-server-ops-count state)
(when (and (number? (:remote-tx state))
(number? (:local-tx state)))
(max 0 (- (:remote-tx state) (:local-tx state)))))
:graph-uuid (:graph-uuid state)
:local-tx (:local-tx state)
:remote-tx (:remote-tx state)
:local-checksum (:local-checksum state)
:remote-checksum (:remote-checksum state)
:rtc-state (if (:rtc-lock state) :open :close)})
(defn- update-detail-log!
[k log]
(when log
(swap! *detail-logs update k (fn [logs] (take 5 (conj logs log))))))
(add-watch rtc-flows/rtc-download-log ::update-detail-download-log
(fn [_ _ _ log] (update-detail-log! :download-logs log)))
(add-watch rtc-flows/rtc-upload-log ::update-detail-upload-log
(fn [_ _ _ log] (update-detail-log! :upload-logs log)))
(add-watch rtc-flows/rtc-misc-log ::update-detail-misc-log
(fn [_ _ _ log] (update-detail-log! :misc-logs log)))
(defn use-detail-info
[]
(merge (rtc-state->detail-info (rfx/use-sub [:rtc/state]))
(hooks/use-atom-value *detail-logs)))
(defn asset-transfer-counts
[progress]
(reduce-kv
(fn [counts _id {:keys [direction loaded total]}]
(if (and (contains? #{:upload :download} direction)
(number? loaded)
(number? total)
(not= loaded total))
(update counts direction inc)
counts))
{:upload 0
:download 0}
(or progress {})))
(defn asset-status-rows
[{:keys [pending-asset-ops missing-asset-upload-files]
transfer-counts :asset-transfer-counts}]
(let [{:keys [upload download]} transfer-counts
missing-count (count missing-asset-upload-files)
pending-upload-count (max 0 (- (or pending-asset-ops 0) missing-count))]
(cond-> []
(pos? missing-count)
(conj {:count missing-count
:label-key :sync/missing-asset-files})
(pos? pending-upload-count)
(conj {:count pending-upload-count
:label-key :sync/pending-asset-uploads})
(pos? (or upload 0))
(conj {:count upload
:label-key :sync/assets-uploading})
(pos? (or download 0))
(conj {:count download
:label-key :sync/assets-downloading}))))
(defn indicator-button-class
[{:keys [online? rtc-state pending-local-ops pending-asset-ops pending-server-ops]}]
(let [open? (and online? (= :open rtc-state))
syncing? (and open? (pos? (or pending-server-ops 0)))
idle? (and open?
(zero? (or pending-local-ops 0))
(zero? (or pending-asset-ops 0))
(zero? (or pending-server-ops 0)))
queuing? (or (pos? (or pending-local-ops 0))
(pos? (or pending-asset-ops 0)))]
(string/join " " (cond-> ["cloud"]
open? (conj "on")
syncing? (conj "syncing")
idle? (conj "idle")
queuing? (conj "queuing")))))
(defn- asset-status-label
[label-key]
(case label-key
:sync/missing-asset-files (t :sync/missing-asset-files)
:sync/pending-asset-uploads (t :sync/pending-asset-uploads)
:sync/assets-uploading (t :sync/assets-uploading)
:sync/assets-downloading (t :sync/assets-downloading)))
(hsx/defc missing-asset-files
[files]
(when (seq files)
[:details.assets-missing-files
[:summary
(t :sync/missing-asset-files-count (count files))]
[:div.flex.flex-col.gap-1.text-sm
(for [{:keys [file]} files]
[:div.flex.flex-row.gap-1.items-center
{:key file}
(ui/icon "alert-triangle" {:size 14})
[:span.truncate file]])]]))
(hsx/defc assets-progressing
[progress]
(let [ids (->> progress
(keep (fn [[id {:keys [loaded total]}]]
(when (and (not= loaded total)
(number? loaded) (number? total))
(uuid id))))
distinct
vec)
[id->block set-id->block!] (hooks/use-state {})
_ (hooks/use-effect!
(fn []
(p/let [results (db-async/<get-blocks (state/get-current-repo) ids {:children? false})]
(set-id->block! (into {} (keep (fn [{:keys [block]}]
(when-let [id (:block/uuid block)]
[id block]))
results))))
nil)
[ids])
downloading (->>
(keep (fn [[id {:keys [direction loaded total]}]]
(when (and (= direction :download)
(not= loaded total)
(number? loaded) (number? total))
(when-let [block (get id->block (uuid id))]
{:block block
:percent (int (* 100 (/ loaded total)))}))) progress)
(sort-by (fn [{:keys [block]}] (:block/title block))))
uploading (->> (keep (fn [[id {:keys [direction loaded total]}]]
(when (and (= direction :upload)
(not= loaded total)
(number? loaded) (number? total))
(when-let [block (get id->block (uuid id))]
{:block block
:percent (int (* 100 (/ loaded total)))}))) progress)
(sort-by (fn [{:keys [block]}] (:block/title block))))]
[:div.assets-sync-progress.flex.flex-col.gap-2
(when (seq downloading)
[:details
[:summary
(t :sync/assets-downloading-count (count downloading))]
[:div.flex.flex-col.gap-1.text-sm
(for [{:keys [block percent]} downloading]
[:div.flex.flex-row.gap-1.items-center
(ui/indicator-progress-pie percent)
(:block/title block)])]])
(when (seq uploading)
[:details
[:summary
(t :sync/assets-uploading-count (count uploading))]
[:div.flex.flex-col.gap-1.text-sm
(for [{:keys [block percent]} uploading]
[:div.flex.flex-row.gap-1.items-center
(ui/indicator-progress-pie percent)
(:block/title block)])]])]))
(hsx/defc details
[]
(let [online? (rfx/use-sub [:network/online?])
repo (state/get-current-repo)
asset-progress (rfx/use-sub [:rtc/asset-upload-download-progress repo])
[expand-debug? set-expand-debug!] (hooks/use-state false)
show-checksums? (or config/dev? util/node-test?)
detail-info (use-detail-info)
{:keys [graph-uuid local-tx remote-tx local-checksum remote-checksum rtc-state
download-logs upload-logs misc-logs pending-local-ops pending-asset-ops
missing-asset-upload-files pending-server-ops]}
detail-info
asset-rows (asset-status-rows {:pending-asset-ops pending-asset-ops
:missing-asset-upload-files missing-asset-upload-files
:asset-transfer-counts (asset-transfer-counts asset-progress)})]
[:div.rtc-info.flex.flex-col.gap-1.p-2.text-gray-11
[:div.font-medium.mb-2 (t (if online? :sync/online :sync/offline))]
[:div [:span.font-medium.mr-1 (or pending-local-ops 0)] (t :sync/pending-local-changes)]
(for [{:keys [count label-key]} asset-rows]
[:div {:key (name label-key)}
[:span.font-medium.mr-1 count]
(asset-status-label label-key)])
;; FIXME: pending-server-ops
[:div [:span.font-medium.mr-1 (or pending-server-ops 0)] (t :sync/pending-server-changes)]
(missing-asset-files missing-asset-upload-files)
(assets-progressing asset-progress)
;; FIXME: What's the type for downloaded log?
(when-let [latest-log (some (fn [l] (when (contains? #{:rtc.log/push-local-update} (:type l)) l)) misc-logs)]
(when-let [time (:created-at latest-log)]
[:div.text-sm (t :sync/last-synced-time-label (locale-format-date time))]))
[:a.fade-link.text-sm {:on-click #(set-expand-debug! (not expand-debug?))}
(t :sync/more-debug-info)]
(when expand-debug?
[:div.rtc-info-debug
[:pre.select-text
(-> (cond-> {:pending-local-ops pending-local-ops}
download-logs (assoc :download download-logs)
upload-logs (assoc :upload upload-logs)
misc-logs (assoc :misc misc-logs)
graph-uuid (assoc :graph-uuid graph-uuid)
local-tx (assoc :local-tx local-tx)
remote-tx (assoc :remote-tx remote-tx)
(and show-checksums? local-checksum) (assoc :local-checksum local-checksum)
(and show-checksums? remote-checksum) (assoc :remote-checksum remote-checksum)
rtc-state (assoc :rtc-state rtc-state))
pprint/pprint
with-out-str)]])
(when-not (= rtc-state :open)
[:div.mt-4
(shui/button {:variant :default
:size :sm
:on-click (fn []
(rtc-handler/<rtc-start! (state/get-current-repo)
{:stop-before-start? true}))}
(t :sync/start-sync))])]))
(hsx/defc indicator
[]
(let [detail-info (use-detail-info)
_ (rfx/use-sub [:auth/current-login-user])
online? (rfx/use-sub [:network/online?])
rtc-state (:rtc-state detail-info)
unpushed-block-update-count (:pending-local-ops detail-info)
pending-asset-ops (:pending-asset-ops detail-info)
pending-server-ops (:pending-server-ops detail-info)
{:keys [local-tx remote-tx]} detail-info]
[:div.cp__rtc-sync
[:div.hidden {"data-testid" "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})]
[:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1
(shui/button-ghost-icon :cloud
{:on-click #(shui/popup-show! (.-target %)
(details)
{:align "end"
:dropdown-menu? true})
:class (indicator-button-class
{:online? online?
:rtc-state rtc-state
:pending-local-ops unpushed-block-update-count
:pending-asset-ops pending-asset-ops
:pending-server-ops pending-server-ops})})]]))
(def ^:private *accumulated-download-logs (atom []))
(when-not config/publishing?
(add-watch rtc-flows/rtc-download-log ::update-accumulated-download-logs
(fn [_ _ _ log]
(when log
(if (= :download-completed (:sub-type log))
(reset! *accumulated-download-logs [])
(swap! *accumulated-download-logs (fn [logs] (take 20 (conj logs log)))))))))
(def ^:private *accumulated-upload-logs (atom []))
(when-not config/publishing?
(add-watch rtc-flows/rtc-upload-log ::update-accumulated-upload-logs
(fn [_ _ _ log]
(when log
(if (= :upload-completed (:sub-type log))
(reset! *accumulated-upload-logs [])
(swap! *accumulated-upload-logs (fn [logs] (take 20 (conj logs log)))))))))
(defn- current-graph-logs
[logs]
(when-let [first-log (first logs)]
(let [graph-uuid (:graph-uuid first-log)]
(take-while (fn [log] (= (str graph-uuid) (str (:graph-uuid log)))) logs))))
(defn- log-row-key
[prefix idx log]
(str prefix "-" (:graph-uuid log) "-" (:sub-type log) "-" (:message log) "-" idx))
(hsx/defc downloading-logs
[]
(let [download-logs (current-graph-logs (hooks/use-atom-value *accumulated-download-logs))]
(when (seq download-logs)
[:div.flex.flex-col.gap-1
(for [[idx log] (map-indexed vector download-logs)]
^{:key (log-row-key "download" idx log)}
[:div (string/capitalize (:message log))])])))
(hsx/defc uploading-logs
[]
(let [upload-logs (current-graph-logs (hooks/use-atom-value *accumulated-upload-logs))]
(when (seq upload-logs)
[:div.capitalize.flex.flex-col.gap-1
(for [[idx log] (map-indexed vector (reverse upload-logs))]
^{:key (log-row-key "upload" idx log)}
[:div (:message log)])])))
(def ^:private *downloading? (atom false))
(add-watch rtc-flows/rtc-download-log ::update-downloading?
(fn [_ _ _ log]
(reset! *downloading? (not= :download-completed (:sub-type log)))))
(hsx/defc downloading-detail
[]
(when (true? (hooks/use-atom-value *downloading?))
(shui/button
{:class "opacity-50"
:variant :ghost
:size :sm
:on-click #(shui/popup-show! (.-target %)
(downloading-logs)
{:align "end"})}
(t :sync/downloading))))
(def ^:private *uploading? (atom false))
(add-watch rtc-flows/rtc-upload-log ::update-uploading?
(fn [_ _ _ log]
(reset! *uploading? (not= :upload-completed (:sub-type log)))))
(defn on-upload-finished-task
[on-success]
(let [watch-key (random-uuid)]
(add-watch rtc-flows/rtc-upload-log watch-key
(fn [_ _ _ log]
(when (= :upload-completed (:sub-type log))
(remove-watch rtc-flows/rtc-upload-log watch-key)
(on-success))))
#(remove-watch rtc-flows/rtc-upload-log watch-key)))
(hsx/defc uploading-detail
[]
(when (true? (hooks/use-atom-value *uploading?))
(shui/button
{:class "opacity-50"
:variant :ghost
:size :sm
:on-click #(shui/popup-show! (.-target %)
(uploading-logs)
{:align "end"})}
(t :sync/uploading))))

View File

@@ -11,7 +11,6 @@
[frontend.date :as date]
[frontend.dicts :as dicts]
[frontend.handler.config :as config-handler]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.global-config :as global-config-handler]
[frontend.handler.notification :as notification]
[frontend.handler.plugin :as plugin-handler]
@@ -637,59 +636,6 @@
(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)}))
(hsx/defc sync-server-url-settings-container
[]
(let [current-url (config/get-custom-sync-server-url)
[url set-url!] (hooks/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.sync-server/clear-success) :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.sync-server/url)]
[:div.p-2
[:p.text-sm.opacity-70.mb-4 (t :settings.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! (t :settings.sync-server/url-invalid-error) :error)
(do
(config/set-custom-sync-server-url! trimmed)
(-> (push-sync-config-to-worker!)
(p/then #(notification/show! (t :settings.sync-server/save-success) :success))
(p/catch #(notification/show! (str "Failed to update worker: " %) :error))))))))}
(t :ui/save))
(when (seq url)
(shui/button
{:size :sm
:variant :outline
: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
@@ -788,23 +734,6 @@
{:left-label "Server"
:action (db-worker-server-button)}))
(hsx/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
"Logseq Sync")]
(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.sync-server/url)
:action (sync-server-url-button)}))
(hsx/defc publish-server-url-settings-container
[]
(let [current-url (config/get-custom-publish-server-url)
@@ -1024,7 +953,6 @@
(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))
(when (util/electron?) (auto-chmod-row t))
@@ -1294,221 +1222,6 @@
[:<>])
(hsx/defc settings-rtc-members
[]
(let [[invite-email set-invite-email!] (hooks/use-state "")
[loading? set-loading!] (hooks/use-state true)
[graph-uuid set-graph-uuid!] (hooks/use-state nil)
current-repo (state/get-current-repo)
manager? (user-handler/manager? current-repo)
users-info (rfx/use-sub [:rtc/users-info])
users (get users-info current-repo)
invite-user! (fn []
(when-not (string/blank? invite-email)
(when graph-uuid
(rtc-handler/<rtc-invite-email graph-uuid invite-email))))]
(hooks/use-effect!
#(do
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid current-repo)
_ (rtc-handler/<rtc-get-users-info)]
(set-graph-uuid! graph-uuid)
(set-loading! false))
nil)
[])
[:div.flex.flex-col.gap-2.mt-4
{:on-key-press (fn [e]
(when (= "Enter" (.-key e))
(invite-user!)))}
[:div.users.flex.flex-col.gap-1
(if loading?
(for [i (range 2)]
[:div.flex.flex-row.items-center.gap-2.pr-4 {:key (str "skeleton-" i)}
(shui/skeleton {:class "h-4 w-32"})
(shui/skeleton {:class "h-4 w-full"})])
(for [{user-name :user/name
user-email :user/email
user-uuid :user/uuid
graph<->user-user-type :graph<->user/user-type} users]
(let [member? (= :member graph<->user-user-type)
can-remove? (and manager? member?)]
[:div.flex.flex-row.items-center.gap-2
{:key (str "user-" (or user-uuid user-name))}
[:div user-name]
(when user-email
(email-component/email-address {:email user-email
:class "opacity-50 text-sm"}))
(when graph<->user-user-type [:div.opacity-50.text-sm (name graph<->user-user-type)])
(when can-remove?
(shui/dropdown-menu
(shui/dropdown-menu-trigger
{:asChild true}
(shui/button
{:variant "ghost"
:size :sm
:class "px-1 h-7"}
(ui/icon "dots" {:size 14})))
(shui/dropdown-menu-content
{:align "end"}
(shui/dropdown-menu-item
{:class "remove-member-menu-item"
:on-click (fn []
(let [member-id user-uuid]
(when (and graph-uuid member-id)
(-> (rtc-handler/<rtc-remove-member! graph-uuid member-id)
(p/then (fn []
(rtc-handler/<rtc-get-users-info true)))
(p/catch (fn [e]
(notification/show! (t :collaboration/remove-access-error) :error)
(log/error :db-sync/remove-member-failed {:error e
:graph-uuid graph-uuid
:member-id member-id})))))))}
(t :collaboration/remove-access)))))])))]
[:div.flex.flex-col.gap-4.mt-4
(shui/input
{:placeholder (t :collaboration/email-address)
:on-change #(set-invite-email!
(string/trim (util/evalue %)))})
(shui/button
{:on-click invite-user!}
(t :collaboration/invite))]]))
(hsx/defc settings-collaboration
[]
[:div.panel-wrap.is-collaboration.mb-8
(settings-rtc-members)])
(hsx/defc forgot-password
[token refresh-token user-uuid]
(let [[new-password set-new-password!] (hooks/use-state "")
[force-reset-status set-force-reset-status!] (hooks/use-state nil)
<force-reset-password-fn
(fn []
(-> (p/do!
(set-force-reset-status! (t :encryption/force-resetting-password))
(state/<invoke-db-worker :thread-api/reset-user-rsa-key-pair
token refresh-token user-uuid new-password)
(set-force-reset-status! (t :encryption/force-reset-password-successfully)))
(p/catch (fn [e]
(log/error :forgot-password e)
(set-force-reset-status! (t :encryption/failed-to-force-reset-password))))))]
[:div.flex.flex-col.gap-4
[:p
(t :encryption/forgot-password-warning)]
[:label.opacity-70 {:for "new-password"} (t :encryption/set-new-password)]
(shui/toggle-password
{:id "new-password"
:value new-password
:on-change #(set-new-password! (util/evalue %))})
(when force-reset-status [:p force-reset-status])
(shui/button
{:on-click <force-reset-password-fn
:disabled (string/blank? new-password)}
(t :encryption/force-reset-password))]))
(hsx/defc reset-encryption-password
[current-password new-password {:keys [set-new-password!
set-current-password!
reset-password-status
on-click forgot? set-forgot!
token refresh-token user-uuid]}]
(let [[reset? set-reset!] (hooks/use-state false)]
(cond
forgot?
(forgot-password token refresh-token user-uuid)
reset?
[:div.flex.flex-col.gap-4
[:label.opacity-70 {:for "current-password"} (t :encryption/current-password)]
(shui/toggle-password
{:id "current-password"
:value current-password
:on-change #(set-current-password! (util/evalue %))})
[:label.opacity-70 {:for "new-password"} (t :encryption/set-new-password)]
(shui/toggle-password
{:id "new-password"
:value new-password
:on-change #(set-new-password! (util/evalue %))})
(when reset-password-status [:p reset-password-status])
(shui/button
{:on-click on-click
:disabled (string/blank? new-password)}
(t :encryption/reset-password))
[:a.opacity-70.hover:opacity-100 {:on-click #(set-forgot! true)}
(t :encryption/forgot-password-question)]]
:else
[:a.opacity-70.hover:opacity-100 {:on-click #(set-reset! true)}
(t :encryption/reset-password)])))
(hsx/defc encryption
[]
(let [user-uuid (user-handler/user-uuid)
token (state/get-auth-id-token)
refresh-token (str (state/get-auth-refresh-token))
[rsa-key-pair set-rsa-key-pair!] (hooks/use-state :not-inited)
[init-key-err set-init-key-err!] (hooks/use-state nil)
[get-key-err set-get-key-err!] (hooks/use-state nil)
[current-password set-current-password!] (hooks/use-state nil)
[new-password set-new-password!] (hooks/use-state nil)
[reset-password-status set-reset-password-status!] (hooks/use-state nil)
[forgot? set-forgot!] (hooks/use-state false)]
[:div.panel-wrap.is-encryption.mb-8
(hooks/use-effect!
(fn []
(when (and user-uuid token)
(-> (p/let [r (state/<invoke-db-worker :thread-api/get-user-rsa-key-pair token user-uuid)]
(set-rsa-key-pair! r))
(p/catch set-get-key-err!))
(-> (p/let [{:keys [password]} (state/<invoke-db-worker :thread-api/get-e2ee-password refresh-token)]
(set-current-password! password))
(p/catch (fn [_] (set-current-password! ""))))))
[user-uuid token])
[:div.flex.flex-col.gap-2.mt-4
(when (and user-uuid token)
(cond
get-key-err
[:p (t :encryption/fetch-key-pair-error get-key-err)]
(= rsa-key-pair :not-inited)
[:p (t :encryption/fetching-key-pair)]
(nil? rsa-key-pair)
[:div.flex.flex-col.gap-2
(when init-key-err [:p (t :encryption/init-key-pair-error init-key-err)])
(shui/button
{:on-click (fn []
(-> (p/do!
(state/<invoke-db-worker :thread-api/init-user-rsa-key-pair
token
refresh-token
user-uuid)
(p/let [r (state/<invoke-db-worker :thread-api/get-user-rsa-key-pair token user-uuid)]
(set-rsa-key-pair! r)))
(p/catch set-init-key-err!)))}
(t :encryption/init-key-pair))]
rsa-key-pair
(let [on-submit (fn []
(-> (p/do!
(set-reset-password-status! (t :encryption/updating-password))
(state/<invoke-db-worker :thread-api/change-e2ee-password
token refresh-token user-uuid current-password new-password)
(set-reset-password-status! (t :encryption/password-updated-successfully)))
(p/catch (fn [e]
(log/error :reset-password-failed e)
(set-reset-password-status! (t :encryption/failed-to-update-password))))))]
[:div.flex.flex-col.gap-4
;; [:p "E2EE key-pair already generated!"]
(when-not forgot?
[:div.flex.flex-col
[:p (t :encryption/remember-password-rich)]
[:p (t :encryption/cloud-password-rich)]])
(reset-encryption-password current-password new-password
{:reset-password-status reset-password-status
:set-new-password! set-new-password!
:set-current-password! set-current-password!
:on-click on-submit
:token token
:forgot? forgot?
:set-forgot! set-forgot!
:refresh-token refresh-token
:user-uuid user-uuid})])))]]))
(hsx/defc mcp-server-row
[t]
(let [server-state (rfx/use-sub [:electron/server])
@@ -1552,8 +1265,7 @@
_installed-plugins (rfx/use-sub [:plugin/installed-plugins])
plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
*active (hooks/use-memo #(atom DEFAULT-ACTIVE-TAB-STATE) [])
[active] (hooks/use-atom *active)
logged-in? (user-handler/logged-in?)]
[active] (hooks/use-atom *active)]
(hooks/use-effect!
(fn []
(state/load-app-user-cfgs)
@@ -1584,11 +1296,6 @@
[:advanced (t :settings/advanced) (ui/icon "bulb")]
[:features (t :settings/features) (ui/icon "app-feature")]
(when logged-in?
[:collaboration (t :settings/collaboration) (ui/icon "users")])
(when logged-in?
[:encryption (t :settings/encryption) (ui/icon "lock")])
(when plugins-of-settings
[:plugins-setting (t :settings/plugins) (ui/icon "puzzle")])]]
@@ -1641,12 +1348,6 @@
:features
(settings-features)
:collaboration
(settings-collaboration)
:encryption
(encryption)
:ai
(if (util/electron?)
(settings-ai)

View File

@@ -39,70 +39,12 @@
;; Enable for local development
;; (def default-publish-api-base "http://localhost:8787")
(goog-define ENABLE-DB-SYNC-LOCAL false)
(defonce db-sync-local? ENABLE-DB-SYNC-LOCAL)
(defonce default-db-sync-ws-url
(if db-sync-local?
"ws://127.0.0.1:8787/sync/%s"
"wss://api.logseq.io/sync/%s"))
(defonce default-db-sync-http-base
(if db-sync-local?
"http://127.0.0.1:8787"
"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))
;; 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

View File

@@ -7,7 +7,6 @@
[frontend.components.plugins :as plugins]
[frontend.config :as config]
[frontend.handler :as handler]
[frontend.handler.db-based.rtc-background-tasks]
[frontend.handler.plugin :as plugin-handler]
[frontend.handler.route :as route-handler]
[frontend.log]

View File

@@ -16,12 +16,8 @@
{:keys [schema]} (persist-db/<open-and-fetch-schema repo opts)
_ (when (nil? schema)
(throw (ex-info "No valid schema found when reloading db" {:repo repo})))
conflicts-by-block (state/<invoke-db-worker
:thread-api/db-sync-get-all-block-conflicts
repo)
_ (state/set-current-repo! repo)
_ (db-subs/reset-graph! repo)
_ (state/set-sync-block-conflicts! repo conflicts-by-block)
end-time (t/now)]
(log/info ::restore-graph!
(str "opens worker graph in "

View File

@@ -1,136 +0,0 @@
(ns frontend.db.rtc.debug-ui
"Debug UI for rtc module"
(:require [fipp.edn :as fipp]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.user :as user]
[frontend.state :as state]
[lambdaisland.glogi :as log]
[logseq.db.frontend.schema :as db-schema]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[io.factorhouse.hsx.core :as hsx]))
(defonce debug-state (atom (state/get-state :rtc/state)))
(defn- stop
[]
(p/do!
(rtc-handler/<rtc-stop!)
(reset! debug-state nil)))
(hsx/defc ^:large-vars/cleanup-todo rtc-debug-ui
[]
(let [[debug-state*] (hooks/use-atom debug-state)
[rtc-logs set-rtc-logs!] (hooks/use-state nil)
[keys-state set-keys-state!] (hooks/use-state nil)
[current-page-blocks-count set-current-page-blocks-count!] (hooks/use-state nil)
rtc-state (:rtc-state debug-state*)]
(hooks/use-effect!
(fn []
(let [logs* (atom nil)
watch-key ::sub-logs]
(add-watch rtc-flows/rtc-log watch-key
(fn [_ _ _ log]
(let [logs (if log
(take 10 (conj @logs* log))
@logs*)]
(reset! logs* logs)
(set-rtc-logs! logs))))
#(remove-watch rtc-flows/rtc-log watch-key)))
[])
(hooks/use-effect!
(fn []
(if-let [page (state/get-current-page)]
(p/let [blocks-count (state/<invoke-db-worker
:thread-api/q
(state/get-current-repo)
['[:find (count ?block) .
:in $ ?page-name
:where
[?page :block/name ?page-name]
[?block :block/page ?page]]
page])]
(set-current-page-blocks-count! blocks-count))
(set-current-page-blocks-count! nil))
nil)
[(state/get-current-page)])
[:div
{:on-click (fn [^js e]
(when-let [^js btn (.closest (.-target e) ".ui__button")]
(.setAttribute btn "disabled" "true")
(js/setTimeout #(.removeAttribute btn "disabled") 2000)))}
[:div.flex.gap-2.flex-wrap.items-center.pb-3
(shui/button
{:size :sm
:on-click
(fn [_]
(p/let [graph-list (rtc-handler/<get-remote-graphs)]
(swap! debug-state assoc :remote-graphs graph-list)))}
(shui/tabler-icon "download") "graph-list")
(shui/button
{:size :sm
:on-click #(user/<upload-user-avatar "TEST_AVATAR")}
(shui/tabler-icon "upload") "upload-test-avatar")]
[:div.pb-4
[:pre.select-text
(-> {:user-uuid (user/user-uuid)
:graph (:graph-uuid debug-state*)
:rtc-state rtc-state
:rtc-logs rtc-logs
:local-tx (:local-tx debug-state*)
:pending-block-update-count (:unpushed-block-update-count debug-state*)
:remote-graphs (:remote-graphs debug-state*)
:online-users (:online-users debug-state*)
:auto-push? (:auto-push? debug-state*)
:remote-profile? (:remote-profile? debug-state*)
:current-page (state/get-current-page)
:blocks-count current-page-blocks-count
:schema-version {:app (db-schema/schema-version->string db-schema/version)
:local-graph (:local-graph-schema-version debug-state*)
:remote-graph (str (:remote-graph-schema-version debug-state*))}}
(fipp/pprint {:width 20})
with-out-str)]]
[:div.my-2.flex.gap-2
(shui/button
{:variant :outline
:size :sm
:class "text-green-rx-09 border-green-rx-10 hover:text-green-rx-10"
:on-click (fn [] (rtc-handler/<rtc-start! (state/get-current-repo)))}
(shui/tabler-icon "player-play") "start")
(shui/button
{:variant :outline
:class "text-red-rx-09 border-red-rx-08 hover:text-red-rx-10"
:size :sm
:on-click (fn [] (stop))}
(shui/tabler-icon "player-stop") "stop")]
[:hr.my-2]
[:div
[:div.pb-2.flex.flex-row.items-center.gap-2
(shui/button
{:size :sm
:on-click (fn [_]
(when-let [user-uuid (user/user-uuid)]
(p/let [user-rsa-key-pair (state/<invoke-db-worker
:thread-api/get-user-rsa-key-pair
(state/get-auth-id-token) user-uuid)]
(set-keys-state! user-rsa-key-pair))))}
(shui/tabler-icon "refresh") "keys-state")
(shui/button
{:size :sm
:on-click (fn [_]
(when-let [token (state/get-auth-id-token)]
(-> (state/<invoke-db-worker
:thread-api/init-user-rsa-key-pair token (user/user-uuid))
(p/catch (fn [error]
(log/error :init-user-rsa-key-pair error))))))}
(shui/tabler-icon "upload") "init upload user rsa-key-pair")]
[:div.pb-1
[:pre.select-text
(-> keys-state
(fipp/pprint {:width 20})
with-out-str)]]]]))

View File

@@ -15,10 +15,8 @@
[frontend.db.restore :as db-restore]
[frontend.error :as error]
[frontend.handler.command-palette :as command-palette]
[frontend.handler.e2ee]
[frontend.handler.events :as events]
[frontend.handler.events.export]
[frontend.handler.events.rtc]
[frontend.handler.events.ui]
[frontend.handler.graph :as graph-handler]
[frontend.handler.global-config :as global-config-handler]

View File

@@ -336,32 +336,3 @@
(p/let [[repo-dir assets-dir] (ensure-assets-dir! (state/get-current-repo))]
(path/path-join repo-dir assets-dir filename)))
(defn- asset-transfer-in-progress?
[progress-entry]
(let [{:keys [loaded total]} progress-entry]
(and (number? loaded) (number? total) (pos? total) (not= loaded total))))
(defn should-request-remote-asset-download?
[repo asset-block file-ready? progress]
(let [asset-uuid (:block/uuid asset-block)
asset-type (:logseq.property.asset/type asset-block)
external-url (:logseq.property.asset/external-url asset-block)
remote-metadata (:logseq.property.asset/remote-metadata asset-block)
progress-entry (get progress (str asset-uuid))]
(and (seq repo)
remote-metadata
asset-uuid
(seq asset-type)
(string/blank? external-url)
(not file-ready?)
(not (asset-transfer-in-progress? progress-entry)))))
(defn maybe-request-remote-asset-download!
[repo asset-block file-ready?]
(let [progress (get (or (state/get-state :rtc/asset-upload-download-progress) {}) repo)]
(when (should-request-remote-asset-download? repo asset-block file-ready? progress)
(state/<invoke-db-worker
:thread-api/db-sync-request-asset-download
repo
(:block/uuid asset-block))
true)))

View File

@@ -2,11 +2,9 @@
"Common fns for developer related functionality"
(:require ["/frontend/utils" :as utils]
[cljs.pprint :as pprint]
[clojure.set :as set]
[clojure.string :as string]
[frontend.context.i18n :refer [t]]
[frontend.format.mldoc :as mldoc]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.notification :as notification]
[frontend.persist-db :as persist-db]
[frontend.state :as state]
@@ -84,13 +82,6 @@
(defn ^:export validate-db []
(state/<invoke-db-worker :thread-api/validate-db (state/get-current-repo)))
(defn- checksum-export-file-name
[repo]
(-> (or repo "graph")
(string/replace #"^/+" "")
(string/replace #"[\\/]+" "_")
(str "_checksum_" (quot (util/time-ms) 1000))))
(defn- client-ops-export-file-name
[repo]
(-> (or repo "graph")
@@ -98,113 +89,6 @@
(string/replace #"[\\/]+" "_")
(str "_client_ops_" (quot (util/time-ms) 1000))))
(defn- <fetch-server-checksum-diagnostics
[repo]
(p/let [base (rtc-handler/http-base)
graph-id' (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)
graph-id (some-> graph-id' str)]
(if (and (seq base) (seq graph-id))
(rtc-handler/fetch-json (str base "/sync/" graph-id "/checksum/diagnostics")
{:method "GET"}
{:error-schema :error})
(p/rejected (ex-info "missing sync diagnostics context"
{:repo repo
:base base
:graph-id graph-id})))))
(defn- normalize-diff-block
[{:keys [block/uuid block/parent block/page] :as block}]
(cond-> block
uuid (assoc :block/uuid (str uuid))
parent (assoc :block/parent (str parent))
page (assoc :block/page (str page))))
(defn- blocks-by-uuid
[blocks]
(into {}
(keep (fn [block]
(let [block' (normalize-diff-block block)
uuid (:block/uuid block')]
(when (seq uuid)
[uuid block']))))
blocks))
(defn- different-blocks
[local-blocks server-blocks]
(let [local-by-uuid (blocks-by-uuid local-blocks)
server-by-uuid (blocks-by-uuid server-blocks)
block-uuids (sort (set/union (set (keys local-by-uuid))
(set (keys server-by-uuid))))]
(->> block-uuids
(keep (fn [uuid]
(let [local-block (get local-by-uuid uuid)
server-block (get server-by-uuid uuid)]
(when (not= local-block server-block)
{:block/uuid uuid
:local-block local-block
:server-block server-block}))))
vec)))
(defn- <log-checksum-mismatch-diff!
[repo export-edn]
(p/let [server-diagnostics (<fetch-server-checksum-diagnostics repo)
server-blocks (map (fn [b]
{:block/uuid (uuid (:uuid b))
:block/parent (when-let [id (:parent b)] (uuid id))
:block/page (when-let [id (:page b)] (uuid id))
:block/order (:order b)})
(:blocks server-diagnostics))
diff-blocks (different-blocks (:blocks export-edn) server-blocks)
diff-data {:repo repo
:local-checksum (:local-checksum export-edn)
:remote-checksum (:remote-checksum export-edn)
:recomputed-checksum (:recomputed-checksum export-edn)
:server-checksum (:checksum server-diagnostics)
:different-blocks diff-blocks}]
(pprint/pprint diff-data)
(when (seq diff-blocks)
(js/console.warn "Checksum mismatch between client and server. Diff data:" diff-data))))
(defn ^:export recompute-checksum-diagnostics
[]
(if-let [repo (state/get-current-repo)]
(-> (state/<invoke-db-worker :thread-api/recompute-checksum-diagnostics repo)
(p/then (fn [{:keys [recomputed-checksum local-checksum remote-checksum blocks checksum-attrs e2ee?]
:as result}]
(if (map? result)
(let [export-edn {:repo repo
:generated-at (.toISOString (js/Date.))
:e2ee? e2ee?
:recomputed-checksum recomputed-checksum
:local-checksum local-checksum
:remote-checksum remote-checksum
:checksum-attrs checksum-attrs
:blocks blocks}
content (with-out-str (pprint/pprint export-edn))
blob (js/Blob. #js [content] (clj->js {:type "text/edn;charset=utf-8"}))
filename (checksum-export-file-name repo)]
(p/let [_ (-> (<log-checksum-mismatch-diff! repo export-edn)
(p/catch (fn [error]
(js/console.error "checksum mismatch diff fetch failed:" error)
nil)))]
(utils/saveToFile blob filename "edn")
(notification/show!
(t :graph.diagnostics/checksum-recomputed-success
recomputed-checksum
(or local-checksum "<nil>")
(or remote-checksum "<nil>")
filename
(count blocks)
(pr-str checksum-attrs))
:success
false)))
(notification/show! (t :graph.diagnostics/checksum-unavailable-warning) :warning))))
(p/catch (fn [error]
(js/console.error "recompute-checksum-diagnostics failed:" error)
(notification/show! (t :graph.diagnostics/checksum-failed-error) :error))))
(notification/show! (t :graph.diagnostics/no-graph-warning) :warning)))
(defn ^:export export-client-ops-sqlite
[]
(if (and util/web-platform? (not (util/electron?)))
@@ -238,9 +122,3 @@
(defn ^:export replace-graph-with-db-file []
(state/pub-event! [:dialog-select/db-graph-replace]))
(defn ^:export rtc-stop []
(rtc-handler/<rtc-stop!))
(defn ^:export rtc-start []
(rtc-handler/<rtc-start! (state/get-current-repo)))

View File

@@ -1,45 +0,0 @@
(ns frontend.handler.db-based.rtc-background-tasks
"Background tasks related to RTC"
(:require [cljs-time.core :as t]
[frontend.config :as config]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.state :as state]
[lambdaisland.glogi :as log]
[logseq.common.util :as common-util]
[promesa.core :as p]))
(defn- add-watch-when-not-publishing!
[atom' key' f]
(when-not config/publishing?
(remove-watch atom' key')
(add-watch atom' key' (fn [_ _ _ value] (f value)))
(when-some [value @atom']
(f value))))
(add-watch-when-not-publishing!
rtc-flows/rtc-try-restart
::restart-rtc-to-reconnect
(fn [{:keys [graph-uuid t]}]
(when (and graph-uuid t)
(p/let [repo (state/get-current-repo)
current-graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
(when (and (= graph-uuid (some-> current-graph-uuid str))
(> 5000 (- (common-util/time-ms) t)))
(log/info :trying-to-restart-rtc graph-uuid :t (t/now))
(rtc-handler/<rtc-start! repo :stop-before-start? false))))))
(add-watch-when-not-publishing!
rtc-flows/logout
::stop-rtc-if-needed
(fn [event]
(when (= :logout event)
(log/info :try-to-stop-rtc-if-needed :logout)
(rtc-handler/<rtc-stop!))))
(add-watch-when-not-publishing!
rtc-flows/trigger-start-rtc
::auto-start-rtc-if-possible
(fn [[start-reason repo]]
(log/info :try-to-start-rtc [start-reason repo])
(p/do! (rtc-handler/<rtc-start! (or repo (state/get-current-repo))))))

View File

@@ -1,135 +0,0 @@
(ns frontend.handler.db-based.rtc-flows
"Reactive RTC atoms."
(:require [frontend.flows :as flows]
[frontend.mobile.flows :as mobile-flows]
[logseq.common.util :as common-util]))
(def rtc-log
(let [state* (atom nil)
source (flows/sub-atom [:rtc/log])]
(add-watch source ::rtc-log
(fn [_ _ _ log]
(when-not (and (= :skip (:sub-type log))
(= :rtc.log/apply-remote-update (:type log)))
(reset! state* log))))
state*))
(def rtc-download-log
(let [state* (atom nil)]
(add-watch rtc-log ::rtc-download-log
(fn [_ _ _ log]
(when (keyword-identical? :rtc.log/download (:type log))
(reset! state* log))))
state*))
(def rtc-upload-log
(let [state* (atom nil)]
(add-watch rtc-log ::rtc-upload-log
(fn [_ _ _ log]
(when (keyword-identical? :rtc.log/upload (:type log))
(reset! state* log))))
state*))
(def rtc-misc-log
(let [state* (atom nil)]
(add-watch rtc-log ::rtc-misc-log
(fn [_ _ _ log]
(when-not (contains? #{:rtc.log/download :rtc.log/upload} (:type log))
(reset! state* log))))
state*))
(def rtc-state
(flows/sub-atom [:rtc/state]))
(def rtc-try-restart
"Updates when it's time to restart rtc loop.
conditions:
- user logged in
- no rtc loop running now
- last rtc stop-reason is websocket message timeout
- online
- throttle 5000ms"
(let [state* (atom nil)
last-emitted-at (atom 0)
update! (fn []
(let [{:keys [rtc-lock last-stop-exception-ex-data graph-uuid]} @rtc-state
login-user @flows/current-login-user
online? @flows/network-online?
now (common-util/time-ms)]
(when (and online?
(some? (:email login-user))
(some? graph-uuid)
(not rtc-lock)
(= :rtc.exception/ws-timeout (:type last-stop-exception-ex-data))
(>= (- now @last-emitted-at) 5000))
(reset! last-emitted-at now)
(reset! state* {:graph-uuid graph-uuid :t now}))))]
(doseq [[watch-key atom'] {::rtc-try-restart-state rtc-state
::rtc-try-restart-online flows/network-online?
::rtc-try-restart-user flows/current-login-user}]
(add-watch atom' watch-key (fn [_ _ _ _] (update!))))
state*))
(def logout
(let [state* (atom nil)]
(add-watch flows/current-login-user ::logout
(fn [_ _ _ user]
(when (= :logout user)
(reset! state* :logout))))
state*))
(def ^:private *rtc-start-trigger (atom nil))
(defn trigger-rtc-start
[repo]
(assert (some? repo))
(reset! *rtc-start-trigger repo))
(defn- document-visible->restart-event
[visibility]
(when (= "visible" visibility)
:document-visible&rtc-not-running))
(defn- network-online->restart-event
[online?]
(when online?
:network-online&rtc-not-running))
(defn- mobile-app-active->restart-event
[app-active?]
(when app-active?
:mobile-app-active&rtc-not-running))
(def trigger-start-rtc
(let [state* (atom nil)
timeout-id (atom nil)
emit! (fn [event]
(when @flows/current-login-user
(when-let [id @timeout-id]
(js/clearTimeout id))
(reset! timeout-id (js/setTimeout #(reset! state* event) 50))))]
(add-watch flows/current-login-user ::trigger-start-rtc-login
(fn [_ _ _ user]
(when (:email user)
(emit! [:login]))))
(add-watch flows/current-repo ::trigger-start-rtc-repo
(fn [_ _ _ repo]
(when repo
(emit! [:graph-switch repo]))))
(add-watch *rtc-start-trigger ::trigger-start-rtc-manual
(fn [_ _ _ repo]
(when repo
(emit! [:trigger-rtc repo]))))
(add-watch flows/document-visibility-state ::trigger-start-rtc-visible
(fn [_ _ _ visibility]
(when-let [event (document-visible->restart-event visibility)]
(emit! [event]))))
(add-watch flows/network-online? ::trigger-start-rtc-online
(fn [_ _ _ online?]
(when-let [event (network-online->restart-event online?)]
(emit! [event]))))
(add-watch mobile-flows/*mobile-app-state ::trigger-start-rtc-mobile-active
(fn [_ _ _ app-active?]
(when-let [event (mobile-app-active->restart-event app-active?)]
(emit! [event]))))
state*))

View File

@@ -1,463 +0,0 @@
(ns frontend.handler.db-based.sync
"DB-sync handler based on Cloudflare Durable Objects."
(:require [clojure.string :as string]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.handler.notification :as notification]
[frontend.handler.repo :as repo-handler]
[frontend.handler.user :as user-handler]
[frontend.persist-db :as persist-db]
[frontend.state :as state]
[frontend.util :as util]
[lambdaisland.glogi :as log]
[logseq.db-sync.malli-schema :as db-sync-schema]
[promesa.core :as p]))
(defn- ws->http-base [ws-url]
(when (string? ws-url)
(let [base (cond
(string/starts-with? ws-url "wss://")
(str "https://" (subs ws-url (count "wss://")))
(string/starts-with? ws-url "ws://")
(str "http://" (subs ws-url (count "ws://")))
:else ws-url)
base (string/replace base #"/sync/%s$" "")]
base)))
(defn http-base []
(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)]
{"authorization" (str "Bearer " token)}))
(defn- with-auth-headers [opts]
(if-let [auth (auth-headers)]
(assoc opts :headers (merge (or (:headers opts) {}) auth))
opts))
(declare fetch-json)
(declare coerce-http-response)
(declare <sync-auth-state-to-db-worker!)
(defn fetch-json
[url opts {:keys [response-schema error-schema] :or {error-schema :error}}]
(p/let [resp (js/fetch url (clj->js (with-auth-headers opts)))
text (.text resp)
data (when (seq text) (js/JSON.parse text))]
(if (.-ok resp)
(let [body (js->clj data :keywordize-keys true)
body (if response-schema
(coerce-http-response response-schema body)
body)]
(if (or (nil? response-schema) body)
body
(throw (ex-info "db-sync invalid response"
{:status (.-status resp)
:url url
:body body}))))
(let [body (when data (js->clj data :keywordize-keys true))
body (if error-schema
(coerce-http-response error-schema body)
body)]
(throw (ex-info "db-sync request failed"
{:status (.-status resp)
:url url
:body body}))))))
(def ^:private invalid-coerce ::invalid-coerce)
(defn- coerce
[coercer value context]
(try
(coercer value)
(catch :default e
(log/error :db-sync/malli-coerce-failed (merge context {:error e :value value}))
invalid-coerce)))
(defn- coerce-http-request [schema-key body]
(if-let [coercer (get db-sync-schema/http-request-coercers schema-key)]
(let [coerced (coerce coercer body {:schema schema-key :dir :request})]
(when-not (= coerced invalid-coerce)
coerced))
body))
(defn- coerce-http-response [schema-key body]
(if-let [coercer (get db-sync-schema/http-response-coercers schema-key)]
(let [coerced (coerce coercer body {:schema schema-key :dir :response})]
(when-not (= coerced invalid-coerce)
coerced))
body))
(defn- remote-graph
[repo]
(some #(when (= repo (:url %)) %) (state/get-rtc-graphs)))
(defn- <get-rtc-graph-uuid
[repo]
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
(some-> graph-uuid str)))
(defn- <get-rtc-graph-e2ee?
[repo]
(state/<invoke-db-worker :thread-api/get-key-value repo :logseq.kv/graph-rtc-e2ee?))
(defn- <ensure-invite-auth!
[]
(user-handler/<ensure-id&access-token!))
(defn- <grant-graph-access-for-invite!
[repo graph-uuid email]
(p/let [e2ee? (when repo (<get-rtc-graph-e2ee? repo))]
(when (and repo e2ee?)
(state/<invoke-db-worker :thread-api/db-sync-grant-graph-access
repo graph-uuid email))))
(defn- should-start-rtc?
[repo]
(and (not (true? (:rtc/uploading? (state/get-state))))
(let [graph (remote-graph repo)]
(and (some? graph)
(not= false (:graph-ready-for-use? graph))))))
(defn- normalize-graph-e2ee?
[graph-e2ee?]
(if (nil? graph-e2ee?)
true
(true? graph-e2ee?)))
(defn- active-graph-operation []
(let [{:rtc/keys [downloading-graph-uuid uploading?]} (state/get-state)]
(cond
downloading-graph-uuid
{:active-operation :download
:active-graph-uuid downloading-graph-uuid}
(true? uploading?)
{:active-operation :upload})))
(defn- reject-graph-operation-in-progress
[requested-operation active-operation]
(p/rejected
(ex-info "graph operation already in progress"
(assoc active-operation
:type :db-sync/graph-operation-in-progress
:requested-operation requested-operation))))
(defn- <ensure-download-runtime-bound!
[repo]
(if (util/electron?)
(p/let [_ (persist-db/<open-and-fetch-schema repo {:sync-download-graph? true})
_ (<sync-auth-state-to-db-worker!)]
nil)
(p/resolved nil)))
(defn- <ensure-user-rsa-keys-on-server!
[{:keys [server-rsa-keys-exists?]}]
(if (not= false server-rsa-keys-exists?)
(p/resolved nil)
(if @state/*db-worker
(-> (p/let [_ (<sync-auth-state-to-db-worker!)]
(state/<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys
{:ensure-server? true
:server-rsa-keys-exists? false}))
(p/catch (fn [error]
(log/error :db-sync/ensure-user-rsa-keys-failed
{:error error
:reason :server-rsa-keys-missing})
nil)))
(do
(log/warn :db-sync/ensure-user-rsa-keys-skipped
{:reason :db-worker-not-ready
:server-rsa-keys-exists? server-rsa-keys-exists?})
(p/resolved nil)))))
(defn- <wait-for-db-worker-ready!
[]
(if @state/*db-worker
(p/resolved true)
(let [ready (p/deferred)
watch-key (keyword "frontend.handler.db-based.sync"
(str "wait-db-worker-ready-" (random-uuid)))]
(add-watch state/*db-worker watch-key
(fn [_ _ _ worker]
(when worker
(remove-watch state/*db-worker watch-key)
(p/resolve! ready true))))
;; If worker becomes ready between the initial check and add-watch.
(when @state/*db-worker
(remove-watch state/*db-worker watch-key)
(p/resolve! ready true))
ready)))
(defn <rtc-stop!
[]
(if @state/*db-worker
(do
(log/info :db-sync/stop true)
(state/<invoke-db-worker :thread-api/db-sync-stop))
(do
(log/info :db-sync/stop-skipped {:reason :db-worker-not-ready})
(p/resolved nil))))
(defn- sync-app-state-payload
[]
(let [payload (select-keys (state/get-state) [:git/current-repo :config
:auth/id-token :auth/access-token :auth/refresh-token
:auth/oauth-token-url :auth/oauth-domain :auth/oauth-client-id
:user/info])]
(cond-> (if (nil? (:git/current-repo payload))
(dissoc payload :git/current-repo)
payload)
(seq config/OAUTH-DOMAIN)
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
(seq config/COGNITO-CLIENT-ID)
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID))))
(defn- <sync-auth-state-to-db-worker!
[]
(p/let [_ (user-handler/<ensure-id&access-token!)
payload (sync-app-state-payload)]
(state/<invoke-db-worker :thread-api/sync-app-state payload)))
(defn <rtc-start!
[repo & {:keys [_stop-before-start?] :as _opts}]
(p/let [_ (<wait-for-db-worker-ready!)]
(if (should-start-rtc? repo)
(do
(log/info :db-sync/start {:repo repo})
(p/let [_ (<sync-auth-state-to-db-worker!)]
(state/<invoke-db-worker :thread-api/db-sync-start repo)))
(do
(log/info :db-sync/skip-start {:repo repo :reason :graph-not-in-remote-list
:remote-graphs-loading? (:rtc/loading-graphs? (state/get-state))})
(<rtc-stop!)))))
(defonce ^:private debounced-update-presence
(util/debounce
(fn [editing-block-uuid]
(state/<invoke-db-worker :thread-api/db-sync-update-presence editing-block-uuid))
1000))
(defn <rtc-update-presence!
[editing-block-uuid]
(debounced-update-presence editing-block-uuid))
(defn <rtc-get-users-info
([] (<rtc-get-users-info false))
([force?]
(let [repo (state/get-current-repo)]
(p/let [graph-uuid (<get-rtc-graph-uuid repo)]
(when graph-uuid
(let [base (http-base)
users-info (state/get-state :rtc/users-info)
cached-users (get users-info repo)]
(cond
(and (not force?) (contains? users-info repo))
(p/resolved cached-users)
base
(p/let [_ (user-handler/<ensure-id&access-token!)
resp (fetch-json (str base "/graphs/" graph-uuid "/members")
{:method "GET"}
{:response-schema :graph-members/list})
members (:members resp)
users (mapv (fn [{:keys [user-id role email username]}]
(let [name (or username email user-id)
user-type (some-> role keyword)]
(cond-> {:user/uuid user-id
:user/name name
:graph<->user/user-type user-type}
(string? email) (assoc :user/email email))))
members)]
(state/set-state! :rtc/users-info users :nested-path repo)
users)
:else
(p/resolved nil))))))))
(defn <rtc-create-graph!
([repo]
(<rtc-create-graph! repo true true))
([repo graph-e2ee?]
(<rtc-create-graph! repo graph-e2ee? true))
([repo graph-e2ee? graph-ready-for-use?]
(state/<invoke-db-worker :thread-api/db-sync-create-remote-graph
repo graph-e2ee? graph-ready-for-use?)))
(defn <rtc-delete-graph!
[graph-uuid _schema-version]
(let [base (http-base)]
(if (and graph-uuid base)
(p/let [_ (user-handler/<ensure-id&access-token!)]
(fetch-json (str base "/graphs/" graph-uuid)
{:method "DELETE"}
{:response-schema :graphs/delete}))
(p/rejected (ex-info "db-sync missing graph id"
{:type :db-sync/invalid-graph
:graph-uuid graph-uuid
:base base})))))
(defn <rtc-download-graph!
([graph-name graph-uuid]
(<rtc-download-graph! graph-name graph-uuid true))
([graph-name graph-uuid graph-e2ee?]
(if-let [operation (active-graph-operation)]
(reject-graph-operation-in-progress :download operation)
(do
(state/set-state! :rtc/downloading-graph-uuid graph-uuid)
(state/pub-event!
[:rtc/log {:type :rtc.log/download
:sub-type :download-progress
:graph-uuid graph-uuid
:message "Preparing graph snapshot download"}])
(let [graph-e2ee? (normalize-graph-e2ee? graph-e2ee?)
base (http-base)]
(-> (if (and graph-uuid base)
(p/let [_ (user-handler/<ensure-id&access-token!)
graph (str config/db-version-prefix graph-name)
_ (<ensure-download-runtime-bound! graph)
_ (state/<invoke-db-worker :thread-api/db-sync-download-graph-by-id
graph graph-uuid graph-e2ee?)
_ (when (util/electron?)
(state/<invoke-db-worker :thread-api/db-sync-download-missing-assets
graph graph-uuid))]
true)
(p/rejected (ex-info "db-sync missing graph info"
{:type :db-sync/invalid-graph
:graph-uuid graph-uuid
:base base})))
(p/catch (fn [error]
(throw error)))
(p/finally
(fn []
(state/set-state! :rtc/downloading-graph-uuid nil)))))))))
(defn <get-remote-graphs
[]
(let [base (http-base)]
(if-not base
(p/resolved [])
(-> (p/let [_ (state/set-state! :rtc/loading-graphs? true)
_ (user-handler/<ensure-id&access-token!)
resp (fetch-json (str base "/graphs")
{:method "GET"}
{:response-schema :graphs/list})
_ (<ensure-user-rsa-keys-on-server! {:server-rsa-keys-exists?
(:user-rsa-keys-exists? resp)})
graphs (:graphs resp)
result (mapv (fn [graph]
(let [graph-e2ee? (if (contains? graph :graph-e2ee?)
(normalize-graph-e2ee? (:graph-e2ee? graph))
true)
graph-ready-for-use? (not= false (:graph-ready-for-use? graph))]
(merge
{:url (str config/db-version-prefix (:graph-name graph))
:GraphName (:graph-name graph)
:GraphSchemaVersion (:schema-version graph)
:GraphUUID (:graph-id graph)
:rtc-graph? true
:graph-e2ee? graph-e2ee?
:graph-ready-for-use? graph-ready-for-use?
:graph<->user-user-type (:role graph)
:graph<->user-grant-by-user (:invited-by graph)}
(dissoc graph :graph-id :graph-name :schema-version :role :invited-by :graph-ready-for-use?))))
graphs)]
(state/set-state! :rtc/graphs result)
(repo-handler/refresh-repos!)
result)
(p/finally
(fn []
(state/set-state! :rtc/loading-graphs? false)))))))
(defn <rtc-invite-email
[graph-uuid email]
(let [base (http-base)
graph-uuid (str graph-uuid)]
(if (and base (string? graph-uuid) (string? email))
(->
(p/let [_ (<ensure-invite-auth!)
body (coerce-http-request :graph-members/create
{:email email
:role "member"})
_ (when (nil? body)
(throw (ex-info "db-sync invalid invite body"
{:graph-uuid graph-uuid
:email email})))
_ (fetch-json (str base "/graphs/" graph-uuid "/members")
{:method "POST"
:headers {"content-type" "application/json"}
:body (js/JSON.stringify (clj->js body))}
{:response-schema :graph-members/create})
repo (state/get-current-repo)
_ (<grant-graph-access-for-invite! repo graph-uuid email)
_ (<rtc-get-users-info true)]
(notification/show! (t :sync/invitation-sent) :success))
(p/catch (fn [e]
(if (= "user not found" (get-in (ex-data e) [:body :error]))
(notification/show! (t :sync/user-doesnt-exist-yet) :warning)
(do
(notification/show! (t :sync/something-wrong) :error)
(log/error :db-sync/invite-email-failed
{:error e
:graph-uuid graph-uuid
:email email}))))))
(p/rejected (ex-info "db-sync missing invite info"
{:type :db-sync/invalid-invite
:graph-uuid graph-uuid
:email email
:base base})))))
(defn <rtc-remove-member!
[graph-uuid member-id]
(let [base (http-base)
graph-uuid (some-> graph-uuid str)
member-id (some-> member-id str)]
(if (and base (string? graph-uuid) (string? member-id))
(p/let [_ (user-handler/<ensure-id&access-token!)]
(fetch-json (str base "/graphs/" graph-uuid "/members/" member-id)
{:method "DELETE"}
{:response-schema :graph-members/delete}))
(p/rejected (ex-info "db-sync missing member info"
{:type :db-sync/invalid-member
:graph-uuid graph-uuid
:member-id member-id
:base base})))))
(defn <rtc-leave-graph!
[graph-uuid]
(if-let [member-id (user-handler/user-uuid)]
(<rtc-remove-member! graph-uuid member-id)
(p/rejected (ex-info "db-sync missing user id"
{:type :db-sync/invalid-member
:graph-uuid graph-uuid}))))
(defn <rtc-upload-graph!
[repo _graph-e2ee?]
(if-let [operation (active-graph-operation)]
(reject-graph-operation-in-progress :upload operation)
(do
(state/set-state! :rtc/uploading? true)
(-> (p/let [_ (<sync-auth-state-to-db-worker!)
_ (state/<invoke-db-worker :thread-api/db-sync-upload-graph repo)
_ (<get-remote-graphs)
_ (state/set-state! :rtc/uploading? false)
_ (<rtc-start! repo)]
true)
(p/finally
(fn []
(state/set-state! :rtc/uploading? false)))))))
(defn <rtc-create-graph-and-start-sync!
[repo graph-e2ee?]
(p/let [graph-id (<rtc-create-graph! repo graph-e2ee? true)]
(when (nil? graph-id)
(throw (ex-info "graph id doesn't exist when creating remote graph" {:repo repo})))
(p/do!
(<get-remote-graphs)
(<rtc-start! repo))))

View File

@@ -1,68 +0,0 @@
(ns frontend.handler.e2ee
"rtc E2EE related fns"
(:require [electron.ipc :as ipc]
[frontend.mobile.secure-storage :as secure-storage]
[frontend.util :as util]
[promesa.core :as p]))
(def ^:private save-op :keychain/save-e2ee-password)
(def ^:private get-op :keychain/get-e2ee-password)
(def ^:private delete-op :keychain/delete-e2ee-password)
(defn- <keychain-save!
[key encrypted-text]
(cond
(util/electron?)
(ipc/ipc save-op key encrypted-text)
(util/capacitor?)
(secure-storage/<set-item! key encrypted-text)
:else
(p/resolved nil)))
(defn- <keychain-get
[key]
(cond
(util/electron?)
(ipc/ipc get-op key)
(util/capacitor?)
(secure-storage/<get-item key)
:else
(p/resolved nil)))
(defn- <keychain-delete!
[key]
(cond
(util/electron?)
(ipc/ipc delete-op key)
(util/capacitor?)
(secure-storage/<remove-item! key)
:else
(p/resolved nil)))
(defn native-storage-supported?
[]
(or (util/electron?) (util/capacitor?)))
(defn <native-save-secret!
[key encrypted-text]
(if (native-storage-supported?)
(<keychain-save! key encrypted-text)
(p/resolved nil)))
(defn <native-get-secret
[key]
(if (native-storage-supported?)
(<keychain-get key)
(p/resolved nil)))
(defn <native-delete-secret!
[key]
(if (native-storage-supported?)
(<keychain-delete! key)
(p/resolved nil)))

View File

@@ -8,7 +8,6 @@
[cljs-time.core :as t]
[clojure.string :as string]
[frontend.commands :as commands]
[frontend.components.rtc.download-progress :as download-progress]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
@@ -20,10 +19,7 @@
[frontend.handler.comments :as comments-handler]
[frontend.handler.common.page :as page-common-handler]
[frontend.handler.db-based.property :as db-property-handler]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.events.rtc-error :as rtc-error]
[frontend.handler.export :as export]
[frontend.handler.graph :as graph-handler]
[frontend.handler.notification :as notification]
@@ -34,7 +30,6 @@
[frontend.handler.route :as route-handler]
[frontend.handler.shell :as shell-handler]
[frontend.handler.ui :as ui-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.util :as mobile-util]
[frontend.modules.instrumentation.posthog :as posthog]
[frontend.modules.outliner.pipeline :as pipeline]
@@ -283,7 +278,6 @@
(when graph
(schedule-search-index-build! graph))
(export/auto-db-backup! graph)
(rtc-flows/trigger-rtc-start graph)
(fsrs/update-due-cards-count)
nil)
@@ -414,69 +408,6 @@
(<get-upsert-type-block repo (:block/uuid db-block))))]
(js/setTimeout #(editor-handler/edit-block! converted-block :max) 100)))))
(defn- editing-users-by-block
[online-users current-user-uuid]
(reduce (fn [result {:user/keys [editing-block-uuid uuid] :as user}]
(if (and (string? editing-block-uuid)
(not= uuid current-user-uuid)
(not (contains? result editing-block-uuid)))
(assoc result editing-block-uuid user)
result))
{}
online-users))
(defn- sync-editing-users-by-block!
[online-users]
(let [current-user-uuid (user-handler/user-uuid)
old-users-by-block (or (state/get-state :rtc/editing-users-by-block) {})
new-users-by-block (editing-users-by-block online-users current-user-uuid)
affected-block-ids (into (set (keys old-users-by-block))
(keys new-users-by-block))]
(doseq [block-id affected-block-ids
:let [old-user (get old-users-by-block block-id)
new-user (get new-users-by-block block-id)]
:when (not= old-user new-user)]
(state/set-state! :rtc/editing-users-by-block new-user :nested-path block-id))))
(defevent! :rtc/sync-state [[_ state]]
(when (contains? state :online-users)
(sync-editing-users-by-block! (:online-users state)))
(doseq [[k value] state]
(state/set-state! :rtc/state value :nested-path k)))
(defevent! :rtc/presence-update [[_ {:keys [editing-block-uuid]}]]
(rtc-handler/<rtc-update-presence! editing-block-uuid))
(defevent! :rtc/log [[_ data]]
(state/set-state! :rtc/log data))
(defevent! :rtc/remote-graph-gone [_]
(p/do!
(notification/show! (t :graph/removed-from-sync) :warning false)
(rtc-handler/<get-remote-graphs)))
(defevent! :rtc/download-remote-graph [[_ graph-name graph-uuid graph-schema-version graph-e2ee?]]
(assert (= (:major (db-schema/parse-schema-version db-schema/version))
(:major (db-schema/parse-schema-version graph-schema-version)))
{:app db-schema/version
:remote-graph graph-schema-version})
(->
(p/do!
(when (util/mobile?)
(download-progress/show! graph-name))
(rtc-handler/<rtc-download-graph! graph-name graph-uuid graph-e2ee?)
(rtc-handler/<get-remote-graphs)
(state/pub-event! [:graph/switch (str config/db-version-prefix graph-name) {:rtc-download? true}])
(when (util/mobile?)
(download-progress/hide!)))
(p/catch (fn [e]
(println "RTC download graph failed, error:")
(log/error :rtc-download-graph-failed e)
(when (util/mobile?)
(download-progress/hide!))
(when (rtc-error/e2ee-decrypt-failed? e)
(notification/show! (t :encryption/wrong-password) :error false))))))
;; db-worker -> UI
(defevent! :db/sync-changes [[_ data]]
(pipeline/invoke-hooks data)

View File

@@ -1,108 +0,0 @@
(ns frontend.handler.events.rtc
"RTC events"
(:require-macros [frontend.handler.events.macros :refer [defevent!]])
(:require [frontend.common.crypt :as crypt]
[frontend.components.e2ee :as e2ee]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.flows :as flows]
[frontend.handler.events :as events]
[frontend.handler.notification :as notification]
[frontend.state :as state]
[lambdaisland.glogi :as log]
[logseq.shui.ui :as shui]
[promesa.core :as p]))
(defn- rtc-collaborators-dialog?
[]
(= :rtc-collaborators (state/get-dialog-id)))
(defn- close-e2ee-blocking-ui!
[]
(when-not (rtc-collaborators-dialog?)
(shui/dialog-close-all!))
(shui/popup-hide! :download-rtc-graph))
(defevent! :rtc/decrypt-user-e2ee-private-key [[_ encrypted-private-key]]
(let [private-key-promise (p/deferred)
refresh-token (str (state/get-auth-refresh-token))]
(close-e2ee-blocking-ui!)
(->
(p/let [{:keys [password]} (state/<invoke-db-worker :thread-api/get-e2ee-password refresh-token)
private-key (crypt/<decrypt-private-key password encrypted-private-key)]
(p/resolve! private-key-promise private-key))
(p/catch
(fn [error]
(log/error :read-e2ee-password-failed error)
(shui/dialog-open!
#(e2ee/e2ee-password-to-decrypt-private-key encrypted-private-key private-key-promise)
{:auto-width? true
:content-props {:onPointerDownOutside #(.preventDefault %)}
:on-close (fn []
(p/reject! private-key-promise (ex-info "input E2EE password cancelled" {}))
(shui/dialog-close!))}))))
private-key-promise))
(defevent! :rtc/request-e2ee-password [[_ {:keys [reason]}]]
(let [password-promise (p/deferred)
decrypt-reason? (= :decrypt-user-rsa-private-key reason)]
(close-e2ee-blocking-ui!)
(shui/dialog-open!
#(if decrypt-reason?
(e2ee/e2ee-request-password password-promise)
(e2ee/e2ee-request-new-password password-promise))
{:auto-width? true
:content-props {:onPointerDownOutside #(.preventDefault %)}
:on-close (fn []
(p/reject! password-promise (ex-info "cancelled" {}))
(shui/dialog-close!))})
password-promise))
(defevent! :rtc/storage-exceed-limit [[_]]
(notification/show! (t :sync/storage-exceed-limit) :warning false))
(defevent! :rtc/graph-count-exceed-limit [[_]]
(notification/show! (t :sync/graph-count-exceed-limit) :warning false))
(defonce ^:private *sync-app-state-cancel! (atom nil))
(defn- sync-app-state!
[]
(when-let [cancel! @*sync-app-state-cancel!]
(cancel!))
(let [state-atoms {:git/current-repo flows/current-repo
:config (flows/sub-atom [:config])
:auth/id-token (flows/sub-atom [:auth/id-token])
:auth/access-token (flows/sub-atom [:auth/access-token])
:auth/refresh-token (flows/sub-atom [:auth/refresh-token])
:auth/oauth-token-url (flows/sub-atom [:auth/oauth-token-url])
:auth/oauth-domain (flows/sub-atom [:auth/oauth-domain])
:auth/oauth-client-id (flows/sub-atom [:auth/oauth-client-id])
:user/info (flows/sub-atom [:user/info])}
<init-sync-done? (p/deferred)
last-state (atom ::not-set)
app-state (fn []
(cond-> (update-vals state-atoms deref)
(seq config/OAUTH-DOMAIN)
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
(seq config/COGNITO-CLIENT-ID)
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID)))
sync! (fn []
(let [m (app-state)]
(when-not (= @last-state m)
(reset! last-state m)
(p/let [_ (when (:git/current-repo m)
(state/<invoke-db-worker :thread-api/sync-app-state m))]
(p/resolve! <init-sync-done?)))))]
(doseq [[k atom'] state-atoms]
(add-watch atom' [::sync-app-state k] (fn [_ _ _ _] (sync!))))
(sync!)
(reset! *sync-app-state-cancel!
(fn []
(doseq [[k atom'] state-atoms]
(remove-watch atom' [::sync-app-state k]))))
<init-sync-done?))
(defevent! :rtc/sync-app-state [[_]]
(sync-app-state!))

View File

@@ -1,38 +0,0 @@
(ns frontend.handler.events.rtc-error
"RTC event error helpers.")
(def ^:private e2ee-decrypt-error-codes
#{:db-sync/invalid-e2ee-password})
(defn- throwable-message
[error]
(or (ex-message error)
(when (instance? js/Error error)
(.-message error))
(some-> error str)))
(defn- error-texts
[error]
(when error
(let [data (ex-data error)]
(concat
[(throwable-message error)
(:error-message data)
(:error-cause data)]
(error-texts (:error data))
(error-texts (ex-cause error))))))
(defn- error-codes
[error]
(when error
(let [data (ex-data error)]
(concat
[(:code data)]
(error-codes (:error data))
(error-codes (ex-cause error))))))
(defn e2ee-decrypt-failed?
[error]
(boolean
(or (some e2ee-decrypt-error-codes (error-codes error))
(some #{"decrypt-aes-key"} (error-texts error)))))

View File

@@ -2,7 +2,6 @@
"UI events"
(:require-macros [frontend.handler.events.macros :refer [defevent!]])
(:require [clojure.core.async :as async]
[clojure.core.async.interop :refer [p->c]]
[frontend.components.assets :as assets]
[frontend.components.cmdk.core :as cmdk]
[frontend.components.icon :as icon-component]
@@ -25,8 +24,6 @@
[frontend.extensions.pdf.assets :as pdf-assets]
[frontend.fs :as fs]
[frontend.handler.assets :as assets-handler]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.events :as events]
[frontend.handler.notification :as notification]
@@ -50,15 +47,6 @@
[promesa.core :as p]
[logseq.shui.dialog.core :as shui-dialog]))
(defn- <asset-file-ready?
[asset file-name]
(if (or config/publishing?
(seq (:logseq.property.asset/external-url asset)))
(p/resolved true)
(fs/file-exists?
(config/get-repo-dir (state/get-current-repo))
(path/path-join common-config/local-assets-dir file-name))))
(defevent! :go/search [_]
(when-not (editor-handler/dialog-exists? :ls-dialog-cmdk)
(shui/dialog-open!
@@ -100,11 +88,6 @@
(plugin/user-proxy-settings-container agent-opts)
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
(defevent! :go/sync-server-settings [[_]]
(shui/dialog-open!
(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)
@@ -456,28 +439,21 @@
:h (or (:logseq.property.asset/height asset) 800)}])))
video?
(p/let [file-ready? (<asset-file-ready? asset file-name)
requested? (assets-handler/maybe-request-remote-asset-download!
(state/get-current-repo)
asset
file-ready?)]
(if requested?
(notification/show! (t :asset/downloading))
(p/let [url (assets-handler/<make-asset-url rel-path)]
(when url
(shui/dialog-open!
(fn []
[:div.flex.flex-col.gap-2.items-center
[:div.font-medium.text-sm.self-start.truncate.max-w-full
(:block/title asset)]
[:video.rounded.max-w-full
{:src url
:controls true
:autoPlay true
:style {:max-height "80vh"}}]])
{:id :asset-video-preview
:auto-width? true
:center? true})))))
(p/let [url (assets-handler/<make-asset-url rel-path)]
(when url
(shui/dialog-open!
(fn []
[:div.flex.flex-col.gap-2.items-center
[:div.font-medium.text-sm.self-start.truncate.max-w-full
(:block/title asset)]
[:video.rounded.max-w-full
{:src url
:controls true
:autoPlay true
:style {:max-height "80vh"}}]])
{:id :asset-video-preview
:auto-width? true
:center? true})))
pdf?
(p/let [url (assets-handler/<make-asset-url rel-path)]
@@ -487,21 +463,6 @@
:else
(route-handler/redirect-to-page! (:block/uuid asset))))))
(defn ensure-user-rsa-keys-if-possible!
[]
(if @state/*db-worker
(-> (p/do!
(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)})
(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)
nil)))
(p/resolved nil)))
(defevent! :user/fetch-info-and-graphs [[_]]
(state/set-state! [:ui/loading? :login] false)
(async/go
@@ -513,18 +474,8 @@
(do
(state/set-user-info! result)
(when-let [uid (user-handler/user-uuid)]
(sentry-event/set-user! uid)
(ensure-user-rsa-keys-if-possible!))
(let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)
fetch-graphs? (and (user-handler/logged-in?)
(or (= status :welcome)
(user-handler/rtc-group?)))]
(when fetch-graphs?
(async/<! (p->c (rtc-handler/<get-remote-graphs)))
(repo-handler/refresh-repos!)
(when-let [current-repo (state/get-current-repo)]
(when (some #(= current-repo (:url %)) (state/get-rtc-graphs))
(rtc-flows/trigger-rtc-start current-repo))))))))))
(sentry-event/set-user! uid))
(repo-handler/refresh-repos!))))))
(defevent! :dialog/show-block [[_ block option]]
(shui/dialog-open!

View File

@@ -21,7 +21,6 @@
[frontend.util :as util]
[frontend.util.text :as text-util]
[logseq.common.version :as build-version]
[logseq.db.frontend.schema :as db-schema]
[promesa.core :as p]))
;; Project settings should be checked in two situations:
@@ -103,43 +102,11 @@
:root (config/get-local-dir graph-name)
:nfs? true}))))))
(defn combine-local-&-remote-graphs
[local-repos remote-repos]
(when-let [repos' (seq (concat (map (fn [{:keys [sync-meta metadata] :as repo}]
(let [graph-id (some-> (or (:kv/value metadata)
(second sync-meta)) str)]
(if (and graph-id (nil? (:GraphUUID repo)))
(assoc repo :GraphUUID graph-id)
repo)))
local-repos)
(some->> remote-repos
(map #(assoc % :remote? true)))))]
(let [app-major-schema-version (str (:major (db-schema/parse-schema-version db-schema/version)))
repos' (group-by :url repos')
repos'' (mapcat (fn [[k vs]]
(if (some? k)
(let [remote-repos (filter :remote? vs)
version-matched-remote-repo
(first
(filter
#(= app-major-schema-version (:GraphSchemaVersion %))
remote-repos))]
[(merge (first vs) (second vs) version-matched-remote-repo)])
vs))
repos')]
(sort-by (fn [repo]
(let [graph-name (or (:GraphName repo)
(last (string/split (:root repo) #"/")))]
[(:remote? repo) (string/lower-case graph-name)])) repos''))))
(defn refresh-repos!
[]
(p/let [repos (get-repos)
repos' (combine-local-&-remote-graphs
repos
(state/get-rtc-graphs))]
(state/set-repos! repos')
repos'))
(p/let [repos (get-repos)]
(state/set-repos! repos)
repos))
(defn graph-ready!
;; FIXME: Call electron that the graph is loaded, an ugly implementation for redirect to page when graph is restored

View File

@@ -5,7 +5,6 @@
[cljs-time.coerce :as tc]
[cljs-time.core :as t]
[cljs.core.async :as async :refer [<! go timeout]]
[clojure.set :as set]
[clojure.string :as string]
[electron.ipc :as ipc]
[frontend.common.thread-api :refer [def-thread-api]]
@@ -271,14 +270,6 @@
(auto-fill-refresh-token-from-cognito!)
(state/pub-event! [:user/fetch-info-and-graphs]))
(defn- clear-e2ee-password!
[]
(when @state/*db-worker
(-> (state/<invoke-db-worker :thread-api/clear-e2ee-password)
(p/catch (fn [error]
(js/console.warn :clear-e2ee-password-failed error)
nil)))))
(defn ^:export login-with-username-password-e2e
[username' password client-id client-secret]
(let [text-encoder (new js/TextEncoder)
@@ -306,7 +297,6 @@
{:id-token id-token :access-token access-token :refresh-token refresh-token})))))
(defn logout []
(clear-e2ee-password!)
(clear-tokens)
(.clear js/localStorage)
(state/clear-user-info!)
@@ -354,13 +344,6 @@
;;; user groups
(defn rtc-group?
[]
(boolean (or
config/dev?
(some? (config/get-custom-sync-server-url))
(seq (set/intersection (state/user-groups) #{"team" "rtc_2025_07_10"})))))
(defn alpha-user?
[]
(or config/dev?
@@ -375,20 +358,6 @@
[]
(or (alpha-user?) (beta-user?)))
(defn get-user-type
[repo]
(-> (some #(when (= repo (:url %)) %) (:rtc/graphs (state/get-state)))
:graph<->user-user-type))
(defn manager?
[repo]
(= (get-user-type repo) "manager"))
;; TODO: Remove if still unused
#_(defn member?
[repo]
(= (get-user-type repo) "member"))
(defn <upload-user-avatar
[avatar-str]
(when-let [token (state/get-auth-id-token)]

View File

@@ -3,14 +3,11 @@
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[frontend.common.thread-api :as thread-api]
[frontend.common.crypt :as crypt]
[frontend.context.i18n :as i18n]
[frontend.handler.e2ee :as e2ee-handler]
[frontend.handler.notification :as notification]
[frontend.state :as state]
[lambdaisland.glogi :as log]
[logseq.db :as ldb]
[promesa.core :as p]))
[logseq.db :as ldb]))
(defmulti handle identity)
@@ -41,16 +38,6 @@
(defmethod handle :add-repo [_ _worker data]
(state/add-repo! {:url (:repo data)}))
(defmethod handle :rtc-sync-state [_ _worker data]
(let [state data]
(state/pub-event! [:rtc/sync-state state])))
(defmethod handle :rtc-asset-upload-download-progress [_ _worker {:keys [repo asset-id progress]}]
(when (and (seq repo) (seq asset-id) (map? progress))
(state/update-state!
:rtc/asset-upload-download-progress
(fn [m] (assoc-in m [repo asset-id] progress)))))
(defmethod handle :asset-file-write-finish [_ _worker {:keys [repo asset-id ts]}]
(when (and (seq repo) (seq asset-id))
(state/update-state!
@@ -67,13 +54,6 @@
(defmethod handle :resync [_ _worker data]
(state/pub-event! [:db/resync data]))
(defmethod handle :sync-conflicts-updated [_ _worker {:keys [repo block-uuid conflicts]}]
(when (and (seq repo) block-uuid)
(state/set-sync-block-conflicts! repo block-uuid conflicts)))
(defmethod handle :rtc-log [_ _worker log]
(state/pub-event! [:rtc/log log]))
(defmethod handle :export-current-db [_]
(state/pub-event! [:db/export-sqlite]))
@@ -90,103 +70,6 @@
(defmethod handle :notify-existing-file [_ _worker data]
(state/pub-event! [:graph/notify-existing-file data]))
(defmethod handle :remote-graph-gone []
(state/pub-event! [:rtc/remote-graph-gone]))
(defn- <invoke-worker-thread-api
[wrapped-worker qkw & args]
(apply wrapped-worker qkw args))
(defn- ui-request-error->payload
[error]
(let [data (ex-data error)]
(merge {:code (or (:code data) :ui-request-failed)
:message (or (ex-message error) (str error))}
(when (seq data)
{:data data}))))
(defn- <db-worker-ui-action
[action payload]
(case action
:request-e2ee-password
(p/let [password-promise (state/pub-event! [:rtc/request-e2ee-password payload])
password password-promise]
{:password password})
:decrypt-user-e2ee-private-key
(let [encrypted-private-key (:encrypted-private-key payload)]
(p/let [private-key-promise (state/pub-event! [:rtc/decrypt-user-e2ee-private-key encrypted-private-key])
private-key private-key-promise]
(crypt/<export-private-key private-key)))
:native-save-e2ee-password
(let [{:keys [key encrypted-text]} payload]
(if-not (and (string? key) (string? encrypted-text))
(p/rejected (ex-info "invalid native-save-e2ee-password payload"
{:code :invalid-ui-action-payload
:action action
:payload payload}))
(if-not (e2ee-handler/native-storage-supported?)
(p/resolved {:supported? false})
(p/let [_ (e2ee-handler/<native-save-secret! key encrypted-text)]
{:supported? true}))))
:native-get-e2ee-password
(let [{:keys [key]} payload]
(if-not (string? key)
(p/rejected (ex-info "invalid native-get-e2ee-password payload"
{:code :invalid-ui-action-payload
:action action
:payload payload}))
(if-not (e2ee-handler/native-storage-supported?)
(p/resolved {:supported? false})
(p/let [encrypted-text (e2ee-handler/<native-get-secret key)]
{:supported? true
:encrypted-text encrypted-text}))))
:native-delete-e2ee-password
(let [{:keys [key]} payload]
(if-not (string? key)
(p/rejected (ex-info "invalid native-delete-e2ee-password payload"
{:code :invalid-ui-action-payload
:action action
:payload payload}))
(if-not (e2ee-handler/native-storage-supported?)
(p/resolved {:supported? false})
(p/let [_ (e2ee-handler/<native-delete-secret! key)]
{:supported? true}))))
(p/rejected (ex-info "unsupported db-worker ui action"
{:code :unsupported-ui-action
:action action
:payload payload}))))
(defn- <handle-db-worker-ui-request
[wrapped-worker {:keys [request-id action payload]}]
(if (and (string? request-id) (keyword? action))
(-> (<db-worker-ui-action action payload)
(p/then (fn [result]
(<invoke-worker-thread-api wrapped-worker
:thread-api/resolve-ui-request
request-id
result)))
(p/catch (fn [error]
(log/warn :db-worker/ui-request-failed
{:request-id request-id
:action action
:error error})
(<invoke-worker-thread-api wrapped-worker
:thread-api/reject-ui-request
request-id
(ui-request-error->payload error)))))
(log/error :db-worker/ui-request-invalid
{:request-id request-id
:action action
:payload payload})))
(defmethod handle :db-worker/ui-request [_ wrapped-worker data]
(<handle-db-worker-ui-request wrapped-worker data))
(defmethod handle :default [_ _worker data]
(prn :debug "Worker data not handled: " data))

View File

@@ -523,22 +523,10 @@
:desc "(Dev) Validate current graph"
:inactive (not (state/developer-mode?))
:fn :frontend.handler.common.developer/validate-db}
:dev/recompute-checksum {:binding []
:desc "(Dev) Recompute graph checksum"
:inactive (not (state/developer-mode?))
:fn :frontend.handler.common.developer/recompute-checksum-diagnostics}
:dev/export-client-ops-sqlite {:binding []
:desc "(Dev) Export client ops sqlite"
:inactive (not (state/developer-mode?))
:fn :frontend.handler.common.developer/export-client-ops-sqlite}
:dev/rtc-stop {:binding []
:desc "(Dev) RTC Stop"
:inactive (not (state/developer-mode?))
:fn :frontend.handler.common.developer/rtc-stop}
:dev/rtc-start {:binding []
:desc "(Dev) RTC Start"
:inactive (not (state/developer-mode?))
:fn :frontend.handler.common.developer/rtc-start}})
:fn :frontend.handler.common.developer/export-client-ops-sqlite}})
(let [keyboard-commands
{::commands (->> (keys all-built-in-keyboard-shortcuts)
@@ -746,11 +734,8 @@
:dev/show-page-data
:dev/replace-graph-with-db-file
:dev/validate-db
:dev/recompute-checksum
:dev/export-client-ops-sqlite
:dev/gc-graph
:dev/rtc-stop
:dev/rtc-start
:ui/customize-appearance])
(with-meta {:before m/enable-when-not-editing-mode!}))
@@ -912,11 +897,8 @@
:dev/show-page-data
:dev/replace-graph-with-db-file
:dev/validate-db
:dev/recompute-checksum
:dev/export-client-ops-sqlite
:dev/gc-graph
:dev/rtc-stop
:dev/rtc-start
:ui/clear-all-notifications]
:shortcut.category/plugins

View File

@@ -220,12 +220,6 @@
(ipc/ipc "releaseDbWorkerRuntime" repo)
(p/resolved nil)))
(defn- current-db-sync-config
[]
{:enabled? true
:ws-url (config/db-sync-ws-url)
:http-base (config/db-sync-http-base)})
(defn- <sync-markdown-mirror-setting!
[repo]
(state/<invoke-db-worker :thread-api/markdown-mirror-set-enabled
@@ -327,9 +321,7 @@
:repo repo :error e)))))
(do
(set-remote-runtime! repo client session-id)
(p/let [_ (state/<invoke-db-worker :thread-api/set-db-sync-config
(current-db-sync-config))
_ (<sync-markdown-mirror-setting! repo)]
(p/let [_ (<sync-markdown-mirror-setting! repo)]
(sync-markdown-mirror-setting-watch!)
nil)
(ldb/register-transact-fn!

View File

@@ -174,9 +174,6 @@
(defn stop-db-worker!
[]
(when @state/*db-worker
(-> (state/<invoke-db-worker :thread-api/cancel-ui-requests {:reason :stop-db-worker})
(p/catch (constantly nil))))
(when-let [^js worker @state/*db-worker-thread]
(set! (.-onmessage worker) nil)
(.terminate worker))
@@ -222,11 +219,6 @@
(worker-handler/handle-message! worker wrapped-worker)
(reset! state/*db-worker wrapped-worker)
(-> (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)})
_ (state/pub-event! [:rtc/sync-app-state])
_ (log/info "init worker spent" (str (- (util/time-ms) t1) "ms"))
_ (sync-ui-state!)
_ (ask-persist-permission!)

View File

@@ -340,18 +340,6 @@
:auth/oauth-client-id nil
:auth/current-login-user nil
;; graph-uuid -> ...
:rtc/state {}
:rtc/editing-users-by-block {}
:rtc/loading-graphs? nil
;; only latest rtc-log stored here, derive a log stream from state when needed.
:rtc/log nil
:rtc/uploading? false
:rtc/downloading-graph-uuid nil
:rtc/graphs []
:rtc/online-info {}
:rtc/asset-upload-download-progress {}
:rtc/users-info {}
:sync/block-conflicts {}
:user/info {:UserGroups (storage/get :user-groups)}
@@ -360,7 +348,6 @@
:ui/loading? {}
:ui/container-id 0
:ui/cached-key->container-id {}
:feature/enable-sync? (storage/get :logseq-sync-enabled)
:ui/find-in-page nil
:comments/inline-thread nil
@@ -879,23 +866,6 @@ should be done through this fn in order to get global config and config defaults
[]
(:git/current-repo (rfx/snapshot)))
(defn get-rtc-graphs
[]
(:rtc/graphs (rfx/snapshot)))
;; TODO: rtc version
(comment
(defn get-remote-graph-usage
[graphs]
(->> graphs
(map #(hash-map :uuid (:GraphUUID %)
:name (:GraphName %)
:used-gbs (/ (:GraphStorageUsage %) 1024 1024 1024)
:limit-gbs (/ (:GraphStorageLimit %) 1024 1024 1024)
:used-percent (/ (:GraphStorageUsage %) (:GraphStorageLimit %) 0.01)))
(map #(assoc % :free-gbs (- (:limit-gbs %) (:used-gbs %))))
(vec))))
(defn get-repos
[]
(get-in (rfx/snapshot) [:me :repos]))
@@ -1312,27 +1282,22 @@ should be done through this fn in order to get global config and config defaults
(defn clear-edit!
[& {:keys [clear-editing-block?]
:or {clear-editing-block? true}}]
(let [online-users (some-> (get-state :rtc/state) :online-users)]
(swap-state!
(fn [db]
(cond-> (assoc db
:editor/action nil
:editor/args nil
:editor/start-pos nil
:editor/last-saved-cursor {}
:editor/cursor-range nil
:ui/select-query-cache {}
:editor/block-refs #{}
:editor/action-data nil
:view/selected-blocks nil)
clear-editing-block?
(assoc :editor/editing? nil
:editor/block nil
:editor/content {}))))
(when (and clear-editing-block?
(coll? online-users)
(> (count online-users) 1))
(pub-event! [:rtc/presence-update {:editing-block-uuid nil}])))
(swap-state!
(fn [db]
(cond-> (assoc db
:editor/action nil
:editor/args nil
:editor/start-pos nil
:editor/last-saved-cursor {}
:editor/cursor-range nil
:ui/select-query-cache {}
:editor/block-refs #{}
:editor/action-data nil
:view/selected-blocks nil)
clear-editing-block?
(assoc :editor/editing? nil
:editor/block nil
:editor/content {}))))
nil)
(defn set-editor-last-pos!
@@ -1876,9 +1841,6 @@ should be done through this fn in order to get global config and config defaults
(assoc-in [:editor/content block-id] content))
native-platform?
(assoc :mobile/show-action-bar? false))))
(let [online-users (some-> (get-state :rtc/state) :online-users)]
(when (and (coll? online-users) (> (count online-users) 1))
(pub-event! [:rtc/presence-update {:editing-block-uuid (str block-id)}])))
(when (= :code (:logseq.property.node/display-type block))
(pub-event! [:editor/focus-code-editor block block-element]))
(when-let [input (gdom/getElement edit-input-id)]

View File

@@ -8,26 +8,3 @@
(get (state/get-state) (keyword path))
(state/get-state))
(bean/->js)))
(defn- current-repo-or-throw
[]
(if-let [repo (state/get-current-repo)]
repo
(throw (ex-info "No current repo" {:type :debug/no-current-repo}))))
(defn ^:export syncStopUpload
[]
(state/<invoke-db-worker :thread-api/db-sync-stop-upload (current-repo-or-throw)))
(defn ^:export syncResumeUpload
[]
(state/<invoke-db-worker :thread-api/db-sync-resume-upload (current-repo-or-throw)))
(defn ^:export syncUploadStopped
[]
(state/<invoke-db-worker :thread-api/db-sync-upload-stopped? (current-repo-or-throw)))
;; Snake_case aliases for consistency with existing debug exports.
(defn ^:export sync_stop_upload [] (syncStopUpload))
(defn ^:export sync_resume_upload [] (syncResumeUpload))
(defn ^:export sync_upload_stopped [] (syncUploadStopped))

View File

@@ -3,16 +3,13 @@
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[frontend.context.i18n :refer [t]]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.notification :as notification]
[frontend.handler.repo :as repo-handler]
[frontend.handler.route :as route-handler]
[frontend.mobile.util :as mobile-util]
[frontend.state :as state]
[frontend.storage :as storage]
[frontend.util :as util]
[lambdaisland.glogi :as log]
[mobile.navigation :as mobile-nav]
[mobile.search :as mobile-search]
[mobile.state :as mobile-state]
@@ -113,47 +110,6 @@
(when (repo-handler/removable-repo? repo (state/get-repos))
(repo-handler/remove-repo! repo))))
(defn- refresh-remote-graphs!
[]
(state/set-state! :rtc/loading-graphs? true)
(-> (rtc-handler/<get-remote-graphs)
(p/finally
(fn []
(state/set-state! :rtc/loading-graphs? false)))))
(defn- delete-remote-graph!
[url graph-uuid graph-schema-version]
(state/set-state! :rtc/loading-graphs? true)
(-> (p/do!
(when (= (state/get-current-repo) url)
(rtc-handler/<rtc-stop!))
(rtc-handler/<rtc-delete-graph! graph-uuid graph-schema-version)
(rtc-handler/<get-remote-graphs))
(p/finally
(fn []
(state/set-state! :rtc/loading-graphs? false)))))
(defn- leave-remote-graph!
[url graph-uuid]
(state/set-state! :rtc/loading-graphs? true)
(-> (p/do!
(when (= (state/get-current-repo) url)
(rtc-handler/<rtc-stop!))
(rtc-handler/<rtc-leave-graph! graph-uuid))
(p/then
(fn []
(notification/show! (t :graph/left) :success)
(rtc-handler/<get-remote-graphs)))
(p/catch
(fn [e]
(notification/show! (t :graph/leave-error) :error)
(log/error :db-sync/leave-graph-failed
{:error e
:graph-uuid graph-uuid})))
(p/finally
(fn []
(state/set-state! :rtc/loading-graphs? false)))))
(defn add-graph-action-listener!
[]
(when (and (mobile-util/native-ios?) liquid-tabs)
@@ -167,42 +123,11 @@
(when-not (string/blank? url)
(state/pub-event! [:graph/switch url])))
"download"
(let [graph-name (.-graphName data)
graph-uuid (.-graphUUID data)
graph-schema-version (.-graphSchemaVersion data)]
(when (and (not (string/blank? graph-name))
(not (string/blank? graph-uuid))
(not (string/blank? graph-schema-version)))
(state/pub-event!
[:rtc/download-remote-graph
graph-name
graph-uuid
graph-schema-version
(boolean (.-graphE2ee data))])))
"refresh"
(refresh-remote-graphs!)
"deleteLocal"
(when-let [url (.-url data)]
(when-not (string/blank? url)
(delete-local-graph! url)))
"deleteRemote"
(let [url (.-url data)
graph-uuid (.-graphUUID data)
graph-schema-version (.-graphSchemaVersion data)]
(when (and (not (string/blank? graph-uuid))
(not (string/blank? graph-schema-version)))
(delete-remote-graph! url graph-uuid graph-schema-version)))
"leaveRemote"
(let [url (.-url data)
graph-uuid (.-graphUUID data)]
(when-not (string/blank? graph-uuid)
(leave-remote-graph! url graph-uuid)))
nil)))))
(defn add-keyboard-hack-listener!

View File

@@ -6,10 +6,8 @@
[frontend.components.quick-add :as quick-add]
[frontend.context.i18n :as i18n :refer [t]]
[frontend.handler.common :as common-handler]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.repo :as repo-handler]
[frontend.handler.user :as user-handler]
[frontend.extensions.fsrs :as fsrs]
[frontend.mobile.util :as mobile-util]
[frontend.rfx :as rfx]
@@ -112,8 +110,8 @@
:cancelButton (t :ui/cancel)})
(defn- native-graph-actions
[repos {:keys [url root remote? GraphUUID GraphSchemaVersion] :as graph}]
(let [graph-name (config/db-graph-name url)
[repos {:keys [root] :as graph}]
(let [graph-name (config/db-graph-name (:url graph))
warning (t :graph/delete-warning)
delete-message (fn [message] (str message "\n\n" warning))
delete-local? (repo-handler/removable-repo? graph repos)]
@@ -124,114 +122,57 @@
"deleteLocal"
(t :graph/delete-local-action)
true
(delete-message (t :graph/delete-local-confirm-desc graph-name))))
(and remote?
GraphUUID
GraphSchemaVersion
(user-handler/manager? url))
(conj
(native-graph-action
"deleteRemote"
(t :graph/delete-server-action)
true
(delete-message (t :graph/delete-server-confirm-desc graph-name))))
(and remote?
GraphUUID
(not (user-handler/manager? url)))
(conj
(native-graph-action
"leaveRemote"
(t :graph/leave-action)
true
(t :graph/leave-confirm-desc))))))
(delete-message (t :graph/delete-local-confirm-desc graph-name)))))))
(defn- native-graph-item
[repos downloading-graph-id {:keys [url root remote? graph-e2ee?
GraphName GraphSchemaVersion GraphUUID
graph-ready-for-use? created-at last-seen-at]
:as graph}]
[repos {:keys [url root created-at last-seen-at] :as graph}]
(let [display-name (text-util/get-graph-name-from-path url)
time (safe-locale-date (or last-seen-at created-at))
downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))]
time (safe-locale-date (or last-seen-at created-at))]
(when (seq display-name)
{:id (or GraphUUID url)
{:id url
:url url
:displayName display-name
:subtitle (when time (t :graph/last-opened-at-label time))
:remote (boolean remote?)
:local (boolean root)
:readyForUse (not= false graph-ready-for-use?)
:downloading (boolean downloading?)
:e2ee (boolean graph-e2ee?)
:graphName GraphName
:graphUUID GraphUUID
:graphSchemaVersion GraphSchemaVersion
:readyForUse true
:downloading false
:actions (native-graph-actions repos graph)})))
(defn- native-graph-sections
[repos remotes login? downloading-graph-id]
(let [repos (util/distinct-by :url repos)
repos (->> (if (and login? (seq remotes))
(repo-handler/combine-local-&-remote-graphs repos remotes)
repos)
[repos]
(let [repos (->> repos
(remove #(= (:url %) config/demo-repo))
(filter (fn [item] (config/db-based-graph? (:url item))))
(util/distinct-by :url))
repos (cond->>
(remove #(= (:url %) config/demo-repo) repos)
true
(filter (fn [item]
(config/db-based-graph? (:url item)))))
{remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)
{own-graphs true shared-graphs false}
(group-by (fn [graph] (= "manager" (:graph<->user-user-type graph))) remote-graphs)
section (fn [id title refreshable? show-empty? graphs]
(let [items (vec (keep #(native-graph-item repos downloading-graph-id %) graphs))]
(when (or show-empty? (seq items))
{:id id
:title title
:refreshable refreshable?
:graphs items})))]
(vec
(keep identity
[(section "local" (t :graph/local-graphs) false true local-graphs)
(section "remote" (t :graph/remote-graphs) true false own-graphs)
(section "shared" (t :graph/shared-graphs) false false shared-graphs)]))))
items (vec (keep #(native-graph-item repos %) repos))]
(if (seq items)
[{:id "local"
:title (t :graph/local-graphs)
:refreshable false
:graphs items}]
[])))
(defn- use-native-graphs-effects!
[]
(let [id-token (rfx/use-sub [:auth/id-token])
repos (rfx/use-sub [:me :repos])
remotes (rfx/use-sub [:rtc/graphs])
downloading-graph-id (rfx/use-sub [:rtc/downloading-graph-uuid])
loading-graphs? (rfx/use-sub [:rtc/loading-graphs?])
(let [repos (rfx/use-sub [:me :repos])
route-match (rfx/use-sub [:route-match])
_preferred-language (rfx/use-sub [:preferred-language])
[tab] (hooks/use-atom mobile-state/*tab)
login? (boolean id-token)
route-name (get-in route-match [:data :name])
visible? (and (= tab "graphs")
(not (contains? #{:import :export} route-name)))
sections (native-graph-sections repos remotes login? downloading-graph-id)
sections (native-graph-sections repos)
labels {:refresh (t :ui/refresh)
:preparing (t :graph/preparing)
:downloading (t :graph/downloading)}]
(hooks/use-effect!
(fn []
(when (and (mobile-util/native-ios?)
login?
(user-handler/rtc-group?))
(rtc-handler/<get-remote-graphs))
nil)
[login?])
(hooks/use-effect!
(fn []
(bottom-tabs/update-native-graphs! {:labels labels
:sections sections
:visible visible?
:refreshing (boolean loading-graphs?)})
:refreshing false})
nil)
[labels sections visible? loading-graphs?])))
[labels sections visible?])))
(hsx/defc native-graphs-bridge
[]

View File

@@ -3,7 +3,6 @@
(:require ["@capacitor/dialog" :refer [Dialog]]
[clojure.string :as string]
[frontend.components.repo :as repo]
[frontend.components.rtc.indicator :as rtc-indicator]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
[frontend.db.async :as db-async]
@@ -11,7 +10,6 @@
[frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.util :as mobile-util]
[frontend.rfx :as rfx]
[frontend.state :as state]
@@ -165,22 +163,6 @@
{:title (t :flashcard/select-cards)
:default-height false})))
(defn current-local-uploadable-graph
[rtc-graph-uuid]
(let [current-repo (state/get-current-repo)]
(some (fn [{:keys [url] :as graph}]
(when (and (= current-repo url)
(repo/local-uploadable-graph?
(assoc graph :rtc-graph?
(boolean rtc-graph-uuid))))
graph))
(state/get-repos))))
(defn <load-rtc-graph-uuid
[current-repo db-worker-ready?]
(when (and current-repo db-worker-ready?)
(state/<invoke-db-worker :thread-api/get-rtc-graph-uuid current-repo)))
(defn- register-native-top-bar-events! [*configure-top-bar-f]
(when (and (mobile-util/native-platform?)
mobile-util/native-top-bar
@@ -200,13 +182,6 @@
"add-graph" (open-new-db-graph!)
"home-setting" (open-home-settings-actions!)
"graph-setting" (open-graph-settings-actions!)
"sync" (p/let [rtc-graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid
(state/get-current-repo))]
(if-let [graph (current-local-uploadable-graph rtc-graph-uuid)]
(repo/upload-local-graph-with-confirm! graph)
(shui/popup-show! nil
(rtc-indicator/details)
{})))
"favorite" (when-let [id (state/get-current-page)]
(when (common-util/uuid-string? id)
(p/let [block (db-async/<get-block (state/get-current-repo)
@@ -236,7 +211,7 @@
(reset! native-top-bar-listener-version native-top-bar-listener-current-version)))
(defn- configure-native-top-bar!
[{:keys [tab title route-name route-view sync-color favorited? show-sync? show-local-upload?]}]
[{:keys [tab title route-name route-view favorited?]}]
(when (and (mobile-util/native-platform?)
mobile-util/native-top-bar)
(let [hidden? (and (mobile-util/native-ios?) (= tab "search"))
@@ -261,13 +236,7 @@
(= tab "home")
(cond-> []
(nil? route-view)
(conj {:id "home-setting" :systemIcon "ellipsis"})
(and show-local-upload? (not page?))
(conj {:id "sync" :systemIcon "icloud.and.arrow.up"
:size "medium"})
(and (not show-local-upload?) show-sync? (not page?))
(conj {:id "sync" :systemIcon "circle.fill" :color sync-color
:size "small"}))
(conj {:id "home-setting" :systemIcon "ellipsis"}))
(= tab "graphs")
[{:id "graph-setting" :systemIcon "ellipsis"}
@@ -325,43 +294,8 @@
native-ios-graphs? (and (mobile-util/native-ios?) (= tab "graphs"))
page-route? (and (= route-name :page) (not native-ios-graphs?))
[*configure-top-bar-f _] (hooks/use-state (atom nil))
[rtc-graph-uuid set-rtc-graph-uuid!] (hooks/use-state nil)
db-worker-ready? (hooks/use-atom-value state/db-worker-ready?)
detail-info (rtc-indicator/use-detail-info)
_ (rfx/use-sub [:auth/current-login-user])
online? (rfx/use-sub [:network/online?])
rtc-state (:rtc-state detail-info)
graph-uuid (or (:graph-uuid detail-info)
rtc-graph-uuid)
local-uploadable-graph (current-local-uploadable-graph graph-uuid)
show-sync? (and current-repo graph-uuid (user-handler/logged-in?))
show-local-upload? (some? local-uploadable-graph)
unpushed-block-update-count (:pending-local-ops detail-info)
pending-asset-ops (:pending-asset-ops detail-info)
fallback-title (build-fallback-title current-repo tab flashcards-header)
sync-color (if (and online?
(= :open rtc-state)
(zero? unpushed-block-update-count)
(zero? pending-asset-ops))
;; green
"#16A34A"
;; yellow
"#CA8A04")]
(hooks/use-effect!
(fn []
(if-let [rtc-graph-uuid-request (<load-rtc-graph-uuid current-repo db-worker-ready?)]
(let [cancelled? (atom false)]
(-> rtc-graph-uuid-request
(p/then (fn [uuid]
(when-not @cancelled?
(set-rtc-graph-uuid! uuid))))
(p/catch (fn [_] nil)))
#(reset! cancelled? true))
(do
(set-rtc-graph-uuid! nil)
nil)))
[current-repo db-worker-ready?])
fallback-title (build-fallback-title current-repo tab flashcards-header)]
(hooks/use-effect!
(fn []
(when (and (mobile-util/native-platform?)
@@ -374,14 +308,11 @@
:title title
:route-name route-name
:route-view route-view
:sync-color sync-color
:show-sync? show-sync?
:show-local-upload? show-local-upload?
:favorited? favorited?}))]
(reset! *configure-top-bar-f f)
(f nil)))
nil)
[current-repo tab route-name route-view fallback-title sync-color show-sync? show-local-upload?])
[current-repo tab route-name route-view fallback-title])
(hooks/use-effect!
(fn []
@@ -403,16 +334,13 @@
:title title
:route-name route-name
:route-view route-view
:sync-color sync-color
:show-sync? show-sync?
:show-local-upload? show-local-upload?
:favorited? favorited?}))]
(reset! *configure-top-bar-f f)
(f favorited?))))
(p/catch (fn [_] nil)))
#(reset! cancelled? true))
nil))
[current-repo tab route-name route-view route-id sync-color show-sync? show-local-upload? page-route?])
[current-repo tab route-name route-view route-id page-route?])
[:<>]))

View File

@@ -149,23 +149,16 @@
(defn popup-hide!
[& args]
(cond
(= :download-rtc-graph (first args))
(do
(dismiss-native-sheet!)
(mobile-state/set-tab! "home"))
:else
(if (and @*last-popup? (not (= (first args) :editor.commands/commands)))
(if (some? @*pending-native-sheet-data)
(do
(reset! *pending-native-sheet-data nil)
(reset! *last-popup? false)
(reset! *last-popup-data nil)
(mobile-state/set-popup-presenting! false)
(mobile-state/set-popup! nil))
(dismiss-native-sheet!))
(apply shui-popup/hide! args))))
(if (and @*last-popup? (not (= (first args) :editor.commands/commands)))
(if (some? @*pending-native-sheet-data)
(do
(reset! *pending-native-sheet-data nil)
(reset! *last-popup? false)
(reset! *last-popup-data nil)
(mobile-state/set-popup-presenting! false)
(mobile-state/set-popup! nil))
(dismiss-native-sheet!))
(apply shui-popup/hide! args)))
(set! shui/popup-show! popup-show!)
(set! shui/popup-hide! popup-hide!)

View File

@@ -4,7 +4,6 @@
[frontend.components.dnd :as dnd]
[frontend.components.email :as email-component]
[frontend.components.user.login :as login]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.dicts :as dicts]
[frontend.handler.ui :as ui-handler]
@@ -278,14 +277,6 @@
(shui/popup-show! nil (fn [] (log)) {}))}
[:span.text-base (t :mobile.settings/check-log)]]
[:div.mobile-setting-item
{:on-click #(state/pub-event! [:go/sync-server-settings])}
[:span.text-base (t :settings.sync-server/url)]
[:span.text-sm.opacity-70
(if-let [custom (config/get-custom-sync-server-url)]
custom
"Logseq Sync")]]
(when login?
[:div.mobile-setting-item
{:on-click (fn []

View File

@@ -4,7 +4,6 @@
[clojure.string :as string]
[frontend.background-tasks]
[frontend.handler :as fhandler]
[frontend.handler.db-based.rtc-background-tasks]
[frontend.handler.notification :as notification]
[frontend.handler.route :as route-handler]
[frontend.mobile.util :as mobile-util]

View File

@@ -1,31 +1,15 @@
(ns frontend.components.repo-test
(:require [cljs.test :refer [deftest is async]]
[frontend.components.repo :as repo]
[frontend.components.rtc.indicator :as rtc-indicator]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.graph :as graph-handler]
[frontend.handler.repo :as repo-handler]
[frontend.handler.user :as user-handler]
[frontend.state :as state]
[frontend.mobile.util :as mobile-util]
[frontend.util :as util]
[logseq.shui.ui :as shui]
[promesa.core :as p]))
(defn- ensure-rsa-key-fn
[]
#'repo/ensure-e2ee-rsa-key-for-cloud!)
(defn- finish-async-test!
[done]
(js/setTimeout done 0))
(deftest local-uploadable-graph-allows-native-mobile-local-graph-without-root-test
(with-redefs [mobile-util/native-platform? (constantly true)
user-handler/logged-in? (constantly true)
user-handler/rtc-group? (constantly true)]
(is (true? (repo/local-uploadable-graph? {:url "logseq_db_mobile"})))))
(deftest open-in-another-tab-action-is-web-only-for-existing-graphs-test
(let [actionable-f (some-> (resolve 'frontend.components.repo/open-in-another-tab-action?) deref)]
(is (fn? actionable-f) "All graphs should expose a web open-in-another-tab action predicate")
@@ -80,381 +64,6 @@
(is false (str error))
(finish-async-test! done))))))))
(deftest upload-local-graph-with-confirm-asks-before-upload-test
(async done
(let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref)
dialogs (atom [])
upload-calls (atom [])
finished-calls (atom 0)]
(if-not upload-fn
(do
(is false "missing upload-local-graph-with-confirm!")
(finish-async-test! done))
(-> (p/with-redefs [shui/dialog-confirm! (fn [content opts]
(swap! dialogs conj {:content content
:opts opts})
(p/resolved nil))
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
(swap! upload-calls conj [repo graph-e2ee?])
(p/resolved nil))
state/get-current-repo (fn []
"logseq_db_demo")
rtc-indicator/on-upload-finished-task (fn [f]
(swap! finished-calls inc)
(f))
util/mobile? (fn [] false)
shui/popup-show! (fn [& _] nil)
shui/popup-hide! (fn [& _] nil)]
(upload-fn {:url "logseq_db_demo"
:graph-e2ee? true}))
(p/then (fn [_]
(is (= 1 (count @dialogs)))
(is (= [["logseq_db_demo" true]] @upload-calls))
(is (= 1 @finished-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest upload-local-graph-switches-before-uploading-non-current-graph-test
(async done
(let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref)
current-repo (atom "logseq_db_current")
calls (atom [])]
(if-not upload-fn
(do
(is false "missing upload-local-graph-with-confirm!")
(finish-async-test! done))
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
(p/resolved nil))
state/get-current-repo (fn []
@current-repo)
state/pub-event! (fn [event]
(swap! calls conj event)
(reset! current-repo (second event))
(p/resolved nil))
repo/<invoke-db-worker
(fn [op repo key]
(swap! calls conj [op repo key])
(is (= :thread-api/get-key-value op))
(is (= "logseq_db_other" repo))
(is (= :logseq.kv/graph-rtc-e2ee? key))
(p/resolved false))
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
(swap! calls conj [:upload repo graph-e2ee?])
(p/resolved nil))
rtc-indicator/on-upload-finished-task (fn [f]
(f))
util/mobile? (fn [] false)
shui/popup-show! (fn [& _] nil)
shui/popup-hide! (fn [& _] nil)]
(upload-fn {:url "logseq_db_other"}))
(p/then (fn [_]
(is (= [[:graph/switch "logseq_db_other"]
[:thread-api/get-key-value "logseq_db_other" :logseq.kv/graph-rtc-e2ee?]
[:upload "logseq_db_other" false]]
@calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest upload-local-graph-reads-current-graph-e2ee-from-worker-test
(async done
(let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref)
calls (atom [])]
(if-not upload-fn
(do
(is false "missing upload-local-graph-with-confirm!")
(finish-async-test! done))
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
(p/resolved nil))
state/get-current-repo (fn []
"logseq_db_demo")
repo/<invoke-db-worker
(fn [op repo key]
(swap! calls conj [op repo key])
(is (= :thread-api/get-key-value op))
(is (= "logseq_db_demo" repo))
(is (= :logseq.kv/graph-rtc-e2ee? key))
(p/resolved false))
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
(swap! calls conj [:upload repo graph-e2ee?])
(p/resolved nil))
rtc-indicator/on-upload-finished-task (fn [f]
(f))
util/mobile? (fn [] false)
shui/popup-show! (fn [& _] nil)
shui/popup-hide! (fn [& _] nil)]
(upload-fn {:url "logseq_db_demo"}))
(p/then (fn [_]
(is (= [[:thread-api/get-key-value "logseq_db_demo" :logseq.kv/graph-rtc-e2ee?]
[:upload "logseq_db_demo" false]]
@calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest upload-local-graph-shows-mobile-popup-before-upload-starts-test
(async done
(let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref)
calls (atom [])]
(if-not upload-fn
(do
(is false "missing upload-local-graph-with-confirm!")
(finish-async-test! done))
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
(p/resolved nil))
state/get-current-repo (fn []
"logseq_db_demo")
rtc-handler/<rtc-upload-graph! (fn [_repo _graph-e2ee?]
(swap! calls conj :upload)
(is (= [:popup-show :finish-handler :upload] @calls))
(p/resolved nil))
rtc-indicator/on-upload-finished-task (fn [_f]
(swap! calls conj :finish-handler))
util/mobile? (fn [] true)
shui/popup-show! (fn [& _]
(swap! calls conj :popup-show))
shui/popup-hide! (fn [& _]
(swap! calls conj :popup-hide))]
(upload-fn {:url "logseq_db_demo"
:graph-e2ee? true}))
(p/then (fn [_]
(is (= [:popup-show :finish-handler :upload :popup-hide]
@calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest upload-local-graph-hides-mobile-popup-when-upload-errors-test
(async done
(let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref)
calls (atom [])]
(if-not upload-fn
(do
(is false "missing upload-local-graph-with-confirm!")
(finish-async-test! done))
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
(p/resolved nil))
state/get-current-repo (fn []
"logseq_db_demo")
rtc-handler/<rtc-upload-graph! (fn [_repo _graph-e2ee?]
(swap! calls conj :upload)
(p/rejected (ex-info "upload failed" {})))
rtc-indicator/on-upload-finished-task (fn [_f]
(swap! calls conj :finish-handler))
util/mobile? (fn [] true)
shui/popup-show! (fn [& _]
(swap! calls conj :popup-show))
shui/popup-hide! (fn [& _]
(swap! calls conj :popup-hide))]
(upload-fn {:url "logseq_db_demo"
:graph-e2ee? true}))
(p/then (fn [_]
(is false "expected upload failure")
(finish-async-test! done)))
(p/catch (fn [error]
(is (= "upload failed" (ex-message error)))
(is (= [:popup-show :finish-handler :upload :popup-hide]
@calls))
(finish-async-test! done))))))))
(deftest ensure-rsa-key-does-not-create-graph-test
(async done
(let [ensure-fn (ensure-rsa-key-fn)
db-worker-calls (atom [])
create-calls (atom 0)
ensured-values (atom [])]
(if-not ensure-fn
(do
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
(finish-async-test! done))
(-> (p/with-redefs [state/pub-event! (fn [_event]
(p/resolved nil))
repo/<invoke-db-worker
(fn [op & args]
(swap! db-worker-calls conj (into [op] args))
(if (= op :thread-api/db-sync-ensure-user-rsa-keys)
(p/resolved {:public-key "pk"})
(p/resolved nil)))
repo-handler/new-db!
(fn [& _]
(swap! create-calls inc)
(p/resolved "repo"))]
(ensure-fn {:cloud? true
:graph-e2ee? true
:refresh-token "refresh"
:token "token"
:user-uuid "user-1"
:e2ee-rsa-key-ensured? false}
(fn [value]
(swap! ensured-values conj value))))
(p/then (fn [_]
(let [[set-config-call ensure-call] @db-worker-calls
set-config (second set-config-call)]
(is (= :thread-api/set-db-sync-config (first set-config-call)))
(is (= :thread-api/db-sync-ensure-user-rsa-keys (first ensure-call)))
(is (= true (:enabled? set-config)))
(is (contains? set-config :ws-url))
(is (contains? set-config :http-base))
(is (not (contains? set-config :oauth-domain)))
(is (not (contains? set-config :oauth-client-id))))
(is (= [true] @ensured-values))
(is (zero? @create-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest ensure-rsa-key-nil-result-does-not-create-graph-test
(async done
(let [ensure-fn (ensure-rsa-key-fn)
db-worker-calls (atom [])
create-calls (atom 0)
ensured-values (atom [])]
(if-not ensure-fn
(do
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
(finish-async-test! done))
(-> (p/with-redefs [state/pub-event! (fn [_event]
(p/resolved nil))
repo/<invoke-db-worker
(fn [op & args]
(swap! db-worker-calls conj (into [op] args))
(p/resolved nil))
repo-handler/new-db!
(fn [& _]
(swap! create-calls inc)
(p/resolved "repo"))]
(ensure-fn {:cloud? true
:graph-e2ee? true
:refresh-token "refresh"
:token "token"
:user-uuid "user-1"
:e2ee-rsa-key-ensured? false}
(fn [value]
(swap! ensured-values conj value))))
(p/then (fn [_]
(let [[set-config-call ensure-call] @db-worker-calls
set-config (second set-config-call)]
(is (= :thread-api/set-db-sync-config (first set-config-call)))
(is (= :thread-api/db-sync-ensure-user-rsa-keys (first ensure-call)))
(is (= true (:enabled? set-config)))
(is (contains? set-config :ws-url))
(is (contains? set-config :http-base))
(is (not (contains? set-config :oauth-domain)))
(is (not (contains? set-config :oauth-client-id))))
(is (= [false] @ensured-values))
(is (zero? @create-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest ensure-rsa-key-skips-when-prerequisites-missing-test
(async done
(let [ensure-fn (ensure-rsa-key-fn)
ensure-calls (atom 0)
create-calls (atom 0)
ensured-values (atom [])]
(if-not ensure-fn
(do
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
(finish-async-test! done))
(-> (p/with-redefs [repo/<invoke-db-worker
(fn [& _]
(swap! ensure-calls inc)
(p/resolved {:public-key "pk"}))
repo-handler/new-db!
(fn [& _]
(swap! create-calls inc)
(p/resolved "repo"))]
(ensure-fn {:cloud? false
:refresh-token nil
:token nil
:user-uuid nil
:e2ee-rsa-key-ensured? false}
(fn [value]
(swap! ensured-values conj value))))
(p/then (fn [_]
(is (zero? @ensure-calls))
(is (empty? @ensured-values))
(is (zero? @create-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest ensure-rsa-key-skips-when-graph-e2ee-disabled-test
(async done
(let [ensure-fn (ensure-rsa-key-fn)
ensure-calls (atom 0)
ensured-values (atom [])]
(if-not ensure-fn
(do
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
(finish-async-test! done))
(-> (p/with-redefs [repo/<invoke-db-worker
(fn [& _]
(swap! ensure-calls inc)
(p/resolved {:public-key "pk"}))]
(ensure-fn {:cloud? true
:graph-e2ee? false
:refresh-token "refresh"
:token "token"
:user-uuid "user-1"
:e2ee-rsa-key-ensured? false}
(fn [value]
(swap! ensured-values conj value))))
(p/then (fn [_]
(is (zero? @ensure-calls))
(is (empty? @ensured-values))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done))))))))
(deftest not-ready-remote-graph-does-not-trigger-download-test
(let [events (atom [])
links (#'repo/repos-dropdown-links
[{:url "logseq_db_demo"
:remote? true
:rtc-graph? true
:GraphName "demo"
:GraphUUID "graph-1"
:GraphSchemaVersion "65"
:graph-ready-for-use? false}]
nil
nil)
on-click (get-in (first links) [:options :on-click])]
(with-redefs [state/pub-event! (fn [event]
(swap! events conj event))]
(on-click #js {})
(is (empty? @events)))))
(deftest native-mobile-remote-graph-download-allows-popup-to-close-test
(let [events (atom [])
links (#'repo/repos-dropdown-links
[{:url "logseq_db_demo"
:remote? true
:rtc-graph? true
:GraphName "demo"
:GraphUUID "graph-1"
:GraphSchemaVersion "65"
:graph-ready-for-use? true}]
nil
nil)
on-click (get-in (first links) [:options :on-click])]
(with-redefs [mobile-util/native-platform? (constantly true)
state/pub-event! (fn [event]
(swap! events conj event))]
(is (not (false? (on-click #js {}))))
(is (= [[:rtc/download-remote-graph "demo" "graph-1" "65" nil]]
@events)))))
(deftest graph-selection-defers-closing-sidebar-at-mobile-breakpoint-test
(async done
(let [close-f (some-> (resolve 'frontend.components.repo/close-sidebar-after-repo-popup-action!)
@@ -498,35 +107,12 @@
(is (true? (target? (target true))))
(is (false? (target? (target false)))))))
(deftest shift-click-opens-downloaded-remote-graph-in-new-tab-with-graph-id-test
(let [events (atom [])
links (#'repo/repos-dropdown-links
[{:url "logseq_db_demo"
:root "/graphs/demo"
:remote? true
:rtc-graph? true
:GraphName "demo"
:GraphUUID "graph-uuid"
:GraphSchemaVersion "65"
:graph-ready-for-use? true}]
nil
nil)
on-click (get-in (first links) [:options :on-click])]
(with-redefs [state/pub-event! (fn [event]
(swap! events conj event))]
(on-click #js {:shiftKey true})
(is (= [[:graph/open-new-window {:repo "logseq_db_demo"
:graph-id "graph-uuid"}]]
@events)))))
(deftest shift-click-opens-local-graph-in-new-tab-with-registry-graph-id-test
(async done
(let [events (atom [])
links (#'repo/repos-dropdown-links
[{:url "logseq_db_demo"
:root "/graphs/demo"
:graph-ready-for-use? true}]
nil
:root "/graphs/demo"}]
nil)
on-click (get-in (first links) [:options :on-click])]
(-> (p/with-redefs [graph-handler/<get-graph-registry
@@ -551,9 +137,7 @@
registry-reads (atom 0)
links (#'repo/repos-dropdown-links
[{:url "logseq_db_demo"
:root "/graphs/demo"
:graph-ready-for-use? true}]
nil
:root "/graphs/demo"}]
nil)
on-click (get-in (first links) [:options :on-click])]
(with-redefs [util/electron? (constantly true)

View File

@@ -1,50 +0,0 @@
(ns frontend.components.rtc.indicator-test
(:require [cljs.test :refer [deftest is]]
[frontend.components.rtc.indicator :as indicator]))
(deftest asset-transfer-counts-counts-active-uploads-and-downloads
(is (= {:upload 2
:download 1}
(indicator/asset-transfer-counts
{"upload-1" {:direction :upload :loaded 0 :total 10}
"upload-2" {:direction :upload :loaded 5 :total 10}
"upload-done" {:direction :upload :loaded 10 :total 10}
"download-1" {:direction :download :loaded 1 :total 10}
"missing-total" {:direction :download :loaded 1}
"other" {:direction :other :loaded 0 :total 10}}))))
(deftest asset-status-rows-shows-pending-upload-and-active-transfer-info
(is (= [{:count 2 :label-key :sync/missing-asset-files}
{:count 1 :label-key :sync/pending-asset-uploads}
{:count 1 :label-key :sync/assets-uploading}
{:count 2 :label-key :sync/assets-downloading}]
(indicator/asset-status-rows
{:pending-asset-ops 3
:missing-asset-upload-files [{:file "assets/missing-1.pdf"}
{:file "assets/missing-2.pdf"}]
:asset-transfer-counts {:upload 1
:download 2}}))))
(deftest asset-status-rows-hides-zero-counts
(is (= []
(indicator/asset-status-rows
{:pending-asset-ops 0
:missing-asset-upload-files []
:asset-transfer-counts {:upload 0
:download 0}}))))
(deftest detail-info-state-is-closed-without-rtc-lock
(is (= :close
(:rtc-state (indicator/rtc-state->detail-info {})))))
(deftest indicator-button-class-is-space-separated
(let [class-name (indicator/indicator-button-class
{:online? true
:rtc-state :open
:pending-local-ops 0
:pending-asset-ops 0
:pending-server-ops 0})]
(is (string? class-name))
(is (not (re-find #"," class-name)))
(is (= #{"cloud" "on" "idle"}
(set (.split class-name " "))))))

View File

@@ -14,60 +14,6 @@
(is (= "/tmp/home/logseq/graphs/foo~2Fbar/backup"
(config/get-electron-backup-dir (str common-config/db-version-prefix "foo/bar"))))))
(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"))

View File

@@ -11,8 +11,6 @@
(deftest restore-graph-does-not-create-renderer-datascript-conn-test
(async done
(let [repo "logseq_db_restore_without_ui_conn"
block-id (str (random-uuid))
conflicts-by-block {block-id [{:value "remote"}]}
previous-state (state/get-state)
previous-conns @db-conn/conns
calls (atom [])
@@ -36,13 +34,6 @@
(swap! calls conj [:current-repo repo'])
(state/swap-state! assoc :git/current-repo repo')
nil)
state/<invoke-db-worker
(fn [api repo']
(swap! calls conj [:worker api repo'])
(p/resolved conflicts-by-block))
state/set-sync-block-conflicts!
(fn [& args]
(swap! calls conj (into [:hydrate] args)))
db-subs/reset-graph!
(fn [repo']
(swap! reset-graphs conj repo')
@@ -56,14 +47,12 @@
(is (= [[:graph/restored repo] [:ui/re-render-root]]
@events))
(is (= [[:open repo]
[:worker :thread-api/db-sync-get-all-block-conflicts repo]
[:current-repo repo]
[:reset repo]
[:hydrate repo conflicts-by-block]
[:event [:graph/restored repo]]
[:event [:ui/re-render-root]]]
@calls)
"Restore hydrates all conflicts once after graph reset and before rendering.")
"Restore resets renderer subscriptions before rendering.")
(is (empty? @db-conn/conns)
"Restoring a graph must not create a renderer DataScript connection.")))
(p/catch

View File

@@ -2,7 +2,6 @@
(:require [cljs.test :refer [async deftest is]]
[clojure.string :as string]
[frontend.handler.common.developer :as dev-common-handler]
[frontend.handler.db-based.sync :as rtc-handler]
[frontend.handler.notification :as notification]
[frontend.state :as state]
[promesa.core :as p]))
@@ -36,31 +35,3 @@
(fn [error]
(is false (str error))))
(p/finally done)))))
(deftest checksum-diagnostics-fetches-graph-id-through-worker-test
(async done
(let [repo "logseq_db_checksum_worker"
worker-calls (atom [])
fetch-calls (atom [])]
(p/with-redefs [state/<invoke-db-worker
(fn [api repo']
(swap! worker-calls conj [api repo'])
(p/resolved #uuid "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
rtc-handler/http-base
(fn [] "https://sync.example.test")
rtc-handler/fetch-json
(fn [url request opts]
(swap! fetch-calls conj [url request opts])
(p/resolved {:checksum "server-checksum"}))]
(-> (p/let [result (#'dev-common-handler/<fetch-server-checksum-diagnostics repo)]
(is (= {:checksum "server-checksum"} result))
(is (= [[:thread-api/get-rtc-graph-uuid repo]]
@worker-calls))
(is (= [["https://sync.example.test/sync/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/checksum/diagnostics"
{:method "GET"}
{:error-schema :error}]]
@fetch-calls)))
(p/catch
(fn [error]
(is false (str error))))
(p/finally done))))))

View File

@@ -1,24 +0,0 @@
(ns frontend.handler.db-based.rtc-flows-test
(:require [cljs.test :refer [deftest is]]
[frontend.config :as config]
[frontend.handler.db-based.rtc-background-tasks :as rtc-background-tasks]
[frontend.handler.db-based.rtc-flows :as rtc-flows]))
(deftest resume-restart-events-do-not-depend-on-cached-rtc-lock-test
(is (= :document-visible&rtc-not-running
(#'rtc-flows/document-visible->restart-event "visible")))
(is (= :network-online&rtc-not-running
(#'rtc-flows/network-online->restart-event true)))
(is (= :mobile-app-active&rtc-not-running
(#'rtc-flows/mobile-app-active->restart-event true))))
(deftest rtc-background-watch-consumes-existing-event-test
(let [source (atom [:graph-switch "repo"])
events (atom [])]
(with-redefs [config/publishing? false]
(#'rtc-background-tasks/add-watch-when-not-publishing!
source
::consume-existing-event
#(swap! events conj %))
(is (= [[:graph-switch "repo"]] @events))
(remove-watch source ::consume-existing-event))))

View File

@@ -1,764 +0,0 @@
(ns frontend.handler.db-based.sync-test
(:require [cljs.test :refer [deftest is async]]
[clojure.string :as string]
[frontend.config :as config]
[frontend.handler.db-based.sync :as db-sync]
[frontend.persist-db :as persist-db]
[frontend.handler.repo :as repo-handler]
[frontend.handler.user :as user-handler]
[frontend.state :as state]
[frontend.util :as util]
[promesa.core :as p]))
(defn- finish-async-test!
[done]
(js/setTimeout done 0))
(deftest coerce-http-request-does-not-add-client-revision-to-member-request-test
(is (= {:email "user@example.com"}
(#'db-sync/coerce-http-request :graph-members/create
{:email "user@example.com"}))))
(deftest remove-member-request-test
(async done
(let [called (atom nil)]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
db-sync/fetch-json (fn [url opts _]
(reset! called {:url url :opts opts})
(p/resolved {:ok true}))
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))]
(p/let [_ (db-sync/<rtc-remove-member! "graph-1" "user-2")
{:keys [url opts]} @called]
(is (= "http://base/graphs/graph-1/members/user-2" url))
(is (= "DELETE" (:method opts)))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))))))
(deftest leave-graph-uses-current-user-test
(async done
(let [called (atom nil)]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
db-sync/fetch-json (fn [url opts _]
(reset! called {:url url :opts opts})
(p/resolved {:ok true}))
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
user-handler/user-uuid (fn [] "user-1")]
(p/let [_ (db-sync/<rtc-leave-graph! "graph-1")
{:keys [url opts]} @called]
(is (= "http://base/graphs/graph-1/members/user-1" url))
(is (= "DELETE" (:method opts)))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))))))
(deftest leave-graph-missing-user-test
(async done
(-> (p/with-redefs [user-handler/user-uuid (fn [] nil)]
(db-sync/<rtc-leave-graph! "graph-1"))
(p/then (fn [_]
(is false "expected rejection")
(finish-async-test! done)))
(p/catch (fn [e]
(is (= :db-sync/invalid-member (:type (ex-data e))))
(finish-async-test! done))))))
(deftest rtc-get-users-info-uses-cached-members-test
(async done
(let [state-prev (state/get-state)
fetch-calls (atom 0)]
(state/replace-state! (assoc state-prev :rtc/users-info {}))
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
db-sync/<get-rtc-graph-uuid
(fn [repo]
(is (= "repo-1" repo))
(p/resolved "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
state/get-current-repo (fn [] "repo-1")
db-sync/fetch-json (fn [url opts _]
(swap! fetch-calls inc)
(is (= "http://base/graphs/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/members" url))
(is (= "GET" (:method opts)))
(p/resolved {:members [{:user-id "user-1"
:role "member"
:email "user@example.com"
:username "User"}]}))
user-handler/<ensure-id&access-token! (fn []
(p/resolved true))]
(p/let [first-result (db-sync/<rtc-get-users-info)
second-result (db-sync/<rtc-get-users-info)
refreshed-result (db-sync/<rtc-get-users-info true)]
(is (= 2 @fetch-calls))
(is (= first-result second-result))
(is (= first-result refreshed-result))
(is (= {"repo-1" first-result} (state/get-state :rtc/users-info)))))
(p/catch (fn [e]
(is false (str e))))
(p/finally (fn []
(state/replace-state! state-prev)
(finish-async-test! done)))))))
(deftest rtc-create-graph-persists-disabled-e2ee-flag-test
(async done
(let [worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved {:graph-id "graph-1"
:graph-e2ee? false}))]
(db-sync/<rtc-create-graph! "logseq_db_demo" false))
(p/then (fn [result]
(is (= {:graph-id "graph-1"
:graph-e2ee? false}
result))
(is (= [[:thread-api/db-sync-create-remote-graph
"logseq_db_demo"
false
true]]
@worker-calls))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))))))
(deftest rtc-create-graph-defaults-e2ee-enabled-test
(async done
(let [worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved {:graph-id "graph-2"
:graph-e2ee? true}))]
(db-sync/<rtc-create-graph! "logseq_db_demo"))
(p/then (fn [result]
(is (= {:graph-id "graph-2"
:graph-e2ee? true}
result))
(is (= [[:thread-api/db-sync-create-remote-graph
"logseq_db_demo"
true
true]]
@worker-calls))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))))))
(deftest rtc-worker-error-values-reject-test
(async done
(let [state-prev (state/get-state)
worker-prev @state/*db-worker
error (ex-info "invalid-e2ee-password"
{:code :db-sync/invalid-e2ee-password})
continued-calls (atom [])
capture (fn [promise]
(-> promise
(p/then (fn [result] [:resolved result]))
(p/catch (fn [reason] [:rejected reason]))))]
(state/set-state! :rtc/uploading? false)
(reset! state/*db-worker
(fn [op & _args]
(if (contains? #{:thread-api/db-sync-create-remote-graph
:thread-api/db-sync-upload-graph}
op)
(p/resolved error)
(p/resolved :ok))))
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
db-sync/<get-remote-graphs (fn []
(swap! continued-calls conj :refresh)
(p/resolved []))
db-sync/<rtc-start! (fn [& _]
(swap! continued-calls conj :start)
(p/resolved :ok))]
(p/all [(capture (db-sync/<rtc-create-graph! "logseq_db_demo" true))
(capture (db-sync/<rtc-upload-graph! "logseq_db_demo" true))]))
(p/then (fn [results]
(is (= [:rejected :rejected] (mapv first results)))
(is (every? #(= :db-sync/invalid-e2ee-password
(:code (ex-data (second %))))
results))
(is (empty? @continued-calls))
(finish-async-test! done)))
(p/catch (fn [failure]
(is false (str failure))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)
(state/replace-state! state-prev)))))))
(deftest rtc-upload-graph-creates-remote-graph-as-not-ready-test
(async done
(let [upload-calls (atom [])
refresh-calls (atom 0)
start-calls (atom [])
state-prev (state/get-state)]
(state/set-auth-id-token "id-token-1")
(state/set-auth-access-token "access-token-1")
(state/set-auth-refresh-token "refresh-token-1")
(state/set-state! :auth/oauth-token-url "http://oauth/token")
(state/set-state! :auth/oauth-client-id "client-1")
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& args]
(swap! upload-calls conj args)
(p/resolved :ok))
db-sync/<get-remote-graphs (fn []
(swap! refresh-calls inc)
(p/resolved []))
db-sync/<rtc-start! (fn [repo & _]
(swap! start-calls conj repo)
(p/resolved :ok))]
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
(p/then (fn [_]
(is (= [:thread-api/sync-app-state
:thread-api/db-sync-upload-graph]
(mapv first @upload-calls)))
(is (= "refresh-token-1"
(:auth/refresh-token (second (first @upload-calls)))))
(is (= [:thread-api/db-sync-upload-graph "logseq_db_demo"]
(second @upload-calls)))
(is (= 1 @refresh-calls))
(is (= ["logseq_db_demo"] @start-calls))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))
(p/finally (fn []
(state/replace-state! state-prev)))))))
(deftest rtc-upload-graph-syncs-cleared-refresh-token-test
(async done
(let [worker-calls (atom [])
state-prev (state/get-state)]
(state/set-auth-refresh-token nil)
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
db-sync/<get-remote-graphs (fn [] (p/resolved []))
db-sync/<rtc-start! (fn [& _] (p/resolved :ok))]
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
(p/then (fn [_]
(let [payload (second (first @worker-calls))]
(is (contains? payload :auth/refresh-token))
(is (nil? (:auth/refresh-token payload))))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))
(p/finally (fn []
(state/replace-state! state-prev)))))))
(deftest rtc-download-graph-rejects-while-another-download-is-active-test
(async done
(let [state-prev (state/get-state)
worker-calls (atom [])]
(state/swap-state! assoc
:rtc/downloading-graph-uuid "graph-1"
:rtc/uploading? false)
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
util/electron? (fn [] false)
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/pub-event! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "demo-graph" "graph-2" false))
(p/then (fn [_]
(is false "expected rejection")
(state/replace-state! state-prev)
(finish-async-test! done)))
(p/catch (fn [error]
(is (= :db-sync/graph-operation-in-progress
(:type (ex-data error))))
(is (empty? @worker-calls))
(state/replace-state! state-prev)
(finish-async-test! done)))))))
(deftest rtc-download-graph-rejects-while-upload-is-active-test
(async done
(let [state-prev (state/get-state)
worker-calls (atom [])]
(state/swap-state! assoc
:rtc/downloading-graph-uuid nil
:rtc/uploading? true)
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/pub-event! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
(p/then (fn [_]
(is false "expected rejection")
(state/replace-state! state-prev)
(finish-async-test! done)))
(p/catch (fn [error]
(is (= :db-sync/graph-operation-in-progress
(:type (ex-data error))))
(is (empty? @worker-calls))
(state/replace-state! state-prev)
(finish-async-test! done)))))))
(deftest rtc-upload-graph-rejects-while-download-is-active-test
(async done
(let [state-prev (state/get-state)
upload-calls (atom [])
refresh-calls (atom 0)
start-calls (atom [])]
(state/swap-state! assoc
:rtc/downloading-graph-uuid "graph-1"
:rtc/uploading? false)
(-> (p/with-redefs [state/<invoke-db-worker (fn [& args]
(swap! upload-calls conj args)
(p/resolved :ok))
db-sync/<get-remote-graphs (fn []
(swap! refresh-calls inc)
(p/resolved []))
db-sync/<rtc-start! (fn [repo & _]
(swap! start-calls conj repo)
(p/resolved :ok))]
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
(p/then (fn [_]
(is false "expected rejection")
(state/replace-state! state-prev)
(finish-async-test! done)))
(p/catch (fn [error]
(is (= :db-sync/graph-operation-in-progress
(:type (ex-data error))))
(is (empty? @upload-calls))
(is (zero? @refresh-calls))
(is (empty? @start-calls))
(state/replace-state! state-prev)
(finish-async-test! done)))))))
(deftest rtc-create-graph-and-start-sync-does-not-upload-snapshot-test
(async done
(let [create-calls (atom [])
refresh-calls (atom 0)
start-calls (atom [])
upload-calls (atom [])]
(-> (p/with-redefs [db-sync/<rtc-create-graph! (fn [repo graph-e2ee? & [graph-ready-for-use?]]
(swap! create-calls conj [repo graph-e2ee? graph-ready-for-use?])
(p/resolved "graph-4"))
db-sync/<get-remote-graphs (fn []
(swap! refresh-calls inc)
(p/resolved []))
db-sync/<rtc-start! (fn [repo & _]
(swap! start-calls conj repo)
(p/resolved :ok))
state/<invoke-db-worker (fn [& args]
(swap! upload-calls conj args)
(p/resolved :ok))]
(db-sync/<rtc-create-graph-and-start-sync! "logseq_db_demo" true))
(p/then (fn [_]
(is (= [["logseq_db_demo" true true]] @create-calls))
(is (= 1 @refresh-calls))
(is (= ["logseq_db_demo"] @start-calls))
(is (empty? @upload-calls))
(finish-async-test! done)))
(p/catch (fn [e]
(is false (str e))
(finish-async-test! done)))))))
(deftest rtc-start-skips-while-graph-upload-is-active-test
(async done
(let [worker-prev @state/*db-worker
state-prev (state/get-state)
calls (atom [])]
(reset! state/*db-worker :worker)
(state/swap-state! assoc
:rtc/uploading? true
:rtc/loading-graphs? false)
(-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "demo-graph"}])
state/<invoke-db-worker (fn [& args]
(swap! calls conj args)
(p/resolved :ok))]
(db-sync/<rtc-start! "demo-graph"))
(p/then (fn [_]
(is (not-any? #(= :thread-api/db-sync-start (first %)) @calls))
(is (some #(= :thread-api/db-sync-stop (first %)) @calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)
(state/replace-state! state-prev)))))))
(deftest rtc-start-skips-when-remote-graph-is-not-ready-for-use-test
(async done
(let [worker-prev @state/*db-worker
state-prev (state/get-state)
calls (atom [])]
(reset! state/*db-worker :worker)
(state/swap-state! assoc
:rtc/uploading? false
:rtc/loading-graphs? false)
(-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "demo-graph"
:graph-ready-for-use? false}])
state/<invoke-db-worker (fn [& args]
(swap! calls conj args)
(p/resolved :ok))]
(db-sync/<rtc-start! "demo-graph"))
(p/then (fn [_]
(is (not-any? #(= :thread-api/db-sync-start (first %)) @calls))
(is (some #(= :thread-api/db-sync-stop (first %)) @calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)
(state/replace-state! state-prev)))))))
(deftest rtc-start-syncs-auth-state-before-db-sync-start-test
(async done
(let [worker-prev @state/*db-worker
state-prev (state/get-state)
calls (atom [])]
(reset! state/*db-worker :worker)
(state/replace-state! (assoc state-prev
:git/current-repo "demo-graph"
:auth/id-token "id-token"
:auth/access-token "access-token"
:auth/refresh-token "refresh-token"
:user/info {:sub "user-1"}
:config {:a 1}
:rtc/uploading? false
:rtc/loading-graphs? false))
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/get-rtc-graphs (fn [] [{:url "demo-graph"
:graph-ready-for-use? true}])
state/<invoke-db-worker (fn [& args]
(swap! calls conj args)
(p/resolved :ok))]
(db-sync/<rtc-start! "demo-graph"))
(p/then (fn [_]
(is (= :thread-api/sync-app-state (ffirst @calls)))
(is (= (cond-> {:git/current-repo "demo-graph"
:config {:a 1}
:auth/id-token "id-token"
:auth/access-token "access-token"
:auth/refresh-token "refresh-token"
:auth/oauth-token-url nil
:user/info {:sub "user-1"}}
(seq config/OAUTH-DOMAIN)
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
(seq config/COGNITO-CLIENT-ID)
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID))
(second (first @calls))))
(is (= [:thread-api/db-sync-start "demo-graph"]
(second @calls)))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)
(state/replace-state! state-prev)))))))
(deftest rtc-download-graph-emits-feedback-before-snapshot-fetch-test
(let [trace (atom [])
log-events (atom [])]
(with-redefs [db-sync/http-base (fn [] "http://base")
state/set-state! (fn [k v]
(swap! trace conj [:set k v]))
state/pub-event! (fn [[event payload :as e]]
(when (and (= :rtc/log event)
(= "graph-1" (:graph-uuid payload)))
(swap! trace conj :log)
(swap! log-events conj e)))
;; Keep auth pending so we only validate immediate click-time feedback.
user-handler/<ensure-id&access-token! (fn [] (p/deferred))
db-sync/fetch-json (fn [url _opts _schema]
(swap! trace conj [:fetch url])
(p/resolved {:t 1}))]
(db-sync/<rtc-download-graph! "demo-graph" "graph-1")
(is (= [[:set :rtc/downloading-graph-uuid "graph-1"] :log]
(take 2 @trace)))
(let [[event {:keys [type sub-type graph-uuid message]}] (first @log-events)]
(is (= :rtc/log event))
(is (= :rtc.log/download type))
(is (= :download-progress sub-type))
(is (= "graph-1" graph-uuid))
(is (and (string? message)
(string/includes? message "Preparing")))))))
(deftest get-remote-graphs-includes-ready-for-use-flag-test
(async done
(let [graphs-state (atom nil)
worker-prev @state/*db-worker
ensure-calls (atom [])]
(reset! state/*db-worker :worker)
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
db-sync/fetch-json (fn [_url _opts _schema]
(p/resolved {:graphs [{:graph-id "graph-1"
:graph-name "demo"
:schema-version "65"
:graph-e2ee? true
:graph-ready-for-use? false
:created-at 1
:updated-at 2}]
:user-rsa-keys-exists? true}))
state/<invoke-db-worker (fn [& args]
(when (= :thread-api/db-sync-ensure-user-rsa-keys
(first args))
(swap! ensure-calls conj args))
(p/resolved :ok))
state/set-state! (fn [k v]
(when (= k :rtc/graphs)
(reset! graphs-state v))
nil)
repo-handler/refresh-repos! (fn [] nil)]
(db-sync/<get-remote-graphs))
(p/then (fn [graphs]
(is (= false (:graph-ready-for-use? (first graphs))))
(is (= false (:graph-ready-for-use? (first @graphs-state))))
(is (empty? @ensure-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)))))))
(deftest get-remote-graphs-ensures-user-rsa-keys-when-server-missing-test
(async done
(let [worker-prev @state/*db-worker
ensure-calls (atom [])]
(reset! state/*db-worker :worker)
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
db-sync/fetch-json (fn [_url _opts _schema]
(p/resolved {:graphs []
:user-rsa-keys-exists? false}))
state/<invoke-db-worker (fn [& args]
(when (= :thread-api/db-sync-ensure-user-rsa-keys
(first args))
(swap! ensure-calls conj args))
(p/resolved {:public-key "pk"}))
state/set-state! (fn [& _] nil)
repo-handler/refresh-repos! (fn [] nil)]
(db-sync/<get-remote-graphs))
(p/then (fn [_]
(is (= [[:thread-api/db-sync-ensure-user-rsa-keys
{:ensure-server? true
:server-rsa-keys-exists? false}]]
@ensure-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)))))))
(deftest get-remote-graphs-syncs-auth-before-ensuring-user-rsa-keys-test
(async done
(let [worker-prev @state/*db-worker
state-prev (state/get-state)
worker-calls (atom [])]
(reset! state/*db-worker :worker)
(state/swap-state! assoc
:auth/id-token "id-token-1"
:auth/access-token "access-token-1"
:auth/refresh-token "refresh-token-1"
:auth/oauth-token-url "http://oauth/token"
:auth/oauth-client-id "client-1")
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn []
(p/resolved true))
db-sync/fetch-json (fn [_url _opts _schema]
(p/resolved {:graphs []
:user-rsa-keys-exists? false}))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved {:public-key "pk"}))
state/set-state! (fn [& _] nil)
repo-handler/refresh-repos! (fn [] nil)]
(db-sync/<get-remote-graphs))
(p/then (fn [_]
(is (= [:thread-api/sync-app-state
:thread-api/db-sync-ensure-user-rsa-keys]
(mapv first @worker-calls)))
(is (= "refresh-token-1"
(:auth/refresh-token (second (first @worker-calls)))))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)
(state/replace-state! state-prev)))))))
(deftest rtc-download-graph-delegates-to-worker-download-api-test
(async done
(let [worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/set-state! (fn [& _] nil)
state/pub-event! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
(p/then (fn [_]
(is (= 1 (count @worker-calls)))
(let [[op graph graph-uuid graph-e2ee?] (first @worker-calls)]
(is (= :thread-api/db-sync-download-graph-by-id op))
(is (string/ends-with? graph "demo-graph"))
(is (= "graph-1" graph-uuid))
(is (= false graph-e2ee?)))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))))))
(deftest rtc-download-graph-sets-and-clears-downloading-state-test
(async done
(let [state-calls (atom [])
worker-prev @state/*db-worker]
(reset! state/*db-worker nil)
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
state/<invoke-db-worker (fn [& _] (p/resolved :ok))
state/pub-event! (fn [& _] nil)
state/set-state! (fn [k v]
(swap! state-calls conj [k v])
nil)]
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
(p/then (fn [_]
(is (= [[:rtc/downloading-graph-uuid "graph-1"]
[:rtc/downloading-graph-uuid nil]]
@state-calls))
(finish-async-test! done)))
(p/catch (fn [error]
(reset! state/*db-worker worker-prev)
(is false (str error))
(finish-async-test! done)))
(p/finally (fn []
(reset! state/*db-worker worker-prev)))))))
(deftest rtc-download-graph-rebinds-desktop-runtime-before-download-test
(async done
(let [runtime-bound-repo (atom "logseq_db_d")
invoke-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
util/electron? (fn [] true)
persist-db/<open-and-fetch-schema (fn [repo _opts]
(reset! runtime-bound-repo repo)
(p/resolved {:schema {}}))
state/<invoke-db-worker (fn [& args]
(swap! invoke-calls conj args)
(let [[op repo] args]
(case op
:thread-api/sync-app-state
(p/resolved :ok)
:thread-api/db-sync-download-graph-by-id
(if (= repo @runtime-bound-repo)
(p/resolved :ok)
(p/rejected
(ex-info "repo does not match bound repo"
{:status 409
:code :repo-mismatch
:error {:code "repo-mismatch"
:message "repo does not match bound repo"
:repo repo
:bound-repo @runtime-bound-repo}})))
(p/resolved :ok))))
state/pub-event! (fn [& _] nil)
state/set-state! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "db1" "graph-1" false))
(p/then (fn [_]
(is (= "logseq_db_db1" @runtime-bound-repo))
(is (= :thread-api/sync-app-state
(ffirst @invoke-calls)))
(is (= :thread-api/db-sync-download-graph-by-id
(first (second @invoke-calls))))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))))))
(deftest rtc-download-graph-downloads-missing-assets-on-electron-test
(async done
(let [worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn []
(p/resolved true))
util/electron? (fn [] true)
persist-db/<open-and-fetch-schema (fn [_repo _opts]
(p/resolved {:schema {}}))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/pub-event! (fn [& _] nil)
state/set-state! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "db1" "graph-1" false))
(p/then (fn [_]
(is (= [:thread-api/sync-app-state
:thread-api/db-sync-download-graph-by-id
:thread-api/db-sync-download-missing-assets]
(mapv first @worker-calls)))
(is (= ["logseq_db_db1" "graph-1"]
(rest (nth @worker-calls 2))))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))))))
(deftest rtc-download-graph-skips-runtime-rebind-outside-electron-test
(async done
(let [runtime-rebind-calls (atom [])
worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
util/electron? (fn [] false)
persist-db/<open-and-fetch-schema (fn [& args]
(swap! runtime-rebind-calls conj args)
(p/resolved {:schema {}}))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/pub-event! (fn [& _] nil)
state/set-state! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "db1" "graph-1" true))
(p/then (fn [_]
(is (empty? @runtime-rebind-calls))
(is (= :thread-api/db-sync-download-graph-by-id
(ffirst @worker-calls)))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))))))
(deftest rtc-download-graph-syncs-auth-state-after-runtime-rebind-test
(async done
(let [worker-calls (atom [])]
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
util/electron? (fn [] true)
persist-db/<open-and-fetch-schema (fn [_repo _opts]
(p/resolved {:schema {}}))
state/<invoke-db-worker (fn [& args]
(swap! worker-calls conj args)
(p/resolved :ok))
state/pub-event! (fn [& _] nil)
state/set-state! (fn [& _] nil)]
(db-sync/<rtc-download-graph! "db1" "graph-1" true))
(p/then (fn [_]
(is (= :thread-api/sync-app-state
(ffirst @worker-calls)))
(is (= :thread-api/db-sync-download-graph-by-id
(first (second @worker-calls))))
(finish-async-test! done)))
(p/catch (fn [error]
(is false (str error))
(finish-async-test! done)))))))

View File

@@ -1,21 +0,0 @@
(ns frontend.handler.events.rtc-error-test
(:require [cljs.test :refer [deftest is]]
[frontend.handler.events.rtc-error :as rtc-error]))
(deftest e2ee-decrypt-failed-detects-invalid-password-code-test
(is (true?
(rtc-error/e2ee-decrypt-failed?
(ex-info "db-sync download failed"
{:code :db-sync/invalid-e2ee-password})))))
(deftest e2ee-decrypt-failed-detects-nested-error-test
(is (true?
(rtc-error/e2ee-decrypt-failed?
(ex-info "db-sync download failed"
{:error (ex-info "decrypt-aes-key" {})})))))
(deftest e2ee-decrypt-failed-ignores-other-errors-test
(is (false?
(rtc-error/e2ee-decrypt-failed?
(ex-info "db-sync download failed"
{:error-message "snapshot download failed"})))))

View File

@@ -1,27 +0,0 @@
(ns frontend.handler.events-test
(:require [cljs.test :refer [deftest is testing]]
[frontend.components.rtc.download-progress :as download-progress]
[frontend.mobile.util :as mobile-util]
[logseq.shui.ui :as shui]))
(deftest download-graph-progress-surface-test
(let [calls (atom [])]
(testing "web mobile uses a centered dialog"
(with-redefs [mobile-util/native-platform? (constantly false)
shui/dialog-open! (fn [& args] (swap! calls conj [:dialog args]))
shui/dialog-close! (fn [& args] (swap! calls conj [:dialog-close args]))
shui/popup-show! (fn [& args] (swap! calls conj [:popup args]))
shui/popup-hide! (fn [& args] (swap! calls conj [:popup-hide args]))]
(download-progress/show! "My notes")
(download-progress/hide!)
(is (= [:dialog :dialog-close] (mapv first @calls)))))
(testing "native mobile keeps the native popup"
(reset! calls [])
(with-redefs [mobile-util/native-platform? (constantly true)
shui/dialog-open! (fn [& args] (swap! calls conj [:dialog args]))
shui/dialog-close! (fn [& args] (swap! calls conj [:dialog-close args]))
shui/popup-show! (fn [& args] (swap! calls conj [:popup args]))
shui/popup-hide! (fn [& args] (swap! calls conj [:popup-hide args]))]
(download-progress/show! "My notes")
(download-progress/hide!)
(is (= [:popup :popup-hide] (mapv first @calls)))))))

View File

@@ -131,41 +131,3 @@
(restore!)
(done))))))
(deftest logout-clears-e2ee-password-when-db-worker-ready-test
(testing "logout should request db-worker to clear persisted e2ee password"
(let [ops* (atom [])
old-worker @state/*db-worker]
(reset! state/*db-worker :worker)
(try
(with-mocked-local-storage
(fn []
(with-redefs [state/<invoke-db-worker (fn [op & _]
(swap! ops* conj op)
(p/resolved nil))
state/clear-user-info! (fn [] nil)
state/pub-event! (fn [& _] nil)
user-handler/clear-tokens (fn [] nil)]
(user-handler/logout)
(is (= :thread-api/clear-e2ee-password
(first @ops*))))))
(finally
(reset! state/*db-worker old-worker))))))
(deftest logout-skips-e2ee-password-clear-when-db-worker-missing-test
(testing "logout should not call db-worker API when db-worker is unavailable"
(let [invoke-calls* (atom 0)
old-worker @state/*db-worker]
(reset! state/*db-worker nil)
(try
(with-mocked-local-storage
(fn []
(with-redefs [state/<invoke-db-worker (fn [& _]
(swap! invoke-calls* inc)
(p/resolved nil))
state/clear-user-info! (fn [] nil)
state/pub-event! (fn [& _] nil)
user-handler/clear-tokens (fn [] nil)]
(user-handler/logout)
(is (zero? @invoke-calls*)))))
(finally
(reset! state/*db-worker old-worker))))))

View File

@@ -1,8 +1,7 @@
(ns frontend.handler.worker-test
(:require [cljs.test :refer [async deftest is]]
[frontend.handler.worker :as worker-handler]
[frontend.state :as state]
[promesa.core :as p]))
[frontend.state :as state]))
(deftest handle-message-reports-comlink-worker-throw-with-extra-data-test
(let [captured-events (atom [])
@@ -63,52 +62,3 @@
(finally
(reset! state/*db-worker previous-worker)
(reset! state/*db-worker-thread previous-thread)))))
(deftest db-worker-ui-request-resolve-passes-request-id-and-result-test
(async done
(let [calls (atom [])
wrapped-worker (fn [qkw & args]
(swap! calls conj [qkw args])
(p/resolved {:ok true}))]
(with-redefs [worker-handler/<db-worker-ui-action
(fn [_action _payload]
(p/resolved {:password "pw"}))]
(-> (worker-handler/handle :db-worker/ui-request
wrapped-worker
{:request-id "req-1"
:action :request-e2ee-password
:payload {:reason :decrypt-user-rsa-private-key}})
(p/then (fn [_]
(is (= [[:thread-api/resolve-ui-request ["req-1" {:password "pw"}]]]
@calls))
(done)))
(p/catch (fn [error]
(is nil (str "unexpected error: " error))
(done))))))))
(deftest db-worker-ui-request-reject-passes-request-id-and-error-test
(async done
(let [calls (atom [])
wrapped-worker (fn [qkw & args]
(swap! calls conj [qkw args])
(p/resolved {:ok true}))]
(with-redefs [worker-handler/<db-worker-ui-action
(fn [_action _payload]
(p/rejected (ex-info "boom" {:code :boom :x 1})))]
(-> (worker-handler/handle :db-worker/ui-request
wrapped-worker
{:request-id "req-2"
:action :request-e2ee-password
:payload {:reason :decrypt-user-rsa-private-key}})
(p/then (fn [_]
(is (= [[:thread-api/reject-ui-request
["req-2"
{:code :boom
:message "boom"
:data {:code :boom
:x 1}}]]]
@calls))
(done)))
(p/catch (fn [error]
(is nil (str "unexpected error: " error))
(done))))))))

View File

@@ -2,7 +2,6 @@
(:require [cljs.test :refer [async deftest is use-fixtures]]
[electron.ipc :as ipc]
[frontend.common.thread-api :as thread-api]
[frontend.config :as config]
[frontend.handler.notification :as notification]
[frontend.persist-db.browser :as browser]
[frontend.persist-db :as persist-db]
@@ -680,47 +679,6 @@
(set! storage/remove original-storage-remove)
(done)))))))
(deftest electron-ensure-remote-pushes-db-sync-config-on-start-test
(async done
(let [worker-calls (atom [])
ensure-remote! #'persist-db/<ensure-remote!
original-ipc ipc/ipc
original-start! remote/start!
original-stop! remote/stop!
original-ws config/db-sync-ws-url
original-http config/db-sync-http-base]
(reset-runtime-state!)
(set! ipc/ipc (fn [channel repo]
(is (= "db-worker-runtime" channel))
(p/resolved {:base-url "http://127.0.0.1:9101"
:auth-token nil
:repo repo})))
(set! config/db-sync-ws-url (fn [] "wss://sync.example.test/sync/%s"))
(set! config/db-sync-http-base (fn [] "https://sync.example.test"))
(set! remote/start! (fn [{:keys [repo]}]
(->FakeRemote repo
(fn [qkw & args]
(swap! worker-calls conj [qkw args])
(p/resolved nil)))))
(set! remote/stop! (fn [_] (p/resolved true)))
(-> (p/let [_ (ensure-remote! "logseq_db_graph_a")]
(let [set-config-call (first (filter #(= :thread-api/set-db-sync-config (first %))
@worker-calls))]
(is (= [:thread-api/set-db-sync-config
[{:enabled? true
:ws-url "wss://sync.example.test/sync/%s"
:http-base "https://sync.example.test"}]]
set-config-call))))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally (fn []
(set! ipc/ipc original-ipc)
(set! remote/start! original-start!)
(set! remote/stop! original-stop!)
(set! config/db-sync-ws-url original-ws)
(set! config/db-sync-http-base original-http)
(done)))))))
(deftest electron-ensure-remote-pushes-markdown-mirror-setting-on-start-test
(async done
(let [worker-calls (atom [])

View File

@@ -1,21 +0,0 @@
(ns mobile.header-test
(:require [cljs.test :refer [deftest is testing]]
[frontend.state :as state]
[mobile.components.header :as header]))
(deftest rtc-graph-uuid-is-not-loaded-before-db-worker-is-ready
(testing "mobile startup does not invoke the DB worker before initialization"
(let [calls (atom [])]
(with-redefs [state/<invoke-db-worker (fn [& args]
(swap! calls conj args))]
(is (nil? (header/<load-rtc-graph-uuid "graph" false)))
(is (empty? @calls))))))
(deftest rtc-graph-uuid-is-loaded-after-db-worker-is-ready
(let [calls (atom [])]
(with-redefs [state/<invoke-db-worker (fn [& args]
(swap! calls conj args)
:graph-uuid)]
(is (= :graph-uuid (header/<load-rtc-graph-uuid "graph" true)))
(is (= [[:thread-api/get-rtc-graph-uuid "graph"]]
@calls)))))