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