UNPKG

@foxxie/centra

Version:

A fork of Centra to support shortcut methods in Foxxie, and with typescript support.

227 lines (177 loc) 5.81 kB
import { join } from 'node:path'; import { RequestOptions, request } from 'node:http'; import { request as httpsRequest } from 'node:https'; import { stringify } from 'querystring'; import { createGunzip, createInflate } from 'zlib'; import { version, name } from '../package.json'; import { HttpMethod, ResOptions } from '../index'; import { CentraResponse } from './CentraResponse'; const supportedCompressions = ['gzip', 'deflate']; export class CentraRequest { public url: URL; public method: HttpMethod; public data: string | Buffer | null; public sendDataAs: 'form' | 'json' | 'buffer' | null; public reqHeaders: Record<string, any>; public streamEnabled: boolean; public compressionEnabled: boolean; public timeoutTime: number | null; public ua: string; public coreOptions: RequestOptions; public resOptions: ResOptions; public constructor(url: string | URL, method: HttpMethod = 'GET') { this.url = typeof url === 'string' ? new URL(url) : url; this.method = method; this.data = null; this.sendDataAs = null; this.reqHeaders = {}; this.streamEnabled = false; this.compressionEnabled = false; this.timeoutTime = null; this.ua = `${name}@${version}`; this.coreOptions = {}; this.resOptions = { maxBuffer: 50 * 1000000 // 50 MB }; return this; } public query(a1: string | Record<string, any>, a2?: any): this { if (typeof a1 === 'object') { Object.keys(a1).forEach(queryKey => { this.url.searchParams.append(queryKey, a1[queryKey]); }); } else this.url.searchParams.append(a1, a2); return this; } public path(relativePath: string): this { this.url.pathname = join(this.url.pathname, relativePath); return this; } public body(data: any, sendAs?: 'json' | 'buffer' | 'form'): this { this.sendDataAs = typeof data === 'object' && !sendAs && !Buffer.isBuffer(data) ? 'json' : sendAs ? (sendAs.toLowerCase() as typeof sendAs) : 'buffer'; this.data = this.sendDataAs === 'form' ? stringify(data) : this.sendDataAs === 'json' ? JSON.stringify(data) : data; return this; } public header(a1: string | Record<string, string>, a2?: string): this { if (typeof a1 === 'object') { Object.keys(a1).forEach(headerName => { this.reqHeaders[headerName.toLowerCase()] = a1[headerName]; }); } else this.reqHeaders[a1.toLowerCase()] = a2; return this; } public timeout(timeout: number): this { this.timeoutTime = timeout; return this; } public option<T extends keyof RequestOptions>(name: T, value: RequestOptions[T]): this { this.coreOptions[name] = value; return this; } public agent(agent: string): this { this.ua = agent; return this; } public stream(): this { this.streamEnabled = true; return this; } public compress(): this { this.compressionEnabled = true; if (!this.reqHeaders['accept-encoding']) this.reqHeaders['accept-encoding'] = supportedCompressions.join(', '); return this; } public async json<T = any>(): Promise<T> { const result = await this.send(); return result.json(); } public async raw(): Promise<Buffer> { const result = await this.send(); return result.body as Buffer; } public async text(): Promise<string> { const result = await this.send(); return result.text(); } public send(): Promise<CentraResponse> { return new Promise((resolve, reject) => { if (this.data) { if (!this.reqHeaders.hasOwnProperty('content-type')) { if (this.sendDataAs === 'json') { this.reqHeaders['content-type'] = 'application/json'; } else if (this.sendDataAs === 'form') { this.reqHeaders['content-type'] = 'application/x-www-form-urlencoded'; } } if (!this.reqHeaders.hasOwnProperty('content-length')) { this.reqHeaders['content-length'] = Buffer.byteLength(this.data); } } this.header('User-Agent', this.ua); const options = Object.assign( { protocol: this.url.protocol, host: this.url.hostname, port: this.url.port, path: this.url.pathname + (this.url.search === null ? '' : this.url.search), method: this.method, headers: this.reqHeaders }, this.coreOptions ); let req; const resHandler = res => { let stream = res; if (this.compressionEnabled) { if (res.headers['content-encoding'] === 'gzip') { stream = res.pipe(createGunzip()); } else if (res.headers['content-encoding'] === 'deflate') { stream = res.pipe(createInflate()); } } let centraRes; if (this.streamEnabled) { resolve(stream); } else { centraRes = new CentraResponse(res, this.resOptions); stream.on('error', err => { reject(err); }); stream.on('aborted', () => { reject(new Error('Server aborted request')); }); stream.on('data', chunk => { centraRes._addChunk(chunk); if (this.resOptions.maxBuffer !== null && centraRes.body.length > this.resOptions.maxBuffer) { stream.destroy(); reject('Received a response which was longer than acceptable when buffering. (' + this.body.length + ' bytes)'); } }); stream.on('end', () => { resolve(centraRes); }); } }; if (this.url.protocol === 'http:') { req = request(options, resHandler); } else if (this.url.protocol === 'https:') { req = httpsRequest(options, resHandler); } else if (this.url.protocol === 'localhost:') { req = request(options, resHandler); } else throw new Error('Bad URL protocol: ' + this.url.protocol); if (this.timeoutTime) { req.setTimeout(this.timeoutTime, () => { req.abort(); if (!this.streamEnabled) { reject(new Error('Timeout reached')); } }); } req.on('error', err => { reject(err); }); if (this.data) req.write(this.data); req.end(); }); } }