Files
factorio-clusterio/packages/web_ui/src/bootstrap.jsx
T
Hornwitser de48d807f1 Refactor connector logic
Refine the state flow used in the connector abstractions to better
handle close while connecting and resuming.  The new logic is to close
the connection immediately in these two states instead of continuing
trying to connect and then giving up some point later.

For the connectors the handshake state is split into connecting
and resuming, new is replaced by closed, and the closing state is turned
into a boolean flag as it's possible that closing is desired while
connecting, connected or resuming.  The connecting state is not present
in the server connector because there's nothing for the server to do in
that connector state.

The timeout logic is completely removed, once a connection attempt is
started for the client it will continue to attempt to connect, attempt
to resume dropped connections and attempt reconnects until either an
authentication error occurs or the client explicitly requests to close
the connection.

A new session timeout is added to control how long to attempt to resume
the connection until it's considered dead and invalidated.  This allows
stale connections to give up earlier, and swallow up less data while in
the dropped but not yet invalidated state.

For the client side sending messages while it's connecting has been
changed to throw an error instead of queuing up the message and
(possibly) sending it after the connection is established.  Code
shouldn't try to send messages without knowing if the connection it's
trying to send them over is active or not.
2021-08-30 20:20:49 +02:00

93 lines
2.6 KiB
React

import "./index.css";
import React from "react";
import ReactDOM from "react-dom";
import { libConfig, libLogging, libPlugin } from "@clusterio/lib";
const { ConsoleTransport, WebConsoleFormat, logger } = libLogging;
import App from "./components/App";
import { Control, ControlConnector } from "./util/websocket";
async function loadScript(url) {
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 loadPlugins() {
let response = await fetch(`${webRoot}api/plugins`);
let pluginList;
if (response.ok) {
pluginList = await response.json();
} else {
logger.error("Failed to get plugin data, running without plugins");
pluginList = [];
}
let plugins = new Map();
await __webpack_init_sharing__("default");
for (let meta of pluginList) {
try {
await loadScript(`${webRoot}plugins/${meta.name}/remoteEntry.js`);
let container = window[`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("./info"))();
let pluginPackage = (await container.get("./package.json"))();
let WebPluginClass = libPlugin.BaseWebPlugin;
if (meta.enabled && pluginInfo.webEntrypoint) {
let webModule = (await container.get(pluginInfo.webEntrypoint))();
if (!webModule.WebPlugin) {
throw new Error("Plugin webEntrypoint does not export WebPlugin class");
}
WebPluginClass = webModule.WebPlugin;
}
let plugin = new WebPluginClass(container, pluginPackage, pluginInfo, logger);
await plugin.init();
plugins.set(pluginInfo.name, plugin);
} catch (err) {
logger.error(`Failed to load plugin ${meta.name}`);
if (err.stack) {
logger.error(err.stack);
}
}
}
return plugins;
}
export default async function bootstrap() {
logger.add(new ConsoleTransport({
level: "verbose",
format: new WebConsoleFormat(),
}));
let plugins = await loadPlugins();
let pluginInfos = [...plugins.values()].map(p => p.info);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
let wsUrl = new URL(window.webRoot, document.location);
wsUrl.protocol = wsUrl.protocol.replace("http", "ws");
let controlConnector = new ControlConnector(wsUrl, 120);
let control = new Control(controlConnector, plugins);
ReactDOM.render(<App control={control}/>, document.getElementById("root"));
}