import { VercelRequest , VercelResponse , VercelRequestCookies , VercelRequestQuery , VercelRequestBody , } from './types' ; import { Server } from 'http' ; import type { Bridge } from '@vercel/node-bridge/bridge' ; function getBodyParser ( req , body ) { return function parseBody ( ) : VercelRequestBody { if ( ! req . headers [ 'content-type' ] ) { return undefined ; } const { parse : parseContentType } = require ( 'content-type' ) ; const { type } = parseContentType ( req . headers [ 'content-type' ] ) ; if ( type === 'application/json' ) { try { const str = body . toString ( ) ; return str ? JSON . parse ( str ) : { } ; } catch ( error ) { throw new ApiError ( 400 , 'Invalid JSON' ) ; } } if ( type === 'application/octet-stream' ) { return body ; } if ( type === 'application/x-www-form-urlencoded' ) { const { parse : parseQS } = require ( 'querystring' ) ; return parseQS ( body . toString ( ) ) ; } if ( type === 'text/plain' ) { return body . toString ( ) ; } return undefined ; } ; } function getQueryParser ( { url = '/' } ) { return function parseQuery ( ) : VercelRequestQuery { const { parse : parseURL } = require ( 'url' ) ; return parseURL ( url , true ) . query ; } ; } function getCookieParser ( req ) { return function parseCookie ( ) : VercelRequestCookies { const header = req . headers . cookie ; if ( ! header ) { return { } ; } const { parse } = require ( 'cookie' ) ; return parse ( Array . isArray ( header ) ? header . join ( ';' ) : header ) ; } ; } function status ( res , statusCode ) { res . statusCode = statusCode ; return res ; } function redirect ( res , statusOrUrl , url ? ) { if ( typeof statusOrUrl === 'string' ) { url = statusOrUrl ; statusOrUrl = 307 ; } if ( typeof statusOrUrl !== 'number' || typeof url !== 'string' ) { throw new Error ( `Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').` ) ; } res . writeHead ( statusOrUrl , { Location : url } ) . end ( ) ; return res ; } function setCharset ( type , charset ) { const { parse , format } = require ( 'content-type' ) ; const parsed = parse ( type ) ; parsed . parameters . charset = charset ; return format ( parsed ) ; } function createETag ( body , encoding ) { const etag = require ( 'etag' ) ; const buf = ! Buffer . isBuffer ( body ) ? Buffer . from ( body , encoding ) : body ; return etag ( buf , { weak : true } ) ; } function send ( req , res , body ) { let chunk = body ; let encoding ; switch ( typeof chunk ) { case 'string' : if ( ! res . getHeader ( 'content-type' ) ) { res . setHeader ( 'content-type' , 'text/html' ) ; } break ; case 'boolean' : case 'number' : case 'object' : if ( chunk === null ) { chunk = '' ; } else if ( Buffer . isBuffer ( chunk ) ) { if ( ! res . getHeader ( 'content-type' ) ) { res . setHeader ( 'content-type' , 'application/octet-stream' ) ; } } else { return json ( req , res , chunk ) ; } break ; } if ( typeof chunk === 'string' ) { encoding = 'utf8' ; const type = res . getHeader ( 'content-type' ) ; if ( typeof type === 'string' ) { res . setHeader ( 'content-type' , setCharset ( type , 'utf-8' ) ) ; } } let len ; if ( chunk !== undefined ) { if ( Buffer . isBuffer ( chunk ) ) { len = chunk . length ; } else if ( typeof chunk === 'string' ) { if ( chunk . length < 1000 ) { len = Buffer . byteLength ( chunk , encoding ) ; } else { const buf = Buffer . from ( chunk , encoding ) ; len = buf . length ; chunk = buf ; encoding = undefined ; } } else { throw new Error ( '`body` is not a valid string, object, boolean, number, Stream, or Buffer' ) ; } if ( len !== undefined ) { res . setHeader ( 'content-length' , len ) ; } } let etag ; if ( ! res . getHeader ( 'etag' ) && len !== undefined && ( etag = createETag ( chunk , encoding ) ) ) { res . setHeader ( 'etag' , etag ) ; } if ( 204 === res . statusCode || 304 === res . statusCode ) { res . removeHeader ( 'Content-Type' ) ; res . removeHeader ( 'Content-Length' ) ; res . removeHeader ( 'Transfer-Encoding' ) ; chunk = '' ; } if ( req . method === 'HEAD' ) { res . end ( ) ; } else if ( encoding ) { res . end ( chunk , encoding ) ; } else { res . end ( chunk ) ; } return res ; } function json ( req , res , jsonBody ) { const body = JSON . stringify ( jsonBody ) ; if ( ! res . getHeader ( 'content-type' ) ) { res . setHeader ( 'content-type' , 'application/json; charset=utf-8' ) ; } return send ( req , res , body ) ; } export class ApiError extends Error { readonly statusCode : number ; constructor ( statusCode , message ) { super ( message ) ; this . statusCode = statusCode ; } } export function sendError ( res , statusCode , message ) { res . statusCode = statusCode ; res . statusMessage = message ; res . end ( ) ; } function setLazyProp < T > ( req , prop , getter ) { const opts = { configurable : true , enumerable : true } ; const optsReset = { ... opts , writable : true } ; Object . defineProperty ( req , prop , { ... opts , get : ( ) => { const value = getter ( ) ; Object . defineProperty ( req , prop , { ... optsReset , value } ) ; return value ; } , set : value => { Object . defineProperty ( req , prop , { ... optsReset , value } ) ; } , } ) ; } export function createServerWithHelpers ( handler , bridge ) { const server = new Server ( async ( _req , _res ) => { const req = _req as VercelRequest ; const res = _res as VercelResponse ; try { const reqId = req . headers [ 'x-now-bridge-request-id' ] ; delete req . headers [ 'x-now-bridge-request-id' ] ; if ( typeof reqId !== 'string' ) { throw new ApiError ( 500 , 'Internal Server Error' ) ; } const event = bridge . consumeEvent ( reqId ) ; setLazyProp < VercelRequestCookies > ( req , 'cookies' , getCookieParser ( req ) ) ; setLazyProp < VercelRequestQuery > ( req , 'query' , getQueryParser ( req ) ) ; setLazyProp < VercelRequestBody > ( req , 'body' , getBodyParser ( req , event . body ) ) ; res . status = statusCode => status ( res , statusCode ) ; res . redirect = ( statusOrUrl , url ) => redirect ( res , statusOrUrl , url ) ; res . send = body => send ( req , res , body ) ; res . json = jsonBody => json ( req , res , jsonBody ) ; await handler ( req , res ) ; } catch ( err ) { if ( err instanceof ApiError ) { sendError ( res , err . statusCode , err . message ) ; } else { throw err ; } } } ) ; return server ; }