mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 15:54:52 +09:00
Merge pull request #483 from bbassie/refactor/init-function-exports
Export init functions instead of plugin classes
This commit is contained in:
28 files changed
+268
-134
No files matched your search
@@ -47,6 +47,4 @@ export const plugin: lib.PluginDeclaration = {
|
||||
initialValue: false,
|
||||
},
|
||||
},
|
||||
|
||||
webEntrypoint: "./web",
|
||||
};
|
||||
@@ -18,7 +18,6 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
exposes: {
|
||||
"./": "./index.ts",
|
||||
"./package.json": "./package.json",
|
||||
"./web": "./web/index.tsx",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
// This class is empty because an instance plugin must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
}
|
||||
// Empty because an instance entrypoint must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
export default async function () {}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { BaseControllerPlugin, type Controller } from "@clusterio/controller";
|
||||
import type { Controller, ControllerPluginContext } from "@clusterio/controller";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
import * as path from "node:path";
|
||||
|
||||
const loaded = new WeakMap<Controller, ControllerPlugin>();
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
export class ControllerPlugin {
|
||||
controller: Controller;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
groups!: lib.SubscribableDatastore<messages.GroupRecord>;
|
||||
roleMappings!: lib.SubscribableDatastore<messages.RoleMappingRecord>;
|
||||
manualAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
|
||||
@@ -16,6 +19,12 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
return loaded.get(controller);
|
||||
}
|
||||
|
||||
constructor(context: ControllerPluginContext) {
|
||||
this.controller = context.controller;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
loaded.set(this.controller, this);
|
||||
const databaseDirectory = this.controller.config.get("controller.database_directory");
|
||||
@@ -70,6 +79,8 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
this.controller.handle(messages.RoleMappingDeleteRequest, this.handleRoleMappingDeleteRequest.bind(this));
|
||||
this.controller.handle(messages.RoleMappingGetRequest, this.handleRoleMappingGetRequest.bind(this));
|
||||
this.controller.handle(messages.RoleMappingListRequest, this.handleRoleMappingListRequest.bind(this));
|
||||
|
||||
this.controller.hooks.shutdown.attach(this.name, this.onShutdown.bind(this));
|
||||
}
|
||||
|
||||
async onShutdown() {
|
||||
@@ -440,3 +451,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
return Promise.all(playerNames.map(name => this.computeResolvedAssignment(name)));
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: ControllerPluginContext) {
|
||||
await new ControllerPlugin(context).init();
|
||||
}
|
||||
+20
-2
@@ -1,4 +1,4 @@
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import type { Instance, InstancePluginContext } from "@clusterio/host";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
|
||||
@@ -17,7 +17,10 @@ export type IpcPlayerAssignments = {
|
||||
assignments: Record<string, number>,
|
||||
};
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
export class InstancePlugin {
|
||||
instance: Instance;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
// Once only, don't send permissions for these groups
|
||||
// This is used for groups created on this instance that only need the controller generated id
|
||||
skipSendingPermissions = new Set<string>();
|
||||
@@ -26,12 +29,23 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
// Track known online players so that we only apply assignment updates for them
|
||||
onlinePlayers = new Set<string>();
|
||||
|
||||
constructor(context: InstancePluginContext) {
|
||||
this.instance = context.instance;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
|
||||
this.instance.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentUpdatedEvent.bind(this));
|
||||
this.instance.server.handle(`exp_group:group_updated`, this.handleGroupUpdatedIPC.bind(this))
|
||||
this.instance.server.handle(`exp_group:group_deleted`, this.handleGroupDeletedIPC.bind(this))
|
||||
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
|
||||
|
||||
const hooks = this.instance.hooks;
|
||||
hooks.instanceConfigFieldChanged.attach(this.name, this.onInstanceConfigFieldChanged.bind(this));
|
||||
hooks.start.attach(this.name, this.onStart.bind(this));
|
||||
hooks.playerEvent.attach(this.name, this.onPlayerEvent.bind(this));
|
||||
}
|
||||
|
||||
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
||||
@@ -169,3 +183,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
await this.instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true)
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: InstancePluginContext) {
|
||||
await new InstancePlugin(context).init();
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Modal, Form, Select } from "antd";
|
||||
|
||||
import { ControlContext, useUsers } from "@clusterio/web_ui";
|
||||
import * as messages from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useGroups } from "..";
|
||||
|
||||
export default function AssignmentForm({ open, setOpen, initial }: {
|
||||
open: boolean,
|
||||
@@ -11,9 +11,8 @@ export default function AssignmentForm({ open, setOpen, initial }: {
|
||||
initial?: messages.AssignmentRecord,
|
||||
}) {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
|
||||
const [groups] = plugin.useGroups();
|
||||
const [groups] = useGroups();
|
||||
const [users] = useUsers();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { EditOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
|
||||
import { ControlContext, useAccount } from "@clusterio/web_ui";
|
||||
import { AssignmentDeleteRequest, AssignmentRecord } from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useAssignments, useGroups } from "..";
|
||||
|
||||
import AssignmentForm from "./AssignmentForm";
|
||||
import DeletedConfirm from "./DeleteConfirm";
|
||||
@@ -13,13 +13,12 @@ const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base"
|
||||
|
||||
export default function AssignmentsTable() {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
const account = useAccount();
|
||||
|
||||
const searchInput = useRef<InputRef>(null);
|
||||
|
||||
const [assignments, assignmentsSynced] = plugin.useAssignments();
|
||||
const [groups, groupsSynced] = plugin.useGroups();
|
||||
const [assignments, assignmentsSynced] = useAssignments();
|
||||
const [groups, groupsSynced] = useGroups();
|
||||
|
||||
const [editing, setEditing] = useState<AssignmentRecord | undefined>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ControlContext, useAccount, useDefaultModPack, PageLayout, PageHeader,
|
||||
import DeletedConfirm from "./DeleteConfirm";
|
||||
|
||||
import * as messages from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useGroups } from "..";
|
||||
|
||||
const DOMAIN_MAPPING = {
|
||||
"Admin": ["admin", "cheat", "permission", "infinity", "editor", "spawn"],
|
||||
@@ -34,14 +34,13 @@ function getDomain(name: string) {
|
||||
|
||||
export default function GroupViewPage() {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
const navigate = useNavigate();
|
||||
const account = useAccount();
|
||||
|
||||
const { id } = useParams();
|
||||
const groupId = Number(id);
|
||||
|
||||
const [groups, synced] = plugin.useGroups();
|
||||
const [groups, synced] = useGroups();
|
||||
const group = groups.get(groupId);
|
||||
|
||||
const [name, setName] = useState("");
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Table } from "antd";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ControlContext } from "@clusterio/web_ui";
|
||||
import { GroupRecord } from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useGroups } from "..";
|
||||
|
||||
import GroupForm from "./GroupForm";
|
||||
|
||||
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
|
||||
|
||||
export default function GroupsTable() {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [groups, synced] = plugin.useGroups();
|
||||
const [groups, synced] = useGroups();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Modal, Form, Select, InputNumber, Switch, Alert } from "antd";
|
||||
|
||||
import { ControlContext, useRoles } from "@clusterio/web_ui";
|
||||
import { RoleMappingRecord, RoleMappingCreateRequest, RoleMappingUpdateRequest } from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useGroups } from "..";
|
||||
|
||||
export default function RoleMappingForm({ open, setOpen, initial }: {
|
||||
open: boolean,
|
||||
@@ -11,9 +11,8 @@ export default function RoleMappingForm({ open, setOpen, initial }: {
|
||||
initial?: RoleMappingRecord,
|
||||
}) {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
|
||||
const [groups] = plugin.useGroups();
|
||||
const [groups] = useGroups();
|
||||
const [roles] = useRoles();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -4,18 +4,17 @@ import { EditOutlined } from "@ant-design/icons";
|
||||
|
||||
import { ControlContext, useAccount, useRoles } from "@clusterio/web_ui";
|
||||
import { RoleMappingRecord, RoleMappingDeleteRequest } from "../../messages.js";
|
||||
import type { WebPlugin } from "..";
|
||||
import { useGroups, useRoleMappings } from "..";
|
||||
|
||||
import RoleMappingForm from "./RoleMappingForm";
|
||||
import DeletedConfirm from "./DeleteConfirm";
|
||||
|
||||
export default function RoleMappingsTable() {
|
||||
const control = useContext(ControlContext);
|
||||
const plugin = control.plugins.get("exp_groups") as WebPlugin;
|
||||
const account = useAccount();
|
||||
|
||||
const [roleMappings, roleMappingsSynced] = plugin.useRoleMappings();
|
||||
const [groups, groupsSynced] = plugin.useGroups();
|
||||
const [roleMappings, roleMappingsSynced] = useRoleMappings();
|
||||
const [groups, groupsSynced] = useGroups();
|
||||
const [roles, rolesSynced] = useRoles();
|
||||
|
||||
const [editing, setEditing] = useState<RoleMappingRecord | undefined>();
|
||||
|
||||
+39
-37
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useCallback, useSyncExternalStore } from "react";
|
||||
import { BaseWebPlugin, PageLayout, PageHeader, useAccount, SectionHeader } from "@clusterio/web_ui";
|
||||
import { WebPluginContext, PageLayout, PageHeader, useAccount, SectionHeader } from "@clusterio/web_ui";
|
||||
import { Button } from "antd";
|
||||
|
||||
import * as messages from "../messages.js";
|
||||
@@ -65,44 +65,46 @@ function ExpGroupsPage() {
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
export class WebPlugin extends BaseWebPlugin {
|
||||
groups = new lib.MapSubscriber(messages.GroupUpdatedEvent, this.control);
|
||||
assignments = new lib.MapSubscriber(messages.ManualAssignmentUpdatedEvent, this.control);
|
||||
roleMappings = new lib.MapSubscriber(messages.RoleMappingUpdatedEvent, this.control);
|
||||
let groups: lib.MapSubscriber<messages.GroupUpdatedEvent> | undefined;
|
||||
let assignments: lib.MapSubscriber<messages.ManualAssignmentUpdatedEvent> | undefined;
|
||||
let roleMappings: lib.MapSubscriber<messages.RoleMappingUpdatedEvent> | undefined;
|
||||
|
||||
async init() {
|
||||
this.pages = [
|
||||
{
|
||||
path: "/permission_groups",
|
||||
sidebarName: "Permission Groups",
|
||||
permission: (account => account.hasAnyPermission(
|
||||
"exp_groups.group.list",
|
||||
"exp_groups.assignment.list",
|
||||
"exp_groups.role_mapping.list",
|
||||
)),
|
||||
content: <ExpGroupsPage />,
|
||||
},
|
||||
{
|
||||
path: "/permission_groups/:id/view",
|
||||
sidebarPath: "/permission_groups",
|
||||
permission: "exp_groups.group.get",
|
||||
content: <GroupViewPage />,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function useGroups() {
|
||||
const subscribe = useCallback((cb: () => void) => groups!.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => groups!.getSnapshot());
|
||||
}
|
||||
|
||||
useGroups() {
|
||||
const subscribe = useCallback((cb: () => void) => this.groups.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => this.groups.getSnapshot());
|
||||
}
|
||||
export function useAssignments() {
|
||||
const subscribe = useCallback((cb: () => void) => assignments!.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => assignments!.getSnapshot());
|
||||
}
|
||||
|
||||
useAssignments() {
|
||||
const subscribe = useCallback((cb: () => void) => this.assignments.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => this.assignments.getSnapshot());
|
||||
}
|
||||
export function useRoleMappings() {
|
||||
const subscribe = useCallback((cb: () => void) => roleMappings!.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => roleMappings!.getSnapshot());
|
||||
}
|
||||
|
||||
useRoleMappings() {
|
||||
const subscribe = useCallback((cb: () => void) => this.roleMappings.subscribe(cb), []);
|
||||
return useSyncExternalStore(subscribe, () => this.roleMappings.getSnapshot());
|
||||
}
|
||||
export default function registerPlugin(context: WebPluginContext) {
|
||||
context.control.hooks.pages.attach("exp_groups", () => [
|
||||
{
|
||||
path: "/permission_groups",
|
||||
sidebarName: "Permission Groups",
|
||||
permission: (account => account.hasAnyPermission(
|
||||
"exp_groups.group.list",
|
||||
"exp_groups.assignment.list",
|
||||
"exp_groups.role_mapping.list",
|
||||
)),
|
||||
content: <ExpGroupsPage />,
|
||||
},
|
||||
{
|
||||
path: "/permission_groups/:id/view",
|
||||
sidebarPath: "/permission_groups",
|
||||
permission: "exp_groups.group.get",
|
||||
content: <GroupViewPage />,
|
||||
},
|
||||
]);
|
||||
|
||||
groups = new lib.MapSubscriber(messages.GroupUpdatedEvent, context.control);
|
||||
assignments = new lib.MapSubscriber(messages.ManualAssignmentUpdatedEvent, context.control);
|
||||
roleMappings = new lib.MapSubscriber(messages.RoleMappingUpdatedEvent, context.control);
|
||||
}
|
||||
+3
-7
@@ -1,7 +1,3 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
// This class is empty because an instance plugin must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
}
|
||||
// Empty because an instance entrypoint must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
export default async function () {}
|
||||
@@ -1,7 +1,3 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
// This class is empty because an instance plugin must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
}
|
||||
// Empty because an instance entrypoint must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
export default async function () {}
|
||||
@@ -1,11 +1,20 @@
|
||||
import { BaseControllerPlugin } from "@clusterio/controller";
|
||||
import type { Controller, ControllerPluginContext } from "@clusterio/controller";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
import * as path from "node:path";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
export class ControllerPlugin {
|
||||
controller: Controller;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
reports!: lib.SubscribableDatastore<messages.ReportRecord>;
|
||||
|
||||
constructor(context: ControllerPluginContext) {
|
||||
this.controller = context.controller;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const databaseDirectory = this.controller.config.get("controller.database_directory");
|
||||
|
||||
@@ -23,6 +32,8 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
this.controller.handle(messages.ReportGetRequest, this.handleReportGetRequest.bind(this));
|
||||
this.controller.handle(messages.ReportCreateRequest, this.handleReportCreateRequest.bind(this));
|
||||
this.controller.handle(messages.ReportDeleteRequest, this.handleReportDeleteRequest.bind(this));
|
||||
|
||||
this.controller.hooks.shutdown.attach(this.name, this.onShutdown.bind(this));
|
||||
}
|
||||
|
||||
async onShutdown() {
|
||||
@@ -141,3 +152,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: ControllerPluginContext) {
|
||||
await new ControllerPlugin(context).init();
|
||||
}
|
||||
+17
-2
@@ -1,4 +1,5 @@
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import type { Instance, InstancePluginContext } from "@clusterio/host";
|
||||
import type * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
|
||||
/** Sent by the lua side when a player reports another. */
|
||||
@@ -28,7 +29,17 @@ export type IpcReportDelete = {
|
||||
* the answer is handed back to lua once it arrives. The instance may have
|
||||
* stopped by then, in which case the answer is dropped.
|
||||
*/
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
export class InstancePlugin {
|
||||
instance: Instance;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
|
||||
constructor(context: InstancePluginContext) {
|
||||
this.instance = context.instance;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.instance.server.handle("exp_reports:create", this.handleCreateIPC.bind(this));
|
||||
this.instance.server.handle("exp_reports:list", this.handleListIPC.bind(this));
|
||||
@@ -93,3 +104,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: InstancePluginContext) {
|
||||
await new InstancePlugin(context).init();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import t from "tap";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Controller, InstanceRecord } from "@clusterio/controller";
|
||||
import { ControllerPlugin } from "../dist/node/controller.js";
|
||||
import entrypoint, { ControllerPlugin } from "../dist/node/controller.js";
|
||||
import * as messages from "../dist/node/messages.js";
|
||||
import { plugin as pluginDeclaration } from "../dist/node/index.js";
|
||||
|
||||
@@ -46,13 +46,23 @@ async function startPlugin(t2, { config = {} } = {}) {
|
||||
broadcast(event);
|
||||
};
|
||||
|
||||
const plugin = new ControllerPlugin({ name: "exp_reports" }, controller, undefined, logger);
|
||||
const plugin = new ControllerPlugin({ plugin: { name: "exp_reports" }, controller, logger });
|
||||
plugin.logger = { ...logger, warn: message => state.warnings.push(message) };
|
||||
plugin.postWebhook = async (url, body) => { state.posts.push({ url, body }); };
|
||||
await plugin.init();
|
||||
return { plugin, controller, state };
|
||||
}
|
||||
|
||||
t.test("entrypoint", async t2 => {
|
||||
const controllerConfig = new lib.ControllerConfig("controller", { "controller.database_directory": t2.testdir() });
|
||||
const controller = new Controller(logger, [], controllerConfig);
|
||||
await entrypoint({ controller, logger, plugin: { name: "exp_reports" }, metrics: undefined });
|
||||
for (const hook of ["shutdown"]) {
|
||||
t2.ok([...controller.hooks[hook].attached].includes("exp_reports"), `attached to ${hook}`);
|
||||
}
|
||||
await controller.hooks.shutdown.invoke();
|
||||
});
|
||||
|
||||
t.test("class ControllerPlugin", t2 => {
|
||||
t2.test(".handleReportCreateRequest() from an instance records the instance and the reporter", async t3 => {
|
||||
const { plugin } = await startPlugin(t3);
|
||||
|
||||
@@ -10,6 +10,8 @@ for (const Message of pluginDeclaration.messages) {
|
||||
lib.Link.register(Message);
|
||||
}
|
||||
|
||||
const logger = { child: () => logger, info: () => {}, warn: () => {}, error: () => {}, verbose: () => {} };
|
||||
|
||||
class TestConnector extends lib.BaseConnector {
|
||||
constructor() {
|
||||
super(lib.Address.fromShorthand({ instanceId: 1 }), lib.Address.fromShorthand({ hostId: 1 }));
|
||||
@@ -61,7 +63,7 @@ async function startPlugin(t2, { reports = [report(1, "bob", "alice"), report(2,
|
||||
instance.notifyStatus("running");
|
||||
state.sent.length = 0;
|
||||
|
||||
const plugin = new InstancePlugin({ name: "exp_reports" }, instance, {});
|
||||
const plugin = new InstancePlugin({ plugin: { name: "exp_reports" }, instance, host: {}, logger });
|
||||
await plugin.init();
|
||||
return { plugin, instance, state };
|
||||
}
|
||||
|
||||
+20
-2
@@ -1,11 +1,14 @@
|
||||
import { BaseControllerPlugin, InstanceRecord, type Controller } from "@clusterio/controller";
|
||||
import type { Controller, ControllerPluginContext, InstanceRecord } from "@clusterio/controller";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
import * as path from "node:path";
|
||||
|
||||
const loaded = new WeakMap<Controller, ControllerPlugin>();
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
export class ControllerPlugin {
|
||||
controller: Controller;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
roleMeta!: lib.SubscribableDatastore<messages.RoleMetaRecord>;
|
||||
|
||||
/** The plugin loaded on a controller, for the exp_scenario seed. */
|
||||
@@ -13,6 +16,12 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
return loaded.get(controller);
|
||||
}
|
||||
|
||||
constructor(context: ControllerPluginContext) {
|
||||
this.controller = context.controller;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
loaded.set(this.controller, this);
|
||||
const databaseDirectory = this.controller.config.get("controller.database_directory");
|
||||
@@ -45,6 +54,11 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
|
||||
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
||||
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
||||
|
||||
const hooks = this.controller.hooks;
|
||||
hooks.shutdown.attach(this.name, this.onShutdown.bind(this));
|
||||
hooks.controllerConfigFieldChanged.attach(this.name, this.onControllerConfigFieldChanged.bind(this));
|
||||
hooks.playerEvent.attach(this.name, this.onPlayerEvent.bind(this));
|
||||
}
|
||||
|
||||
async onShutdown() {
|
||||
@@ -319,3 +333,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: ControllerPluginContext) {
|
||||
await new ControllerPlugin(context).init();
|
||||
}
|
||||
+20
-2
@@ -1,4 +1,4 @@
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import type { Instance, InstancePluginContext } from "@clusterio/host";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as messages from "./messages.js";
|
||||
|
||||
@@ -9,11 +9,25 @@ export type IpcAssignmentUpdate = {
|
||||
unassign: number[] | undefined,
|
||||
};
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
export class InstancePlugin {
|
||||
instance: Instance;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
|
||||
constructor(context: InstancePluginContext) {
|
||||
this.instance = context.instance;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.instance.handle(messages.RoleUpdatedEvent, this.handleRoleUpdatedEvent.bind(this));
|
||||
this.instance.handle(messages.AssignmentUpdatedEvent, this.handleAssignmentUpdatedEvent.bind(this));
|
||||
this.instance.server.handle("exp_roles:assignment_update", this.handleAssignmentUpdateIPC.bind(this));
|
||||
|
||||
const hooks = this.instance.hooks;
|
||||
hooks.instanceConfigFieldChanged.attach(this.name, this.onInstanceConfigFieldChanged.bind(this));
|
||||
hooks.start.attach(this.name, this.onStart.bind(this));
|
||||
}
|
||||
|
||||
get syncMode() {
|
||||
@@ -106,3 +120,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: InstancePluginContext) {
|
||||
await new InstancePlugin(context).init();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import t from "tap";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Controller } from "@clusterio/controller";
|
||||
import { ControllerPlugin } from "../dist/node/controller.js";
|
||||
import entrypoint, { ControllerPlugin } from "../dist/node/controller.js";
|
||||
import * as messages from "../dist/node/messages.js";
|
||||
|
||||
// The controller validates message classes against the link registry
|
||||
@@ -44,13 +44,23 @@ async function startPlugin(t2, { roles = [] } = {}) {
|
||||
permissionsUpdated(user);
|
||||
};
|
||||
|
||||
const plugin = new ControllerPlugin({ name: "exp_roles" }, controller, undefined, logger);
|
||||
const plugin = new ControllerPlugin({ plugin: { name: "exp_roles" }, controller, logger });
|
||||
await plugin.init();
|
||||
return { plugin, controller, state };
|
||||
}
|
||||
|
||||
const role = (id, name, permissions = []) => new lib.Role(id, name, "", new Set(permissions));
|
||||
|
||||
t.test("entrypoint", async t2 => {
|
||||
const controllerConfig = new lib.ControllerConfig("controller", { "controller.database_directory": t2.testdir() });
|
||||
const controller = new Controller(logger, [], controllerConfig);
|
||||
await entrypoint({ controller, logger, plugin: { name: "exp_roles" }, metrics: undefined });
|
||||
for (const hook of ["shutdown", "controllerConfigFieldChanged", "playerEvent"]) {
|
||||
t2.ok([...controller.hooks[hook].attached].includes("exp_roles"), `attached to ${hook}`);
|
||||
}
|
||||
await controller.hooks.shutdown.invoke();
|
||||
});
|
||||
|
||||
t.test("class ControllerPlugin", t2 => {
|
||||
t2.test(".init() and .sweepRoleMeta() manage the role properties", async t3 => {
|
||||
const { plugin } = await startPlugin(t3, { roles: [role(0, "Cluster Admin"), role(5, "Moderator")] });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import t from "tap";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Instance } from "@clusterio/host";
|
||||
import { InstancePlugin } from "../dist/node/instance.js";
|
||||
import entrypoint, { InstancePlugin } from "../dist/node/instance.js";
|
||||
import { plugin as pluginDeclaration } from "../dist/node/index.js";
|
||||
import * as messages from "../dist/node/messages.js";
|
||||
|
||||
@@ -69,7 +69,7 @@ async function startPlugin(t2, { syncMode = "bidirectional", roles = sampleRoles
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const plugin = new InstancePlugin({ name: "exp_roles" }, instance, {});
|
||||
const plugin = new InstancePlugin({ plugin: { name: "exp_roles" }, instance, host: {}, logger });
|
||||
plugin.logger = { ...logger, warn: message => state.warnings.push(message) };
|
||||
await plugin.init();
|
||||
return { plugin, state };
|
||||
@@ -81,6 +81,20 @@ function decodeRcon(command) {
|
||||
return { receiver: match[1], payload: JSON.parse(match[2]) };
|
||||
}
|
||||
|
||||
t.test("entrypoint", async t2 => {
|
||||
const instanceConfig = new lib.InstanceConfig("host");
|
||||
instanceConfig.set("instance.id", 1);
|
||||
instanceConfig.set("instance.name", "test");
|
||||
const instance = new Instance(
|
||||
{ assignGamePort: () => 1 }, new TestConnector(), t2.testdir(), "factorioDir", instanceConfig
|
||||
);
|
||||
instance.server = { handle: () => {} };
|
||||
await entrypoint({ instance, host: {}, logger, plugin: { name: "exp_roles" } });
|
||||
for (const hook of ["instanceConfigFieldChanged", "start"]) {
|
||||
t2.ok([...instance.hooks[hook].attached].includes("exp_roles"), `attached to ${hook}`);
|
||||
}
|
||||
});
|
||||
|
||||
t.test("class InstancePlugin", t2 => {
|
||||
t2.test(".onStart() subscribes and initialises the lua module", async t3 => {
|
||||
const { plugin, state } = await startPlugin(t3, { syncMode: "enabled" });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseControllerPlugin } from "@clusterio/controller";
|
||||
import type { Controller, ControllerPluginContext } from "@clusterio/controller";
|
||||
import { RoleMetaRecord } from "@expcluster/roles";
|
||||
import { GroupRecord, GroupPermissions, RoleMappingRecord } from "@expcluster/permission-groups";
|
||||
import { ControllerPlugin as RolesPlugin } from "@expcluster/roles/dist/node/controller.js";
|
||||
@@ -7,7 +7,15 @@ import { ControllerPlugin as GroupsPlugin } from "@expcluster/permission-groups/
|
||||
import * as messages from "./messages.js";
|
||||
import { SeedRole, SeedGroup, seedRoles, seedGroups, flattenSeedPermissions } from "./seed.js";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
export class ControllerPlugin {
|
||||
controller: Controller;
|
||||
logger: lib.Logger;
|
||||
|
||||
constructor(context: ControllerPluginContext) {
|
||||
this.controller = context.controller;
|
||||
this.logger = context.logger;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.controller.handle(messages.SeedRequest, this.handleSeedRequest.bind(this));
|
||||
}
|
||||
@@ -148,6 +156,10 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: ControllerPluginContext) {
|
||||
await new ControllerPlugin(context).init();
|
||||
}
|
||||
|
||||
function newId(datastore: { has(id: number): boolean }) {
|
||||
let id = Math.random() * 2 ** 31 | 0;
|
||||
while (datastore.has(id)) {
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
async init() {
|
||||
}
|
||||
|
||||
async onStart() {
|
||||
}
|
||||
}
|
||||
// Empty because an instance entrypoint must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
export default async function () {}
|
||||
@@ -40,12 +40,12 @@ async function startPlugin(t2, { withPlugins = true } = {}) {
|
||||
|
||||
if (withPlugins) {
|
||||
for (const [name, Plugin] of [["exp_roles", RolesPlugin], ["exp_groups", GroupsPlugin]]) {
|
||||
const plugin = new Plugin({ name }, controller, undefined, logger);
|
||||
const plugin = new Plugin({ plugin: { name }, controller, logger });
|
||||
await plugin.init();
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = new ControllerPlugin({ name: "exp_scenario" }, controller, undefined, logger);
|
||||
const plugin = new ControllerPlugin({ plugin: { name: "exp_scenario" }, controller, logger });
|
||||
await plugin.init();
|
||||
return { plugin, controller };
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { BaseWebPlugin } from "@clusterio/web_ui";
|
||||
import { WebPluginContext } from "@clusterio/web_ui";
|
||||
|
||||
import Seed from "./components/Seed";
|
||||
|
||||
export class WebPlugin extends BaseWebPlugin {
|
||||
async init() {
|
||||
this.componentExtra = {
|
||||
RolesPage: Seed,
|
||||
};
|
||||
}
|
||||
export default function registerPlugin(context: WebPluginContext) {
|
||||
context.control.hooks.extensionComponents.attach("exp_scenario", () => ({
|
||||
RolesPage: Seed,
|
||||
}));
|
||||
}
|
||||
@@ -1,10 +1,27 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import type { Instance, InstancePluginContext } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
export class InstancePlugin {
|
||||
instance: Instance;
|
||||
logger: lib.Logger;
|
||||
name: string;
|
||||
private updateInterval?: ReturnType<typeof setInterval>;
|
||||
private gameTimes: number[] = [];
|
||||
|
||||
constructor(context: InstancePluginContext) {
|
||||
this.instance = context.instance;
|
||||
this.logger = context.logger;
|
||||
this.name = context.plugin.name;
|
||||
}
|
||||
|
||||
init() {
|
||||
const hooks = this.instance.hooks;
|
||||
hooks.start.attach(this.name, this.onStart.bind(this));
|
||||
hooks.exit.attach(this.name, this.onExit.bind(this));
|
||||
hooks.instanceConfigFieldChanged.attach(this.name, this.onInstanceConfigFieldChanged.bind(this));
|
||||
hooks.playerEvent.attach(this.name, this.onPlayerEvent.bind(this));
|
||||
}
|
||||
|
||||
async onStart() {
|
||||
if (!this.instance.config.get("factorio.settings")["auto_pause"]) {
|
||||
this.setInterval();
|
||||
@@ -56,7 +73,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
}
|
||||
|
||||
try {
|
||||
const newGameTime = await this.sendRcon(`/_rcon return exp_server_ups.refresh(${ups})`);
|
||||
const newGameTime = await this.instance.sendRcon(`/_rcon return exp_server_ups.refresh(${ups})`, false, this.name);
|
||||
this.gameTimes.push(Number(newGameTime));
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Failed to receive new game time: ${error}`);
|
||||
@@ -67,3 +84,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (context: InstancePluginContext) {
|
||||
new InstancePlugin(context).init();
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
// This class is empty because an instance plugin must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
}
|
||||
// Empty because an instance entrypoint must be defined for a module to be injected
|
||||
// This requirement may change in the future to allow for standalone modules
|
||||
export default async function () {}
|
||||
Reference in new issue
Block a user