From 1d8dae864d8e85d83e894527488f651fe43ef37a Mon Sep 17 00:00:00 2001 From: ckohen Date: Sat, 2 Oct 2021 00:31:19 -0700 Subject: [PATCH 1/4] feat(Client): add apiResponse event for debugging --- package-lock.json | 21 +++++++++++++++++++-- package.json | 1 + src/rest/APIRequest.js | 32 ++++++++++++++++++++++++++++++++ src/rest/RequestHandler.js | 20 +++++++++++++++++++- src/util/Constants.js | 1 + typings/index.d.ts | 15 +++++++++++++++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77fa56fee9b4..87a18f9201d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@discordjs/collection": "^0.2.1", "@discordjs/form-data": "^3.0.1", "@sapphire/async-queue": "^1.1.5", + "@types/node-fetch": "^2.5.12", "@types/ws": "^8.2.0", "discord-api-types": "^0.23.1", "node-fetch": "^2.6.1", @@ -2307,6 +2308,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" }, + "node_modules/@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -5636,7 +5646,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -14018,6 +14027,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -16627,7 +16645,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", diff --git a/package.json b/package.json index dae2659dc591..f35cdfa5194f 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@discordjs/collection": "^0.2.1", "@discordjs/form-data": "^3.0.1", "@sapphire/async-queue": "^1.1.5", + "@types/node-fetch": "^2.5.12", "@types/ws": "^8.2.0", "discord-api-types": "^0.23.1", "node-fetch": "^2.6.1", diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index 2ec64baa33db..787c48f446fa 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -7,13 +7,36 @@ const { UserAgent } = require('../util/Constants'); let agent = null; +/** + * Respresents a request made to the Discord API + */ class APIRequest { constructor(rest, method, path, options) { this.rest = rest; + /** + * The client making this request + * @type {Client} + */ this.client = rest.client; + /** + * The http method used in this request + * @type {HTTPMethod} + */ this.method = method; + /** + * The API route identifying the ratelimit for this request + * @type {string} + */ this.route = options.route; + /** + * Additional options for this request + * @type {Object} + */ this.options = options; + /** + * The number of times this request has been previously made + * @type {number} + */ this.retries = 0; const { userAgentSuffix } = this.client.options; @@ -26,6 +49,10 @@ class APIRequest { .flatMap(([key, value]) => (Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]])); queryString = new URLSearchParams(query).toString(); } + /** + * The full path used to make the request + * @type {string} + */ this.path = `${path}${queryString && `?${queryString}`}`; } @@ -80,3 +107,8 @@ class APIRequest { } module.exports = APIRequest; + +/** + * @external HTTPMethod + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods} + */ diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index d80570dac224..b325a052b251 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -5,7 +5,7 @@ const DiscordAPIError = require('./DiscordAPIError'); const HTTPError = require('./HTTPError'); const RateLimitError = require('./RateLimitError'); const { - Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING }, + Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE }, } = require('../util/Constants'); const Util = require('../util/Util'); @@ -176,6 +176,19 @@ class RequestHandler { return this.execute(request); } + if (this.manager.client.listenerCount(API_RESPONSE)) { + /** + * Emitted after every api request has received a response. + * This event does not necessarily correlate to completion of the request, e.g. when hitting a rate limit. + * This is an informational event that is emitted quite frequently, + * it is highly recommended to check `request.path` to filter the data. + * @event Client#apiResponse + * @param {APIRequest} request The request that triggered this response + * @param {Response} response The response received from the discord API + */ + this.manager.client.emit(API_RESPONSE, request, res); + } + let sublimitTimeout; if (res.headers) { const serverDate = res.headers.get('date'); @@ -315,3 +328,8 @@ class RequestHandler { } module.exports = RequestHandler; + +/** + * @external Response + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Response} + */ diff --git a/src/util/Constants.js b/src/util/Constants.js index b6e488b796e9..7a62b57a8149 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -124,6 +124,7 @@ exports.Opcodes = { exports.Events = { RATE_LIMIT: 'rateLimit', INVALID_REQUEST_WARNING: 'invalidRequestWarning', + API_RESPONSE: 'apiResponse', CLIENT_READY: 'ready', APPLICATION_COMMAND_CREATE: 'applicationCommandCreate', APPLICATION_COMMAND_DELETE: 'applicationCommandDelete', diff --git a/typings/index.d.ts b/typings/index.d.ts index 7d8c60a0ae76..d66917279633 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -49,6 +49,7 @@ import { import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; import { AgentOptions } from 'node:https'; +import { Response } from 'node-fetch'; import { Stream } from 'node:stream'; import { MessagePort, Worker } from 'node:worker_threads'; import * as WebSocket from 'ws'; @@ -183,6 +184,18 @@ export abstract class AnonymousGuild extends BaseGuild { public splashURL(options?: StaticImageURLOptions): string | null; } +export class APIRequest { + private constructor(rest: unknown, method: string, path: string, options: unknown); + public client: Client; + public method: string; + public options: unknown; + public path: string; + private rest: unknown; + public retries: number; + public route: string; + private make(): Promise; +} + export abstract class Application extends Base { protected constructor(client: Client, data: RawApplicationData); public readonly createdAt: Date; @@ -3439,6 +3452,7 @@ export interface ChannelWebhookCreateOptions { } export interface ClientEvents { + apiResponse: [request: APIRequest, response: Response]; applicationCommandCreate: [command: ApplicationCommand]; applicationCommandDelete: [command: ApplicationCommand]; applicationCommandUpdate: [oldCommand: ApplicationCommand | null, newCommand: ApplicationCommand]; @@ -3677,6 +3691,7 @@ export interface ConstantsColors { export interface ConstantsEvents { RATE_LIMIT: 'rateLimit'; INVALID_REQUEST_WARNING: 'invalidRequestWarning'; + API_RESPONSE: 'apiResponse'; CLIENT_READY: 'ready'; APPLICATION_COMMAND_CREATE: 'applicationCommandCreate'; APPLICATION_COMMAND_DELETE: 'applicationCommandDelete'; From 8ee1cce04c064575d06ff52b11b384a8d1a70284 Mon Sep 17 00:00:00 2001 From: ckohen Date: Sun, 3 Oct 2021 19:04:09 -0700 Subject: [PATCH 2/4] refactor: don't emit full class Co-Authored-By: Shubham Parihar Co-Authored-By: Rodry <38259440+ImRodry@users.noreply.github.com> Co-Authored-By: Vlad Frangu --- src/rest/APIRequest.js | 32 -------------------------------- src/rest/RequestHandler.js | 29 +++++++++++++++++++++++++++-- typings/index.d.ts | 20 ++++++++------------ 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index 787c48f446fa..2ec64baa33db 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -7,36 +7,13 @@ const { UserAgent } = require('../util/Constants'); let agent = null; -/** - * Respresents a request made to the Discord API - */ class APIRequest { constructor(rest, method, path, options) { this.rest = rest; - /** - * The client making this request - * @type {Client} - */ this.client = rest.client; - /** - * The http method used in this request - * @type {HTTPMethod} - */ this.method = method; - /** - * The API route identifying the ratelimit for this request - * @type {string} - */ this.route = options.route; - /** - * Additional options for this request - * @type {Object} - */ this.options = options; - /** - * The number of times this request has been previously made - * @type {number} - */ this.retries = 0; const { userAgentSuffix } = this.client.options; @@ -49,10 +26,6 @@ class APIRequest { .flatMap(([key, value]) => (Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]])); queryString = new URLSearchParams(query).toString(); } - /** - * The full path used to make the request - * @type {string} - */ this.path = `${path}${queryString && `?${queryString}`}`; } @@ -107,8 +80,3 @@ class APIRequest { } module.exports = APIRequest; - -/** - * @external HTTPMethod - * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods} - */ diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index b325a052b251..848e371fd19b 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -162,6 +162,16 @@ class RequestHandler { } this.manager.globalRemaining--; + /** + * Represents a request that will or has been made to the Discord API + * @typedef {Object} APIRequest + * @property {HTTPMethod} method The HTTP method used in this request + * @property {string} path The full path used to make the request + * @property {string} route The API route identifying the ratelimit for this request + * @property {Object} options Additional options for this request + * @property {number} retries The number of times this request has been attempted + */ + // Perform the request let res; try { @@ -178,7 +188,7 @@ class RequestHandler { if (this.manager.client.listenerCount(API_RESPONSE)) { /** - * Emitted after every api request has received a response. + * Emitted after every API request has received a response. * This event does not necessarily correlate to completion of the request, e.g. when hitting a rate limit. * This is an informational event that is emitted quite frequently, * it is highly recommended to check `request.path` to filter the data. @@ -186,7 +196,17 @@ class RequestHandler { * @param {APIRequest} request The request that triggered this response * @param {Response} response The response received from the discord API */ - this.manager.client.emit(API_RESPONSE, request, res); + this.manager.client.emit( + API_RESPONSE, + { + method: request.method, + path: request.path, + route: request.route, + options: request.options, + retries: request.retries, + }, + res.clone(), + ); } let sublimitTimeout; @@ -329,6 +349,11 @@ class RequestHandler { module.exports = RequestHandler; +/** + * @external HTTPMethod + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods} + */ + /** * @external Response * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Response} diff --git a/typings/index.d.ts b/typings/index.d.ts index d66917279633..03f1917c2963 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -184,18 +184,6 @@ export abstract class AnonymousGuild extends BaseGuild { public splashURL(options?: StaticImageURLOptions): string | null; } -export class APIRequest { - private constructor(rest: unknown, method: string, path: string, options: unknown); - public client: Client; - public method: string; - public options: unknown; - public path: string; - private rest: unknown; - public retries: number; - public route: string; - private make(): Promise; -} - export abstract class Application extends Base { protected constructor(client: Client, data: RawApplicationData); public readonly createdAt: Date; @@ -3142,6 +3130,14 @@ export interface APIErrors { STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007; } +export interface APIRequest { + method: 'get' | 'post' | 'delete' | 'patch' | 'put'; + options: unknown; + path: string; + retries: number; + route: string; +} + export interface ApplicationAsset { name: string; id: Snowflake; From 51cfb4948f7d4f68e7e32dea0f56a65c50efa7a4 Mon Sep 17 00:00:00 2001 From: ckohen Date: Sun, 3 Oct 2021 19:08:29 -0700 Subject: [PATCH 3/4] feat(Client): add apiRequest event Co-Authored-By: SpaceEEC <24881032+SpaceEEC@users.noreply.github.com> --- src/rest/RequestHandler.js | 20 +++++++++++++++++++- src/util/Constants.js | 1 + typings/index.d.ts | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index 848e371fd19b..93f2ba629531 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -5,7 +5,7 @@ const DiscordAPIError = require('./DiscordAPIError'); const HTTPError = require('./HTTPError'); const RateLimitError = require('./RateLimitError'); const { - Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE }, + Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST }, } = require('../util/Constants'); const Util = require('../util/Util'); @@ -172,6 +172,24 @@ class RequestHandler { * @property {number} retries The number of times this request has been attempted */ + if (this.manager.client.listenerCount(API_REQUEST)) { + /** + * Emitted before every API request. + * This event can emit several times for the same request, e.g. when hitting a rate limit. + * This is an informational event that is emitted quite frequently, + * it is highly recommended to check `request.path` to filter the data. + * @event Client#apiRequest + * @param {APIRequest} request The request that is about to be sent + */ + this.manager.client.emit(API_REQUEST, { + method: request.method, + path: request.path, + route: request.route, + options: request.options, + retries: request.retries, + }); + } + // Perform the request let res; try { diff --git a/src/util/Constants.js b/src/util/Constants.js index 7a62b57a8149..82c8c5fa3765 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -125,6 +125,7 @@ exports.Events = { RATE_LIMIT: 'rateLimit', INVALID_REQUEST_WARNING: 'invalidRequestWarning', API_RESPONSE: 'apiResponse', + API_REQUEST: 'apiRequest', CLIENT_READY: 'ready', APPLICATION_COMMAND_CREATE: 'applicationCommandCreate', APPLICATION_COMMAND_DELETE: 'applicationCommandDelete', diff --git a/typings/index.d.ts b/typings/index.d.ts index 03f1917c2963..4816402f9216 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -3449,6 +3449,7 @@ export interface ChannelWebhookCreateOptions { export interface ClientEvents { apiResponse: [request: APIRequest, response: Response]; + apiRequest: [request: APIRequest]; applicationCommandCreate: [command: ApplicationCommand]; applicationCommandDelete: [command: ApplicationCommand]; applicationCommandUpdate: [oldCommand: ApplicationCommand | null, newCommand: ApplicationCommand]; @@ -3688,6 +3689,7 @@ export interface ConstantsEvents { RATE_LIMIT: 'rateLimit'; INVALID_REQUEST_WARNING: 'invalidRequestWarning'; API_RESPONSE: 'apiResponse'; + API_REQUEST: 'apiRequest'; CLIENT_READY: 'ready'; APPLICATION_COMMAND_CREATE: 'applicationCommandCreate'; APPLICATION_COMMAND_DELETE: 'applicationCommandDelete'; From 9c56db119d2ce96a131c03e9074186a0dbfdebdf Mon Sep 17 00:00:00 2001 From: ckohen Date: Thu, 7 Oct 2021 02:22:05 -0700 Subject: [PATCH 4/4] docs: fix Discord capitalization --- src/rest/RequestHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index 93f2ba629531..6ad8fdfa703a 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -212,7 +212,7 @@ class RequestHandler { * it is highly recommended to check `request.path` to filter the data. * @event Client#apiResponse * @param {APIRequest} request The request that triggered this response - * @param {Response} response The response received from the discord API + * @param {Response} response The response received from the Discord API */ this.manager.client.emit( API_RESPONSE,