]> source.dussan.org Git - gitea.git/commitdiff
Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971)
authorwxiaoguang <wxiaoguang@gmail.com>
Wed, 30 Mar 2022 05:52:24 +0000 (13:52 +0800)
committerGitHub <noreply@github.com>
Wed, 30 Mar 2022 05:52:24 +0000 (13:52 +0800)
* ROOT_URL issues: some users did wrong to there app.ini config, then:
    * The assets can not be loaded (AppSubUrl != "" and users try to access http://host:3000/)
    *The ROOT_URL is wrong, then many URLs in Gitea are broken.
Now Gitea show enough information to users.

* JavaScript error issues, there are many users affected by JavaScript errors, some are caused by frontend bugs, some are caused by broken customized templates. If these JS errors can be found at first time, then maintainers do not need to ask about how bug occurs again and again.

* Some people like to modify the `head.tmpl`, so we separate the script part to `head_script.tmpl`, then it's much safer.

* use specialized CSS class "js-global-error", end users still have a chance to hide error messages by customized CSS styles.

templates/base/footer.tmpl
templates/base/head.tmpl
templates/base/head_script.tmpl [new file with mode: 0644]
web_src/js/bootstrap.js [new file with mode: 0644]
web_src/js/features/common-global.js
web_src/js/index.js
web_src/js/publicpath.js [deleted file]

index 1aabfa2f5c6442e1eb415831bfc1cd2bf8f5819e..9bf16f8aa5b55858a923476ec53b26ee31b92087 100644 (file)
@@ -22,7 +22,7 @@
                <script src='https://hcaptcha.com/1/api.js' async></script>
        {{end}}
 {{end}}
-       <script src="{{AssetUrlPrefix}}/js/index.js?v={{MD5 AppVer}}"></script>
+       <script src="{{AssetUrlPrefix}}/js/index.js?v={{MD5 AppVer}}" onerror="alert('Failed to load asset files from ' + this.src + ', please make sure the asset files can be accessed and the ROOT_URL setting in app.ini is correct.')"></script>
 {{template "custom/footer" .}}
 </body>
 </html>
index 2a9c24255d145965285226306d7da77610bf28c9..35157e9b954b442ea48fff86f6880b6e4265f131 100644 (file)
        <link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom">
        <link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss">
 {{end}}
-       <script>
-               <!-- /* eslint-disable */ -->
-               window.config = {
-                       appVer: '{{AppVer}}',
-                       appSubUrl: '{{AppSubUrl}}',
-                       assetUrlPrefix: '{{AssetUrlPrefix}}',
-                       runModeIsProd: {{.RunModeIsProd}},
-                       customEmojis: {{CustomEmojis}},
-                       useServiceWorker: {{UseServiceWorker}},
-                       csrfToken: '{{.CsrfToken}}',
-                       pageData: {{.PageData}},
-                       requireTribute: {{.RequireTribute}},
-                       notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}
-                       enableTimeTracking: {{EnableTimetracking}},
-                       {{if .RequireTribute}}
-                       tributeValues: Array.from(new Map([
-                               {{ range .Participants }}
-                               ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
-                               name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
-                               {{ end }}
-                               {{ range .Assignees }}
-                               ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
-                               name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
-                               {{ end }}
-                               {{ range .MentionableTeams }}
-                                       ['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}',
-                                       name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
-                               {{ end }}
-                       ]).values()),
-                       {{end}}
-                       mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
-                       {{/* this global i18n object should only contain general texts. for specialized texts, it should be provided inside the related modules by: (1) API response (2) HTML data-attribute (3) PageData */}}
-                       i18n: {
-                               copy_success: '{{.i18n.Tr "copy_success"}}',
-                               copy_error: '{{.i18n.Tr "copy_error"}}',
-                               error_occurred: '{{.i18n.Tr "error.occurred"}}',
-                               network_error: '{{.i18n.Tr "error.network_error"}}',
-                       },
-               };
-               {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
-               window.config.pageData = window.config.pageData || {};
-       </script>
        <link rel="icon" href="{{AssetUrlPrefix}}/img/logo.svg" type="image/svg+xml">
        <link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
        <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{MD5 AppVer}}">
+
+       {{template "base/head_script" .}}
+
        <noscript>
                <style>
                        .dropdown:hover > .menu { display: block; }
diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl
new file mode 100644 (file)
index 0000000..e6a8060
--- /dev/null
@@ -0,0 +1,49 @@
+{{/*
+==== DO NOT EDIT ====
+If you are customizing Gitea, please do not change this file.
+If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
+*/}}
+<script>
+       <!-- /* eslint-disable */ -->
+       window.addEventListener('error', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
+       window.config = {
+               appVer: '{{AppVer}}',
+               appUrl: '{{AppUrl}}',
+               appSubUrl: '{{AppSubUrl}}',
+               assetUrlPrefix: '{{AssetUrlPrefix}}',
+               runModeIsProd: {{.RunModeIsProd}},
+               customEmojis: {{CustomEmojis}},
+               useServiceWorker: {{UseServiceWorker}},
+               csrfToken: '{{.CsrfToken}}',
+               pageData: {{.PageData}},
+               requireTribute: {{.RequireTribute}},
+               notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}
+               enableTimeTracking: {{EnableTimetracking}},
+               {{if .RequireTribute}}
+               tributeValues: Array.from(new Map([
+                       {{ range .Participants }}
+                       ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
+                       name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
+                       {{ end }}
+                       {{ range .Assignees }}
+                       ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
+                       name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
+                       {{ end }}
+                       {{ range .MentionableTeams }}
+                               ['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}',
+                               name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
+                       {{ end }}
+               ]).values()),
+               {{end}}
+               mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
+               {{/* this global i18n object should only contain general texts. for specialized texts, it should be provided inside the related modules by: (1) API response (2) HTML data-attribute (3) PageData */}}
+               i18n: {
+                       copy_success: '{{.i18n.Tr "copy_success"}}',
+                       copy_error: '{{.i18n.Tr "copy_error"}}',
+                       error_occurred: '{{.i18n.Tr "error.occurred"}}',
+                       network_error: '{{.i18n.Tr "error.network_error"}}',
+               },
+       };
+       {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
+       window.config.pageData = window.config.pageData || {};
+</script>
diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js
new file mode 100644 (file)
index 0000000..cf13b2a
--- /dev/null
@@ -0,0 +1,41 @@
+import {joinPaths} from './utils.js';
+
+// DO NOT IMPORT window.config HERE!
+// to make sure the error handler always works, we should never import `window.config`, because some user's custom template breaks it.
+
+// This sets up the URL prefix used in webpack's chunk loading.
+// This file must be imported before any lazy-loading is being attempted.
+__webpack_public_path__ = joinPaths(window?.config?.assetUrlPrefix ?? '/', '/');
+
+export function showGlobalErrorMessage(msg) {
+  const pageContent = document.querySelector('.page-content');
+  if (!pageContent) return;
+  const el = document.createElement('div');
+  el.innerHTML = `<div class="ui container negative message center aligned js-global-error" style="white-space: pre-line;"></div>`;
+  el.childNodes[0].textContent = msg;
+  pageContent.prepend(el.childNodes[0]);
+}
+
+/**
+ * @param {ErrorEvent} e
+ */
+function processWindowErrorEvent(e) {
+  showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`);
+}
+
+function initGlobalErrorHandler() {
+  if (!window.config) {
+    showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
+  }
+
+  // we added an event handler for window error at the very beginning of <script> of page head
+  // the handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before this init
+  // then in this init, we can collect all error events and show them
+  for (const e of window._globalHandlerErrors || []) {
+    processWindowErrorEvent(e);
+  }
+  // then, change _globalHandlerErrors to an object with push method, to process further error events directly
+  window._globalHandlerErrors = {'push': (e) => processWindowErrorEvent(e)};
+}
+
+initGlobalErrorHandler();
index a9baf9be0c68374b6a7a876ce6741da64506011b..60af4d0d67e0e918f416e054cc165df18b679c87 100644 (file)
@@ -3,8 +3,9 @@ import 'jquery.are-you-sure';
 import {mqBinarySearch} from '../utils.js';
 import createDropzone from './dropzone.js';
 import {initCompColorPicker} from './comp/ColorPicker.js';
+import {showGlobalErrorMessage} from '../bootstrap.js';
 
-const {csrfToken} = window.config;
+const {appUrl, csrfToken} = window.config;
 
 export function initGlobalFormDirtyLeaveConfirm() {
   // Warn users that try to leave a page after entering data into a form.
@@ -343,3 +344,20 @@ export function initGlobalButtons() {
     });
   });
 }
+
+/**
+ * Too many users set their ROOT_URL to wrong value, and it causes a lot of problems:
+ *   * Cross-origin API request without correct cookie
+ *   * Incorrect href in <a>
+ *   * ...
+ * So we check whether current URL starts with AppUrl(ROOT_URL).
+ * If they don't match, show a warning to users.
+ */
+export function checkAppUrl() {
+  const curUrl = window.location.href;
+  if (curUrl.startsWith(appUrl)) {
+    return;
+  }
+  showGlobalErrorMessage(`Your ROOT_URL in app.ini is ${appUrl} but you are visiting ${curUrl}
+You should set ROOT_URL correctly, otherwise the web may not work correctly.`);
+}
index b7eba5e6649a4c0a6cb12f1f26ed7e779515b959..18b949e4e62f72f3f792db2e1279d34c7b793711 100644 (file)
@@ -1,4 +1,5 @@
-import './publicpath.js';
+// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
+import './bootstrap.js';
 
 import $ from 'jquery';
 import {initVueEnv} from './components/VueComponentLoader.js';
@@ -39,6 +40,7 @@ import {
 } from './features/repo-issue.js';
 import {initRepoEllipsisButton, initRepoCommitLastCommitLoader} from './features/repo-commit.js';
 import {
+  checkAppUrl,
   initFootLanguageMenu,
   initGlobalButtonClickOnEnter,
   initGlobalButtons,
@@ -82,7 +84,6 @@ $.fn.tab.settings.silent = true;
 $.fn.checkbox.settings.enableEnterKey = false;
 
 initVueEnv();
-
 $(document).ready(() => {
   initGlobalCommon();
 
@@ -169,4 +170,6 @@ $(document).ready(() => {
   initUserAuthWebAuthn();
   initUserAuthWebAuthnRegister();
   initUserSettings();
+
+  checkAppUrl();
 });
diff --git a/web_src/js/publicpath.js b/web_src/js/publicpath.js
deleted file mode 100644 (file)
index 44448a8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-// This sets up the URL prefix used in webpack's chunk loading.
-// This file must be imported before any lazy-loading is being attempted.
-import {joinPaths} from './utils.js';
-const {assetUrlPrefix} = window.config;
-
-__webpack_public_path__ = joinPaths(assetUrlPrefix, '/');