Playit.gg tunnel #491

Closed
Sirherobrine23 wants to merge 4 commits from 484i into main
Showing only changes of commit 3bdcd58884 - Show all commits

@ -4,6 +4,35 @@ 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};
/**
* Create a key to asynchronously authenticate playit.gg clients
*/
export async function playitClaimUrl(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<agentSecret> {
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;
}
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 = {
@ -35,47 +64,84 @@ export type playitTunnelAuth = {
} }
export type playitTunnelOptions = {secretKey: string, apiUrl?: string, controlAddress?: string, clientVersion?: 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
}
}[]
}
/**
* 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) { export async function playitTunnel(options: playitTunnelOptions) {
options = {apiUrl: "api.playit.cloud", controlAddress: "control.playit.gg", clientVersion: "0.2.3", ...options}; 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"); if (!options.secretKey) throw new Error("Required secret key to auth in playit.gg");
const Authorization = format("agent-key %s", options.secretKey); const Authorization = format("agent-key %s", options.secretKey);
const accountInfo = await httpRequest.getJSON<playitAgentAccountStatus>({url: format("https://%s/agent", options.apiUrl), method: "POST", headers: {Authorization}, body: {type: "get-agent-account-status", client_version: options.clientVersion}}).catch((err: HTTPError) => { const playitApiUrls = {
agent: format("https://%s/agent", options.apiUrl),
account: format("https://%s/account", options.apiUrl)
};
const accountInfo = await httpRequest.getJSON<playitAgentAccountStatus>({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"); if (err.response.statusCode === 400) throw new Error("Secret key is invalid or not registred");
throw err; throw err;
}); });
if (accountInfo.status !== "verified-account") throw new Error("Verify account fist"); if (accountInfo.status !== "verified-account") throw new Error("Verify account fist");
const agentInfo = await httpRequest.getJSON<agentConfig>({url: format("https://%s/agent", options.apiUrl), method: "POST", headers: {Authorization}, body: {type: "get-agent-config", client_version: options.clientVersion}}); const agentInfo = await httpRequest.getJSON<agentConfig>({url: playitApiUrls.agent, method: "POST", headers: {Authorization}, body: {type: "get-agent-config", client_version: options.clientVersion}});
const signTunnel = await httpRequest.getJSON<playitTunnelAuth>({url: format("https://%s/agent", options.apiUrl), method: "POST", headers: {Authorization}, body: { type: "sign-tunnel-request", RegisterAgent: null }}).catch(err => err.response?.body?.toString()||err); const signTunnel = await httpRequest.getJSON<playitTunnelAuth>({url: playitApiUrls.agent, method: "POST", headers: {Authorization}, body: { type: "sign-tunnel-request", RegisterAgent: null }}).catch(err => err.response?.body?.toString()||err);
return {agentInfo, signTunnel}; async function listTunnels() {
} const data = await httpRequest.getJSON<tunnelList>({
url: playitApiUrls.account,
export type agentSecret = {type: "agent-secret", secret_key: string}; method: "POST",
headers: {Authorization},
/** body: {
* Create a key to asynchronously authenticate playit.gg clients type: "list-account-tunnels"
*/
export async function playitClaimUrl(clainUrlCallback?: (url: string) => void) {
const claimCode = crypto.pseudoRandomBytes(5).toString("hex");
const url = format("https://playit.gg/claim/%s?type=%s&name=%s", claimCode, "bdscore_agent", `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<agentSecret> {
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; }).catch(err => Promise.reject(JSON.parse(err.response?.body?.toString())));
});
return {...data};
} }
return (await getSecret()).secret_key; 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);
}
return {agentInfo, signTunnel, listTunnels, createTunnel};
} }
export type proxyUdpToTcpOptions = { export type proxyUdpToTcpOptions = {