mirror of
https://github.com/flarum/core.git
synced 2025-08-08 09:26:34 +02:00
chore: graceful failure from extend/override errors (#4134)
This commit is contained in:
@@ -279,6 +279,13 @@ export default class Application {
|
|||||||
|
|
||||||
initialRoute!: string;
|
initialRoute!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public currentInitializerExtension: string | null = null;
|
||||||
|
|
||||||
|
private handledErrors: { extension: null | string; errorId: string; error: any }[] = [];
|
||||||
|
|
||||||
public load(payload: Application['data']) {
|
public load(payload: Application['data']) {
|
||||||
this.data = payload;
|
this.data = payload;
|
||||||
this.translator.setLocale(payload.locale);
|
this.translator.setLocale(payload.locale);
|
||||||
@@ -288,17 +295,19 @@ export default class Application {
|
|||||||
const caughtInitializationErrors: CallableFunction[] = [];
|
const caughtInitializationErrors: CallableFunction[] = [];
|
||||||
|
|
||||||
this.initializers.toArray().forEach((initializer) => {
|
this.initializers.toArray().forEach((initializer) => {
|
||||||
try {
|
this.currentInitializerExtension = initializer.itemName.includes('/')
|
||||||
initializer(this);
|
|
||||||
} catch (e) {
|
|
||||||
const extension = initializer.itemName.includes('/')
|
|
||||||
? initializer.itemName.replace(/(\/flarum-ext-)|(\/flarum-)/g, '-')
|
? initializer.itemName.replace(/(\/flarum-ext-)|(\/flarum-)/g, '-')
|
||||||
: initializer.itemName;
|
: initializer.itemName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
initializer(this);
|
||||||
|
} catch (e) {
|
||||||
caughtInitializationErrors.push(() =>
|
caughtInitializationErrors.push(() =>
|
||||||
fireApplicationError(
|
fireApplicationError(
|
||||||
extractText(app.translator.trans('core.lib.error.extension_initialiation_failed_message', { extension })),
|
extractText(
|
||||||
`${extension} failed to initialize`,
|
app.translator.trans('core.lib.error.extension_initialiation_failed_message', { extension: this.currentInitializerExtension })
|
||||||
|
),
|
||||||
|
`${this.currentInitializerExtension} failed to initialize`,
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -727,4 +736,12 @@ export default class Application {
|
|||||||
|
|
||||||
return prefix + url + (queryString ? '?' + queryString : '');
|
return prefix + url + (queryString ? '?' + queryString : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleErrorOnce(extension: null | string, errorId: string, userTitle: string, consoleTitle: string, error: any) {
|
||||||
|
if (this.handledErrors.some((e) => e.errorId === errorId)) return;
|
||||||
|
|
||||||
|
this.handledErrors.push({ extension, errorId, error });
|
||||||
|
|
||||||
|
fireApplicationError(userTitle, consoleTitle, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
import extractText from './utils/extractText';
|
||||||
|
import app from './app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend an object's method by running its output through a mutating callback
|
* Extend an object's method by running its output through a mutating callback
|
||||||
* every time it is called.
|
* every time it is called.
|
||||||
@@ -28,6 +31,8 @@ export function extend<T extends Record<string, any>, K extends KeyOfType<T, Fun
|
|||||||
methods: K | K[],
|
methods: K | K[],
|
||||||
callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void
|
callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void
|
||||||
) {
|
) {
|
||||||
|
const extension = app.currentInitializerExtension;
|
||||||
|
|
||||||
// A lazy loaded module, only apply the function after the module is loaded.
|
// A lazy loaded module, only apply the function after the module is loaded.
|
||||||
if (typeof object === 'string') {
|
if (typeof object === 'string') {
|
||||||
let [namespace, id] = flarum.reg.namespaceAndIdFromPath(object);
|
let [namespace, id] = flarum.reg.namespaceAndIdFromPath(object);
|
||||||
@@ -45,7 +50,17 @@ export function extend<T extends Record<string, any>, K extends KeyOfType<T, Fun
|
|||||||
object[method] = function (this: T, ...args: Parameters<T[K]>) {
|
object[method] = function (this: T, ...args: Parameters<T[K]>) {
|
||||||
const value = original ? original.apply(this, args) : undefined;
|
const value = original ? original.apply(this, args) : undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
callback.apply(this, [value, ...args]);
|
callback.apply(this, [value, ...args]);
|
||||||
|
} catch (e) {
|
||||||
|
app.handleErrorOnce(
|
||||||
|
extension,
|
||||||
|
`${extension}::extend::${object.constructor.name}::${method.toString()}`,
|
||||||
|
extractText(app.translator.trans('core.lib.error.extension_runtime_failed_message', { extension })),
|
||||||
|
`${extension} failed to extend ${object.constructor.name}::${method.toString()}`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
} as T[K];
|
} as T[K];
|
||||||
@@ -86,6 +101,8 @@ export function override<T extends Record<any, any>, K extends KeyOfType<T, Func
|
|||||||
methods: K | K[],
|
methods: K | K[],
|
||||||
newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void
|
newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void
|
||||||
) {
|
) {
|
||||||
|
const extension = app.currentInitializerExtension;
|
||||||
|
|
||||||
// A lazy loaded module, only apply the function after the module is loaded.
|
// A lazy loaded module, only apply the function after the module is loaded.
|
||||||
if (typeof object === 'string') {
|
if (typeof object === 'string') {
|
||||||
let [namespace, id] = flarum.reg.namespaceAndIdFromPath(object);
|
let [namespace, id] = flarum.reg.namespaceAndIdFromPath(object);
|
||||||
@@ -101,7 +118,17 @@ export function override<T extends Record<any, any>, K extends KeyOfType<T, Func
|
|||||||
const original: Function = object[method];
|
const original: Function = object[method];
|
||||||
|
|
||||||
object[method] = function (this: T, ...args: Parameters<T[K]>) {
|
object[method] = function (this: T, ...args: Parameters<T[K]>) {
|
||||||
|
try {
|
||||||
return newMethod.apply(this, [original?.bind(this), ...args]);
|
return newMethod.apply(this, [original?.bind(this), ...args]);
|
||||||
|
} catch (e) {
|
||||||
|
app.handleErrorOnce(
|
||||||
|
extension,
|
||||||
|
`${extension}::extend::${object.constructor.name}::${method.toString()}`,
|
||||||
|
extractText(app.translator.trans('core.lib.error.extension_runtime_failed_message', { extension })),
|
||||||
|
`${extension} failed to override ${object.constructor.name}::${method.toString()}`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
} as T[K];
|
} as T[K];
|
||||||
|
|
||||||
Object.assign(object[method], original);
|
Object.assign(object[method], original);
|
||||||
|
@@ -3,9 +3,9 @@ import app from '../app';
|
|||||||
/**
|
/**
|
||||||
* Fire a Flarum error which is shown in the JS console for everyone and in an alert for the admin.
|
* 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 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 consoleTitle an error title that goes in the console, doesn't have to be localized.
|
||||||
* @param error: the error.
|
* @param error the error.
|
||||||
*/
|
*/
|
||||||
export default function fireApplicationError(userTitle: string, consoleTitle: string, error: any) {
|
export default function fireApplicationError(userTitle: string, consoleTitle: string, error: any) {
|
||||||
console.group(`%c${consoleTitle}`, 'background-color: #d83e3e; color: #ffffff; font-weight: bold;');
|
console.group(`%c${consoleTitle}`, 'background-color: #d83e3e; color: #ffffff; font-weight: bold;');
|
||||||
|
@@ -722,6 +722,7 @@ core:
|
|||||||
db_error_message: "Database query failed. This may be caused by an incompatibility between an extension and your database driver."
|
db_error_message: "Database query failed. This may be caused by an incompatibility between an extension and your database driver."
|
||||||
dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"
|
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."
|
extension_initialiation_failed_message: "{extension} failed to initialize, check the browser console for further information."
|
||||||
|
extension_runtime_failed_message: "{extension} encountered an error while running. Check the browser console for further information."
|
||||||
generic_message: "Oops! Something went wrong. Please reload the page and try again."
|
generic_message: "Oops! Something went wrong. Please reload the page and try again."
|
||||||
generic_cross_origin_message: "Oops! Something went wrong during a cross-origin request. Please reload the page and try again."
|
generic_cross_origin_message: "Oops! Something went wrong during a cross-origin request. Please reload the page and try again."
|
||||||
missing_dependencies_message: "Cannot enable {extension} until the following dependencies are enabled: {extensions}"
|
missing_dependencies_message: "Cannot enable {extension} until the following dependencies are enabled: {extensions}"
|
||||||
|
Reference in New Issue
Block a user