♻️ Refactor http client.

Start using Fetch API.
This commit is contained in:
Andrey Antukh 2021-04-09 17:21:34 +02:00 committed by Alonso Torres
parent 9a0f6018a7
commit 7d14aef393
15 changed files with 257 additions and 305 deletions

View file

@ -165,6 +165,5 @@
["/rpc" {:middleware [(:middleware session) ["/rpc" {:middleware [(:middleware session)
middleware/activity-logger]} middleware/activity-logger]}
["/query/:type" {:get (:query-handler rpc)}] ["/query/:type" {:get (:query-handler rpc)}]
["/mutation/:type" {:post (:mutation-handler rpc)}]]]])) ["/mutation/:type" {:post (:mutation-handler rpc)}]]]]))

View file

@ -11,7 +11,7 @@
danlentz/clj-uuid {:mvn/version "0.1.9"} danlentz/clj-uuid {:mvn/version "0.1.9"}
frankiesardo/linked {:mvn/version "1.3.0"} frankiesardo/linked {:mvn/version "1.3.0"}
funcool/beicon {:mvn/version "2021.01.29-1"} funcool/beicon {:mvn/version "2021.04.09-1"}
funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/cuerdas {:mvn/version "2020.03.26-3"}
funcool/okulary {:mvn/version "2020.04.14-0"} funcool/okulary {:mvn/version "2020.04.14-0"}
funcool/potok {:mvn/version "3.2.0"} funcool/potok {:mvn/version "3.2.0"}

View file

