@@ -108,6 +108,104 @@ export class ProxyServer extends EventEmitter {
108108 }
109109 }
110110
111+ /**
112+ * Wraps the response to inject a route-change script into HTML pages.
113+ * When the app is embedded in Live Preview iframe, this script notifies the parent
114+ * when the route changes (e.g. "Go to Home" click) so the path input stays in sync.
115+ * Injection only occurs when Content-Type is text/html and body contains </body>.
116+ */
117+ private static wrapResponseForRouteInjection ( _req : IncomingMessage , res : ServerResponse ) : ServerResponse {
118+ let statusCode = 200 ;
119+ let headers : Record < string , string | string [ ] | number | undefined > = { } ;
120+ const chunks : Buffer [ ] = [ ] ;
121+
122+ const routeScript =
123+ '<script>(function(){if(window===window.top)return;function send(){try{var p=window.location.pathname||\'/\';window.parent.postMessage({command:\'routeChanged\',route:p,_source:\'webapps-proxy-injected-script\'},\'*\')}catch(e){}}send();window.addEventListener(\'popstate\',send);var _push=history.pushState;if(_push){history.pushState=function(){_push.apply(this,arguments);setTimeout(send,0)}}var _replace=history.replaceState;if(_replace){history.replaceState=function(){_replace.apply(this,arguments);setTimeout(send,0)}}if(typeof window.navigation!==\'undefined\'&&window.navigation.addEventListener){window.navigation.addEventListener(\'navigate\',function(){setTimeout(send,0)})}})();</script>' ;
124+
125+ const wrapped = Object . create ( res , {
126+ writeHead : {
127+ value : (
128+ code : number ,
129+ h ?: Record < string , string | string [ ] | number | undefined >
130+ ) => {
131+ statusCode = code ;
132+ if ( h ) {
133+ const merged : Record < string , string | string [ ] | number | undefined > = {
134+ ...headers ,
135+ ...h
136+ } ;
137+ headers = merged ;
138+ }
139+ return true ;
140+ }
141+ } ,
142+ write : {
143+ value : (
144+ chunk : Buffer | string ,
145+ encoding ?: BufferEncoding | ( ( err ?: Error ) => void ) ,
146+ cb ?: ( err ?: Error ) => void
147+ ) => {
148+ let actualEncoding : BufferEncoding | undefined ;
149+ let actualCb : ( ( err ?: Error ) => void ) | undefined ;
150+ if ( typeof encoding === 'function' ) {
151+ actualCb = encoding ;
152+ actualEncoding = undefined ;
153+ } else {
154+ actualEncoding = encoding ;
155+ actualCb = cb ;
156+ }
157+ chunks . push (
158+ Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk , actualEncoding as BufferEncoding )
159+ ) ;
160+ if ( actualCb ) actualCb ( ) ;
161+ return true ;
162+ }
163+ } ,
164+ end : {
165+ value : (
166+ chunk ?: Buffer | string | ( ( ) => void ) ,
167+ encoding ?: BufferEncoding | ( ( ) => void ) ,
168+ cb ?: ( ) => void
169+ ) => {
170+ let actualChunk : Buffer | string | undefined ;
171+ let actualEncoding : BufferEncoding | undefined ;
172+ let actualCb : ( ( ) => void ) | undefined ;
173+ if ( typeof chunk === 'function' ) {
174+ actualCb = chunk ;
175+ actualChunk = undefined ;
176+ actualEncoding = undefined ;
177+ } else if ( typeof encoding === 'function' ) {
178+ actualCb = encoding ;
179+ actualChunk = chunk ;
180+ actualEncoding = undefined ;
181+ } else {
182+ actualChunk = chunk ;
183+ actualEncoding = encoding ;
184+ actualCb = cb ;
185+ }
186+ if ( actualChunk )
187+ chunks . push (
188+ Buffer . isBuffer ( actualChunk )
189+ ? actualChunk
190+ : Buffer . from ( actualChunk , actualEncoding as BufferEncoding )
191+ ) ;
192+ const body = Buffer . concat ( chunks ) ;
193+ const contentType = ( headers [ 'content-type' ] ?? headers [ 'Content-Type' ] ?? '' ) as string ;
194+ if ( contentType . includes ( 'text/html' ) && body . includes ( '</body>' ) ) {
195+ const injected = body . toString ( ) . replace ( '</body>' , `${ routeScript } </body>` ) ;
196+ res . writeHead ( statusCode , { ...headers , 'content-length' : Buffer . byteLength ( injected ) } ) ;
197+ res . end ( injected , actualCb ) ;
198+ } else {
199+ res . writeHead ( statusCode , headers ) ;
200+ res . end ( body , actualCb ) ;
201+ }
202+ }
203+ }
204+ } ) as ServerResponse ;
205+
206+ return wrapped ;
207+ }
208+
111209 // Public instance methods
112210 public clearActiveDevServerError ( ) : void {
113211 if ( this . activeDevServerError ) {
@@ -359,8 +457,8 @@ export class ProxyServer extends EventEmitter {
359457 }
360458
361459 if ( this . proxyHandler ) {
362- // Package handles all errors internally and returns proper HTTP responses
363- await this . proxyHandler ( req , res ) ;
460+ const wrappedRes = ProxyServer . wrapResponseForRouteInjection ( req , res ) ;
461+ await this . proxyHandler ( req , wrappedRes ) ;
364462 } else {
365463 this . logger . error ( 'Proxy handler not initialized' ) ;
366464 res . writeHead ( 500 , { 'Content-Type' : 'application/json' } ) ;
0 commit comments