diff --git a/src/helpers/members/send_direct_message.ts b/src/helpers/members/send_direct_message.ts index a4ff63bbf..17270c087 100644 --- a/src/helpers/members/send_direct_message.ts +++ b/src/helpers/members/send_direct_message.ts @@ -16,7 +16,7 @@ export async function sendDirectMessage( recipient_id: memberId, }) as DMChannelCreatePayload; const discordenoChannel = await structures.createDiscordenoChannel( - dmChannelData as unknown as ChannelCreatePayload, + dmChannelData as unknown as DiscordChannel, ); // Recreate the channel and add it undert he users id await cacheHandlers.set("channels", memberId, discordenoChannel); diff --git a/src/rest/create_request_body.ts b/src/rest/create_request_body.ts index 3751b48c8..65d9173ca 100644 --- a/src/rest/create_request_body.ts +++ b/src/rest/create_request_body.ts @@ -1,8 +1,12 @@ +import { FileContent } from "../types/misc/file_content.ts"; import { USER_AGENT } from "../util/constants.ts"; -import { rest } from "./rest.ts"; +import { rest, RestPayload, RestRequest } from "./rest.ts"; /** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */ -export function createRequestBody(queuedRequest: QueuedRequest) { +export function createRequestBody(queuedRequest: { + request: RestRequest; + payload: RestPayload; +}) { const headers: { [key: string]: string } = { Authorization: rest.token, "User-Agent": USER_AGENT, @@ -16,7 +20,7 @@ export function createRequestBody(queuedRequest: QueuedRequest) { // IF A REASON IS PROVIDED ENCODE IT IN HEADERS if (queuedRequest.payload.body?.reason) { headers["X-Audit-Log-Reason"] = encodeURIComponent( - queuedRequest.payload.body.reason, + queuedRequest.payload.body.reason as string ); } @@ -25,12 +29,12 @@ export function createRequestBody(queuedRequest: QueuedRequest) { const form = new FormData(); form.append( "file", - queuedRequest.payload.body.file.blob, - queuedRequest.payload.body.file.name, + (queuedRequest.payload.body.file as FileContent).blob, + (queuedRequest.payload.body.file as FileContent).name ); form.append( "payload_json", - JSON.stringify({ ...queuedRequest.payload.body, file: undefined }), + JSON.stringify({ ...queuedRequest.payload.body, file: undefined }) ); queuedRequest.payload.body.file = form; } else if ( @@ -42,8 +46,9 @@ export function createRequestBody(queuedRequest: QueuedRequest) { return { headers, - body: queuedRequest.payload.body?.file || - JSON.stringify(queuedRequest.payload.body), + body: + (queuedRequest.payload.body?.file || + JSON.stringify(queuedRequest.payload.body)) as FormData | string, method: queuedRequest.request.method.toUpperCase(), }; } diff --git a/src/rest/handle_payload.ts b/src/rest/handle_payload.ts deleted file mode 100644 index 3f0700a81..000000000 --- a/src/rest/handle_payload.ts +++ /dev/null @@ -1,34 +0,0 @@ -// SERVERLESS REST CLIENT THAT CAN WORK ACROSS SHARDS/WORKERS TO COMMUNICATE GLOBAL RATE LIMITS EASILY -import { rest } from "./rest.ts"; - -/** Handler function for every request. Converts to json, verified authorization & requirements and begins processing the request */ -export async function handlePayload( - request: Request, -) { - // INSTANTLY IGNORE ANY REQUESTS THAT DON'T HAVE THE SECRET AUTHORIZATION KEY - const authorization = request.headers.get("authorization"); - if (authorization !== rest.authorization) return; - // READ BUFFER AFTER AUTH CHECK - const buffer = await Deno.readAll(request.body); - try { - // CONVERT THE BODY TO JSON - const data = JSON.parse(new TextDecoder().decode(buffer)); - if ( - !["GET", "POST", "PUT", "PATCH", "HEAD", "DELETE"].includes( - request.method, - ) - ) { - return request.respond( - { - status: 400, - body: JSON.stringify({ error: "Invalid METHOD." }), - }, - ); - } - - // PROCESS THE REQUEST - await rest.processRequest(request, { body: data, retryCount: 0 }); - } catch (error) { - rest.eventHandlers.error("serverRequest", error); - } -} diff --git a/src/rest/mod.ts b/src/rest/mod.ts index 4d4c4225d..4667ec676 100644 --- a/src/rest/mod.ts +++ b/src/rest/mod.ts @@ -1,7 +1,6 @@ export * from "./check_rate_limits.ts"; export * from "./cleanup_queues.ts"; export * from "./create_request_body.ts"; -export * from "./handle_payload.ts"; export * from "./process_queue.ts"; export * from "./process_rate_limited_paths.ts"; export * from "./process_request.ts"; diff --git a/src/rest/process_queue.ts b/src/rest/process_queue.ts index 8825fe697..2139e226d 100644 --- a/src/rest/process_queue.ts +++ b/src/rest/process_queue.ts @@ -30,7 +30,6 @@ export async function processQueue(id: string) { // IF THIS DOESNT HAVE ANY ITEMS JUST CANCEL, THE CLEANER WILL REMOVE IT. if (!queuedRequest) return; - const basicURL = rest.simplifyUrl( queuedRequest.request.url, queuedRequest.request.method.toUpperCase() @@ -42,8 +41,9 @@ export async function processQueue(id: string) { // PAUSE FOR THIS SPECIFC REQUEST await delay(urlResetIn); continue; - } // IF A BUCKET EXISTS, CHECK THE BUCKET'S RATE LIMITS + } + // IF A BUCKET EXISTS, CHECK THE BUCKET'S RATE LIMITS const bucketResetIn = queuedRequest.payload.bucketId ? rest.checkRateLimits(queuedRequest.payload.bucketId) : false; @@ -172,7 +172,7 @@ export async function processQueue(id: string) { } catch (error) { // SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR rest.eventHandlers.fetchFailed(queuedRequest.payload, error); - queuedRequest.request.reject(error); + queuedRequest.request.reject?.(error); queuedRequest.request.respond({ status: 404, body: JSON.stringify({ error }), diff --git a/src/rest/process_request.ts b/src/rest/process_request.ts index 89f965e9c..722e2bde5 100644 --- a/src/rest/process_request.ts +++ b/src/rest/process_request.ts @@ -1,10 +1,10 @@ import { BASE_URL } from "../util/constants.ts"; -import { rest } from "./rest.ts"; +import { rest, RestPayload, RestRequest } from "./rest.ts"; /** Processes a request and assigns it to a queue or creates a queue if none exists for it. */ export async function processRequest( - request: ServerRequest, - payload: RunMethodOptions + request: RestRequest, + payload: RestPayload ) { const route = request.url.substring(request.url.indexOf("api/")); const parts = route.split("/"); diff --git a/src/rest/process_request_headers.ts b/src/rest/process_request_headers.ts index e6f882ce7..bb855d616 100644 --- a/src/rest/process_request_headers.ts +++ b/src/rest/process_request_headers.ts @@ -9,7 +9,8 @@ export function processRequestHeaders(url: string, headers: Headers) { const retryAfter = headers.get("x-ratelimit-reset-after"); const reset = Date.now() + Number(retryAfter) * 1000; const global = headers.get("x-ratelimit-global"); - const bucketId = headers.get("x-ratelimit-bucket"); + // undefined override null needed for typings + const bucketId = headers.get("x-ratelimit-bucket") || undefined; // IF THERE IS NO REMAINING RATE LIMIT, MARK IT AS RATE LIMITED if (remaining === "0") { @@ -55,7 +56,7 @@ export function processRequestHeaders(url: string, headers: Headers) { } } - if (ratelimited && !rest.processingRateLimitedPaths) { + if (ratelimited && rest.processingRateLimitedPaths) { rest.processRateLimitedPaths(); } return ratelimited ? bucketId : undefined; diff --git a/src/rest/rest.ts b/src/rest/rest.ts index e18e45d3f..91d6a0b97 100644 --- a/src/rest/rest.ts +++ b/src/rest/rest.ts @@ -1,7 +1,6 @@ import { checkRateLimits } from "./check_rate_limits.ts"; import { cleanupQueues } from "./cleanup_queues.ts"; import { createRequestBody } from "./create_request_body.ts"; -import { handlePayload } from "./handle_payload.ts"; import { processQueue } from "./process_queue.ts"; import { processRateLimitedPaths } from "./process_rate_limited_paths.ts"; import { processRequest } from "./process_request.ts"; @@ -17,25 +16,30 @@ export const rest = { apiVersion: "8", /** The secret authorization key to confirm that this was a request made by you and not a DDOS attack. */ authorization: "discordeno_best_lib_ever", - pathQueues: new Map(), + pathQueues: new Map< + string, + { + request: RestRequest; + payload: RestPayload; + }[] + >(), processingQueue: false, processingRateLimitedPaths: false, globallyRateLimited: false, - ratelimitedPaths: new Map(), + ratelimitedPaths: new Map(), eventHandlers: { // BY DEFAULT WE WILL LOG ALL ERRORS TO CONSOLE. USER CAN CHOOSE TO OVERRIDE error: function (...args: unknown[]) {}, // PLACEHOLDERS TO ALLOW USERS TO CUSTOMIZE debug: function (type: string, error: string | Record) {}, - fetching(payload: Record) {}, - fetched(payload: Record) {}, - fetchSuccess(payload: Record) {}, - fetchFailed(payload: Record, error: any) {}, + fetching(payload: RestPayload) {}, + fetched(payload: RestPayload) {}, + fetchSuccess(payload: RestPayload) {}, + fetchFailed(payload: RestPayload, error: unknown) {}, globallyRateLimited(url: string, resetsAt: number) {}, - retriesMaxed(payload: Record) {}, + retriesMaxed(payload: RestPayload) {}, }, /** Handler function for every request. Converts to json, verified authorization & requirements and begins processing the request */ - handlePayload, checkRateLimits, cleanupQueues, processQueue, @@ -46,3 +50,22 @@ export const rest = { runMethod, simplifyUrl, }; + +export interface RestRequest { + url: string; + method: string; + respond: (payload: { status: number; body?: string }) => unknown; + reject?: (error: unknown) => unknown; +} + +export interface RestPayload { + bucketId?: string; + body?: Record; + retryCount: number; +} + +export interface RestRateLimitedPath { + url: string; + resetTimestamp: number; + bucketId?: string; +} diff --git a/src/rest/run_method.ts b/src/rest/run_method.ts index 183f7e439..74999b16c 100644 --- a/src/rest/run_method.ts +++ b/src/rest/run_method.ts @@ -1,13 +1,13 @@ import { API_VERSION, BASE_URL, IMAGE_BASE_URL } from "../util/constants.ts"; import { rest } from "./rest.ts"; -export function runMethod( +export async function runMethod( method: "get" | "post" | "put" | "delete" | "patch", url: string, body?: unknown, retryCount = 0, - bucketId?: string | null, -): Promise { + bucketId?: string +) { rest.eventHandlers.debug?.("requestCreate", { method, url, @@ -24,22 +24,18 @@ export function runMethod( !url.startsWith(`${BASE_URL}/v${API_VERSION}`) && !url.startsWith(IMAGE_BASE_URL) ) { - return fetch(url, { + const result = await fetch(url, { body: JSON.stringify(body || {}), headers: { authorization: rest.authorization, }, method: method.toUpperCase(), - }) - .then((res) => { - if (res.status === 204) return undefined; + }).catch((error) => { + console.error(error); + throw errorStack; + }); - return (res.json() as unknown) as T; - }) - .catch((error) => { - console.error(error); - throw errorStack; - }); + return result.status !== 204 ? await result.json() : undefined; } // No proxy so we need to handle all rate limiting and such @@ -54,11 +50,9 @@ export function runMethod( }, { bucketId, - url, - method, - body, + body: body as Record | undefined, retryCount, - }, + } ); }); }