this repo has no description

feat: update roster to be more resilient

dunkirk.sh ccfd7c4a 427867f5

verified
+128 -78
+128 -78
proxy/roster-client.ts
··· 1 1 import mc from "minecraft-protocol"; 2 + import { appendFileSync } from "fs"; 2 3 3 4 type Player = { 4 5 uuid: string; ··· 12 13 const TARGET_HOST = "localhost"; 13 14 const TARGET_PORT = 25566; 14 15 const MINECRAFT_VERSION = "1.20.4"; 16 + const LOG_FILE = "roster.log"; 17 + const RECONNECT_DELAY = 5000; 15 18 16 19 const roster = new Map<string, Player>(); 20 + let client: mc.Client; 21 + let reconnectTimeout: NodeJS.Timeout | null = null; 17 22 18 - const client = mc.createClient({ 19 - host: TARGET_HOST, 20 - port: TARGET_PORT, 21 - username: "hepticWarbler", 22 - version: MINECRAFT_VERSION, 23 - keepAlive: true, 24 - auth: "offline", 25 - }); 23 + function log(message: string) { 24 + const timestamp = new Date().toISOString(); 25 + const logEntry = `[${timestamp}] ${message}\n`; 26 + appendFileSync(LOG_FILE, logEntry); 27 + console.log(message); 28 + } 26 29 27 - client.on("login", () => { 28 - console.log("āœ… Joined server, tracking player list…"); 29 - }); 30 + function setupClient(client: mc.Client) { 31 + client.on("login", () => { 32 + log("āœ… Joined server, tracking player list…"); 33 + }); 30 34 31 - client.on("packet", (data, meta) => { 32 - if (meta.state !== "play") return; 35 + client.on("packet", (data, meta) => { 36 + if (meta.state !== "play") return; 33 37 34 - switch (meta.name) { 35 - case "player_info": { 36 - const action = data.action; 37 - for (const item of data.data) { 38 - const uuid = item.UUID ?? item.uuid; 39 - const name = item.name ?? item.username ?? ""; 40 - const entry = roster.get(uuid) ?? { uuid, name }; 38 + switch (meta.name) { 39 + case "player_info": { 40 + const action = data.action; 41 + for (const item of data.data) { 42 + const uuid = item.UUID ?? item.uuid; 43 + const name = item.name ?? item.username ?? ""; 44 + const entry = roster.get(uuid) ?? { uuid, name }; 41 45 42 - if (action === 0) { 43 - roster.set(uuid, { ...entry, name }); 44 - console.log(`āž• join: ${name} (${uuid})`); 45 - } else if (action === 1) { 46 - roster.set(uuid, { ...entry, gameMode: item.gamemode }); 47 - } else if (action === 2) { 48 - roster.set(uuid, { ...entry, latency: item.ping }); 49 - } else if (action === 4) { 50 - roster.delete(uuid); 51 - console.log(`āž– leave: ${name} (${uuid})`); 46 + if (action === 0) { 47 + roster.set(uuid, { ...entry, name }); 48 + log(`āž• join: ${name} (${uuid})`); 49 + } else if (action === 1) { 50 + roster.set(uuid, { ...entry, gameMode: item.gamemode }); 51 + } else if (action === 2) { 52 + roster.set(uuid, { ...entry, latency: item.ping }); 53 + } else if (action === 4) { 54 + roster.delete(uuid); 55 + log(`āž– leave: ${name} (${uuid})`); 56 + } 52 57 } 58 + break; 53 59 } 54 - break; 55 - } 56 - case "player_info_update": { 57 - const actions: string[] = data.actions; 58 - for (const v of data.values) { 59 - const uuid: string = v.uuid; 60 - const prev = roster.get(uuid); 61 - let name = prev?.name ?? v.name ?? v.profile?.name ?? ""; 62 - let listed = prev?.listed; 60 + case "player_info_update": { 61 + const actions: string[] = data.actions; 62 + for (const v of data.values) { 63 + const uuid: string = v.uuid; 64 + const prev = roster.get(uuid); 65 + let name = prev?.name ?? v.name ?? v.profile?.name ?? ""; 66 + let listed = prev?.listed; 63 67 64 - if (actions.includes("add_player")) { 65 - name = v.name ?? name; 66 - listed = true; 67 - roster.set(uuid, { 68 - uuid, 69 - name, 70 - listed, 71 - properties: v.properties, 72 - latency: v.latency, 73 - gameMode: v.gamemode, 74 - }); 75 - console.log(`āž• join: ${name} (${uuid})`); 76 - } 77 - if (actions.includes("update_latency")) { 78 - roster.set(uuid, { ...(prev ?? { uuid, name }), latency: v.latency }); 79 - } 80 - if (actions.includes("update_gamemode")) { 81 - roster.set(uuid, { 82 - ...(prev ?? { uuid, name }), 83 - gameMode: v.gamemode, 84 - }); 85 - } 86 - if (actions.includes("remove_player")) { 87 - roster.delete(uuid); 88 - console.log(`āž– leave: ${name} (${uuid})`); 68 + if (actions.includes("add_player")) { 69 + name = v.name ?? name; 70 + listed = true; 71 + roster.set(uuid, { 72 + uuid, 73 + name, 74 + listed, 75 + properties: v.properties, 76 + latency: v.latency, 77 + gameMode: v.gamemode, 78 + }); 79 + log(`āž• join: ${name} (${uuid})`); 80 + } 81 + if (actions.includes("update_latency")) { 82 + roster.set(uuid, { ...(prev ?? { uuid, name }), latency: v.latency }); 83 + } 84 + if (actions.includes("update_gamemode")) { 85 + roster.set(uuid, { 86 + ...(prev ?? { uuid, name }), 87 + gameMode: v.gamemode, 88 + }); 89 + } 90 + if (actions.includes("remove_player")) { 91 + roster.delete(uuid); 92 + log(`āž– leave: ${name} (${uuid})`); 93 + } 89 94 } 95 + break; 90 96 } 91 - break; 92 97 } 93 - } 94 - }); 98 + }); 99 + 100 + client.on("error", (err) => { 101 + if (err instanceof Error) { 102 + log("āŒ client error: " + err.message); 103 + if (err.stack) log(err.stack); 104 + } else if (err && typeof err === "object" && "errors" in err) { 105 + log("āŒ client error: AggregateError"); 106 + const errors = (err as any).errors; 107 + if (Array.isArray(errors)) { 108 + errors.forEach((e: any, i: number) => { 109 + log(` [${i}] ${e.message || e}`); 110 + }); 111 + } 112 + } else { 113 + log("āŒ client error: " + String(err)); 114 + } 115 + }); 116 + 117 + client.on("end", (reason) => { 118 + log("šŸ”Œ Disconnected: " + reason); 119 + scheduleReconnect(); 120 + }); 121 + 122 + client.on("kick_disconnect", (packet) => { 123 + const reason = JSON.parse(packet.reason); 124 + log("āš ļø Kicked from server: " + JSON.stringify(reason)); 125 + scheduleReconnect(); 126 + }); 127 + } 128 + 129 + function scheduleReconnect() { 130 + if (reconnectTimeout) return; 131 + 132 + log(`šŸ”„ Reconnecting in ${RECONNECT_DELAY / 1000} seconds...`); 133 + reconnectTimeout = setTimeout(() => { 134 + reconnectTimeout = null; 135 + connect(); 136 + }, RECONNECT_DELAY); 137 + } 138 + 139 + function connect() { 140 + client = mc.createClient({ 141 + host: TARGET_HOST, 142 + port: TARGET_PORT, 143 + username: "hepticWarbler", 144 + version: MINECRAFT_VERSION, 145 + keepAlive: true, 146 + auth: "offline", 147 + }); 148 + 149 + setupClient(client); 150 + } 151 + 152 + connect(); 95 153 96 154 setInterval(() => { 97 155 const players = Array.from(roster.values()); 98 - console.log("\nšŸ“‹ Current players:", players.length); 156 + log("\nšŸ“‹ Current players: " + players.length); 99 157 players.forEach((p) => { 100 - console.log( 158 + log( 101 159 ` - ${p.name} (${p.uuid}) | ping: ${p.latency}ms | mode: ${p.gameMode}`, 102 160 ); 103 161 }); 104 - console.log(""); 162 + log(""); 105 163 }, 10000); 106 - 107 - client.on("error", (err) => { 108 - console.error("āŒ client error:", err); 109 - }); 110 - 111 - client.on("end", (reason) => { 112 - console.log("šŸ”Œ Disconnected:", reason); 113 - });