/********************************************************************************* Type Definitions *********************************************************************************/ type type = { } /********************************************************************************* Internal Definitions *********************************************************************************/ import * as path from 'path' import * as fs from 'fs' import * as util from 'util' import * as cookie from 'cookie' import * as utils from './utils' const readFile = util.promisify(fs.readFile) export const internal = { session: { newCookie: (uid: string): string => cookie.serialize('uid', uid, { sameSite: true, secure: true, httpOnly: true, }), removeCookie: (uid: string): string => cookie.serialize('uid', uid, { expires: new Date(1), sameSite: true, secure: true, httpOnly: true }) }, mimeTypes: { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpg', '.gif': 'image/gif', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.woff': 'application/font-woff', '.ttf': 'application/font-ttf', '.eot': 'application/vnd.ms-fontobject', '.otf': 'application/font-otf', '.svg': 'application/image/svg+xml', 'default': 'application/octet-stream' }, statusCodes: { 100: { payload: 'Continue' }, 101: { paylaod: 'Switching Protocols' }, 102: { paylaod: 'Processing' }, // RFC 2518, obsoleted by RFC 491 103: { paylaod: 'Early Hints' }, 200: { paylaod: 'OK' }, 201: { paylaod: 'Created' }, 202: { paylaod: 'Accepted' }, 203: { paylaod: 'Non-Authoritative Information' }, 204: { paylaod: 'No Content' }, 205: { paylaod: 'Reset Content' }, 206: { paylaod: 'Partial Content' }, 207: { paylaod: 'Multi-Status' }, // RFC 491 208: { payload: 'Already Reported' }, 226: { payload: 'IM Used' }, 300: { payload: 'Multiple Choices' }, // RFC 723 301: { payload: 'Moved Permanently' }, 302: { payload: 'Found' }, 303: { payload: 'See Other' }, 304: { payload: 'Not Modified' }, 305: { payload: 'Use Proxy' }, 307: { payload: 'Temporary Redirect' }, 308: { payload: 'Permanent Redirect' }, // RFC 723 400: { payload: 'Bad Request' }, 401: { payload: 'Unauthorized' }, 402: { payload: 'Payment Required' }, 403: { payload: 'Forbidden' }, 404: { payload: 'Not Found' }, 405: { payload: 'Method Not Allowed' }, 406: { payload: 'Not Acceptable' }, 407: { payload: 'Proxy Authentication Required' }, 408: { payload: 'Request Timeout' }, 409: { payload: 'Conflict' }, 410: { payload: 'Gone' }, 411: { payload: 'Length Required' }, 412: { payload: 'Precondition Failed' }, 413: { payload: 'Payload Too Large' }, 414: { payload: 'URI Too Long' }, 415: { payload: 'Unsupported Media Type' }, 416: { payload: 'Range Not Satisfiable' }, 417: { payload: 'Expectation Failed' }, 418: { payload: 'I\'m a Teapot' }, // RFC 716 421: { payload: 'Misdirected Request' }, 422: { payload: 'Unprocessable Entity' }, // RFC 491 423: { payload: 'Locked' }, // RFC 491 424: { payload: 'Failed Dependency' }, // RFC 491 425: { payload: 'Unordered Collection' }, // RFC 4918 426: { payload: 'Upgrade Required' }, // RFC 2817 428: { payload: 'Precondition Required' }, // RFC 6585 429: { payload: 'Too Many Requests' }, // RFC 6585 431: { payload: 'Request Header Fields Too Large' }, // RFC 6585 451: { payload: 'Unavailable For Legal Reasons' }, 500: { payload: 'Internal Server Error' }, 501: { payload: 'Not Implemented' }, 502: { payload: 'Bad Gateway' }, 503: { payload: 'Service Unavailable' }, 504: { payload: 'Gateway Timeout' }, 505: { payload: 'HTTP Version Not Supported' }, 506: { payload: 'Variant Also Negotiates' }, // RFC 2295 507: { payload: 'Insufficient Storage' }, // RFC 4918 508: { payload: 'Loop Detected' }, 509: { payload: 'Bandwidth Limit Exceeded' }, 510: { payload: 'Not Extended' }, // RFC 2774 511: { payload: 'Network Authentication Required' } // RFC 6585 }, sendFile: async (filePath: string): Promise => { const extension = String(path.extname(filePath)).toLowerCase() const contentType = internal.mimeTypes[extension] || 'application/octet-stream' try { const file = await readFile(filePath) return { status: 200, headers: { 'Content-Type': contentType }, payload: file, } } catch (e) { return e.code == 'ENOENT' ? Promise.reject(new Error('Not Found')) : Promise.reject(new Error('Unknown Error')) } } } /********************************************************************************* Method Definitions *********************************************************************************/ export function getResponse(data) { const statusCodes = data.server.status || internal.statusCodes const currentStatus = statusCodes[data.error] return data.error ? { status: data.error, payload: currentStatus.payload, headers: currentStatus.headers || {} } : data.resource.handler(data.request, { response: { status: code => ({ status: code, payload: statusCodes[code].payload, headers: statusCodes[code].headers || {} }), fileContent: (content, type) => ({ status: 200, payload: content, headers: { 'Content-Type': internal.mimeTypes[type] || internal.mimeTypes.default } }) } }) } export function addDefaultHeaders(data) { const defaultHeaders = { 'Content-Type': 'application/json' } return utils.set(data, 'headers', Object.assign(defaultHeaders, data.headers)) } export function serializeResponse(data) { const serializedPayload = data.headers['Content-Type'] === 'application/json' ? JSON.stringify(data.payload) : data.payload return { status: data.status, headers: Object.assign({ 'Content-Length': Buffer.byteLength(serializedPayload, data.encoding || 'utf-8') }, data.headers), payload: serializedPayload } }