mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-11-21 04:56:04 -08:00
Add EagProxyAAS password protection + direct connect
This commit is contained in:
parent
61cbcfca3e
commit
67c3e0bbda
1958
package-lock.json
generated
1958
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -2,4 +2,9 @@ export const config = {
|
||||||
bindInternalServerPort: 25569,
|
bindInternalServerPort: 25569,
|
||||||
bindInternalServerIp: "127.0.0.1",
|
bindInternalServerIp: "127.0.0.1",
|
||||||
allowCustomPorts: true,
|
allowCustomPorts: true,
|
||||||
|
disallowHypixel: false,
|
||||||
|
authentication: {
|
||||||
|
enabled: true,
|
||||||
|
password: "nope",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
import { config } from "./config.js";
|
import { config } from "./config.js";
|
||||||
import { createServer } from "minecraft-protocol";
|
import { createServer } from "minecraft-protocol";
|
||||||
import { ClientState, ConnectionState, ServerGlobals } from "./types.js";
|
import { ClientState, ConnectionState, ServerGlobals } from "./types.js";
|
||||||
import { handleConnect, hushConsole, setSG } from "./utils.js";
|
import { handleConnect, hushConsole, sendChatComponent, setSG } from "./utils.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
|
import { handleCommand } from "./commands.js";
|
||||||
|
import { registerEndpoints } from "./service/endpoints.js";
|
||||||
|
|
||||||
const PluginManager = PLUGIN_MANAGER;
|
const PluginManager = PLUGIN_MANAGER;
|
||||||
const metadata = JSON.parse(
|
const metadata = JSON.parse(
|
||||||
readFileSync(
|
readFileSync(process.platform == "win32" ? path.join(path.dirname(new URL(import.meta.url).pathname), "metadata.json").slice(1) : path.join(path.dirname(new URL(import.meta.url).pathname), "metadata.json")).toString()
|
||||||
process.platform == "win32"
|
|
||||||
? path
|
|
||||||
.join(
|
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
|
||||||
"metadata.json"
|
|
||||||
)
|
|
||||||
.slice(1)
|
|
||||||
: path.join(
|
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
|
||||||
"metadata.json"
|
|
||||||
)
|
|
||||||
).toString()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const Logger = PluginManager.Logger;
|
const Logger = PluginManager.Logger;
|
||||||
|
@ -31,14 +21,11 @@ const Player = PluginManager.Player;
|
||||||
const MineProtocol = PluginManager.MineProtocol;
|
const MineProtocol = PluginManager.MineProtocol;
|
||||||
const EaglerSkins = PluginManager.EaglerSkins;
|
const EaglerSkins = PluginManager.EaglerSkins;
|
||||||
const Util = PluginManager.Util;
|
const Util = PluginManager.Util;
|
||||||
|
|
||||||
hushConsole();
|
hushConsole();
|
||||||
|
|
||||||
const logger = new Logger("EaglerProxyAAS");
|
const logger = new Logger("EaglerProxyAAS");
|
||||||
logger.info(`Starting ${metadata.name} v${metadata.version}...`);
|
logger.info(`Starting ${metadata.name} v${metadata.version}...`);
|
||||||
logger.info(
|
logger.info(`(internal server port: ${config.bindInternalServerPort}, internal server IP: ${config.bindInternalServerPort})`);
|
||||||
`(internal server port: ${config.bindInternalServerPort}, internal server IP: ${config.bindInternalServerPort})`
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info("Starting internal server...");
|
logger.info("Starting internal server...");
|
||||||
let server = createServer({
|
let server = createServer({
|
||||||
|
@ -55,28 +42,178 @@ let server = createServer({
|
||||||
setSG(sGlobals);
|
setSG(sGlobals);
|
||||||
|
|
||||||
server.on("login", (client) => {
|
server.on("login", (client) => {
|
||||||
logger.info(
|
const proxyPlayer = PluginManager.proxy.players.get(client.username);
|
||||||
`Client ${client.username} has connected to the authentication server.`
|
if (proxyPlayer != null) {
|
||||||
);
|
const url = new URL(proxyPlayer.ws.httpRequest.url, `http${PluginManager.proxy.config.tls?.enabled ? "s" : ""}://${proxyPlayer.ws.httpRequest.headers.host}`);
|
||||||
client.on("end", () => {
|
if (url.pathname == "/connect-vanilla") {
|
||||||
sGlobals.players.delete(client.username);
|
const host = url.searchParams.get("ip"),
|
||||||
logger.info(
|
port = url.searchParams.get("port"),
|
||||||
`Client ${client.username} has disconnected from the authentication server.`
|
type: "OFFLINE" | "ONLINE" = url.searchParams.get("authType") as any;
|
||||||
);
|
|
||||||
});
|
if (isNaN(Number(port))) return proxyPlayer.disconnect(Enums.ChatColor.RED + "Bad port number");
|
||||||
const cs: ClientState = {
|
if (
|
||||||
state: ConnectionState.AUTH,
|
!/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test(
|
||||||
gameClient: client,
|
host
|
||||||
token: null,
|
)
|
||||||
lastStatusUpdate: null,
|
) {
|
||||||
};
|
return proxyPlayer.disconnect(Enums.ChatColor.RED + "Bad host provided");
|
||||||
sGlobals.players.set(client.username, cs);
|
}
|
||||||
handleConnect(cs);
|
|
||||||
|
if (type == "ONLINE") {
|
||||||
|
const _profile = proxyPlayer.ws.httpRequest.headers["Minecraft-Profile"];
|
||||||
|
if (!_profile) proxyPlayer.disconnect(Enums.ChatColor.RED + "Missing Minecraft-Profile header");
|
||||||
|
let profile;
|
||||||
|
try {
|
||||||
|
profile = JSON.parse(_profile as string);
|
||||||
|
} catch (err) {
|
||||||
|
proxyPlayer.disconnect(Enums.ChatColor.RED + "Could not read Minecraft-Profile header");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Direct OFFLINE proxy forward connection from Eaglercraft player (${client.username}) received.`);
|
||||||
|
proxyPlayer.on("vanillaPacket", (packet, origin) => {
|
||||||
|
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||||
|
packet.cancel = true;
|
||||||
|
handleCommand(proxyPlayer, packet.params.message as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendChatComponent(client, {
|
||||||
|
text: `Joining server under ${profile.selectedProfile.name}/your Minecraft account's username! Run `,
|
||||||
|
color: "aqua",
|
||||||
|
extra: [
|
||||||
|
{
|
||||||
|
text: "/eag-help",
|
||||||
|
color: "gold",
|
||||||
|
hoverEvent: {
|
||||||
|
action: "show_text",
|
||||||
|
value: Enums.ChatColor.GOLD + "Click me to run this command!",
|
||||||
|
},
|
||||||
|
clickEvent: {
|
||||||
|
action: "run_command",
|
||||||
|
value: "/eag-help",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " for a list of proxy commands.",
|
||||||
|
color: "aqua",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
(proxyPlayer as any)._onlineSession = {
|
||||||
|
auth: "mojang",
|
||||||
|
username: profile.selectedProfile.name,
|
||||||
|
session: {
|
||||||
|
accessToken: profile.accessToken,
|
||||||
|
clientToken: profile.selectedProfile.id,
|
||||||
|
selectedProfile: {
|
||||||
|
id: profile.selectedProfile.id,
|
||||||
|
name: profile.selectedProfile.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
proxyPlayer
|
||||||
|
.switchServers({
|
||||||
|
host: host,
|
||||||
|
port: Number(port),
|
||||||
|
version: "1.8.8",
|
||||||
|
username: profile.selectedProfile.name,
|
||||||
|
auth: "mojang",
|
||||||
|
keepAlive: false,
|
||||||
|
session: {
|
||||||
|
accessToken: profile.accessToken,
|
||||||
|
clientToken: profile.selectedProfile.id,
|
||||||
|
selectedProfile: {
|
||||||
|
id: profile.selectedProfile.id,
|
||||||
|
name: profile.selectedProfile.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skipValidation: true,
|
||||||
|
hideErrors: true,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (!client.ended) {
|
||||||
|
proxyPlayer.disconnect(
|
||||||
|
Enums.ChatColor.RED +
|
||||||
|
`Something went wrong whilst switching servers: ${err.message}${err.code == "ENOTFOUND" ? (host.includes(":") ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` : "\nIs that IP valid?") : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type == "OFFLINE") {
|
||||||
|
logger.info(`Direct ONLINE proxy forward connection from Eaglercraft player (${client.username}) received.`);
|
||||||
|
logger.info(`Player ${client.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.username}) using offline mode!`);
|
||||||
|
proxyPlayer.on("vanillaPacket", (packet, origin) => {
|
||||||
|
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||||
|
packet.cancel = true;
|
||||||
|
handleCommand(proxyPlayer, packet.params.message as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sendChatComponent(client, {
|
||||||
|
text: `Joining server under ${client.username}/your Eaglercraft account's username! Run `,
|
||||||
|
color: "aqua",
|
||||||
|
extra: [
|
||||||
|
{
|
||||||
|
text: "/eag-help",
|
||||||
|
color: "gold",
|
||||||
|
hoverEvent: {
|
||||||
|
action: "show_text",
|
||||||
|
value: Enums.ChatColor.GOLD + "Click me to run this command!",
|
||||||
|
},
|
||||||
|
clickEvent: {
|
||||||
|
action: "run_command",
|
||||||
|
value: "/eag-help",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " for a list of proxy commands.",
|
||||||
|
color: "aqua",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyPlayer
|
||||||
|
.switchServers({
|
||||||
|
host: host,
|
||||||
|
port: Number(port),
|
||||||
|
auth: "offline",
|
||||||
|
username: client.username,
|
||||||
|
version: "1.8.8",
|
||||||
|
keepAlive: false,
|
||||||
|
skipValidation: true,
|
||||||
|
hideErrors: true,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (!client.ended) {
|
||||||
|
proxyPlayer.disconnect(
|
||||||
|
Enums.ChatColor.RED +
|
||||||
|
`Something went wrong whilst switching servers: ${err.message}${err.code == "ENOTFOUND" ? (host.includes(":") ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` : "\nIs that IP valid?") : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
proxyPlayer.disconnect(Enums.ChatColor.RED + "Missing authentication type");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info(`Client ${client.username} has connected to the authentication server.`);
|
||||||
|
client.on("end", () => {
|
||||||
|
sGlobals.players.delete(client.username);
|
||||||
|
logger.info(`Client ${client.username} has disconnected from the authentication server.`);
|
||||||
|
});
|
||||||
|
const cs: ClientState = {
|
||||||
|
state: ConnectionState.AUTH,
|
||||||
|
gameClient: client,
|
||||||
|
token: null,
|
||||||
|
lastStatusUpdate: null,
|
||||||
|
};
|
||||||
|
sGlobals.players.set(client.username, cs);
|
||||||
|
handleConnect(cs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(`Proxy player object is null for ${client.username}?!`);
|
||||||
|
client.end("Indirect connection to internal authentication server detected!");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info("Redirecting backend server IP... (this is required for the plugin to function)");
|
||||||
"Redirecting backend server IP... (this is required for the plugin to function)"
|
|
||||||
);
|
|
||||||
CONFIG.adapter.server = {
|
CONFIG.adapter.server = {
|
||||||
host: config.bindInternalServerIp,
|
host: config.bindInternalServerIp,
|
||||||
port: config.bindInternalServerPort,
|
port: config.bindInternalServerPort,
|
||||||
|
@ -84,3 +221,7 @@ CONFIG.adapter.server = {
|
||||||
CONFIG.adapter.motd = {
|
CONFIG.adapter.motd = {
|
||||||
l1: Enums.ChatColor.GOLD + "EaglerProxy as a Service",
|
l1: Enums.ChatColor.GOLD + "EaglerProxy as a Service",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PLUGIN_MANAGER.addListener("proxyFinishLoading", () => {
|
||||||
|
registerEndpoints();
|
||||||
|
});
|
||||||
|
|
33
src/plugins/EagProxyAAS/service/endpoints.ts
Normal file
33
src/plugins/EagProxyAAS/service/endpoints.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { config } from "../config.js";
|
||||||
|
|
||||||
|
export async function registerEndpoints() {
|
||||||
|
const proxy = PLUGIN_MANAGER.proxy;
|
||||||
|
proxy.on("httpConnection", (req, res, ctx) => {
|
||||||
|
if (req.url.startsWith("/eagpaas/metadata")) {
|
||||||
|
ctx.handled = true;
|
||||||
|
res.writeHead(200).end(
|
||||||
|
JSON.stringify({
|
||||||
|
branding: "EagProxyAAS",
|
||||||
|
version: "1",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (req.url.startsWith("/eagpaas/validate")) {
|
||||||
|
ctx.handled = true;
|
||||||
|
if (config.authentication.enabled) {
|
||||||
|
if (req.headers["authorization"] !== `Basic ${config.authentication.password}`) {
|
||||||
|
return res.writeHead(403).end(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
reason: "Access Denied",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.writeHead(200).end(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -37,21 +37,10 @@ export function setSG(svr: ServerGlobals) {
|
||||||
|
|
||||||
export function disconectIdle() {
|
export function disconectIdle() {
|
||||||
SERVER.players.forEach((client) => {
|
SERVER.players.forEach((client) => {
|
||||||
if (
|
if (client.state == ConnectionState.AUTH && Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH) {
|
||||||
client.state == ConnectionState.AUTH &&
|
client.gameClient.end("Timed out waiting for user to login via Microsoft");
|
||||||
Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH
|
} else if (client.state == ConnectionState.SUCCESS && Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED) {
|
||||||
) {
|
client.gameClient.end(Enums.ChatColor.RED + "Please enter the IP of the server you'd like to connect to in chat.");
|
||||||
client.gameClient.end(
|
|
||||||
"Timed out waiting for user to login via Microsoft"
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
client.state == ConnectionState.SUCCESS &&
|
|
||||||
Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED
|
|
||||||
) {
|
|
||||||
client.gameClient.end(
|
|
||||||
Enums.ChatColor.RED +
|
|
||||||
"Please enter the IP of the server you'd like to connect to in chat."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -93,10 +82,7 @@ export function handleConnect(client: ClientState) {
|
||||||
onConnect(client);
|
onConnect(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function awaitCommand(
|
export function awaitCommand(client: Client, filter: (msg: string) => boolean): Promise<string> {
|
||||||
client: Client,
|
|
||||||
filter: (msg: string) => boolean
|
|
||||||
): Promise<string> {
|
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
const onMsg = (packet) => {
|
const onMsg = (packet) => {
|
||||||
if (filter(packet.message)) {
|
if (filter(packet.message)) {
|
||||||
|
@ -105,8 +91,7 @@ export function awaitCommand(
|
||||||
res(packet.message);
|
res(packet.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onEnd = () =>
|
const onEnd = () => rej("Client disconnected before promise could be resolved");
|
||||||
rej("Client disconnected before promise could be resolved");
|
|
||||||
client.on("chat", onMsg);
|
client.on("chat", onMsg);
|
||||||
client.on("end", onEnd);
|
client.on("end", onEnd);
|
||||||
});
|
});
|
||||||
|
@ -119,12 +104,7 @@ export function sendMessage(client: Client, msg: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendCustomMessage(
|
export function sendCustomMessage(client: Client, msg: string, color: string, ...components: { text: string; color: string }[]) {
|
||||||
client: Client,
|
|
||||||
msg: string,
|
|
||||||
color: string,
|
|
||||||
...components: { text: string; color: string }[]
|
|
||||||
) {
|
|
||||||
client.write("chat", {
|
client.write("chat", {
|
||||||
message: JSON.stringify(
|
message: JSON.stringify(
|
||||||
components.length > 0
|
components.length > 0
|
||||||
|
@ -198,12 +178,7 @@ export function sendMessageLogin(client: Client, url: string, token: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateState(
|
export function updateState(client: Client, newState: "CONNECTION_TYPE" | "AUTH_EASYMC" | "AUTH" | "SERVER", uri?: string, code?: string) {
|
||||||
client: Client,
|
|
||||||
newState: "CONNECTION_TYPE" | "AUTH_EASYMC" | "AUTH" | "SERVER",
|
|
||||||
uri?: string,
|
|
||||||
code?: string
|
|
||||||
) {
|
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
case "CONNECTION_TYPE":
|
case "CONNECTION_TYPE":
|
||||||
client.write("playerlist_header", {
|
client.write("playerlist_header", {
|
||||||
|
@ -226,10 +201,7 @@ export function updateState(
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "AUTH":
|
case "AUTH":
|
||||||
if (code == null || uri == null)
|
if (code == null || uri == null) throw new Error("Missing code/uri required for title message type AUTH");
|
||||||
throw new Error(
|
|
||||||
"Missing code/uri required for title message type AUTH"
|
|
||||||
);
|
|
||||||
client.write("playerlist_header", {
|
client.write("playerlist_header", {
|
||||||
header: JSON.stringify({
|
header: JSON.stringify({
|
||||||
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
||||||
|
@ -245,9 +217,7 @@ export function updateState(
|
||||||
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
||||||
}),
|
}),
|
||||||
footer: JSON.stringify({
|
footer: JSON.stringify({
|
||||||
text: `${Enums.ChatColor.RED}/join <ip>${
|
text: `${Enums.ChatColor.RED}/join <ip>${config.allowCustomPorts ? " [port]" : ""}`,
|
||||||
config.allowCustomPorts ? " [port]" : ""
|
|
||||||
}`,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -271,10 +241,7 @@ export async function onConnect(client: ClientState) {
|
||||||
client.state = ConnectionState.AUTH;
|
client.state = ConnectionState.AUTH;
|
||||||
client.lastStatusUpdate = Date.now();
|
client.lastStatusUpdate = Date.now();
|
||||||
|
|
||||||
sendMessageWarning(
|
sendMessageWarning(client.gameClient, `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`);
|
||||||
client.gameClient,
|
|
||||||
`WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`
|
|
||||||
);
|
|
||||||
await new Promise((res) => setTimeout(res, 2000));
|
await new Promise((res) => setTimeout(res, 2000));
|
||||||
|
|
||||||
sendMessageWarning(
|
sendMessageWarning(
|
||||||
|
@ -283,12 +250,20 @@ export async function onConnect(client: ClientState) {
|
||||||
);
|
);
|
||||||
await new Promise((res) => setTimeout(res, 2000));
|
await new Promise((res) => setTimeout(res, 2000));
|
||||||
|
|
||||||
sendMessageWarning(
|
sendMessageWarning(client.gameClient, `WARNING: It is highly suggested that you turn down settings, as gameplay tends to be very laggy and unplayable on low powered devices.`);
|
||||||
client.gameClient,
|
|
||||||
`WARNING: It is highly suggested that you turn down settings, as gameplay tends to be very laggy and unplayable on low powered devices.`
|
|
||||||
);
|
|
||||||
await new Promise((res) => setTimeout(res, 2000));
|
await new Promise((res) => setTimeout(res, 2000));
|
||||||
|
|
||||||
|
if (config.authentication.enabled) {
|
||||||
|
sendCustomMessage(client.gameClient, "This instance is password-protected. Sign in with /password <password>", "gold");
|
||||||
|
const password = await awaitCommand(client.gameClient, (msg) => msg.startsWith("/password "));
|
||||||
|
if (password === `/password ${config.authentication.password}`) {
|
||||||
|
sendCustomMessage(client.gameClient, "Successfully signed into instance!", "green");
|
||||||
|
} else {
|
||||||
|
client.gameClient.end(Enums.ChatColor.RED + "Bad password!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendCustomMessage(client.gameClient, "What would you like to do?", "gray");
|
sendCustomMessage(client.gameClient, "What would you like to do?", "gray");
|
||||||
sendChatComponent(client.gameClient, {
|
sendChatComponent(client.gameClient, {
|
||||||
text: "1) ",
|
text: "1) ",
|
||||||
|
@ -344,11 +319,7 @@ export async function onConnect(client: ClientState) {
|
||||||
value: "$3",
|
value: "$3",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, "Select an option from the above (1 = online, 2 = offline, 3 = EasyMC), either by clicking or manually typing out the option's number on the list.", "green");
|
||||||
client.gameClient,
|
|
||||||
"Select an option from the above (1 = online, 2 = offline, 3 = EasyMC), either by clicking or manually typing out the option's number on the list.",
|
|
||||||
"green"
|
|
||||||
);
|
|
||||||
updateState(client.gameClient, "CONNECTION_TYPE");
|
updateState(client.gameClient, "CONNECTION_TYPE");
|
||||||
|
|
||||||
let chosenOption: ConnectType | null = null;
|
let chosenOption: ConnectType | null = null;
|
||||||
|
@ -356,11 +327,7 @@ export async function onConnect(client: ClientState) {
|
||||||
const option = await awaitCommand(client.gameClient, (msg) => true);
|
const option = await awaitCommand(client.gameClient, (msg) => true);
|
||||||
switch (option.replace(/\$/gim, "")) {
|
switch (option.replace(/\$/gim, "")) {
|
||||||
default:
|
default:
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, `I don't understand what you meant by "${option}", please reply with a valid option!`, "red");
|
||||||
client.gameClient,
|
|
||||||
`I don't understand what you meant by "${option}", please reply with a valid option!`,
|
|
||||||
"red"
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "1":
|
case "1":
|
||||||
chosenOption = ConnectType.ONLINE;
|
chosenOption = ConnectType.ONLINE;
|
||||||
|
@ -390,17 +357,8 @@ export async function onConnect(client: ClientState) {
|
||||||
savedAuth;
|
savedAuth;
|
||||||
const authHandler = auth(),
|
const authHandler = auth(),
|
||||||
codeCallback = (code: ServerDeviceCodeResponse) => {
|
codeCallback = (code: ServerDeviceCodeResponse) => {
|
||||||
updateState(
|
updateState(client.gameClient, "AUTH", code.verification_uri, code.user_code);
|
||||||
client.gameClient,
|
sendMessageLogin(client.gameClient, code.verification_uri, code.user_code);
|
||||||
"AUTH",
|
|
||||||
code.verification_uri,
|
|
||||||
code.user_code
|
|
||||||
);
|
|
||||||
sendMessageLogin(
|
|
||||||
client.gameClient,
|
|
||||||
code.verification_uri,
|
|
||||||
code.user_code
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
authHandler.once("error", (err) => {
|
authHandler.once("error", (err) => {
|
||||||
if (!client.gameClient.ended) client.gameClient.end(err.message);
|
if (!client.gameClient.ended) client.gameClient.end(err.message);
|
||||||
|
@ -415,64 +373,30 @@ export async function onConnect(client: ClientState) {
|
||||||
res(result);
|
res(result);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
sendMessage(
|
sendMessage(client.gameClient, Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!");
|
||||||
client.gameClient,
|
|
||||||
Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!"
|
|
||||||
);
|
|
||||||
|
|
||||||
client.state = ConnectionState.SUCCESS;
|
client.state = ConnectionState.SUCCESS;
|
||||||
client.lastStatusUpdate = Date.now();
|
client.lastStatusUpdate = Date.now();
|
||||||
updateState(client.gameClient, "SERVER");
|
updateState(client.gameClient, "SERVER");
|
||||||
sendMessage(
|
sendMessage(client.gameClient, `Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${
|
|
||||||
config.allowCustomPorts ? " [port]" : ""
|
|
||||||
}${Enums.ChatColor.RESET}.`
|
|
||||||
);
|
|
||||||
let host: string, port: number;
|
let host: string, port: number;
|
||||||
while (true) {
|
while (true) {
|
||||||
const msg = await awaitCommand(client.gameClient, (msg) =>
|
const msg = await awaitCommand(client.gameClient, (msg) => msg.startsWith("/join")),
|
||||||
msg.startsWith("/join")
|
|
||||||
),
|
|
||||||
parsed = msg.split(/ /gi, 3);
|
parsed = msg.split(/ /gi, 3);
|
||||||
if (parsed.length < 2)
|
if (parsed.length < 2) sendMessage(client.gameClient, `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
sendMessage(
|
else if (parsed.length > 2 && isNaN(parseInt(parsed[2]))) sendMessage(client.gameClient, `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Please provide a server to connect to. ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else if (parsed.length > 2 && isNaN(parseInt(parsed[2])))
|
|
||||||
sendMessage(
|
|
||||||
client.gameClient,
|
|
||||||
`A valid port number has to be passed! ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else {
|
else {
|
||||||
host = parsed[1];
|
host = parsed[1];
|
||||||
if (parsed.length > 2) port = parseInt(parsed[2]);
|
if (parsed.length > 2) port = parseInt(parsed[2]);
|
||||||
if (port != null && !config.allowCustomPorts) {
|
if (port != null && !config.allowCustomPorts) {
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>" + (config.allowCustomPorts ? " [port]" : ""), "red");
|
||||||
client.gameClient,
|
|
||||||
"You are not allowed to use custom server ports! /join <ip>" +
|
|
||||||
(config.allowCustomPorts ? " [port]" : ""),
|
|
||||||
"red"
|
|
||||||
);
|
|
||||||
host = null;
|
host = null;
|
||||||
port = null;
|
port = null;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (host.match(/^(?:\*\.)?((?!hypixel\.net$)[^.]+\.)*hypixel\.net$/) && config.disallowHypixel) {
|
||||||
host.match(/^(?:\*\.)?((?!hypixel\.net$)[^.]+\.)*hypixel\.net$/)
|
|
||||||
) {
|
|
||||||
sendCustomMessage(
|
sendCustomMessage(
|
||||||
client.gameClient,
|
client.gameClient,
|
||||||
"Disallowed server, refusing to connect! Hypixel has been known to falsely flag Eaglercraft clients, and thus we do not allow connecting to their server. /join <ip>" +
|
"Disallowed server, refusing to connect! Hypixel has been known to falsely flag Eaglercraft clients, and thus we do not allow connecting to their server. /join <ip>" + (config.allowCustomPorts ? " [port]" : ""),
|
||||||
(config.allowCustomPorts ? " [port]" : ""),
|
|
||||||
"red"
|
"red"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -505,21 +429,10 @@ export async function onConnect(client: ClientState) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
logger.info(
|
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Minecraft account's username (${savedAuth.selectedProfile.name}) using online mode!`);
|
||||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Minecraft account's username (${savedAuth.selectedProfile.name}) using online mode!`
|
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||||
);
|
|
||||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
|
||||||
client.gameClient.username
|
|
||||||
);
|
|
||||||
player.on("vanillaPacket", (packet, origin) => {
|
player.on("vanillaPacket", (packet, origin) => {
|
||||||
if (
|
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||||
origin == "CLIENT" &&
|
|
||||||
packet.name == "chat" &&
|
|
||||||
(packet.params.message as string)
|
|
||||||
.toLowerCase()
|
|
||||||
.startsWith("/eag-") &&
|
|
||||||
!packet.cancel
|
|
||||||
) {
|
|
||||||
packet.cancel = true;
|
packet.cancel = true;
|
||||||
handleCommand(player, packet.params.message as string);
|
handleCommand(player, packet.params.message as string);
|
||||||
}
|
}
|
||||||
|
@ -560,13 +473,7 @@ export async function onConnect(client: ClientState) {
|
||||||
if (!client.gameClient.ended) {
|
if (!client.gameClient.ended) {
|
||||||
client.gameClient.end(
|
client.gameClient.end(
|
||||||
Enums.ChatColor.RED +
|
Enums.ChatColor.RED +
|
||||||
`Something went wrong whilst switching servers: ${err.message}${
|
`Something went wrong whilst switching servers: ${err.message}${err.code == "ENOTFOUND" ? (host.includes(":") ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` : "\nIs that IP valid?") : ""}`
|
||||||
err.code == "ENOTFOUND"
|
|
||||||
? host.includes(":")
|
|
||||||
? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.`
|
|
||||||
: "\nIs that IP valid?"
|
|
||||||
: ""
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -576,10 +483,7 @@ export async function onConnect(client: ClientState) {
|
||||||
client.lastStatusUpdate = Date.now();
|
client.lastStatusUpdate = Date.now();
|
||||||
updateState(client.gameClient, "AUTH_EASYMC");
|
updateState(client.gameClient, "AUTH_EASYMC");
|
||||||
|
|
||||||
sendMessageWarning(
|
sendMessageWarning(client.gameClient, `WARNING: You've chosen to use an account from EasyMC's account pool. Please note that accounts and shared, and may be banned from whatever server you are attempting to join.`);
|
||||||
client.gameClient,
|
|
||||||
`WARNING: You've chosen to use an account from EasyMC's account pool. Please note that accounts and shared, and may be banned from whatever server you are attempting to join.`
|
|
||||||
);
|
|
||||||
sendChatComponent(client.gameClient, {
|
sendChatComponent(client.gameClient, {
|
||||||
text: "Please generate an alt token at ",
|
text: "Please generate an alt token at ",
|
||||||
color: "white",
|
color: "white",
|
||||||
|
@ -621,9 +525,7 @@ export async function onConnect(client: ClientState) {
|
||||||
|
|
||||||
let appendOptions: any;
|
let appendOptions: any;
|
||||||
while (true) {
|
while (true) {
|
||||||
const tokenResponse = await awaitCommand(client.gameClient, (msg) =>
|
const tokenResponse = await awaitCommand(client.gameClient, (msg) => msg.toLowerCase().startsWith("/login")),
|
||||||
msg.toLowerCase().startsWith("/login")
|
|
||||||
),
|
|
||||||
splitResponse = tokenResponse.split(/ /gim, 2).slice(1);
|
splitResponse = tokenResponse.split(/ /gim, 2).slice(1);
|
||||||
if (splitResponse.length != 1) {
|
if (splitResponse.length != 1) {
|
||||||
sendChatComponent(client.gameClient, {
|
sendChatComponent(client.gameClient, {
|
||||||
|
@ -660,9 +562,7 @@ export async function onConnect(client: ClientState) {
|
||||||
color: "white",
|
color: "white",
|
||||||
hoverEvent: {
|
hoverEvent: {
|
||||||
action: "show_text",
|
action: "show_text",
|
||||||
value:
|
value: Enums.ChatColor.GOLD + "Click me to open in a new window!",
|
||||||
Enums.ChatColor.GOLD +
|
|
||||||
"Click me to open in a new window!",
|
|
||||||
},
|
},
|
||||||
clickEvent: {
|
clickEvent: {
|
||||||
action: "open_url",
|
action: "open_url",
|
||||||
|
@ -692,18 +592,10 @@ export async function onConnect(client: ClientState) {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, "Validating alt token...", "gray");
|
||||||
client.gameClient,
|
|
||||||
"Validating alt token...",
|
|
||||||
"gray"
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
appendOptions = await getTokenProfileEasyMc(token);
|
appendOptions = await getTokenProfileEasyMc(token);
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, `Successfully validated your alt token and retrieved your session profile! You'll be joining to your preferred server as ${appendOptions.username}.`, "green");
|
||||||
client.gameClient,
|
|
||||||
`Successfully validated your alt token and retrieved your session profile! You'll be joining to your preferred server as ${appendOptions.username}.`,
|
|
||||||
"green"
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
sendChatComponent(client.gameClient, {
|
sendChatComponent(client.gameClient, {
|
||||||
|
@ -736,45 +628,18 @@ export async function onConnect(client: ClientState) {
|
||||||
client.state = ConnectionState.SUCCESS;
|
client.state = ConnectionState.SUCCESS;
|
||||||
client.lastStatusUpdate = Date.now();
|
client.lastStatusUpdate = Date.now();
|
||||||
updateState(client.gameClient, "SERVER");
|
updateState(client.gameClient, "SERVER");
|
||||||
sendMessage(
|
sendMessage(client.gameClient, `Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${
|
|
||||||
config.allowCustomPorts ? " [port]" : ""
|
|
||||||
}${Enums.ChatColor.RESET}.`
|
|
||||||
);
|
|
||||||
let host: string, port: number;
|
let host: string, port: number;
|
||||||
while (true) {
|
while (true) {
|
||||||
const msg = await awaitCommand(client.gameClient, (msg) =>
|
const msg = await awaitCommand(client.gameClient, (msg) => msg.startsWith("/join")),
|
||||||
msg.startsWith("/join")
|
|
||||||
),
|
|
||||||
parsed = msg.split(/ /gi, 3);
|
parsed = msg.split(/ /gi, 3);
|
||||||
if (parsed.length < 2)
|
if (parsed.length < 2) sendMessage(client.gameClient, `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
sendMessage(
|
else if (parsed.length > 2 && isNaN(parseInt(parsed[2]))) sendMessage(client.gameClient, `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Please provide a server to connect to. ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else if (parsed.length > 2 && isNaN(parseInt(parsed[2])))
|
|
||||||
sendMessage(
|
|
||||||
client.gameClient,
|
|
||||||
`A valid port number has to be passed! ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else {
|
else {
|
||||||
host = parsed[1];
|
host = parsed[1];
|
||||||
if (parsed.length > 2) port = parseInt(parsed[2]);
|
if (parsed.length > 2) port = parseInt(parsed[2]);
|
||||||
if (port != null && !config.allowCustomPorts) {
|
if (port != null && !config.allowCustomPorts) {
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>", "red");
|
||||||
client.gameClient,
|
|
||||||
"You are not allowed to use custom server ports! /join <ip>",
|
|
||||||
"red"
|
|
||||||
);
|
|
||||||
host = null;
|
host = null;
|
||||||
port = null;
|
port = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -806,21 +671,10 @@ export async function onConnect(client: ClientState) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
logger.info(
|
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their EasyMC alt token's username (${appendOptions.username}) using EasyMC mode!`);
|
||||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their EasyMC alt token's username (${appendOptions.username}) using EasyMC mode!`
|
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||||
);
|
|
||||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
|
||||||
client.gameClient.username
|
|
||||||
);
|
|
||||||
player.on("vanillaPacket", (packet, origin) => {
|
player.on("vanillaPacket", (packet, origin) => {
|
||||||
if (
|
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||||
origin == "CLIENT" &&
|
|
||||||
packet.name == "chat" &&
|
|
||||||
(packet.params.message as string)
|
|
||||||
.toLowerCase()
|
|
||||||
.startsWith("/eag-") &&
|
|
||||||
!packet.cancel
|
|
||||||
) {
|
|
||||||
packet.cancel = true;
|
packet.cancel = true;
|
||||||
handleCommand(player, packet.params.message as string);
|
handleCommand(player, packet.params.message as string);
|
||||||
}
|
}
|
||||||
|
@ -843,13 +697,7 @@ export async function onConnect(client: ClientState) {
|
||||||
if (!client.gameClient.ended) {
|
if (!client.gameClient.ended) {
|
||||||
client.gameClient.end(
|
client.gameClient.end(
|
||||||
Enums.ChatColor.RED +
|
Enums.ChatColor.RED +
|
||||||
`Something went wrong whilst switching servers: ${err.message}${
|
`Something went wrong whilst switching servers: ${err.message}${err.code == "ENOTFOUND" ? (host.includes(":") ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` : "\nIs that IP valid?") : ""}`
|
||||||
err.code == "ENOTFOUND"
|
|
||||||
? host.includes(":")
|
|
||||||
? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.`
|
|
||||||
: "\nIs that IP valid?"
|
|
||||||
: ""
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -857,45 +705,18 @@ export async function onConnect(client: ClientState) {
|
||||||
client.state = ConnectionState.SUCCESS;
|
client.state = ConnectionState.SUCCESS;
|
||||||
client.lastStatusUpdate = Date.now();
|
client.lastStatusUpdate = Date.now();
|
||||||
updateState(client.gameClient, "SERVER");
|
updateState(client.gameClient, "SERVER");
|
||||||
sendMessage(
|
sendMessage(client.gameClient, `Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${
|
|
||||||
config.allowCustomPorts ? " [port]" : ""
|
|
||||||
}${Enums.ChatColor.RESET}.`
|
|
||||||
);
|
|
||||||
let host: string, port: number;
|
let host: string, port: number;
|
||||||
while (true) {
|
while (true) {
|
||||||
const msg = await awaitCommand(client.gameClient, (msg) =>
|
const msg = await awaitCommand(client.gameClient, (msg) => msg.startsWith("/join")),
|
||||||
msg.startsWith("/join")
|
|
||||||
),
|
|
||||||
parsed = msg.split(/ /gi, 3);
|
parsed = msg.split(/ /gi, 3);
|
||||||
if (parsed.length < 2)
|
if (parsed.length < 2) sendMessage(client.gameClient, `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
sendMessage(
|
else if (parsed.length > 2 && isNaN(parseInt(parsed[2]))) sendMessage(client.gameClient, `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${Enums.ChatColor.RESET}.`);
|
||||||
client.gameClient,
|
|
||||||
`Please provide a server to connect to. ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else if (parsed.length > 2 && isNaN(parseInt(parsed[2])))
|
|
||||||
sendMessage(
|
|
||||||
client.gameClient,
|
|
||||||
`A valid port number has to be passed! ${
|
|
||||||
Enums.ChatColor.GOLD
|
|
||||||
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
|
|
||||||
Enums.ChatColor.RESET
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
else {
|
else {
|
||||||
host = parsed[1];
|
host = parsed[1];
|
||||||
if (parsed.length > 2) port = parseInt(parsed[2]);
|
if (parsed.length > 2) port = parseInt(parsed[2]);
|
||||||
if (port != null && !config.allowCustomPorts) {
|
if (port != null && !config.allowCustomPorts) {
|
||||||
sendCustomMessage(
|
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>", "red");
|
||||||
client.gameClient,
|
|
||||||
"You are not allowed to use custom server ports! /join <ip>",
|
|
||||||
"red"
|
|
||||||
);
|
|
||||||
host = null;
|
host = null;
|
||||||
port = null;
|
port = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -927,21 +748,10 @@ export async function onConnect(client: ClientState) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
logger.info(
|
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.gameClient.username}) using offline mode!`);
|
||||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.gameClient.username}) using offline mode!`
|
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||||
);
|
|
||||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
|
||||||
client.gameClient.username
|
|
||||||
);
|
|
||||||
player.on("vanillaPacket", (packet, origin) => {
|
player.on("vanillaPacket", (packet, origin) => {
|
||||||
if (
|
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||||
origin == "CLIENT" &&
|
|
||||||
packet.name == "chat" &&
|
|
||||||
(packet.params.message as string)
|
|
||||||
.toLowerCase()
|
|
||||||
.startsWith("/eag-") &&
|
|
||||||
!packet.cancel
|
|
||||||
) {
|
|
||||||
packet.cancel = true;
|
packet.cancel = true;
|
||||||
handleCommand(player, packet.params.message as string);
|
handleCommand(player, packet.params.message as string);
|
||||||
}
|
}
|
||||||
|
@ -961,122 +771,32 @@ export async function onConnect(client: ClientState) {
|
||||||
if (!client.gameClient.ended) {
|
if (!client.gameClient.ended) {
|
||||||
client.gameClient.end(
|
client.gameClient.end(
|
||||||
Enums.ChatColor.RED +
|
Enums.ChatColor.RED +
|
||||||
`Something went wrong whilst switching servers: ${err.message}${
|
`Something went wrong whilst switching servers: ${err.message}${err.code == "ENOTFOUND" ? (host.includes(":") ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` : "\nIs that IP valid?") : ""}`
|
||||||
err.code == "ENOTFOUND"
|
|
||||||
? host.includes(":")
|
|
||||||
? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.`
|
|
||||||
: "\nIs that IP valid?"
|
|
||||||
: ""
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!client.gameClient.ended) {
|
if (!client.gameClient.ended) {
|
||||||
logger.error(
|
logger.error(`Error whilst processing user ${client.gameClient.username}: ${err.stack || err}`);
|
||||||
`Error whilst processing user ${client.gameClient.username}: ${
|
client.gameClient.end(Enums.ChatColor.YELLOW + "Something went wrong whilst processing your request. Please reconnect.");
|
||||||
err.stack || err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
client.gameClient.end(
|
|
||||||
Enums.ChatColor.YELLOW +
|
|
||||||
"Something went wrong whilst processing your request. Please reconnect."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateSpawnChunk(): Chunk.PCChunk {
|
export function generateSpawnChunk(): Chunk.PCChunk {
|
||||||
const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk;
|
const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk;
|
||||||
chunk.initialize(
|
chunk.initialize(() => new McBlock(REGISTRY.blocksByName.air.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
() =>
|
chunk.setBlock(new Vec3(8, 64, 8), new McBlock(REGISTRY.blocksByName.sea_lantern.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
new McBlock(
|
chunk.setBlock(new Vec3(8, 67, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
REGISTRY.blocksByName.air.id,
|
chunk.setBlock(new Vec3(7, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
REGISTRY.biomesByName.the_end.id,
|
chunk.setBlock(new Vec3(7, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
0
|
chunk.setBlock(new Vec3(9, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
)
|
chunk.setBlock(new Vec3(9, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
);
|
chunk.setBlock(new Vec3(8, 65, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
chunk.setBlock(
|
chunk.setBlock(new Vec3(8, 66, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
new Vec3(8, 64, 8),
|
chunk.setBlock(new Vec3(8, 65, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
new McBlock(
|
chunk.setBlock(new Vec3(8, 66, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||||
REGISTRY.blocksByName.sea_lantern.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(8, 67, 8),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(7, 65, 8),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(7, 66, 8),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(9, 65, 8),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(9, 66, 8),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(8, 65, 7),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(8, 66, 7),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(8, 65, 9),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
chunk.setBlock(
|
|
||||||
new Vec3(8, 66, 9),
|
|
||||||
new McBlock(
|
|
||||||
REGISTRY.blocksByName.barrier.id,
|
|
||||||
REGISTRY.biomesByName.the_end.id,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// chunk.setBlockLight(new Vec3(8, 65, 8), 15);
|
// chunk.setBlockLight(new Vec3(8, 65, 8), 15);
|
||||||
chunk.setBlockLight(new Vec3(8, 66, 8), 15);
|
chunk.setBlockLight(new Vec3(8, 66, 8), 15);
|
||||||
return chunk;
|
return chunk;
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import pkg, {
|
import pkg, { Client, ClientOptions, createClient, states } from "minecraft-protocol";
|
||||||
Client,
|
|
||||||
ClientOptions,
|
|
||||||
createClient,
|
|
||||||
states,
|
|
||||||
} from "minecraft-protocol";
|
|
||||||
import { WebSocket } from "ws";
|
import { WebSocket } from "ws";
|
||||||
import { Logger } from "../logger.js";
|
import { Logger } from "../logger.js";
|
||||||
import { Chat } from "./Chat.js";
|
import { Chat } from "./Chat.js";
|
||||||
|
@ -15,11 +10,12 @@ import { MineProtocol } from "./Protocol.js";
|
||||||
import { EaglerSkins } from "./skins/EaglerSkins.js";
|
import { EaglerSkins } from "./skins/EaglerSkins.js";
|
||||||
import { Util } from "./Util.js";
|
import { Util } from "./Util.js";
|
||||||
import { BungeeUtil } from "./BungeeUtil.js";
|
import { BungeeUtil } from "./BungeeUtil.js";
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
|
||||||
const { createSerializer, createDeserializer } = pkg;
|
const { createSerializer, createDeserializer } = pkg;
|
||||||
|
|
||||||
export class Player extends EventEmitter {
|
export class Player extends EventEmitter {
|
||||||
public ws: WebSocket;
|
public ws: WebSocket & { httpRequest: IncomingMessage };
|
||||||
public username?: string;
|
public username?: string;
|
||||||
public skin?: EaglerSkins.EaglerSkin;
|
public skin?: EaglerSkins.EaglerSkin;
|
||||||
public uuid?: string;
|
public uuid?: string;
|
||||||
|
@ -37,14 +33,13 @@ export class Player extends EventEmitter {
|
||||||
public clientDeserializer: any;
|
public clientDeserializer: any;
|
||||||
private _kickMessage: string;
|
private _kickMessage: string;
|
||||||
|
|
||||||
constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) {
|
constructor(ws: WebSocket & { httpRequest: IncomingMessage }, playerName?: string, serverConnection?: Client) {
|
||||||
super();
|
super();
|
||||||
this._logger = new Logger(`PlayerHandler-${playerName}`);
|
this._logger = new Logger(`PlayerHandler-${playerName}`);
|
||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
this.username = playerName;
|
this.username = playerName;
|
||||||
this.serverConnection = serverConnection;
|
this.serverConnection = serverConnection;
|
||||||
if (this.username != null)
|
if (this.username != null) this.uuid = Util.generateUUIDFromPlayer(this.username);
|
||||||
this.uuid = Util.generateUUIDFromPlayer(this.username);
|
|
||||||
this.serverSerializer = createSerializer({
|
this.serverSerializer = createSerializer({
|
||||||
state: states.PLAY,
|
state: states.PLAY,
|
||||||
isServer: true,
|
isServer: true,
|
||||||
|
@ -81,28 +76,15 @@ export class Player extends EventEmitter {
|
||||||
if (msg instanceof Buffer == false) return;
|
if (msg instanceof Buffer == false) return;
|
||||||
const decoder = PACKET_REGISTRY.get(msg[0]);
|
const decoder = PACKET_REGISTRY.get(msg[0]);
|
||||||
if (decoder && decoder.sentAfterHandshake) {
|
if (decoder && decoder.sentAfterHandshake) {
|
||||||
if (
|
if (!decoder && this.state != Enums.ClientState.POST_HANDSHAKE && msg.length >= 1) {
|
||||||
!decoder &&
|
this._logger.warn(`Packet with ID 0x${Buffer.from([msg[0]]).toString("hex")} is missing a corresponding packet handler! Processing for this packet will be skipped.`);
|
||||||
this.state != Enums.ClientState.POST_HANDSHAKE &&
|
|
||||||
msg.length >= 1
|
|
||||||
) {
|
|
||||||
this._logger.warn(
|
|
||||||
`Packet with ID 0x${Buffer.from([msg[0]]).toString(
|
|
||||||
"hex"
|
|
||||||
)} is missing a corresponding packet handler! Processing for this packet will be skipped.`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let parsed: Packet, err: boolean;
|
let parsed: Packet, err: boolean;
|
||||||
try {
|
try {
|
||||||
parsed = new decoder.class();
|
parsed = new decoder.class();
|
||||||
parsed.deserialize(msg);
|
parsed.deserialize(msg);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this.state != Enums.ClientState.POST_HANDSHAKE)
|
if (this.state != Enums.ClientState.POST_HANDSHAKE) this._logger.warn(`Packet ID 0x${Buffer.from([msg[0]]).toString("hex")} failed to parse! The packet will be skipped.`);
|
||||||
this._logger.warn(
|
|
||||||
`Packet ID 0x${Buffer.from([msg[0]]).toString(
|
|
||||||
"hex"
|
|
||||||
)} failed to parse! The packet will be skipped.`
|
|
||||||
);
|
|
||||||
err = true;
|
err = true;
|
||||||
}
|
}
|
||||||
if (!err) {
|
if (!err) {
|
||||||
|
@ -113,10 +95,7 @@ export class Player extends EventEmitter {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const parsed = this.serverDeserializer.parsePacketBuffer(msg)?.data,
|
const parsed = this.serverDeserializer.parsePacketBuffer(msg)?.data,
|
||||||
translated = this.translator.translatePacketClient(
|
translated = this.translator.translatePacketClient(parsed.params, parsed),
|
||||||
parsed.params,
|
|
||||||
parsed
|
|
||||||
),
|
|
||||||
packetData = {
|
packetData = {
|
||||||
name: translated[0],
|
name: translated[0],
|
||||||
params: translated[1],
|
params: translated[1],
|
||||||
|
@ -132,12 +111,7 @@ export class Player extends EventEmitter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.debug(
|
this._logger.debug(`Client ${this.username!} sent an unrecognized packet that could not be parsed!\n${err.stack ?? err}`);
|
||||||
`Client ${this
|
|
||||||
.username!} sent an unrecognized packet that could not be parsed!\n${
|
|
||||||
err.stack ?? err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -147,21 +121,12 @@ export class Player extends EventEmitter {
|
||||||
this.ws.send(packet.serialize());
|
this.ws.send(packet.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async read(
|
public async read(packetId?: Enums.PacketId, filter?: (packet: Packet) => boolean): Promise<Packet> {
|
||||||
packetId?: Enums.PacketId,
|
|
||||||
filter?: (packet: Packet) => boolean
|
|
||||||
): Promise<Packet> {
|
|
||||||
let res;
|
let res;
|
||||||
await Util.awaitPacket(this.ws, (packet) => {
|
await Util.awaitPacket(this.ws, (packet) => {
|
||||||
if ((packetId != null && packetId == packet[0]) || packetId == null) {
|
if ((packetId != null && packetId == packet[0]) || packetId == null) {
|
||||||
const decoder = PACKET_REGISTRY.get(packet[0]);
|
const decoder = PACKET_REGISTRY.get(packet[0]);
|
||||||
if (
|
if (decoder != null && decoder.packetId == packet[0] && (this.state == Enums.ClientState.PRE_HANDSHAKE || decoder.sentAfterHandshake) && decoder.boundTo == Enums.PacketBounds.S) {
|
||||||
decoder != null &&
|
|
||||||
decoder.packetId == packet[0] &&
|
|
||||||
(this.state == Enums.ClientState.PRE_HANDSHAKE ||
|
|
||||||
decoder.sentAfterHandshake) &&
|
|
||||||
decoder.boundTo == Enums.PacketBounds.S
|
|
||||||
) {
|
|
||||||
let parsed: Packet,
|
let parsed: Packet,
|
||||||
err = false;
|
err = false;
|
||||||
try {
|
try {
|
||||||
|
@ -188,16 +153,7 @@ export class Player extends EventEmitter {
|
||||||
|
|
||||||
public disconnect(message: Chat.Chat | string) {
|
public disconnect(message: Chat.Chat | string) {
|
||||||
if (this.state == Enums.ClientState.POST_HANDSHAKE) {
|
if (this.state == Enums.ClientState.POST_HANDSHAKE) {
|
||||||
this.ws.send(
|
this.ws.send(Buffer.concat([[0x40], MineProtocol.writeString(typeof message == "string" ? message : JSON.stringify(message))].map((arr) => (arr instanceof Uint8Array ? arr : Buffer.from(arr)))));
|
||||||
Buffer.concat(
|
|
||||||
[
|
|
||||||
[0x40],
|
|
||||||
MineProtocol.writeString(
|
|
||||||
typeof message == "string" ? message : JSON.stringify(message)
|
|
||||||
),
|
|
||||||
].map((arr) => (arr instanceof Uint8Array ? arr : Buffer.from(arr)))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.ws.close();
|
this.ws.close();
|
||||||
} else {
|
} else {
|
||||||
const packet = new SCDisconnectPacket();
|
const packet = new SCDisconnectPacket();
|
||||||
|
@ -208,10 +164,7 @@ export class Player extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect(options: ClientOptions) {
|
public async connect(options: ClientOptions) {
|
||||||
if (this._alreadyConnected)
|
if (this._alreadyConnected) throw new Error(`Invalid state: Player has already been connected to a server, and .connect() was just called. Please use switchServers() instead.`);
|
||||||
throw new Error(
|
|
||||||
`Invalid state: Player has already been connected to a server, and .connect() was just called. Please use switchServers() instead.`
|
|
||||||
);
|
|
||||||
this._alreadyConnected = true;
|
this._alreadyConnected = true;
|
||||||
this.serverConnection = createClient(
|
this.serverConnection = createClient(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
|
@ -269,9 +222,7 @@ export class Player extends EventEmitter {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await this._bindListenersMineClient(this.serverConnection, true, () =>
|
await this._bindListenersMineClient(this.serverConnection, true, () => oldConnection.end())
|
||||||
oldConnection.end()
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.emit("switchServer", this.serverConnection, this);
|
this.emit("switchServer", this.serverConnection, this);
|
||||||
res();
|
res();
|
||||||
|
@ -283,11 +234,7 @@ export class Player extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _bindListenersMineClient(
|
private async _bindListenersMineClient(client: Client, switchingServers?: boolean, onSwitch?: Function) {
|
||||||
client: Client,
|
|
||||||
switchingServers?: boolean,
|
|
||||||
onSwitch?: Function
|
|
||||||
) {
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
let stream = false,
|
let stream = false,
|
||||||
uuid;
|
uuid;
|
||||||
|
@ -300,17 +247,13 @@ export class Player extends EventEmitter {
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
this.disconnect(
|
this.disconnect(`${Enums.ChatColor.RED}Something went wrong: ${err.stack ?? err}`);
|
||||||
`${Enums.ChatColor.RED}Something went wrong: ${err.stack ?? err}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!stream && this.state != Enums.ClientState.DISCONNECTED) {
|
if (!stream && this.state != Enums.ClientState.DISCONNECTED) {
|
||||||
client.end("Timed out waiting for server connection.");
|
client.end("Timed out waiting for server connection.");
|
||||||
this.disconnect(
|
this.disconnect(Enums.ChatColor.RED + "Timed out waiting for server connection!");
|
||||||
Enums.ChatColor.RED + "Timed out waiting for server connection!"
|
|
||||||
);
|
|
||||||
throw new Error("Timed out waiting for server connection!");
|
throw new Error("Timed out waiting for server connection!");
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
@ -349,14 +292,8 @@ export class Player extends EventEmitter {
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
if (switchingServers) {
|
if (switchingServers) {
|
||||||
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
||||||
this.translator = new BungeeUtil.PacketUUIDTranslator(
|
this.translator = new BungeeUtil.PacketUUIDTranslator(client.uuid, this.uuid);
|
||||||
client.uuid,
|
const pckSeq = BungeeUtil.getRespawnSequence(packet, this.serverSerializer);
|
||||||
this.uuid
|
|
||||||
);
|
|
||||||
const pckSeq = BungeeUtil.getRespawnSequence(
|
|
||||||
packet,
|
|
||||||
this.serverSerializer
|
|
||||||
);
|
|
||||||
this.ws.send(
|
this.ws.send(
|
||||||
this.serverSerializer.createPacketBuffer({
|
this.serverSerializer.createPacketBuffer({
|
||||||
name: "login",
|
name: "login",
|
||||||
|
@ -367,19 +304,12 @@ export class Player extends EventEmitter {
|
||||||
stream = true;
|
stream = true;
|
||||||
if (onSwitch) onSwitch();
|
if (onSwitch) onSwitch();
|
||||||
res(null);
|
res(null);
|
||||||
} else if (
|
} else if (meta.name == "success" && meta.state == states.LOGIN && !uuid) {
|
||||||
meta.name == "success" &&
|
|
||||||
meta.state == states.LOGIN &&
|
|
||||||
!uuid
|
|
||||||
) {
|
|
||||||
uuid = packet.uuid;
|
uuid = packet.uuid;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
||||||
this.translator = new BungeeUtil.PacketUUIDTranslator(
|
this.translator = new BungeeUtil.PacketUUIDTranslator(client.uuid, this.uuid);
|
||||||
client.uuid,
|
|
||||||
this.uuid
|
|
||||||
);
|
|
||||||
this.ws.send(
|
this.ws.send(
|
||||||
this.serverSerializer.createPacketBuffer({
|
this.serverSerializer.createPacketBuffer({
|
||||||
name: "login",
|
name: "login",
|
||||||
|
@ -389,19 +319,12 @@ export class Player extends EventEmitter {
|
||||||
stream = true;
|
stream = true;
|
||||||
if (onSwitch) onSwitch();
|
if (onSwitch) onSwitch();
|
||||||
res(null);
|
res(null);
|
||||||
} else if (
|
} else if (meta.name == "success" && meta.state == states.LOGIN && !uuid) {
|
||||||
meta.name == "success" &&
|
|
||||||
meta.state == states.LOGIN &&
|
|
||||||
!uuid
|
|
||||||
) {
|
|
||||||
uuid = packet.uuid;
|
uuid = packet.uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const translated = this.translator!.translatePacketServer(
|
const translated = this.translator!.translatePacketServer(packet, meta),
|
||||||
packet,
|
|
||||||
meta
|
|
||||||
),
|
|
||||||
eventData = {
|
eventData = {
|
||||||
name: translated[0],
|
name: translated[0],
|
||||||
params: translated[1],
|
params: translated[1],
|
||||||
|
@ -444,8 +367,5 @@ export declare interface Player {
|
||||||
on<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
on<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
||||||
once<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
once<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
||||||
|
|
||||||
emit<U extends keyof PlayerEvents>(
|
emit<U extends keyof PlayerEvents>(event: U, ...args: Parameters<PlayerEvents[U]>): boolean;
|
||||||
event: U,
|
|
||||||
...args: Parameters<PlayerEvents[U]>
|
|
||||||
): boolean;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,7 @@ import SCIdentifyPacket from "./packets/SCIdentifyPacket.js";
|
||||||
import { Motd } from "./Motd.js";
|
import { Motd } from "./Motd.js";
|
||||||
import { Player } from "./Player.js";
|
import { Player } from "./Player.js";
|
||||||
import { Enums } from "./Enums.js";
|
import { Enums } from "./Enums.js";
|
||||||
import {
|
import { NETWORK_VERSION, PROXY_BRANDING, PROXY_VERSION, VANILLA_PROTOCOL_VERSION } from "../meta.js";
|
||||||
NETWORK_VERSION,
|
|
||||||
PROXY_BRANDING,
|
|
||||||
PROXY_VERSION,
|
|
||||||
VANILLA_PROTOCOL_VERSION,
|
|
||||||
} from "../meta.js";
|
|
||||||
import { CSUsernamePacket } from "./packets/CSUsernamePacket.js";
|
import { CSUsernamePacket } from "./packets/CSUsernamePacket.js";
|
||||||
import { SCSyncUuidPacket } from "./packets/SCSyncUuidPacket.js";
|
import { SCSyncUuidPacket } from "./packets/SCSyncUuidPacket.js";
|
||||||
import { SCReadyPacket } from "./packets/SCReadyPacket.js";
|
import { SCReadyPacket } from "./packets/SCReadyPacket.js";
|
||||||
|
@ -61,42 +56,30 @@ export class Proxy extends EventEmitter {
|
||||||
// hijack the initial handler logger to append [InitialHandler] to the beginning
|
// hijack the initial handler logger to append [InitialHandler] to the beginning
|
||||||
(this.initalHandlerLogger as any)._info = this.initalHandlerLogger.info;
|
(this.initalHandlerLogger as any)._info = this.initalHandlerLogger.info;
|
||||||
this.initalHandlerLogger.info = (msg: string) => {
|
this.initalHandlerLogger.info = (msg: string) => {
|
||||||
(this.initalHandlerLogger as any)._info(
|
(this.initalHandlerLogger as any)._info(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
(this.initalHandlerLogger as any)._warn = this.initalHandlerLogger.warn;
|
(this.initalHandlerLogger as any)._warn = this.initalHandlerLogger.warn;
|
||||||
this.initalHandlerLogger.warn = (msg: string) => {
|
this.initalHandlerLogger.warn = (msg: string) => {
|
||||||
(this.initalHandlerLogger as any)._warn(
|
(this.initalHandlerLogger as any)._warn(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
(this.initalHandlerLogger as any)._error = this.initalHandlerLogger.error;
|
(this.initalHandlerLogger as any)._error = this.initalHandlerLogger.error;
|
||||||
this.initalHandlerLogger.error = (msg: string) => {
|
this.initalHandlerLogger.error = (msg: string) => {
|
||||||
(this.initalHandlerLogger as any)._error(
|
(this.initalHandlerLogger as any)._error(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
(this.initalHandlerLogger as any)._fatal = this.initalHandlerLogger.fatal;
|
(this.initalHandlerLogger as any)._fatal = this.initalHandlerLogger.fatal;
|
||||||
this.initalHandlerLogger.fatal = (msg: string) => {
|
this.initalHandlerLogger.fatal = (msg: string) => {
|
||||||
(this.initalHandlerLogger as any)._fatal(
|
(this.initalHandlerLogger as any)._fatal(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
(this.initalHandlerLogger as any)._debug = this.initalHandlerLogger.debug;
|
(this.initalHandlerLogger as any)._debug = this.initalHandlerLogger.debug;
|
||||||
this.initalHandlerLogger.debug = (msg: string) => {
|
this.initalHandlerLogger.debug = (msg: string) => {
|
||||||
(this.initalHandlerLogger as any)._debug(
|
(this.initalHandlerLogger as any)._debug(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
instanceCount++;
|
instanceCount++;
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
process.on("uncaughtException", (err) => {
|
||||||
this._logger.warn(
|
this._logger.warn(`An uncaught exception was caught! Error: ${err.stack}`);
|
||||||
`An uncaught exception was caught! Error: ${err.stack}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("unhandledRejection", (err) => {
|
process.on("unhandledRejection", (err) => {
|
||||||
|
@ -107,16 +90,10 @@ export class Proxy extends EventEmitter {
|
||||||
public async init() {
|
public async init() {
|
||||||
this._logger.info(`Starting ${PROXY_BRANDING} v${PROXY_VERSION}...`);
|
this._logger.info(`Starting ${PROXY_BRANDING} v${PROXY_VERSION}...`);
|
||||||
global.PROXY = this;
|
global.PROXY = this;
|
||||||
if (this.loaded)
|
if (this.loaded) throw new Error("Can't initiate if proxy instance is already initialized or is being initialized!");
|
||||||
throw new Error(
|
|
||||||
"Can't initiate if proxy instance is already initialized or is being initialized!"
|
|
||||||
);
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.packetRegistry = await loadPackets();
|
this.packetRegistry = await loadPackets();
|
||||||
this.skinServer = new EaglerSkins.SkinServer(
|
this.skinServer = new EaglerSkins.SkinServer(this, this.config.skinUrlWhitelist);
|
||||||
this,
|
|
||||||
this.config.skinUrlWhitelist
|
|
||||||
);
|
|
||||||
global.PACKET_REGISTRY = this.packetRegistry;
|
global.PACKET_REGISTRY = this.packetRegistry;
|
||||||
if (this.config.motd == "FORWARD") {
|
if (this.config.motd == "FORWARD") {
|
||||||
this._pollServer(this.config.server.host, this.config.server.port);
|
this._pollServer(this.config.server.host, this.config.server.port);
|
||||||
|
@ -136,22 +113,12 @@ export class Proxy extends EventEmitter {
|
||||||
},
|
},
|
||||||
(req, res) => this._handleNonWSRequest(req, res, this.config)
|
(req, res) => this._handleNonWSRequest(req, res, this.config)
|
||||||
)
|
)
|
||||||
.listen(
|
.listen(this.config.bindPort || 8080, this.config.bindHost || "127.0.0.1");
|
||||||
this.config.bindPort || 8080,
|
|
||||||
this.config.bindHost || "127.0.0.1"
|
|
||||||
);
|
|
||||||
this.wsServer = new WebSocketServer({
|
this.wsServer = new WebSocketServer({
|
||||||
noServer: true,
|
noServer: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.httpServer = http
|
this.httpServer = http.createServer((req, res) => this._handleNonWSRequest(req, res, this.config)).listen(this.config.bindPort || 8080, this.config.bindHost || "127.0.0.1");
|
||||||
.createServer((req, res) =>
|
|
||||||
this._handleNonWSRequest(req, res, this.config)
|
|
||||||
)
|
|
||||||
.listen(
|
|
||||||
this.config.bindPort || 8080,
|
|
||||||
this.config.bindHost || "127.0.0.1"
|
|
||||||
);
|
|
||||||
this.wsServer = new WebSocketServer({
|
this.wsServer = new WebSocketServer({
|
||||||
noServer: true,
|
noServer: true,
|
||||||
});
|
});
|
||||||
|
@ -166,73 +133,49 @@ export class Proxy extends EventEmitter {
|
||||||
try {
|
try {
|
||||||
await this._handleWSConnectionReq(r, s, h);
|
await this._handleWSConnectionReq(r, s, h);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.error(
|
this._logger.error(`Error was caught whilst trying to handle WebSocket upgrade! Error: ${err.stack ?? err}`);
|
||||||
`Error was caught whilst trying to handle WebSocket upgrade! Error: ${
|
|
||||||
err.stack ?? err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.pluginManager.emit("proxyFinishLoading", this, this.pluginManager);
|
this.pluginManager.emit("proxyFinishLoading", this, this.pluginManager);
|
||||||
this._logger.info(
|
this._logger.info(`Started WebSocket server and binded to ${this.config.bindHost} on port ${this.config.bindPort}.`);
|
||||||
`Started WebSocket server and binded to ${this.config.bindHost} on port ${this.config.bindPort}.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleNonWSRequest(
|
private _handleNonWSRequest(req: http.IncomingMessage, res: http.ServerResponse, config: Config["adapter"]) {
|
||||||
req: http.IncomingMessage,
|
const ctx: Util.Handlable = { handled: false };
|
||||||
res: http.ServerResponse,
|
this.emit("httpConnection", req, res, ctx);
|
||||||
config: Config["adapter"]
|
if (!ctx.handled) res.setHeader("Content-Type", "text/html").writeHead(426).end(UPGRADE_REQUIRED_RESPONSE);
|
||||||
) {
|
|
||||||
res
|
|
||||||
.setHeader("Content-Type", "text/html")
|
|
||||||
.writeHead(426)
|
|
||||||
.end(UPGRADE_REQUIRED_RESPONSE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly LOGIN_TIMEOUT = 30000;
|
readonly LOGIN_TIMEOUT = 30000;
|
||||||
|
|
||||||
private async _handleWSConnection(ws: WebSocket) {
|
private async _handleWSConnection(ws: WebSocket, req: http.IncomingMessage) {
|
||||||
const firstPacket = await Util.awaitPacket(ws);
|
const firstPacket = await Util.awaitPacket(ws);
|
||||||
let player: Player, handled: boolean;
|
let player: Player, handled: boolean;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
this.initalHandlerLogger.warn(
|
this.initalHandlerLogger.warn(
|
||||||
`Disconnecting client ${
|
`Disconnecting client ${
|
||||||
player
|
player ? player.username ?? `[/${(ws as any)._socket.remoteAddress}:${(ws as any)._socket.remotePort}` : `[/${(ws as any)._socket.remoteAddress}:${(ws as any)._socket.remotePort}`
|
||||||
? player.username ??
|
|
||||||
`[/${(ws as any)._socket.remoteAddress}:${
|
|
||||||
(ws as any)._socket.remotePort
|
|
||||||
}`
|
|
||||||
: `[/${(ws as any)._socket.remoteAddress}:${
|
|
||||||
(ws as any)._socket.remotePort
|
|
||||||
}`
|
|
||||||
} due to connection timing out.`
|
} due to connection timing out.`
|
||||||
);
|
);
|
||||||
if (player)
|
if (player) player.disconnect(`${Enums.ChatColor.YELLOW} Your connection timed out whilst processing handshake, please try again.`);
|
||||||
player.disconnect(
|
|
||||||
`${Enums.ChatColor.YELLOW} Your connection timed out whilst processing handshake, please try again.`
|
|
||||||
);
|
|
||||||
else ws.close();
|
else ws.close();
|
||||||
}
|
}
|
||||||
}, this.LOGIN_TIMEOUT);
|
}, this.LOGIN_TIMEOUT);
|
||||||
try {
|
try {
|
||||||
|
const ctx: Util.Handlable = { handled: false };
|
||||||
|
await this.emit("wsConnection", ws, req, ctx);
|
||||||
|
if (ctx.handled) return;
|
||||||
|
|
||||||
if (firstPacket.toString() === "Accept: MOTD") {
|
if (firstPacket.toString() === "Accept: MOTD") {
|
||||||
if (this.broadcastMotd) {
|
if (this.broadcastMotd) {
|
||||||
if ((this.broadcastMotd as any)._static) {
|
if ((this.broadcastMotd as any)._static) {
|
||||||
this.broadcastMotd.jsonMotd.data.online = this.players.size;
|
this.broadcastMotd.jsonMotd.data.online = this.players.size;
|
||||||
// sample for players
|
// sample for players
|
||||||
this.broadcastMotd.jsonMotd.data.players = [];
|
this.broadcastMotd.jsonMotd.data.players = [];
|
||||||
const playerSample = [...this.players.keys()]
|
const playerSample = [...this.players.keys()].filter((sample) => !sample.startsWith("!phs_")).slice(0, 5);
|
||||||
.filter((sample) => !sample.startsWith("!phs_"))
|
|
||||||
.slice(0, 5);
|
|
||||||
this.broadcastMotd.jsonMotd.data.players = playerSample;
|
this.broadcastMotd.jsonMotd.data.players = playerSample;
|
||||||
if (this.players.size - playerSample.length > 0)
|
if (this.players.size - playerSample.length > 0) this.broadcastMotd.jsonMotd.data.players.push(`${Enums.ChatColor.GRAY}${Enums.ChatColor.ITALIC}(and ${this.players.size - playerSample.length} more)`);
|
||||||
this.broadcastMotd.jsonMotd.data.players.push(
|
|
||||||
`${Enums.ChatColor.GRAY}${Enums.ChatColor.ITALIC}(and ${
|
|
||||||
this.players.size - playerSample.length
|
|
||||||
} more)`
|
|
||||||
);
|
|
||||||
|
|
||||||
const bufferized = this.broadcastMotd.toBuffer();
|
const bufferized = this.broadcastMotd.toBuffer();
|
||||||
ws.send(bufferized[0]);
|
ws.send(bufferized[0]);
|
||||||
|
@ -246,24 +189,15 @@ export class Proxy extends EventEmitter {
|
||||||
handled = true;
|
handled = true;
|
||||||
ws.close();
|
ws.close();
|
||||||
} else {
|
} else {
|
||||||
player = new Player(ws);
|
(ws as any).httpRequest = req;
|
||||||
|
player = new Player(ws as any);
|
||||||
const loginPacket = new CSLoginPacket().deserialize(firstPacket);
|
const loginPacket = new CSLoginPacket().deserialize(firstPacket);
|
||||||
player.state = Enums.ClientState.PRE_HANDSHAKE;
|
player.state = Enums.ClientState.PRE_HANDSHAKE;
|
||||||
if (loginPacket.gameVersion != VANILLA_PROTOCOL_VERSION) {
|
if (loginPacket.gameVersion != VANILLA_PROTOCOL_VERSION) {
|
||||||
player.disconnect(
|
player.disconnect(`${Enums.ChatColor.RED}Please connect to this proxy on EaglercraftX 1.8.9.`);
|
||||||
`${Enums.ChatColor.RED}Please connect to this proxy on EaglercraftX 1.8.9.`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
} else if (loginPacket.networkVersion != NETWORK_VERSION) {
|
} else if (loginPacket.networkVersion != NETWORK_VERSION) {
|
||||||
player.disconnect(
|
player.disconnect(`${Enums.ChatColor.RED}Your EaglercraftX version is too ${loginPacket.networkVersion > NETWORK_VERSION ? "new" : "old"}! Please ${loginPacket.networkVersion > NETWORK_VERSION ? "downgrade" : "update"}.`);
|
||||||
`${Enums.ChatColor.RED}Your EaglercraftX version is too ${
|
|
||||||
loginPacket.networkVersion > NETWORK_VERSION ? "new" : "old"
|
|
||||||
}! Please ${
|
|
||||||
loginPacket.networkVersion > NETWORK_VERSION
|
|
||||||
? "downgrade"
|
|
||||||
: "update"
|
|
||||||
}.`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -275,35 +209,22 @@ export class Proxy extends EventEmitter {
|
||||||
player.username = loginPacket.username;
|
player.username = loginPacket.username;
|
||||||
player.uuid = Util.generateUUIDFromPlayer(player.username);
|
player.uuid = Util.generateUUIDFromPlayer(player.username);
|
||||||
if (this.players.size > this.config.maxConcurrentClients) {
|
if (this.players.size > this.config.maxConcurrentClients) {
|
||||||
player.disconnect(
|
player.disconnect(`${Enums.ChatColor.YELLOW}Proxy is full! Please try again later.`);
|
||||||
`${Enums.ChatColor.YELLOW}Proxy is full! Please try again later.`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (this.players.get(player.username) != null || this.players.get(`!phs.${player.uuid}`) != null) {
|
||||||
this.players.get(player.username) != null ||
|
player.disconnect(`${Enums.ChatColor.YELLOW}Someone under your username (${player.username}) is already connected to the proxy!`);
|
||||||
this.players.get(`!phs.${player.uuid}`) != null
|
|
||||||
) {
|
|
||||||
player.disconnect(
|
|
||||||
`${Enums.ChatColor.YELLOW}Someone under your username (${player.username}) is already connected to the proxy!`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.players.set(`!phs.${player.uuid}`, player);
|
this.players.set(`!phs.${player.uuid}`, player);
|
||||||
this._logger.info(
|
this._logger.info(
|
||||||
`Player ${loginPacket.username} (${Util.generateUUIDFromPlayer(
|
`Player ${loginPacket.username} (${Util.generateUUIDFromPlayer(loginPacket.username)}) running ${loginPacket.brand}/${loginPacket.version} (net ver: ${loginPacket.networkVersion}, game ver: ${
|
||||||
loginPacket.username
|
loginPacket.gameVersion
|
||||||
)}) running ${loginPacket.brand}/${loginPacket.version} (net ver: ${
|
}) is attempting to connect!`
|
||||||
loginPacket.networkVersion
|
|
||||||
}, game ver: ${loginPacket.gameVersion}) is attempting to connect!`
|
|
||||||
);
|
);
|
||||||
player.write(new SCIdentifyPacket());
|
player.write(new SCIdentifyPacket());
|
||||||
const usernamePacket: CSUsernamePacket = (await player.read(
|
const usernamePacket: CSUsernamePacket = (await player.read(Enums.PacketId.CSUsernamePacket)) as any;
|
||||||
Enums.PacketId.CSUsernamePacket
|
|
||||||
)) as any;
|
|
||||||
if (usernamePacket.username !== player.username) {
|
if (usernamePacket.username !== player.username) {
|
||||||
player.disconnect(
|
player.disconnect(`${Enums.ChatColor.YELLOW}Failed to complete handshake. Your game version may be too old or too new.`);
|
||||||
`${Enums.ChatColor.YELLOW}Failed to complete handshake. Your game version may be too old or too new.`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const syncUuid = new SCSyncUuidPacket();
|
const syncUuid = new SCSyncUuidPacket();
|
||||||
|
@ -311,12 +232,7 @@ export class Proxy extends EventEmitter {
|
||||||
syncUuid.uuid = player.uuid;
|
syncUuid.uuid = player.uuid;
|
||||||
player.write(syncUuid);
|
player.write(syncUuid);
|
||||||
|
|
||||||
const prom = await Promise.all([
|
const prom = await Promise.all([player.read(Enums.PacketId.CSReadyPacket), (await player.read(Enums.PacketId.CSSetSkinPacket)) as CSSetSkinPacket]),
|
||||||
player.read(Enums.PacketId.CSReadyPacket),
|
|
||||||
(await player.read(
|
|
||||||
Enums.PacketId.CSSetSkinPacket
|
|
||||||
)) as CSSetSkinPacket,
|
|
||||||
]),
|
|
||||||
skin = prom[1],
|
skin = prom[1],
|
||||||
obj = new EaglerSkins.EaglerSkin();
|
obj = new EaglerSkins.EaglerSkin();
|
||||||
obj.owner = player;
|
obj.owner = player;
|
||||||
|
@ -331,45 +247,31 @@ export class Proxy extends EventEmitter {
|
||||||
player.initListeners();
|
player.initListeners();
|
||||||
this._bindListenersToPlayer(player);
|
this._bindListenersToPlayer(player);
|
||||||
player.state = Enums.ClientState.POST_HANDSHAKE;
|
player.state = Enums.ClientState.POST_HANDSHAKE;
|
||||||
this._logger.info(
|
this._logger.info(`Handshake Success! Connecting player ${player.username} to server...`);
|
||||||
`Handshake Success! Connecting player ${player.username} to server...`
|
|
||||||
);
|
|
||||||
handled = true;
|
handled = true;
|
||||||
await player.connect({
|
await player.connect({
|
||||||
host: this.config.server.host,
|
host: this.config.server.host,
|
||||||
port: this.config.server.port,
|
port: this.config.server.port,
|
||||||
username: player.username,
|
username: player.username,
|
||||||
});
|
});
|
||||||
this._logger.info(
|
this._logger.info(`Player ${player.username} successfully connected to server.`);
|
||||||
`Player ${player.username} successfully connected to server.`
|
|
||||||
);
|
|
||||||
this.emit("playerConnect", player);
|
this.emit("playerConnect", player);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.initalHandlerLogger.warn(
|
this.initalHandlerLogger.warn(`Error occurred whilst handling handshake: ${err.stack ?? err}`);
|
||||||
`Error occurred whilst handling handshake: ${err.stack ?? err}`
|
|
||||||
);
|
|
||||||
handled = true;
|
handled = true;
|
||||||
ws.close();
|
ws.close();
|
||||||
if (player && player.uuid && this.players.has(`!phs.${player.uuid}`))
|
if (player && player.uuid && this.players.has(`!phs.${player.uuid}`)) this.players.delete(`!phs.${player.uuid}`);
|
||||||
this.players.delete(`!phs.${player.uuid}`);
|
if (player && player.uuid && this.players.has(player.username)) this.players.delete(player.username);
|
||||||
if (player && player.uuid && this.players.has(player.username))
|
|
||||||
this.players.delete(player.username);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _bindListenersToPlayer(player: Player) {
|
private _bindListenersToPlayer(player: Player) {
|
||||||
let sentDisconnectMsg = false;
|
let sentDisconnectMsg = false;
|
||||||
player.on("disconnect", () => {
|
player.on("disconnect", () => {
|
||||||
if (this.players.has(player.username))
|
if (this.players.has(player.username)) this.players.delete(player.username);
|
||||||
this.players.delete(player.username);
|
this.initalHandlerLogger.info(`DISCONNECT ${player.username} <=> DISCONNECTED`);
|
||||||
this.initalHandlerLogger.info(
|
if (!sentDisconnectMsg) this._logger.info(`Player ${player.username} (${player.uuid}) disconnected from the proxy server.`);
|
||||||
`DISCONNECT ${player.username} <=> DISCONNECTED`
|
|
||||||
);
|
|
||||||
if (!sentDisconnectMsg)
|
|
||||||
this._logger.info(
|
|
||||||
`Player ${player.username} (${player.uuid}) disconnected from the proxy server.`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
player.on("proxyPacket", async (packet) => {
|
player.on("proxyPacket", async (packet) => {
|
||||||
if (packet.packetId == Enums.PacketId.CSChannelMessagePacket) {
|
if (packet.packetId == Enums.PacketId.CSChannelMessagePacket) {
|
||||||
|
@ -379,23 +281,15 @@ export class Proxy extends EventEmitter {
|
||||||
await this.skinServer.handleRequest(msg, player);
|
await this.skinServer.handleRequest(msg, player);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.error(
|
this._logger.error(`Failed to process channel message packet! Error: ${err.stack || err}`);
|
||||||
`Failed to process channel message packet! Error: ${
|
|
||||||
err.stack || err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on("switchServer", (client) => {
|
player.on("switchServer", (client) => {
|
||||||
this.initalHandlerLogger.info(
|
this.initalHandlerLogger.info(`SWITCH_SERVER ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`);
|
||||||
`SWITCH_SERVER ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
player.on("joinServer", (client) => {
|
player.on("joinServer", (client) => {
|
||||||
this.initalHandlerLogger.info(
|
this.initalHandlerLogger.info(`SERVER_CONNECTED ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`);
|
||||||
`SERVER_CONNECTED ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,62 +298,33 @@ export class Proxy extends EventEmitter {
|
||||||
private _pollServer(host: string, port: number, interval?: number) {
|
private _pollServer(host: string, port: number, interval?: number) {
|
||||||
(async () => {
|
(async () => {
|
||||||
while (true) {
|
while (true) {
|
||||||
const motd = await Motd.MOTD.generateMOTDFromPing(host, port).catch(
|
const motd = await Motd.MOTD.generateMOTDFromPing(host, port).catch((err) => {
|
||||||
(err) => {
|
this._logger.warn(`Error polling ${host}:${port} for MOTD: ${err.stack ?? err}`);
|
||||||
this._logger.warn(
|
});
|
||||||
`Error polling ${host}:${port} for MOTD: ${err.stack ?? err}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (motd) this.broadcastMotd = motd;
|
if (motd) this.broadcastMotd = motd;
|
||||||
await new Promise((res) =>
|
await new Promise((res) => setTimeout(res, interval ?? Proxy.POLL_INTERVAL));
|
||||||
setTimeout(res, interval ?? Proxy.POLL_INTERVAL)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleWSConnectionReq(
|
private async _handleWSConnectionReq(req: http.IncomingMessage, socket: Duplex, head: Buffer) {
|
||||||
req: http.IncomingMessage,
|
const origin = req.headers.origin == null || req.headers.origin == "null" ? null : req.headers.origin;
|
||||||
socket: Duplex,
|
|
||||||
head: Buffer
|
|
||||||
) {
|
|
||||||
const origin =
|
|
||||||
req.headers.origin == null || req.headers.origin == "null"
|
|
||||||
? null
|
|
||||||
: req.headers.origin;
|
|
||||||
if (!this.config.origins.allowOfflineDownloads && origin == null) {
|
if (!this.config.origins.allowOfflineDownloads && origin == null) {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (this.config.origins.originBlacklist != null && this.config.origins.originBlacklist.some((host) => Util.areDomainsEqual(host, origin))) {
|
||||||
this.config.origins.originBlacklist != null &&
|
|
||||||
this.config.origins.originBlacklist.some((host) =>
|
|
||||||
Util.areDomainsEqual(host, origin)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (this.config.origins.originWhitelist != null && !this.config.origins.originWhitelist.some((host) => Util.areDomainsEqual(host, origin))) {
|
||||||
this.config.origins.originWhitelist != null &&
|
|
||||||
!this.config.origins.originWhitelist.some((host) =>
|
|
||||||
Util.areDomainsEqual(host, origin)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.wsServer.handleUpgrade(req, socket, head, (ws) =>
|
await this.wsServer.handleUpgrade(req, socket, head, (ws) => this._handleWSConnection(ws, req));
|
||||||
this._handleWSConnection(ws)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.error(
|
this._logger.error(`Error was caught whilst trying to handle WebSocket connection request! Error: ${err.stack ?? err}`);
|
||||||
`Error was caught whilst trying to handle WebSocket connection request! Error: ${
|
|
||||||
err.stack ?? err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -475,13 +340,12 @@ export class Proxy extends EventEmitter {
|
||||||
interface ProxyEvents {
|
interface ProxyEvents {
|
||||||
playerConnect: (player: Player) => void;
|
playerConnect: (player: Player) => void;
|
||||||
playerDisconnect: (player: Player) => void;
|
playerDisconnect: (player: Player) => void;
|
||||||
|
|
||||||
|
httpConnection: (req: http.IncomingMessage, res: http.ServerResponse, ctx: Util.Handlable) => void;
|
||||||
|
wsConnection: (ws: WebSocket, req: http.IncomingMessage, ctx: Util.Handlable) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface Proxy {
|
export declare interface Proxy {
|
||||||
on<U extends keyof ProxyEvents>(event: U, listener: ProxyEvents[U]): this;
|
on<U extends keyof ProxyEvents>(event: U, listener: ProxyEvents[U]): this;
|
||||||
|
emit<U extends keyof ProxyEvents>(event: U, ...args: Parameters<ProxyEvents[U]>): boolean;
|
||||||
emit<U extends keyof ProxyEvents>(
|
|
||||||
event: U,
|
|
||||||
...args: Parameters<ProxyEvents[U]>
|
|
||||||
): boolean;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,26 +37,18 @@ export namespace Util {
|
||||||
export function uuidStringToBuffer(uuid: string): Buffer {
|
export function uuidStringToBuffer(uuid: string): Buffer {
|
||||||
if (!uuid) return Buffer.alloc(16); // Return empty buffer
|
if (!uuid) return Buffer.alloc(16); // Return empty buffer
|
||||||
const hexStr = uuid.replace(/-/g, "");
|
const hexStr = uuid.replace(/-/g, "");
|
||||||
if (uuid.length != 36 || hexStr.length != 32)
|
if (uuid.length != 36 || hexStr.length != 32) throw new Error(`Invalid UUID string: ${uuid}`);
|
||||||
throw new Error(`Invalid UUID string: ${uuid}`);
|
|
||||||
return Buffer.from(hexStr, "hex");
|
return Buffer.from(hexStr, "hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uuidBufferToString(buffer: Buffer): string {
|
export function uuidBufferToString(buffer: Buffer): string {
|
||||||
if (buffer.length != 16)
|
if (buffer.length != 16) throw new Error(`Invalid buffer length for uuid: ${buffer.length}`);
|
||||||
throw new Error(`Invalid buffer length for uuid: ${buffer.length}`);
|
|
||||||
if (buffer.equals(Buffer.alloc(16))) return null; // If buffer is all zeros, return null
|
if (buffer.equals(Buffer.alloc(16))) return null; // If buffer is all zeros, return null
|
||||||
const str = buffer.toString("hex");
|
const str = buffer.toString("hex");
|
||||||
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(
|
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`;
|
||||||
12,
|
|
||||||
16
|
|
||||||
)}-${str.slice(16, 20)}-${str.slice(20)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function awaitPacket(
|
export function awaitPacket(ws: WebSocket, filter?: (msg: Buffer) => boolean): Promise<Buffer> {
|
||||||
ws: WebSocket,
|
|
||||||
filter?: (msg: Buffer) => boolean
|
|
||||||
): Promise<Buffer> {
|
|
||||||
return new Promise<Buffer>((res, rej) => {
|
return new Promise<Buffer>((res, rej) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const msgCb = (msg: any) => {
|
const msgCb = (msg: any) => {
|
||||||
|
@ -64,17 +56,13 @@ export namespace Util {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
ws.removeListener("message", msgCb);
|
ws.removeListener("message", msgCb);
|
||||||
ws.removeListener("close", discon);
|
ws.removeListener("close", discon);
|
||||||
ws.setMaxListeners(
|
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
|
||||||
);
|
|
||||||
res(msg);
|
res(msg);
|
||||||
} else if (filter == null) {
|
} else if (filter == null) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
ws.removeListener("message", msgCb);
|
ws.removeListener("message", msgCb);
|
||||||
ws.removeListener("close", discon);
|
ws.removeListener("close", discon);
|
||||||
ws.setMaxListeners(
|
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
|
||||||
);
|
|
||||||
res(msg);
|
res(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -82,9 +70,7 @@ export namespace Util {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
ws.removeListener("message", msgCb);
|
ws.removeListener("message", msgCb);
|
||||||
ws.removeListener("close", discon);
|
ws.removeListener("close", discon);
|
||||||
ws.setMaxListeners(
|
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
|
||||||
);
|
|
||||||
rej("Connection closed");
|
rej("Connection closed");
|
||||||
};
|
};
|
||||||
ws.setMaxListeners(ws.getMaxListeners() + 2);
|
ws.setMaxListeners(ws.getMaxListeners() + 2);
|
||||||
|
@ -93,9 +79,7 @@ export namespace Util {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ws.removeListener("message", msgCb);
|
ws.removeListener("message", msgCb);
|
||||||
ws.removeListener("close", discon);
|
ws.removeListener("close", discon);
|
||||||
ws.setMaxListeners(
|
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
|
||||||
);
|
|
||||||
rej("Timed out");
|
rej("Timed out");
|
||||||
}, 10000);
|
}, 10000);
|
||||||
});
|
});
|
||||||
|
@ -104,57 +88,28 @@ export namespace Util {
|
||||||
export function validateUsername(user: string): void | never {
|
export function validateUsername(user: string): void | never {
|
||||||
if (user.length > 20) throw new Error("Username is too long!");
|
if (user.length > 20) throw new Error("Username is too long!");
|
||||||
if (user.length < 3) throw new Error("Username is too short!");
|
if (user.length < 3) throw new Error("Username is too short!");
|
||||||
if (!!user.match(USERNAME_REGEX))
|
if (!!user.match(USERNAME_REGEX)) throw new Error("Invalid username. Username can only contain alphanumeric characters, and the underscore (_) character.");
|
||||||
throw new Error(
|
|
||||||
"Invalid username. Username can only contain alphanumeric characters, and the underscore (_) character."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function areDomainsEqual(d1: string, d2: string): boolean {
|
export function areDomainsEqual(d1: string, d2: string): boolean {
|
||||||
if (d1.endsWith("*."))
|
if (d1.endsWith("*.")) d1 = d1.replace("*.", "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION.");
|
||||||
d1 = d1.replace(
|
|
||||||
"*.",
|
|
||||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION."
|
|
||||||
);
|
|
||||||
const parseResult1 = parseDomain(d1),
|
const parseResult1 = parseDomain(d1),
|
||||||
parseResult2 = parseDomain(d2);
|
parseResult2 = parseDomain(d2);
|
||||||
if (
|
if (parseResult1.type != ParseResultType.Invalid && parseResult2.type != ParseResultType.Invalid) {
|
||||||
parseResult1.type != ParseResultType.Invalid &&
|
if (parseResult1.type == ParseResultType.Ip && parseResult2.type == ParseResultType.Ip) {
|
||||||
parseResult2.type != ParseResultType.Invalid
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
parseResult1.type == ParseResultType.Ip &&
|
|
||||||
parseResult2.type == ParseResultType.Ip
|
|
||||||
) {
|
|
||||||
return parseResult1.hostname == parseResult2.hostname ? true : false;
|
return parseResult1.hostname == parseResult2.hostname ? true : false;
|
||||||
} else if (
|
} else if (parseResult1.type == ParseResultType.Listed && parseResult2.type == ParseResultType.Listed) {
|
||||||
parseResult1.type == ParseResultType.Listed &&
|
if (parseResult1.subDomains[0] == "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION") {
|
||||||
parseResult2.type == ParseResultType.Listed
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
parseResult1.subDomains[0] ==
|
|
||||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION"
|
|
||||||
) {
|
|
||||||
// wildcard
|
// wildcard
|
||||||
const domainPlusTld1 =
|
const domainPlusTld1 = parseResult1.domain + ("." + parseResult1.topLevelDomains.join("."));
|
||||||
parseResult1.domain +
|
const domainPlusTld2 = parseResult2.domain + ("." + parseResult2.topLevelDomains.join("."));
|
||||||
("." + parseResult1.topLevelDomains.join("."));
|
|
||||||
const domainPlusTld2 =
|
|
||||||
parseResult2.domain +
|
|
||||||
("." + parseResult2.topLevelDomains.join("."));
|
|
||||||
return domainPlusTld1 == domainPlusTld2 ? true : false;
|
return domainPlusTld1 == domainPlusTld2 ? true : false;
|
||||||
} else {
|
} else {
|
||||||
// no wildcard
|
// no wildcard
|
||||||
return d1 == d2 ? true : false;
|
return d1 == d2 ? true : false;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (parseResult1.type == ParseResultType.NotListed && parseResult2.type == ParseResultType.NotListed) {
|
||||||
parseResult1.type == ParseResultType.NotListed &&
|
if (parseResult1.labels[0] == "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION") {
|
||||||
parseResult2.type == ParseResultType.NotListed
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
parseResult1.labels[0] ==
|
|
||||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION"
|
|
||||||
) {
|
|
||||||
// wildcard
|
// wildcard
|
||||||
const domainPlusTld1 = parseResult1.labels.slice(2).join(".");
|
const domainPlusTld1 = parseResult1.labels.slice(2).join(".");
|
||||||
const domainPlusTld2 = parseResult1.labels.slice(2).join(".");
|
const domainPlusTld2 = parseResult1.labels.slice(2).join(".");
|
||||||
|
@ -163,15 +118,8 @@ export namespace Util {
|
||||||
// no wildcard
|
// no wildcard
|
||||||
return d1 == d2 ? true : false;
|
return d1 == d2 ? true : false;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (parseResult1.type == ParseResultType.Reserved && parseResult2.type == ParseResultType.Reserved) {
|
||||||
parseResult1.type == ParseResultType.Reserved &&
|
if (parseResult1.hostname == "" && parseResult1.hostname === parseResult2.hostname) return true;
|
||||||
parseResult2.type == ParseResultType.Reserved
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
parseResult1.hostname == "" &&
|
|
||||||
parseResult1.hostname === parseResult2.hostname
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
else {
|
else {
|
||||||
// uncertain, fallback to exact hostname matching
|
// uncertain, fallback to exact hostname matching
|
||||||
return d1 == d2 ? true : false;
|
return d1 == d2 ? true : false;
|
||||||
|
@ -229,10 +177,7 @@ export namespace Util {
|
||||||
flags: number;
|
flags: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generatePositionPacket(
|
export function generatePositionPacket(currentPos: PlayerPosition, newPos: PositionPacket): PositionPacket {
|
||||||
currentPos: PlayerPosition,
|
|
||||||
newPos: PositionPacket
|
|
||||||
): PositionPacket {
|
|
||||||
const DEFAULT_RELATIVITY = 0x01; // relative to X-axis
|
const DEFAULT_RELATIVITY = 0x01; // relative to X-axis
|
||||||
const newPosPacket = {
|
const newPosPacket = {
|
||||||
x: newPos.x - currentPos.x * 2,
|
x: newPos.x - currentPos.x * 2,
|
||||||
|
@ -244,4 +189,8 @@ export namespace Util {
|
||||||
};
|
};
|
||||||
return newPosPacket;
|
return newPosPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Handlable = {
|
||||||
|
handled: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,7 @@ import { EaglerSkins } from "../skins/EaglerSkins.js";
|
||||||
import { BungeeUtil } from "../BungeeUtil.js";
|
import { BungeeUtil } from "../BungeeUtil.js";
|
||||||
|
|
||||||
export class PluginManager extends EventEmitter {
|
export class PluginManager extends EventEmitter {
|
||||||
public plugins: Map<
|
public plugins: Map<string, { exports: any; metadata: PluginLoaderTypes.PluginMetadataPathed }>;
|
||||||
string,
|
|
||||||
{ exports: any; metadata: PluginLoaderTypes.PluginMetadataPathed }
|
|
||||||
>;
|
|
||||||
public proxy: Proxy;
|
public proxy: Proxy;
|
||||||
|
|
||||||
public Logger: typeof Logger = Logger;
|
public Logger: typeof Logger = Logger;
|
||||||
|
@ -59,179 +56,67 @@ export class PluginManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
pluginsString = pluginsString.substring(0, pluginsString.length - 1);
|
pluginsString = pluginsString.substring(0, pluginsString.length - 1);
|
||||||
this._logger.info(`Found ${pluginMeta.size} plugin(s): ${pluginsString}`);
|
this._logger.info(`Found ${pluginMeta.size} plugin(s): ${pluginsString}`);
|
||||||
if(pluginMeta.size !== 0){
|
if (pluginMeta.size !== 0) {
|
||||||
this._logger.info(`Loading ${pluginMeta.size} plugin(s)...`);
|
this._logger.info(`Loading ${pluginMeta.size} plugin(s)...`);
|
||||||
const successLoadCount = await this._loadPlugins(
|
const successLoadCount = await this._loadPlugins(pluginMeta, this._getLoadOrder(pluginMeta));
|
||||||
pluginMeta,
|
|
||||||
this._getLoadOrder(pluginMeta)
|
|
||||||
);
|
|
||||||
this._logger.info(`Successfully loaded ${successLoadCount} plugin(s).`);
|
this._logger.info(`Successfully loaded ${successLoadCount} plugin(s).`);
|
||||||
}
|
}
|
||||||
this.emit("pluginsFinishLoading", this);
|
this.emit("pluginsFinishLoading", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _findPlugins(
|
private async _findPlugins(dir: string): Promise<Map<string, PluginLoaderTypes.PluginMetadataPathed>> {
|
||||||
dir: string
|
|
||||||
): Promise<Map<string, PluginLoaderTypes.PluginMetadataPathed>> {
|
|
||||||
const ret: Map<string, PluginLoaderTypes.PluginMetadataPathed> = new Map();
|
const ret: Map<string, PluginLoaderTypes.PluginMetadataPathed> = new Map();
|
||||||
const lsRes = (await Promise.all(
|
const lsRes = (await Promise.all((await fs.readdir(dir)).filter((ent) => !ent.endsWith(".disabled")).map(async (res) => [pathUtil.join(dir, res), await fs.stat(pathUtil.join(dir, res))]))) as [string, Stats][];
|
||||||
(await fs.readdir(dir))
|
|
||||||
.filter((ent) => !ent.endsWith(".disabled"))
|
|
||||||
.map(async (res) => [
|
|
||||||
pathUtil.join(dir, res),
|
|
||||||
await fs.stat(pathUtil.join(dir, res)),
|
|
||||||
])
|
|
||||||
)) as [string, Stats][];
|
|
||||||
for (const [path, details] of lsRes) {
|
for (const [path, details] of lsRes) {
|
||||||
if (details.isFile()) {
|
if (details.isFile()) {
|
||||||
if (path.endsWith(".jar")) {
|
if (path.endsWith(".jar")) {
|
||||||
this._logger.warn(`Non-EaglerProxy plugin found! (${path})`);
|
this._logger.warn(`Non-EaglerProxy plugin found! (${path})`);
|
||||||
this._logger.warn(
|
this._logger.warn(`BungeeCord plugins are NOT supported! Only custom EaglerProxy plugins are allowed.`);
|
||||||
`BungeeCord plugins are NOT supported! Only custom EaglerProxy plugins are allowed.`
|
|
||||||
);
|
|
||||||
} else if (path.endsWith(".zip")) {
|
} else if (path.endsWith(".zip")) {
|
||||||
this._logger.warn(`.zip file found in plugin directory! (${path})`);
|
this._logger.warn(`.zip file found in plugin directory! (${path})`);
|
||||||
this._logger.warn(
|
this._logger.warn(`A .zip file was found in the plugins directory! Perhaps you forgot to unzip it?`);
|
||||||
`A .zip file was found in the plugins directory! Perhaps you forgot to unzip it?`
|
} else this._logger.debug(`Skipping file found in plugin folder: ${path}`);
|
||||||
);
|
|
||||||
} else
|
|
||||||
this._logger.debug(`Skipping file found in plugin folder: ${path}`);
|
|
||||||
} else {
|
} else {
|
||||||
const metadataPath = pathUtil.resolve(
|
const metadataPath = pathUtil.resolve(pathUtil.join(path, "metadata.json"));
|
||||||
pathUtil.join(path, "metadata.json")
|
|
||||||
);
|
|
||||||
let metadata: PluginLoaderTypes.PluginMetadata;
|
let metadata: PluginLoaderTypes.PluginMetadata;
|
||||||
try {
|
try {
|
||||||
const file = await fs.readFile(metadataPath);
|
const file = await fs.readFile(metadataPath);
|
||||||
metadata = JSON.parse(file.toString());
|
metadata = JSON.parse(file.toString());
|
||||||
// do some type checking
|
// do some type checking
|
||||||
if (typeof metadata.name != "string")
|
if (typeof metadata.name != "string") throw new TypeError("<metadata>.name is either null or not of a string type!");
|
||||||
throw new TypeError(
|
if (typeof metadata.id != "string") throw new TypeError("<metadata>.id is either null or not of a string type!");
|
||||||
"<metadata>.name is either null or not of a string type!"
|
if (/ /gm.test(metadata.id)) throw new Error(`<metadata>.id contains whitespace!`);
|
||||||
);
|
if (!semver.valid(metadata.version)) throw new Error("<metadata>.version is either null, not a string, or is not a valid SemVer!");
|
||||||
if (typeof metadata.id != "string")
|
if (typeof metadata.entry_point != "string") throw new TypeError("<metadata>.entry_point is either null or not a string!");
|
||||||
throw new TypeError(
|
if (!metadata.entry_point.endsWith(".js")) throw new Error(`<metadata>.entry_point (${metadata.entry_point}) references a non-JavaScript file!`);
|
||||||
"<metadata>.id is either null or not of a string type!"
|
if (!(await Util.fsExists(pathUtil.resolve(path, metadata.entry_point)))) throw new Error(`<metadata>.entry_point (${metadata.entry_point}) references a non-existent file!`);
|
||||||
);
|
if (metadata.requirements instanceof Array == false) throw new TypeError("<metadata>.requirements is either null or not an array!");
|
||||||
if (/ /gm.test(metadata.id))
|
|
||||||
throw new Error(`<metadata>.id contains whitespace!`);
|
|
||||||
if (!semver.valid(metadata.version))
|
|
||||||
throw new Error(
|
|
||||||
"<metadata>.version is either null, not a string, or is not a valid SemVer!"
|
|
||||||
);
|
|
||||||
if (typeof metadata.entry_point != "string")
|
|
||||||
throw new TypeError(
|
|
||||||
"<metadata>.entry_point is either null or not a string!"
|
|
||||||
);
|
|
||||||
if (!metadata.entry_point.endsWith(".js"))
|
|
||||||
throw new Error(
|
|
||||||
`<metadata>.entry_point (${metadata.entry_point}) references a non-JavaScript file!`
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
!(await Util.fsExists(pathUtil.resolve(path, metadata.entry_point)))
|
|
||||||
)
|
|
||||||
throw new Error(
|
|
||||||
`<metadata>.entry_point (${metadata.entry_point}) references a non-existent file!`
|
|
||||||
);
|
|
||||||
if (metadata.requirements instanceof Array == false)
|
|
||||||
throw new TypeError(
|
|
||||||
"<metadata>.requirements is either null or not an array!"
|
|
||||||
);
|
|
||||||
for (const requirement of metadata.requirements as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
for (const requirement of metadata.requirements as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
||||||
if (typeof requirement != "object" || requirement == null)
|
if (typeof requirement != "object" || requirement == null) throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}] is either null or not an object!`);
|
||||||
throw new TypeError(
|
if (typeof requirement.id != "string") throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].id is either null or not a string!`);
|
||||||
`<metadata>.requirements[${(
|
if (/ /gm.test(requirement.id)) throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].id contains whitespace!`);
|
||||||
metadata.requirements as any
|
if (semver.validRange(requirement.version) == null && requirement.version != "any")
|
||||||
).indexOf(requirement)}] is either null or not an object!`
|
throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].version is either null or not a valid SemVer!`);
|
||||||
);
|
|
||||||
if (typeof requirement.id != "string")
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.requirements[${(
|
|
||||||
metadata.requirements as any
|
|
||||||
).indexOf(requirement)}].id is either null or not a string!`
|
|
||||||
);
|
|
||||||
if (/ /gm.test(requirement.id))
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.requirements[${(
|
|
||||||
metadata.requirements as any
|
|
||||||
).indexOf(requirement)}].id contains whitespace!`
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
semver.validRange(requirement.version) == null &&
|
|
||||||
requirement.version != "any"
|
|
||||||
)
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.requirements[${(
|
|
||||||
metadata.requirements as any
|
|
||||||
).indexOf(
|
|
||||||
requirement
|
|
||||||
)}].version is either null or not a valid SemVer!`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (metadata.load_after instanceof Array == false)
|
if (metadata.load_after instanceof Array == false) throw new TypeError("<metadata>.load_after is either null or not an array!");
|
||||||
throw new TypeError(
|
|
||||||
"<metadata>.load_after is either null or not an array!"
|
|
||||||
);
|
|
||||||
for (const loadReq of metadata.load_after as string[]) {
|
for (const loadReq of metadata.load_after as string[]) {
|
||||||
if (typeof loadReq != "string")
|
if (typeof loadReq != "string") throw new TypeError(`<metadata>.load_after[${(metadata.load_after as any).indexOf(loadReq)}] is either null, or not a valid ID!`);
|
||||||
throw new TypeError(
|
if (/ /gm.test(loadReq)) throw new TypeError(`<metadata>.load_after[${(metadata.load_after as any).indexOf(loadReq)}] contains whitespace!`);
|
||||||
`<metadata>.load_after[${(metadata.load_after as any).indexOf(
|
|
||||||
loadReq
|
|
||||||
)}] is either null, or not a valid ID!`
|
|
||||||
);
|
|
||||||
if (/ /gm.test(loadReq))
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.load_after[${(metadata.load_after as any).indexOf(
|
|
||||||
loadReq
|
|
||||||
)}] contains whitespace!`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (metadata.incompatibilities instanceof Array == false)
|
if (metadata.incompatibilities instanceof Array == false) throw new TypeError("<metadata>.incompatibilities is either null or not an array!");
|
||||||
throw new TypeError(
|
|
||||||
"<metadata>.incompatibilities is either null or not an array!"
|
|
||||||
);
|
|
||||||
for (const incompatibility of metadata.incompatibilities as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
for (const incompatibility of metadata.incompatibilities as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
||||||
if (typeof incompatibility != "object" || incompatibility == null)
|
if (typeof incompatibility != "object" || incompatibility == null) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}] is either null or not an object!`);
|
||||||
throw new TypeError(
|
if (typeof incompatibility.id != "string") throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].id is either null or not a string!`);
|
||||||
`<metadata>.incompatibilities[${(
|
if (/ /gm.test(incompatibility.id)) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].id contains whitespace!`);
|
||||||
metadata.load_after as any
|
if (semver.validRange(incompatibility.version) == null) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].version is either null or not a valid SemVer!`);
|
||||||
).indexOf(incompatibility)}] is either null or not an object!`
|
|
||||||
);
|
|
||||||
if (typeof incompatibility.id != "string")
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.incompatibilities[${(
|
|
||||||
metadata.load_after as any
|
|
||||||
).indexOf(incompatibility)}].id is either null or not a string!`
|
|
||||||
);
|
|
||||||
if (/ /gm.test(incompatibility.id))
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.incompatibilities[${(
|
|
||||||
metadata.load_after as any
|
|
||||||
).indexOf(incompatibility)}].id contains whitespace!`
|
|
||||||
);
|
|
||||||
if (semver.validRange(incompatibility.version) == null)
|
|
||||||
throw new TypeError(
|
|
||||||
`<metadata>.incompatibilities[${(
|
|
||||||
metadata.load_after as any
|
|
||||||
).indexOf(
|
|
||||||
incompatibility
|
|
||||||
)}].version is either null or not a valid SemVer!`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (ret.has(metadata.id))
|
if (ret.has(metadata.id)) throw new Error(`Duplicate plugin ID detected: ${metadata.id}. Are there duplicate plugins in the plugin folder?`);
|
||||||
throw new Error(
|
|
||||||
`Duplicate plugin ID detected: ${metadata.id}. Are there duplicate plugins in the plugin folder?`
|
|
||||||
);
|
|
||||||
ret.set(metadata.id, {
|
ret.set(metadata.id, {
|
||||||
path: pathUtil.resolve(path),
|
path: pathUtil.resolve(path),
|
||||||
...metadata,
|
...metadata,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.warn(
|
this._logger.warn(`Failed to load plugin metadata file at ${metadataPath}: ${err.stack ?? err}`);
|
||||||
`Failed to load plugin metadata file at ${metadataPath}: ${
|
|
||||||
err.stack ?? err
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
this._logger.warn("This plugin will skip loading due to an error.");
|
this._logger.warn("This plugin will skip loading due to an error.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,27 +124,16 @@ export class PluginManager extends EventEmitter {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _validatePluginList(
|
private async _validatePluginList(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>) {
|
||||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>
|
|
||||||
) {
|
|
||||||
for (const [id, plugin] of plugins) {
|
for (const [id, plugin] of plugins) {
|
||||||
for (const req of plugin.requirements) {
|
for (const req of plugin.requirements) {
|
||||||
if (
|
if (!plugins.has(req.id) && req.id != "eaglerproxy" && !req.id.startsWith("module:")) {
|
||||||
!plugins.has(req.id) &&
|
this._logger.fatal(`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires plugin ${req.id}@${req.version}, but it is not found!`);
|
||||||
req.id != "eaglerproxy" &&
|
|
||||||
!req.id.startsWith("module:")
|
|
||||||
) {
|
|
||||||
this._logger.fatal(
|
|
||||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires plugin ${req.id}@${req.version}, but it is not found!`
|
|
||||||
);
|
|
||||||
this._logger.fatal("Loading has halted due to missing dependencies.");
|
this._logger.fatal("Loading has halted due to missing dependencies.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (req.id == "eaglerproxy") {
|
if (req.id == "eaglerproxy") {
|
||||||
if (
|
if (!semver.satisfies(PROXY_VERSION, req.version) && req.version != "any") {
|
||||||
!semver.satisfies(PROXY_VERSION, req.version) &&
|
|
||||||
req.version != "any"
|
|
||||||
) {
|
|
||||||
this._logger.fatal(
|
this._logger.fatal(
|
||||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a proxy version that satisfies the SemVer requirement ${req.version}, but the proxy version is ${PROXY_VERSION} and does not satisfy the SemVer requirement!`
|
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a proxy version that satisfies the SemVer requirement ${req.version}, but the proxy version is ${PROXY_VERSION} and does not satisfy the SemVer requirement!`
|
||||||
);
|
);
|
||||||
|
@ -272,32 +146,19 @@ export class PluginManager extends EventEmitter {
|
||||||
await import(moduleName);
|
await import(moduleName);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code == "ERR_MODULE_NOT_FOUND") {
|
if (err.code == "ERR_MODULE_NOT_FOUND") {
|
||||||
|
this._logger.fatal(`Plugin ${plugin.name}@${plugin.version} requires NPM module ${moduleName}${req.version == "any" ? "" : `@${req.version}`} to be installed, but it is not found!`);
|
||||||
this._logger.fatal(
|
this._logger.fatal(
|
||||||
`Plugin ${plugin.name}@${
|
`Please install this missing package by running "npm install ${moduleName}${req.version == "any" ? "" : `@${req.version}`}". If you're using yarn, run "yarn add ${moduleName}${
|
||||||
plugin.version
|
|
||||||
} requires NPM module ${moduleName}${
|
|
||||||
req.version == "any" ? "" : `@${req.version}`
|
|
||||||
} to be installed, but it is not found!`
|
|
||||||
);
|
|
||||||
this._logger.fatal(
|
|
||||||
`Please install this missing package by running "npm install ${moduleName}${
|
|
||||||
req.version == "any" ? "" : `@${req.version}`
|
|
||||||
}". If you're using yarn, run "yarn add ${moduleName}${
|
|
||||||
req.version == "any" ? "" : `@${req.version}`
|
req.version == "any" ? "" : `@${req.version}`
|
||||||
}" instead.`
|
}" instead.`
|
||||||
);
|
);
|
||||||
this._logger.fatal(
|
this._logger.fatal("Loading has halted due to dependency issues.");
|
||||||
"Loading has halted due to dependency issues."
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let dep = plugins.get(req.id);
|
let dep = plugins.get(req.id);
|
||||||
if (
|
if (!semver.satisfies(dep.version, req.version) && req.version != "any") {
|
||||||
!semver.satisfies(dep.version, req.version) &&
|
|
||||||
req.version != "any"
|
|
||||||
) {
|
|
||||||
this._logger.fatal(
|
this._logger.fatal(
|
||||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a version of plugin ${dep.name} that satisfies the SemVer requirement ${req.version}, but the plugin ${dep.name}'s version is ${dep.version} and does not satisfy the SemVer requirement!`
|
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a version of plugin ${dep.name} that satisfies the SemVer requirement ${req.version}, but the plugin ${dep.name}'s version is ${dep.version} and does not satisfy the SemVer requirement!`
|
||||||
);
|
);
|
||||||
|
@ -313,19 +174,13 @@ export class PluginManager extends EventEmitter {
|
||||||
this._logger.fatal(
|
this._logger.fatal(
|
||||||
`Error whilst loading plugins: Plugin incompatibility found! Plugin ${plugin.name}@${plugin.version} is incompatible with ${plugin_incomp.name}@${plugin_incomp.version} as it satisfies the SemVer requirement of ${incomp.version}!`
|
`Error whilst loading plugins: Plugin incompatibility found! Plugin ${plugin.name}@${plugin.version} is incompatible with ${plugin_incomp.name}@${plugin_incomp.version} as it satisfies the SemVer requirement of ${incomp.version}!`
|
||||||
);
|
);
|
||||||
this._logger.fatal(
|
this._logger.fatal("Loading has halted due to plugin incompatibility issues.");
|
||||||
"Loading has halted due to plugin incompatibility issues."
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} else if (incomp.id == "eaglerproxy") {
|
} else if (incomp.id == "eaglerproxy") {
|
||||||
if (semver.satisfies(PROXY_VERSION, incomp.version)) {
|
if (semver.satisfies(PROXY_VERSION, incomp.version)) {
|
||||||
this._logger.fatal(
|
this._logger.fatal(`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} is incompatible with proxy version ${PROXY_VERSION} as it satisfies the SemVer requirement of ${incomp.version}!`);
|
||||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} is incompatible with proxy version ${PROXY_VERSION} as it satisfies the SemVer requirement of ${incomp.version}!`
|
this._logger.fatal("Loading has halted due to plugin incompatibility issues.");
|
||||||
);
|
|
||||||
this._logger.fatal(
|
|
||||||
"Loading has halted due to plugin incompatibility issues."
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,9 +188,7 @@ export class PluginManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLoadOrder(
|
private _getLoadOrder(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>): PluginLoaderTypes.PluginLoadOrder {
|
||||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>
|
|
||||||
): PluginLoaderTypes.PluginLoadOrder {
|
|
||||||
let order = [],
|
let order = [],
|
||||||
lastPlugin: any;
|
lastPlugin: any;
|
||||||
plugins.forEach((v) => order.push(v.id));
|
plugins.forEach((v) => order.push(v.id));
|
||||||
|
@ -362,21 +215,12 @@ export class PluginManager extends EventEmitter {
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadPlugins(
|
private async _loadPlugins(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>, order: PluginLoaderTypes.PluginLoadOrder): Promise<number> {
|
||||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>,
|
|
||||||
order: PluginLoaderTypes.PluginLoadOrder
|
|
||||||
): Promise<number> {
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
for (const id of order) {
|
for (const id of order) {
|
||||||
let pluginMeta = plugins.get(id);
|
let pluginMeta = plugins.get(id);
|
||||||
try {
|
try {
|
||||||
const imp = await import(
|
const imp = await import(process.platform == "win32" ? pathToFileURL(pathUtil.join(pluginMeta.path, pluginMeta.entry_point)).toString() : pathUtil.join(pluginMeta.path, pluginMeta.entry_point));
|
||||||
process.platform == "win32"
|
|
||||||
? pathToFileURL(
|
|
||||||
pathUtil.join(pluginMeta.path, pluginMeta.entry_point)
|
|
||||||
).toString()
|
|
||||||
: pathUtil.join(pluginMeta.path, pluginMeta.entry_point)
|
|
||||||
);
|
|
||||||
this.plugins.set(pluginMeta.id, {
|
this.plugins.set(pluginMeta.id, {
|
||||||
exports: imp,
|
exports: imp,
|
||||||
metadata: pluginMeta,
|
metadata: pluginMeta,
|
||||||
|
@ -384,11 +228,7 @@ export class PluginManager extends EventEmitter {
|
||||||
successCount++;
|
successCount++;
|
||||||
this.emit("pluginLoad", pluginMeta.id, imp);
|
this.emit("pluginLoad", pluginMeta.id, imp);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._logger.warn(
|
this._logger.warn(`Failed to load plugin entry point for plugin (${pluginMeta.name}) at ${pluginMeta.path}: ${err.stack ?? err}`);
|
||||||
`Failed to load plugin entry point for plugin (${
|
|
||||||
pluginMeta.name
|
|
||||||
}) at ${pluginMeta.path}: ${err.stack ?? err}`
|
|
||||||
);
|
|
||||||
this._logger.warn("This plugin will skip loading due to an error.");
|
this._logger.warn("This plugin will skip loading due to an error.");
|
||||||
}
|
}
|
||||||
return successCount;
|
return successCount;
|
||||||
|
@ -403,18 +243,9 @@ interface PluginManagerEvents {
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface PluginManager {
|
export declare interface PluginManager {
|
||||||
on<U extends keyof PluginManagerEvents>(
|
on<U extends keyof PluginManagerEvents>(event: U, listener: PluginManagerEvents[U]): this;
|
||||||
event: U,
|
|
||||||
listener: PluginManagerEvents[U]
|
|
||||||
): this;
|
|
||||||
|
|
||||||
emit<U extends keyof PluginManagerEvents>(
|
emit<U extends keyof PluginManagerEvents>(event: U, ...args: Parameters<PluginManagerEvents[U]>): boolean;
|
||||||
event: U,
|
|
||||||
...args: Parameters<PluginManagerEvents[U]>
|
|
||||||
): boolean;
|
|
||||||
|
|
||||||
once<U extends keyof PluginManagerEvents>(
|
once<U extends keyof PluginManagerEvents>(event: U, listener: PluginManagerEvents[U]): this;
|
||||||
event: U,
|
|
||||||
listener: PluginManagerEvents[U]
|
|
||||||
): this;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user