2021-02-15 12:15:16 +01:00
|
|
|
;; 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/.
|
|
|
|
;;
|
2021-04-10 09:43:04 +02:00
|
|
|
;; Copyright (c) UXBOX Labs SL
|
2021-02-15 12:15:16 +01:00
|
|
|
|
2020-06-29 16:07:48 +02:00
|
|
|
(ns app.browser
|
|
|
|
(:require
|
2021-08-19 14:17:51 +02:00
|
|
|
["generic-pool" :as gp]
|
2021-11-16 11:58:59 +01:00
|
|
|
["puppeteer-core" :as pp]
|
2021-05-14 14:13:46 +02:00
|
|
|
[app.common.data :as d]
|
2021-11-16 11:58:59 +01:00
|
|
|
[app.common.logging :as l]
|
2021-08-19 14:17:51 +02:00
|
|
|
[app.common.uuid :as uuid]
|
2021-05-05 09:28:12 +02:00
|
|
|
[app.config :as cf]
|
2021-05-14 14:13:46 +02:00
|
|
|
[promesa.core :as p]))
|
2020-06-29 16:07:48 +02:00
|
|
|
|
2021-11-16 11:58:59 +01:00
|
|
|
|
|
|
|
(l/set-level! :trace)
|
|
|
|
|
2021-05-05 09:28:12 +02:00
|
|
|
;; --- BROWSER API
|
|
|
|
|
2021-05-14 14:13:46 +02:00
|
|
|
(def default-timeout 30000)
|
|
|
|
(def default-viewport {:width 1920 :height 1080 :scale 1})
|
|
|
|
(def default-user-agent
|
2020-06-29 16:07:48 +02:00
|
|
|
(str "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
|
|
"(KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"))
|
|
|
|
|
2021-05-14 14:13:46 +02:00
|
|
|
(defn set-cookie!
|
|
|
|
[page {:keys [key value domain]}]
|
|
|
|
(.setCookie ^js page #js {:name key
|
|
|
|
:value value
|
|
|
|
:domain domain}))
|
|
|
|
|
|
|
|
(defn configure-page!
|
|
|
|
[page {:keys [timeout cookie user-agent viewport]}]
|
|
|
|
(let [timeout (or timeout default-timeout)
|
|
|
|
user-agent (or user-agent default-user-agent)
|
|
|
|
viewport (d/merge default-viewport viewport)]
|
|
|
|
(p/do!
|
|
|
|
(.setViewport ^js page #js {:width (:width viewport)
|
|
|
|
:height (:height viewport)
|
|
|
|
:deviceScaleFactor (:scale viewport)})
|
|
|
|
(.setUserAgent ^js page user-agent)
|
|
|
|
(.setDefaultTimeout ^js page timeout)
|
|
|
|
(when cookie
|
|
|
|
(set-cookie! page cookie)))))
|
2020-06-29 16:07:48 +02:00
|
|
|
|
|
|
|
(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]
|
2021-03-02 10:51:06 +01:00
|
|
|
(.waitForTimeout ^js page ms))
|
|
|
|
|
|
|
|
(defn wait-for
|
|
|
|
([page selector] (wait-for page selector nil))
|
2021-05-14 14:13:46 +02:00
|
|
|
([page selector {:keys [visible timeout] :or {visible false timeout 10000}}]
|
2021-03-02 10:51:06 +01:00
|
|
|
(.waitForSelector ^js page selector #js {:visible visible})))
|
2020-06-29 16:07:48 +02:00
|
|
|
|
|
|
|
(defn screenshot
|
2020-07-02 14:48:17 +02:00
|
|
|
([frame] (screenshot frame nil))
|
2020-07-30 15:23:11 +02:00
|
|
|
([frame {:keys [full-page? omit-background? type]
|
2020-07-02 14:48:17 +02:00
|
|
|
:or {full-page? false
|
2020-07-30 15:23:11 +02:00
|
|
|
type "png"
|
2020-07-02 14:48:17 +02:00
|
|
|
omit-background? false}}]
|
|
|
|
(.screenshot ^js frame #js {:fullPage full-page?
|
2022-01-18 16:58:00 +01:00
|
|
|
:clip nil
|
2020-07-30 15:23:11 +02:00
|
|
|
:type (name type)
|
2020-07-02 14:48:17 +02:00
|
|
|
:omitBackground omit-background?})))
|
|
|
|
|
2021-07-02 13:19:04 +02:00
|
|
|
(defn pdf
|
|
|
|
([page] (pdf page nil))
|
2022-01-18 16:58:00 +01:00
|
|
|
([page {:keys [viewport save-path]}]
|
|
|
|
(p/let [viewport (d/merge default-viewport viewport)]
|
|
|
|
(.emulateMediaType ^js page "screen")
|
2021-08-30 16:54:27 +02:00
|
|
|
(.pdf ^js page #js {:path save-path
|
|
|
|
:width (:width viewport)
|
2021-07-02 13:19:04 +02:00
|
|
|
:height (:height viewport)
|
|
|
|
:scale (:scale viewport)
|
2022-01-18 16:58:00 +01:00
|
|
|
:printBackground true
|
|
|
|
:preferCSSPageSize false}))))
|
2020-07-02 14:48:17 +02:00
|
|
|
(defn eval!
|
|
|
|
[frame f]
|
|
|
|
(.evaluate ^js frame f))
|
|
|
|
|
|
|
|
(defn select
|
|
|
|
[frame selector]
|
|
|
|
(.$ ^js frame selector))
|
2020-06-29 16:07:48 +02:00
|
|
|
|
2020-07-30 15:23:11 +02:00
|
|
|
(defn select-all
|
|
|
|
[frame selector]
|
|
|
|
(.$$ ^js frame selector))
|
|
|
|
|
2020-06-29 16:07:48 +02:00
|
|
|
|
2021-05-05 09:28:12 +02:00
|
|
|
;; --- BROWSER STATE
|
|
|
|
|
2021-08-19 14:17:51 +02:00
|
|
|
(defonce pool (atom nil))
|
|
|
|
(defonce pool-browser-id (atom 1))
|
|
|
|
|
|
|
|
(def browser-pool-factory
|
|
|
|
(letfn [(create []
|
|
|
|
(let [path (cf/get :browser-executable-path "/usr/bin/google-chrome")]
|
2021-11-05 12:11:28 +01:00
|
|
|
(-> (pp/launch #js {:executablePath path :args #js ["--no-sandbox" "--font-render-hinting=none"]})
|
2021-08-19 14:17:51 +02:00
|
|
|
(p/then (fn [browser]
|
|
|
|
(let [id (deref pool-browser-id)]
|
2021-11-16 11:58:59 +01:00
|
|
|
(l/info :origin "factory" :action "create" :browser-id id)
|
2021-08-19 14:17:51 +02:00
|
|
|
(unchecked-set browser "__id" id)
|
|
|
|
(swap! pool-browser-id inc)
|
|
|
|
browser))))))
|
|
|
|
(destroy [obj]
|
|
|
|
(let [id (unchecked-get obj "__id")]
|
2021-11-16 11:58:59 +01:00
|
|
|
(l/info :origin "factory" :action "destroy" :browser-id id)
|
2021-08-19 14:17:51 +02:00
|
|
|
(.close ^js obj)))
|
|
|
|
|
|
|
|
(validate [obj]
|
2021-09-21 16:08:42 +02:00
|
|
|
(let [id (unchecked-get obj "__id")]
|
2021-11-16 11:58:59 +01:00
|
|
|
(l/info :origin "factory" :action "validate" :browser-id id :obj obj)
|
2021-09-21 16:08:42 +02:00
|
|
|
(p/resolved (.isConnected ^js obj))))]
|
2021-08-19 14:17:51 +02:00
|
|
|
|
|
|
|
#js {:create create
|
|
|
|
:destroy destroy
|
|
|
|
:validate validate}))
|
2021-05-05 09:28:12 +02:00
|
|
|
|
|
|
|
(defn init
|
|
|
|
[]
|
2021-11-16 11:58:59 +01:00
|
|
|
(l/info :msg "initializing browser pool")
|
2021-08-19 14:17:51 +02:00
|
|
|
(let [opts #js {:max (cf/get :browser-pool-max 3)
|
|
|
|
:min (cf/get :browser-pool-min 0)
|
|
|
|
:testOnBorrow true
|
2021-09-20 14:37:42 +02:00
|
|
|
:evictionRunIntervalMillis 5000
|
2021-08-19 14:17:51 +02:00
|
|
|
:numTestsPerEvictionRun 5
|
|
|
|
:acquireTimeoutMillis 120000 ; 2min
|
2021-09-20 14:37:42 +02:00
|
|
|
:idleTimeoutMillis 10000}]
|
2021-08-19 14:17:51 +02:00
|
|
|
|
|
|
|
(reset! pool (gp/createPool browser-pool-factory opts))
|
|
|
|
(p/resolved nil)))
|
2021-05-05 09:28:12 +02:00
|
|
|
|
|
|
|
(defn stop
|
|
|
|
[]
|
2021-08-19 14:17:51 +02:00
|
|
|
(when-let [pool (deref pool)]
|
2021-11-16 11:58:59 +01:00
|
|
|
(l/info :msg "finalizing browser pool")
|
2021-08-19 14:17:51 +02:00
|
|
|
(-> (.drain ^js pool)
|
|
|
|
(p/then (fn [] (.clear ^js pool))))))
|
|
|
|
|
|
|
|
(defn exec!
|
2021-09-21 16:08:42 +02:00
|
|
|
[callback]
|
2021-11-16 11:58:59 +01:00
|
|
|
(letfn [(release-browser [pool browser]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(-> (p/do! (.release ^js pool browser))
|
|
|
|
(p/handle (fn [res err]
|
|
|
|
(l/trace :action "exec:release-browser" :browser-id id)
|
|
|
|
(when err (js/console.log err))
|
|
|
|
(if err
|
|
|
|
(p/rejected err)
|
|
|
|
(p/resolved res)))))))
|
|
|
|
|
|
|
|
(destroy-browser [pool browser]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(-> (p/do! (.destroy ^js pool browser))
|
|
|
|
(p/handle (fn [res err]
|
|
|
|
(l/trace :action "exec:destroy-browser" :browser-id id)
|
|
|
|
(when err (js/console.log err))
|
|
|
|
(if err
|
|
|
|
(p/rejected err)
|
|
|
|
(p/resolved res)))))))
|
|
|
|
|
|
|
|
(handle-error [pool browser obj err]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(if err
|
|
|
|
(do
|
|
|
|
(l/trace :action "exec:handle-error" :browser-id id)
|
|
|
|
(-> (p/do! (destroy-browser pool browser))
|
|
|
|
(p/handle #(p/rejected err))))
|
|
|
|
(p/resolved obj))))
|
|
|
|
|
|
|
|
(on-result [pool browser context result]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(l/trace :action "exec:on-result" :browser-id id)
|
|
|
|
(-> (p/do! (.close ^js context))
|
|
|
|
(p/handle (fn [_ err]
|
|
|
|
(if err
|
|
|
|
(destroy-browser pool browser)
|
|
|
|
(release-browser pool browser))))
|
|
|
|
(p/handle #(p/resolved result)))))
|
|
|
|
|
|
|
|
(on-page [pool browser context page]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(l/trace :action "exec:on-page" :browser-id id)
|
|
|
|
(-> (p/do! (callback page))
|
|
|
|
(p/handle (partial handle-error pool browser))
|
|
|
|
(p/then (partial on-result pool browser context)))))
|
2021-09-21 16:08:42 +02:00
|
|
|
|
|
|
|
(on-context [pool browser ctx]
|
2021-11-16 11:58:59 +01:00
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(l/trace :action "exec:on-context" :browser-id id)
|
|
|
|
(-> (p/do! (.newPage ^js ctx))
|
|
|
|
(p/handle (partial handle-error pool browser))
|
|
|
|
(p/then (partial on-page pool browser ctx)))))
|
|
|
|
|
|
|
|
(on-acquire [pool browser err]
|
|
|
|
(let [id (unchecked-get browser "__id")]
|
|
|
|
(l/trace :action "exec:on-acquire" :browser-id id)
|
|
|
|
(if err
|
|
|
|
(js/console.log err)
|
|
|
|
(-> (p/do! (.createIncognitoBrowserContext ^js browser))
|
|
|
|
(p/handle (partial handle-error pool browser))
|
|
|
|
(p/then (partial on-context pool browser))))))]
|
2021-09-21 16:08:42 +02:00
|
|
|
|
2021-08-19 14:17:51 +02:00
|
|
|
(when-let [pool (deref pool)]
|
2021-09-21 16:08:42 +02:00
|
|
|
(-> (p/do! (.acquire ^js pool))
|
2021-11-16 11:58:59 +01:00
|
|
|
(p/handle (partial on-acquire pool))))))
|