penpot/frontend/src/app/main/ui/error_boundary.cljs
Andrey Antukh ab4e195cca
Add protection for stale cache of js assets loading issues (#8638)
*  Use update-when for update dashboard state

This make updates more consistent and reduces possible eventual
consistency issues in out of order events execution.

* 🐛 Detect stale JS modules at boot and force reload

When the browser serves cached JS files from a previous deployment
alongside a fresh index.html, code-split modules reference keyword
constants that do not exist in the stale shared.js, causing TypeError
crashes.

This adds a compile-time version tag (via goog-define / closure-defines)
that is baked into the JS bundle. At boot, it is compared against the
runtime version tag from index.html (which is always fresh due to
no-cache headers). If they differ, the app forces a hard page reload
before initializing, ensuring all JS modules come from the same build.

* 📎 Ensure consistent version across builds on github e2e test workflow

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-17 15:04:06 +01:00

56 lines
2.2 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.ui.error-boundary
"React error boundary components"
(:require
["react-error-boundary" :as reb]
[app.common.exceptions :as ex]
[app.config :as cf]
[app.main.errors :as errors]
[app.main.refs :as refs]
[goog.functions :as gfn]
[rumext.v2 :as mf]))
(mf/defc error-boundary*
[{:keys [fallback children]}]
(let [fallback-wrapper
(mf/with-memo [fallback]
(mf/fnc fallback-wrapper*
[{:keys [error reset-error-boundary]}]
(let [route (mf/deref refs/route)
data (errors/exception->error-data error)]
[:> fallback {:data data
:route route
:on-reset reset-error-boundary}])))
on-error
(mf/with-memo []
;; NOTE: The debounce is necessary just for simplicity,
;; becuase for some reasons the error is reported twice in a
;; very small amount of time, so we debounce for 100ms for
;; avoid duplicate and redundant reports
(gfn/debounce (fn [error info]
;; If the error is a stale-asset error (cross-build
;; module mismatch), force a hard page reload instead
;; of showing the error page to the user.
(if (errors/stale-asset-error? error)
(cf/throttled-reload :reason (ex-message error))
(do
(set! errors/last-exception error)
(ex/print-throwable error)
(js/console.error
"Component trace: \n"
(unchecked-get info "componentStack")
"\n"
error))))
100))]
[:> reb/ErrorBoundary
{:FallbackComponent fallback-wrapper
:onError on-error}
children]))