import { IncomingMessage , ServerResponse } from 'http' ; import zlib from 'zlib' ; export type RenderCallback = ( errorValue : Error , successValue ? : string | { [ key ] : unknown ; } | null ) => void ; export type RenderFunction = ( callback : RenderCallback , ... args : unknown [ ] ) => void ; export interface AppInvocationInfo { renderFunction : RenderFunction ; renderFunctionArgs : unknown [ ] ; renderFunctionCallback ? : RenderCallback ; } export type JsonObject = { [ key ] : unknown ; id : string ; functionName : string ; moduleName : string ; args : string [ ] ; } ; export type AppInvocationInfoResolver = ( bodyJson : string | JsonObject , req : IncomingMessage , res : ServerResponse ) => AppInvocationInfo ; export interface SSRMiddlewareOptions { appInvocationInfoResolver : AppInvocationInfoResolver ; } export type WebServerMiddleware = ( req : IncomingMessage , res : ServerResponse , next ? : ( err ? : unknown ) => void ) => void ; export type SSRMiddleware = ( options : SSRMiddlewareOptions ) => WebServerMiddleware ; export const ssrMiddleware = ( { appInvocationInfoResolver , } ) => { if ( ! appInvocationInfoResolver ) { throw new Error ( 'No AppInvocationInfo resolver was provided for SSR middleware' ) ; } return ( req , res ) => { let callback ; readRequestBodyAsJson ( req ) . then ( ( bodyJson ) => { if ( ! bodyJson ) { throw new Error ( `Request body was not JSON: ${ req . url }` ) ; } const invocationInfo = appInvocationInfoResolver ( bodyJson , req , res ) ; callback = invocationInfo . renderFunctionCallback || getDefaultAppRendererCallback ( res ) ; invocationInfo . renderFunction ( callback , ... invocationInfo . renderFunctionArgs ) ; } ) . catch ( ( err ) => { console . error ( err ) ; callback ( err , null ) ; } ) ; } ; } ; param { ServerResponse } res export function getDefaultAppRendererCallback ( res ) { const callback = ( errorValue , successValue ) => { if ( errorValue ) { respondWithError ( res , errorValue ) ; } else if ( typeof successValue !== 'string' ) { let successValueJson = { } ; try { successValueJson = JSON . stringify ( successValue ) ; } catch ( ex ) { respondWithError ( res , ex ) ; return ; } res . setHeader ( 'Content-Type' , 'application/json' ) ; res . end ( successValueJson ) ; } else { res . setHeader ( 'Content-Type' , 'text/plain' ) ; res . end ( successValue ) ; } } ; return callback ; } param { IncomingMessage } request export function readRequestBodyAsJson ( request ) { const dataWriter = { output : Buffer . from ( '' ) } ; request . on ( 'data' , onReadableStreamDataHandler ( dataWriter ) ) ; return new Promise ( ( resolve , reject ) => { request . on ( 'end' , ( ) => { const contentEncoding = request . headers [ 'content-encoding' ] ; extractJsonFromStreamData ( dataWriter . output , contentEncoding ) . then ( ( json ) => resolve ( json ) ) . catch ( ( err ) => reject ( err ) ) ; } ) ; } ) ; } param { ServerResponse } res param { Error } errorValue export function respondWithError ( res , errorValue ) { console . error ( errorValue ) ; res . statusCode = 500 ; res . end ( JSON . stringify ( { errorMessage : errorValue . message || errorValue , errorDetails : errorValue . stack || null , } ) ) ; } param dataWriter param { Buffer } dataWriter . output export function onReadableStreamDataHandler ( dataWriter ) { return ( data ) => { if ( Buffer . isBuffer ( data ) ) { dataWriter . output = Buffer . concat ( [ dataWriter . output , data ] ) ; } else { dataWriter . output = Buffer . concat ( [ dataWriter . output , Buffer . from ( data ) ] ) ; } } ; } param { Buffer } data param { string } contentEncoding export function extractJsonFromStreamData ( data , contentEncoding ? ) { let responseString ; if ( contentEncoding && ( contentEncoding . indexOf ( 'gzip' ) !== - 1 || contentEncoding . indexOf ( 'deflate' ) !== - 1 ) ) { responseString = new Promise ( ( resolve , reject ) => { zlib . unzip ( data , ( error , result ) => { if ( error ) { reject ( error ) ; } if ( result ) { resolve ( result . toString ( 'utf-8' ) ) ; } } ) ; } ) ; } else { responseString = Promise . resolve ( data . toString ( 'utf-8' ) ) ; } return responseString . then ( tryParseJson ) ; } param { string } jsonString export function tryParseJson ( jsonString ) { try { const json = JSON . parse ( jsonString ) ; if ( json && typeof json === 'object' && json !== null ) { return json ; } } catch ( e ) { console . error ( `error parsing json string '${ jsonString }'` , e ) ; } return null ; }