mirror of
https://codeberg.org/ashley/poke.git
synced 2024-11-27 04:38:14 +01:00
168 lines
4.8 KiB
JavaScript
168 lines
4.8 KiB
JavaScript
|
/*
|
||
|
Internet Systems Consortium license
|
||
|
|
||
|
Copyright (c) 2017-2018, dragonfire535
|
||
|
|
||
|
https://github.com/dragonfire535/node-superfetch
|
||
|
|
||
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||
|
with or without fee is hereby granted, provided that the above copyright notice
|
||
|
and this permission notice appear in all copies.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||
|
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||
|
THIS SOFTWARE.
|
||
|
|
||
|
*/
|
||
|
const fetch = require('node-fetch');
|
||
|
const FormData = require('form-data');
|
||
|
const { URL } = require('url');
|
||
|
const { METHODS } = require('http');
|
||
|
|
||
|
class Request {
|
||
|
constructor(options) {
|
||
|
if (!options.url) throw new Error('The "url" option is required.');
|
||
|
this.url = new URL(options.url);
|
||
|
this.method = options.method ? options.method.toUpperCase() : 'GET';
|
||
|
if (!METHODS.includes(this.method)) throw new Error(`The method "${this.method}" is not supported.`);
|
||
|
this.headers = options.headers || {};
|
||
|
this.body = options.body || null;
|
||
|
this.redirectCount = typeof options.redirects === 'undefined' ? 20 : options.redirects;
|
||
|
this.agent = options.agent || null;
|
||
|
this.noResultData = options.noResultData || false;
|
||
|
}
|
||
|
|
||
|
async _request() {
|
||
|
const response = await fetch(this.url.toString(), {
|
||
|
method: this.method,
|
||
|
headers: this.headers,
|
||
|
follow: this.redirectCount,
|
||
|
body: this.body,
|
||
|
agent: this.agent
|
||
|
});
|
||
|
let raw = null;
|
||
|
if (!this.noResultData) raw = await response.buffer();
|
||
|
const headers = {};
|
||
|
for (const [header, value] of response.headers.entries()) headers[header] = value;
|
||
|
const res = {
|
||
|
status: response.status,
|
||
|
statusText: response.statusText,
|
||
|
headers,
|
||
|
url: response.url,
|
||
|
redirected: response.redirected,
|
||
|
ok: response.ok,
|
||
|
raw,
|
||
|
get text() {
|
||
|
if (this.noResultData) return null;
|
||
|
return raw.toString();
|
||
|
},
|
||
|
get body() {
|
||
|
if (this.noResultData) return null;
|
||
|
if (/application\/json/gi.test(headers['content-type'])) {
|
||
|
try {
|
||
|
return JSON.parse(raw.toString());
|
||
|
} catch (err) {
|
||
|
return raw.toString();
|
||
|
}
|
||
|
} else {
|
||
|
return raw;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
if (!response.ok) {
|
||
|
const err = new Error(`${res.status} ${res.statusText}`);
|
||
|
Object.assign(err, res);
|
||
|
throw err;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
then(resolver, rejector) {
|
||
|
return this._request().then(resolver, rejector);
|
||
|
}
|
||
|
|
||
|
catch(rejector) {
|
||
|
return this.then(null, rejector);
|
||
|
}
|
||
|
|
||
|
end(cb) {
|
||
|
return this.then(
|
||
|
response => cb ? cb(null, response) : response,
|
||
|
err => cb ? cb(err, err.status ? err : null) : err
|
||
|
);
|
||
|
}
|
||
|
|
||
|
query(queryOrName, value) {
|
||
|
if (typeof queryOrName === 'object') {
|
||
|
for (const [param, val] of Object.entries(queryOrName)) this.url.searchParams.append(param, val);
|
||
|
} else if (typeof queryOrName === 'string' && value) {
|
||
|
this.url.searchParams.append(queryOrName, value);
|
||
|
} else {
|
||
|
throw new TypeError('The "query" parameter must be either an object or a query field.');
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
set(headersOrName, value) {
|
||
|
if (typeof headersOrName === 'object') {
|
||
|
for (const [header, val] of Object.entries(headersOrName)) this.headers[header] = val;
|
||
|
} else if (typeof headersOrName === 'string' && value) {
|
||
|
this.headers[headersOrName] = value;
|
||
|
} else {
|
||
|
throw new TypeError('The "headers" parameter must be either an object or a header field.');
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
attach(...args) {
|
||
|
if (!this.body || !(this.body instanceof FormData)) this.body = new FormData();
|
||
|
if (typeof args[0] === 'object') {
|
||
|
for (const [key, val] of Object.entries(args[0])) this.attach(key, val);
|
||
|
} else {
|
||
|
this.body.append(...args);
|
||
|
}
|
||
|
this.set(this.body.getHeaders());
|
||
|
this.set('content-length', this.body.getLengthSync());
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
send(body, raw = false) {
|
||
|
if (body instanceof FormData) raw = true;
|
||
|
if (!raw && body !== null && typeof body === 'object') {
|
||
|
const header = this.headers['content-type'];
|
||
|
if (header) {
|
||
|
if (/application\/json/gi.test(header)) body = JSON.stringify(body);
|
||
|
} else {
|
||
|
this.set('content-type', 'application/json');
|
||
|
body = JSON.stringify(body);
|
||
|
}
|
||
|
}
|
||
|
this.body = body;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
redirects(amount) {
|
||
|
if (typeof amount !== 'number') throw new TypeError('The "amount" parameter must be a number.');
|
||
|
this.redirectCount = amount;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
agent(agent) {
|
||
|
this.agent = agent;
|
||
|
return this;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const method of METHODS) {
|
||
|
if (!/^[A-Z$_]+$/gi.test(method)) continue;
|
||
|
Request[method.toLowerCase()] = (url, options) => new Request({ url, method, ...options });
|
||
|
}
|
||
|
|
||
|
Request.version = "1.0.0";
|
||
|
|
||
|
module.exports = Request;
|