Files
factorio-clusterio/packages/web_ui/src/bootstrap.tsx
T
Hornwitser fb3fe09466 Remove tlsCa option
The migration from phin to fetch in #755 was hampred by the lack of an
option to set the CA when using fetch.  This meant places that used phin
to talk to the controller with the tlsCa option could not be migrated to
use fetch.

Remove the tlsCa option to unblock this dependency. The replacement for
this option is to set the NODE_EXTRA_CA_CERTS environment variable to a
path to the certificate file instead, which works with fetch.
2026-03-04 17:43:32 +01:00

148 lines
4.6 KiB
TypeScript

import "./style.css";
import React from "react";
import { createRoot } from "react-dom/client";
import * as lib from "@clusterio/lib";
import App from "./components/App";
import BaseWebPlugin, { InputComponent } from "./BaseWebPlugin";
import InputRole from "./components/InputRole";
import InputModPack from "./components/InputModPack";
import InputTargetVersion from "./components/InputTargetVersion";
import InputPartialVersion from "./components/InputPartialVersion";
import { Control, ControlConnector } from "./util/websocket";
const { ConsoleTransport, WebConsoleFormat, logger } = lib;
async function loadScript(url: string) {
let script = document.createElement("script");
script.src = url;
script.type = "text/javascript";
script.async = true;
let result = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
document.head.appendChild(script);
return result;
}
async function loadPluginInfos(): Promise<lib.PluginWebpackEnvInfo[]> {
let response = await fetch(`${webRoot}api/plugins`);
let pluginList: lib.PluginWebApi[];
if (response.ok) {
pluginList = await response.json();
} else {
logger.error("Failed to get plugin data, running without plugins");
pluginList = [];
}
let pluginInfos: lib.PluginWebpackEnvInfo[] = [];
await __webpack_init_sharing__("default");
for (let meta of pluginList) {
if (meta.web.error) {
logger.error(`Failed to load plugin ${meta.name}: ${meta.web.error}`);
continue;
}
try {
await loadScript(`${webRoot}${meta.web.main}`);
let container: any = (window as { [key: string]: any })[`plugin_${meta.name}`];
if (!container) {
throw new Error(`Plugin did not expose its container via plugin_${meta.name}`);
}
await container.init(__webpack_share_scopes__.default);
let pluginInfo = (await container.get("./"))().plugin;
pluginInfo.container = container;
pluginInfo.package = (await container.get("./package.json"))();
pluginInfo.enabled = meta.enabled;
pluginInfos.push(pluginInfo);
} catch (err: any) {
pluginInfos.push({
name: meta.name,
title: meta.name,
enabled: false,
error: err.message,
});
logger.error(`Failed to load plugin info for ${meta.name}`);
if (err.stack) {
logger.error(err.stack);
}
}
}
return pluginInfos;
}
async function loadPlugins(pluginInfos: lib.PluginWebpackEnvInfo[], control: Control) {
let plugins = new Map<string, BaseWebPlugin>();
for (let pluginInfo of pluginInfos) {
if (!pluginInfo.enabled) {
continue;
}
try {
let WebPluginClass = BaseWebPlugin;
if (pluginInfo.webEntrypoint) {
let webModule = (await pluginInfo.container.get(pluginInfo.webEntrypoint))();
if (!webModule.WebPlugin) {
pluginInfo.error = "Plugin webEntrypoint does not export WebPlugin class";
throw new Error(pluginInfo.error);
}
WebPluginClass = webModule.WebPlugin;
}
let plugin = new WebPluginClass(pluginInfo.container, pluginInfo.package, pluginInfo, control, logger);
await plugin.init();
plugins.set(pluginInfo.name, plugin);
} catch (err: any) {
pluginInfo.error = `Error loading plugin: ${err.message}`;
logger.error(`Failed to load plugin ${pluginInfo.name}`);
if (err.stack) {
logger.error(err.stack);
}
}
}
return plugins;
}
function inputComponentsFromPlugins(plugins: Map<string, BaseWebPlugin>) {
const inputComponents: Record<string, InputComponent> = {
"partial_version": InputPartialVersion,
"target_version": InputTargetVersion,
"mod_pack": InputModPack,
"role": InputRole,
};
for (let [pluginName, plugin] of plugins) {
for (const [name, Component] of Object.entries(plugin.inputComponents)) {
if (Object.prototype.hasOwnProperty.call(inputComponents, name)) {
lib.logger.warn(`Plugin ${pluginName} is redefining config inputComponent ${name}`);
}
inputComponents[name] = Component;
}
}
return inputComponents;
}
export default async function bootstrap() {
logger.add(new ConsoleTransport({
level: "verbose",
format: new WebConsoleFormat(),
}));
let pluginInfos = await loadPluginInfos();
lib.registerPluginMessages(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
let wsUrl = new URL(webRoot, document.location.href);
let controlConnector = new ControlConnector(wsUrl.href, 120);
let control = new Control(controlConnector, new Map(pluginInfos.map(p => [p.name, p])));
control.plugins = await loadPlugins(pluginInfos, control);
control.inputComponents = inputComponentsFromPlugins(control.plugins);
const root = createRoot(document.getElementById("root") as HTMLDivElement);
root.render(<App control={control}/>);
}