Don't let third-party script failures mask a working app
The boot guard reported every failed resource behind a full-screen #boot-error panel. Cloudflare injects beacon.min.js at the edge, and ad blockers and filtering DNS block it routinely, so a blocked analytics script painted "CourseHub failed to start" over an app that had started fine. Mark the resources we actually ship with data-boot-critical and ignore the rest, and stop showing the panel once app.js has a UI on screen — that also keeps a late unhandled rejection (a dropped fetch in loadMeta, a flaky scan) from covering a running app. A genuine failure of app.js itself still reports as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TD5SRevPpgFguCwzNv9ZR7
This commit is contained in:
@@ -451,16 +451,22 @@
|
|||||||
|
|
||||||
/* ---------- auth ---------- */
|
/* ---------- auth ---------- */
|
||||||
|
|
||||||
|
/* Tells the boot-error handler in index.php that a UI is on screen, so later
|
||||||
|
failures get logged instead of buried under a full-screen error panel. */
|
||||||
|
function markBooted() { window.__chBooted = true; }
|
||||||
|
|
||||||
function showAuth() {
|
function showAuth() {
|
||||||
if (cleanupView) { cleanupView(); cleanupView = null; }
|
if (cleanupView) { cleanupView(); cleanupView = null; }
|
||||||
shell.hidden = true;
|
shell.hidden = true;
|
||||||
authEl.hidden = false;
|
authEl.hidden = false;
|
||||||
renderLogin();
|
renderLogin();
|
||||||
|
markBooted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showShell() {
|
function showShell() {
|
||||||
authEl.hidden = true;
|
authEl.hidden = true;
|
||||||
shell.hidden = false;
|
shell.hidden = false;
|
||||||
|
markBooted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLogin(error = '') {
|
function renderLogin(error = '') {
|
||||||
|
|||||||
@@ -26,16 +26,23 @@ function asset(string $path): string
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.css" rel="stylesheet" data-boot-critical>
|
||||||
<link href="<?= asset('assets/style.css') ?>" rel="stylesheet">
|
<link href="<?= asset('assets/style.css') ?>" rel="stylesheet" data-boot-critical>
|
||||||
|
|
||||||
<!-- Installed before every other script so it can report failures in them
|
<!-- Installed before every other script so it can report failures in them
|
||||||
(including assets/app.js itself). Without it a JS error leaves the shell
|
(including assets/app.js itself). Without it a JS error leaves the shell
|
||||||
hidden and the page simply blank, with the reason only in the console. -->
|
hidden and the page simply blank, with the reason only in the console.
|
||||||
|
|
||||||
|
The report is a full-screen panel, so it is deliberately hard to trigger:
|
||||||
|
only resources carrying data-boot-critical count, and only until app.js
|
||||||
|
signals that it has put a UI on screen. Anything else — Cloudflare's
|
||||||
|
injected analytics beacon, a blocked font, a failing fetch long after
|
||||||
|
boot — goes to the console instead of over a working app. -->
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var box = null;
|
var box = null;
|
||||||
function report(kind, detail) {
|
function report(kind, detail) {
|
||||||
|
if (window.__chBooted) { console.error('CourseHub — ' + kind + ':', detail); return; }
|
||||||
if (!box) {
|
if (!box) {
|
||||||
box = document.createElement('pre');
|
box = document.createElement('pre');
|
||||||
box.id = 'boot-error';
|
box.id = 'boot-error';
|
||||||
@@ -48,6 +55,10 @@ function asset(string $path): string
|
|||||||
window.addEventListener('error', function (e) {
|
window.addEventListener('error', function (e) {
|
||||||
var el = e.target;
|
var el = e.target;
|
||||||
if (el && el !== window && (el.src || el.href)) {
|
if (el && el !== window && (el.src || el.href)) {
|
||||||
|
if (!(el.hasAttribute && el.hasAttribute('data-boot-critical'))) {
|
||||||
|
console.warn('CourseHub — non-critical resource failed:', el.src || el.href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
report('Failed to load', el.src || el.href);
|
report('Failed to load', el.src || el.href);
|
||||||
} else {
|
} else {
|
||||||
report('Error', (e.message || e.error) +
|
report('Error', (e.message || e.error) +
|
||||||
@@ -126,8 +137,8 @@ function asset(string $path): string
|
|||||||
<div class="toasts" id="toasts"></div>
|
<div class="toasts" id="toasts"></div>
|
||||||
<div id="overlay-root"></div>
|
<div id="overlay-root"></div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.min.js" data-boot-critical></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js" data-boot-critical></script>
|
||||||
<script src="<?= asset('assets/app.js') ?>"></script>
|
<script src="<?= asset('assets/app.js') ?>" data-boot-critical></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user