♻️ Refactor exporter state initialization.

This commit is contained in:
Andrey Antukh 2021-05-05 09:28:12 +02:00 committed by Andrés Moya
parent 4825efb582
commit abb244c940
12 changed files with 308 additions and 287 deletions

View file

@ -1,11 +1,13 @@
{:dependencies {:dependencies
[[funcool/promesa "6.0.0"] [[com.cognitect/transit-cljs "0.8.269"]
[danlentz/clj-uuid "0.1.9"] [danlentz/clj-uuid "0.1.9"]
[frankiesardo/linked "1.3.0"]
[funcool/cuerdas "2021.05.02-0"] [funcool/cuerdas "2021.05.02-0"]
[funcool/promesa "6.0.0"]
[integrant/integrant "0.8.0"]
[lambdaisland/glogi "1.0.106"] [lambdaisland/glogi "1.0.106"]
[metosin/reitit-core "0.5.13"] [lambdaisland/uri "1.4.54"]
[com.cognitect/transit-cljs "0.8.269"] [metosin/reitit-core "0.5.13"]]
[frankiesardo/linked "1.3.0"]]
:source-paths ["src" "vendor" "../common"] :source-paths ["src" "vendor" "../common"]
:jvm-opts ["-Xmx512m" "-Xms50m" "-XX:+UseSerialGC"] :jvm-opts ["-Xmx512m" "-Xms50m" "-XX:+UseSerialGC"]

View file

@ -6,10 +6,13 @@
(ns app.browser (ns app.browser
(:require (:require
[app.config :as cf]
[lambdaisland.glogi :as log] [lambdaisland.glogi :as log]
[promesa.core :as p] [promesa.core :as p]
["puppeteer-cluster" :as ppc])) ["puppeteer-cluster" :as ppc]))
;; --- BROWSER API
(def USER-AGENT (def USER-AGENT
(str "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " (str "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36")) "(KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"))
@ -74,24 +77,38 @@
:value value :value value
:domain domain})) :domain domain}))
(defn start! ;; --- BROWSER STATE
([] (start! nil))
([{:keys [concurrency concurrency-strategy]
:or {concurrency 10
concurrency-strategy :incognito}}]
(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
:puppeteerOptions #js {:args #js ["--no-sandbox"]}}]
(.launch ^js ppc/Cluster opts))))
(defn stop! (def instance (atom nil))
[instance]
(p/do! (defn- create-browser
(.idle ^js instance) [concurrency strategy]
(.close ^js instance) (let [strategy (case strategy
(log/info :msg "shutdown headless browser") :browser (.-CONCURRENCY_BROWSER ^js ppc/Cluster)
nil)) :incognito (.-CONCURRENCY_CONTEXT ^js ppc/Cluster)
:page (.-CONCURRENCY_PAGE ^js ppc/Cluster))
opts #js {:concurrency strategy
:maxConcurrency concurrency
:puppeteerOptions #js {:args #js ["--no-sandbox"]}}]
(.launch ^js ppc/Cluster opts)))
(defn init
[]
(let [concurrency (cf/get :browser-concurrency)
strategy (cf/get :browser-strategy)]
(-> (create-browser concurrency strategy)
(p/then #(reset! instance %))
(p/catch (fn [error]
(log/error :msg "failed to initialize browser")
(js/console.error error))))))
(defn stop
[]
(if-let [instance @instance]
(p/do!
(.idle ^js instance)
(.close ^js instance)
(log/info :msg "shutdown headless browser"))
(p/resolved nil)))

View file

@ -5,22 +5,54 @@
;; Copyright (c) UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.config (ns app.config
(:refer-clojure :exclude [get])
(:require (:require
["process" :as process] ["process" :as process]
[cljs.pprint] [cljs.pprint]
[cuerdas.core :as str])) [cuerdas.core :as str]
[app.common.spec :as us]
[cljs.spec.alpha :as s]
[cljs.core :as c]
[lambdaisland.uri :as u]))
(defn- keywordize (def defaults
[s] {:public-uri "http://localhost:3449"
(-> (str/kebab s) :http-server-port 6061
(str/keyword))) :browser-concurrency 5
:browser-strategy :incognito})
(defonce env (s/def ::public-uri ::us/string)
(let [env (unchecked-get process "env")] (s/def ::http-server-port ::us/integer)
(persistent! (s/def ::browser-concurrency ::us/integer)
(reduce #(assoc! %1 (keywordize %2) (unchecked-get env %2)) (s/def ::browser-strategy ::us/keyword)
(transient {})
(js/Object.keys env)))))
(defonce config (s/def ::config
{:public-uri (:penpot-public-uri env "http://localhost:3449")}) (s/keys :opt-un [::public-uri
::http-server-port
::browser-concurrency
::browser-strategy]))
(defn- read-env
[prefix]
(let [env (unchecked-get process "env")
kwd (fn [s] (-> (str/kebab s) (str/keyword)))
prefix (str prefix "-")
len (count prefix)]
(reduce (fn [res key]
(cond-> res
(str/starts-with? key prefix)
(assoc (kwd (subs key len))
(unchecked-get env key))))
{}
(js/Object.keys env))))
(def config
(atom (->> (read-env "penpot")
(merge defaults)
(us/conform ::config))))
(defn get
"A configuration getter."
([key]
(c/get @config key))
([key default]
(c/get @config key default)))

View file

@ -21,10 +21,9 @@
(defn start (defn start
[& args] [& args]
(log/info :msg "initializing") (log/info :msg "initializing")
(p/let [browser (bwr/start!) (p/do!
server (http/start! {:browser browser})] (bwr/init)
(reset! state {:http server (http/init)))
:browser browser})))
(def main start) (def main start)
@ -35,8 +34,6 @@
(log/info :msg "stoping") (log/info :msg "stoping")
(p/do! (p/do!
(when-let [instance (:browser @state)] (bwr/stop)
(bwr/stop! instance)) (http/stop)
(when-let [instance (:http @state)]
(http/stop! instance))
(done))) (done)))

