import _ from "lodash" ; import { join } from "path" ; import { NoBundleOrRedirectToError , NoMatchingEntrypointError , StoragesInconsistencyError , } from "../../common/errors" ; import removePrefix from "../../common/removePrefix" ; import Usecase from "../../common/Usecase" ; import { IConfiguration } from "../../entities/Configuration" ; import { IEndpointRequest } from "../../entities/EndpointRequest" ; import { IEndpointResponse } from "../../entities/EndpointResponse" ; import { IEntrypoint } from "../../entities/Entrypoint" ; import addTrailingSlash from "./addTrailingSlash" ; import configureHtml from "./configureHtml" ; import findMatchingAsset from "./findMatchingAsset" ; import getCanonicalHostname from "./getCanonicalHostname" ; import getConfigurationScript from "./getConfigurationScript" ; import isCanonicalPath from "./isCanonicalPath" ; import toAbsolute from "./toAbsolute" ; import whitelistInlineScript from "./whitelistInlineScript" ; export default class RespondToEndpointRequest extends Usecase { async exec ( request ) { const canonicalHostname = getCanonicalHostname ( request . hostname ) ; const requestedUrl = join ( canonicalHostname , request . path ) ; const entrypoints = await this . storages . entrypoints . findManyByUrlMatcherHostname ( canonicalHostname ) ; const matchingEntrypoint = _ ( entrypoints ) . filter ( ( entrypoint ) => _ . startsWith ( requestedUrl , entrypoint . urlMatcher ) || _ . startsWith ( addTrailingSlash ( requestedUrl ) , entrypoint . urlMatcher ) ) . sortBy ( "urlMatcher.length" ) . last ( ) ; if ( ! matchingEntrypoint ) { throw new NoMatchingEntrypointError ( requestedUrl ) ; } if ( ! _ . startsWith ( requestedUrl , matchingEntrypoint . urlMatcher ) ) { return { statusCode : 301 , headers : { location : addTrailingSlash ( request . path ) , } , } ; } if ( matchingEntrypoint . redirectTo ) { return { statusCode : 302 , headers : { location : matchingEntrypoint . redirectTo , } , } ; } if ( ! matchingEntrypoint . bundleId ) { throw new NoBundleOrRedirectToError ( matchingEntrypoint . urlMatcher ) ; } const urlMatcherPath = matchingEntrypoint . urlMatcher . slice ( matchingEntrypoint . urlMatcher . indexOf ( "/" ) ) ; const requestedPath = toAbsolute ( removePrefix ( request . path , urlMatcherPath ) ) ; const linkedBundle = await this . storages . bundles . findOne ( matchingEntrypoint . bundleId ) ; if ( ! linkedBundle ) { throw new StoragesInconsistencyError ( `Entrypoint with id = ${ matchingEntrypoint . id } links to a non-existing bundle with id = ${ matchingEntrypoint . bundleId }` ) ; } const fallbackAsset = _ . find ( linkedBundle . assets , { path : linkedBundle . fallbackAssetPath , } ) ; if ( ! fallbackAsset ) { throw new StoragesInconsistencyError ( `Bundle with id = ${ linkedBundle . id } specifies a non-existing fallback asset with path = ${ linkedBundle . fallbackAssetPath }` ) ; } const matchingAsset = findMatchingAsset ( requestedPath , linkedBundle . assets , fallbackAsset ) ; if ( ! isCanonicalPath ( requestedPath , matchingAsset , fallbackAsset ) ) { const canonicalRemotePath = join ( urlMatcherPath , matchingAsset . path ) ; return { statusCode : 301 , headers : { location : canonicalRemotePath , } , } ; } const content = await this . storages . bundles . getBundleAssetContent ( linkedBundle . id , matchingAsset . path ) ; if ( ! content ) { throw new StoragesInconsistencyError ( `Asset with path = ${ matchingAsset . path } specified in bundle with id = ${ linkedBundle . id } has no content` ) ; } const isHtmlAsset = matchingAsset . mimeType === "text/html" ; const configurationScript = isHtmlAsset ? getConfigurationScript ( await this . getConfiguration ( matchingEntrypoint ) ) : null ; return { statusCode : matchingAsset . path === fallbackAsset . path ? linkedBundle . fallbackStatusCode : 200 , headers : { "content-type" : matchingAsset . mimeType , ... ( isHtmlAsset ? whitelistInlineScript ( matchingAsset . headers , configurationScript ! . sha256 ) : matchingAsset . headers ) , } , body : isHtmlAsset ? configureHtml ( content , configurationScript ! . content ) : content , } ; } private async getConfiguration ( entrypoint ) { let configuration ; if ( entrypoint . configuration ) { configuration = entrypoint . configuration ; } else { const linkedApp = await this . storages . apps . findOne ( entrypoint . appId ) ; if ( ! linkedApp ) { throw new StoragesInconsistencyError ( `Entrypoint with id = ${ entrypoint . id } links to a non-existing app with id = ${ entrypoint . appId }` ) ; } configuration = linkedApp . defaultConfiguration ; } return { ... configuration , BASE_PATH : entrypoint . urlMatcher . slice ( entrypoint . urlMatcher . indexOf ( "/" ) ) , } ; } }