Matches verifyUserToken returning the user. HostConnection._saveInfo is renamed saveRecord to match User.saveRecord. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.0 KiB
Migrating Plugins to 2.1
This guide covers the changes needed to bring a plugin written for 2.0 up to 2.1. Plugins still on an alpha should start with the 2.0 guide.
Request errors
RequestError is gone.
Errors thrown by request handlers are now received by the requester as the same class, see Request errors.
Replace RequestError with the class that fits:
- Invalid input:
ValidationError. - Operation disabled by config:
ForbiddenError. - Missing instance, user, save etc.:
NotFoundError. - Instance running, name taken etc.:
ConflictError. - Host not connected, instance not running:
UnavailableError. - Timeouts:
TimeoutError.
catch blocks that check err instanceof RequestError should check ClientError instead, or the specific class.
Checks of err.code for remote errors can use instanceof, e.g. err instanceof lib.SessionLost.
The config errors lib.InvalidValue, lib.InvalidField and lib.InvalidAccess were renamed to lib.InvalidValueError, lib.InvalidFieldError and lib.InvalidAccessError.
Errors of classes that are not registered, like a TypeError from a bug, are received as InternalServerError.
Plugins that define their own error classes need to list them in errors of the plugin declaration.
Build the web module with Vite
The web interface is now built with Vite instead of webpack, and plugin web modules are loaded as ES modules instead of through Module Federation. A plugin built with webpack fails to load with the error "Incompatible dist/web/manifest.json, the plugin must be rebuilt with Vite".
Delete webpack.config.cjs and add a vite.config.js next to package.json:
import { defineConfig } from "vite";
import { clusterioPlugin } from "@clusterio/web_ui/vite.mjs";
export default defineConfig({
plugins: [clusterioPlugin()],
});
In package.json replace webpack-cli --mode production in the prepare script with vite build, and replace the webpack, webpack-cli and webpack-merge dev dependencies with vite.
Add vite.config.js to .npmignore if you had webpack.config.js there.
clusterioPlugin exposes index.ts (or index.js) as "./", package.json as "./package.json" and web/index.tsx (or .ts, .jsx, .js) as "./web".
These are the same keys the plugin template used with webpack, so webEntrypoint: "./web" stays as it is.
If your webpack config exposed other paths, move the code so it is reachable from web/index.tsx.
The web interface provides @clusterio/lib, @clusterio/web_ui, @sinclair/typebox, antd, react, react-dom, react-router and react-router-dom to plugins.
These are left as imports in the plugin bundle and resolved by the browser, there is no need to list them as shared.
Everything else is bundled into the plugin, including @ant-design/icons.
Import react-dom/client and react/jsx-runtime by those names, other subpaths of the shared packages are bundled as a separate copy.
Imports of assert, buffer, events, os, path, process, stream, util and zlib resolve to the same browser packages as before, provided by @clusterio/web_ui.
The container property of WebPluginContext is now the plugin's ES module namespace, await container.get("./web") returns the module instead of a factory function.
lib.PluginWebpackEnvInfo is renamed to lib.PluginWebEnvInfo.
Renamed lib functions
lib.safeOutputFile is renamed to lib.atomicOverwriteFile.
It takes the same arguments.
Host pairing
Host tokens are no longer generated by users. A host joins the cluster by having the code it shows when it first starts approved, see Host pairing for the HTTP endpoints it uses.
HostGenerateTokenRequestandHostConfigCreateRequestare removed. Pending requests are listed withHostPairingListRequest, approved withHostPairingApproveRequestand rejected withHostPairingRejectRequest, and changes to them are sent to subscribers ofHostPairingUpdatesEvent.HostRevokeTokensRequestis replaced byHostRevokeRequest, which deletes the host instead of only invalidating its tokens. Instances assigned to the host become unassigned.- The permissions
core.host.generate_token,core.host.revoke_tokenandcore.host.create_configare removed. Checkcore.host.approveandcore.host.revokeinstead. clusteriocontroller bootstrap generate-host-tokenis removed, scripts that set up hosts can start the host with--user-tokento approve it with a user token.
Host records are no longer created when a host connects, only by approving it, and the controller rejects hosts it has no record of.
Revoking a host deletes its record and note.
Plugins that keep data per host should remove it when the host is deleted.
On the controller the update event of controller.hosts.records gives the record with isDeleted set to true, and subscribers of HostUpdatesEvent receive the host with isDeleted set to true.
A host that is revoked and approved again gets a new id.
Host manager
controller.hosts is now a HostManager, like controller.users is a UserManager.
get, getMutable, has and values work as before.
Everything else on the datastore, like set, delete, size and the update event, is on controller.hosts.records.
The host manager also signs and checks host tokens and creates and deletes hosts:
controller.generateHostToken(hostId)is nowcontroller.hosts.signHostToken(hostId).controller.verifyHostToken(token)is nowcontroller.hosts.verifyHostToken(token). It returns theHostRecordinstead of the host id.controller.hostPairing.createHost(name)is nowcontroller.hosts.createHost(name). It returns the newHostRecordinstead of{ hostId, token, name }, sign a token for it withsignHostToken.controller.hosts.revokeHost(hostId, revokedBy)deletes a host the same wayHostRevokeRequestdoes.
User tokens are checked with controller.users.verifyUserToken(token), which returns the user or throws if the token is invalid.