diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d9ab78675..c93ac2e5b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Run lifecycle hooks for specific codebases. (#6011) +- Fixed issue causing `firebase emulators:start` to crash in Next.js apps (#6005) diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index a9525311ad3..56f8dbfade9 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.22", + "version": "0.0.22-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.22", + "version": "0.0.22-alpha.0", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 6967534c740..299232a9572 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -63,20 +63,97 @@ export async function warnIfCustomBuildScript( } } -function proxyResponse(original: ServerResponse, next: () => void) { - return (response: IncomingMessage | ServerResponse) => { - const { statusCode, statusMessage } = response; - if (!statusCode) { - original.end(); - return; - } - if (statusCode === 404) { - return next(); - } - const headers = "getHeaders" in response ? response.getHeaders() : response.headers; - original.writeHead(statusCode, statusMessage, headers); - response.pipe(original); - }; +/** + * Proxy a HTTP response + * It uses the Proxy object to intercept the response and buffer it until the + * response is finished. This allows us to modify the response before sending + * it back to the client. + */ +export function proxyResponse( + req: IncomingMessage, + res: ServerResponse, + next: () => void +): ServerResponse { + const proxiedRes = new ServerResponse(req); + // Object to store the original response methods + const buffer: [ + string, + Parameters + ][] = []; + + // Proxy the response methods + // The apply handler is called when the method e.g. write, setHeader, etc. is called + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply + // The target is the original method + // The thisArg is the proxied response + // The args are the arguments passed to the method + proxiedRes.write = new Proxy(proxiedRes.write.bind(proxiedRes), { + apply: ( + target: ServerResponse["write"], + thisArg: ServerResponse, + args: Parameters + ) => { + // call the original write method on the proxied response + target.call(thisArg, ...args); + // store the method call in the buffer + buffer.push(["write", args]); + }, + }); + + proxiedRes.setHeader = new Proxy(proxiedRes.setHeader.bind(proxiedRes), { + apply: ( + target: ServerResponse["setHeader"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["setHeader", args]); + }, + }); + proxiedRes.removeHeader = new Proxy(proxiedRes.removeHeader.bind(proxiedRes), { + apply: ( + target: ServerResponse["removeHeader"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["removeHeader", args]); + }, + }); + proxiedRes.writeHead = new Proxy(proxiedRes.writeHead.bind(proxiedRes), { + apply: ( + target: ServerResponse["writeHead"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["writeHead", args]); + }, + }); + proxiedRes.end = new Proxy(proxiedRes.end.bind(proxiedRes), { + apply: ( + target: ServerResponse["end"], + thisArg: ServerResponse, + args: Parameters + ) => { + // call the original end method on the proxied response + target.call(thisArg, ...args); + // if the proxied response is a 404, call next to continue down the middleware chain + // otherwise, send the buffered response i.e. call the original response methods: write, setHeader, etc. + // and then end the response and clear the buffer + if (proxiedRes.statusCode === 404) { + next(); + } else { + for (const [fn, args] of buffer) { + (res as any)[fn](...args); + } + res.end(...args); + buffer.length = 0; + } + }, + }); + + return proxiedRes; } export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { @@ -127,9 +204,8 @@ export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { originalRes.end(); }); } else { - await Promise.resolve(hostOrRequestHandler(originalReq, originalRes, next)); - const proxiedRes = new ServerResponse(originalReq); - proxyResponse(originalRes, next)(proxiedRes); + const proxiedRes = proxyResponse(originalReq, originalRes, next); + await hostOrRequestHandler(originalReq, proxiedRes, next); } }; }