nfp_sites/base/media/client.mjs

209 lines
5.6 KiB
JavaScript

import crypto from 'crypto'
import fs from 'fs/promises'
import path from 'path'
import http from 'http'
import https from 'https'
import { URL } from 'url'
import { encode } from '../util.mjs'
export default class Client {
constructor() {
}
customRequest(method = 'GET', path, body, options = {}) {
if (path.slice(0, 4) !== 'http') {
throw new Error('Http client path invalid')
}
let urlObj = new URL(path)
let d1 = new Date()
return new Promise((resolve, reject) => {
const opts = {
method: options.method || method,
timeout: options.timeout || 60000,
protocol: options.protocol || urlObj.protocol,
username: options.username || urlObj.username,
password: options.password || urlObj.password,
host: options.host || urlObj.hostname,
port: options.port || Number(urlObj.port),
path: options.path || urlObj.pathname + urlObj.search,
headers: options.headers || {},
}
if (options.agent) {
opts.agent = options.agent
}
// opts.agent = agent
let req
if (path.startsWith('https')) {
req = https.request(opts)
} else {
req = http.request(opts)
}
req.on('error', (err) => {
reject(err)
})
req.on('timeout', function() {
req.destroy()
let d2 = new Date()
reject(new Error(`Request ${method} ${path} timed out`))
})
req.on('response', res => {
let output = ''
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
})
}
createJwt(body, secret) {
let header = {
typ: 'JWT',
alg: 'HS256',
}
body.iat = Math.floor(Date.now() / 1000)
body.exp = body.iat + 300
// Base64 encode header and body
let headerBase64 = encode(Buffer.from(JSON.stringify(header)))
let bodyBase64 = encode(Buffer.from(JSON.stringify(body)))
let headerBodyBase64 = headerBase64 + '.' + bodyBase64
const hmac = crypto.createHmac('sha256', secret)
hmac.update(headerBodyBase64)
let signatureBuffer = hmac.digest()
// Construct final JWT
let signatureBase64 = encode(signatureBuffer)
return headerBodyBase64 + '.' + signatureBase64
}
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;
}
post(url, body) {
let parsed = JSON.stringify(body)
return this.customRequest('POST', url, parsed, {
headers: {
'Content-Type': 'application/json',
'Content-Length': parsed.length,
},
})
}
delete(url, body) {
let parsed = JSON.stringify(body)
return this.customRequest('DELETE', url, parsed, {
headers: {
'Content-Type': 'application/json',
'Content-Length': parsed.length,
},
})
}
upload(url, files, method = 'POST', body = {}) {
const boundary = `---------${this.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]
let filename = ''
if (typeof(file) === 'object') {
if (typeof(file.file) !== 'string' || typeof(file.filename) !== 'string') {
throw new Error('Invalid value in client.upload for key ' + key)
}
filename = file.filename
file = file.file
} else {
filename = path.basename(file)
}
return fs.readFile(file).then(data => {
uploadBody.push(Buffer.from(
`${crlf}--${boundary}${crlf}` +
`Content-Disposition: form-data; name="${key}"; filename="${filename}"` + 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, {
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': multipartBody.length,
},
})
})
}
}