mirror of
https://github.com/logseq/logseq.git
synced 2026-05-29 23:19:38 +00:00
The npm `open` package (v8.4.2) is CommonJS with `module.exports = open;`
and no `.default` property, so importing it as `["open" :default open-external]`
left `open-external` undefined. Every custom-scheme click (freetube://, tg://,
etc.) reached `open-default-app!`, showed the confirmation dialog, and then
threw `TypeError: ...default is not a function` from inside (default-open url),
which Electron logged as an uncaughtException with no user-visible feedback.
Switch to `:as open-external` so the symbol binds to module.exports itself
(the open function). The neighboring node-fetch import is left as `:default`
because node-fetch v2.7.0 explicitly sets `exports.default = exports` for
ESM-interop, so it actually has a default export.
Regression from d6403b7746 (#12460), which replaced the prior working
`(defonce open (js/require "open"))` with the shadow-cljs ESM-style import.
298 lines
9.3 KiB
Clojure
298 lines
9.3 KiB
Clojure
(ns electron.utils
|
|
(:require ["electron" :refer [app BrowserWindow]]
|
|
["node-fetch" :default node-fetch]
|
|
["open" :as open-external]
|
|
["fs-extra" :as fs]
|
|
["path" :as node-path]
|
|
[cljs-bean.core :as bean]
|
|
[clojure.string :as string]
|
|
[electron.configs :as cfgs]
|
|
[electron.logger :as logger]
|
|
[logseq.cli.common.graph :as cli-common-graph]
|
|
[logseq.common.graph-dir :as graph-dir]
|
|
[logseq.common.config :as common-config]
|
|
[promesa.core :as p]
|
|
[shadow.esm :refer [dynamic-import]]))
|
|
|
|
(defonce *win (atom nil)) ;; The main window
|
|
|
|
(defonce mac? (= (.-platform js/process) "darwin"))
|
|
(defonce win32? (= (.-platform js/process) "win32"))
|
|
(defonce linux? (= (.-platform js/process) "linux"))
|
|
|
|
(defonce prod? (= js/process.env.NODE_ENV "production"))
|
|
|
|
(defonce dev? (not prod?))
|
|
(defonce *fetchAgent (atom nil))
|
|
(defonce *proxy-agent-ctors (atom nil))
|
|
(defonce extract-zip (js/require "extract-zip"))
|
|
|
|
(defn- <ensure-proxy-agent-ctors
|
|
"Load ESM-only proxy agent packages once and cache their constructors."
|
|
[]
|
|
(or @*proxy-agent-ctors
|
|
(reset! *proxy-agent-ctors
|
|
(p/let [https-module (dynamic-import "https-proxy-agent")
|
|
socks-module (dynamic-import "socks-proxy-agent")
|
|
ctors {:http (.-HttpsProxyAgent ^js https-module)
|
|
:socks5 (.-SocksProxyAgent ^js socks-module)}]
|
|
ctors))))
|
|
|
|
(defn open
|
|
([target] (open target nil))
|
|
([target options]
|
|
(if options
|
|
(open-external target (bean/->js options))
|
|
(open-external target))))
|
|
|
|
(defn fetch
|
|
([url] (fetch url nil))
|
|
([url options]
|
|
(node-fetch url (bean/->js (merge options {:agent @*fetchAgent})))))
|
|
|
|
(defn fix-win-path!
|
|
[path]
|
|
(when (not-empty path)
|
|
(if win32?
|
|
(string/replace path "\\" "/")
|
|
path)))
|
|
|
|
(defn to-native-win-path!
|
|
"Convert path to native win path"
|
|
[path]
|
|
(when (not-empty path)
|
|
(if win32?
|
|
(string/replace path "/" "\\")
|
|
path)))
|
|
|
|
(defn get-ls-dotdir-root
|
|
[]
|
|
(let [lg-dir (node-path/join (.getPath app "home") ".logseq")]
|
|
(when-not (fs/existsSync lg-dir)
|
|
(fs/mkdirSync lg-dir))
|
|
(fix-win-path! lg-dir)))
|
|
|
|
(defn get-ls-default-plugins
|
|
[]
|
|
(let [plugins-root (node-path/join (get-ls-dotdir-root) "plugins")
|
|
_ (when-not (fs/existsSync plugins-root)
|
|
(fs/mkdirSync plugins-root))
|
|
dirs (js->clj (fs/readdirSync plugins-root #js{"withFileTypes" true}))
|
|
dirs (->> dirs
|
|
(filter #(.isDirectory %))
|
|
(filter (fn [f] (not (some #(string/starts-with? (.-name f) %) ["_" "."]))))
|
|
(map #(node-path/join plugins-root (.-name %))))]
|
|
dirs))
|
|
|
|
(defn- set-fetch-agent-proxy
|
|
"Set proxy for fetch agent(plugin system)
|
|
protocol: http | socks5"
|
|
[{:keys [protocol host port]}]
|
|
(if (and protocol host port (or (= protocol "http") (= protocol "socks5")))
|
|
(p/let [ctors (<ensure-proxy-agent-ctors)
|
|
proxy-url (str protocol "://" host ":" port)]
|
|
(if-let [ctor (get ctors (keyword protocol))]
|
|
(reset! *fetchAgent (new ctor proxy-url))
|
|
(logger/error "Unknown proxy protocol:" protocol)))
|
|
(reset! *fetchAgent nil)))
|
|
|
|
(defn <set-electron-proxy
|
|
"Set proxy for electron
|
|
type: system | direct | socks5 | http"
|
|
([{:keys [type host port] :or {type "system"}}]
|
|
(let [->proxy-rules (fn [type host port]
|
|
(cond
|
|
(= type "http")
|
|
(str "http=" host ":" port ";https=" host ":" port)
|
|
(= type "socks5")
|
|
(str "http=socks5://" host ":" port ";https=socks5://" host ":" port)
|
|
(or (= type "socks") (= type "socks4"))
|
|
(str "http=socks://" host ":" port ";https=socks://" host ":" port)
|
|
(= type "direct")
|
|
"direct://"
|
|
:else
|
|
nil))
|
|
config (cond
|
|
(= type "system")
|
|
#js {:mode "system"}
|
|
|
|
(= type "direct")
|
|
#js {:mode "direct"}
|
|
|
|
(or (= type "socks5") (= type "http"))
|
|
#js {:mode "fixed_servers"
|
|
:proxyRules (->proxy-rules type host port)
|
|
:proxyBypassRules "<local>"}
|
|
|
|
:else
|
|
#js {:mode "system"})
|
|
sess (.. ^js @*win -webContents -session)]
|
|
(if sess
|
|
(p/do!
|
|
(.setProxy sess config)
|
|
(.forceReloadProxyConfig sess))
|
|
(p/resolved nil)))))
|
|
|
|
(defn- parse-pac-rule
|
|
"Parse Proxy Auto Config(PAC) line"
|
|
[line]
|
|
(let [parts (string/split line #"[ :]")
|
|
type (first parts)]
|
|
(cond
|
|
(= type "DIRECT")
|
|
nil
|
|
|
|
(and (contains? #{"PROXY" "HTTP" "SOCKS"} type)
|
|
(>= (count parts) 3))
|
|
{:protocol (if (= type "SOCKS") "socks5" "http")
|
|
:host (nth parts 1)
|
|
:port (nth parts 2)}
|
|
|
|
:else
|
|
(do
|
|
(logger/warn "Unknown PAC rule:" line)
|
|
nil))))
|
|
|
|
(defn <get-system-proxy
|
|
"Get system proxy for url, requires proxy to be set to system"
|
|
([] (<get-system-proxy "https://www.google.com"))
|
|
([for-url]
|
|
(when-let [sess (.. ^js @*win -webContents -session)]
|
|
(p/let [proxy (.resolveProxy sess for-url)
|
|
pac-opts (->> (string/split proxy #";")
|
|
(map parse-pac-rule)
|
|
(remove nil?))]
|
|
(when (seq pac-opts)
|
|
(first pac-opts))))))
|
|
|
|
(defn <set-proxy
|
|
"Set proxy for electron, fetch"
|
|
([{:keys [type host port] :or {type "system"} :as opts}]
|
|
(logger/info "set proxy to" opts)
|
|
(cond
|
|
(= type "system")
|
|
(p/let [_ (<set-electron-proxy {:type "system"})
|
|
proxy (<get-system-proxy)]
|
|
(set-fetch-agent-proxy proxy))
|
|
|
|
(= type "direct")
|
|
(p/let [_ (<set-electron-proxy {:type "direct"})]
|
|
(set-fetch-agent-proxy nil))
|
|
|
|
(or (= type "socks5") (= type "http"))
|
|
(p/let [_ (<set-electron-proxy {:type type :host host :port port})]
|
|
(set-fetch-agent-proxy {:protocol type :host host :port port}))
|
|
|
|
:else
|
|
(logger/error "Unknown proxy type:" type))))
|
|
|
|
(defn <restore-proxy-settings
|
|
"Restore proxy settings from configs.edn"
|
|
[]
|
|
(let [settings (cfgs/get-item :settings/agent)
|
|
settings (cond
|
|
(:type settings)
|
|
settings
|
|
|
|
;; migration from old config
|
|
(not-empty (:protocol settings))
|
|
(assoc settings :type (:protocol settings))
|
|
|
|
:else
|
|
{:type "system"})]
|
|
(logger/info "restore proxy settings" settings)
|
|
(<set-proxy settings)))
|
|
|
|
(defn save-proxy-settings
|
|
"Save proxy settings to configs.edn"
|
|
[{test' :test :keys [type host port] :or {type "system"}}]
|
|
(if (or (= type "system") (= type "direct"))
|
|
(cfgs/set-item! :settings/agent {:type type :test test'})
|
|
(cfgs/set-item! :settings/agent {:type type :protocol type :host host :port port :test test'})))
|
|
|
|
(defn read-file-raw
|
|
[path]
|
|
(fs/readFileSync path))
|
|
|
|
(defn read-file
|
|
[path]
|
|
(try
|
|
(when (fs/existsSync path)
|
|
(.toString (fs/readFileSync path)))
|
|
(catch :default e
|
|
(logger/error "Read file:" e))))
|
|
|
|
(defn get-focused-window
|
|
[]
|
|
(.getFocusedWindow BrowserWindow))
|
|
|
|
(defn get-win-from-sender
|
|
[^js evt]
|
|
(try
|
|
(.fromWebContents BrowserWindow (.-sender evt))
|
|
(catch :default _
|
|
nil)))
|
|
|
|
(defn send-to-renderer
|
|
"Notice: pass the `window` parameter if you can. Otherwise, the message
|
|
will not be received if there's no focused window.
|
|
Use `send-to-focused-renderer` instead if you want to set a window for fallback"
|
|
([kind payload]
|
|
(send-to-renderer (get-focused-window) kind payload))
|
|
([window kind payload]
|
|
(when window
|
|
(.. ^js window -webContents
|
|
(send (name kind) (bean/->js payload))))))
|
|
|
|
(defn send-to-focused-renderer
|
|
"Try to send to focused window. If no focused window, fallback to the `fallback-win`"
|
|
([kind payload fallback-win]
|
|
(let [focused-win (get-focused-window)
|
|
win (if focused-win focused-win fallback-win)]
|
|
(send-to-renderer win kind payload))))
|
|
|
|
(defn get-graph-dir
|
|
"required by all internal state in the electron section"
|
|
[graph-name]
|
|
(when (and (string? graph-name)
|
|
(string/starts-with? graph-name common-config/db-version-prefix))
|
|
(let [repo (common-config/canonicalize-db-version-repo graph-name)]
|
|
(node-path/join (cli-common-graph/get-db-graphs-dir)
|
|
(graph-dir/repo->encoded-graph-dir-name repo)))))
|
|
|
|
(comment
|
|
(defn get-graph-name
|
|
"Reverse `get-graph-dir`"
|
|
[graph-dir]
|
|
(str common-config/db-version-prefix (node-path/basename graph-dir))))
|
|
|
|
(defn decode-protected-assets-schema-path
|
|
[schema-path]
|
|
(cond-> schema-path
|
|
(string? schema-path)
|
|
(string/replace "/logseq__colon/" ":/")))
|
|
|
|
;; Keep update with the normalization in main
|
|
(defn normalize
|
|
[s]
|
|
(.normalize s "NFC"))
|
|
|
|
(defn normalize-lc
|
|
[s]
|
|
(normalize (string/lower-case s)))
|
|
|
|
(defn safe-decode-uri-component
|
|
[uri]
|
|
(try
|
|
(js/decodeURIComponent uri)
|
|
(catch :default _
|
|
(println "decodeURIComponent failed: " uri)
|
|
uri)))
|
|
|
|
(defn fs-stat->clj
|
|
[path]
|
|
(let [stat (fs/statSync path)]
|
|
{:size (.-size stat)
|
|
:mtime (.-mtime stat)
|
|
:ctime (.-ctime stat)}))
|