Skip to content

Instantly share code, notes, and snippets.

@SagnikPradhan
Last active April 3, 2020 08:53
Show Gist options
  • Save SagnikPradhan/d1bce39db1424d0af1fe9e88943d7d8c to your computer and use it in GitHub Desktop.
Save SagnikPradhan/d1bce39db1424d0af1fe9e88943d7d8c to your computer and use it in GitHub Desktop.
Request Function in Typescript for a Discord API Wrapper
import https from 'https';
import {validErrors, AppError} from '../../Error-Management';
// All errors originating here are of the same type
const errName = validErrors['DISCORD_CLIENT_INTERNAL'];
// Better object
type ValidObjectValues = string|number|boolean|string[]|number[]|boolean[];
type BetterObject<T> = {[index: string]: T|BetterObject<T>};
// Request Options
interface RequestOptions {
body: BetterObject<ValidObjectValues>|Multipart;
headers: {
'Content-Type': 'application/json'|'multipart/form-data';
'User-Agent': string;
[header: string]: string;
};
}
/**
* Multipart Form Data
*/
export class Multipart {
boundary = 'discordtslibraryboundary';
#multipart = '';
/**
* Create a Multipart form
* @param data - Data to convert as Multipart
*/
constructor(...data: {
name: string;
data: Buffer|BetterObject<string|number|boolean>|string;
fileName?: string;
}[]) {
if (data.length > 0) {
// Add each of them
data.forEach(({name, data, fileName}) =>
this.append(name, data, fileName));
}
}
/**
* Add Field to multipart
* @param name - Name of the field
* @param data - Data to be sent
* @param fileName - Filename for files
*/
append(
name: string,
data: Buffer|BetterObject<string|number|boolean>|string,
fileName?: string,
): void {
if (!data) return;
// Field Start
let str = `\r\n--${this.boundary}\r\n`;
// Sub headers
str += `Content-Disposition: form-data; name="${name}"`;
str += fileName ? `; filename="${fileName}"` : '';
// Content Type
// Files
if (data instanceof Buffer) {
str += `\r\nContent-Type: application/octet-stream`;
};
// JSON Payloads
if (typeof(data) == 'object' && !fileName) {
str += `\r\nContent-Type: application/json`;
data = JSON.stringify(data);
}
// Data
str += '\r\n\r\n';
str += data;
this.#multipart += str;
}
/**
* Complete current multipart form and return
*/
parse(): string {
return this.#multipart + `\r\n--${this.boundary}--`;
}
}
/**
* Parse body into string
* @param type - Type of body
* @param body - Body itself
*/
function parseBody(body: BetterObject<ValidObjectValues> | Multipart): string {
// If Multipart body
if (body instanceof Multipart) return body.parse();
// If JSON Body
if (['object', 'boolean', 'string', 'number'].includes(typeof body)) {
return JSON.stringify(body);
}
// Other types not supported
throw new AppError(
errName, 'Invalid Request Body', {bodyType: typeof body}, false,
);
}
/**
* HTTPS Request Function
* @param path - URL to request to
* @param method - HTTP Method to use
* @param options - Request options
*/
export function request(
path: string,
method: 'GET' | 'POST' | 'PATCH' | 'UPDATE' | 'DELETE',
options: RequestOptions,
): Promise<void> {
return new Promise((resolve, reject) => {
const reqURL = new URL(path);
// Make request
const clientRequest = https.request(reqURL, {
method,
headers: options.headers,
}, (response) => {
let chunks = '';
// Handle Errors
response.on('error', ({name, message}) => {
reject(new AppError(
errName, 'Error on Response', {name, message}, false,
));
});
response.on('data', (chunk) => chunks += chunk);
response.on('end', () => {
// Try parsing the response
try {
resolve(JSON.parse(chunks));
} catch ({name, message}) {
reject(new AppError(
errName, 'Error on Parsing Response', {name, message}, false,
));
}
});
});
// Handle Errors
clientRequest.on('error', ({name, message}) => {
reject(new AppError(
errName, 'Error on Request', {name, message}, false,
));
});
// Write body if available
if (options.body) clientRequest.write(parseBody(options.body));
// End Request
clientRequest.end();
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment