diff --git a/plugins/player_auth/controller.ts b/plugins/player_auth/controller.ts index a1b51e53..0887bb99 100644 --- a/plugins/player_auth/controller.ts +++ b/plugins/player_auth/controller.ts @@ -3,11 +3,12 @@ import express, { type Request, type Response } from "express"; import util from "util"; import jwt from "jsonwebtoken"; +import { Static } from "@sinclair/typebox"; import * as lib from "@clusterio/lib"; import { BaseControllerPlugin } from "@clusterio/controller"; const { basicType } = lib; -import { FetchPlayerCodeRequest, SetVerifyCodeRequest } from "./messages"; +import { FetchPlayerCodeRequest, PlayerAuthServer, SetVerifyCodeRequest } from "./messages"; async function generateCode(length: number): Promise { @@ -46,12 +47,32 @@ export class ControllerPlugin extends BaseControllerPlugin { }, 60e3).unref(); this.controller.app.get("/api/player_auth/servers", (req: Request, res: Response) => { - let servers: string[] = []; - for (let instance of this.controller.instances.values()) { - if (instance.status === "running" && instance.config.get("player_auth.load_plugin")) { - servers.push(instance.config.get("factorio.settings")["name"] as string || "unnamed server"); + const servers: Static[] = []; + + for (const instance of this.controller.instances.values()) { + const pluginLoaded = instance.config.get("player_auth.load_plugin"); + const assignedHost = instance.config.get("instance.assigned_host"); + if (instance.status !== "running" || !pluginLoaded || !assignedHost) { + continue; } + + const host = this.controller.hosts.get(assignedHost); + if (!host || !host.publicAddress) { + continue; + } + + const address = instance.gamePort !== undefined + ? `${host.publicAddress}:${instance.gamePort}` + : host.publicAddress; + + const settings = instance.config.get("factorio.settings"); + servers.push({ + name: settings["name"] as string || "unnamed server", + factorioVersion: instance.factorioVersion, + address, + }); } + res.send(servers); }); diff --git a/plugins/player_auth/messages.ts b/plugins/player_auth/messages.ts index 7cf9b2d1..653438da 100644 --- a/plugins/player_auth/messages.ts +++ b/plugins/player_auth/messages.ts @@ -1,5 +1,32 @@ import { Type, Static } from "@sinclair/typebox"; +export class PlayerAuthServer { + declare ["constructor"]: typeof PlayerAuthServer; + + static jsonSchema = Type.Object({ + name: Type.String(), + address: Type.String(), + factorioVersion: Type.Optional(Type.String()), + }); + + constructor( + public name: string, + public address: string, + public factorioVersion?: string, + ) {} + + toJSON(): Static { + if (this.factorioVersion === undefined) { + return { name: this.name, address: this.address }; + } + return { name: this.name, address: this.address, factorioVersion: this.factorioVersion }; + } + + static fromJSON(json: Static) { + return new this(json.name, json.address, json.factorioVersion); + } +} + class FetchPlayerCodeResponse { constructor( public playerCode: string, diff --git a/plugins/player_auth/web/index.tsx b/plugins/player_auth/web/index.tsx index 12118708..835242c0 100644 --- a/plugins/player_auth/web/index.tsx +++ b/plugins/player_auth/web/index.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react"; import { BaseWebPlugin, notifyErrorHandler } from "@clusterio/web_ui"; import { Button, Form, Input, Spin, Typography } from "antd"; +import { PlayerAuthServer } from "../messages"; import "./style.css"; const { Paragraph, Text } = Typography; @@ -12,21 +13,39 @@ interface LoginFormProps { } function LoginForm(props: LoginFormProps) { - let [servers, setServers] = useState(null); + const [servers, setServers] = useState(null); let [playerCode, setPlayerCode] = useState(null); let [playerCodeError, setPlayerCodeError] = useState(null); let [verifyCode, setVerifyCode] = useState(); let [verifyToken, setVerifyToken] = useState(null); useEffect(() => { - (async () => { + let cancelled = false; + + async function load() { let response = await fetch(`${webRoot}api/player_auth/servers`); - if (response.ok) { - setServers(await response.json()); - } else { - setServers([]); + if (!response.ok) { + if (!cancelled) { + setServers([]); + const err = new Error(await response.text()); + notifyErrorHandler("Error retrieving servers")(err); + } + return; } - })(); + + const json = await response.json(); + if (!cancelled) { + setServers(json.map((s: any) => PlayerAuthServer.fromJSON(s))); + } + } + + load(); + const interval = setInterval(load, 15 * 1000); + + return () => { + cancelled = true; + clearInterval(interval); + }; }, []); useEffect(() => { @@ -61,7 +80,7 @@ function LoginForm(props: LoginFormProps) { clearInterval(checkLoop); notifyErrorHandler("Error verifying code")(err); }); - }, 2000); + }, 2 * 1000); return () => { clearInterval(checkLoop); }; } @@ -74,8 +93,8 @@ function LoginForm(props: LoginFormProps) { if (servers.length === 0) { return - There are no servers in the cluster currently running that can complete - the login via Factorio. Please try again later. + There are no running servers available for login via Factorio at the moment. + Please try again later. ; } @@ -89,9 +108,35 @@ function LoginForm(props: LoginFormProps) { Start Factorio, join one of the following multiplayer servers and type /web-login into the chat: -
-
    - {servers.map(text =>
  • {text}
  • )} +
    +
      + {servers.map(server => ( +
    • +
      + + {server.name} + {server.factorioVersion && ( + ({server.factorioVersion}) + )} + + + +
      +
    • + ))}