Skip to content

Commit 9a01e16

Browse files
authored
fix: route toolbar well-known requests locally (#59)
1 parent 31dff1f commit 9a01e16

3 files changed

Lines changed: 129 additions & 7 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vercel/microfrontends": patch
3+
---
4+
5+
Route Vercel Toolbar well-known requests through the local proxy to the locally running application that served the page. This supports `@vercel/toolbar@0.2.6+`, which uses `/.well-known/vercel-toolbar/*` for local dev server access.

‎packages/microfrontends/src/bin/local-proxy.test.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,48 @@ describe('class ProxyRequestRouter', () => {
378378
});
379379
});
380380

381+
it('redirects vercel toolbar requests to referring application', () => {
382+
const config = simpleConfig();
383+
const router = new ProxyRequestRouter(config, {
384+
localApps: ['vercel-site', 'vercel-marketing'],
385+
});
386+
const url = '/.well-known/vercel-toolbar/microfrontend-config';
387+
expect(
388+
router.getTarget({
389+
url,
390+
headers: {
391+
referer: 'http://localhost:3024/blog/',
392+
},
393+
}),
394+
).toMatchObject({
395+
application: 'vercel-marketing',
396+
protocol: 'http',
397+
hostname: 'localhost',
398+
port: 5984,
399+
path: url,
400+
});
401+
});
402+
403+
it('redirects vercel toolbar requests to the single local application without a referer', () => {
404+
const config = simpleConfig();
405+
const router = new ProxyRequestRouter(config, {
406+
localApps: ['vercel-marketing'],
407+
});
408+
const url = '/.well-known/vercel-toolbar/microfrontend-config?from=zero';
409+
expect(
410+
router.getTarget({
411+
url,
412+
headers: {},
413+
}),
414+
).toMatchObject({
415+
application: 'vercel-marketing',
416+
protocol: 'http',
417+
hostname: 'localhost',
418+
port: 5984,
419+
path: url,
420+
});
421+
});
422+
381423
it('redirects next source map to locally running application', () => {
382424
const config = simpleConfig();
383425
const router = new ProxyRequestRouter(config, {

‎packages/microfrontends/src/bin/local-proxy.ts‎

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const MFE_FLAG_VALUE = 'vercel-mfe-flag-value';
3939
// - with flag enabled middleware will route the request to the zone
4040
// This flag informs middleware to ignore the flag function and use this value instead.
4141
const MFE_FLAG_VALUE_HEADER = `x-${MFE_FLAG_VALUE}`;
42+
const VERCEL_TOOLBAR_PROXY_BASE_PATH = '/.well-known/vercel-toolbar';
4243

4344
interface ProxyTarget {
4445
application: string;
@@ -217,6 +218,16 @@ export class ProxyRequestRouter {
217218
middlewareMfeZone?: string;
218219
mfeFlagValue?: boolean;
219220
}): ProxyTarget | null {
221+
const toolbarTarget = this.checkVercelToolbar({
222+
path,
223+
url,
224+
referer,
225+
applications,
226+
});
227+
if (toolbarTarget) {
228+
return toolbarTarget;
229+
}
230+
220231
for (const application of Object.values(applications)) {
221232
const target = this.getApplicationTarget(application);
222233
if (middlewareMfeZone) {
@@ -354,6 +365,59 @@ export class ProxyRequestRouter {
354365
};
355366
}
356367

368+
checkVercelToolbar({
369+
path,
370+
url,
371+
referer = undefined,
372+
applications,
373+
}: {
374+
path: string;
375+
url: URL;
376+
referer?: string;
377+
applications: ChildApplication[];
378+
}): ProxyTarget | null {
379+
const isVercelToolbarRequest =
380+
url.pathname === VERCEL_TOOLBAR_PROXY_BASE_PATH ||
381+
url.pathname.startsWith(`${VERCEL_TOOLBAR_PROXY_BASE_PATH}/`);
382+
if (!isVercelToolbarRequest) {
383+
return null;
384+
}
385+
386+
if (referer) {
387+
const refererURL = new URL(referer);
388+
const refererTarget =
389+
this.findMatchingApplication({
390+
path: `${refererURL.pathname}${refererURL.search}`,
391+
url: refererURL,
392+
applications,
393+
}) ?? this.getDefaultHost(this.config);
394+
395+
if (refererTarget.isLocal) {
396+
logger.debug(
397+
` ${path} - Routing Vercel Toolbar request to referring application: ${formatProxyTarget(refererTarget)}`,
398+
);
399+
return {
400+
...refererTarget,
401+
path: `${url.pathname}${url.search}`,
402+
};
403+
}
404+
}
405+
406+
const localApp = this.getSingleLocalApp();
407+
if (!localApp) {
408+
return null;
409+
}
410+
411+
const target = this.getApplicationTarget(localApp);
412+
logger.debug(
413+
` ${path} - Routing Vercel Toolbar request to only locally running application: ${formatProxyTarget(target)}`,
414+
);
415+
return {
416+
...target,
417+
path: `${url.pathname}${url.search}`,
418+
};
419+
}
420+
357421
checkNextSourceMap({ url }: { url: URL }): ProxyTarget | null {
358422
const isSourceMap = pathToRegexp('/__nextjs_source-map').test(url.pathname);
359423
if (!isSourceMap) {
@@ -425,21 +489,32 @@ export class ProxyRequestRouter {
425489

426490
isDefaultAppLocal(): boolean {
427491
const defaultApp = this.config.getDefaultApplication();
428-
return Boolean(
429-
this.localApps.find(
430-
(name) => name === defaultApp.name || name === defaultApp.packageName,
431-
),
432-
);
492+
return this.isApplicationLocal(defaultApp);
433493
}
434494

435495
getArbitraryLocalApp() {
436496
for (const application of this.config.getAllApplications()) {
437-
const name = application.name;
438-
if (this.localApps.includes(name)) {
497+
if (this.isApplicationLocal(application)) {
439498
return application;
440499
}
441500
}
442501
}
502+
503+
getSingleLocalApp(): Application | undefined {
504+
const localApplications = this.config
505+
.getAllApplications()
506+
.filter((application) => this.isApplicationLocal(application));
507+
508+
return localApplications.length === 1 ? localApplications[0] : undefined;
509+
}
510+
511+
isApplicationLocal(application: Application): boolean {
512+
return Boolean(
513+
this.localApps.find(
514+
(name) => name === application.name || name === application.packageName,
515+
),
516+
);
517+
}
443518
}
444519

445520
export interface ProxyRequest {

0 commit comments

Comments
 (0)