UNPKG

@xhayper/discord-rpc

Version:
305 lines (304 loc) 14.4 kB
"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();