Spaces:
Paused
Paused
| ; | |
| var __create = Object.create; | |
| var __defProp = Object.defineProperty; | |
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
| var __getOwnPropNames = Object.getOwnPropertyNames; | |
| var __getProtoOf = Object.getPrototypeOf; | |
| var __hasOwnProp = Object.prototype.hasOwnProperty; | |
| var __export = (target, all) => { | |
| for (var name in all) | |
| __defProp(target, name, { get: all[name], enumerable: true }); | |
| }; | |
| var __copyProps = (to, from, except, desc) => { | |
| if (from && typeof from === "object" || typeof from === "function") { | |
| for (let key of __getOwnPropNames(from)) | |
| if (!__hasOwnProp.call(to, key) && key !== except) | |
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | |
| } | |
| return to; | |
| }; | |
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | |
| // If the importer is in node compatibility mode or this is not an ESM | |
| // file that has been converted to a CommonJS file using a Babel- | |
| // compatible transform (i.e. "__esModule" has not been set), then set | |
| // "default" to the CommonJS "module.exports" for node compatibility. | |
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | |
| mod | |
| )); | |
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
| var sockets_exports = {}; | |
| __export(sockets_exports, { | |
| PM: () => PM, | |
| ServerStream: () => ServerStream, | |
| Sockets: () => Sockets | |
| }); | |
| module.exports = __toCommonJS(sockets_exports); | |
| var fs = __toESM(require("fs")); | |
| var http = __toESM(require("http")); | |
| var https = __toESM(require("https")); | |
| var path = __toESM(require("path")); | |
| var import_lib = require("../lib"); | |
| var import_ip_tools = require("./ip-tools"); | |
| var import_battle = require("../sim/battle"); | |
| /** | |
| * Connections | |
| * Pokemon Showdown - http://pokemonshowdown.com/ | |
| * | |
| * Abstraction layer for multi-process SockJS connections. | |
| * | |
| * This file handles all the communications between the users' | |
| * browsers, the networking processes, and users.ts in the | |
| * main process. | |
| * | |
| * @license MIT | |
| */ | |
| const Sockets = new class { | |
| async onSpawn(worker) { | |
| const id = worker.workerid; | |
| for await (const data2 of worker.stream) { | |
| switch (data2.charAt(0)) { | |
| case "*": { | |
| worker.load++; | |
| const [socketid, ip, protocol] = data2.substr(1).split("\n"); | |
| Users.socketConnect(worker, id, socketid, ip, protocol); | |
| break; | |
| } | |
| case "!": { | |
| worker.load--; | |
| const socketid = data2.substr(1); | |
| Users.socketDisconnect(worker, id, socketid); | |
| break; | |
| } | |
| case "<": { | |
| const idx = data2.indexOf("\n"); | |
| const socketid = data2.substr(1, idx - 1); | |
| const message = data2.substr(idx + 1); | |
| Users.socketReceive(worker, id, socketid, message); | |
| break; | |
| } | |
| default: | |
| } | |
| } | |
| } | |
| onUnspawn(worker) { | |
| Users.socketDisconnectAll(worker, worker.workerid); | |
| } | |
| listen(port, bindAddress, workerCount) { | |
| if (port !== void 0 && !isNaN(port)) { | |
| Config.port = port; | |
| Config.ssl = null; | |
| } else { | |
| port = Config.port; | |
| try { | |
| const cloudenv = require("cloud-env"); | |
| bindAddress = cloudenv.get("IP", bindAddress); | |
| port = cloudenv.get("PORT", port); | |
| } catch { | |
| } | |
| } | |
| if (bindAddress !== void 0) { | |
| Config.bindaddress = bindAddress; | |
| } | |
| if (port !== void 0) { | |
| Config.port = port; | |
| } | |
| if (workerCount === void 0) { | |
| workerCount = Config.workers !== void 0 ? Config.workers : 1; | |
| } | |
| PM.env = { PSPORT: Config.port, PSBINDADDR: Config.bindaddress || "0.0.0.0", PSNOSSL: Config.ssl ? 0 : 1 }; | |
| PM.subscribeSpawn((worker) => void this.onSpawn(worker)); | |
| PM.subscribeUnspawn(this.onUnspawn); | |
| PM.spawn(workerCount); | |
| } | |
| socketSend(worker, socketid, message) { | |
| void worker.stream.write(`>${socketid} | |
| ${message}`); | |
| } | |
| socketDisconnect(worker, socketid) { | |
| void worker.stream.write(`!${socketid}`); | |
| } | |
| roomBroadcast(roomid, message) { | |
| for (const worker of PM.workers) { | |
| void worker.stream.write(`#${roomid} | |
| ${message}`); | |
| } | |
| } | |
| roomAdd(worker, roomid, socketid) { | |
| void worker.stream.write(`+${roomid} | |
| ${socketid}`); | |
| } | |
| roomRemove(worker, roomid, socketid) { | |
| void worker.stream.write(`-${roomid} | |
| ${socketid}`); | |
| } | |
| channelBroadcast(roomid, message) { | |
| for (const worker of PM.workers) { | |
| void worker.stream.write(`:${roomid} | |
| ${message}`); | |
| } | |
| } | |
| channelMove(worker, roomid, channelid, socketid) { | |
| void worker.stream.write(`.${roomid} | |
| ${channelid} | |
| ${socketid}`); | |
| } | |
| eval(worker, query) { | |
| void worker.stream.write(`$${query}`); | |
| } | |
| }(); | |
| class ServerStream extends import_lib.Streams.ObjectReadWriteStream { | |
| constructor(config) { | |
| super(); | |
| /** socketid:Connection */ | |
| this.sockets = /* @__PURE__ */ new Map(); | |
| /** roomid:socketid:Connection */ | |
| this.rooms = /* @__PURE__ */ new Map(); | |
| /** roomid:socketid:channelid */ | |
| this.roomChannels = /* @__PURE__ */ new Map(); | |
| this.socketCounter = 0; | |
| this.receivers = { | |
| "$"(data) { | |
| eval(data.substr(1)); | |
| }, | |
| "!"(data2) { | |
| const socketid = data2.substr(1); | |
| const socket = this.sockets.get(socketid); | |
| if (!socket) | |
| return; | |
| socket.destroy(); | |
| this.sockets.delete(socketid); | |
| for (const [curRoomid, curRoom] of this.rooms) { | |
| curRoom.delete(socketid); | |
| const roomChannel = this.roomChannels.get(curRoomid); | |
| if (roomChannel) | |
| roomChannel.delete(socketid); | |
| if (!curRoom.size) { | |
| this.rooms.delete(curRoomid); | |
| if (roomChannel) | |
| this.roomChannels.delete(curRoomid); | |
| } | |
| } | |
| }, | |
| ">"(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const socketid = data2.substr(1, nlLoc - 1); | |
| const socket = this.sockets.get(socketid); | |
| if (!socket) | |
| return; | |
| const message = data2.substr(nlLoc + 1); | |
| socket.write(message); | |
| }, | |
| "#"(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const roomid = data2.substr(1, nlLoc - 1); | |
| const room = roomid ? this.rooms.get(roomid) : this.sockets; | |
| if (!room) | |
| return; | |
| const message = data2.substr(nlLoc + 1); | |
| for (const curSocket of room.values()) | |
| curSocket.write(message); | |
| }, | |
| "+"(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const socketid = data2.substr(nlLoc + 1); | |
| const socket = this.sockets.get(socketid); | |
| if (!socket) | |
| return; | |
| const roomid = data2.substr(1, nlLoc - 1); | |
| let room = this.rooms.get(roomid); | |
| if (!room) { | |
| room = /* @__PURE__ */ new Map(); | |
| this.rooms.set(roomid, room); | |
| } | |
| room.set(socketid, socket); | |
| }, | |
| "-"(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const roomid = data2.slice(1, nlLoc); | |
| const room = this.rooms.get(roomid); | |
| if (!room) | |
| return; | |
| const socketid = data2.slice(nlLoc + 1); | |
| room.delete(socketid); | |
| const roomChannel = this.roomChannels.get(roomid); | |
| if (roomChannel) | |
| roomChannel.delete(socketid); | |
| if (!room.size) { | |
| this.rooms.delete(roomid); | |
| if (roomChannel) | |
| this.roomChannels.delete(roomid); | |
| } | |
| }, | |
| "."(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const roomid = data2.slice(1, nlLoc); | |
| const nlLoc2 = data2.indexOf("\n", nlLoc + 1); | |
| const channelid = Number(data2.slice(nlLoc + 1, nlLoc2)); | |
| const socketid = data2.slice(nlLoc2 + 1); | |
| let roomChannel = this.roomChannels.get(roomid); | |
| if (!roomChannel) { | |
| roomChannel = /* @__PURE__ */ new Map(); | |
| this.roomChannels.set(roomid, roomChannel); | |
| } | |
| if (channelid === 0) { | |
| roomChannel.delete(socketid); | |
| } else { | |
| roomChannel.set(socketid, channelid); | |
| } | |
| }, | |
| ":"(data2) { | |
| const nlLoc = data2.indexOf("\n"); | |
| const roomid = data2.slice(1, nlLoc); | |
| const room = this.rooms.get(roomid); | |
| if (!room) | |
| return; | |
| const messages = [ | |
| null, | |
| null, | |
| null, | |
| null, | |
| null | |
| ]; | |
| const message = data2.substr(nlLoc + 1); | |
| const channelMessages = (0, import_battle.extractChannelMessages)(message, [0, 1, 2, 3, 4]); | |
| const roomChannel = this.roomChannels.get(roomid); | |
| for (const [curSocketid, curSocket] of room) { | |
| const channelid = roomChannel?.get(curSocketid) || 0; | |
| if (!messages[channelid]) | |
| messages[channelid] = channelMessages[channelid].join("\n"); | |
| curSocket.write(messages[channelid]); | |
| } | |
| } | |
| }; | |
| if (!config.bindaddress) | |
| config.bindaddress = "0.0.0.0"; | |
| this.isTrustedProxyIp = config.proxyip ? import_ip_tools.IPTools.checker(config.proxyip) : () => false; | |
| this.server = http.createServer(); | |
| this.serverSsl = null; | |
| if (config.ssl) { | |
| let key; | |
| try { | |
| key = path.resolve(__dirname, config.ssl.options.key); | |
| if (!fs.statSync(key).isFile()) | |
| throw new Error(); | |
| try { | |
| key = fs.readFileSync(key); | |
| } catch (e) { | |
| (0, import_lib.crashlogger)( | |
| new Error(`Failed to read the configured SSL private key PEM file: | |
| ${e.stack}`), | |
| `Socket process ${process.pid}` | |
| ); | |
| } | |
| } catch { | |
| console.warn("SSL private key config values will not support HTTPS server option values in the future. Please set it to use the absolute path of its PEM file."); | |
| key = config.ssl.options.key; | |
| } | |
| let cert; | |
| try { | |
| cert = path.resolve(__dirname, config.ssl.options.cert); | |
| if (!fs.statSync(cert).isFile()) | |
| throw new Error(); | |
| try { | |
| cert = fs.readFileSync(cert); | |
| } catch (e) { | |
| (0, import_lib.crashlogger)( | |
| new Error(`Failed to read the configured SSL certificate PEM file: | |
| ${e.stack}`), | |
| `Socket process ${process.pid}` | |
| ); | |
| } | |
| } catch { | |
| console.warn("SSL certificate config values will not support HTTPS server option values in the future. Please set it to use the absolute path of its PEM file."); | |
| cert = config.ssl.options.cert; | |
| } | |
| if (key && cert) { | |
| try { | |
| this.serverSsl = https.createServer({ ...config.ssl.options, key, cert }); | |
| } catch (e) { | |
| (0, import_lib.crashlogger)(new Error(`The SSL settings are misconfigured: | |
| ${e.stack}`), `Socket process ${process.pid}`); | |
| } | |
| } | |
| } | |
| try { | |
| if (config.disablenodestatic) | |
| throw new Error("disablenodestatic"); | |
| const StaticServer = require("node-static").Server; | |
| const roomidRegex = /^\/(?:[A-Za-z0-9][A-Za-z0-9-]*)\/?$/; | |
| const cssServer = new StaticServer("./config"); | |
| const avatarServer = new StaticServer("./config/avatars"); | |
| const staticServer = new StaticServer("./server/static"); | |
| const staticRequestHandler = (req, res) => { | |
| req.resume(); | |
| req.addListener("end", () => { | |
| if (config.customhttpresponse?.(req, res)) { | |
| return; | |
| } | |
| let server2 = staticServer; | |
| if (req.url) { | |
| if (req.url === "/custom.css") { | |
| server2 = cssServer; | |
| } else if (req.url.startsWith("/avatars/")) { | |
| req.url = req.url.substr(8); | |
| server2 = avatarServer; | |
| } else if (roomidRegex.test(req.url)) { | |
| req.url = "/"; | |
| } | |
| } | |
| server2.serve(req, res, (e) => { | |
| if (e && e.status === 404) { | |
| staticServer.serveFile("404.html", 404, {}, req, res); | |
| } | |
| }); | |
| }); | |
| }; | |
| this.server.on("request", staticRequestHandler); | |
| if (this.serverSsl) | |
| this.serverSsl.on("request", staticRequestHandler); | |
| } catch (e) { | |
| if (e.message === "disablenodestatic") { | |
| console.log("node-static is disabled"); | |
| } else { | |
| console.log("Could not start node-static - try `npm install` if you want to use it"); | |
| } | |
| } | |
| const sockjs = require("sockjs"); | |
| const options = { | |
| sockjs_url: `//play.pokemonshowdown.com/js/lib/sockjs-1.4.0-nwjsfix.min.js`, | |
| prefix: "/showdown", | |
| log(severity, message) { | |
| if (severity === "error") | |
| console.log(`ERROR: ${message}`); | |
| } | |
| }; | |
| if (config.wsdeflate !== null) { | |
| try { | |
| const deflate = require("permessage-deflate").configure(config.wsdeflate); | |
| options.faye_server_options = { extensions: [deflate] }; | |
| } catch { | |
| (0, import_lib.crashlogger)( | |
| new Error("Dependency permessage-deflate is not installed or is otherwise unaccessable. No message compression will take place until server restart."), | |
| "Sockets" | |
| ); | |
| } | |
| } | |
| const server = sockjs.createServer(options); | |
| process.once("disconnect", () => this.cleanup()); | |
| process.once("exit", () => this.cleanup()); | |
| server.on("connection", (connection) => this.onConnection(connection)); | |
| server.installHandlers(this.server, {}); | |
| this.server.listen(config.port, config.bindaddress); | |
| console.log(`Worker ${PM.workerid} now listening on ${config.bindaddress}:${config.port}`); | |
| if (this.serverSsl) { | |
| server.installHandlers(this.serverSsl, {}); | |
| this.serverSsl.listen(config.ssl.port, config.bindaddress); | |
| console.log(`Worker ${PM.workerid} now listening for SSL on port ${config.ssl.port}`); | |
| } | |
| console.log(`Test your server at http://${config.bindaddress === "0.0.0.0" ? "localhost" : config.bindaddress}:${config.port}`); | |
| } | |
| /** | |
| * Clean up any remaining connections on disconnect. If this isn't done, | |
| * the process will not exit until any remaining connections have been destroyed. | |
| * Afterwards, the worker process will die on its own | |
| */ | |
| cleanup() { | |
| for (const socket of this.sockets.values()) { | |
| try { | |
| socket.destroy(); | |
| } catch { | |
| } | |
| } | |
| this.sockets.clear(); | |
| this.rooms.clear(); | |
| this.roomChannels.clear(); | |
| this.server.close(); | |
| if (this.serverSsl) | |
| this.serverSsl.close(); | |
| setImmediate(() => process.exit(0)); | |
| } | |
| onConnection(socket) { | |
| if (!socket) | |
| return; | |
| if (!socket.remoteAddress) { | |
| try { | |
| socket.destroy(); | |
| } catch { | |
| } | |
| return; | |
| } | |
| const socketid = `${++this.socketCounter}`; | |
| this.sockets.set(socketid, socket); | |
| let socketip = socket.remoteAddress; | |
| if (this.isTrustedProxyIp(socketip)) { | |
| const ips = (socket.headers["x-forwarded-for"] || "").split(",").reverse(); | |
| for (const ip of ips) { | |
| const proxy = ip.trim(); | |
| if (!this.isTrustedProxyIp(proxy)) { | |
| socketip = proxy; | |
| break; | |
| } | |
| } | |
| } | |
| this.push(`*${socketid} | |
| ${socketip} | |
| ${socket.protocol}`); | |
| socket.on("data", (message) => { | |
| if (!message) | |
| return; | |
| if (message.length > 100 * 1024) { | |
| socket.write(`|popup|Your message must be below 100KB`); | |
| console.log(`Dropping client message ${message.length / 1024} KB...`); | |
| console.log(message.slice(0, 160)); | |
| return; | |
| } | |
| if (typeof message !== "string" || message.startsWith("{")) | |
| return; | |
| const pipeIndex = message.indexOf("|"); | |
| if (pipeIndex < 0 || pipeIndex === message.length - 1) | |
| return; | |
| this.push(`<${socketid} | |
| ${message}`); | |
| }); | |
| socket.once("close", () => { | |
| this.push(`!${socketid}`); | |
| this.sockets.delete(socketid); | |
| for (const room of this.rooms.values()) | |
| room.delete(socketid); | |
| }); | |
| } | |
| _write(data2) { | |
| const receiver = this.receivers[data2.charAt(0)]; | |
| if (receiver) | |
| receiver.call(this, data2); | |
| } | |
| } | |
| const PM = new import_lib.ProcessManager.RawProcessManager({ | |
| module, | |
| setupChild: () => new ServerStream(Config), | |
| isCluster: true | |
| }); | |
| if (!PM.isParentProcess) { | |
| global.Config = require("./config-loader").Config; | |
| if (Config.crashguard) { | |
| process.on("uncaughtException", (err) => { | |
| (0, import_lib.crashlogger)(err, `Socket process ${PM.workerid} (${process.pid})`); | |
| }); | |
| process.on("unhandledRejection", (err) => { | |
| (0, import_lib.crashlogger)(err || {}, `Socket process ${PM.workerid} (${process.pid}) Promise`); | |
| }); | |
| } | |
| if (Config.sockets) { | |
| try { | |
| require.resolve("node-oom-heapdump"); | |
| } catch (e) { | |
| if (e.code !== "MODULE_NOT_FOUND") | |
| throw e; | |
| throw new Error( | |
| "node-oom-heapdump is not installed, but it is a required dependency if Config.ofesockets is set to true! Run npm install node-oom-heapdump and restart the server." | |
| ); | |
| } | |
| global.nodeOomHeapdump = require("node-oom-heapdump")({ | |
| addTimestamp: true | |
| }); | |
| } | |
| if (process.env.PSPORT) | |
| Config.port = +process.env.PSPORT; | |
| if (process.env.PSBINDADDR) | |
| Config.bindaddress = process.env.PSBINDADDR; | |
| if (process.env.PSNOSSL && parseInt(process.env.PSNOSSL)) | |
| Config.ssl = null; | |
| import_lib.Repl.start(`sockets-${PM.workerid}-${process.pid}`, (cmd) => eval(cmd)); | |
| } | |
| //# sourceMappingURL=sockets.js.map | |