preact-material-components
Version:
preact wrapper for "Material Components for the web"
157 lines (125 loc) • 3.9 kB
JavaScript
// Native
const server = require('http').Server
const { Stream } = require('stream')
// Packages
const contentType = require('content-type')
const getRawBody = require('raw-body')
const { readable } = require('is-stream')
const { NODE_ENV } = process.env
const DEV = NODE_ENV === 'development'
const serve = fn => server((req, res) => exports.run(req, res, fn))
module.exports = serve
exports = serve
exports.default = serve
const createError = (code, message, original) => {
const err = new Error(message)
err.statusCode = code
err.originalError = original
return err
}
const send = (res, code, obj = null) => {
res.statusCode = code
if (obj === null) {
res.end()
return
}
if (Buffer.isBuffer(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
res.setHeader('Content-Length', obj.length)
res.end(obj)
return
}
if (obj instanceof Stream || readable(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
obj.pipe(res)
return
}
let str = obj
if (typeof obj === 'object' || typeof obj === 'number') {
// We stringify before setting the header
// in case `JSON.stringify` throws and a
// 500 has to be sent instead
// the `JSON.stringify` call is split into
// two cases as `JSON.stringify` is optimized
// in V8 if called with only one argument
if (DEV) {
str = JSON.stringify(obj, null, 2)
} else {
str = JSON.stringify(obj)
}
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/json; charset=utf-8')
}
}
res.setHeader('Content-Length', Buffer.byteLength(str))
res.end(str)
}
const sendError = (req, res, errorObj) => {
const statusCode = errorObj.statusCode || errorObj.status
const message = statusCode ? errorObj.message : 'Internal Server Error'
send(res, statusCode || 500, DEV ? errorObj.stack : message)
if (errorObj instanceof Error) {
console.error(errorObj.stack)
} else {
console.warn('thrown error must be an instance Error')
}
}
exports.send = send
exports.sendError = sendError
exports.createError = createError
exports.run = (req, res, fn) =>
new Promise(resolve => resolve(fn(req, res)))
.then(val => {
if (val === null) {
send(res, 204, null)
return
}
// Send value if it is not undefined, otherwise assume res.end
// will be called later
if (undefined !== val) {
send(res, res.statusCode || 200, val)
}
})
.catch(err => sendError(req, res, err))
// Maps requests to buffered raw bodies so that
// multiple calls to `json` work as expected
const rawBodyMap = new WeakMap()
const parseJSON = str => {
try {
return JSON.parse(str)
} catch (err) {
throw createError(400, 'Invalid JSON', err)
}
}
exports.buffer = (req, { limit = '1mb', encoding } = {}) =>
Promise.resolve().then(() => {
const type = req.headers['content-type'] || 'text/plain'
const length = req.headers['content-length']
if (encoding === undefined) {
encoding = contentType.parse(type).parameters.charset
}
const body = rawBodyMap.get(req)
if (body) {
return body
}
return getRawBody(req, { limit, length, encoding })
.then(buf => {
rawBodyMap.set(req, buf)
return buf
})
.catch(err => {
if (err.type === 'entity.too.large') {
throw createError(413, `Body exceeded ${limit} limit`, err)
} else {
throw createError(400, 'Invalid body', err)
}
})
})
exports.text = (req, { limit, encoding } = {}) =>
exports.buffer(req, { limit, encoding }).then(body => body.toString(encoding))
exports.json = (req, opts) =>
exports.text(req, opts).then(body => parseJSON(body))