[bugfix] Plugins: Add error handling. when error occurred while rendering a plugin, set status disabled and an error.

This commit is contained in:
Sakamoto Shiina
2025-06-23 11:24:18 +09:00
parent db61f33578
commit b15a26c3d5
7 changed files with 78 additions and 30 deletions

View File

@@ -270,7 +270,7 @@ config_page:
label: "WebSocket Port" label: "WebSocket Port"
notifications: notifications:
save_success: "Settings have been saved" save_success: "Settings have been saved."
plugin_notifications: plugin_notifications:
downloading: Downloading the plugin. downloading: Downloading the plugin.
@@ -282,6 +282,7 @@ plugin_notifications:
updated_error: Update failed. updated_error: Update failed.
disabled_out_of_support: The plugin has been disabled. It's not supported on this VRCT version. disabled_out_of_support: The plugin has been disabled. It's not supported on this VRCT version.
disabled_due_to_an_error: "An error was detected while running the plugin. Please report this to the plugin developer."
is_enabled: The plugin has enabled. is_enabled: The plugin has enabled.
is_disabled: The plugin has disabled. is_disabled: The plugin has disabled.

View File

@@ -282,6 +282,7 @@ plugin_notifications:
updated_error: プラグインのアップデートに失敗しました。 updated_error: プラグインのアップデートに失敗しました。
disabled_out_of_support: 現在のバージョンとの互換性がありません。プラグインを無効にしました。 disabled_out_of_support: 現在のバージョンとの互換性がありません。プラグインを無効にしました。
disabled_due_to_an_error: プラグイン実行中にエラーを検知しました。プラグイン開発者に報告してください。
is_enabled: プラグインを有効にしました。 is_enabled: プラグインを有効にしました。
is_disabled: プラグインを無効にしました。 is_disabled: プラグインを無効にしました。

View File

