@xhayper/discord-rpc
Version:
a fork of discordjs/RPC
305 lines (304 loc) • 14.4 kB
JavaScript
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _Client_refreshToken, _Client_transport, _Client_user, _Client_application, _Client_rest, _Client_refreshTimeout, _Client_connectionPromise, _Client__nonceMap;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = void 0;
const v10_1 = require("discord-api-types/v10");
const async_event_emitter_1 = require("@vladfrangu/async_event_emitter");
const IPC_1 = require("./transport/IPC");
const WebSocket_1 = require("./transport/WebSocket");
const ClientUser_1 = require("./structures/ClientUser");
const RPCError_1 = require("./utils/RPCError");
const rest_1 = require("@discordjs/rest");
const node_crypto_1 = __importDefault(require("node:crypto"));
const Transport_1 = require("./structures/Transport");
class Client extends async_event_emitter_1.AsyncEventEmitter {
get user() {
return __classPrivateFieldGet(this, _Client_user, "f");
}
get application() {
return __classPrivateFieldGet(this, _Client_application, "f");
}
get transport() {
return __classPrivateFieldGet(this, _Client_transport, "f");
}
get isConnected() {
return __classPrivateFieldGet(this, _Client_transport, "f").isConnected;
}
constructor(options) {
super();
/**
* application id
*/
Object.defineProperty(this, "clientId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* application secret
*/
Object.defineProperty(this, "clientSecret", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* pipe id
*/
Object.defineProperty(this, "pipeId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
_Client_refreshToken.set(this, void 0);
/**
* transport instance
*/
_Client_transport.set(this, void 0);
/**
* current user
*/
_Client_user.set(this, void 0);
/**
* current application
*/
_Client_application.set(this, void 0);
_Client_rest.set(this, void 0);
_Client_refreshTimeout.set(this, void 0);
_Client_connectionPromise.set(this, void 0);
_Client__nonceMap.set(this, new Map());
this.clientId = options.clientId;
this.clientSecret = options.clientSecret;
this.pipeId = options.pipeId;
__classPrivateFieldSet(this, _Client_rest, new rest_1.REST({ version: "10" }).setToken("this-is-a-dummy"), "f");
__classPrivateFieldSet(this, _Client_transport, !options.transport?.type || options.transport.type === "ipc"
? new IPC_1.IPCTransport({
client: this,
pathList: options.transport?.pathList
})
: new (options.transport.type === "websocket" ? WebSocket_1.WebSocketTransport : options.transport.type)({
client: this
}), "f");
__classPrivateFieldGet(this, _Client_transport, "f").on("message", (message) => {
if (message.cmd === "DISPATCH" && message.evt === "READY") {
if (message.data.user)
__classPrivateFieldSet(this, _Client_user, new ClientUser_1.ClientUser(this, message.data.user), "f");
if (message.data.config && message.data.config.cdn_host)
__classPrivateFieldGet(this, _Client_rest, "f").options.cdn = message.data.config.cdn_host;
this.emit("connected");
}
else {
if (message.nonce && __classPrivateFieldGet(this, _Client__nonceMap, "f").has(message.nonce)) {
const nonceObj = __classPrivateFieldGet(this, _Client__nonceMap, "f").get(message.nonce);
if (message.evt === "ERROR") {
nonceObj.error.code = message.data.code;
nonceObj.error.message = message.data.message;
nonceObj?.reject(nonceObj.error);
}
else
nonceObj?.resolve(message);
__classPrivateFieldGet(this, _Client__nonceMap, "f").delete(message.nonce);
}
this.emit(message.evt, message.data);
}
});
}
/**
* @hidden
*/
async request(cmd, args, evt) {
const error = new RPCError_1.RPCError(Transport_1.RPC_ERROR_CODE.UNKNOWN_ERROR);
RPCError_1.RPCError.captureStackTrace(error, this.request);
return new Promise((resolve, reject) => {
const nonce = node_crypto_1.default.randomUUID();
__classPrivateFieldGet(this, _Client_transport, "f").send({ cmd, args, evt, nonce });
__classPrivateFieldGet(this, _Client__nonceMap, "f").set(nonce, { resolve, reject, error });
});
}
// #endregion
// #region Authorization handlers
async authenticate(accessToken) {
const { application, user } = (await this.request("AUTHENTICATE", { access_token: accessToken })).data;
__classPrivateFieldSet(this, _Client_application, application, "f");
__classPrivateFieldSet(this, _Client_user, new ClientUser_1.ClientUser(this, user), "f");
this.emit("ready");
}
async refreshAccessToken() {
this.emit("debug", "CLIENT | Refreshing access token!");
const exchangeResponse = await __classPrivateFieldGet(this, _Client_rest, "f").post(v10_1.Routes.oauth2TokenExchange(), {
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret ?? "",
grant_type: "refresh_token",
refresh_token: __classPrivateFieldGet(this, _Client_refreshToken, "f") ?? ""
}),
headers: {
"content-type": "application/x-www-form-urlencoded"
},
passThroughBody: true
});
this.hanleAccessTokenResponse(exchangeResponse);
this.emit("debug", "CLIENT | Access token refreshed!");
return exchangeResponse.access_token;
}
hanleAccessTokenResponse(data) {
if (!("access_token" in data) ||
!("refresh_token" in data) ||
!("expires_in" in data) ||
!("token_type" in data))
throw new TypeError(`Invalid access token response!\nData: ${JSON.stringify(data, null, 2)}`);
__classPrivateFieldGet(this, _Client_rest, "f").setToken(data.access_token);
__classPrivateFieldGet(this, _Client_rest, "f").options.authPrefix = data.token_type;
__classPrivateFieldSet(this, _Client_refreshToken, data.refresh_token, "f");
__classPrivateFieldSet(this, _Client_refreshTimeout, setTimeout(() => void this.refreshAccessToken(), data.expires_in), "f");
}
async authorize(options) {
if (!this.clientSecret)
throw new ReferenceError("Client secret is required for authorization!");
let rpcToken;
if (options.useRPCToken) {
rpcToken = // Sadly discord-api-types doesn't have the oauth2/token/rpc endpoint
(await __classPrivateFieldGet(this, _Client_rest, "f").post("/oauth2/token/rpc", {
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret
}),
headers: {
"content-type": "application/x-www-form-urlencoded"
}
})).rpc_token;
}
const { code } = (await this.request("AUTHORIZE", {
scopes: options.scopes,
client_id: this.clientId,
rpc_token: options.useRPCToken ? rpcToken : undefined,
prompt: options.prompt ?? "consent"
})).data;
const exchangeResponse = await __classPrivateFieldGet(this, _Client_rest, "f").post(v10_1.Routes.oauth2TokenExchange(), {
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
grant_type: "authorization_code",
code
}),
headers: {
"content-type": "application/x-www-form-urlencoded"
},
passThroughBody: true
});
this.hanleAccessTokenResponse(exchangeResponse);
return exchangeResponse.access_token;
}
// #endregion
/**
* Used to subscribe to events. `evt` of the payload should be set to the event being subscribed to. `args` of the payload should be set to the args needed for the event.
* @param event event name now subscribed to
* @param args args for the event
* @returns an object to unsubscribe from the event
*/
async subscribe(event, args) {
await this.request("SUBSCRIBE", args, event);
return {
/**
* Unsubscribes from the event
*/
unsubscribe: () => this.request("UNSUBSCRIBE", args, event)
};
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* connect to the local rpc server
*/
async connect() {
if (__classPrivateFieldGet(this, _Client_connectionPromise, "f"))
return __classPrivateFieldGet(this, _Client_connectionPromise, "f");
const error = new RPCError_1.RPCError(Transport_1.RPC_ERROR_CODE.UNKNOWN_ERROR);
RPCError_1.RPCError.captureStackTrace(error, this.connect);
__classPrivateFieldSet(this, _Client_connectionPromise, new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
__classPrivateFieldSet(this, _Client_connectionPromise, undefined, "f");
error.code = Transport_1.CUSTOM_RPC_ERROR_CODE.CONNECTION_TIMEOUT;
error.message = "Connection timed out";
reject(error);
}, 10e3);
if (typeof timeout === "object" && "unref" in timeout)
timeout.unref();
this.once("connected", () => {
__classPrivateFieldSet(this, _Client_connectionPromise, undefined, "f");
__classPrivateFieldGet(this, _Client_transport, "f").once("close", (reason) => {
__classPrivateFieldGet(this, _Client__nonceMap, "f").forEach((promise) => {
promise.error.code =
typeof reason === "object" ? reason.code : Transport_1.CUSTOM_RPC_ERROR_CODE.CONNECTION_ENDED;
promise.error.message =
typeof reason === "object" ? reason.message : (reason ?? "Connection ended");
promise.reject(promise.error);
});
this.emit("disconnected");
});
clearTimeout(timeout);
resolve();
});
__classPrivateFieldGet(this, _Client_transport, "f").connect().catch(reject);
}), "f");
return __classPrivateFieldGet(this, _Client_connectionPromise, "f");
}
/**
* will try to authorize if a scope is specified, else it's the same as `connect()`
* @param options options for the authorization
*/
async login(options) {
await this.connect();
if (!options || !options.scopes) {
this.emit("ready");
return;
}
if (options.accessToken) {
await this.authenticate(options.accessToken);
return;
}
let accessToken = "";
if (options.refreshToken) {
__classPrivateFieldSet(this, _Client_refreshToken, options.refreshToken, "f");
accessToken = await this.refreshAccessToken();
}
else {
if (!this.clientSecret)
throw new ReferenceError("Client secret is required for authorization!");
accessToken = await this.authorize(options);
}
await this.authenticate(accessToken);
}
/**
* disconnects from the local rpc server
*/
async destroy() {
if (__classPrivateFieldGet(this, _Client_refreshTimeout, "f")) {
clearTimeout(__classPrivateFieldGet(this, _Client_refreshTimeout, "f"));
__classPrivateFieldSet(this, _Client_refreshTimeout, undefined, "f");
__classPrivateFieldSet(this, _Client_refreshToken, undefined, "f");
}
await __classPrivateFieldGet(this, _Client_transport, "f").close();
}
getCdn() {
return __classPrivateFieldGet(this, _Client_rest, "f").cdn;
}
}
exports.Client = Client;
_Client_refreshToken = new WeakMap(), _Client_transport = new WeakMap(), _Client_user = new WeakMap(), _Client_application = new WeakMap(), _Client_rest = new WeakMap(), _Client_refreshTimeout = new WeakMap(), _Client_connectionPromise = new WeakMap(), _Client__nonceMap = new WeakMap();