diff --git a/framework/core/js/src/common/Application.tsx b/framework/core/js/src/common/Application.tsx index 6dd4fe922..36af01500 100644 --- a/framework/core/js/src/common/Application.tsx +++ b/framework/core/js/src/common/Application.tsx @@ -33,6 +33,7 @@ import type Mithril from 'mithril'; import type Component from './Component'; import type { ComponentAttrs } from './Component'; import Model, { SavedModelData } from './Model'; +import fireApplicationError from './helpers/fireApplicationError'; export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd'; @@ -262,7 +263,25 @@ export default class Application { } public boot() { - this.initializers.toArray().forEach((initializer) => initializer(this)); + const caughtInitializationErrors: CallableFunction[] = []; + + this.initializers.toArray().forEach((initializer) => { + try { + initializer(this); + } catch (e) { + const extension = initializer.itemName.includes('/') + ? initializer.itemName.replace(/(\/flarum-ext-)|(\/flarum-)/g, '-') + : initializer.itemName; + + caughtInitializationErrors.push(() => + fireApplicationError( + extractText(app.translator.trans('core.lib.error.extension_initialiation_failed_message', { extension })), + `${extension} failed to initialize`, + e + ) + ); + } + }); this.store.pushPayload({ data: this.data.resources }); @@ -273,6 +292,8 @@ export default class Application { this.mount(); this.initialRoute = window.location.href; + + caughtInitializationErrors.forEach((handler) => handler()); } // TODO: This entire system needs a do-over for v2 diff --git a/framework/core/js/src/common/helpers/fireApplicationError.ts b/framework/core/js/src/common/helpers/fireApplicationError.ts new file mode 100644 index 000000000..b4e8c9e85 --- /dev/null +++ b/framework/core/js/src/common/helpers/fireApplicationError.ts @@ -0,0 +1,18 @@ +import app from '../app'; + +/** + * Fire a Flarum error which is shown in the JS console for everyone and in an alert for the admin. + * + * @param userTitle: a user friendly title of the error, should be localized. + * @param consoleTitle: an error title that goes in the console, doesn't have to be localized. + * @param error: the error. + */ +export default function fireApplicationError(userTitle: string, consoleTitle: string, error: any) { + console.group(`%c${consoleTitle}`, 'background-color: #d83e3e; color: #ffffff; font-weight: bold;'); + console.error(error); + console.groupEnd(); + + if (app.session?.user?.isAdmin()) { + app.alerts.show({ type: 'error' }, `${userTitle}`); + } +} diff --git a/framework/core/js/src/common/models/User.tsx b/framework/core/js/src/common/models/User.tsx index c1f6a9830..8b2042b3e 100644 --- a/framework/core/js/src/common/models/User.tsx +++ b/framework/core/js/src/common/models/User.tsx @@ -42,6 +42,10 @@ export default class User extends Model { return Model.hasMany('groups').call(this); } + isAdmin() { + return Model.attribute('isAdmin').call(this); + } + joinTime() { return Model.attribute('joinTime', Model.transformDate).call(this); } diff --git a/framework/core/locale/core.yml b/framework/core/locale/core.yml index e634f5ad8..037e8c1f1 100644 --- a/framework/core/locale/core.yml +++ b/framework/core/locale/core.yml @@ -534,6 +534,7 @@ core: # These translations are displayed as error messages. error: dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}" + extension_initialiation_failed_message: "{extension} failed to initialize, check the browser console for further information." generic_message: "Oops! Something went wrong. Please reload the page and try again." missing_dependencies_message: "Cannot enable {extension} until the following dependencies are enabled: {extensions}" not_found_message: The requested resource was not found. diff --git a/framework/core/src/Api/Serializer/CurrentUserSerializer.php b/framework/core/src/Api/Serializer/CurrentUserSerializer.php index c8b5cd1cb..24a9417a7 100644 --- a/framework/core/src/Api/Serializer/CurrentUserSerializer.php +++ b/framework/core/src/Api/Serializer/CurrentUserSerializer.php @@ -25,7 +25,8 @@ class CurrentUserSerializer extends UserSerializer 'markedAllAsReadAt' => $this->formatDate($user->marked_all_as_read_at), 'unreadNotificationCount' => (int) $user->getUnreadNotificationCount(), 'newNotificationCount' => (int) $user->getNewNotificationCount(), - 'preferences' => (array) $user->preferences + 'preferences' => (array) $user->preferences, + 'isAdmin' => $user->isAdmin(), ]; return $attributes;