0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-15 09:11:21 -05:00
penpot/frontend/src/app/main/errors.cljs
2024-03-01 16:32:31 +01:00

292 lines
9.6 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) KALEIDOS INC
(ns app.main.errors
"Generic error handling"
(:require
[app.common.exceptions :as ex]
[app.common.pprint :as pp]
[app.common.schema :as-alias sm]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.users :as du]
[app.main.store :as st]
[app.util.globals :as glob]
[app.util.i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :refer [storage]]
[app.util.timers :as ts]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
(defn- print-data!
[data]
(-> data
(dissoc ::sm/explain)
(dissoc :explain)
(dissoc ::trace)
(dissoc ::instance)
(pp/pprint {:width 70})))
(defn- print-explain!
[data]
(when-let [explain (or (ex/explain data)
(:explain data))]
(js/console.log explain)))
(defn- print-trace!
[data]
(some-> data ::trace js/console.log))
(defn- print-group!
[message f]
(try
(js/console.group message)
(f)
(catch :default _ nil)
(finally
(js/console.groupEnd message))))
(defn print-cause!
[message cause]
(print-group! message (fn []
(print-data! cause)
(print-explain! cause)
(print-trace! cause))))
(defn print-error!
[cause]
(cond
(map? cause)
(print-cause! (:hint cause "Unexpected Error") cause)
(ex/error? cause)
(print-cause! (ex-message cause) (ex-data cause))
:else
(let [trace (.-stack cause)]
(print-cause! (ex-message cause)
{:hint (ex-message cause)
::trace trace
::instance cause}))))
(defn on-error
"A general purpose error handler."
[error]
(if (map? error)
(ptk/handle-error error)
(let [data (ex-data error)
data (-> data
(assoc :hint (or (:hint data) (ex-message error)))
(assoc ::instance error)
(assoc ::trace (.-stack error)))]
(ptk/handle-error data))))
;; Set the main potok error handler
(reset! st/on-error on-error)
(defmethod ptk/handle-error :default
[error]
(st/async-emit! (rt/assign-exception error))
(print-group! "Unhandled Error"
(fn []
(print-trace! error)
(print-data! error))))
;; We receive a explicit authentication error; this explicitly clears
;; all profile data and redirect the user to the login page. This is
;; here and not in app.main.errors because of circular dependency.
(defmethod ptk/handle-error :authentication
[_]
(let [msg (tr "errors.auth.unable-to-login")
uri (. (. js/document -location) -href)]
(st/emit! (du/logout {:capture-redirect true}))
(ts/schedule 500 #(st/emit! (msg/warn msg)))
(ts/schedule 1000 #(swap! storage assoc :redirect-url uri))))
;; Error that happens on an active business model validation does not
;; passes an validation (example: profile can't leave a team). From
;; the user perspective a error flash message should be visualized but
;; user can continue operate on the application. Can happen in backend
;; and frontend.
(defmethod ptk/handle-error :validation
[{:keys [code] :as error}]
(print-group! "Validation Error"
(fn []
(print-data! error)
(print-explain! error)))
(cond
(= code :invalid-paste-data)
(let [message (tr "errors.paste-data-validation")]
(st/async-emit!
(msg/show {:content message
:notification-type :toast
:type :error
:timeout 3000})))
:else
(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.
(defmethod ptk/handle-error :assertion
[error]
(ts/schedule
#(st/emit! (msg/show {:content "Internal Assertion Error"
:notification-type :toast
:type :error
:timeout 3000})))
(print-group! "Internal Assertion Error"
(fn []
(print-trace! error)
(print-data! error)
(print-explain! error))))
;; ;; All the errors that happens on worker are handled here.
(defmethod ptk/handle-error :worker-error
[error]
(ts/schedule
#(st/emit!
(msg/show {:content "Something wrong has happened (on worker)."
:notification-type :toast
:type :error
:timeout 3000})))
(print-group! "Internal Worker Error"
(fn []
(print-data! error))))
;; Error on parsing an SVG
;; TODO: looks unused and deprecated
(defmethod ptk/handle-error :svg-parser
[_]
(ts/schedule
#(st/emit! (msg/show {:content "SVG is invalid or malformed"
:notification-type :toast
:type :error
:timeout 3000}))))
;; TODO: should be handled in the event and not as general error handler
(defmethod ptk/handle-error :comment-error
[_]
(ts/schedule
#(st/emit! (msg/show {:content "There was an error with the comment"
:notification-type :toast
:type :error
:timeout 3000}))))
;; That are special case server-errors that should be treated
;; differently.
(derive :not-found ::exceptional-state)
(derive :bad-gateway ::exceptional-state)
(derive :service-unavailable ::exceptional-state)
(defmethod ptk/handle-error ::exceptional-state
[error]
(when-let [cause (::instance error)]
(js/console.log (.-stack cause)))
(ts/schedule
#(st/emit! (rt/assign-exception error))))
(defn- redirect-to-dashboard
[]
(let [team-id (:current-team-id @st/state)
project-id (:current-project-id @st/state)]
(if (and project-id team-id)
(st/emit! (rt/nav :dashboard-files {:team-id team-id :project-id project-id}))
(set! (.-href glob/location) ""))))
(defmethod ptk/handle-error :restriction
[{:keys [code] :as error}]
(cond
(= :migration-in-progress code)
(let [message (tr "errors.migration-in-progress" (:feature error))
on-accept (constantly nil)]
(st/emit! (modal/show {:type :alert :message message :on-accept on-accept})))
(= :team-feature-mismatch code)
(let [message (tr "errors.team-feature-mismatch" (:feature error))
on-accept (constantly nil)]
(st/emit! (modal/show {:type :alert :message message :on-accept on-accept})))
(= :file-feature-mismatch code)
(let [message (tr "errors.file-feature-mismatch" (:feature error))]
(st/emit! (modal/show {:type :alert :message message :on-accept redirect-to-dashboard})))
(= :feature-mismatch code)
(let [message (tr "errors.feature-mismatch" (:feature error))]
(st/emit! (modal/show {:type :alert :message message :on-accept redirect-to-dashboard})))
(= :feature-not-supported code)
(let [message (tr "errors.feature-not-supported" (:feature error))]
(st/emit! (modal/show {:type :alert :message message :on-accept redirect-to-dashboard})))
(= :file-version-not-supported code)
(let [message (tr "errors.version-not-supported")]
(st/emit! (modal/show {:type :alert :message message :on-accept redirect-to-dashboard})))
(= :max-quote-reached code)
(let [message (tr "errors.max-quote-reached" (:target error))]
(st/emit! (modal/show {:type :alert :message message})))
(or (= :paste-feature-not-enabled code)
(= :missing-features-in-paste-content code)
(= :paste-feature-not-supported code))
(let [message (tr "errors.feature-not-supported" (:feature error))]
(st/emit! (modal/show {:type :alert :message message})))
:else
(print-cause! "Restriction Error" error)))
;; This happens when the backed server fails to process the
;; request. This can be caused by an internal assertion or any other
;; uncontrolled error.
(defmethod ptk/handle-error :server-error
[error]
(st/async-emit! (rt/assign-exception error))
(print-group! "Server Error"
(fn []
(print-data! (dissoc error :data))
(when-let [werror (:data error)]
(cond
(= :assertion (:type werror))
(print-group! "Assertion Error"
(fn []
(print-data! werror)
(print-explain! werror)))
:else
(print-group! "Unexpected"
(fn []
(print-data! werror)
(print-explain! werror))))))))
(defonce uncaught-error-handler
(letfn [(is-ignorable-exception? [cause]
(let [message (ex-message cause)]
(or (= message "Possible side-effect in debug-evaluate")
(= message "Unexpected end of input")
(str/starts-with? message "Unexpected token "))))
(on-unhandled-error [event]
(.preventDefault ^js event)
(when-let [error (unchecked-get event "error")]
(when-not (is-ignorable-exception? error)
(on-error error))))]
(.addEventListener glob/window "error" on-unhandled-error)
(fn []
(.removeEventListener glob/window "error" on-unhandled-error))))