mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 00:40:07 +00:00
feat(rest): Add events (#4245)
* feat(rest): Add events * Apply suggestions from code review Co-authored-by: Awesome Stickz <awesome@stickz.dev> * Remove timeTook, consolidate event types * Update packages/rest/src/manager.ts Co-authored-by: Awesome Stickz <awesome@stickz.dev> * Fix type error * Apply suggestions from code review Co-authored-by: Link <lts20050703@gmail.com> --------- Co-authored-by: Awesome Stickz <awesome@stickz.dev> Co-authored-by: Link <lts20050703@gmail.com>
This commit is contained in:
@@ -119,6 +119,12 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
token: options.token,
|
||||
version: options.version ?? DISCORD_API_VERSION,
|
||||
logger: options.logger ?? logger,
|
||||
events: {
|
||||
request: () => {},
|
||||
response: () => {},
|
||||
requestError: () => {},
|
||||
...options.events,
|
||||
},
|
||||
|
||||
routes: createRoutes(),
|
||||
|
||||
@@ -440,10 +446,16 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
loggingHeaders.authorization = `${authorizationScheme} tokenhere`
|
||||
}
|
||||
|
||||
const request = new Request(url, payload)
|
||||
rest.events.request(request, {
|
||||
body: options.requestBodyOptions?.body,
|
||||
})
|
||||
|
||||
rest.logger.debug(`sending request to ${url}`, 'with payload:', { ...payload, headers: loggingHeaders })
|
||||
const response = await fetch(url, payload).catch(async (error) => {
|
||||
const response = await fetch(request).catch(async (error) => {
|
||||
rest.logger.error(error)
|
||||
// Mark request and completed
|
||||
rest.events.requestError(request, error, { body: options.requestBodyOptions?.body })
|
||||
// Mark request as completed
|
||||
rest.invalidBucket.handleCompletedRequest(999, false)
|
||||
options.reject({
|
||||
ok: false,
|
||||
@@ -454,7 +466,15 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
})
|
||||
rest.logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`)
|
||||
|
||||
// Mark request and completed
|
||||
// Sometimes the Content-Type may be "application/json; charset=utf-8", for this reason, we need to check the start of the header
|
||||
const body = await (response.headers.get('Content-Type')?.startsWith('application/json') ? response.json() : response.text()).catch(() => null)
|
||||
|
||||
rest.events.response(request, response, {
|
||||
requestBody: options.requestBodyOptions?.body,
|
||||
responseBody: body,
|
||||
})
|
||||
|
||||
// Mark request as completed
|
||||
rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get(RATE_LIMIT_SCOPE_HEADER) === 'shared')
|
||||
|
||||
// Set the bucket id if it was available on the headers
|
||||
@@ -466,15 +486,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
rest.logger.debug(`Request to ${url} failed.`)
|
||||
|
||||
if (response.status !== HttpResponseCode.TooManyRequests) {
|
||||
const body = response.headers.get('Content-Type') === 'application/json' ? ((await response.json()) as object) : await response.text()
|
||||
|
||||
options.reject({ ok: false, status: response.status, statusText: response.statusText, body })
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the response body to avoid leaking memory
|
||||
await response.arrayBuffer()
|
||||
|
||||
rest.logger.debug(`Request to ${url} was ratelimited.`)
|
||||
// Too many attempts, get rid of request from queue.
|
||||
if (options.retryCount >= rest.maxRetryCount) {
|
||||
@@ -498,8 +513,8 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
return await options.retryRequest?.(options)
|
||||
}
|
||||
|
||||
// Discord sometimes sends no response with no content.
|
||||
options.resolve({ ok: true, status: response.status, body: response.status === HttpResponseCode.NoContent ? undefined : await response.text() })
|
||||
// Discord sometimes sends a response with no content
|
||||
options.resolve({ ok: true, status: response.status, body: response.status === HttpResponseCode.NoContent ? undefined : body })
|
||||
},
|
||||
|
||||
simplifyUrl(url, method) {
|
||||
@@ -569,12 +584,22 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
options.headers[rest.authorizationHeader] = rest.authorization
|
||||
}
|
||||
|
||||
const result = await fetch(`${rest.baseUrl}/v${rest.version}${route}`, rest.createRequestBody(method, options))
|
||||
const request = new Request(`${rest.baseUrl}/v${rest.version}${route}`, rest.createRequestBody(method, options))
|
||||
rest.events.request(request, {
|
||||
body: options?.body,
|
||||
})
|
||||
|
||||
const result = await fetch(request)
|
||||
|
||||
// Sometimes the Content-Type may be "application/json; charset=utf-8", for this reason, we need to check the start of the header
|
||||
const body = await (result.headers.get('Content-Type')?.startsWith('application/json') ? result.json() : result.text()).catch(() => null)
|
||||
|
||||
rest.events.response(request, result, {
|
||||
requestBody: options?.body,
|
||||
responseBody: body,
|
||||
})
|
||||
|
||||
if (!result.ok) {
|
||||
// Sometime the Content-Type may be "application/json; charset=utf-8", for this reason we need to check the start of the header
|
||||
const body = await (result.headers.get('Content-Type')?.startsWith('application/json') ? result.json() : result.text()).catch(() => null)
|
||||
|
||||
error.cause = Object.assign(Object.create(baseErrorPrototype), {
|
||||
ok: false,
|
||||
status: result.status,
|
||||
@@ -584,7 +609,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
throw error
|
||||
}
|
||||
|
||||
return result.status !== 204 ? await result.json() : undefined
|
||||
return result.status !== 204 ? (typeof body === 'string' ? JSON.parse(body) : body) : undefined
|
||||
}
|
||||
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
@@ -597,7 +622,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
await rest.processRequest(payload)
|
||||
},
|
||||
resolve: (data) => {
|
||||
resolve(data.status !== 204 ? JSON.parse(data.body ?? '{}') : undefined)
|
||||
resolve(data.status !== 204 ? (typeof data.body === 'string' ? JSON.parse(data.body) : data.body) : undefined)
|
||||
},
|
||||
reject: (reason) => {
|
||||
let errorText: string
|
||||
|
||||
@@ -196,6 +196,8 @@ export interface CreateRestManagerOptions {
|
||||
* @default logger // The logger exported by `@discordeno/utils`
|
||||
*/
|
||||
logger?: Pick<typeof logger, 'debug' | 'info' | 'warn' | 'error' | 'fatal'>
|
||||
/** Events for the rest manager */
|
||||
events?: Partial<RestManagerEvents>
|
||||
}
|
||||
|
||||
export interface RestManager {
|
||||
@@ -241,6 +243,8 @@ export interface RestManager {
|
||||
routes: RestRoutes
|
||||
/** The logger to use for the rest manager */
|
||||
logger: Pick<typeof logger, 'debug' | 'info' | 'warn' | 'error' | 'fatal'>
|
||||
/** Events for the rest manager */
|
||||
events: RestManagerEvents
|
||||
/** Allows the user to inject custom headers that will be sent with every request. */
|
||||
createBaseHeaders: () => Record<string, string>
|
||||
/** Whether or not the rest manager should keep objects in raw snake case from discord. */
|
||||
@@ -3251,7 +3255,8 @@ export interface RestRateLimitedPath {
|
||||
export interface RestRequestResponse {
|
||||
ok: boolean
|
||||
status: number
|
||||
body?: string
|
||||
/** The returned body parsed if it was JSON, otherwise it will be the raw body as a string */
|
||||
body?: string | object
|
||||
}
|
||||
|
||||
export interface RestRequestRejection {
|
||||
@@ -3263,3 +3268,29 @@ export interface RestRequestRejection {
|
||||
body?: string | object
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface RestManagerEvents {
|
||||
/**
|
||||
* Emitted when a request is made to the API.
|
||||
*
|
||||
* @remarks
|
||||
* The body that will be sent to the API is available in the `extra` parameter. Do not consume the body in the `Request` object and use the one in the `extra` parameter instead.
|
||||
*/
|
||||
request: (request: Request, extra: { body: any }) => void
|
||||
/**
|
||||
* Emitted when a response is received from the API.
|
||||
*
|
||||
* @remarks
|
||||
* This is fired for both successful and failed requests, you should check the Response object to determine if the request was successful or not.
|
||||
*
|
||||
* Both the request and the response body are available in the `extra` parameter. Do not consume the body in the `Request` or `Response` object and use the one in the `extra` parameter instead.
|
||||
*/
|
||||
response: (request: Request, response: Response, extra: { requestBody: any; responseBody: string | object }) => void
|
||||
/**
|
||||
* Emitted when a request errors due to fetch error.
|
||||
*
|
||||
* @remarks
|
||||
* The body that was sent to the API is available in the `extra` parameter.
|
||||
*/
|
||||
requestError: (request: Request, error: any, extra: { body: any }) => void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user