From 4d715d76cf029c0daa53461ace85c1874d7b0039 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Mon, 16 Mar 2026 16:13:36 +0530 Subject: [PATCH 1/2] Inject route-change script for Live Preview path sync @W-21574818@ Made-with: Cursor --- src/proxy/ProxyServer.ts | 116 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/src/proxy/ProxyServer.ts b/src/proxy/ProxyServer.ts index 8e3f977..9d46ad8 100644 --- a/src/proxy/ProxyServer.ts +++ b/src/proxy/ProxyServer.ts @@ -359,8 +359,8 @@ export class ProxyServer extends EventEmitter { } if (this.proxyHandler) { - // Package handles all errors internally and returns proper HTTP responses - await this.proxyHandler(req, res); + const wrappedRes = this.wrapResponseForRouteInjection(req, res); + await this.proxyHandler(req, wrappedRes); } else { this.logger.error('Proxy handler not initialized'); res.writeHead(500, { 'Content-Type': 'application/json' }); @@ -368,6 +368,118 @@ export class ProxyServer extends EventEmitter { } } + /** + * Wraps the response to inject a route-change script into HTML pages. + * When the app is embedded in Live Preview iframe, this script notifies the parent + * when the route changes (e.g. "Go to Home" click) so the path input stays in sync. + */ + // eslint-disable-next-line class-methods-use-this -- static helper, kept as instance method for call-site consistency + private wrapResponseForRouteInjection(req: IncomingMessage, res: ServerResponse): ServerResponse { + const url = req.url ?? '/'; + const isLikelyHtml = + req.method === 'GET' && + !url.includes('/services') && + !url.includes('/lwr/') && + !url.endsWith('.js') && + !url.endsWith('.css') && + !url.endsWith('.json') && + !url.includes('import='); + + if (!isLikelyHtml) { + return res; + } + + let statusCode = 200; + let headers: Record = {}; + const chunks: Buffer[] = []; + + const routeScript = + ''; + + const wrapped = Object.create(res, { + writeHead: { + value: ( + code: number, + h?: Record + ) => { + statusCode = code; + if (h) { + const merged: Record = { + ...headers, + ...h + }; + headers = merged; + } + return true; + } + }, + write: { + value: ( + chunk: Buffer | string, + encoding?: BufferEncoding | ((err?: Error) => void), + cb?: (err?: Error) => void + ) => { + let actualEncoding: BufferEncoding | undefined; + let actualCb: ((err?: Error) => void) | undefined; + if (typeof encoding === 'function') { + actualCb = encoding; + actualEncoding = undefined; + } else { + actualEncoding = encoding; + actualCb = cb; + } + chunks.push( + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, actualEncoding as BufferEncoding) + ); + if (actualCb) actualCb(); + return true; + } + }, + end: { + value: ( + chunk?: Buffer | string | (() => void), + encoding?: BufferEncoding | (() => void), + cb?: () => void + ) => { + let actualChunk: Buffer | string | undefined; + let actualEncoding: BufferEncoding | undefined; + let actualCb: (() => void) | undefined; + if (typeof chunk === 'function') { + actualCb = chunk; + actualChunk = undefined; + actualEncoding = undefined; + } else if (typeof encoding === 'function') { + actualCb = encoding; + actualChunk = chunk; + actualEncoding = undefined; + } else { + actualChunk = chunk; + actualEncoding = encoding; + actualCb = cb; + } + if (actualChunk) + chunks.push( + Buffer.isBuffer(actualChunk) + ? actualChunk + : Buffer.from(actualChunk, actualEncoding as BufferEncoding) + ); + const body = Buffer.concat(chunks); + const contentType = (headers['content-type'] ?? headers['Content-Type'] ?? '') as string; + if (contentType.includes('text/html') && body.includes('')) { + const injected = body.toString().replace('', `${routeScript}`); + res.writeHead(statusCode, { ...headers, 'content-length': Buffer.byteLength(injected) }); + res.end(injected, actualCb); + } else { + res.writeHead(statusCode, headers); + res.end(body, actualCb); + } + } + } + }) as ServerResponse; + + return wrapped; + } + private handleWebSocketUpgrade(req: IncomingMessage, socket: NodeJS.Socket, head: Buffer): void { const url = req.url ?? '/'; this.logger.debug(`[WebSocket] Upgrade request: ${url}`); From c44c8dfb5a1a8d17f971fbc0b26a1fe6eec5977b Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Mon, 16 Mar 2026 16:20:52 +0530 Subject: [PATCH 2/2] Fix eslint and use Content-Type for HTML injection @W-21574818@ Made-with: Cursor --- src/proxy/ProxyServer.ts | 212 ++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 113 deletions(-) diff --git a/src/proxy/ProxyServer.ts b/src/proxy/ProxyServer.ts index 9d46ad8..3afd3d3 100644 --- a/src/proxy/ProxyServer.ts +++ b/src/proxy/ProxyServer.ts @@ -108,6 +108,104 @@ export class ProxyServer extends EventEmitter { } } + /** + * Wraps the response to inject a route-change script into HTML pages. + * When the app is embedded in Live Preview iframe, this script notifies the parent + * when the route changes (e.g. "Go to Home" click) so the path input stays in sync. + * Injection only occurs when Content-Type is text/html and body contains . + */ + private static wrapResponseForRouteInjection(_req: IncomingMessage, res: ServerResponse): ServerResponse { + let statusCode = 200; + let headers: Record = {}; + const chunks: Buffer[] = []; + + const routeScript = + ''; + + const wrapped = Object.create(res, { + writeHead: { + value: ( + code: number, + h?: Record + ) => { + statusCode = code; + if (h) { + const merged: Record = { + ...headers, + ...h + }; + headers = merged; + } + return true; + } + }, + write: { + value: ( + chunk: Buffer | string, + encoding?: BufferEncoding | ((err?: Error) => void), + cb?: (err?: Error) => void + ) => { + let actualEncoding: BufferEncoding | undefined; + let actualCb: ((err?: Error) => void) | undefined; + if (typeof encoding === 'function') { + actualCb = encoding; + actualEncoding = undefined; + } else { + actualEncoding = encoding; + actualCb = cb; + } + chunks.push( + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, actualEncoding as BufferEncoding) + ); + if (actualCb) actualCb(); + return true; + } + }, + end: { + value: ( + chunk?: Buffer | string | (() => void), + encoding?: BufferEncoding | (() => void), + cb?: () => void + ) => { + let actualChunk: Buffer | string | undefined; + let actualEncoding: BufferEncoding | undefined; + let actualCb: (() => void) | undefined; + if (typeof chunk === 'function') { + actualCb = chunk; + actualChunk = undefined; + actualEncoding = undefined; + } else if (typeof encoding === 'function') { + actualCb = encoding; + actualChunk = chunk; + actualEncoding = undefined; + } else { + actualChunk = chunk; + actualEncoding = encoding; + actualCb = cb; + } + if (actualChunk) + chunks.push( + Buffer.isBuffer(actualChunk) + ? actualChunk + : Buffer.from(actualChunk, actualEncoding as BufferEncoding) + ); + const body = Buffer.concat(chunks); + const contentType = (headers['content-type'] ?? headers['Content-Type'] ?? '') as string; + if (contentType.includes('text/html') && body.includes('')) { + const injected = body.toString().replace('', `${routeScript}`); + res.writeHead(statusCode, { ...headers, 'content-length': Buffer.byteLength(injected) }); + res.end(injected, actualCb); + } else { + res.writeHead(statusCode, headers); + res.end(body, actualCb); + } + } + } + }) as ServerResponse; + + return wrapped; + } + // Public instance methods public clearActiveDevServerError(): void { if (this.activeDevServerError) { @@ -359,7 +457,7 @@ export class ProxyServer extends EventEmitter { } if (this.proxyHandler) { - const wrappedRes = this.wrapResponseForRouteInjection(req, res); + const wrappedRes = ProxyServer.wrapResponseForRouteInjection(req, res); await this.proxyHandler(req, wrappedRes); } else { this.logger.error('Proxy handler not initialized'); @@ -368,118 +466,6 @@ export class ProxyServer extends EventEmitter { } } - /** - * Wraps the response to inject a route-change script into HTML pages. - * When the app is embedded in Live Preview iframe, this script notifies the parent - * when the route changes (e.g. "Go to Home" click) so the path input stays in sync. - */ - // eslint-disable-next-line class-methods-use-this -- static helper, kept as instance method for call-site consistency - private wrapResponseForRouteInjection(req: IncomingMessage, res: ServerResponse): ServerResponse { - const url = req.url ?? '/'; - const isLikelyHtml = - req.method === 'GET' && - !url.includes('/services') && - !url.includes('/lwr/') && - !url.endsWith('.js') && - !url.endsWith('.css') && - !url.endsWith('.json') && - !url.includes('import='); - - if (!isLikelyHtml) { - return res; - } - - let statusCode = 200; - let headers: Record = {}; - const chunks: Buffer[] = []; - - const routeScript = - ''; - - const wrapped = Object.create(res, { - writeHead: { - value: ( - code: number, - h?: Record - ) => { - statusCode = code; - if (h) { - const merged: Record = { - ...headers, - ...h - }; - headers = merged; - } - return true; - } - }, - write: { - value: ( - chunk: Buffer | string, - encoding?: BufferEncoding | ((err?: Error) => void), - cb?: (err?: Error) => void - ) => { - let actualEncoding: BufferEncoding | undefined; - let actualCb: ((err?: Error) => void) | undefined; - if (typeof encoding === 'function') { - actualCb = encoding; - actualEncoding = undefined; - } else { - actualEncoding = encoding; - actualCb = cb; - } - chunks.push( - Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, actualEncoding as BufferEncoding) - ); - if (actualCb) actualCb(); - return true; - } - }, - end: { - value: ( - chunk?: Buffer | string | (() => void), - encoding?: BufferEncoding | (() => void), - cb?: () => void - ) => { - let actualChunk: Buffer | string | undefined; - let actualEncoding: BufferEncoding | undefined; - let actualCb: (() => void) | undefined; - if (typeof chunk === 'function') { - actualCb = chunk; - actualChunk = undefined; - actualEncoding = undefined; - } else if (typeof encoding === 'function') { - actualCb = encoding; - actualChunk = chunk; - actualEncoding = undefined; - } else { - actualChunk = chunk; - actualEncoding = encoding; - actualCb = cb; - } - if (actualChunk) - chunks.push( - Buffer.isBuffer(actualChunk) - ? actualChunk - : Buffer.from(actualChunk, actualEncoding as BufferEncoding) - ); - const body = Buffer.concat(chunks); - const contentType = (headers['content-type'] ?? headers['Content-Type'] ?? '') as string; - if (contentType.includes('text/html') && body.includes('')) { - const injected = body.toString().replace('', `${routeScript}`); - res.writeHead(statusCode, { ...headers, 'content-length': Buffer.byteLength(injected) }); - res.end(injected, actualCb); - } else { - res.writeHead(statusCode, headers); - res.end(body, actualCb); - } - } - } - }) as ServerResponse; - - return wrapped; - } - private handleWebSocketUpgrade(req: IncomingMessage, socket: NodeJS.Socket, head: Buffer): void { const url = req.url ?? '/'; this.logger.debug(`[WebSocket] Upgrade request: ${url}`);