mirror of
https://github.com/penpot/penpot.git
synced 2025-06-04 17:11:39 +02:00
181 lines
5.5 KiB
Clojure
181 lines
5.5 KiB
Clojure
;; 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.util.http
|
|
"A http client with rx streams interface."
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.transit :as t]
|
|
[app.common.uri :as u]
|
|
[app.config :as cfg]
|
|
[app.util.cache :as c]
|
|
[app.util.globals :as globals]
|
|
[app.util.object :as obj]
|
|
[app.util.time :as dt]
|
|
[app.util.webapi :as wapi]
|
|
[beicon.core :as rx]
|
|
[cuerdas.core :as str]
|
|
[promesa.core :as p]))
|
|
|
|
(defprotocol IBodyData
|
|
"A helper for define body data with the appropiate headers."
|
|
(-update-headers [_ headers])
|
|
(-get-body-data [_]))
|
|
|
|
(extend-protocol IBodyData
|
|
globals/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
|
|
[method]
|
|
(case method
|
|
:head "HEAD"
|
|
:options "OPTIONS"
|
|
:get "GET"
|
|
:post "POST"
|
|
:put "PUT"
|
|
:patch "PATCH"
|
|
:delete "DELETE"
|
|
:trace "TRACE"))
|
|
|
|
(defn parse-headers
|
|
[headers]
|
|
(into {} (map vec) (seq (.entries ^js headers))))
|
|
|
|
(def default-headers
|
|
{"x-frontend-version" (:full @cfg/version)})
|
|
|
|
(defn fetch
|
|
[{:keys [method uri query headers body timeout mode omit-default-headers]
|
|
:or {timeout 10000 mode :cors headers {}}}]
|
|
(rx/Observable.create
|
|
(fn [subscriber]
|
|
(let [controller (js/AbortController.)
|
|
signal (.-signal ^js controller)
|
|
unsubscribed? (volatile! false)
|
|
abortable? (volatile! true)
|
|
query (cond
|
|
(string? query) query
|
|
(map? query) (u/map->query-string query)
|
|
:else nil)
|
|
uri (cond-> uri
|
|
(string? uri) (u/uri)
|
|
(some? query) (assoc :query query))
|
|
|
|
headers (cond-> headers
|
|
(not omit-default-headers)
|
|
(d/merge default-headers))
|
|
|
|
headers (-update-headers body headers)
|
|
|
|
body (-get-body-data body)
|
|
|
|
params #js {:method (translate-method method)
|
|
:headers (clj->js headers)
|
|
:body body
|
|
:mode (d/name mode)
|
|
:redirect "follow"
|
|
:credentials "same-origin"
|
|
:referrerPolicy "no-referrer"
|
|
:signal signal}]
|
|
(-> (js/fetch (str uri) params)
|
|
(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-str 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-str body))
|
|
response)))
|
|
|
|
(defn success?
|
|
[{:keys [status]}]
|
|
(<= 200 status 299))
|
|
|
|
(defn server-error?
|
|
[{:keys [status]}]
|
|
(<= 500 status 599))
|
|
|
|
(defn client-error?
|
|
[{:keys [status]}]
|
|
(<= 400 status 499))
|
|
|
|
(defn as-promise
|
|
[observable]
|
|
(p/create
|
|
(fn [resolve reject]
|
|
(->> (rx/take 1 observable)
|
|
(rx/subs resolve reject)))))
|
|
|
|
(defn fetch-data-uri [uri]
|
|
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
|
(->> (send! {:method :get
|
|
:uri uri
|
|
:response-type :blob
|
|
:omit-default-headers true})
|
|
(rx/map :body)
|
|
(rx/mapcat wapi/read-file-as-data-url)
|
|
(rx/map #(hash-map uri %)))))
|
|
|
|
(defn fetch-text [url]
|
|
(c/with-cache {:key url :max-age (dt/duration {:hours 4})}
|
|
(->> (send!
|
|
{:method :get
|
|
:mode :cors
|
|
:omit-default-headers true
|
|
:uri url
|
|
:response-type :text})
|
|
(rx/map :body))))
|