2020-09-07 00:47:53 +00:00
|
|
|
import http from 'http'
|
|
|
|
import https from 'https'
|
|
|
|
import fs from 'fs'
|
2020-09-12 20:31:36 +00:00
|
|
|
import url from 'url'
|
2020-09-07 00:47:53 +00:00
|
|
|
|
2022-01-11 16:51:15 +00:00
|
|
|
function resolveRelative(from, to) {
|
|
|
|
const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
|
|
|
|
if (resolvedUrl.protocol === 'resolve:') {
|
|
|
|
// `from` is a relative URL.
|
|
|
|
const { pathname, search, hash } = resolvedUrl;
|
|
|
|
return pathname + search + hash;
|
|
|
|
}
|
|
|
|
return resolvedUrl.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function request(config, path, filePath = null, redirects, fastRaw = false) {
|
|
|
|
if (!config || typeof(config) !== 'object' || Array.isArray(config)) {
|
2020-09-13 04:36:33 +00:00
|
|
|
return Promise.reject(new Error('Request must be called with config in first parameter'))
|
|
|
|
}
|
2022-01-11 16:51:15 +00:00
|
|
|
let newRedirects = (redirects || 0) + 1
|
|
|
|
if (!path || typeof(path) !== 'string' || !path.startsWith('http')) {
|
|
|
|
return Promise.reject(new Error('URL was empty or invalid'))
|
|
|
|
}
|
|
|
|
let parsed
|
|
|
|
try {
|
|
|
|
parsed = new url.URL(path)
|
|
|
|
} catch {
|
|
|
|
return Promise.reject(new Error('URL was empty or invalid'))
|
2020-09-12 20:31:36 +00:00
|
|
|
}
|
2020-09-07 00:47:53 +00:00
|
|
|
|
2022-01-11 16:51:15 +00:00
|
|
|
let h = http
|
2020-09-07 00:47:53 +00:00
|
|
|
if (parsed.protocol === 'https:') {
|
|
|
|
h = https
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
if (!path) {
|
|
|
|
return reject(new Error('Request path was empty'))
|
|
|
|
}
|
2020-09-13 04:36:33 +00:00
|
|
|
let headers = {
|
|
|
|
'User-Agent': 'TheThing/service-core',
|
|
|
|
Accept: 'application/vnd.github.v3+json'
|
|
|
|
}
|
|
|
|
if (config.githubAuthToken && path.indexOf('api.github.com') >= 0) {
|
|
|
|
headers['Authorization'] = `token ${config.githubAuthToken}`
|
|
|
|
}
|
2022-01-11 16:51:15 +00:00
|
|
|
let timeout = fastRaw ? 5000 : config.timeout || 10000
|
|
|
|
|
|
|
|
let req = null
|
|
|
|
|
|
|
|
let timedout = false
|
|
|
|
let timer = setTimeout(function() {
|
|
|
|
timedout = true
|
|
|
|
if (req) { req.destroy() }
|
|
|
|
reject(new Error(`Request ${path} timed out out after ${timeout}`))
|
|
|
|
}, timeout)
|
|
|
|
|
|
|
|
req = h.request({
|
2020-09-07 00:47:53 +00:00
|
|
|
path: parsed.pathname + parsed.search,
|
|
|
|
port: parsed.port,
|
|
|
|
method: 'GET',
|
2020-09-13 04:36:33 +00:00
|
|
|
headers: headers,
|
2022-01-11 16:51:15 +00:00
|
|
|
timeout: timeout,
|
2020-09-07 00:47:53 +00:00
|
|
|
hostname: parsed.hostname
|
|
|
|
}, function(res) {
|
2022-01-11 16:51:15 +00:00
|
|
|
if (timedout) { return }
|
|
|
|
clearTimeout(timer)
|
|
|
|
|
2020-09-07 00:47:53 +00:00
|
|
|
let output = ''
|
|
|
|
if (filePath) {
|
|
|
|
let file = fs.createWriteStream(filePath)
|
|
|
|
res.pipe(file)
|
|
|
|
} else {
|
|
|
|
res.on('data', function(chunk) {
|
|
|
|
output += chunk
|
|
|
|
})
|
|
|
|
}
|
|
|
|
res.on('end', function() {
|
|
|
|
if (res.statusCode >= 300 && res.statusCode < 400) {
|
2020-09-12 20:31:36 +00:00
|
|
|
if (newRedirects > 5) {
|
2020-09-07 00:47:53 +00:00
|
|
|
return reject(new Error(`Too many redirects (last one was ${res.headers.location})`))
|
|
|
|
}
|
2020-09-12 20:31:36 +00:00
|
|
|
if (!res.headers.location) {
|
|
|
|
return reject(new Error('Redirect returned no path in location header'))
|
|
|
|
}
|
|
|
|
if (res.headers.location.startsWith('http')) {
|
2022-01-11 16:51:15 +00:00
|
|
|
return resolve(request(config, res.headers.location, filePath, newRedirects, fastRaw))
|
2020-09-12 20:31:36 +00:00
|
|
|
} else {
|
2022-01-11 16:51:15 +00:00
|
|
|
return resolve(request(config, resolveRelative(path, res.headers.location), filePath, newRedirects, fastRaw))
|
2020-09-12 20:31:36 +00:00
|
|
|
}
|
2020-09-07 00:47:53 +00:00
|
|
|
} else if (res.statusCode >= 400) {
|
2020-09-12 20:31:36 +00:00
|
|
|
return reject(new Error(`HTTP Error ${res.statusCode}: ${output}`))
|
2020-09-07 00:47:53 +00:00
|
|
|
}
|
|
|
|
resolve({
|
|
|
|
statusCode: res.statusCode,
|
|
|
|
status: res.statusCode,
|
|
|
|
statusMessage: res.statusMessage,
|
|
|
|
headers: res.headers,
|
|
|
|
body: output
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2022-01-11 16:51:15 +00:00
|
|
|
|
|
|
|
req.on('error', function(err) {
|
|
|
|
if (timedout) return
|
|
|
|
reject(new Error(`Error during request ${path}: ${err.message}`))
|
|
|
|
})
|
|
|
|
req.on('timeout', function(err) {
|
|
|
|
if (timedout) return
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
|
2020-09-07 00:47:53 +00:00
|
|
|
req.end()
|
|
|
|
}).then(function(res) {
|
2022-01-11 16:51:15 +00:00
|
|
|
if (!filePath && !fastRaw) {
|
|
|
|
if (typeof(res.body) === 'string') {
|
|
|
|
try {
|
|
|
|
res.body = JSON.parse(res.body)
|
|
|
|
} catch(e) {
|
|
|
|
throw new Error(`Error parsing body ${res.body}: ${e.message}`)
|
|
|
|
}
|
2020-09-07 00:47:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
}
|