code.gitea.io/gitea@v1.22.3/web_src/js/bootstrap.js (about)

     1  // DO NOT IMPORT window.config HERE!
     2  // to make sure the error handler always works, we should never import `window.config`, because
     3  // some user's custom template breaks it.
     4  
     5  // This sets up the URL prefix used in webpack's chunk loading.
     6  // This file must be imported before any lazy-loading is being attempted.
     7  __webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`;
     8  
     9  function shouldIgnoreError(err) {
    10    const ignorePatterns = [
    11      '/assets/js/monaco.', // https://github.com/go-gitea/gitea/issues/30861 , https://github.com/microsoft/monaco-editor/issues/4496
    12    ];
    13    for (const pattern of ignorePatterns) {
    14      if (err.stack?.includes(pattern)) return true;
    15    }
    16    return false;
    17  }
    18  
    19  export function showGlobalErrorMessage(msg, msgType = 'error') {
    20    const msgContainer = document.querySelector('.page-content') ?? document.body;
    21    const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages
    22    let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
    23    if (!msgDiv) {
    24      const el = document.createElement('div');
    25      el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
    26      msgDiv = el.childNodes[0];
    27    }
    28    // merge duplicated messages into "the message (count)" format
    29    const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
    30    msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
    31    msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
    32    msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
    33    msgContainer.prepend(msgDiv);
    34  }
    35  
    36  /**
    37   * @param {ErrorEvent|PromiseRejectionEvent} event - Event
    38   * @param {string} event.message - Only present on ErrorEvent
    39   * @param {string} event.error - Only present on ErrorEvent
    40   * @param {string} event.type - Only present on ErrorEvent
    41   * @param {string} event.filename - Only present on ErrorEvent
    42   * @param {number} event.lineno - Only present on ErrorEvent
    43   * @param {number} event.colno - Only present on ErrorEvent
    44   * @param {string} event.reason - Only present on PromiseRejectionEvent
    45   * @param {number} event.promise - Only present on PromiseRejectionEvent
    46   */
    47  function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}) {
    48    const err = error ?? reason;
    49    const assetBaseUrl = String(new URL(__webpack_public_path__, window.location.origin));
    50    const {runModeIsProd} = window.config ?? {};
    51  
    52    // `error` and `reason` are not guaranteed to be errors. If the value is falsy, it is likely a
    53    // non-critical event from the browser. We log them but don't show them to users. Examples:
    54    // - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors
    55    // - https://github.com/mozilla-mobile/firefox-ios/issues/10817
    56    // - https://github.com/go-gitea/gitea/issues/20240
    57    if (!err) {
    58      if (message) console.error(new Error(message));
    59      if (runModeIsProd) return;
    60    }
    61  
    62    if (err instanceof Error) {
    63      // If the error stack trace does not include the base URL of our script assets, it likely came
    64      // from a browser extension or inline script. Do not show such errors in production.
    65      if (!err.stack?.includes(assetBaseUrl) && runModeIsProd) return;
    66      // Ignore some known errors that are unable to fix
    67      if (shouldIgnoreError(err)) return;
    68    }
    69  
    70    let msg = err?.message ?? message;
    71    if (lineno) msg += ` (${filename} @ ${lineno}:${colno})`;
    72    const dot = msg.endsWith('.') ? '' : '.';
    73    const renderedType = type === 'unhandledrejection' ? 'promise rejection' : type;
    74    showGlobalErrorMessage(`JavaScript ${renderedType}: ${msg}${dot} Open browser console to see more details.`);
    75  }
    76  
    77  function initGlobalErrorHandler() {
    78    if (window._globalHandlerErrors?._inited) {
    79      showGlobalErrorMessage(`The global error handler has been initialized, do not initialize it again`);
    80      return;
    81    }
    82    if (!window.config) {
    83      showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
    84    }
    85    // we added an event handler for window error at the very beginning of <script> of page head the
    86    // handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before
    87    // this init then in this init, we can collect all error events and show them.
    88    for (const e of window._globalHandlerErrors || []) {
    89      processWindowErrorEvent(e);
    90    }
    91    // then, change _globalHandlerErrors to an object with push method, to process further error
    92    // events directly
    93    window._globalHandlerErrors = {_inited: true, push: (e) => processWindowErrorEvent(e)};
    94  }
    95  
    96  initGlobalErrorHandler();