mirror of
https://github.com/penpot/penpot.git
synced 2026-03-27 22:00:35 +01:00
✨ Improve error handling and exception formatting (#8757)
* ✨ Improve error handling and exception formatting - Enhance exception formatting with visual separators and cause chaining - Add new handler for :internal error type - Refine error types: change assertion-related errors to :assertion type - Improve error messages and hints consistency - Clean up error handling in zip utilities and HTTP modules * 🐛 Properly handle AbortError on fetch request unsubscription When a fetch request in-flight is cancelled due to RxJS unsubscription (e.g. navigating away from the workspace while thumbnail loads are pending), the AbortController.abort() call triggers a catch handler that previously relied solely on a @unsubscribed? flag to suppress the error. This was unreliable: nested observables spawned inside rx/mapcat (such as datauri->blob-uri conversions within get-file-object-thumbnails) could abort independently, with their own AbortController instances, meaning the outer unsubscribed? flag was never set and the AbortError propagated as an unhandled exception. Add an explicit AbortError name check as a disjunctive condition so that abort errors originating from any observable in the chain are suppressed at the source, regardless of subscription state. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
@@ -245,8 +245,10 @@
|
||||
(defn format-throwable
|
||||
[cause & {:as opts}]
|
||||
(with-out-str
|
||||
(println "====================")
|
||||
(when-let [exdata (ex-data cause)]
|
||||
(when-let [hint (get exdata :hint)]
|
||||
(when-let [hint (or (get exdata :hint)
|
||||
(ex-message cause))]
|
||||
(when (str/index-of hint "\n")
|
||||
(println "Hint:")
|
||||
(println "--------------------")
|
||||
@@ -273,7 +275,9 @@
|
||||
(when-let [trace (.-stack cause)]
|
||||
(println "Trace:")
|
||||
(println "--------------------")
|
||||
(println (.-stack cause))))))
|
||||
(println (.-stack cause)))
|
||||
|
||||
(println "===================="))))
|
||||
|
||||
(defn first-line
|
||||
[s]
|
||||
@@ -297,6 +301,11 @@
|
||||
(js/console.group title)
|
||||
(try
|
||||
(js/console.log (format-throwable cause))
|
||||
(loop [cause (ex-cause cause)]
|
||||
(when cause
|
||||
(js/console.log "\nCaused by:")
|
||||
(js/console.log (format-throwable cause))
|
||||
(recur (ex-cause cause))))
|
||||
(finally
|
||||
(js/console.groupEnd))))))
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
(rx/map (fn [{:keys [redirect-uri] :as rsp}]
|
||||
(if redirect-uri
|
||||
(rt/nav-raw :uri redirect-uri)
|
||||
(ex/raise :type :internal
|
||||
(ex/raise :type :assertion
|
||||
:code :unexpected-response
|
||||
:hint "unexpected response from OIDC method"
|
||||
:resp (pr-str rsp)))))
|
||||
|
||||
@@ -118,6 +118,12 @@
|
||||
:level :error
|
||||
:timeout 5000})))
|
||||
|
||||
(defmethod ptk/handle-error :internal
|
||||
[error]
|
||||
(st/emit! (rt/assign-exception error))
|
||||
(when-let [cause (::instance error)]
|
||||
(ex/print-throwable cause :prefix "Internal Error")))
|
||||
|
||||
(defmethod ptk/handle-error :default
|
||||
[error]
|
||||
(if (and (string? (:hint error))
|
||||
@@ -209,8 +215,7 @@
|
||||
(st/async-emit! (rt/assign-exception error))))
|
||||
|
||||
;; This is a pure frontend error that can be caused by an active
|
||||
;; assertion (assertion that is preserved on production builds). From
|
||||
;; the user perspective this should be treated as internal error.
|
||||
;; assertion (assertion that is preserved on production builds).
|
||||
(defmethod ptk/handle-error :assertion
|
||||
[error]
|
||||
(when-let [cause (::instance error)]
|
||||
|
||||
@@ -57,13 +57,13 @@
|
||||
|
||||
:else
|
||||
(rx/throw
|
||||
(ex-info "repository request error"
|
||||
{:type :internal
|
||||
:code :repository-access-error
|
||||
(ex/error :type :internal
|
||||
:code :unable-to-process-repository-response
|
||||
:hint "unable to process repository response"
|
||||
:uri uri
|
||||
:status status
|
||||
:headers headers
|
||||
:data body}))))
|
||||
:data body))))
|
||||
|
||||
(def default-options
|
||||
{:update-file {:query-params [:id]}
|
||||
@@ -156,11 +156,11 @@
|
||||
tpoint (ct/tpoint-ms)]
|
||||
|
||||
(when (and response-stream? (not stream?))
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-response-processing
|
||||
(ex/raise :type :assertion
|
||||
:code :unexpected-response
|
||||
:hint "expected normal response, received sse stream"
|
||||
:response-uri (:uri response)
|
||||
:response-status (:status response)))
|
||||
:uri (:uri response)
|
||||
:status (:status response)))
|
||||
|
||||
(if response-stream?
|
||||
(-> (sse/create-stream body)
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
(rx/end! subs)))
|
||||
(obj/set! image "crossOrigin" "anonymous")
|
||||
(obj/set! image "onerror" #(rx/error! subs %))
|
||||
(obj/set! image "onabort" #(rx/error! subs (ex/error :type :internal
|
||||
:code :abort
|
||||
(obj/set! image "onabort" #(rx/error! subs (ex/error :type :abort
|
||||
:code :operation-aborted
|
||||
:hint "operation aborted")))
|
||||
(obj/set! image "src" uri)
|
||||
(fn []
|
||||
|
||||
@@ -106,10 +106,10 @@
|
||||
(p/catch
|
||||
(fn [cause]
|
||||
(vreset! abortable? false)
|
||||
(when-not @unsubscribed?
|
||||
(when-not (or @unsubscribed? (= (.-name ^js cause) "AbortError"))
|
||||
(let [error (ex-info (ex-message cause)
|
||||
{:type :internal
|
||||
:code :unable-to-fetch
|
||||
:code :fetch-error
|
||||
:hint "unable to perform fetch operation"
|
||||
:uri uri
|
||||
:headers headers}
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
(obj/set! reader "onerror"
|
||||
#(rx/error! subs %))
|
||||
(obj/set! reader "onabort"
|
||||
#(rx/error! subs (ex/error :type :internal
|
||||
:code :abort
|
||||
#(rx/error! subs (ex/error :type :abort
|
||||
:code :operation-aborted
|
||||
:hint "operation aborted")))
|
||||
(f reader)
|
||||
(fn []
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"Helpers for make zip file."
|
||||
(:require
|
||||
["@zip.js/zip.js" :as zip]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.util.array :as array]
|
||||
[promesa.core :as p]))
|
||||
|
||||
@@ -27,9 +28,9 @@
|
||||
(reader (js/Uint8Array. blob))
|
||||
|
||||
:else
|
||||
(throw (ex-info "invalid arguments"
|
||||
{:type :internal
|
||||
:code :invalid-type}))))
|
||||
(ex/raise :type :assertion
|
||||
:coce :invalid-type
|
||||
:hint "invalid data received for zip/reader")))
|
||||
|
||||
(defn blob-writer
|
||||
[& {:keys [mtype]}]
|
||||
@@ -62,10 +63,9 @@
|
||||
(.add writer path (new zip/TextReader content))
|
||||
|
||||
:else
|
||||
(throw (ex-info "invalid arguments"
|
||||
{:type :internal
|
||||
:code :invalid-type}))))
|
||||
|
||||
(ex/raise :type :assertion
|
||||
:code :invalid-type
|
||||
:hint "invalid data received for zip/add fn")))
|
||||
|
||||
(defn get-entry
|
||||
[reader path]
|
||||
|
||||
Reference in New Issue
Block a user