diff --git a/package-lock.json b/package-lock.json index 684c61c..4230efc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@the-bds-maneger/core", - "version": "5.2.0", + "version": "5.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@the-bds-maneger/core", - "version": "5.2.0", + "version": "5.3.0", "license": "GPL-3.0", "dependencies": { "@the-bds-maneger/core-utils": "^1.0.0", diff --git a/package.json b/package.json index 348bf4b..325cf3c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "docs": "typedoc --readme none --out docs src/index.ts", "dev": "nodemon", "build": "tsc", - "test:partial": "mocha -r ts-node/register --exit", + "test:partial": "mocha -r ts-node/register -r tsconfig-paths/register --exit", "test": "npm run test:partial -- 'tests/**/*.ts'" }, "repository": { diff --git a/src/bedrock.ts b/src/bedrock.ts index 76ff54e..acbed34 100644 --- a/src/bedrock.ts +++ b/src/bedrock.ts @@ -1,6 +1,6 @@ import { platformManeger } from "@the-bds-maneger/server_versions"; import { manegerConfigProprieties } from "./configManipulate"; -import { randomPort } from "./lib/randomPort"; +import { randomPort } from "./utils/randomPort"; import { pathControl, bdsPlatformOptions } from "./platformPathManeger"; import * as globalPlatfroms from "./globalPlatfroms"; import * as coreUtils from "@the-bds-maneger/core-utils"; diff --git a/src/configManipulate.ts b/src/configManipulate.ts index 7476e36..ad89489 100644 --- a/src/configManipulate.ts +++ b/src/configManipulate.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import utils from "node:util"; -import Proprieties, { properitiesBase } from "./lib/Proprieties"; +import Proprieties, { properitiesBase } from "./utils/Proprieties"; export type configEdit = {name: string, data?: any}; export async function manegerConfigProprieties(config: {configPath: string, configManipulate: {[Properties in updateConfig["name"]]: ((config: string, value: updateConfig["data"]) => string)|{validate?: (value: updateConfig["data"]) => boolean, regexReplace: RegExp, valueFormat: string, addIfNotExist?: string}}}) { diff --git a/src/index.ts b/src/index.ts index 24c1351..97c365a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import * as globalPlatfroms from "./globalPlatfroms"; import * as pluginManeger from "./plugin/plugin"; import * as export_import from "./export_import"; import * as pluginHooks from "./plugin/hook"; -import * as proxy from "./lib/proxy"; +import * as proxy from "./utils/proxy"; // Platforms import * as Bedrock from "./bedrock"; diff --git a/src/java.ts b/src/java.ts index 98133f3..77cf1fd 100644 --- a/src/java.ts +++ b/src/java.ts @@ -3,7 +3,7 @@ import { platformManeger } from "@the-bds-maneger/server_versions"; import { actionV2, actionsV2 } from "./globalPlatfroms"; import { pathControl, bdsPlatformOptions } from "./platformPathManeger"; import { manegerConfigProprieties } from "./configManipulate"; -import { randomPort } from "./lib/randomPort"; +import { randomPort } from "./utils/randomPort"; import fsOld from "node:fs"; import path from "node:path"; import fs from "node:fs/promises"; diff --git a/src/lib/proxy.ts b/src/lib/proxy.ts deleted file mode 100644 index 7515949..0000000 --- a/src/lib/proxy.ts +++ /dev/null @@ -1,86 +0,0 @@ -import dgram from "node:dgram"; -import net from "node:net"; - -export type proxyUdpToTcpOptions = { - udpType?: dgram.SocketType, - listen?: number, - portListen?: (port: number) => void -}; - -export type proxyTcpToUdpClient = { - udpType?: dgram.SocketType, - listen?: number, - remote: { - host: string, - port: number - }, -}; - -/** - * Transfer packets from UDP to TCP to send through some tunnel that only accepts TCP - * - * This also means that it will also have error transporting the data, so it is not guaranteed to work properly even more when dealing with UDP packets. - */ -export function proxyUdpToTcp(udpPort: number, options?: proxyUdpToTcpOptions) { - const tcpServer = net.createServer(); - tcpServer.on("error", err => console.error(err)); - tcpServer.on("connection", socket => { - const udpClient = dgram.createSocket(options?.udpType||"udp4"); - - // Close Sockets - udpClient.once("close", () => socket.end()); - socket.once("close", () => udpClient.close()); - - // Print error - udpClient.on("error", console.error); - socket.on("error", console.error); - - // Pipe Datas - udpClient.on("message", data => socket.write(data)); - socket.on("data", data => udpClient.send(data)); - - // Connect - udpClient.connect(udpPort); - }); - - // Listen - tcpServer.listen(options?.listen||0, function() { - const addr = this.address(); - if (options?.portListen) options.portListen(addr.port); - console.debug("bds proxy port listen, %s, (udp -> tcp)", addr.port); - tcpServer.once("close", () => console.debug("bds proxy close, %s", addr.port)); - }); - - return tcpServer; -} - -export function proxyTcpToUdp(options: proxyTcpToUdpClient) { - const sessions: {[keyIP: string]: net.Socket} = {}; - const udp = dgram.createSocket(options?.udpType||"udp4"); - - udp.on("error", console.error); - udp.on("message", (msg, ipInfo) => { - const keyInfo = `${ipInfo.address}:${ipInfo.port}`; - - // Client TCP - if (!sessions[keyInfo]) { - sessions[keyInfo] = net.createConnection(options.remote); - sessions[keyInfo].on("data", data => udp.send(data, ipInfo.port, ipInfo.address)); - sessions[keyInfo].on("error", console.error); - sessions[keyInfo].once("close", () => { - delete sessions[keyInfo]; - console.log("Client %s:%f close", ipInfo.address, ipInfo.port); - }); - console.log("Client %s:%f connected", ipInfo.address, ipInfo.port); - } - - // Send message - sessions[keyInfo].write(msg); - }); - - // Listen port - udp.bind(options.listen||0, function(){ - const addr = this.address(); - console.log("Port listen, %s (tcp -> udp)", addr.port); - }); -} \ No newline at end of file diff --git a/src/paper.ts b/src/paper.ts index 29d9b89..8c7faa6 100644 --- a/src/paper.ts +++ b/src/paper.ts @@ -3,7 +3,7 @@ import { pathControl, bdsPlatformOptions } from "./platformPathManeger"; import { spigotProprieties } from "./spigot"; import * as globalPlatfroms from "./globalPlatfroms"; import * as coreUtils from "@the-bds-maneger/core-utils"; -import Proprieties from "./lib/Proprieties"; +import Proprieties from "./utils/Proprieties"; import fsOld from "node:fs"; import path from "node:path"; import fs from "node:fs/promises"; diff --git a/src/spigot.ts b/src/spigot.ts index cce7842..3b251ba 100644 --- a/src/spigot.ts +++ b/src/spigot.ts @@ -1,7 +1,7 @@ import { httpRequest, httpRequestLarge, httpRequestGithub } from "@the-bds-maneger/core-utils"; import { pathControl, bdsPlatformOptions } from "./platformPathManeger"; import * as globalPlatfroms from "./globalPlatfroms"; -import Proprieties from "./lib/Proprieties" +import Proprieties from "./utils/Proprieties" import fsOld from "node:fs"; import path from "node:path"; import fs from "node:fs/promises"; diff --git a/src/lib/Proprieties.ts b/src/utils/Proprieties.ts similarity index 100% rename from src/lib/Proprieties.ts rename to src/utils/Proprieties.ts diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts new file mode 100644 index 0000000..eaf8d46 --- /dev/null +++ b/src/utils/proxy.ts @@ -0,0 +1,150 @@ +import type { HTTPError } from "got"; +import { httpRequest } from "@the-bds-maneger/core-utils"; +import { format } from "node:util"; +import crypto from "node:crypto"; +// import dgram from "node:dgram"; +import net from "node:net"; + +export type agentSecret = {type: "agent-secret", secret_key: string}; +export type playitTunnelOptions = {secretKey: string, apiUrl?: string, controlAddress?: string, clientVersion?: string}; +export type tunnelList = { + type: "account-tunnels", + agent_id: string, + tunnels: { + id: string, + enabled: boolean, + name?: string, + ip_address: string, + ip_hostname: string, + custom_domain?: string, + assigned_domain: string, + display_address: string, + is_dedicated_ip: boolean, + from_port: number, + to_port: number, + tunnel_type: "minecraft-bedrock"|"minecraft-java", + port_type: "udp"|"tcp"|"both", + firewall_id?: string, + protocol: { + protocol: "to-agent", + local_ip: string, + local_port: number, + agent_id: number + } + }[] +} +export type playitAgentAccountStatus = {type: "agent-account-status", status: "verified-account", account_id: number}; +export type agentConfig = { + type: "agent-config", + last_update: number, + ping_targets: string[], + ping_target_addresses: string[], + control_address: string, + refresh_from_api: true, + secret_key: string, + mappings: [] +}; + +export type playitTunnelAuth = { + type: "signed-tunnel-request", + auth: { + details: { + account_id: number, + request_timestamp: number, + session_id: number + }, + signature: { + System: { + signature: number[] + } + } + }, + content: number[] +} + + +/** + * Create a key to asynchronously authenticate playit.gg clients + */ +export async function playitClainSecret(clainUrlCallback?: (url: string) => void) { + const claimCode = crypto.pseudoRandomBytes(5).toString("hex"); + const url = format("https://playit.gg/claim/%s?type=%s&name=%s", claimCode, "self-managed", `bdscore_agent`); + if (clainUrlCallback) clainUrlCallback(url); else console.log("Playit claim url: %s", url); + + // Register to API + let waitAuth = 0; + let authAttempts = 0; + async function getSecret(): Promise { + return httpRequest.getJSON({url: "https://api.playit.cloud/agent", method: "POST", headers: {}, body: {type: "exchange-claim-for-secret", claim_key: claimCode}}).catch(async (err: HTTPError) => { + if (err?.response?.statusCode === 404||err?.response?.statusCode === 401) { + if (err.response.statusCode === 404) if (authAttempts++ > 225) throw new Error("client not open auth url"); + if (err.response.statusCode === 401) if (waitAuth++ > 16) throw new Error("Claim code not authorized per client"); + await new Promise(resolve => setTimeout(resolve, 500)); + return getSecret(); + } + throw err; + }); + } + + return (await getSecret()).secret_key; +} + + +/** + * Crie um tunnel para o minecraft bedrock ou minecraft java para que usuarios fora da rede possa se conectar no servidor + */ +export async function playitTunnel(options: playitTunnelOptions) { + options = {apiUrl: "api.playit.cloud", controlAddress: "control.playit.gg", clientVersion: "0.2.3", ...options}; + if (!options.secretKey) throw new Error("Required secret key to auth in playit.gg"); + const Authorization = format("agent-key %s", options.secretKey); + const playitApiUrls = { + agent: format("https://%s/agent", options.apiUrl), + account: format("https://%s/account", options.apiUrl) + }; + const accountInfo = await httpRequest.getJSON({url: playitApiUrls.agent, method: "POST", headers: {Authorization}, body: {type: "get-agent-account-status", client_version: options.clientVersion}}).catch((err: HTTPError) => { + if (err.response.statusCode === 400) throw new Error("Secret key is invalid or not registred"); + throw err; + }); + if (accountInfo.status !== "verified-account") throw new Error("Verify account fist"); + const agentInfo = await httpRequest.getJSON({url: playitApiUrls.agent, method: "POST", headers: {Authorization}, body: {type: "get-agent-config", client_version: options.clientVersion}}); + const signTunnel = await httpRequest.getJSON({url: playitApiUrls.agent, method: "POST", headers: {Authorization}, body: { type: "sign-tunnel-request", RegisterAgent: null }}).catch(err => err.response?.body?.toString()||err); + + async function listTunnels() { + const data = await httpRequest.getJSON({ + url: playitApiUrls.account, + method: "POST", + headers: {Authorization}, + body: { + type: "list-account-tunnels" + } + }).catch(err => Promise.reject(JSON.parse(err.response?.body?.toString()))); + data.tunnels = data.tunnels.filter(tunnel => (["minecraft-bedrock", "minecraft-java"]).includes(tunnel.tunnel_type)); + return data; + } + + async function createTunnel(options: {tunnel_for: "bedrock"|"java", name: string, local_ip: string, local_port: number}) { + const agent_id = (await listTunnels()).agent_id; + const tunnelCreated = await httpRequest.getJSON<{ type: "created", id: string }>({ + url: playitApiUrls.account, + method: "POST", + headers: {Authorization}, + body: { + type: "create-tunnel", agent_id, + name: options.name, + tunnel_type: options.tunnel_for === "bedrock"?"minecraft-bedrock":"minecraft-java", + port_type: options.tunnel_for === "bedrock"?"udp":"tcp", + port_count: 1, + local_ip: options.local_ip, local_port: options.local_port, + } + }); + return (await listTunnels()).tunnels.find(tunnel => tunnel.id === tunnelCreated.id); + } + + async function connectTunnel(name?: string) { + const tunnelInfo = (await listTunnels()).tunnels.find(tunnel => name?(tunnel.name === name||tunnel.id === name):true); + if (!tunnelInfo) throw new Error("No tunnel selected"); + if (tunnelInfo.port_type !== "tcp") throw new Error("Current support only TCP tunnel"); + } + + return {agentInfo, signTunnel, listTunnels, createTunnel, connectTunnel}; +} \ No newline at end of file diff --git a/src/lib/randomPort.ts b/src/utils/randomPort.ts similarity index 100% rename from src/lib/randomPort.ts rename to src/utils/randomPort.ts diff --git a/tests/internal/Proprieties.ts b/tests/internal/Proprieties.ts index 67e5654..ffebf28 100644 --- a/tests/internal/Proprieties.ts +++ b/tests/internal/Proprieties.ts @@ -1,4 +1,4 @@ -import Proprieties from "../../src/lib/Proprieties"; +import Proprieties from "../../src/utils/Proprieties"; const stringExample = `test.ls=aaa\ntest.ab=true`; describe("Proprieties", () => { diff --git a/tsconfig.json b/tsconfig.json index 96fe836..226bbba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,11 @@ "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "allowJs": true, - "lib": ["ES6"] + "lib": ["ES6"], + "baseUrl": ".", + "paths": { + "@core_utils/*": ["./src/utils/*"] + } }, "include": [ "src/**/*"