@@ -26,7 +26,7 @@ import pFilter from 'p-filter'
2626import toReadableStream from 'to-readable-stream'
2727
2828import { BaseCommand } from '../commands/index.js'
29- import { $TSFixMe } from '../commands/types.js'
29+ import { $TSFixMe , NetlifyOptions } from '../commands/types.js'
3030import {
3131 handleProxyRequest ,
3232 initializeProxy as initializeEdgeFunctionsProxy ,
@@ -47,8 +47,11 @@ import { signRedirect } from './sign-redirect.js'
4747import { Request , Rewriter , ServerSettings } from './types.js'
4848
4949const gunzip = util . promisify ( zlib . gunzip )
50+ const gzip = util . promisify ( zlib . gzip )
5051const brotliDecompress = util . promisify ( zlib . brotliDecompress )
52+ const brotliCompress = util . promisify ( zlib . brotliCompress )
5153const deflate = util . promisify ( zlib . deflate )
54+ const inflate = util . promisify ( zlib . inflate )
5255const shouldGenerateETag = Symbol ( 'Internal: response should generate ETag' )
5356
5457const decompressResponseBody = async function ( body : Buffer , contentEncoding = '' ) : Promise < Buffer > {
@@ -58,12 +61,48 @@ const decompressResponseBody = async function (body: Buffer, contentEncoding = '
5861 case 'br' :
5962 return await brotliDecompress ( body )
6063 case 'deflate' :
61- return await deflate ( body )
64+ return await inflate ( body )
6265 default :
6366 return body
6467 }
6568}
6669
70+ const compressResponseBody = async function ( body : string , contentEncoding = '' ) : Promise < Buffer > {
71+ switch ( contentEncoding ) {
72+ case 'gzip' :
73+ return await gzip ( body )
74+ case 'br' :
75+ return await brotliCompress ( body )
76+ case 'deflate' :
77+ return await deflate ( body )
78+ default :
79+ return Buffer . from ( body , 'utf8' )
80+ }
81+ }
82+
83+ type HTMLInjections = NonNullable < NonNullable < NetlifyOptions [ 'config' ] [ 'dev' ] [ 'processing' ] > [ 'html' ] > [ 'injections' ]
84+
85+ const injectHtml = async function (
86+ responseBody : Buffer ,
87+ proxyRes : http . IncomingMessage ,
88+ htmlInjections : HTMLInjections ,
89+ ) : Promise < Buffer > {
90+ const decompressedBody : Buffer = await decompressResponseBody ( responseBody , proxyRes . headers [ 'content-encoding' ] )
91+ const bodyWithInjections : string = ( htmlInjections ?? [ ] ) . reduce ( ( accum , htmlInjection ) => {
92+ if ( ! htmlInjection . html || typeof htmlInjection . html !== 'string' ) {
93+ return accum
94+ }
95+ const location = htmlInjection . location ?? 'before_closing_head_tag'
96+ if ( location === 'before_closing_head_tag' ) {
97+ accum = accum . replace ( '</head>' , `${ htmlInjection . html } </head>` )
98+ } else if ( location === 'before_closing_body_tag' ) {
99+ accum = accum . replace ( '</body>' , `${ htmlInjection . html } </body>` )
100+ }
101+ return accum
102+ } , decompressedBody . toString ( ) )
103+ return await compressResponseBody ( bodyWithInjections , proxyRes . headers [ 'content-encoding' ] )
104+ }
105+
67106// @ts -expect-error TS(7006) FIXME: Parameter 'errorBuffer' implicitly has an 'any' ty... Remove this comment to see the full error message
68107const formatEdgeFunctionError = ( errorBuffer , acceptsHtml ) => {
69108 const {
@@ -416,25 +455,16 @@ const reqToURL = function (req, pathname) {
416455const MILLISEC_TO_SEC = 1e3
417456
418457const initializeProxy = async function ( {
419- // @ts -expect-error TS(7031) FIXME: Binding element 'configPath' implicitly has an 'any... Remove this comment to see the full error message
420458 config,
421- // @ts -expect-error TS(7031) FIXME: Binding element 'distDir' implicitly has an 'any... Remove this comment to see the full error message
422459 configPath,
423- // @ts -expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any... Remove this comment to see the full error message
424460 distDir,
425- // @ts -expect-error TS(7031) FIXME: Binding element 'host' implicitly has an 'any... Remove this comment to see the full error message
426461 env,
427- // @ts -expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'any... Remove this comment to see the full error message
428462 host,
429- // @ts -expect-error TS(7031) FIXME: Binding element 'port' implicitly has an 'any... Remove this comment to see the full error message
430463 imageProxy,
431- // @ts -expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'any... Remove this comment to see the full error message
432464 port,
433- // @ts -expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any... Remove this comment to see the full error message
434465 projectDir,
435- // @ts -expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any... Remove this comment to see the full error message
436466 siteInfo,
437- } ) {
467+ } : { config : NetlifyOptions [ 'config' ] } & Record < string , $TSFixMe > ) {
438468 const proxy = httpProxy . createProxyServer ( {
439469 selfHandleResponse : true ,
440470 target : {
@@ -568,10 +598,18 @@ const initializeProxy = async function ({
568598 const requestURL = new URL ( req . url , `http://${ req . headers . host || '127.0.0.1' } ` )
569599 const headersRules = headersForPath ( headers , requestURL . pathname )
570600
601+ const htmlInjections =
602+ config . dev ?. processing ?. html ?. injections &&
603+ config . dev . processing . html . injections . length !== 0 &&
604+ proxyRes . headers ?. [ 'content-type' ] ?. startsWith ( 'text/html' )
605+ ? config . dev . processing . html . injections
606+ : undefined
607+
571608 // for streamed responses, we can't do etag generation nor error templates.
572609 // we'll just stream them through!
610+ // when html_injections are present in dev config, we can't use streamed response
573611 const isStreamedResponse = proxyRes . headers [ 'content-length' ] === undefined
574- if ( isStreamedResponse ) {
612+ if ( isStreamedResponse && ! htmlInjections ) {
575613 Object . entries ( headersRules ) . forEach ( ( [ key , val ] ) => {
576614 // @ts -expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
577615 res . setHeader ( key , val )
@@ -596,7 +634,7 @@ const initializeProxy = async function ({
596634
597635 proxyRes . on ( 'end' , async function onEnd ( ) {
598636 // @ts -expect-error TS(7005) FIXME: Variable 'responseData' implicitly has an 'any[]' ... Remove this comment to see the full error message
599- const responseBody = Buffer . concat ( responseData )
637+ let responseBody = Buffer . concat ( responseData )
600638
601639 // @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
602640 let responseStatus = req . proxyOptions . status || proxyRes . statusCode
@@ -640,7 +678,17 @@ const initializeProxy = async function ({
640678 return res . end ( )
641679 }
642680
643- res . writeHead ( responseStatus , proxyRes . headers )
681+ let proxyResHeaders = proxyRes . headers
682+
683+ if ( htmlInjections ) {
684+ responseBody = await injectHtml ( responseBody , proxyRes , htmlInjections )
685+ proxyResHeaders = {
686+ ...proxyResHeaders ,
687+ 'content-length' : String ( responseBody . byteLength ) ,
688+ }
689+ }
690+
691+ res . writeHead ( responseStatus , proxyResHeaders )
644692
645693 if ( responseStatus !== 304 ) {
646694 res . write ( responseBody )
0 commit comments