import fs from 'fs/promises' import path from 'path' import http from 'http' import stream from 'stream' import { URL } from 'url' import { defaults } from './helper.mjs' export default function Client(port, opts) { this.options = defaults(opts, {}) this.prefix = `http://localhost:${port}` } Client.prototype.customRequest = function(method = 'GET', path, body, options = {}) { if (path.slice(0, 4) !== 'http') { path = this.prefix + path } let urlObj = new URL(path) return new Promise((resolve, reject) => { const opts = defaults(defaults(options, { method: method, timeout: 500, protocol: urlObj.protocol, username: urlObj.username, password: urlObj.password, host: urlObj.hostname, port: Number(urlObj.port), path: urlObj.pathname + urlObj.search, headers: {}, })) if (options.agent) { opts.agent = options.agent } // opts.agent = agent const req = http.request(opts) req.on('error', (err) => { reject(err) }) req.on('timeout', function() { req.destroy() reject(new Error(`Request ${method} ${path} timed out`)) }) req.on('response', res => { let output = '' if (options.toPipe) { return stream.pipeline(res, options.toPipe, function(err) { if (err) { return reject(err) } resolve() }) } else { res.setEncoding('utf8') res.on('data', function (chunk) { output += chunk.toString() }) } res.on('end', function () { if (options.getRaw) { output = { status: res.statusCode, data: output, headers: res.headers, } } else { if (!output) return resolve(null) try { output = JSON.parse(output) } catch (e) { return reject(new Error(`${e.message} while decoding: ${output}`)) } } if (!options.getRaw && output.status && typeof(output.status) === 'number') { let err = new Error(`Request failed [${output.status}]: ${output.message}`) err.body = output return reject(err) } resolve(output) }) }) if (opts.returnRequest) { return resolve(req) } if (body) { req.write(body) } req.end() return req }) } Client.prototype.get = function(url = '/') { return this.customRequest('GET', url, null) } Client.prototype.post = function(url = '/', body = {}) { let parsed = JSON.stringify(body) return this.customRequest('POST', url, parsed, { headers: { 'Content-Type': 'application/json', 'Content-Length': parsed.length, }, }) } Client.prototype.del = function(url = '/', body = {}) { return this.customRequest('DELETE', url, JSON.stringify(body)) } const random = (length = 8) => { // Declare all characters let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; // Pick characers randomly let str = ''; for (let i = 0; i < length; i++) { str += chars.charAt(Math.floor(Math.random() * chars.length)); } return str; } Client.prototype.upload = function(url, files, method = 'POST', body = {}, overrideType = null) { const boundary = `---------${random(32)}` const crlf = '\r\n' let upload = files if (typeof(upload) === 'string') { upload = { file: files } } let keys = Object.keys(upload) let uploadBody = [] return Promise.all(keys.map(key => { let file = upload[key] return fs.readFile(file).then(data => { const filename = path.basename(file) uploadBody.push(Buffer.from( `${crlf}--${boundary}${crlf}` + `Content-Disposition: form-data; name="${key}"; filename="${filename}"` + (overrideType ? crlf + `Content-Type: ${overrideType}`: '') + crlf + crlf )) uploadBody.push(data) }) })) .then(() => { uploadBody.push( Buffer.concat(Object.keys(body).map(function(key) { return Buffer.from('' + `${crlf}--${boundary}${crlf}` + `Content-Disposition: form-data; name="${key}"` + crlf + crlf + JSON.stringify(body[key]) ) })) ) uploadBody.push(Buffer.from(`${crlf}--${boundary}--`)) let multipartBody = Buffer.concat(uploadBody) return this.customRequest(method, url, multipartBody, { timeout: 5000, headers: { 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': multipartBody.length, }, }) }) }