src/http.js
import fetch from 'node-fetch'
const DEFAULT_REQUEST_TIMEOUT = 1000 * 30 // 30 seconds
/**
* Class to communicate with the outside world.
* Wraps around fetch.
* @private
*/
class Http {
/**
* @param {number} [requestTimeout=DEFAULT_REQUEST_TIMEOUT] A time in ms
* after the start of a request when it should be timed out.
*/
constructor(requestTimeout = DEFAULT_REQUEST_TIMEOUT) {
/**
* @type {{ queue: Object, timeoutId: ?Number }}
* @private
* @readonly
*/
this._ratelimit = {
queue: [],
timeoutId: undefined,
}
/**
* @type {String}
* @private
*/
this._token = ''
/**
* @type {Number}
* @private
* @readonly
*/
this._requestTimeout = requestTimeout
}
/**
* @private
* @param {Object} request
*/
_enqueue(request) {
return new Promise((resolve, reject) => {
this._ratelimit.queue.push({
request,
resolve,
reject,
})
})
}
/**
* @private
* @param {number} timeLeft time left for the ratelimit in seconds.
*/
_setRatelimitTime(timeLeft) {
const info = this._ratelimit
if (info.timeoutId !== undefined) {
return
}
info.timeoutId = setInterval(() => {
for (const item of info.queue) {
fetch(item.request).then(item.resolve, item.reject)
}
info.queue = []
info.timeoutId = undefined
}, timeLeft*1000 + 10)
}
/**
* @param {Object} obj
* @returns {Request}
*/
makeRequest(obj) {
const init = {
method: obj.method,
timeout: this._requestTimeout,
headers: {
...obj.headers,
Authorization: 'Bearer ' + this._token,
'X-API-Client-ID': '12D8',
},
redirect: obj.redirect,
}
if (obj.data != null) {
init.body = JSON.stringify(obj.data)
init.headers['Content-Type'] = 'application/json;charset=UTF-8'
}
return new fetch.Request(obj.url, init)
}
/**
* @private
* @param {Object} obj
*/
async _request(obj) {
const request = this.makeRequest(obj)
const info = this._ratelimit
let res
if (info.timeoutId === undefined) {
res = await fetch(request)
} else {
res = await this._enqueue(request)
}
if (res.ok || res.status === 302) {
return res
}
try {
const body = await res.body()
const parsed = JSON.parse(body)
if ('SecondsLeft' in parsed) {
// Handle rate limit errors
this._setRatelimitTime(Number.parseInt(parsed.SecondsLeft, 10))
return this._request(obj)
}
} catch (_) {
return res
}
}
/**
* Gets the content at `url`
* @param {string} url
* @param {Object} [opt]
* @returns {Promise<Response>}
*/
get(url, opt) {
return this._request({
...opt,
method: 'get',
url: url,
})
}
/**
* Posts the given `data` to `url`
* @param {string} url
* @param {Object} [data]
* @param {Object} [opt]
* @returns {Promise<Response>}
*/
post(url, data, opt) {
return this._request({
...opt,
method: 'post',
url: url,
data: data,
})
}
/**
* Puts the given `data` to `url`
* @param {string} url
* @param {Object} [data]
* @param {Object} [opt]
* @returns {Promise<Response>}
*/
put(url, data, opt) {
return this._request({
...opt,
method: 'put',
url: url,
data: data,
})
}
/**
* Deletes the content at `url`
* @param {string} url
* @returns {Promise<Response>}
*/
delete(url) {
return this._request({
method: 'delete',
url: url,
})
}
}
export default Http