View file

@ -6,29 +6,33 @@
(ns app.http (ns app.http
(:require (:require
[app.config :as cf]
[app.http.export :refer [export-handler]] [app.http.export :refer [export-handler]]
[app.http.thumbnail :refer [thumbnail-handler]]
[app.http.impl :as impl] [app.http.impl :as impl]
[lambdaisland.glogi :as log] [lambdaisland.glogi :as log]
[promesa.core :as p] [promesa.core :as p]
[reitit.core :as r])) [reitit.core :as r]))
(def routes (def routes
[["/export/thumbnail" {:handler thumbnail-handler}] [["/export" {:handler export-handler}]])
["/export" {:handler export-handler}]])
(defn start! (def instance (atom nil))
[extra]
(log/info :msg "starting http server" :port 6061) (defn init
[]
(let [router (r/router routes) (let [router (r/router routes)
handler (impl/handler router extra) handler (impl/handler router)
server (impl/server handler)] server (impl/server handler)
(.listen server 6061) port (cf/get :http-server-port 6061)]
(p/resolved server))) (.listen server port)
(log/info :msg "starting http server" :port port)
(reset! instance server)))
(defn stop! (defn stop
[server] []
(p/create (fn [resolve] (if-let [server @instance]
(.close server (fn [] (p/create (fn [resolve]
(log/info :msg "shutdown http server") (.close server (fn []
(resolve)))))) (log/info :msg "shutdown http server")
(resolve)))))
(p/resolved nil)))

View file

