Files
shields/core/base-service/node-fetch.js
2021-10-08 17:27:50 +01:00

102 lines
3.1 KiB
JavaScript

import { URL, URLSearchParams } from 'url'
import fetch from 'node-fetch'
import { Inaccessible, InvalidResponse } from './errors.js'
const userAgent = 'Shields.io/2003a'
function object2URLSearchParams(obj) {
const qs = {}
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
continue
} else if (value === null) {
qs[key] = ''
} else if (['string', 'number', 'boolean'].includes(typeof value)) {
qs[key] = value
}
}
return new URLSearchParams(qs)
}
function request2NodeFetch({ url, options }) {
const requestOptions = Object.assign({}, options)
const nodeFetchOptions = {}
const nodeFetchUrl = new URL(url)
const interchangableOptions = ['headers', 'method', 'body']
if ('body' in requestOptions && 'form' in requestOptions) {
throw new Error("Options 'form' and 'body' can not both be used")
}
interchangableOptions.forEach(function (opt) {
if (opt in requestOptions) {
nodeFetchOptions[opt] = requestOptions[opt]
delete requestOptions[opt]
}
})
nodeFetchOptions.headers = nodeFetchOptions.headers || {}
if ('qs' in requestOptions) {
if (typeof requestOptions.qs === 'string') {
nodeFetchUrl.search = requestOptions.qs
delete requestOptions.qs
} else if (typeof requestOptions.qs === 'object') {
nodeFetchUrl.search = object2URLSearchParams(requestOptions.qs)
delete requestOptions.qs
} else if (requestOptions.qs == null) {
delete requestOptions.qs
} else {
throw new Error("Property 'qs' must be string, object or null")
}
}
if ('gzip' in requestOptions) {
nodeFetchOptions.compress = requestOptions.gzip
delete requestOptions.gzip
}
if ('auth' in requestOptions) {
const user = requestOptions.auth.user || ''
const pass = requestOptions.auth.pass || ''
const b64authStr = Buffer.from(`${user}:${pass}`).toString('base64')
nodeFetchOptions.headers.Authorization = `Basic ${b64authStr}`
delete requestOptions.auth
}
if ('form' in requestOptions) {
nodeFetchOptions.body = object2URLSearchParams(requestOptions.form)
delete requestOptions.form
}
if (Object.keys(requestOptions).length > 0) {
throw new Error(`Found unrecognised options ${Object.keys(requestOptions)}`)
}
return { url: nodeFetchUrl.toString(), options: nodeFetchOptions }
}
async function sendRequest(fetchLimitBytes, url, options) {
const { url: nodeFetchUrl, options: nodeFetchOptions } = request2NodeFetch({
url,
options,
})
nodeFetchOptions.headers['User-Agent'] = userAgent
nodeFetchOptions.size = fetchLimitBytes
nodeFetchOptions.follow = 10
try {
const resp = await fetch(nodeFetchUrl, nodeFetchOptions)
const body = await resp.text()
resp.statusCode = resp.status
return { res: resp, buffer: body }
} catch (err) {
if (err.type === 'max-size') {
throw new InvalidResponse({
underlyingError: new Error('Maximum response size exceeded'),
})
}
throw new Inaccessible({ underlyingError: err })
}
}
export { request2NodeFetch, sendRequest }