@xhayper/discord-rpc
Version:
a fork of discordjs/RPC
248 lines (247 loc) • 9.52 kB
JavaScript
"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;