@ -1,35 +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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.data.fetch
(:require
[promesa.core :as p]
[potok.core :as ptk]
[okulary.core :as l]
[app.util.object :as obj]
[app.main.store :as st]))
(defn pending-ref []
(l/derived ::to-fetch st/state))
(defn add [to-fetch id]
(let [to-fetch (or to-fetch (hash-set))]
(conj to-fetch id)))
(defn fetch-as-data-uri [url]
(let [id (random-uuid)]
(st/emit! (fn [state] (update state ::to-fetch add id)))
(-> (js/fetch url)
(p/then (fn [res] (.blob res)))
(p/then (fn [blob]
(let [reader (js/FileReader.)]
(p/create (fn [resolve reject]
(obj/set! reader "onload" #(resolve [url (.-result reader)]))
(.readAsDataURL reader blob))))))
(p/finally #(st/emit! (fn [state] (update state ::to-fetch disj id)))))))

View file

@ -1321,7 +1321,9 @@
(let [obj (maybe-translate obj objects selected)] (let [obj (maybe-translate obj objects selected)]
(if (= type :image) (if (= type :image)
(let [url (cfg/resolve-file-media (:metadata obj))] (let [url (cfg/resolve-file-media (:metadata obj))]
(->> (http/fetch-as-data-url url) (->> (http/send! {:method :get :uri url})
(rx/map :body)
(rx/mapcat wapi/read-file-as-data-url)
(rx/map #(assoc obj ::data %)) (rx/map #(assoc obj ::data %))
(rx/take 1))) (rx/take 1)))
(rx/of obj)))) (rx/of obj))))
@ -1459,7 +1461,10 @@
(letfn [;; Given a file-id and img (part generated by the (letfn [;; Given a file-id and img (part generated by the
;; copy-selected event), uploads the new media. ;; copy-selected event), uploads the new media.
(upload-media [file-id imgpart] (upload-media [file-id imgpart]
(->> (http/data-url->blob (:file-data imgpart)) (->> (http/send! {:uri (:file-data imgpart)
:response-type :blob
:method :get})
(rx/map :body)
(rx/map (rx/map
(fn [blob] (fn [blob]
{:name (:name imgpart) {:name (:name imgpart)

View file

@ -394,19 +394,13 @@
(or (contains? props :data) (or (contains? props :data)
(contains? props :uris))))) (contains? props :uris)))))
(defn parse-svg [[name text]] (defn parse-svg
(->> (http/send! {:method :post [[name text]]
:uri "/api/svg/parse" (->> (rp/query! :parse-svg {:data text})
:headers {"content-type" "image/svg+xml"} (rx/map #(assoc % :name name))))
:body text})
(rx/map (fn [{:keys [status body]}]
(let [result (t/decode body)]
(if (= status 200)
(assoc result :name name)
(throw result)))))))
(defn fetch-svg [name uri] (defn fetch-svg [name uri]
(->> (http/send! {:method :get :uri uri}) (->> (http/send! {:method :get :uri uri :mode :no-cors})
(rx/map #(vector (rx/map #(vector
(or name (uu/uri-name uri)) (or name (uu/uri-name uri))
(:body %))))) (:body %)))))

View file

@ -10,9 +10,11 @@
(ns app.main.repo (ns app.main.repo
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[lambdaisland.uri :as u]
[cuerdas.core :as str] [cuerdas.core :as str]
[app.config :as cfg] [app.config :as cfg]
[app.util.http-api :as http])) [app.util.transit :as t]
[app.util.http :as http]))
(defn- handle-response (defn- handle-response
[{:keys [status body] :as response}] [{:keys [status body] :as response}]
@ -30,8 +32,7 @@
(= 0 (:status response)) (= 0 (:status response))
(rx/throw {:type :offline}) (rx/throw {:type :offline})
(and (= 200 status) (= 200 status)
(coll? body))
(rx/of body) (rx/of body)
(and (>= status 400) (and (>= status 400)
@ -43,21 +44,29 @@
:status status :status status
:data body}))) :data body})))
(defn send-query! (def ^:private base-uri (u/uri cfg/public-uri))
[id params]
(let [uri (str cfg/public-uri "/api/rpc/query/" (name id))]
(->> (http/send! {:method :get :uri uri :query params})
(rx/mapcat handle-response))))
(defn send-mutation! (defn- send-query!
"A simple helper for send and receive transit data on the penpot
query api."
[id params] [id params]
(let [uri (str cfg/public-uri "/api/rpc/mutation/" (name id))] (->> (http/send! {:method :get
(->> (http/send! {:method :post :uri uri :body params}) :uri (u/join base-uri "api/rpc/query/" (name id))
(rx/mapcat handle-response)))) :query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
(defn- dispatch (defn- send-mutation!
[& args] "A simple helper for a common case of sending and receiving transit
(first args)) data to the penpot mutation api."
[id params]
(->> (http/send! {:method :post
:uri (u/join base-uri "api/rpc/mutation/" (name id))
:body (http/transit-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
(defn- dispatch [& args] (first args))
(defmulti query dispatch) (defmulti query dispatch)
(defmulti mutation dispatch) (defmulti mutation dispatch)
@ -80,53 +89,59 @@
(defmethod mutation :login-with-google (defmethod mutation :login-with-google
[id params] [id params]
(let [uri (str cfg/public-uri "/api/oauth/google")] (let [uri (u/join base-uri "api/oauth/google")]
(->> (http/send! {:method :post :uri uri :query params}) (->> (http/send! {:method :post :uri uri :query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))) (rx/mapcat handle-response))))
(defmethod mutation :login-with-gitlab (defmethod mutation :login-with-gitlab
[id params] [id params]
(let [uri (str cfg/public-uri "/api/oauth/gitlab")] (let [uri (u/join base-uri "api/oauth/gitlab")]
(->> (http/send! {:method :post :uri uri :query params}) (->> (http/send! {:method :post :uri uri :query params})
(rx/mapcat handle-response)))) (rx/map http/conditional-decode-transit)
(rx/mapcat handle-response))))
(defmethod mutation :login-with-github (defmethod mutation :login-with-github
[id params] [id params]
(let [uri (str cfg/public-uri "/api/oauth/github")] (let [uri (u/join base-uri "api/oauth/github")]
(->> (http/send! {:method :post :uri uri :query params}) (->> (http/send! {:method :post :uri uri :query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))) (rx/mapcat handle-response))))
(defmethod mutation :upload-file-media-object
[id params]
(let [form (js/FormData.)]
(run! (fn [[key val]]
(if (list? val)
(.append form (name key) (first val) (second val))
(.append form (name key) val)))
(seq params))
(send-mutation! id form)))
(defmethod mutation :send-feedback (defmethod mutation :send-feedback
[id params] [id params]
(let [uri (str cfg/public-uri "/api/feedback")] (->> (http/send! {:method :post
(->> (http/send! {:method :post :uri uri :body params}) :uri (u/join base-uri "api/feedback")
(rx/mapcat handle-response)))) :body (http/transit-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
(defmethod mutation :update-profile-photo (defmethod query :export
[id params] [id params]
(let [form (js/FormData.)] (->> (http/send! {:method :post
(run! (fn [[key val]] :uri (u/join base-uri "export")
(.append form (name key) val)) :body (http/transit-data params)
(seq params)) :response-type :blob})
(send-mutation! id form))) (rx/mapcat handle-response)))
(defmethod mutation :update-team-photo (defmethod query :parse-svg
[id {:keys [data] :as params}]
(->> (http/send! {:method :post
:uri (u/join base-uri "api/svg/parse")
:headers {"content-type" "image/svg+xml"}
:body data
:response-type :text})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
(derive :upload-file-media-object ::multipart-upload)
(derive :update-profile-photo ::multipart-upload)
(derive :update-team-photo ::multipart-upload)
(defmethod mutation ::multipart-upload
[id params] [id params]
(let [form (js/FormData.)] (->> (http/send! {:method :post
(run! (fn [[key val]] :uri (u/join base-uri "/api/rpc/mutation/" (name id))
(.append form (name key) val)) :body (http/form-data params)})
(seq params)) (rx/map http/conditional-decode-transit)
(send-mutation! id form))) (rx/mapcat handle-response)))
(def client-error? http/client-error?)
(def server-error? http/server-error?)

View file

@ -5,19 +5,20 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0. ;; defined by the Mozilla Public License, v. 2.0.
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.shapes.image (ns app.main.ui.shapes.image
(:require (:require
[rumext.alpha :as mf]
[app.config :as cfg]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.config :as cfg]
[app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.http :as http]
[app.util.object :as obj] [app.util.object :as obj]
[app.main.ui.context :as muc] [app.util.webapi :as wapi]
[app.main.data.fetch :as df] [beicon.core :as rx]
[promesa.core :as p])) [rumext.alpha :as mf]))
(mf/defc image-shape (mf/defc image-shape
{::mf/wrap-props false} {::mf/wrap-props false}
@ -33,8 +34,12 @@
(mf/deps uri) (mf/deps uri)
(fn [] (fn []
(if embed-resources? (if embed-resources?
(-> (df/fetch-as-data-uri uri) (->> (http/send! {:method :get
(p/then #(reset! data-uri (second %))))))) :uri uri
:response-type :blob})
(rx/map :body)
(rx/mapcat wapi/read-file-as-data-url)
(rx/subs #(reset! data-uri %))))))
(let [transform (geom/transform-matrix shape) (let [transform (geom/transform-matrix shape)
props (-> (attrs/extract-style-attrs shape) props (-> (attrs/extract-style-attrs shape)

View file

@ -11,12 +11,14 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.text :as txt] [app.common.text :as txt]
[app.main.data.fetch :as df]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.util.http :as http]
[app.util.webapi :as wapi]
[app.util.object :as obj] [app.util.object :as obj]
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str] [cuerdas.core :as str]
[promesa.core :as p] [promesa.core :as p]
[beicon.core :as rx]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def font-face-template " (def font-face-template "
@ -44,10 +46,12 @@
"Given a font and the variant-id, retrieves the style CSS for it." "Given a font and the variant-id, retrieves the style CSS for it."
[{:keys [id backend family variants] :as font} font-variant-id] [{:keys [id backend family variants] :as font} font-variant-id]
(if (= :google backend) (if (= :google backend)
(-> (fonts/gfont-url family [{:id font-variant-id}]) (->> (http/send! {:method :get
(js/fetch) :mode :no-cors
(p/then (fn [res] (.text res)))) :uri (fonts/gfont-url family [{:id font-variant-id}])
:response-type :text})
(rx/map :body)
(http/as-promise))
(let [{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants) (let [{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants)
result (str/fmt font-face-template {:family family result (str/fmt font-face-template {:family family
:style style :style style
@ -55,14 +59,26 @@
:weight weight})] :weight weight})]
(p/resolved result)))) (p/resolved result))))
(defn- to-promise
[observable]
(p/create (fn [resolve reject]
(->> (rx/take 1 observable)
(rx/subs resolve reject)))))
(defn get-font-data (defn get-font-data
"Parses the CSS and retrieves the font data as DataURI." "Parses the CSS and retrieves the font data as DataURI."
[^string css] [^string css]
(->> css (let [uris (->> (re-seq #"url\(([^)]+)\)" css)
(re-seq #"url\(([^)]+)\)") (map second))]
(map second) (->> (rx/from (seq uris))
(map df/fetch-as-data-uri) (rx/mapcat (fn [uri]
(p/all))) (http/send! {:method :get
:uri uri
:response-type :blob})))
(rx/map :body)
(rx/mapcat wapi/read-file-as-data-url)
(rx/reduce conj [])
(http/as-promise))))
(defn embed-font (defn embed-font
"Given a font-id and font-variant-id, retrieves the CSS for it and "Given a font-id and font-variant-id, retrieves the CSS for it and

View file

@ -5,7 +5,7 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0. ;; defined by the Mozilla Public License, v. 2.0.
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.workspace.sidebar.options.menus.exports (ns app.main.ui.workspace.sidebar.options.menus.exports
(:require (:require
@ -13,26 +13,24 @@
[beicon.core :as rx] [beicon.core :as rx]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.common.data :as d] [app.common.data :as d]
[app.main.repo :as rp]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.store :as st] [app.main.store :as st]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.http-api :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr t]])) [app.util.i18n :as i18n :refer [tr t]]))
(defn- request-export (defn- request-export
[shape exports] [shape exports]
(http/send! {:method :post (rp/query! :export
:uri "/export" {:page-id (:page-id shape)
:response-type :blob :file-id (:file-id shape)
:auth true :object-id (:id shape)
:body {:page-id (:page-id shape) :name (:name shape)
:file-id (:file-id shape) :exports exports}))
:object-id (:id shape)
:name (:name shape)
:exports exports}}))
(defn- trigger-download (defn- trigger-download
[filename blob] [filename blob]
@ -68,12 +66,11 @@
(swap! loading? not) (swap! loading? not)
(->> (request-export (assoc shape :page-id page-id :file-id file-id) exports) (->> (request-export (assoc shape :page-id page-id :file-id file-id) exports)
(rx/subs (rx/subs
(fn [{:keys [status body] :as response}] (fn [body]
(js/console.log status body) (trigger-download filename body))
(if (= status 200) (fn [error]
(trigger-download filename body) (swap! loading? not)
(st/emit! (dm/error (tr "errors.unexpected-error"))))) (st/emit! (dm/error (tr "errors.unexpected-error"))))
(constantly nil)
(fn [] (fn []
(swap! loading? not)))))) (swap! loading? not))))))

View file

@ -5,13 +5,12 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0. ;; defined by the Mozilla Public License, v. 2.0.
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.workspace.viewport.pixel-overlay (ns app.main.ui.workspace.viewport.pixel-overlay
(:require (:require
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.colors :as dwc] [app.main.data.colors :as dwc]
[app.main.data.fetch :as mdf]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]

View file

@ -5,25 +5,34 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0. ;; defined by the Mozilla Public License, v. 2.0.
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.util.http (ns app.util.http
"A http client with rx streams interface." "A http client with rx streams interface."
(:refer-clojure :exclude [get])
(:require (:require
[app.common.data :as d]
[app.config :as cfg] [app.config :as cfg]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.transit :as t] [app.util.transit :as t]
[beicon.core :as rx] [beicon.core :as rx]
[cljs.core :as c] [cuerdas.core :as str]
[clojure.string :as str] [lambdaisland.uri :as u]
[goog.events :as events]) [promesa.core :as p]))
(:import
[goog.net ErrorCode EventType] (defprotocol IBodyData
[goog.net.XhrIo ResponseType] "A helper for define body data with the appropiate headers."
[goog.net XhrIo] (-update-headers [_ headers])
[goog.Uri QueryData] (-get-body-data [_]))
[goog Uri]))
(extend-protocol IBodyData
js/FormData
(-get-body-data [it] it)
(-update-headers [it headers]
(dissoc headers "content-type" "Content-Type"))
default
(-get-body-data [it] it)
(-update-headers [it headers] headers))
(defn translate-method (defn translate-method
[method] [method]
@ -37,70 +46,93 @@
:delete "DELETE" :delete "DELETE"
:trace "TRACE")) :trace "TRACE"))
(defn- normalize-headers (defn parse-headers
[headers] [headers]
(reduce-kv (fn [acc k v] (into {} (map vec) (seq (.entries ^js headers))))
(assoc acc (str/lower-case k) v))
{} (js->clj headers)))
(defn- translate-error-code
[code]
(condp = code
ErrorCode.TIMEOUT :timeout
ErrorCode.EXCEPTION :exception
ErrorCode.HTTP_ERROR :http
ErrorCode.ABORT :abort
ErrorCode.OFFLINE :offline
nil))
(defn- translate-response-type
[type]
(case type
:text ResponseType.TEXT
:blob ResponseType.BLOB
ResponseType.DEFAULT))
(defn- create-uri
[uri qs qp]
(let [uri (Uri. uri)]
(when qs (.setQuery uri qs))
(when qp
(let [dt (.createFromMap QueryData (clj->js qp))]
(.setQueryData uri dt)))
(.toString uri)))
(def default-headers (def default-headers
{"x-frontend-version" (:full @cfg/version)}) {"x-frontend-version" (:full @cfg/version)})
(defn- fetch (defn fetch
[{:keys [method uri query-string query headers body] :as request} [{:keys [method uri query headers body timeout mode]
{:keys [timeout credentials? response-type] :or {timeout 10000 mode :cors headers {}}}]
:or {timeout 0 credentials? false response-type :text}}] (rx/Observable.create
(let [uri (create-uri uri query-string query) (fn [subscriber]
headers (merge default-headers headers) (let [controller (js/AbortController.)
headers (if headers (clj->js headers) #js {}) signal (.-signal ^js controller)
method (translate-method method) unsubscribed? (volatile! false)
xhr (doto (XhrIo.) abortable? (volatile! true)
(.setResponseType (translate-response-type response-type)) query (cond
(.setWithCredentials credentials?) (string? query) query
(.setTimeoutInterval timeout))] (map? query) (u/map->query-string query)
(rx/create :else nil)
(fn [sink] uri (cond-> uri
(letfn [(on-complete [event] (string? uri) (u/uri)
(let [type (translate-error-code (.getLastErrorCode xhr)) (some? query) (assoc :query query))
status (.getStatus xhr)] headers (->> (d/merge headers default-headers)
(if (pos? status) (-update-headers body))
(sink (rx/end body (-get-body-data body)
{:status status params #js {:method (translate-method method)
:body (.getResponse xhr) :headers (clj->js headers)
:headers (normalize-headers (.getResponseHeaders xhr))})) :body body
(sink (rx/end :mode (d/name mode)
{:status 0 :redirect "follow"
:error (if (= type :http) :abort type) :credentials "same-origin"
::xhr xhr})))))] :referrerPolicy "no-referrer"
(events/listen xhr EventType.COMPLETE on-complete) :signal signal}]
(.send xhr uri method body headers) (-> (js/fetch (str uri) params)
#(.abort xhr)))))) (p/then (fn [response]
(vreset! abortable? false)
(.next ^js subscriber response)
(.complete ^js subscriber)))
(p/catch (fn [err]
(vreset! abortable? false)
(when-not @unsubscribed?
(.error ^js subscriber err)))))
(fn []
(vreset! unsubscribed? true)
(when @abortable?
(.abort ^js controller)))))))
(defn send!
[{:keys [response-type] :or {response-type :text} :as params}]
(letfn [(on-response [response]
(let [body (case response-type
:json (.json ^js response)
:text (.text ^js response)
:blob (.blob ^js response))]
(->> (rx/from body)
(rx/map (fn [body]
{::response response
:status (.-status ^js response)
:headers (parse-headers (.-headers ^js response))
:body body})))))]
(->> (fetch params)
(rx/mapcat on-response))))
(defn form-data
[data]
(letfn [(append [form k v]
(if (list? v)
(.append form (name k) (first v) (second v))
(.append form (name k) v))
form)]
(reduce-kv append (js/FormData.) data)))
(defn transit-data
[data]
(reify IBodyData
(-get-body-data [_] (t/encode data))
(-update-headers [_ headers]
(assoc headers "content-type" "application/transit+json"))))
(defn conditional-decode-transit
[{:keys [body headers status] :as response}]
(let [contentype (get headers "content-type")]
(if (and (str/starts-with? contentype "application/transit+json")
(pos? (count body)))
(assoc response :body (t/decode body))
response)))
(defn success? (defn success?
[{:keys [status]}] [{:keys [status]}]
@ -114,25 +146,8 @@
[{:keys [status]}] [{:keys [status]}]
(<= 400 status 499)) (<= 400 status 499))
(defn send! (defn as-promise
([request] [observable]
(send! request nil)) (p/create (fn [resolve reject]
([request options] (->> (rx/take 1 observable)
(fetch request options))) (rx/subs resolve reject)))))
(defn fetch-as-data-url
[url]
(->> (send! {:method :get :uri url} {:response-type :blob})
(rx/mapcat (fn [{:keys [body] :as rsp}]
(let [reader (js/FileReader.)]
(rx/create (fn [sink]
(obj/set! reader "onload" #(sink (reduced (.-result reader))))
(.readAsDataURL reader body))))))))
(defn data-url->blob
[durl]
(->> (send! {:method :get :uri durl} {:response-type :blob})
(rx/map :body)
(rx/take 1)))

View file

@ -1,59 +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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.util.http-api
"A specific customizations of http client for api access."
(:require
[beicon.core :as rx]
[cuerdas.core :as str]
[app.util.http :as http]
[app.util.transit :as t]))
(defn- conditional-decode
[{:keys [body headers status] :as response}]
(let [contentype (get headers "content-type")]
(if (and (str/starts-with? contentype "application/transit+json")
(pos? (count body)))
(assoc response :body (t/decode body))
response)))
(defn- handle-http-status
[{:keys [body status] :as response}]
(if (http/success? response)
(rx/of {:status status :payload body})
(rx/throw {:status status :payload body})))
(def ^:private default-headers
{"content-type" "application/transit+json"})
(defn- impl-send
[{:keys [body headers auth method query uri response-type]
:or {auth true response-type :text}}]
(let [headers (merge {"Accept" "application/transit+json,*/*"}
(when (map? body) default-headers)
headers)
request {:method method
:uri uri
:headers headers
:query query
:body (if (map? body)
(t/encode body)
body)}
options {:response-type response-type
:credentials? auth}]
(http/send! request options)))
(defn send!
[request]
(->> (impl-send request)
(rx/map conditional-decode)))
(def success? http/success?)
(def client-error? http/client-error?)
(def server-error? http/server-error?)

View file

@ -147,8 +147,8 @@
history (:history state) history (:history state)
router (:router state)] router (:router state)]
(ts/schedule #(on-change router (.getToken ^js history))) (ts/schedule #(on-change router (.getToken ^js history)))
(->> (rx/create (fn [sink] (->> (rx/create (fn [subs]
(let [key (e/listen history "navigate" (fn [o] (sink (.-token ^js o))))] (let [key (e/listen history "navigate" (fn [o] (rx/push! subs (.-token ^js o))))]
(fn [] (fn []
(bhistory/disable! history) (bhistory/disable! history)
(e/unlistenByKey key))))) (e/unlistenByKey key)))))

View file

@ -5,36 +5,36 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0. ;; defined by the Mozilla Public License, v. 2.0.
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.util.webapi (ns app.util.webapi
"HTML5 web api helpers." "HTML5 web api helpers."
(:require (:require
[app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.util.object :as obj] [app.util.object :as obj]
[promesa.core :as p] [app.util.transit :as t]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[app.common.data :as d] [promesa.core :as p]))
[app.util.transit :as t]))
(defn- file-reader
[f]
(rx/create
(fn [subs]
(let [reader (js/FileReader.)]
(obj/set! reader "onload" #(do (rx/push! subs (.-result reader))
(rx/end! subs)))
(f reader)
(constantly nil)))))
(defn read-file-as-text (defn read-file-as-text
[file] [file]
(rx/create (file-reader #(.readAsText %1 file)))
(fn [sink]
(let [fr (js/FileReader.)]
(aset fr "onload" #(sink (rx/end (.-result fr))))
(.readAsText fr file)
(constantly nil)))))
(defn read-file-as-dataurl (defn read-file-as-data-url
[file] [file]
(rx/create (file-reader #(.readAsDataURL ^js %1 file)))
(fn [sick]
(let [fr (js/FileReader.)]
(aset fr "onload" #(sick (rx/end (.-result fr))))
(.readAsDataURL fr file))
(constantly nil))))
(defn ^boolean blob? (defn ^boolean blob?
[v] [v]

View file

@ -15,7 +15,7 @@
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.exports :as exports] [app.main.exports :as exports]
[app.worker.impl :as impl] [app.worker.impl :as impl]
[app.util.http-api :as http] [app.util.http :as http]
["react-dom/server" :as rds])) ["react-dom/server" :as rds]))
(defn- handle-response (defn- handle-response
@ -39,6 +39,7 @@
(->> (http/send! {:uri uri (->> (http/send! {:uri uri
:query {:file-id file-id :id page-id} :query {:file-id file-id :id page-id}
:method :get}) :method :get})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response) (rx/mapcat handle-response)
(rx/subs (fn [body] (rx/subs (fn [body]
(resolve body)) (resolve body))