diff --git a/exporter/shadow-cljs.edn b/exporter/shadow-cljs.edn index 3a81cff0f..7a7ca5859 100644 --- a/exporter/shadow-cljs.edn +++ b/exporter/shadow-cljs.edn @@ -1,11 +1,13 @@ {:dependencies - [[funcool/promesa "6.0.0"] + [[com.cognitect/transit-cljs "0.8.269"] [danlentz/clj-uuid "0.1.9"] + [frankiesardo/linked "1.3.0"] [funcool/cuerdas "2021.05.02-0"] + [funcool/promesa "6.0.0"] + [integrant/integrant "0.8.0"] [lambdaisland/glogi "1.0.106"] - [metosin/reitit-core "0.5.13"] - [com.cognitect/transit-cljs "0.8.269"] - [frankiesardo/linked "1.3.0"]] + [lambdaisland/uri "1.4.54"] + [metosin/reitit-core "0.5.13"]] :source-paths ["src" "vendor" "../common"] :jvm-opts ["-Xmx512m" "-Xms50m" "-XX:+UseSerialGC"] diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 374464420..6b487e9f3 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -6,10 +6,13 @@ (ns app.browser (:require + [app.config :as cf] [lambdaisland.glogi :as log] [promesa.core :as p] ["puppeteer-cluster" :as ppc])) +;; --- BROWSER API + (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")) @@ -74,24 +77,38 @@ :value value :domain domain})) -(defn start! - ([] (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)))) +;; --- BROWSER STATE -(defn stop! - [instance] - (p/do! - (.idle ^js instance) - (.close ^js instance) - (log/info :msg "shutdown headless browser") - nil)) +(def instance (atom nil)) + +(defn- create-browser + [concurrency strategy] + (let [strategy (case strategy + :browser (.-CONCURRENCY_BROWSER ^js ppc/Cluster) + :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))) diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs index e9b83c23b..76da48a1c 100644 --- a/exporter/src/app/config.cljs +++ b/exporter/src/app/config.cljs @@ -5,22 +5,54 @@ ;; Copyright (c) UXBOX Labs SL (ns app.config + (:refer-clojure :exclude [get]) (:require ["process" :as process] [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 - [s] - (-> (str/kebab s) - (str/keyword))) +(def defaults + {:public-uri "http://localhost:3449" + :http-server-port 6061 + :browser-concurrency 5 + :browser-strategy :incognito}) -(defonce env - (let [env (unchecked-get process "env")] - (persistent! - (reduce #(assoc! %1 (keywordize %2) (unchecked-get env %2)) - (transient {}) - (js/Object.keys env))))) +(s/def ::public-uri ::us/string) +(s/def ::http-server-port ::us/integer) +(s/def ::browser-concurrency ::us/integer) +(s/def ::browser-strategy ::us/keyword) -(defonce config - {:public-uri (:penpot-public-uri env "http://localhost:3449")}) +(s/def ::config + (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))) diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs index 6bc699476..7fafd8393 100644 --- a/exporter/src/app/core.cljs +++ b/exporter/src/app/core.cljs @@ -21,10 +21,9 @@ (defn start [& args] (log/info :msg "initializing") - (p/let [browser (bwr/start!) - server (http/start! {:browser browser})] - (reset! state {:http server - :browser browser}))) + (p/do! + (bwr/init) + (http/init))) (def main start) @@ -35,8 +34,6 @@ (log/info :msg "stoping") (p/do! - (when-let [instance (:browser @state)] - (bwr/stop! instance)) - (when-let [instance (:http @state)] - (http/stop! instance)) + (bwr/stop) + (http/stop) (done))) diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index d0d32ebf8..4dca3cac4 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -6,29 +6,33 @@ (ns app.http (:require + [app.config :as cf] [app.http.export :refer [export-handler]] - [app.http.thumbnail :refer [thumbnail-handler]] [app.http.impl :as impl] [lambdaisland.glogi :as log] [promesa.core :as p] [reitit.core :as r])) (def routes - [["/export/thumbnail" {:handler thumbnail-handler}] - ["/export" {:handler export-handler}]]) + [["/export" {:handler export-handler}]]) -(defn start! - [extra] - (log/info :msg "starting http server" :port 6061) +(def instance (atom nil)) + +(defn init + [] (let [router (r/router routes) - handler (impl/handler router extra) - server (impl/server handler)] - (.listen server 6061) - (p/resolved server))) + handler (impl/handler router) + server (impl/server handler) + port (cf/get :http-server-port 6061)] + (.listen server port) + (log/info :msg "starting http server" :port port) + (reset! instance server))) -(defn stop! - [server] - (p/create (fn [resolve] - (.close server (fn [] - (log/info :msg "shutdown http server") - (resolve)))))) +(defn stop + [] + (if-let [server @instance] + (p/create (fn [resolve] + (.close server (fn [] + (log/info :msg "shutdown http server") + (resolve))))) + (p/resolved nil))) diff --git a/exporter/src/app/http/export.cljs b/exporter/src/app/http/export.cljs index 15ac3e8ba..8be2f5470 100644 --- a/exporter/src/app/http/export.cljs +++ b/exporter/src/app/http/export.cljs @@ -6,15 +6,15 @@ (ns app.http.export (:require - [app.http.export-bitmap :as bitmap] - [app.http.export-svg :as svg] + [app.common.exceptions :as exc :include-macros true] + [app.common.spec :as us] + [app.renderer.bitmap :as rb] + [app.renderer.svg :as rs] [app.zipfile :as zip] [cljs.spec.alpha :as s] [cuerdas.core :as str] [lambdaisland.glogi :as log] - [promesa.core :as p] - [app.common.exceptions :as exc :include-macros true] - [app.common.spec :as us])) + [promesa.core :as p])) (s/def ::name ::us/string) (s/def ::page-id ::us/uuid) @@ -38,42 +38,44 @@ (declare attach-filename) (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) token (.get ^js cookies "auth-token")] (case (count exports) - 0 (exc/raise :type :validation :code :missing-exports) - 1 (handle-single-export - request - (assoc (first exports) - :name name - :token token - :file-id file-id - :page-id page-id - :object-id object-id)) - (handle-multiple-export - request - (map (fn [item] - (assoc item - :name name - :token token - :file-id file-id - :page-id page-id - :object-id object-id)) exports))))) + 0 (exc/raise :type :validation + :code :missing-exports) + + 1 (-> (first exports) + (assoc :name name) + (assoc :token token) + (assoc :file-id file-id) + (assoc :page-id page-id) + (assoc :object-id object-id) + (handle-single-export)) + + (->> exports + (map (fn [item] + (-> item + (assoc :name name) + (assoc :token token) + (assoc :file-id file-id) + (assoc :page-id page-id) + (assoc :object-id object-id)))) + (handle-multiple-export))))) (defn- handle-single-export - [{:keys [browser]} params] - (p/let [result (perform-export browser params)] + [params] + (p/let [result (perform-export params)] {:status 200 :body (:content result) :headers {"content-type" (:mime-type result) "content-length" (:length result)}})) (defn- handle-multiple-export - [{:keys [browser]} exports] + [exports] (let [proms (->> exports (attach-filename) - (map (partial perform-export browser)))] + (map perform-export))] (-> (p/all proms) (p/then (fn [results] (reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results))) @@ -83,11 +85,11 @@ :body (.generateNodeStream ^js fzip)}))))) (defn- perform-export - [browser params] + [params] (case (:type params) - :png (bitmap/export browser params) - :jpeg (bitmap/export browser params) - :svg (svg/export browser params))) + :png (rb/render params) + :jpeg (rb/render params) + :svg (rs/render params))) (defn- find-filename-candidate [params used] diff --git a/exporter/src/app/http/export_bitmap.cljs b/exporter/src/app/http/export_bitmap.cljs deleted file mode 100644 index cf17a5e6d..000000000 --- a/exporter/src/app/http/export_bitmap.cljs +++ /dev/null @@ -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")})) - diff --git a/exporter/src/app/http/impl.cljs b/exporter/src/app/http/impl.cljs index 15f00ba5b..3401194d4 100644 --- a/exporter/src/app/http/impl.cljs +++ b/exporter/src/app/http/impl.cljs @@ -13,25 +13,15 @@ [app.util.transit :as t] [cuerdas.core :as str] [lambdaisland.glogi :as log] + [lambdaisland.uri :as u] [promesa.core :as p] - [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 {})))) + [reitit.core :as r])) (defn- match [router ctx] - (let [uri (.parse Uri (unchecked-get ctx "originalUrl"))] - (when-let [match (r/match-by-path router (.getPath ^js uri))] - (assoc match :query-params (query-params uri))))) + (let [uri (u/uri (unchecked-get ctx "originalUrl"))] + (when-let [match (r/match-by-path router (:path uri))] + (assoc match :query-params (u/query-string->map (:query uri)))))) (defn- handle-error [error request] @@ -48,17 +38,21 @@ :headers {"content-type" "text/html"} :body (str "
" (:explain data) "\n")})) + (and (= :internal type) + (= :browser-not-ready code)) + {:status 503 + :headers {"x-error" (t/encode data)} + :body ""} + :else (do (log/error :msg "Unexpected error" :error error) (js/console.error error) {:status 500 - :headers {"x-metadata" (t/encode {:type :unexpected - :message (ex-message error)})} + :headers {"x-error" (t/encode data)} :body ""})))) - (defn- handle-response [ctx {:keys [body headers status] :or {headers {} status 200}}] (run! (fn [[k v]] (.set ^js ctx k v)) headers) @@ -89,17 +83,16 @@ (t/decode)))))))) (defn- wrap-handler - [f extra] + [f] (fn [ctx] (p/let [cookies (unchecked-get ctx "cookies") headers (parse-headers ctx) body (parse-body ctx) - request (assoc extra - :method (str/lower (unchecked-get ctx "method")) - :body body - :ctx ctx - :headers headers - :cookies cookies)] + request {:method (str/lower (unchecked-get ctx "method")) + :body body + :ctx ctx + :headers headers + :cookies cookies}] (-> (p/do! (f request)) (p/then (fn [rsp] (when (map? rsp) @@ -131,10 +124,10 @@ (.createServer http @handler)) (defn handler - [router extra] + [router] (let [instance (doto (new koa) (.use (-> (router-handler router) - (wrap-handler extra))))] + (wrap-handler))))] (specify! instance cljs.core/IDeref (-deref [_] diff --git a/exporter/src/app/http/thumbnail.cljs b/exporter/src/app/http/thumbnail.cljs deleted file mode 100644 index f351d67a4..000000000 --- a/exporter/src/app/http/thumbnail.cljs +++ /dev/null @@ -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)}}))) diff --git a/exporter/src/app/renderer/bitmap.cljs b/exporter/src/app/renderer/bitmap.cljs new file mode 100644 index 000000000..79610316f --- /dev/null +++ b/exporter/src/app/renderer/bitmap.cljs @@ -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")}))) + diff --git a/exporter/src/app/http/export_svg.cljs b/exporter/src/app/renderer/svg.cljs similarity index 83% rename from exporter/src/app/http/export_svg.cljs rename to exporter/src/app/renderer/svg.cljs index 13b6bf946..58458c275 100644 --- a/exporter/src/app/http/export_svg.cljs +++ b/exporter/src/app/renderer/svg.cljs @@ -4,24 +4,24 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.http.export-svg +(ns app.renderer.svg (:require ["path" :as path] ["xml-js" :as xml] - [app.browser :as bwr] + [app.browser :as bw] [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.spec :as us] - [app.config :as cfg] + [app.config :as cf] [app.util.shell :as sh] [cljs.spec.alpha :as s] [clojure.walk :as walk] [cuerdas.core :as str] [lambdaisland.glogi :as log] - [promesa.core :as p]) - (:import - goog.Uri)) + [lambdaisland.uri :as u] + [app.renderer.bitmap :refer [create-cookie]] + [promesa.core :as p])) (log/set-level "app.http.export-svg" :trace) @@ -67,7 +67,6 @@ (nil? d) (str/empty? d))))) - (defn flatten-toplevel-svg-elements "Flattens XML data structure if two nested top-side SVG elements found." [item] @@ -165,7 +164,9 @@ ;; objects. (let [vbox (-> (get-in result ["attributes" "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 (assoc "name" "g") (assoc "attributes" {}) @@ -212,8 +213,8 @@ (extract-single-node [node] (log/trace :fn :extract-single-node) - (p/let [attrs (bwr/eval! node extract-element-attrs) - shot (bwr/screenshot node {:omit-background? true :type "png"})] + (p/let [attrs (bw/eval! node extract-element-attrs) + shot (bw/screenshot node {:omit-background? true :type "png"})] {:id (unchecked-get attrs "id") :x (unchecked-get attrs "x") :y (unchecked-get attrs "y") @@ -235,12 +236,12 @@ (process-text-nodes [page] (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)))))) (extract-svg [page] - (p/let [dom (bwr/select page "#screenshot") - xmldata (bwr/eval! dom (fn [elem] (.-outerHTML ^js elem))) + (p/let [dom (bw/select page "#screenshot") + xmldata (bw/eval! dom (fn [elem] (.-outerHTML ^js elem))) nodes (process-text-nodes page) nodes (d/index-by :id nodes) result (replace-text-nodes xmldata nodes)] @@ -253,30 +254,28 @@ (render-in-page [page {:keys [uri cookie] :as rctx}] (p/do! - (bwr/emulate! page {:viewport [1920 1080] + (bw/emulate! page {:viewport [1920 1080] :scale 4}) - (bwr/set-cookie! page cookie) - (bwr/navigate! page uri) - ;; (bwr/wait-for page "#screenshot foreignObject" {:visible true}) - (bwr/sleep page 2000) - ;; (bwr/eval! page (js* "() => document.body.style.background = 'transparent'")) + (bw/set-cookie! page cookie) + (bw/navigate! page uri) + ;; (bw/wait-for page "#screenshot foreignObject" {:visible true}) + (bw/sleep page 2000) + ;; (bw/eval! page (js* "() => document.body.style.background = 'transparent'")) page)) (handle [rctx page] (p/let [page (render-in-page page rctx)] (extract-svg page)))] - (let [path (str "/render-object/" file-id "/" page-id "/" object-id) - uri (doto (Uri. (:public-uri cfg/config)) - (.setPath "/") - (.setFragment path)) - rctx {:cookie {:domain (str (.getDomain uri) ":" (.getPort uri)) - :key "auth-token" - :value token} - :uri (.toString uri)}] - - (log/info :uri (.toString uri)) - (bwr/exec! browser (partial handle rctx))))) + (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) + rctx {:cookie cookie + :uri (str uri)}] + (log/info :uri (:uri rctx)) + (bw/exec! browser (partial handle rctx))))) (s/def ::name ::us/string) (s/def ::suffix ::us/string) @@ -288,18 +287,25 @@ (s/def ::token ::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] :opt-un [::filename])) -(defn export - [browser params] - (us/assert ::export-params params) - (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"})) +(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 (render-object browser params)] + {:content content + :filename (or (:filename params) + (str (:name params) + (:suffix params "") + ".svg")) + :length (alength content) + :mime-type "image/svg+xml"}))) diff --git a/exporter/src/app/util/transit.cljs b/exporter/src/app/util/transit.cljs index 38fbe3d4b..6afaa015e 100644 --- a/exporter/src/app/util/transit.cljs +++ b/exporter/src/app/util/transit.cljs @@ -25,5 +25,5 @@ (defn encode [data] - (let [w (t/writer :json {:handlers +write-handlers+})] + (let [w (t/writer :json-verbose {:handlers +write-handlers+})] (t/write w data)))