UNPKG

@eclipse-scout/core

Version:
1,112 lines (1,094 loc) 5.71 MB
import * as __WEBPACK_EXTERNAL_MODULE_reflect_metadata_lite_5c49451e__ from "reflect-metadata/lite"; import * as __WEBPACK_EXTERNAL_MODULE_sourcemapped_stacktrace_254f05bd__ from "sourcemapped-stacktrace"; /******/ var __webpack_modules__ = ({ /***/ "./src/App.ts": /*!********************!*\ !*** ./src/App.ts ***! \********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ App: () => (/* binding */ App) /* harmony export */ }); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index */ "./src/index.ts"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! jquery */ "jquery"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_1__); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ let instance = null; let listeners = []; let bootstrappers = []; class App extends _index__WEBPACK_IMPORTED_MODULE_0__.EventEmitter { static addListener(type, handler) { let listener = { type: type, func: handler }; if (instance) { instance.events.addListener(listener); } else { listeners.push(listener); } return listener; } /** * Adds a function that needs to be executed while bootstrapping. * @see AppModel.bootstrappers */ static addBootstrapper(bootstrapper) { if (bootstrappers.indexOf(bootstrapper) > -1) { throw new Error('Bootstrapper is already registered.'); } bootstrappers.push(bootstrapper); } /** * The response of a successful ajax call with status 200 may contain a {@link JsonErrorResponse}. * This method detects this case and throws an error containing the error details of the response together with the given request url. * @throws JsonErrorResponseContainer * @returns the given data as it is if it does not contain an error */ static handleJsonError(url, data) { if (data?.error) { // The result may contain a json error (e.g. session timeout) -> abort processing throw { error: data.error, url: url }; } return data; } static get() { return instance; } static _set(newApp) { if (instance) { jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isWarnEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.warn('Overwriting already existing App "' + instance + '" with "' + newApp + '".'); } instance = newApp; } remote; initialized; sessions; errorHandler; version; bootstrappers; _loadingTimeoutId; constructor() { super(); this.remote = false; this.initialized = false; this.sessions = []; this.bootstrappers = []; this._loadingTimeoutId = null; // register the listeners which were added to scout before the app is created listeners.forEach(listener => { this.addListener(listener); }); listeners = []; App._set(this); this.errorHandler = this._createErrorHandler(); } /** * Main initialization function. * * Calls {@link _prepare}, {@link _bootstrap} and {@link _init}.<br> * At the initial phase the essential objects are initialized, those which are required for the next phases like logging and the object factory.<br> * During the bootstrap phase additional scripts may get loaded required for a successful session startup.<br> * The actual initialization does not get started before these bootstrap scripts are loaded. */ init(options) { options = options || {}; return this._prepare(options).then(this._bootstrap.bind(this, options.bootstrap)).then(this._init.bind(this, options)).then(this._initDone.bind(this, options)).catch(this._fail.bind(this, options)); } /** * Initializes the logging framework and the object factory. * This happens at the prepare phase because all these things should be available from the beginning. */ _prepare(options) { return this._prepareLogging(options).done(() => { this._prepareEssentials(options); this._prepareDone(options); }); } _prepareEssentials(options) { _index__WEBPACK_IMPORTED_MODULE_0__.ObjectFactory.get().init(); } _prepareDone(options) { this.trigger('prepare', { options: options }); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isDebugEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.debug('App prepared'); } _prepareLogging(options) { return _index__WEBPACK_IMPORTED_MODULE_0__.logging.bootstrap(); } /** * Executes the bootstrappers. * * The actual session startup begins only when all promises of the bootstrappers are completed. * This gives the possibility to dynamically load additional scripts or files which are mandatory for a successful application startup. */ _bootstrap(options) { options = options || {}; options.bootstrappers = options.bootstrappers || []; this.bootstrappers = [...this._defaultBootstrappers(options), ...options.bootstrappers, ...this.bootstrappers, ...bootstrappers].filter(bootstrapper => !!bootstrapper); return jquery__WEBPACK_IMPORTED_MODULE_1___default().promiseAll(this._doBootstrap()).catch(this._bootstrapFail.bind(this, options)).then(this._bootstrapDone.bind(this, options)); // BootstrapDone must only be executed if there are no boostrap errors } _defaultBootstrappers(options) { return [_index__WEBPACK_IMPORTED_MODULE_0__.Device.get().bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.Device.get()), _index__WEBPACK_IMPORTED_MODULE_0__.fonts.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.fonts, options.fonts), _index__WEBPACK_IMPORTED_MODULE_0__.locales.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.locales, options.localesUrl), _index__WEBPACK_IMPORTED_MODULE_0__.texts.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.texts, options.textsUrl), _index__WEBPACK_IMPORTED_MODULE_0__.codes.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.codes, options.codesUrl), _index__WEBPACK_IMPORTED_MODULE_0__.access.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.access, options.permissionsUrl), _index__WEBPACK_IMPORTED_MODULE_0__.config.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.config, options.configUrl), _index__WEBPACK_IMPORTED_MODULE_0__.uiPreferences.bootstrap.bind(_index__WEBPACK_IMPORTED_MODULE_0__.uiPreferences)]; } _doBootstrap() { return this.bootstrappers.map(bootstrapper => bootstrapper()); } _bootstrapDone(options) { _index__WEBPACK_IMPORTED_MODULE_0__.webstorage.removeItemFromSessionStorage('scout:bootstrapErrorPageReload'); this.trigger('bootstrap', { options: options }); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isDebugEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.debug('App bootstrapped'); } /** * @param vararg may either be * - an {@link AjaxError} for requests executed with {@link ajax} or {@link AjaxCall} * - a {@link JQuery.jqXHR} for requests executed with {@link $.ajax}. The parameters `textStatus`, `errorThrown` and `requestOptions` are only set in this case. * - a {@link JsonErrorResponseContainer} if a successful response contained a {@link JsonErrorResponse} which was transformed to an error (e.g. using {@link App.handleJsonError}). */ _bootstrapFail(options, vararg, textStatus, errorThrown, requestOptions) { jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isInfoEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info('App bootstrap failed'); // If one of the bootstrap ajax call fails due to a session timeout, the index.html is probably loaded from cache without asking the server for its validity. // Normally, loading the index.html should already return a session timeout, but if it is loaded from the (back button) cache, no request will be done and therefore no timeout can be returned. // The browser is allowed to display a page when navigating back without issuing a request even though cache-headers are set to must-revalidate. // The only way to prevent it would be the no-store header but then pressing back would always reload the page and not only on a session timeout. // Sometimes the JavaScript and therefore the ajax calls won't be executed in case the page is loaded from that cache, but sometimes they will nevertheless (we don't know the reasons). // So, if it that happens, the server will either return a session timeout or a status 401 (Unauthorized) and the best thing we can do is to reload the page hoping a request for the index.html // will be done which eventually will be forwarded to the login page. // Additionally, requests may fail due to other various reasons, e.g. Chrome may report ERR_NETWORK_CHANGED or ERR_CERT_VERIFER_CHANGED. // Since a page reload normally solves these issues as well, the reload is done on any error not just session timeouts. let { url, message } = this._analyzeBootstrapError(vararg, textStatus, errorThrown, requestOptions); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isInfoEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info(`Error for resource ${url}. Reloading page...`); if (_index__WEBPACK_IMPORTED_MODULE_0__.webstorage.getItemFromSessionStorage('scout:bootstrapErrorPageReload')) { // Prevent loop in case reloading did not solve the problem jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isWarnEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.warn('Prevented automatic reload, startup will likely fail.'); _index__WEBPACK_IMPORTED_MODULE_0__.webstorage.removeItemFromSessionStorage('scout:bootstrapErrorPageReload'); throw new Error(`Bootstrap resource ${url} could not be loaded: ${message}`); } _index__WEBPACK_IMPORTED_MODULE_0__.webstorage.setItemToSessionStorage('scout:bootstrapErrorPageReload', 'true'); _index__WEBPACK_IMPORTED_MODULE_0__.scout.reloadPage(); // Make sure promise will be rejected with all original arguments so that it can be eventually handled by this._fail // eslint-disable-next-line prefer-rest-params let args = _index__WEBPACK_IMPORTED_MODULE_0__.objects.argumentsToArray(arguments).slice(1); return jquery__WEBPACK_IMPORTED_MODULE_1___default().rejectedPromise(...args); } _analyzeBootstrapError(vararg, textStatus, errorThrown, requestOptions) { let ajaxError; let jsonError; if (vararg instanceof _index__WEBPACK_IMPORTED_MODULE_0__.AjaxError) { ajaxError = vararg; } else if (jquery__WEBPACK_IMPORTED_MODULE_1___default().isJqXHR(vararg)) { ajaxError = new _index__WEBPACK_IMPORTED_MODULE_0__.AjaxError({ jqXHR: vararg, textStatus: textStatus, errorThrown: errorThrown, requestOptions: requestOptions }); } else if (_index__WEBPACK_IMPORTED_MODULE_0__.objects.isObject(vararg) && vararg.error) { jsonError = vararg; } let url; let message; if (ajaxError) { // AJAX error // If a resource returns 401 (unauthorized) it is likely a session timeout. // This may happen for REST resources or if a reverse proxy returned the response url = ajaxError.requestOptions?.url || ''; message = `${ajaxError.errorDo?.message || ''}`; let httpStatus = `${this.errorHandler.formatAjaxStatus(ajaxError.jqXHR, ajaxError.errorThrown)}`; if (!message.includes(httpStatus)) { message = `${message} [${this.errorHandler.formatAjaxStatus(ajaxError.jqXHR, ajaxError.errorThrown)}]`.trim(); } } else if (jsonError) { // Json based error // Json errors (normally processed by Session.js) are returned with http status 200 url = jsonError.url; message = `${jsonError.error.message} [Code ${jsonError.error.code}]`; } return { url, message }; } /** * Initializes a session for each html element with class '.scout' and stores them in scout.sessions. */ _init(options) { options = options || {}; this.setLoading(true); let compatibilityPromise = this._checkBrowserCompatibility(options); if (compatibilityPromise) { this.setLoading(false); return compatibilityPromise.then(newOptions => this._init(newOptions)); } this._initVersion(options); this._prepareDOM(); this._installErrorHandler(); this._installGlobalMouseDownInterceptor(); this._installSyntheticActiveStateHandler(); this._ajaxSetup(); this._installExtensions(); this._triggerInstallExtensions(); return this._load(options).then(this._loadSessions.bind(this, options.session)); } /** * Maybe implemented to load data from a server before the desktop is created. * @returns promise which is resolved after the loading is complete */ _load(options) { return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(); } _checkBrowserCompatibility(options) { let device = _index__WEBPACK_IMPORTED_MODULE_0__.Device.get(); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isInfoEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info('Detected browser ' + device.browser + ' version ' + device.browserVersion); if (!_index__WEBPACK_IMPORTED_MODULE_0__.scout.nvl(options.checkBrowserCompatibility, true) || device.isSupportedBrowser()) { // No check requested or browser is supported return; } let deferred = jquery__WEBPACK_IMPORTED_MODULE_1___default().Deferred(); let newOptions = _index__WEBPACK_IMPORTED_MODULE_0__.objects.valueCopy(options); newOptions.checkBrowserCompatibility = false; jquery__WEBPACK_IMPORTED_MODULE_1___default()('.scout').each(function () { let $entryPoint = jquery__WEBPACK_IMPORTED_MODULE_1___default()(this); let $box = $entryPoint.appendDiv(); $box.load('unsupported-browser.html', () => { $box.find('button').on('click', () => { $box.remove(); deferred.resolve(newOptions); }); }); }); return deferred.promise(); } setLoading(loading) { if (loading) { this._loadingTimeoutId = setTimeout(() => { // Don't start loading if a desktop is already rendered to prevent flickering when the loading will be set to false after app initialization finishes if (!this.sessions.some(session => session.desktop && session.desktop.rendered)) { this._renderLoading(); } }, 200); } else { clearTimeout(this._loadingTimeoutId); this._loadingTimeoutId = null; this._removeLoading(); } } _renderLoading() { let $body = jquery__WEBPACK_IMPORTED_MODULE_1___default()('body'), $loadingRoot = $body.children('.application-loading-root'); if (!$loadingRoot.length) { $loadingRoot = $body.appendDiv('application-loading-root').addClass('application-loading-root').fadeIn(); } _index__WEBPACK_IMPORTED_MODULE_0__.aria.role($loadingRoot, 'alert'); _index__WEBPACK_IMPORTED_MODULE_0__.aria.screenReaderOnly($loadingRoot.appendDiv('text').attr('lang', 'en-US').text('Loading')); this._renderLoadingElement($loadingRoot, 'application-loading01'); this._renderLoadingElement($loadingRoot, 'application-loading02'); this._renderLoadingElement($loadingRoot, 'application-loading03'); } _renderLoadingElement($loadingRoot, cssClass) { if ($loadingRoot.children('.' + cssClass).length) { return; } $loadingRoot.appendDiv(cssClass).hide().fadeIn(); } _removeLoading() { let $loadingRoot = jquery__WEBPACK_IMPORTED_MODULE_1___default()('body').children('.application-loading-root'); // the fadeout animation only contains a to-value and no from-value // therefore set the current value to the elements style $loadingRoot.css('opacity', $loadingRoot.css('opacity')); // Add animation listener before adding the classes to ensure the listener will always be triggered even while debugging $loadingRoot.oneAnimationEnd(() => $loadingRoot.remove()); if ($loadingRoot.css('opacity') === '1') { $loadingRoot.addClass('fadeout and-more'); } else { $loadingRoot.addClass('fadeout'); } if (!_index__WEBPACK_IMPORTED_MODULE_0__.Device.get().supportsCssAnimation()) { // fallback for old browsers that do not support the animation-end event $loadingRoot.remove(); } } _initVersion(options) { this.version = _index__WEBPACK_IMPORTED_MODULE_0__.scout.nvl(this.version, options.version, jquery__WEBPACK_IMPORTED_MODULE_1___default()('scout-version').data('value')); } _prepareDOM() { _index__WEBPACK_IMPORTED_MODULE_0__.scout.prepareDOM(document); } _installGlobalMouseDownInterceptor() { _index__WEBPACK_IMPORTED_MODULE_0__.scout.installGlobalMouseDownInterceptor(document); } _installSyntheticActiveStateHandler() { _index__WEBPACK_IMPORTED_MODULE_0__.scout.installSyntheticActiveStateHandler(document); } /** * Installs a global error handler. * * Note: we do not install an error handler on popup-windows because everything is controlled by the main-window * so exceptions will also occur in that window. This also means, the fatal message-box will be displayed in the * main-window, even when a popup-window is opened and active. * * Caution: The error.stack doesn't look the same in different browsers. Chrome for instance puts the error message * on the first line of the stack. Firefox does only contain the stack lines, without the message, but in return * the stack trace is much longer :) */ _installErrorHandler() { window.onerror = this.errorHandler.windowErrorHandler; // FIXME bsh, cgu: use ErrorHandler to handle unhandled promise rejections. Just replacing jQuery.Deferred.exceptionHook(error, stack) does not work // because it is called on every exception and not only on unhandled. // https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event would be exactly what we need, but jQuery does not support it. // Bluebird has a polyfill -> can it be ported to jQuery? } _createErrorHandler(opts) { opts = jquery__WEBPACK_IMPORTED_MODULE_1___default().extend({}, opts); return _index__WEBPACK_IMPORTED_MODULE_0__.scout.create(_index__WEBPACK_IMPORTED_MODULE_0__.ErrorHandler, opts); } /** * Uses the object returned by {@link _ajaxDefaults} to set up ajax. The values in that object are used as default values for every ajax call. */ _ajaxSetup() { let ajaxDefaults = this._ajaxDefaults(); if (ajaxDefaults) { jquery__WEBPACK_IMPORTED_MODULE_1___default().ajaxSetup(ajaxDefaults); } } /** * Returns the defaults for every ajax call. You may override it to set custom defaults. * By default {@link _beforeAjaxCall} is assigned to the beforeSend method. * * Note: This will affect every ajax call, so use it with care! See also the advice on https://api.jquery.com/jquery.ajaxsetup/. */ _ajaxDefaults() { return { beforeSend: this._beforeAjaxCall.bind(this) }; } /** * Called before every ajax call. Sets the header X-Scout-Correlation-Id. * * Maybe overridden to set custom headers or to execute other code which should run before an ajax call. */ _beforeAjaxCall(request, settings) { request.setRequestHeader('X-Scout-Correlation-Id', _index__WEBPACK_IMPORTED_MODULE_0__.numbers.correlationId()); request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // explicitly add here because jQuery only adds it automatically if it is no crossDomain request if (this.sessions[0] && this.sessions[0].ready) { request.setRequestHeader('Accept-Language', this.sessions[0].locale.languageTag); } } _loadSessions(options) { options = options || {}; let promises = []; jquery__WEBPACK_IMPORTED_MODULE_1___default()('.scout').each((i, elem) => { let $entryPoint = jquery__WEBPACK_IMPORTED_MODULE_1___default()(elem); options.portletPartId = options.portletPartId || $entryPoint.data('partid') || '0'; let promise = this._loadSession($entryPoint, options); promises.push(promise); }); return jquery__WEBPACK_IMPORTED_MODULE_1___default().promiseAll(promises); } /** * @returns promise which is resolved when the session is ready */ _loadSession($entryPoint, model) { let sessionModel = { $entryPoint: $entryPoint }; let options = jquery__WEBPACK_IMPORTED_MODULE_1___default().extend({}, model, sessionModel); options.locale = options.locale || this._loadLocale(); let session = this._createSession(options); this.sessions.push(session); // TODO [7.0] cgu improve this, start must not be executed because it currently does a server request let desktop = this._createDesktop(session.root); this._triggerDesktopReady(desktop); session.render(() => { session._renderDesktop(); // Ensure layout is valid (explicitly layout immediately and don't wait for setTimeout to run to make layouting invisible to the user) session.layoutValidator.validate(); session.focusManager.validateFocus(); session.ready = true; this._triggerSessionReady(session); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isInfoEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info('Session initialized. Detected ' + _index__WEBPACK_IMPORTED_MODULE_0__.Device.get()); }); return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(); } /** @internal */ _triggerDesktopReady(desktop) { this.trigger('desktopReady', { desktop: desktop }); } /** @internal */ _triggerSessionReady(session) { this.trigger('sessionReady', { session: session }); } _createSession(options) { return _index__WEBPACK_IMPORTED_MODULE_0__.scout.create(_index__WEBPACK_IMPORTED_MODULE_0__.Session, options); } _createDesktop(parent) { return _index__WEBPACK_IMPORTED_MODULE_0__.scout.create(_index__WEBPACK_IMPORTED_MODULE_0__.Desktop, { parent: parent }); } /** * @returns the locale to be used when no locale is provided as session option. By default, the navigators locale is used. */ _loadLocale() { return _index__WEBPACK_IMPORTED_MODULE_0__.locales.getNavigatorLocale(); } _initDone(options) { this.initialized = true; this.setLoading(false); this.trigger('init', { options: options }); jquery__WEBPACK_IMPORTED_MODULE_1___default().log.isInfoEnabled() && jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info('App initialized'); } _fail(options, error) { for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } jquery__WEBPACK_IMPORTED_MODULE_1___default().log.error('App initialization failed.'); this.setLoading(false); let promises = []; if (_index__WEBPACK_IMPORTED_MODULE_0__.webstorage.getItemFromSessionStorage('scout:bootstrapErrorPageReload')) { // Do not append a message, page is about to be reloaded } else if (this.sessions.length === 0) { promises.push(this.errorHandler.handle(error, ...args).then(errorInfo => { this._appendStartupError(jquery__WEBPACK_IMPORTED_MODULE_1___default()('body'), errorInfo); })); } else { // Session.js may already display a fatal message box // -> don't handle the error again and display multiple error messages this.sessions.filter(session => !session.ready && !session.isFatalMessageShown()).forEach(session => { session.$entryPoint.empty(); const errorHandler = this._createErrorHandler({ session: session }); const promise = errorHandler.analyzeError(error).then(info => { info.showAsFatalError = true; return errorHandler.handleErrorInfo(info); }); promises.push(promise); }); } this.trigger('fail', { error }); // Reject with original rejection arguments return jquery__WEBPACK_IMPORTED_MODULE_1___default().promiseAll(promises).then(errorInfo => jquery__WEBPACK_IMPORTED_MODULE_1___default().rejectedPromise(error, ...args)); } _appendStartupError($parent, errorInfo) { let $error = $parent.appendDiv('startup-error'); $error.appendDiv('startup-error-title').text('The application could not be started'); let message = errorInfo.message; if (errorInfo?.httpStatus) { message = this.errorHandler.getMessageBodyForHttpStatus(errorInfo?.httpStatus); } if (message) { $error.appendDiv('startup-error-message').text(message); } } /** * Override this method to install extensions to Scout objects. Since the extension feature replaces functions * on the prototype of the Scout objects you must apply 'function patches' to Scout framework or other code before * the extensions are installed. * * The default implementation does nothing. */ _installExtensions() { // NOP } /** * @see AppEventMap#installExtensions */ _triggerInstallExtensions() { this.trigger('installExtensions'); } } /***/ }), /***/ "./src/AppEventMap.ts": /*!****************************!*\ !*** ./src/AppEventMap.ts ***! \****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /***/ }), /***/ "./src/ErrorHandler.ts": /*!*****************************!*\ !*** ./src/ErrorHandler.ts ***! \*****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ ErrorHandler: () => (/* binding */ ErrorHandler) /* harmony export */ }); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index */ "./src/index.ts"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! jquery */ "jquery"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var sourcemapped_stacktrace__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! sourcemapped-stacktrace */ "sourcemapped-stacktrace"); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class ErrorHandler { objectType; logError; displayError; sendError; session; windowErrorHandler; constructor() { this.logError = true; this.displayError = true; this.sendError = false; this.windowErrorHandler = this._onWindowError.bind(this); this.session = null; } /** * Use this constant to configure whether all instances of the ErrorHandler should write * to the console. When you've installed a console appender to log4javascript you can set the * value to false, because the ErrorHandler also calls $.log.error and thus the appender has * already written the message to the console. We don't want to see it twice. */ static CONSOLE_OUTPUT = true; init(options) { jquery__WEBPACK_IMPORTED_MODULE_1___default().extend(this, options); } // Signature matches the "window.onerror" event handler // https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror _onWindowError(errorMessage, fileName, lineNumber, columnNumber, error) { try { if (this._isIgnorableScriptError(errorMessage, fileName, lineNumber, columnNumber, error)) { this.handleErrorInfo({ log: `Ignoring error. Message: ${errorMessage}`, showAsFatalError: true, level: _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.INFO }); return; } if (error instanceof Error) { this.analyzeError(error).then(info => { info.showAsFatalError = true; return this.handleErrorInfo(info); }).catch(error => console.error('Error in global JavaScript error handler', error)); return; } let code = 'J00'; let log = errorMessage + ' at ' + fileName + ':' + lineNumber + '\n(' + 'Code ' + code + ')'; this.handleErrorInfo({ message: errorMessage, showAsFatalError: true, code: code, log: log }); } catch (err) { throw new Error('Error in global JavaScript error handler: ' + err.message + ' (original error: ' + errorMessage + ' at ' + fileName + ':' + lineNumber + ')'); } } _isIgnorableScriptError(message, fileName, lineNumber, columnNumber, error) { // Ignore errors caused by scripts from a different origin. // Example: Firefox on iOS throws an error, probably caused by an internal Firefox script. // The error does not affect the application and cannot be prevented by the app either since we don't know what script it is and what it does. // In that case the error must not be shown to the user, instead just log it silently. // https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror return message && message.toLowerCase().indexOf('script error') > -1 && !fileName && !lineNumber && !columnNumber && !error; } /** * Handles unexpected JavaScript errors. The arguments are first analyzed and then handled. * * This method may be called by passing the arguments individually or as an array (or array-like object) * in the first argument. * Examples: * 1. try { ... } catch (err) { handler.handle(err); } * 2. $.get().fail(function(jqXHR, textStatus, errorThrown) { handler.handle(jqXHR, textStatus, errorThrown); } * 3. $.get().fail(function(jqXHR, textStatus, errorThrown) { handler.handle(arguments); } // <-- recommended * * @param errorOrArgs error or array or array-like object containing the error and other arguments * @returns the analyzed errorInfo */ handle(errorOrArgs) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } let error = errorOrArgs; if (errorOrArgs && args.length === 0) { if (String(errorOrArgs) === '[object Arguments]') { error = errorOrArgs[0]; args = [...errorOrArgs].slice(1); } else if (Array.isArray(errorOrArgs)) { error = errorOrArgs[0]; args = errorOrArgs.slice(1); } } return this.analyzeError(error, ...args).then(this.handleErrorInfo.bind(this)); } /** * Returns an "errorInfo" object for the given arguments. The following cases are handled: * 1. Error objects (code: computed by getJsErrorCode()) * 2. jQuery AJAX errors (code: 'X' + HTTP status code) * 3. Nothing (code: 'P3') * 4. Everything else (code: 'P4') */ analyzeError(error) { let errorInfo = { error: error, message: null, code: null, httpStatus: null, errorDo: null, stack: null, mappedStack: null, mappingError: null, log: null, debugInfo: null }; for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } return this._analyzeError(errorInfo, ...args); } _analyzeError(errorInfo) { let error = errorInfo.error; // 1. Regular errors if (error instanceof Error) { // Map stack first before analyzing the error return this.mapStack(error.stack).catch(result => { errorInfo.mappingError = result.message + '\n' + result.error.message + '\n' + result.error.stack; return null; }).then(mappedStack => { errorInfo.mappedStack = mappedStack; this._analyzeRegularError(errorInfo); return errorInfo; }); } // 2. Ajax errors if (jquery__WEBPACK_IMPORTED_MODULE_1___default().isJqXHR(error) || Array.isArray(error) && jquery__WEBPACK_IMPORTED_MODULE_1___default().isJqXHR(error[0]) || error instanceof _index__WEBPACK_IMPORTED_MODULE_0__.AjaxError) { for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { args[_key3 - 1] = arguments[_key3]; } this._analyzeAjaxError(errorInfo, ...args); return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(errorInfo); } // 3. No reason provided if (!error) { this._analyzeNoError(errorInfo); return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(errorInfo); } // 4. Everything else (e.g. when strings are thrown) this._analyzeOtherError(errorInfo); return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(errorInfo); } _analyzeRegularError(errorInfo) { let error = errorInfo.error; errorInfo.code = this.getJsErrorCode(error); errorInfo.showAsFatalError = true; errorInfo.message = String(error.message || error); if (error.stack) { errorInfo.stack = String(error.stack); } if (error['debugInfo']) { // scout extension errorInfo.debugInfo = error['debugInfo']; } let stack = errorInfo.mappedStack || errorInfo.stack; let log = []; if (!stack || stack.indexOf(errorInfo.message) === -1) { // Only log message if not already included in stack log.push(errorInfo.message); } if (stack) { log.push(stack); } if (errorInfo.mappingError) { log.push(errorInfo.mappingError); } if (errorInfo.debugInfo) { // Error throwers may put a "debugInfo" string on the error object that is then added to the log string (this is a scout extension). log.push('----- Additional debug information: -----\n' + errorInfo.debugInfo); } errorInfo.log = _index__WEBPACK_IMPORTED_MODULE_0__.arrays.format(log, '\n'); } _analyzeAjaxError(errorInfo) { for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { args[_key4 - 1] = arguments[_key4]; } let error = errorInfo.error; let jqXHR, errorThrown, requestOptions, errorDo = null; if (error instanceof _index__WEBPACK_IMPORTED_MODULE_0__.AjaxError) { // Scout Ajax Error jqXHR = error.jqXHR; errorThrown = error.errorThrown; requestOptions = error.requestOptions; // scout extension errorDo = error.errorDo; } else { // jQuery $.ajax() error (arguments of the fail handler are: jqXHR, textStatus, errorThrown, requestOptions) // The first argument (jqXHR) is stored in errorInfo.error (may even be an array) -> create args array again and extract the parameters args = _index__WEBPACK_IMPORTED_MODULE_0__.arrays.ensure(error).concat(args); jqXHR = args[0]; errorThrown = args[2]; requestOptions = args[3]; // scout extension let errorDoCandidate = jqXHR?.responseJSON?.error; if (_index__WEBPACK_IMPORTED_MODULE_0__.AjaxError.isErrorDo(errorDoCandidate)) { errorDo = errorDoCandidate; } } let ajaxRequest = this.formatAjaxRequest(requestOptions); let ajaxStatus = this.formatAjaxStatus(jqXHR, errorThrown); errorInfo.httpStatus = jqXHR.status || 0; errorInfo.code = 'X' + errorInfo.httpStatus; errorInfo.errorDo = errorDo; if (errorDo) { errorInfo.message = errorDo.message; errorInfo.level = this._severityToLogLevel(errorDo.severity); } // logging info let req = 'AJAX call' + _index__WEBPACK_IMPORTED_MODULE_0__.strings.box(' "', ajaxRequest, '"') + ' failed' + _index__WEBPACK_IMPORTED_MODULE_0__.strings.box(' [', ajaxStatus, ']'); let log = []; if (errorInfo.message) { log.push(errorInfo.message); } else { errorInfo.message = req; } log.push(req); if (jqXHR.responseText) { errorInfo.debugInfo = 'Response text:\n' + jqXHR.responseText; log.push(errorInfo.debugInfo); } errorInfo.log = _index__WEBPACK_IMPORTED_MODULE_0__.arrays.format(log, '\n'); } formatAjaxStatus(jqXHR, errorThrown) { return jqXHR.status ? _index__WEBPACK_IMPORTED_MODULE_0__.strings.join(' ', jqXHR.status + '', errorThrown) : 'Connection error'; } formatAjaxRequest(requestOptions) { return requestOptions ? _index__WEBPACK_IMPORTED_MODULE_0__.strings.join(' ', requestOptions.method, requestOptions.url) : ''; } _severityToLogLevel(severity) { switch (severity) { case 'ok': return _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.DEBUG; case 'info': return _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.INFO; case 'warning': return _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.WARN; default: return _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.ERROR; } } _analyzeOtherError(errorInfo) { let error = errorInfo.error; // Everything else (e.g. when strings are thrown) let s = typeof error === 'string' || typeof error === 'number' ? String(error) : null; errorInfo.code = 'P4'; errorInfo.message = s || 'Unexpected error'; if (!s) { try { s = JSON.stringify(error); // may throw "cyclic object value" error } catch (err) { s = String(error); } } errorInfo.log = 'Unexpected error: ' + s; } _analyzeNoError(errorInfo) { errorInfo.code = 'P3'; errorInfo.message = 'Unknown error'; errorInfo.log = 'Unexpected error (no reason provided)'; } mapStack(stack) { let deferred = jquery__WEBPACK_IMPORTED_MODULE_1___default().Deferred(); try { sourcemapped_stacktrace__WEBPACK_IMPORTED_MODULE_2__.mapStackTrace(stack, mappedStack => { deferred.resolve(_index__WEBPACK_IMPORTED_MODULE_0__.arrays.format(mappedStack, '\n')); }); } catch (e) { return jquery__WEBPACK_IMPORTED_MODULE_1___default().rejectedPromise({ message: 'stacktrace mapping failed', error: e }); } return deferred.promise(); } /** * Expects an object as returned by {@link analyzeError} and handles it: * - If the flag "logError" is set, the log message is printed to the console * - If there is a scout session and the flag "displayError" is set, the error is shown in a message box. * - If there is a scout session and the flag "sendError" is set, the error is sent to the UI server. */ handleErrorInfo(errorInfo) { errorInfo.level = _index__WEBPACK_IMPORTED_MODULE_0__.scout.nvl(errorInfo.level, _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.ERROR); if (this.logError && errorInfo.log) { this._logErrorInfo(errorInfo); } // Note: The error handler is installed globally, and we cannot tell in which scout session the error happened. // We simply use the first scout session to display the message box and log the error. This is not ideal in the // multi-session-case (portlet), but currently there is no other way. Besides, this feature is not in use yet. let session = this.session || _index__WEBPACK_IMPORTED_MODULE_0__.App.get().sessions[0]; if (session) { if (this.sendError) { this._sendErrorMessage(session, errorInfo.log, errorInfo.level); } if (this.displayError) { if (errorInfo.showAsFatalError) { if (errorInfo.level === _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.ERROR) { return this._showInternalUiErrorMessageBox(session, errorInfo.message, errorInfo.code, errorInfo.log).then(() => errorInfo); } } else { return this._showErrorMessageBox(session, errorInfo).then(() => errorInfo); } } } return jquery__WEBPACK_IMPORTED_MODULE_1___default().resolvedPromise(errorInfo); } _logErrorInfo(errorInfo) { switch (errorInfo.level) { case _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.TRACE: jquery__WEBPACK_IMPORTED_MODULE_1___default().log.trace(errorInfo.log); break; case _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.DEBUG: jquery__WEBPACK_IMPORTED_MODULE_1___default().log.debug(errorInfo.log); break; case _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.INFO: jquery__WEBPACK_IMPORTED_MODULE_1___default().log.info(errorInfo.log); break; case _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.WARN: jquery__WEBPACK_IMPORTED_MODULE_1___default().log.warn(errorInfo.log); break; default: jquery__WEBPACK_IMPORTED_MODULE_1___default().log.error(errorInfo.log); } // Note: when the null-logger is active it has already written the error to the console // when the $.log.error function has been called above, so we don't have to log again here. let writeToConsole = ErrorHandler.CONSOLE_OUTPUT; if ((jquery__WEBPACK_IMPORTED_MODULE_1___default().log) instanceof _index__WEBPACK_IMPORTED_MODULE_0__.NullLogger) { writeToConsole = false; } if (writeToConsole && window && window.console) { if (errorInfo.level === _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.ERROR && window.console.error) { window.console.error(errorInfo.log); } else if (errorInfo.level === _index__WEBPACK_IMPORTED_MODULE_0__.LogLevel.WARN && window.console.warn) { window.console.warn(errorInfo.log); } else if (window.console.log) { window.console.log(errorInfo.log); } } } /** * Generate a "cool looking" error code from the JS error object, that * does not reveal too much technical information, but at least indicates * that a JS runtime error has occurred. (In contrast, fatal errors from * the server have numeric error codes.) */ getJsErrorCode(error) { if (error) { if (error.name === 'EvalError') { return 'E1'; } if (error.name === 'InternalError') { return 'I2'; } if (error.name === 'RangeError') { return 'A3'; } if (error.name === 'ReferenceError') { return 'R4'; } if (error.name === 'SyntaxError') { return 'S5'; } if (error.name === 'TypeError') { return 'T6'; } if (error.name === 'URIError') { return 'U7'; } } return 'J0'; } _showErrorMessageBox(session, errorInfo) { const parent = session.desktop || new _index__WEBPACK_IMPORTED_MODULE_0__.NullWidget(); const msgBoxModel = { parent, session, ...this._buildErrorMessageBoxModel(session, errorInfo) }; const messageBox = _index__WEBPACK_IMPORTED_MODULE_0__.scout.create(_index__WEBPACK_IMPORTED_MODULE_0__.MessageBox, msgBoxModel); messageBox.on('action', event => messageBox.close()); messageBox.render(session.$entryPoint); // do not use messageBox.open() as the Desktop might not yet be ready (e.g. if there is an error in the App startup) return messageBox.when('action'); } _buildErrorMessageBoxModel(session, errorInfo) { const status = this.errorInfoToStatus(session, errorInfo); let code = null; if (errorInfo.error instanceof _index__WEBPACK_IMPORTED_MODULE_0__.Status && errorInfo.error.code !== 0) { code = errorInfo.error.code + ''; } if (errorInfo?.errorDo?.errorCode && errorInfo.errorDo.errorCode !== '0') { code = errorInfo.errorDo.errorCode; } let body = status.message || session.optText('ui.UnexpectedProblem', 'Unexpected problem'); if (code) { body += ' (' + session.optText('ui.ErrorCodeX', 'Code ' + code, code) + ').'; } let html = null; if (errorInfo?.errorDo?.correlationId) { let $container = session.desktop?.$container || session.$entryPoint; let correlationIdText = session.optText('CorrelationId', 'Correlation ID'); html = $container.makeDiv('error-popup-correlation-id', correlationIdText + ': ' + errorInfo.errorDo.correlationId)[0].outerHTML; } return { header: errorInfo.errorDo?.title, body: body, html: html, iconId: status.iconId, severity: status.severity, hiddenText: errorInfo.log, yesButtonText: session.optText('Ok', 'Ok') }; } _showInternalUiErrorMessageBox(session, errorMessage, errorCode, logMessage) { let options = { header: session.optText('ui.UnexpectedProblem', 'Internal UI Error'), body: _index__WEBPACK_IMPORTED_MODULE_0__.strings.join('\n\n', session.optText('ui.InternalUiErrorMsg', errorMessage, ' (' + session.optText('ui.ErrorCodeX', 'Code ' + errorCode, errorCode) + ')'), session.optText('ui.UiInconsistentMsg', '')), yesButtonText: session.optText('ui.Reload', 'Reload'), yesButtonAction: _index__WEBPACK_IMPORTED_MODULE_0__.scout.reloadPage, noButtonText: undefined, hiddenText: logMessage, iconId: _index__WEBPACK_IMPORTED_MODULE_0__.icons.SLIPPERY }; if (session.inDevelopmentMode) { options.noButtonText = session.optText('ui.Ignore', 'Ignore'); } return session.showFatalMessage(options, errorCode); } _sendErrorMessage(session, logMessage, logLevel) { session.sendLogRequest(logMessage, logLevel); } errorInfoToStatus(session, errorInfo) { if (errorInfo.error instanceof _index__WEBPACK_IMPORTED_MODULE_0__.Status) { // if a Status is thrown return errorInfo.error; } return _index__WEBPACK_IMPORTED_MODULE_0__.Status.ensure({ message: this._errorInfoToStatusMessage(session, errorInfo), severity: this._errorInfoToStatusSeverity(errorInfo), code: this._errorInfoToStatusCode(errorInfo) }); } _errorInfoToStatusCode(errorInfo) { let errorCode = errorInfo?.errorDo?.errorCode; if (errorCode && errorCode !== '0' && _index__WEBPACK_IMPORTED_MODULE_0__.objects.isNumber(errorCode)) { return _index__WEBPACK_IMPORTED_MODULE_0__.numbers.ensure(errorCode); } return errorInfo.httpStatus; } _errorInfoToStatusSeverity(errorInfo) { switch (errorInfo?.errorDo?.severity) { case 'ok': return _index__WEBPACK_IMPORTED_MODULE_0__.Status.Severity.OK; case 'info': return _index__WEBPACK_IMPORTED_MODULE_0__.Status.Severity.INFO; case 'warning': return _index__WEBPACK_IMPORTED_MODULE_0__.Status.Severity.WARNING; default: return _index__WEBPACK_IMPORTED_MODULE_0__.Status.Severity.ERROR; } } _errorInfoToStatusMessage(session, errorInfo) { if (typeof errorInfo.error === 'string') { return errorInfo.error; } const errorDo = errorInfo.errorDo; if (errorDo) { let body = errorDo.message; if (body && body !== 'undefined') { // ProcessingException has default text 'undefined' which is not very helpful -> don't use it return body; } } if (errorInfo.error?.message) { return errorInfo.error.message; } if (errorInfo.message) { return errorInfo.message; } return this.getMessageBodyForHttpStatus(errorInfo?.httpStatus, session); } /** * Gets the default error message body for the given HTTP status error code. * @param httpStatus The HTTP status error code. E.g. 503 (Service Unavailable) * @param session An optional Session for a message in the language of the user. The default language is used if omitted. * @returns The error message for the given error HTTP status. */ getMessageBodyForHttpStatus(httpStatus, session) { let body; let textMap = session ? session.textMap : _index__WEBPACK_IMPORTED_MODULE_0__.texts.get('default'); switch (httpStatus) { case 404: // Not Found body = textMap.optGet('TheRequestedResourceCouldNotBeFound', 'The requested resource could not be found.'); break; case 502: // Bad Gateway case 503: // Service Unavailable case 504: // Gateway Timeout body = textMap.optGet('NetSystemsNotAvailable', 'The system is partially unavailable at the moment.') + '\n\n' + textMap.optGet('PleaseTryAgainLater', 'Please try again later.'); break; case 403: // Forbidden body = textMap.optGet('YouAreNotAuthorizedToPerformThisAction', 'You are not authorized to perform this action.'); break; default: body = textMap.optGet('ui.UnexpectedProblem', 'Unexpected problem'); } return body; } } /***/ }), /***/ "./src/Extension.ts": /*!**************************!*\ !*** ./src/Extension.ts ***! \**************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Extension: () => (/* binding */ Extension) /* harmony export */ }); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index */ "./src/index.ts"); /* * Copyright (c) 2010, 2024 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ /** * This class is used to extend an existing Scout object. In or