Playit.gg tunnel #491
@ -2,38 +2,37 @@ import type { HTTPError } from "got";
|
|||||||
import { httpRequest } from "@the-bds-maneger/core-utils";
|
import { httpRequest } from "@the-bds-maneger/core-utils";
|
||||||
import { format } from "node:util";
|
import { format } from "node:util";
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import dgram from "node:dgram";
|
// import dgram from "node:dgram";
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import { writeFileSync } from "node:fs";
|
|
||||||
|
|
||||||
export type agentSecret = {type: "agent-secret", secret_key: string};
|
export type agentSecret = {type: "agent-secret", secret_key: string};
|
||||||
|
export type playitTunnelOptions = {secretKey: string, apiUrl?: string, controlAddress?: string, clientVersion?: string};
|
||||||
/**
|
export type tunnelList = {
|
||||||
* Create a key to asynchronously authenticate playit.gg clients
|
type: "account-tunnels",
|
||||||
*/
|
agent_id: string,
|
||||||
export async function playitClaimUrl(clainUrlCallback?: (url: string) => void) {
|
tunnels: {
|
||||||
const claimCode = crypto.pseudoRandomBytes(5).toString("hex");
|
id: string,
|
||||||
const url = format("https://playit.gg/claim/%s?type=%s&name=%s", claimCode, "self-managed", `bdscore_agent`);
|
enabled: boolean,
|
||||||
if (clainUrlCallback) clainUrlCallback(url); else console.log("Playit claim url: %s", url);
|
name?: string,
|
||||||
|
ip_address: string,
|
||||||
// Register to API
|
ip_hostname: string,
|
||||||
let waitAuth = 0;
|
custom_domain?: string,
|
||||||
let authAttempts = 0;
|
assigned_domain: string,
|
||||||
async function getSecret(): Promise<agentSecret> {
|
display_address: string,
|
||||||
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) => {
|
is_dedicated_ip: boolean,
|
||||||
if (err?.response?.statusCode === 404||err?.response?.statusCode === 401) {
|
from_port: number,
|
||||||
if (err.response.statusCode === 404) if (authAttempts++ > 225) throw new Error("client not open auth url");
|
to_port: number,
|
||||||
if (err.response.statusCode === 401) if (waitAuth++ > 16) throw new Error("Claim code not authorized per client");
|
tunnel_type: "minecraft-bedrock"|"minecraft-java",
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
port_type: "udp"|"tcp"|"both",
|
||||||
return getSecret();
|
firewall_id?: string,
|
||||||
}
|
protocol: {
|
||||||
throw err;
|
protocol: "to-agent",
|
||||||
});
|
local_ip: string,
|
||||||
}
|
local_port: number,
|
||||||
|
agent_id: number
|
||||||
return (await getSecret()).secret_key;
|
}
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type playitAgentAccountStatus = {type: "agent-account-status", status: "verified-account", account_id: number};
|
export type playitAgentAccountStatus = {type: "agent-account-status", status: "verified-account", account_id: number};
|
||||||
export type agentConfig = {
|
export type agentConfig = {
|
||||||
type: "agent-config",
|
type: "agent-config",
|
||||||
@ -63,34 +62,34 @@ export type playitTunnelAuth = {
|
|||||||
content: number[]
|
content: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type playitTunnelOptions = {secretKey: string, apiUrl?: string, controlAddress?: string, clientVersion?: string};
|
|
||||||
export type tunnelList = {
|
/**
|
||||||
type: "account-tunnels",
|
* Create a key to asynchronously authenticate playit.gg clients
|
||||||
agent_id: string,
|
*/
|
||||||
tunnels: {
|
export async function playitClainSecret(clainUrlCallback?: (url: string) => void) {
|
||||||
id: string,
|
const claimCode = crypto.pseudoRandomBytes(5).toString("hex");
|
||||||
enabled: boolean,
|
const url = format("https://playit.gg/claim/%s?type=%s&name=%s", claimCode, "self-managed", `bdscore_agent`);
|
||||||
name?: string,
|
if (clainUrlCallback) clainUrlCallback(url); else console.log("Playit claim url: %s", url);
|
||||||
ip_address: string,
|
|
||||||
ip_hostname: string,
|
// Register to API
|
||||||
custom_domain?: string,
|
let waitAuth = 0;
|
||||||
assigned_domain: string,
|
let authAttempts = 0;
|
||||||
display_address: string,
|
async function getSecret(): Promise<agentSecret> {
|
||||||
is_dedicated_ip: boolean,
|
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) => {
|
||||||
from_port: number,
|
if (err?.response?.statusCode === 404||err?.response?.statusCode === 401) {
|
||||||
to_port: number,
|
if (err.response.statusCode === 404) if (authAttempts++ > 225) throw new Error("client not open auth url");
|
||||||
tunnel_type: "minecraft-bedrock"|"minecraft-java",
|
if (err.response.statusCode === 401) if (waitAuth++ > 16) throw new Error("Claim code not authorized per client");
|
||||||
port_type: "udp"|"tcp"|"both",
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
firewall_id?: string,
|
return getSecret();
|
||||||
protocol: {
|
}
|
||||||
protocol: "to-agent",
|
throw err;
|
||||||
local_ip: string,
|
});
|
||||||
local_port: number,
|
}
|
||||||
agent_id: number
|
|
||||||
}
|
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
|
* Crie um tunnel para o minecraft bedrock ou minecraft java para que usuarios fora da rede possa se conectar no servidor
|
||||||
*/
|
*/
|
||||||
@ -119,8 +118,8 @@ export async function playitTunnel(options: playitTunnelOptions) {
|
|||||||
type: "list-account-tunnels"
|
type: "list-account-tunnels"
|
||||||
}
|
}
|
||||||
}).catch(err => Promise.reject(JSON.parse(err.response?.body?.toString())));
|
}).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};
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTunnel(options: {tunnel_for: "bedrock"|"java", name: string, local_ip: string, local_port: number}) {
|
async function createTunnel(options: {tunnel_for: "bedrock"|"java", name: string, local_ip: string, local_port: number}) {
|
||||||
@ -141,89 +140,11 @@ export async function playitTunnel(options: playitTunnelOptions) {
|
|||||||
return (await listTunnels()).tunnels.find(tunnel => tunnel.id === tunnelCreated.id);
|
return (await listTunnels()).tunnels.find(tunnel => tunnel.id === tunnelCreated.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {agentInfo, signTunnel, listTunnels, createTunnel};
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
export type proxyUdpToTcpOptions = {
|
return {agentInfo, signTunnel, listTunnels, createTunnel, connectTunnel};
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import Proprieties from "../../src/lib/Proprieties";
|
import Proprieties from "../../src/utils/Proprieties";
|
||||||
const stringExample = `test.ls=aaa\ntest.ab=true`;
|
const stringExample = `test.ls=aaa\ntest.ab=true`;
|
||||||
|
|
||||||
describe("Proprieties", () => {
|
describe("Proprieties", () => {
|
||||||
|
Reference in New Issue
Block a user