@@ -105,7 +105,12 @@ const PluginDownloadContainer = () => {
</div> </div>
<div className={styles.plugin_info_wrapper}> <div className={styles.plugin_info_wrapper}>
{plugin.is_error ? ( {plugin.is_error ? (
<p style={{ color: "red" }}>Error: {plugin.error_message}</p> <div>
<p style={{ color: "red" }}>{t(`plugin_notifications.${plugin.error_message_type}`)}</p>
<p style={{ color: "red" }}>plugin_version: {plugin.downloaded_plugin_info.plugin_version}</p>
<p style={{ color: "red" }}>min_supported_vrct_version: {plugin.downloaded_plugin_info.min_supported_vrct_version}</p>
<p style={{ color: "red" }}>max_supported_vrct_version: {plugin.downloaded_plugin_info.max_supported_vrct_version}</p>
</div>
) : ( ) : (
<PluginsControlComponent <PluginsControlComponent
variable_state={variable_state} variable_state={variable_state}

View File

@@ -27,6 +27,7 @@
flex-direction: column; flex-direction: column;
gap: 0.4rem; gap: 0.4rem;
max-width: 50%; max-width: 50%;
flex-shrink: 0;
} }
.plugin_info_wrapper { .plugin_info_wrapper {

View File

@@ -1,14 +1,28 @@
import React from "react"; import { usePlugins } from "@logics_configs";
import { ErrorBoundary } from "react-error-boundary";
export const PluginHost = ({render_components}) => { export const PluginHost = ({ render_components }) => {
const { setErrorPlugin } = usePlugins();
return ( return (
<> <>
{render_components {render_components.map((plugin, index) => {
.map((plugin, index) => { const PluginComponent = plugin.component;
const PluginComponent = plugin.component; const plugin_id = plugin.plugin_id;
return PluginComponent ? <PluginComponent key={index} /> : null;
})} return PluginComponent ? (
<ErrorBoundary
key={plugin_id || index}
fallbackRender={() => null}
onError={(_error, _info) => {
// Disable the plugin on error
setErrorPlugin(plugin_id, "disabled_due_to_an_error");
}}
>
<PluginComponent />
</ErrorBoundary>
) : null;
})}
</> </>
); );
}; };

View File

@@ -69,14 +69,14 @@ export const SnackbarController = () => {
}, 100); }, 100);
}; };
setContainerKey(prevKey => prevKey + 1); // setContainerKey(prevKey => prevKey + 1);
asyncShowNotification(); asyncShowNotification();
}, [settings]); }, [settings]);
return ( return (
<ToastContainer <ToastContainer
key={containerKey} // key={containerKey}
position="bottom-left" position="bottom-left"
transition={CustomTransition} transition={CustomTransition}
hideProgressBar={false} hideProgressBar={false}

View File

@@ -282,44 +282,57 @@ export const usePlugins = () => {
}); });
}; };
const toggleSavedPluginsStatus = (target_plugin_id) => { const setSavedPluginEnabled = (target_plugin_id, is_enabled) => {
const successPluginNotification = (message) => showNotification_Success(message, { const notify = () => {
hide_duration: 1000, const msg_key = is_enabled
category_id: "to_enable_plugin" ? "plugin_notifications.is_enabled"
}); : "plugin_notifications.is_disabled";
const is_exists = currentSavedPluginsStatus.data.some( showNotification_Success(t(msg_key), {
hide_duration: 1000,
category_id: "switch_enable_plugin",
});
}
const exists = currentSavedPluginsStatus.data.some(
(d) => d.plugin_id === target_plugin_id (d) => d.plugin_id === target_plugin_id
); );
let new_value = []; let new_value = [];
if (is_exists) {
if (exists) {
new_value = currentSavedPluginsStatus.data.map((d) => { new_value = currentSavedPluginsStatus.data.map((d) => {
if (d.plugin_id === target_plugin_id) { if (d.plugin_id === target_plugin_id) {
d.is_enabled = !d.is_enabled; d.is_enabled = is_enabled;
(d.is_enabled) notify();
? successPluginNotification(t("plugin_notifications.is_enabled"))
: successPluginNotification(t("plugin_notifications.is_disabled"));
} }
return d; return d;
}); });
} else { } else {
new_value.push(...currentSavedPluginsStatus.data); // 存在しない場合は追加
new_value.push({ new_value = [
plugin_id: target_plugin_id, ...currentSavedPluginsStatus.data,
is_enabled: true, { plugin_id: target_plugin_id, is_enabled: is_enabled }
}); ];
successPluginNotification(t("plugin_notifications.is_enabled")) notify();
} }
// 「currentPluginsData.data」でis_downloadedがtrueのものだけ残す // ダウンロード済みプラグインのみ残す
new_value = new_value.filter((item) => new_value = new_value.filter((item) =>
currentPluginsData.data.some( currentPluginsData.data.some(
(plugin) => plugin.plugin_id === item.plugin_id && plugin.is_downloaded (p) => p.plugin_id === item.plugin_id && p.is_downloaded
) )
); );
setSavedPluginsStatus(new_value); setSavedPluginsStatus(new_value);
}; };
const toggleSavedPluginsStatus = (plugin_id) => {
// 現在の状態を探す(未登録なら false とみなす)
const current = currentSavedPluginsStatus.data.find(
(d) => d.plugin_id === plugin_id
)?.is_enabled ?? false;
setSavedPluginEnabled(plugin_id, !current);
};
// Init時の処理 非対応のものを無効化する際に、savedDPluginsStatusから不要なものを削除する処理が邪魔になるので該当コードを削除したバージョン。Init以外で使用する時にはリファクタが必要になる。 // Init時の処理 非対応のものを無効化する際に、savedDPluginsStatusから不要なものを削除する処理が邪魔になるので該当コードを削除したバージョン。Init以外で使用する時にはリファクタが必要になる。
const setTargetSavedPluginsStatus_Init = (target_plugin_id, is_enabled) => { const setTargetSavedPluginsStatus_Init = (target_plugin_id, is_enabled) => {
@@ -380,6 +393,15 @@ export const usePlugins = () => {
showNotification_SaveSuccess(); showNotification_SaveSuccess();
}; };
const setErrorPlugin = (plugin_id, error_message_type) => {
const error_message = t("plugin_notifications.disabled_due_to_an_error");
setSavedPluginEnabled(plugin_id, false);
updateTargetPluginData(plugin_id, "is_error", true);
updateTargetPluginData(plugin_id, "error_message_type", error_message_type);
showNotification_Error(error_message);
};
return { return {
asyncFetchPluginsInfo, asyncFetchPluginsInfo,
@@ -406,11 +428,15 @@ export const usePlugins = () => {
currentLoadedPlugins, currentLoadedPlugins,
updateLoadedPlugins, updateLoadedPlugins,
setSavedPluginEnabled,
toggleSavedPluginsStatus, toggleSavedPluginsStatus,
setTargetSavedPluginsStatus_Init, setTargetSavedPluginsStatus_Init,
setSavedPluginsStatus, setSavedPluginsStatus,
handlePendingPlugin, handlePendingPlugin,
setErrorPlugin,
}; };
}; };