@ -6,15 +6,15 @@
(ns app.http.export (ns app.http.export
(:require (:require
[app.http.export-bitmap :as bitmap] [app.common.exceptions :as exc :include-macros true]
[app.http.export-svg :as svg] [app.common.spec :as us]
[app.renderer.bitmap :as rb]
[app.renderer.svg :as rs]
[app.zipfile :as zip] [app.zipfile :as zip]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[lambdaisland.glogi :as log] [lambdaisland.glogi :as log]
[promesa.core :as p] [promesa.core :as p]))
[app.common.exceptions :as exc :include-macros true]
[app.common.spec :as us]))
(s/def ::name ::us/string) (s/def ::name ::us/string)
(s/def ::page-id ::us/uuid) (s/def ::page-id ::us/uuid)
@ -38,42 +38,44 @@
(declare attach-filename) (declare attach-filename)
(defn export-handler (defn export-handler
[{:keys [params browser cookies] :as request}] [{:keys [params cookies] :as request}]
(let [{:keys [exports page-id file-id object-id name]} (us/conform ::handler-params params) (let [{:keys [exports page-id file-id object-id name]} (us/conform ::handler-params params)
token (.get ^js cookies "auth-token")] token (.get ^js cookies "auth-token")]
(case (count exports) (case (count exports)
0 (exc/raise :type :validation :code :missing-exports) 0 (exc/raise :type :validation
1 (handle-single-export :code :missing-exports)
request
(assoc (first exports) 1 (-> (first exports)
:name name (assoc :name name)
:token token (assoc :token token)
:file-id file-id (assoc :file-id file-id)
:page-id page-id (assoc :page-id page-id)
:object-id object-id)) (assoc :object-id object-id)
(handle-multiple-export (handle-single-export))
request
(map (fn [item] (->> exports
(assoc item (map (fn [item]
:name name (-> item
:token token (assoc :name name)
:file-id file-id (assoc :token token)
:page-id page-id (assoc :file-id file-id)
:object-id object-id)) exports))))) (assoc :page-id page-id)
(assoc :object-id object-id))))
(handle-multiple-export)))))
(defn- handle-single-export (defn- handle-single-export
[{:keys [browser]} params] [params]
(p/let [result (perform-export browser params)] (p/let [result (perform-export params)]
{:status 200 {:status 200
:body (:content result) :body (:content result)
:headers {"content-type" (:mime-type result) :headers {"content-type" (:mime-type result)
"content-length" (:length result)}})) "content-length" (:length result)}}))
(defn- handle-multiple-export (defn- handle-multiple-export
[{:keys [browser]} exports] [exports]
(let [proms (->> exports (let [proms (->> exports
(attach-filename) (attach-filename)
(map (partial perform-export browser)))] (map perform-export))]
(-> (p/all proms) (-> (p/all proms)
(p/then (fn [results] (p/then (fn [results]
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results))) (reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
@ -83,11 +85,11 @@
:body (.generateNodeStream ^js fzip)}))))) :body (.generateNodeStream ^js fzip)})))))
(defn- perform-export (defn- perform-export
[browser params] [params]
(case (:type params) (case (:type params)
:png (bitmap/export browser params) :png (rb/render params)
:jpeg (bitmap/export browser params) :jpeg (rb/render params)
:svg (svg/export browser params))) :svg (rs/render params)))
(defn- find-filename-candidate (defn- find-filename-candidate
[params used] [params used]

View file

@ -1,80 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.export-bitmap
(:require
[cuerdas.core :as str]
[app.browser :as bwr]
[app.config :as cfg]
[lambdaisland.glogi :as log]
[cljs.spec.alpha :as s]
[promesa.core :as p]
[app.common.exceptions :as exc :include-macros true]
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.spec :as us])
(:import
goog.Uri))
(defn screenshot-object
[browser {:keys [file-id page-id object-id token scale type]}]
(letfn [(handle [page]
(let [path (str "/render-object/" file-id "/" page-id "/" object-id)
uri (doto (Uri. (:public-uri cfg/config))
(.setPath "/")
(.setFragment path))
cookie {:domain (str (.getDomain uri)
":"
(.getPort uri))
:key "auth-token"
:value token}]
(log/info :uri (.toString uri))
(screenshot page (.toString uri) cookie)))
(screenshot [page uri cookie]
(p/do!
(bwr/emulate! page {:viewport [1920 1080]
:scale scale})
(bwr/set-cookie! page cookie)
(bwr/navigate! page uri)
(bwr/eval! page (js* "() => document.body.style.background = 'transparent'"))
(p/let [dom (bwr/select page "#screenshot")]
(case type
:png (bwr/screenshot dom {:omit-background? true :type type})
:jpeg (bwr/screenshot dom {:omit-background? false :type type})))))]
(bwr/exec! browser handle)))
(s/def ::name ::us/string)
(s/def ::suffix ::us/string)
(s/def ::type #{:jpeg :png})
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::token ::us/string)
(s/def ::filename ::us/string)
(s/def ::export-params
(s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::scale ::token ::file-id]
:opt-un [::filename]))
(defn export
[browser params]
(us/assert ::export-params params)
(p/let [content (screenshot-object browser params)]
{:content content
:filename (or (:filename params)
(str (:name params)
(:suffix params "")
(case (:type params)
:png ".png"
:jpeg ".jpg")))
:length (alength content)
:mime-type (case (:type params)
:png "image/png"
:jpeg "image/jpeg")}))

View file

