mirror of
https://github.com/penpot/penpot.git
synced 2025-06-10 21:11:37 +02:00
🎉 Add initial exporter (nodejs) application.
This commit is contained in:
parent
d521416329
commit
c2db6d4f35
10 changed files with 1048 additions and 0 deletions
66
exporter/src/app/browser.cljs
Normal file
66
exporter/src/app/browser.cljs
Normal file
|
@ -0,0 +1,66 @@
|
|||
(ns app.browser
|
||||
(:require
|
||||
[lambdaisland.glogi :as log]
|
||||
[promesa.core :as p]
|
||||
["puppeteer-cluster" :as ppc]))
|
||||
|
||||
(def USER-AGENT
|
||||
(str "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"))
|
||||
|
||||
(defn exec!
|
||||
[browser f]
|
||||
(.execute ^js browser (fn [props]
|
||||
(let [page (unchecked-get props "page")]
|
||||
(f page)))))
|
||||
|
||||
(defn emulate!
|
||||
[page {:keys [viewport user-agent]
|
||||
:or {user-agent USER-AGENT}}]
|
||||
(let [[width height] viewport]
|
||||
(.emulate page #js {:viewport #js {:width width
|
||||
:height height}
|
||||
:userAgent user-agent})))
|
||||
|
||||
(defn navigate!
|
||||
([page url] (navigate! page url nil))
|
||||
([page url {:keys [wait-until]
|
||||
:or {wait-until "networkidle2"}}]
|
||||
(.goto ^js page url #js {:waitUntil wait-until})))
|
||||
|
||||
(defn sleep
|
||||
[page ms]
|
||||
(.waitFor ^js page ms))
|
||||
|
||||
(defn screenshot
|
||||
([page] (screenshot page nil))
|
||||
([page {:keys [full-page?]
|
||||
:or {full-page? true}}]
|
||||
(.screenshot ^js page #js {:fullPage full-page? :omitBackground true})))
|
||||
|
||||
(defn set-cookie!
|
||||
[page {:keys [key value domain]}]
|
||||
(.setCookie ^js page #js {:name key
|
||||
:value value
|
||||
:domain domain}))
|
||||
|
||||
(defn start!
|
||||
([] (start! nil))
|
||||
([{:keys [concurrency concurrency-strategy]
|
||||
:or {concurrency 2
|
||||
concurrency-strategy :browser}}]
|
||||
(let [ccst (case concurrency-strategy
|
||||
:browser (.-CONCURRENCY_BROWSER ^js ppc/Cluster)
|
||||
:incognito (.-CONCURRENCY_CONTEXT ^js ppc/Cluster)
|
||||
:page (.-CONCURRENCY_PAGE ^js ppc/Cluster))
|
||||
opts #js {:concurrency ccst
|
||||
:maxConcurrency concurrency}]
|
||||
(.launch ^js ppc/Cluster opts))))
|
||||
|
||||
(defn stop!
|
||||
[instance]
|
||||
(p/do!
|
||||
(.idle ^js instance)
|
||||
(.close ^js instance)
|
||||
(log/info :msg "shutdown headless browser")
|
||||
nil))
|
20
exporter/src/app/config.cljs
Normal file
20
exporter/src/app/config.cljs
Normal file
|
@ -0,0 +1,20 @@
|
|||
(ns app.config
|
||||
(:require
|
||||
["process" :as process]
|
||||
[cljs.pprint]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn- keywordize
|
||||
[s]
|
||||
(-> (str/kebab s)
|
||||
(str/keyword)))
|
||||
|
||||
(defonce env
|
||||
(let [env (unchecked-get process "env")]
|
||||
(persistent!
|
||||
(reduce #(assoc! %1 (keywordize %2) (unchecked-get env %2))
|
||||
(transient {})
|
||||
(js/Object.keys env)))))
|
||||
|
||||
(defonce config
|
||||
{:domain (:app-domain env "localhost:3449")})
|
36
exporter/src/app/core.cljs
Normal file
36
exporter/src/app/core.cljs
Normal file
|
@ -0,0 +1,36 @@
|
|||
(ns app.core
|
||||
(:require
|
||||
[lambdaisland.glogi :as log]
|
||||
[lambdaisland.glogi.console :as glogi-console]
|
||||
[promesa.core :as p]
|
||||
[app.http :as http]
|
||||
[app.config]
|
||||
[app.browser :as bwr]))
|
||||
|
||||
(glogi-console/install!)
|
||||
(enable-console-print!)
|
||||
|
||||
(defonce state (atom nil))
|
||||
|
||||
(defn start
|
||||
[& args]
|
||||
(log/info :msg "initializing")
|
||||
(p/let [browser (bwr/start!)
|
||||
server (http/start! {:browser browser})]
|
||||
(reset! state {:http server
|
||||
:browser browser})))
|
||||
|
||||
(def main start)
|
||||
|
||||
(defn stop
|
||||
[done]
|
||||
;; an empty line for visual feedback of restart
|
||||
(js/console.log "")
|
||||
|
||||
(log/info :msg "stoping")
|
||||
(p/do!
|
||||
(when-let [instance (:browser @state)]
|
||||
(bwr/stop! instance))
|
||||
(when-let [instance (:http @state)]
|
||||
(http/stop! instance))
|
||||
(done)))
|
83
exporter/src/app/http.cljs
Normal file
83
exporter/src/app/http.cljs
Normal file
|
@ -0,0 +1,83 @@
|
|||
(ns app.http
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
[lambdaisland.glogi :as log]
|
||||
[app.browser :as bwr]
|
||||
[app.http.screenshot :refer [bitmap-handler]]
|
||||
[reitit.core :as r]
|
||||
["koa" :as koa]
|
||||
["http" :as http])
|
||||
(:import
|
||||
goog.Uri))
|
||||
|
||||
(defn query-params
|
||||
"Given goog.Uri, read query parameters into Clojure map."
|
||||
[^goog.Uri uri]
|
||||
(let [q (.getQueryData uri)]
|
||||
(->> q
|
||||
(.getKeys)
|
||||
(map (juxt keyword #(.get q %)))
|
||||
(into {}))))
|
||||
|
||||
(defn- match
|
||||
[router ctx]
|
||||
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))]
|
||||
(when-let [match (r/match-by-path router (.getPath uri))]
|
||||
(let [qparams (query-params uri)
|
||||
params {:path (:path-params match) :query qparams}]
|
||||
(assoc match
|
||||
:params params
|
||||
:query-params qparams)))))
|
||||
|
||||
(defn- handle-response
|
||||
[ctx {:keys [body headers status] :or {headers {} status 200}}]
|
||||
(run! (fn [[k v]] (.set ^js ctx k v)) headers)
|
||||
(set! (.-body ^js ctx) body)
|
||||
(set! (.-status ^js ctx) status)
|
||||
nil)
|
||||
|
||||
(defn- wrap-handler
|
||||
[f extra]
|
||||
(fn [ctx]
|
||||
(let [cookies (unchecked-get ctx "cookies")
|
||||
request (assoc extra :ctx ctx :cookies cookies)]
|
||||
(-> (p/do! (f request))
|
||||
(p/then (fn [rsp]
|
||||
(when (map? rsp)
|
||||
(handle-response ctx rsp))))))))
|
||||
|
||||
(def routes
|
||||
[["/export"
|
||||
["/bitmap" {:handler bitmap-handler}]]])
|
||||
|
||||
(defn- router-handler
|
||||
[router]
|
||||
(fn [{:keys [ctx] :as req}]
|
||||
(let [route (match router ctx)
|
||||
request (assoc req
|
||||
:route route
|
||||
:params (:params route))
|
||||
handler (get-in route [:data :handler])]
|
||||
(if (and route handler)
|
||||
(handler request)
|
||||
{:status 404
|
||||
:body "Not found"}))))
|
||||
|
||||
(defn start!
|
||||
[extra]
|
||||
(log/info :msg "starting http server" :port 6061)
|
||||
(let [router (r/router routes)
|
||||
instance (doto (new koa)
|
||||
(.use (-> (router-handler router)
|
||||
(wrap-handler extra))))
|
||||
server (.createServer http (.callback instance))]
|
||||
(.listen server 6061)
|
||||
(p/resolved server)))
|
||||
|
||||
(defn stop!
|
||||
[server]
|
||||
(p/create (fn [resolve]
|
||||
(.close server (fn []
|
||||
(log/info :msg "shutdown http server")
|
||||
(resolve))))))
|
||||
|
43
exporter/src/app/http/screenshot.cljs
Normal file
43
exporter/src/app/http/screenshot.cljs
Normal file
|
@ -0,0 +1,43 @@
|
|||
(ns app.http.screenshot
|
||||
(:require
|
||||
[app.browser :as bwr]
|
||||
[app.config :as cfg]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn- load-and-screenshot
|
||||
[page url cookie]
|
||||
(p/do!
|
||||
(bwr/emulate! page {:viewport [1920 1080]})
|
||||
(bwr/set-cookie! page cookie)
|
||||
(bwr/navigate! page url)
|
||||
(bwr/sleep page 500)
|
||||
(.evaluate page (js* "() => document.body.style.background = 'transparent'"))
|
||||
(p/let [dom (.$ page "#screenshot")]
|
||||
(.screenshot ^js dom #js {:omitBackground true}))))
|
||||
|
||||
(defn- take-screenshot
|
||||
[browser {:keys [page-id object-id token]}]
|
||||
(letfn [(on-browser [page]
|
||||
(let [url (str "http://" (:domain cfg/config)
|
||||
"/#/render-object/"
|
||||
page-id "/" object-id)
|
||||
cookie {:domain (:domain cfg/config)
|
||||
:key "auth-token"
|
||||
:value token}]
|
||||
(load-and-screenshot page url cookie)))]
|
||||
(bwr/exec! browser on-browser)))
|
||||
|
||||
(defn bitmap-handler
|
||||
[{:keys [params browser cookies] :as request}]
|
||||
(let [page-id (get-in params [:query :page-id])
|
||||
object-id (get-in params [:query :object-id])
|
||||
token (.get ^js cookies "auth-token")]
|
||||
(-> (take-screenshot browser {:page-id page-id
|
||||
:object-id object-id
|
||||
:token token})
|
||||
(p/then (fn [result]
|
||||
{:status 200
|
||||
:body result
|
||||
:headers {"content-type" "image/png"
|
||||
"content-length" (alength result)}})))))
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue