diff --git a/.changeset/fallback-local-proxy-origin.md b/.changeset/fallback-local-proxy-origin.md new file mode 100644 index 0000000..ee24870 --- /dev/null +++ b/.changeset/fallback-local-proxy-origin.md @@ -0,0 +1,5 @@ +--- +"@vercel/microfrontends": patch +--- + +Send the browser-facing local proxy origin to local and fallback applications in the `x-vercel-mfe-local-proxy-origin` request header. diff --git a/README.md b/README.md index 0014779..562f52f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ The local development proxy can preserve the origin used to reach it. Set it exp The origin must be an absolute `http` or `https` origin, such as `https://myapp.localhost`, with no path, query string, hash, username, or password. The proxy uses this origin anywhere it needs to preserve the URL clients used to reach the proxy. +Proxied requests include this value in the `x-vercel-mfe-local-proxy-origin` header, including requests that fall back to a deployed application. When no origin is configured, the header contains the proxy's default `http://localhost:` origin. + ## Need help? Reach out for help in the [Vercel Community](https://community.vercel.com). diff --git a/packages/microfrontends/README.md b/packages/microfrontends/README.md index 0014779..562f52f 100644 --- a/packages/microfrontends/README.md +++ b/packages/microfrontends/README.md @@ -27,6 +27,8 @@ The local development proxy can preserve the origin used to reach it. Set it exp The origin must be an absolute `http` or `https` origin, such as `https://myapp.localhost`, with no path, query string, hash, username, or password. The proxy uses this origin anywhere it needs to preserve the URL clients used to reach the proxy. +Proxied requests include this value in the `x-vercel-mfe-local-proxy-origin` header, including requests that fall back to a deployed application. When no origin is configured, the header contains the proxy's default `http://localhost:` origin. + ## Need help? Reach out for help in the [Vercel Community](https://community.vercel.com). diff --git a/packages/microfrontends/src/bin/local-proxy.test.ts b/packages/microfrontends/src/bin/local-proxy.test.ts index 219375a..5fb9b22 100644 --- a/packages/microfrontends/src/bin/local-proxy.test.ts +++ b/packages/microfrontends/src/bin/local-proxy.test.ts @@ -744,7 +744,7 @@ describe('class LocalProxy', () => { 'x-forwarded-proto': 'https', 'x-forwarded-host': 'vercel.localhost', 'x-forwarded-port': '443', - 'x-vercel-mfe-local-proxy-origin': '1', + 'x-vercel-mfe-local-proxy-origin': 'https://vercel.localhost', }), }), ); @@ -832,7 +832,9 @@ describe('class LocalProxy', () => { }, ); - const req = mockRequest('/a-path-that-surely-does-not-match-any-routes'); + const req = mockRequest('/a-path-that-surely-does-not-match-any-routes', { + 'x-vercel-mfe-local-proxy-origin': 'https://spoofed.example.com', + }); (req as unknown as { on: jest.Mock }).on = jest.fn(); const res = mockResponse(); (res as unknown as { on: jest.Mock }).on = jest.fn(); @@ -847,15 +849,11 @@ describe('class LocalProxy', () => { 'x-forwarded-proto': 'https', 'x-forwarded-host': 'vercel.localhost', 'x-forwarded-port': '443', + 'x-vercel-mfe-local-proxy-origin': 'https://vercel.localhost', }), }), expect.any(Function), ); - const [requestOptions] = (https.request as unknown as jest.Mock).mock - .calls[0] as [{ headers: Record }]; - expect(requestOptions.headers).not.toHaveProperty( - 'x-vercel-mfe-local-proxy-origin', - ); expect(res.writeHead).toHaveBeenCalledWith( 307, expect.objectContaining({ @@ -1101,7 +1099,7 @@ describe('class LocalProxy', () => { 'x-forwarded-proto': 'https', 'x-forwarded-host': 'vercel.localhost', 'x-forwarded-port': '443', - 'x-vercel-mfe-local-proxy-origin': '1', + 'x-vercel-mfe-local-proxy-origin': 'https://vercel.localhost', }), }), ); @@ -1137,7 +1135,7 @@ describe('class LocalProxy', () => { expect(options.headers).not.toHaveProperty('x-forwarded-host'); expect(options.headers).not.toHaveProperty('x-forwarded-port'); expect(options.headers).toMatchObject({ - 'x-vercel-mfe-local-proxy-origin': '1', + 'x-vercel-mfe-local-proxy-origin': 'http://localhost:6720', }); }); }); diff --git a/packages/microfrontends/src/bin/local-proxy.ts b/packages/microfrontends/src/bin/local-proxy.ts index 73f11c1..b48f1cd 100644 --- a/packages/microfrontends/src/bin/local-proxy.ts +++ b/packages/microfrontends/src/bin/local-proxy.ts @@ -21,9 +21,10 @@ import { localAuthHtml } from './local-auth'; import { logger } from './logger'; import type { LocalProxyApplicationResponse, LocalProxyOptions } from './types'; -// This is a header set to `1` by the local proxy on all outgoing requests to locally running applications. -// Applications may optionally route all traffic that they receive without the header to the local proxy. -// This allows the application to appear as if it is serving routes from another microfrontend. +// This header is set by the local proxy on all outgoing requests and contains the +// browser-facing origin. Applications may optionally route all traffic that they +// receive without the header to the local proxy. This allows the application to +// appear as if it is serving routes from another microfrontend. const MFE_LOCAL_PROXY_HEADER = 'x-vercel-mfe-local-proxy-origin'; // This is a query parameter that can be passed to the local proxy to be used as the value of any flags @@ -735,7 +736,7 @@ export class LocalProxy { const headers: Record = this.configuredOrigin ? forwardedHeadersForOrigin(this.getOrigin()) : {}; - headers[MFE_LOCAL_PROXY_HEADER] = '1'; + headers[MFE_LOCAL_PROXY_HEADER] = this.getOrigin(); this.proxy.ws(req, socket, head, { target: target.url, headers, @@ -818,6 +819,7 @@ export class LocalProxy { ...fallbackHeaders, host: hostname, ...forwardedHeaders, + [MFE_LOCAL_PROXY_HEADER]: origin, cookie: Object.entries(cookies) .reduce((acc, [name, value]) => { if ( @@ -920,7 +922,7 @@ export class LocalProxy { }); } else { const headers: Record = { ...forwardedHeaders }; - headers[MFE_LOCAL_PROXY_HEADER] = '1'; + headers[MFE_LOCAL_PROXY_HEADER] = origin; if (mfeFlagValue !== undefined) { headers[MFE_FLAG_VALUE_HEADER] = mfeFlagValue.toString(); }