@vercel/microfrontends 2.3.6 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @vercel/microfrontends
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7a743be: Add an optional configured origin to the local development proxy via `--origin <origin>` or the `MFE_LOCAL_PROXY_ORIGIN` environment variable (for example `https://web.localhost`). When set, the proxy forwards that origin to your applications as `x-forwarded-proto`/`x-forwarded-host`/`x-forwarded-port` and rewrites fallback redirects onto it, so apps served behind a custom local hostname or a TLS terminator see the URL the browser actually used. When no origin is configured, inbound forwarded headers pass through unchanged.
8
+
3
9
  ## 2.3.6
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -21,6 +21,12 @@ Follow the [quickstart](https://vercel.com/docs/microfrontends/quickstart) docum
21
21
 
22
22
  You can use the frameworks and microfrontends frameworks of your choice. `@vercel/microfrontends` supports Next.js, SvelteKit, React Router, Vite, and React.
23
23
 
24
+ ## Local proxy origin
25
+
26
+ The local development proxy can preserve the origin used to reach it. Set it explicitly with `microfrontends proxy --origin <origin>` or `MFE_LOCAL_PROXY_ORIGIN`.
27
+
28
+ 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.
29
+
24
30
  ## Need help?
25
31
 
26
32
  Reach out for help in the [Vercel Community](https://community.vercel.com).
package/dist/bin/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var import_commander = require("commander");
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "@vercel/microfrontends",
33
- version: "2.3.6",
33
+ version: "2.4.0",
34
34
  private: false,
35
35
  description: "Defines configuration and utilities for microfrontends development",
36
36
  keywords: [
@@ -2371,19 +2371,56 @@ var MFE_LOCAL_PROXY_HEADER = "x-vercel-mfe-local-proxy-origin";
2371
2371
  var MFE_FLAG_VALUE = "vercel-mfe-flag-value";
2372
2372
  var MFE_FLAG_VALUE_HEADER = `x-${MFE_FLAG_VALUE}`;
2373
2373
  var VERCEL_TOOLBAR_PROXY_BASE_PATH = "/.well-known/vercel-toolbar";
2374
+ var ORIGIN_ENV = "MFE_LOCAL_PROXY_ORIGIN";
2374
2375
  function normalizeRepeatedSlashesInPath(url) {
2375
2376
  const searchStart = url.indexOf("?");
2376
2377
  const path7 = searchStart === -1 ? url : url.slice(0, searchStart);
2377
2378
  const search = searchStart === -1 ? "" : url.slice(searchStart);
2378
2379
  return `${path7.replaceAll(/\/[\\/]+/g, "/")}${search}`;
2379
2380
  }
2380
- function rewriteRedirectLocation(locationHeader, localhost) {
2381
- const redirectUrl = new import_node_url.URL(locationHeader, localhost);
2382
- const localUrl = new import_node_url.URL(localhost);
2383
- redirectUrl.protocol = localUrl.protocol;
2384
- redirectUrl.host = localUrl.host;
2381
+ function rewriteRedirectLocation(locationHeader, origin) {
2382
+ const redirectUrl = new import_node_url.URL(locationHeader, origin);
2383
+ const originUrl = new import_node_url.URL(origin);
2384
+ redirectUrl.protocol = originUrl.protocol;
2385
+ redirectUrl.host = originUrl.host;
2385
2386
  return redirectUrl.toString();
2386
2387
  }
2388
+ function parseConfiguredOrigin(origin) {
2389
+ if (!origin) {
2390
+ return void 0;
2391
+ }
2392
+ const parsed = parseBrowserOrigin(origin);
2393
+ if (!parsed) {
2394
+ throw new Error(
2395
+ `Invalid origin "${origin}". Expected an absolute http(s) origin with no path, search, hash, username, or password.`
2396
+ );
2397
+ }
2398
+ return parsed.origin;
2399
+ }
2400
+ function parseBrowserOrigin(origin) {
2401
+ try {
2402
+ const url = new import_node_url.URL(origin);
2403
+ const hasOnlyOrigin = url.protocol === "http:" || url.protocol === "https:" ? url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "" : false;
2404
+ return hasOnlyOrigin ? url : void 0;
2405
+ } catch {
2406
+ return void 0;
2407
+ }
2408
+ }
2409
+ function forwardedHeadersForOrigin(origin) {
2410
+ const url = new import_node_url.URL(origin);
2411
+ return {
2412
+ "x-forwarded-proto": url.protocol.slice(0, -1),
2413
+ "x-forwarded-host": url.host,
2414
+ "x-forwarded-port": url.port || (url.protocol === "https:" ? "443" : "80")
2415
+ };
2416
+ }
2417
+ function stripLocalProxyHeader(headers) {
2418
+ return Object.fromEntries(
2419
+ Object.entries(headers).filter(
2420
+ ([key]) => key.toLowerCase() !== MFE_LOCAL_PROXY_HEADER
2421
+ )
2422
+ );
2423
+ }
2387
2424
  var ProxyRequestRouter = class {
2388
2425
  constructor(config, {
2389
2426
  localApps
@@ -2750,11 +2787,13 @@ var LocalProxy = class {
2750
2787
  constructor(config, {
2751
2788
  localApps,
2752
2789
  proxyPort,
2753
- configFilePath
2790
+ configFilePath,
2791
+ origin
2754
2792
  }) {
2755
2793
  this.router = new ProxyRequestRouter(config, { localApps });
2756
2794
  this.proxyPort = proxyPort ?? this.router.config.getLocalProxyPort();
2757
2795
  this.configFilePath = configFilePath;
2796
+ this.configuredOrigin = parseConfiguredOrigin(origin) ?? parseConfiguredOrigin(process.env[ORIGIN_ENV]);
2758
2797
  this.proxy = import_http_proxy.default.createProxyServer({ secure: true });
2759
2798
  this.proxy.on("error", (err, req, res) => {
2760
2799
  if (res instanceof http.ServerResponse) {
@@ -2780,7 +2819,8 @@ var LocalProxy = class {
2780
2819
  }
2781
2820
  static fromFile(filePath, {
2782
2821
  localApps,
2783
- proxyPort
2822
+ proxyPort,
2823
+ origin
2784
2824
  }) {
2785
2825
  let microfrontends;
2786
2826
  if (filePath) {
@@ -2794,6 +2834,7 @@ var LocalProxy = class {
2794
2834
  return new LocalProxy(microfrontends.config, {
2795
2835
  localApps,
2796
2836
  proxyPort,
2837
+ origin,
2797
2838
  configFilePath: filePath
2798
2839
  });
2799
2840
  }
@@ -2826,7 +2867,7 @@ var LocalProxy = class {
2826
2867
  httpServer.on("upgrade", (req, socket, head) => {
2827
2868
  const target = this.router.getTarget(req);
2828
2869
  try {
2829
- const headers = {};
2870
+ const headers = this.configuredOrigin ? forwardedHeadersForOrigin(this.getOrigin()) : {};
2830
2871
  headers[MFE_LOCAL_PROXY_HEADER] = "1";
2831
2872
  this.proxy.ws(req, socket, head, {
2832
2873
  target: target.url,
@@ -2870,6 +2911,8 @@ var LocalProxy = class {
2870
2911
  }
2871
2912
  const target = this.router.getTarget(req);
2872
2913
  const { req: strippedReq, mfeFlagValue } = removeMfeFlagQuery(req);
2914
+ const origin = this.getOrigin();
2915
+ const forwardedHeaders = this.configuredOrigin ? forwardedHeadersForOrigin(this.configuredOrigin) : {};
2873
2916
  if (target.protocol === "https") {
2874
2917
  const { hostname, port, path: path7 } = target;
2875
2918
  const app = this.router.config.getApplication(target.application);
@@ -2880,13 +2923,15 @@ var LocalProxy = class {
2880
2923
  const overrideCookieName = getAppEnvOverrideCookieName(
2881
2924
  target.application
2882
2925
  );
2926
+ const fallbackHeaders = stripLocalProxyHeader(req.headers);
2883
2927
  const requestOptions = {
2884
2928
  hostname,
2885
2929
  path: path7,
2886
2930
  method: req.method,
2887
2931
  headers: {
2888
- ...req.headers,
2932
+ ...fallbackHeaders,
2889
2933
  host: hostname,
2934
+ ...forwardedHeaders,
2890
2935
  cookie: Object.entries(cookies).reduce((acc, [name, value]) => {
2891
2936
  if (value && // strip the override cookie if present, as this causes an auth redirect. The
2892
2937
  // override is handled by the local proxy.
@@ -2900,7 +2945,6 @@ var LocalProxy = class {
2900
2945
  },
2901
2946
  port
2902
2947
  };
2903
- const localhost = `http://localhost:${this.proxyPort}`;
2904
2948
  const proxyReq = https.request(requestOptions, (realRes) => {
2905
2949
  if (realRes.statusCode === 401 && realRes.headers["set-cookie"]?.find(
2906
2950
  (cookie) => cookie.startsWith("_vercel_sso_nonce=")
@@ -2922,7 +2966,7 @@ var LocalProxy = class {
2922
2966
  if (locationHeader) {
2923
2967
  realRes.headers.location = rewriteRedirectLocation(
2924
2968
  locationHeader,
2925
- localhost
2969
+ origin
2926
2970
  );
2927
2971
  }
2928
2972
  }
@@ -2957,7 +3001,7 @@ var LocalProxy = class {
2957
3001
  proxyReq.destroy();
2958
3002
  });
2959
3003
  } else {
2960
- const headers = {};
3004
+ const headers = { ...forwardedHeaders };
2961
3005
  headers[MFE_LOCAL_PROXY_HEADER] = "1";
2962
3006
  if (mfeFlagValue !== void 0) {
2963
3007
  headers[MFE_FLAG_VALUE_HEADER] = mfeFlagValue.toString();
@@ -2968,6 +3012,9 @@ var LocalProxy = class {
2968
3012
  });
2969
3013
  }
2970
3014
  }
3015
+ getOrigin() {
3016
+ return this.configuredOrigin ?? `http://localhost:${this.proxyPort}`;
3017
+ }
2971
3018
  // Handles requests that return data from the local proxy itself.
2972
3019
  // Returns true if the request was handled, false otherwise.
2973
3020
  handleProxyInfoRequest(path7, res) {
@@ -3026,6 +3073,9 @@ var LocalProxy = class {
3026
3073
  logger.info(`
3027
3074
  \u25B2 Microfrontends Proxy (${package_default.version}) Started`);
3028
3075
  logger.info(` - Proxy URL: http://localhost:${this.proxyPort}`);
3076
+ if (this.configuredOrigin) {
3077
+ logger.info(` - Browser origin: ${this.configuredOrigin}`);
3078
+ }
3029
3079
  if (this.configFilePath) {
3030
3080
  logger.info(` - Config: ${this.configFilePath}`);
3031
3081
  }
@@ -3188,7 +3238,7 @@ function main() {
3188
3238
  );
3189
3239
  }
3190
3240
  return parsedValue;
3191
- }).action((filePath, options) => {
3241
+ }).option("--origin <origin>", "Origin browsers use to reach the proxy").action((filePath, options) => {
3192
3242
  if (options.names && options.localApps.length) {
3193
3243
  throw new Error(
3194
3244
  "Both --names and --local-apps are set. --names is deprecated and has been replaced with --local-apps, which functions exactly the same. Please only set --local-apps."
@@ -3196,7 +3246,8 @@ function main() {
3196
3246
  }
3197
3247
  const localProxy = LocalProxy.fromFile(filePath, {
3198
3248
  localApps: options.names ?? options.localApps,
3199
- proxyPort: options.port
3249
+ proxyPort: options.port,
3250
+ origin: options.origin
3200
3251
  });
3201
3252
  localProxy.startServer();
3202
3253
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/microfrontends",
3
- "version": "2.3.6",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "Defines configuration and utilities for microfrontends development",
6
6
  "keywords": [