UNPKG

@xhayper/discord-rpc

Version:
248 lines (247 loc) 9.52 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IPCTransport = exports.IPC_OPCODE = void 0; const Transport_1 = require("../structures/Transport"); const RPCError_1 = require("../utils/RPCError"); const node_crypto_1 = __importDefault(require("node:crypto")); const node_path_1 = __importDefault(require("node:path")); const node_net_1 = __importDefault(require("node:net")); const node_fs_1 = __importDefault(require("node:fs")); var IPC_OPCODE; (function (IPC_OPCODE) { IPC_OPCODE[IPC_OPCODE["HANDSHAKE"] = 0] = "HANDSHAKE"; IPC_OPCODE[IPC_OPCODE["FRAME"] = 1] = "FRAME"; IPC_OPCODE[IPC_OPCODE["CLOSE"] = 2] = "CLOSE"; IPC_OPCODE[IPC_OPCODE["PING"] = 3] = "PING"; IPC_OPCODE[IPC_OPCODE["PONG"] = 4] = "PONG"; })(IPC_OPCODE || (exports.IPC_OPCODE = IPC_OPCODE = {})); const getTempDir = () => { const { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } = process.env; return node_fs_1.default.realpathSync(XDG_RUNTIME_DIR ?? TMPDIR ?? TMP ?? TEMP ?? `${node_path_1.default.sep}tmp`); }; const defaultPathList = [ { platform: ["win32"], format: (id) => `\\\\?\\pipe\\discord-ipc-${id}` }, { platform: ["darwin", "linux", "freebsd", "openbsd", "netbsd"], format: (id) => { // macOS / Linux / FreeBSD / OpenBSD / NetBSD path return node_path_1.default.join(getTempDir(), `discord-ipc-${id}`); } }, { platform: ["linux"], format: (id) => { // snap return node_path_1.default.join(getTempDir(), "snap.discord", `discord-ipc-${id}`); } }, { platform: ["linux"], format: (id) => { // flatpak return node_path_1.default.join(getTempDir(), "app", "com.discordapp.Discord", `discord-ipc-${id}`); } }, { platform: [], format: (id) => { // Super fallback, if thing don't work, well let just use this return node_path_1.default.join(getTempDir(), `discord-ipc-${id}`); } } ]; const createSocket = async (path) => { return new Promise((resolve, reject) => { const onError = () => { socket.removeListener("connect", onConnect); reject(); }; const onConnect = () => { socket.removeListener("error", onError); resolve(socket); }; let socket; if (typeof path === "string") socket = node_net_1.default.createConnection(path); else socket = node_net_1.default.createConnection(path[0], path[1]); socket.once("connect", onConnect); socket.once("error", onError); }); }; class IPCTransport extends Transport_1.Transport { get isConnected() { return this.socket !== undefined && this.socket.readyState === "open"; } constructor(options) { super(options); Object.defineProperty(this, "pathList", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "socket", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "tmpData", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.pathList = options.pathList ?? defaultPathList; this.tmpData = null; } async getSocket() { if (this.socket) return this.socket; const pathList = this.pathList ?? defaultPathList; const pipeId = this.client.pipeId; return new Promise(async (resolve, reject) => { const useablePath = []; for (const pat of pathList) { if (pat.platform.length <= 0 || !pat.platform.includes(process.platform)) continue; let pipeIdList = []; if (pipeId) pipeIdList = [pipeId]; else for (let i = 0; i < 10; i++) pipeIdList.push(i); const maybeTcp = pat.format(0); if (Array.isArray(maybeTcp)) { useablePath.push(maybeTcp); } else { for (const pipeId of pipeIdList) { const socketPath = pat.format(pipeId); if (process.platform !== "win32" && typeof socketPath === "string" && !node_fs_1.default.existsSync(socketPath)) continue; useablePath.push(socketPath); } } } this.client.emit("debug", `CLIENT | Found ${useablePath.length} Discord client path;\n${useablePath.map((x) => (Array.isArray(x) ? `${x[1]}:${x[0]}` : x)).join("\n")}`); if (useablePath.length < 0) return reject(new RPCError_1.RPCError(Transport_1.CUSTOM_RPC_ERROR_CODE.COULD_NOT_FIND_CLIENT, "Unable to find any Discord client")); for (const path of useablePath) { const socket = await createSocket(path).catch(() => undefined); if (socket) return resolve(socket); } return reject(new RPCError_1.RPCError(Transport_1.CUSTOM_RPC_ERROR_CODE.COULD_NOT_CONNECT, "Could not connect to Discord client")); }); } async connect() { if (!this.socket) this.socket = await this.getSocket(); this.emit("open"); this.send({ v: 1, client_id: this.client.clientId }, IPC_OPCODE.HANDSHAKE); this.socket.on("readable", () => { let data = this.tmpData != null ? this.tmpData.data : Buffer.alloc(0); do { if (!this.isConnected) break; const chunk = this.socket?.read(); if (!chunk) break; this.client.emit("debug", `SERVER => CLIENT | ${chunk .toString("hex") .match(/.{1,2}/g) ?.join(" ") .toUpperCase()}`); data = Buffer.concat([data, chunk]); } while (true); if (data.length < 8) { if (data.length === 0) return; // TODO : Handle error this.client.emit("debug", "SERVER => CLIENT | Malformed packet, invalid payload"); return; } const op = this.tmpData != null ? this.tmpData.op : data.readUInt32LE(0); const length = this.tmpData != null ? this.tmpData.length : data.readUInt32LE(4); if (data.length !== length + 8) { // TODO : Handle error this.client.emit("debug", "SERVER => CLIENT | Malformed packet, invalid payload"); this.tmpData = { op: op, length: length, data: data }; return; } this.tmpData = null; let parsedData; try { parsedData = JSON.parse(data.subarray(8, length + 8).toString()); } catch { // TODO : Handle error this.client.emit("debug", "SERVER => CLIENT | Malformed packet, invalid payload"); return; } this.client.emit("debug", `SERVER => CLIENT | OPCODE.${IPC_OPCODE[op]} |`, parsedData); switch (op) { case IPC_OPCODE.FRAME: { if (!data) break; this.emit("message", parsedData); break; } case IPC_OPCODE.CLOSE: { this.emit("close", parsedData); break; } case IPC_OPCODE.PING: { this.send(parsedData, IPC_OPCODE.PONG); this.emit("ping"); break; } } }); this.socket.on("close", () => { this.socket = undefined; this.emit("close", "Closed by Discord"); }); } send(message, op = IPC_OPCODE.FRAME) { this.client.emit("debug", `CLIENT => SERVER | OPCODE.${IPC_OPCODE[op]} |`, message); const dataBuffer = message ? Buffer.from(JSON.stringify(message)) : Buffer.alloc(0); const packet = Buffer.alloc(8); packet.writeUInt32LE(op, 0); packet.writeUInt32LE(dataBuffer.length, 4); this.socket?.write(Buffer.concat([packet, dataBuffer])); } ping() { this.send(node_crypto_1.default.randomUUID(), IPC_OPCODE.PING); } close() { if (!this.socket) return Promise.resolve(); return new Promise((resolve) => { this.socket.once("close", () => { this.emit("close", "Closed by client"); this.socket = undefined; resolve(); }); this.socket.destroy(); }); } } exports.IPCTransport = IPCTransport;