@@ -26,7 +26,7 @@ import pFilter from 'p-filter'
26
26
import toReadableStream from 'to-readable-stream'
27
27
28
28
import { BaseCommand } from '../commands/index.js'
29
- import { $TSFixMe } from '../commands/types.js'
29
+ import { $TSFixMe , NetlifyOptions } from '../commands/types.js'
30
30
import {
31
31
handleProxyRequest ,
32
32
initializeProxy as initializeEdgeFunctionsProxy ,
@@ -47,8 +47,11 @@ import { signRedirect } from './sign-redirect.js'
47
47
import { Request , Rewriter , ServerSettings } from './types.js'
48
48
49
49
const gunzip = util . promisify ( zlib . gunzip )
50
+ const gzip = util . promisify ( zlib . gzip )
50
51
const brotliDecompress = util . promisify ( zlib . brotliDecompress )
52
+ const brotliCompress = util . promisify ( zlib . brotliCompress )
51
53
const deflate = util . promisify ( zlib . deflate )
54
+ const inflate = util . promisify ( zlib . inflate )
52
55
const shouldGenerateETag = Symbol ( 'Internal: response should generate ETag' )
53
56
54
57
const decompressResponseBody = async function ( body : Buffer , contentEncoding = '' ) : Promise < Buffer > {
@@ -58,12 +61,48 @@ const decompressResponseBody = async function (body: Buffer, contentEncoding = '
58
61
case 'br' :
59
62
return await brotliDecompress ( body )
60
63
case 'deflate' :
61
- return await deflate ( body )
64
+ return await inflate ( body )
62
65
default :
63
66
return body
64
67
}
65
68
}
66
69
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
+
67
106
// @ts -expect-error TS(7006) FIXME: Parameter 'errorBuffer' implicitly has an 'any' ty... Remove this comment to see the full error message
68
107
const formatEdgeFunctionError = ( errorBuffer , acceptsHtml ) => {
69
108
const {
@@ -416,25 +455,16 @@ const reqToURL = function (req, pathname) {
416
455
const MILLISEC_TO_SEC = 1e3
417
456
418
457
const 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
420
458
config,
421
- // @ts -expect-error TS(7031) FIXME: Binding element 'distDir' implicitly has an 'any... Remove this comment to see the full error message
422
459
configPath,
423
- // @ts -expect-error TS(7031) FIXME: Binding element 'env' implicitly has an 'any... Remove this comment to see the full error message
424
460
distDir,
425
- // @ts -expect-error TS(7031) FIXME: Binding element 'host' implicitly has an 'any... Remove this comment to see the full error message
426
461
env,
427
- // @ts -expect-error TS(7031) FIXME: Binding element 'imageProxy' implicitly has an 'any... Remove this comment to see the full error message
428
462
host,
429
- // @ts -expect-error TS(7031) FIXME: Binding element 'port' implicitly has an 'any... Remove this comment to see the full error message
430
463
imageProxy,
431
- // @ts -expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'any... Remove this comment to see the full error message
432
464
port,
433
- // @ts -expect-error TS(7031) FIXME: Binding element 'siteInfo' implicitly has an 'any... Remove this comment to see the full error message
434
465
projectDir,
435
- // @ts -expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any... Remove this comment to see the full error message
436
466
siteInfo,
437
- } ) {
467
+ } : { config : NetlifyOptions [ 'config' ] } & Record < string , $TSFixMe > ) {
438
468
const proxy = httpProxy . createProxyServer ( {
439
469
selfHandleResponse : true ,
440
470
target : {
@@ -568,10 +598,18 @@ const initializeProxy = async function ({
568
598
const requestURL = new URL ( req . url , `http://${ req . headers . host || '127.0.0.1' } ` )
569
599
const headersRules = headersForPath ( headers , requestURL . pathname )
570
600
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
+
571
608
// for streamed responses, we can't do etag generation nor error templates.
572
609
// we'll just stream them through!
610
+ // when html_injections are present in dev config, we can't use streamed response
573
611
const isStreamedResponse = proxyRes . headers [ 'content-length' ] === undefined
574
- if ( isStreamedResponse ) {
612
+ if ( isStreamedResponse && ! htmlInjections ) {
575
613
Object . entries ( headersRules ) . forEach ( ( [ key , val ] ) => {
576
614
// @ts -expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
577
615
res . setHeader ( key , val )
@@ -596,7 +634,7 @@ const initializeProxy = async function ({
596
634
597
635
proxyRes . on ( 'end' , async function onEnd ( ) {
598
636
// @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 )
600
638
601
639
// @ts -expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
602
640
let responseStatus = req . proxyOptions . status || proxyRes . statusCode
@@ -640,7 +678,17 @@ const initializeProxy = async function ({
640
678
return res . end ( )
641
679
}
642
680
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 )
644
692
645
693
if ( responseStatus !== 304 ) {
646
694
res . write ( responseBody )
0 commit comments