@ -13,25 +13,15 @@
[app.util.transit :as t] [app.util.transit :as t]
[cuerdas.core :as str] [cuerdas.core :as str]
[lambdaisland.glogi :as log] [lambdaisland.glogi :as log]
[lambdaisland.uri :as u]
[promesa.core :as p] [promesa.core :as p]
[reitit.core :as r]) [reitit.core :as r]))
(:import
goog.Uri))
(defn- query-params
"Given goog.Uri, read query parameters into Clojure map."
[^Uri uri]
(let [^js q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(.get q %)))
(into {}))))
(defn- match (defn- match
[router ctx] [router ctx]
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))] (let [uri (u/uri (unchecked-get ctx "originalUrl"))]
(when-let [match (r/match-by-path router (.getPath ^js uri))] (when-let [match (r/match-by-path router (:path uri))]
(assoc match :query-params (query-params uri))))) (assoc match :query-params (u/query-string->map (:query uri))))))
(defn- handle-error (defn- handle-error
[error request] [error request]
@ -48,17 +38,21 @@
:headers {"content-type" "text/html"} :headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>" (:explain data) "</pre>\n")})) :body (str "<pre style='font-size:16px'>" (:explain data) "</pre>\n")}))
(and (= :internal type)
(= :browser-not-ready code))
{:status 503
:headers {"x-error" (t/encode data)}
:body ""}
:else :else
(do (do
(log/error :msg "Unexpected error" (log/error :msg "Unexpected error"
:error error) :error error)
(js/console.error error) (js/console.error error)
{:status 500 {:status 500
:headers {"x-metadata" (t/encode {:type :unexpected :headers {"x-error" (t/encode data)}
:message (ex-message error)})}
:body ""})))) :body ""}))))
(defn- handle-response (defn- handle-response
[ctx {:keys [body headers status] :or {headers {} status 200}}] [ctx {:keys [body headers status] :or {headers {} status 200}}]
(run! (fn [[k v]] (.set ^js ctx k v)) headers) (run! (fn [[k v]] (.set ^js ctx k v)) headers)
@ -89,17 +83,16 @@
(t/decode)))))))) (t/decode))))))))
(defn- wrap-handler (defn- wrap-handler
[f extra] [f]
(fn [ctx] (fn [ctx]
(p/let [cookies (unchecked-get ctx "cookies") (p/let [cookies (unchecked-get ctx "cookies")
headers (parse-headers ctx) headers (parse-headers ctx)
body (parse-body ctx) body (parse-body ctx)
request (assoc extra request {:method (str/lower (unchecked-get ctx "method"))
:method (str/lower (unchecked-get ctx "method")) :body body
:body body :ctx ctx
:ctx ctx :headers headers
:headers headers :cookies cookies}]
:cookies cookies)]
(-> (p/do! (f request)) (-> (p/do! (f request))
(p/then (fn [rsp] (p/then (fn [rsp]
(when (map? rsp) (when (map? rsp)
@ -131,10 +124,10 @@
(.createServer http @handler)) (.createServer http @handler))
(defn handler (defn handler
[router extra] [router]
(let [instance (doto (new koa) (let [instance (doto (new koa)
(.use (-> (router-handler router) (.use (-> (router-handler router)
(wrap-handler extra))))] (wrap-handler))))]
(specify! instance (specify! instance
cljs.core/IDeref cljs.core/IDeref
(-deref [_] (-deref [_]

View file

@ -1,43 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.thumbnail
(:require
[app.common.exceptions :as exc :include-macros true]
[app.common.spec :as us]
[app.http.export-bitmap :as bitmap]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[lambdaisland.glogi :as log]
[promesa.core :as p]))
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::handler-params
(s/keys :req-un [::page-id ::file-id ::object-id]))
(declare handle-single-export)
(declare handle-multiple-export)
(declare perform-export)
(declare attach-filename)
(defn thumbnail-handler
[{:keys [params browser cookies] :as request}]
(let [{:keys [page-id file-id object-id]} (us/conform ::handler-params params)
params {:token (.get ^js cookies "auth-token")
:file-id file-id
:page-id page-id
:object-id object-id
:scale 0.3
:type :jpeg}]
(p/let [content (bitmap/screenshot-object browser params)]
{:status 200
:body content
:headers {"content-type" "image/jpeg"
"content-length" (alength content)}})))

View file

@ -0,0 +1,91 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.renderer.bitmap
"A bitmap renderer."
(:require
[app.browser :as bw]
[app.common.data :as d]
[app.common.exceptions :as ex :include-macros true]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.config :as cf]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[lambdaisland.uri :as u]
[lambdaisland.glogi :as log]
[promesa.core :as p]))
(defn create-cookie
[uri token]
(let [domain (str (:host uri)
(when (:port uri)
(str ":" (:port uri))))]
{:domain domain
:key "auth-token"
:value token}))
(defn screenshot-object
[browser {:keys [file-id page-id object-id token scale type]}]
(letfn [(handle [page]
(let [path (str "/render-object/" file-id "/" page-id "/" object-id)
uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/")
(assoc :fragment path))
cookie (create-cookie uri token)]
(screenshot page (str uri) cookie)))
(screenshot [page uri cookie]
(log/info :uri uri)
(p/do!
(bw/emulate! page {:viewport [1920 1080]
:scale scale})
(bw/set-cookie! page cookie)
(bw/navigate! page uri)
(bw/eval! page (js* "() => document.body.style.background = 'transparent'"))
(p/let [dom (bw/select page "#screenshot")]
(case type
:png (bw/screenshot dom {:omit-background? true :type type})
:jpeg (bw/screenshot dom {:omit-background? false :type type})))))]
(bw/exec! browser handle)))
(s/def ::name ::us/string)
(s/def ::suffix ::us/string)
(s/def ::type #{:jpeg :png})
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::token ::us/string)
(s/def ::filename ::us/string)
(s/def ::render-params
(s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::scale ::token ::file-id]
:opt-un [::filename]))
(defn render
[params]
(us/assert ::render-params params)
(let [browser @bw/instance]
(when-not browser
(ex/raise :type :internal
:code :browser-not-ready
:hint "browser cluster is not initialized yet"))
(p/let [content (screenshot-object browser params)]
{:content content
:filename (or (:filename params)
(str (:name params)
(:suffix params "")
(case (:type params)
:png ".png"
:jpeg ".jpg")))
:length (alength content)
:mime-type (case (:type params)
:png "image/png"
:jpeg "image/jpeg")})))

View file

@ -4,24 +4,24 @@
;; ;;
;; Copyright (c) UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.http.export-svg (ns app.renderer.svg
(:require (:require
["path" :as path] ["path" :as path]
["xml-js" :as xml] ["xml-js" :as xml]
[app.browser :as bwr] [app.browser :as bw]
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as exc :include-macros true] [app.common.exceptions :as ex :include-macros true]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.spec :as us] [app.common.spec :as us]
[app.config :as cfg] [app.config :as cf]
[app.util.shell :as sh] [app.util.shell :as sh]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[clojure.walk :as walk] [clojure.walk :as walk]
[cuerdas.core :as str] [cuerdas.core :as str]
[lambdaisland.glogi :as log] [lambdaisland.glogi :as log]
[promesa.core :as p]) [lambdaisland.uri :as u]
(:import [app.renderer.bitmap :refer [create-cookie]]
goog.Uri)) [promesa.core :as p]))
(log/set-level "app.http.export-svg" :trace) (log/set-level "app.http.export-svg" :trace)
@ -67,7 +67,6 @@
(nil? d) (nil? d)
(str/empty? d))))) (str/empty? d)))))
(defn flatten-toplevel-svg-elements (defn flatten-toplevel-svg-elements
"Flattens XML data structure if two nested top-side SVG elements found." "Flattens XML data structure if two nested top-side SVG elements found."
[item] [item]
@ -165,7 +164,9 @@
;; objects. ;; objects.
(let [vbox (-> (get-in result ["attributes" "viewBox"]) (let [vbox (-> (get-in result ["attributes" "viewBox"])
(parse-viewbox)) (parse-viewbox))
transform (str/fmt "translate(%s, %s) scale(%s, %s)" x y (/ width (:width vbox)) (/ height (:height vbox)))] transform (str/fmt "translate(%s, %s) scale(%s, %s)" x y
(/ width (:width vbox))
(/ height (:height vbox)))]
(-> result (-> result
(assoc "name" "g") (assoc "name" "g")
(assoc "attributes" {}) (assoc "attributes" {})
@ -212,8 +213,8 @@
(extract-single-node [node] (extract-single-node [node]
(log/trace :fn :extract-single-node) (log/trace :fn :extract-single-node)
(p/let [attrs (bwr/eval! node extract-element-attrs) (p/let [attrs (bw/eval! node extract-element-attrs)
shot (bwr/screenshot node {:omit-background? true :type "png"})] shot (bw/screenshot node {:omit-background? true :type "png"})]
{:id (unchecked-get attrs "id") {:id (unchecked-get attrs "id")
:x (unchecked-get attrs "x") :x (unchecked-get attrs "x")
:y (unchecked-get attrs "y") :y (unchecked-get attrs "y")
@ -235,12 +236,12 @@
(process-text-nodes [page] (process-text-nodes [page]
(log/trace :fn :process-text-nodes) (log/trace :fn :process-text-nodes)
(-> (bwr/select-all page "#screenshot foreignObject") (-> (bw/select-all page "#screenshot foreignObject")
(p/then (fn [nodes] (p/all (map process-text-node nodes)))))) (p/then (fn [nodes] (p/all (map process-text-node nodes))))))
(extract-svg [page] (extract-svg [page]
(p/let [dom (bwr/select page "#screenshot") (p/let [dom (bw/select page "#screenshot")
xmldata (bwr/eval! dom (fn [elem] (.-outerHTML ^js elem))) xmldata (bw/eval! dom (fn [elem] (.-outerHTML ^js elem)))
nodes (process-text-nodes page) nodes (process-text-nodes page)
nodes (d/index-by :id nodes) nodes (d/index-by :id nodes)
result (replace-text-nodes xmldata nodes)] result (replace-text-nodes xmldata nodes)]
@ -253,30 +254,28 @@
(render-in-page [page {:keys [uri cookie] :as rctx}] (render-in-page [page {:keys [uri cookie] :as rctx}]
(p/do! (p/do!
(bwr/emulate! page {:viewport [1920 1080] (bw/emulate! page {:viewport [1920 1080]
:scale 4}) :scale 4})
(bwr/set-cookie! page cookie) (bw/set-cookie! page cookie)
(bwr/navigate! page uri) (bw/navigate! page uri)
;; (bwr/wait-for page "#screenshot foreignObject" {:visible true}) ;; (bw/wait-for page "#screenshot foreignObject" {:visible true})
(bwr/sleep page 2000) (bw/sleep page 2000)
;; (bwr/eval! page (js* "() => document.body.style.background = 'transparent'")) ;; (bw/eval! page (js* "() => document.body.style.background = 'transparent'"))
page)) page))
(handle [rctx page] (handle [rctx page]
(p/let [page (render-in-page page rctx)] (p/let [page (render-in-page page rctx)]
(extract-svg page)))] (extract-svg page)))]
(let [path (str "/render-object/" file-id "/" page-id "/" object-id) (let [path (str "/render-object/" file-id "/" page-id "/" object-id)
uri (doto (Uri. (:public-uri cfg/config)) uri (-> (u/uri (cf/get :public-uri))
(.setPath "/") (assoc :path "/")
(.setFragment path)) (assoc :fragment path))
rctx {:cookie {:domain (str (.getDomain uri) ":" (.getPort uri)) cookie (create-cookie uri token)
:key "auth-token" rctx {:cookie cookie
:value token} :uri (str uri)}]
:uri (.toString uri)}] (log/info :uri (:uri rctx))
(bw/exec! browser (partial handle rctx)))))
(log/info :uri (.toString uri))
(bwr/exec! browser (partial handle rctx)))))
(s/def ::name ::us/string) (s/def ::name ::us/string)
(s/def ::suffix ::us/string) (s/def ::suffix ::us/string)
@ -288,18 +287,25 @@
(s/def ::token ::us/string) (s/def ::token ::us/string)
(s/def ::filename ::us/string) (s/def ::filename ::us/string)
(s/def ::export-params (s/def ::render-params
(s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::file-id ::scale ::token] (s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::file-id ::scale ::token]
:opt-un [::filename])) :opt-un [::filename]))
(defn export (defn render
[browser params] [params]
(us/assert ::export-params params) (us/assert ::render-params params)
(p/let [content (render-object browser params)] (let [browser @bw/instance]
{:content content (when-not browser
:filename (or (:filename params) (ex/raise :type :internal
(str (:name params) :code :browser-not-ready
(:suffix params "") :hint "browser cluster is not initialized yet"))
".svg"))
:length (alength content)
:mime-type "image/svg+xml"})) (p/let [content (render-object browser params)]
{:content content
:filename (or (:filename params)
(str (:name params)
(:suffix params "")
".svg"))
:length (alength content)
:mime-type "image/svg+xml"})))

View file

@ -25,5 +25,5 @@
(defn encode (defn encode
[data] [data]
(let [w (t/writer :json {:handlers +write-handlers+})] (let [w (t/writer :json-verbose {:handlers +write-handlers+})]
(t/write w data))) (t/write w data)))