Skip to content

Commit

Permalink
Feat: APIs http server (logseq#7699)
Browse files Browse the repository at this point in the history
http server
  • Loading branch information
xyhp915 authored Dec 29, 2022
1 parent 1206d54 commit 51aaa02
Show file tree
Hide file tree
Showing 14 changed files with 1,905 additions and 11 deletions.
1,122 changes: 1,122 additions & 0 deletions resources/docs/api_server.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@logseq/rsapi": "0.0.59",
"electron-deeplink": "1.0.10",
"abort-controller": "3.0.0",
"fastify": "latest",
"command-exists": "1.2.9"
},
"devDependencies": {
Expand Down
4 changes: 3 additions & 1 deletion src/electron/electron/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:as utils]
[electron.url :refer [logseq-url-handler]]
[electron.logger :as logger]
[electron.server :as server]
[clojure.string :as string]
[promesa.core :as p]
[cljs-bean.core :as bean]
Expand Down Expand Up @@ -315,10 +316,11 @@
(let [t1 (setup-updater! win)
t2 (setup-app-manager! win)
t3 (handler/set-ipc-handler! win)
t4 (server/setup! win)
tt (exceptions/setup-exception-listeners!)]

(vreset! *teardown-fn
#(doseq [f [t0 t1 t2 t3 tt]]
#(doseq [f [t0 t1 t2 t3 t4 tt]]
(and f (f)))))))

;; setup effects
Expand Down
10 changes: 10 additions & 0 deletions src/electron/electron/handler.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
[electron.file-sync-rsapi :as rsapi]
[electron.backup-file :as backup-file]
[cljs.reader :as reader]
[electron.server :as server]
[electron.find-in-page :as find]))

(defmulti handle (fn [_window args] (keyword (first args))))
Expand Down Expand Up @@ -678,6 +679,15 @@
(defmethod handle :clear-find-in-page [^js win [_]]
(find/clear! win))

(defmethod handle :server/load-state []
(server/load-state-to-renderer!))

(defmethod handle :server/do [^js _win [_ action]]
(server/do-server! action))

(defmethod handle :server/set-config [^js _win [_ config]]
(server/set-config! config))

(defn set-ipc-handler! [window]
(let [main-channel "main"]
(.handle ipcMain main-channel
Expand Down
167 changes: 167 additions & 0 deletions src/electron/electron/server.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
(ns electron.server
(:require ["fastify" :as Fastify]
["electron" :refer [ipcMain]]
["fs-extra" :as fs-extra]
["path" :as path]
[clojure.string :as string]
[promesa.core :as p]
[cljs-bean.core :as bean]
[electron.utils :as utils]
[camel-snake-kebab.core :as csk]
[electron.logger :as logger]
[electron.configs :as cfgs]))

(defonce ^:private *win (atom nil))
(defonce ^:private *server (atom nil))

(defn get-host [] (or (cfgs/get-item :server/host) "0.0.0.0"))
(defn get-port [] (or (cfgs/get-item :server/port) 12315))

(defonce *state
(atom nil))

(defn- reset-state!
[]
(reset! *state {:status nil ;; :running :starting :closing :closed :error
:error nil
:host (get-host)
:port (get-port)
:tokens (cfgs/get-item :server/tokens)
:autostart (cfgs/get-item :server/autostart)}))

(defn- set-status!
([status] (set-status! status nil))
([status error]
(swap! *state assoc :status status :error error)))

(defn load-state-to-renderer!
([] (load-state-to-renderer! @*state))
([s] (utils/send-to-renderer @*win :syncAPIServerState s)))

(defn set-config!
[config]
(when-let [config (and (map? config) (dissoc config :status))]
(reset! *state (merge @*state config))
(doseq [[k v] config]
(cfgs/set-item! (keyword (str "server/" (name k))) v))
(load-state-to-renderer!)))

(defn- setup-state-watch!
[]
(add-watch *state ::ws #(load-state-to-renderer! %4))
#(remove-watch *state ::ws))

(defn type-proxy-api? [s]
(when (string? s)
(string/starts-with? s "logseq.")))

(defn resolve-real-api-method
[s]
(when-not (string/blank? s)
(if (type-proxy-api? s)
(let [s' (string/split s ".")
tag (second s')
tag' (when (and (not (string/blank? tag))
(contains? #{"ui" "git" "assets"} (string/lower-case tag)))
(str tag "_"))]
(csk/->snake_case (str tag' (last s'))))
(string/trim s))))

(defn- validate-auth-token
[token]
(when-let [valid-tokens (cfgs/get-item :server/tokens)]
(when (or (string/blank? token)
(not (some #(or (= % token)
(= (:value %) token)) valid-tokens)))
(throw (js/Error. "Access Deny!")))))

(defn- api-pre-handler!
[^js req ^js rep callback]
(if (= "/" (.-url req))
(callback)
(try
(let [^js headers (.-headers req)]
(validate-auth-token (.-authorization headers))
(callback))
(catch js/Error e
(-> rep
(.code 401)
(.send e))))))

(defonce ^:private *cid (volatile! 0))
(defn- invoke-logseq-api!
[method args]
(p/create
(fn [resolve _reject]
(let [sid (vswap! *cid inc)
ret-handle (fn [^js _w ret] (resolve ret))]
(utils/send-to-renderer @*win :invokeLogseqAPI {:syncId sid :method method :args args})
(.handleOnce ipcMain (str ::sync! sid) ret-handle)))))

(defn- api-invoker-fn!
[^js req ^js rep]
(if-let [^js body (.-body req)]
(if-let [method (resolve-real-api-method (.-method body))]
(-> (invoke-logseq-api! method (.-args body))
(p/then #(.send rep %))
(p/catch #(.send rep %)))
(-> rep
(.code 400)
(.send (js/Error. ":method of body is missing!"))))
(throw (js/Error. "Body{:method :args} is required!"))))

(defn close!
[]
(when (and @*server (= :running (:status @*state)))
(logger/debug "[server] closing ...")
(set-status! :closing)
(-> (.close @*server)
(p/then (fn []
(reset! *server nil)
(set-status! :closed)))
(p/catch (fn [^js e]
(set-status! :running e))))))

(defn start!
[]
(-> (p/let [_ (close!)
_ (set-status! :starting)
^js s (Fastify. #js {:logger true
:requestTimeout (* 1000 42)
:forceCloseConnections true})
;; hooks & routes
_ (doto s
(.addHook "preHandler" api-pre-handler!)
(.post "/api-invoker" api-invoker-fn!)
(.get "/" (fn [_ ^js rep]
(let [html (fs-extra/readFileSync (.join path js/__dirname "./docs/api_server.html"))
HOST (get-host)
PORT (get-port)
html (-> (str html)
(string/replace-first "${HOST}" HOST)
(string/replace-first "${PORT}" PORT))]
(doto rep (.type "text/html")
(.send html))))))
;; listen port
_ (.listen s (bean/->js (select-keys @*state [:host :port])))]
(reset! *server s)
(set-status! :running))
(p/then (fn [] (logger/debug "[server] start successfully!")))
(p/catch (fn [^js e]
(set-status! :error e)
(logger/error "[server] start error! " e)))))

(defn do-server!
[action]
(case (keyword action)
:start (when (contains? #{nil :closed :error} (:status @*state))
(start!))
:stop (close!)
:restart (start!)
:else :dune))

(defn setup!
[^js win]
(reset! *win win)
(let [t (setup-state-watch!)]
(reset-state!) t))
28 changes: 25 additions & 3 deletions src/main/electron/listener.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
[frontend.handler.ui :as ui-handler]
[frontend.handler.user :as user]
[frontend.state :as state]
[frontend.ui :as ui]))
[frontend.ui :as ui]
[promesa.core :as p]))


(defn persist-dbs!
Expand Down Expand Up @@ -210,8 +211,29 @@
;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage
;; No db cache persisting ensured. Should be handled by the caller
(fn [repo]
(ui-handler/open-new-window! repo))))

(ui-handler/open-new-window! repo)))

(js/window.apis.on "invokeLogseqAPI"
(fn [^js data]
(let [sync-id (.-syncId data)
method (.-method data)
args (.-args data)
ret-fn! #(ipc/invoke (str :electron.server/sync! sync-id) %)]

(try
(println "invokeLogseqAPI:" method)
(let [^js apis (aget js/window.logseq "api")]
(when-not (aget apis method)
(throw (js/Error. (str "MethodNotExist:" method))))
(-> (p/promise (apply js-invoke apis method args))
(p/then #(ret-fn! %))
(p/catch #(ret-fn! {:error %}))))
(catch js/Error e
(ret-fn! {:error (.-message e)}))))))

(js/window.apis.on "syncAPIServerState"
(fn [^js data]
(state/set-state! :electron/server (bean/->clj data)))))

(defn listen!
[]
Expand Down
14 changes: 13 additions & 1 deletion src/main/frontend/components/header.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[frontend.components.export :as export]
[frontend.components.page-menu :as page-menu]
[frontend.components.plugins :as plugins]
[frontend.components.server :as server]
[frontend.components.right-sidebar :as sidebar]
[frontend.components.svg :as svg]
[frontend.config :as config]
Expand Down Expand Up @@ -220,7 +221,18 @@
(mobile-util/native-iphone?))
(state/set-left-sidebar-open! false))
(state/pub-event! [:go/search]))}
(ui/icon "search" {:size ui/icon-size})])))]]
(ui/icon "search" {:size ui/icon-size})])))

(when (mobile-util/native-platform?)
(if (or (state/home?) custom-home-page?)
left-menu
(ui/with-shortcut :go/backward "bottom"
[:button.it.navigation.nav-left.button.icon.opacity-70
{:title "Go back" :on-click #(js/window.history.back)}
(ui/icon "chevron-left" {:size 26})])))

(when (state/feature-http-server-enabled?)
(server/server-indicator (state/sub :electron/server)))]]

[:div.r.flex.drag-region
(when (and current-repo
Expand Down
2 changes: 1 addition & 1 deletion src/main/frontend/components/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
> .l {
@apply pl-2;

width: var(--ls-left-sidebar-width);
min-width: var(--ls-left-sidebar-width);
height: 100%;
align-items: center;
transition: padding-left .2s;
Expand Down
Loading

0 comments on commit 51aaa02

Please sign in to comment.