UNPKG

@xhayper/discord-rpc

Version:
219 lines (218 loc) 8.47 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 defaultPathList = [ { platform: ["win32"], format: (id) => `\\\\?\\pipe\\discord-ipc-${id}` }, { platform: ["darwin", "linux"], format: (id) => { // macOS / Linux path const { env: { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } } = process; const prefix = node_fs_1.default.realpathSync(XDG_RUNTIME_DIR ?? TMPDIR ?? TMP ?? TEMP ?? `${node_path_1.default.sep}tmp`); return node_path_1.default.join(prefix, `discord-ipc-${id}`); } }, { platform: ["linux"], format: (id) => { // snap const { env: { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } } = process; const prefix = node_fs_1.default.realpathSync(XDG_RUNTIME_DIR ?? TMPDIR ?? TMP ?? TEMP ?? `${node_path_1.default.sep}tmp`); return node_path_1.default.join(prefix, "snap.discord", `discord-ipc-${id}`); } }, { platform: ["linux"], format: (id) => { // flatpak const { env: { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } } = process; const prefix = node_fs_1.default.realpathSync(XDG_RUNTIME_DIR ?? TMPDIR ?? TMP ?? TEMP ?? `${node_path_1.default.sep}tmp`); return node_path_1.default.join(prefix, "app", "com.discordapp.Discord", `discord-ipc-${id}`); } } ]; const createSocket = async (path) => { return new Promise((resolve, reject) => { const onError = () => { socket.removeListener("conect", onConnect); reject(); }; const onConnect = () => { socket.removeListener("error", onError); resolve(socket); }; const socket = node_net_1.default.createConnection(path); 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 }); this.pathList = options.pathList ?? defaultPathList; } async getSocket() { if (this.socket) return this.socket; const pathList = this.pathList ?? defaultPathList; const pipeId = this.client.pipeId; return new Promise(async (resolve, reject) => { for (const pat of pathList) { const tryCreateSocket = async (path) => { const socket = await createSocket(path).catch(() => undefined); return socket; }; const handleSocketId = async (id) => { if (!pat.platform.includes(process.platform)) return; const socketPath = pat.format(id); if (process.platform !== "win32" && !node_fs_1.default.existsSync(node_path_1.default.dirname(socketPath))) return; return await tryCreateSocket(socketPath); }; if (pipeId) { const socket = await handleSocketId(pipeId); if (socket) return resolve(socket); } else { for (let i = 0; i < 10; i++) { const socket = await handleSocketId(i); if (socket) return resolve(socket); } } } reject(new RPCError_1.RPCError(Transport_1.CUSTOM_RPC_ERROR_CODE.COULD_NOT_CONNECT, "Could not connect")); }); } 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 = 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 = data.readUInt32LE(0); const length = data.readUInt32LE(4); if (data.length !== length + 8) { // TODO : Handle error this.client.emit("debug", "SERVER => CLIENT | Malformed packet, invalid payload"); return; } 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;