@portel/photon 1.32.5 → 1.33.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/dist/auto-ui/beam/routes/api-config.js +1 -1
- package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
- package/dist/auto-ui/beam/types.d.ts +1 -0
- package/dist/auto-ui/beam/types.d.ts.map +1 -1
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +58 -9
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/auto-ui/components/card.d.ts +1 -1
- package/dist/auto-ui/components/card.d.ts.map +1 -1
- package/dist/auto-ui/components/card.js +1 -1
- package/dist/auto-ui/components/card.js.map +1 -1
- package/dist/auto-ui/components/checklist.d.ts +1 -1
- package/dist/auto-ui/components/checklist.d.ts.map +1 -1
- package/dist/auto-ui/components/checklist.js +1 -1
- package/dist/auto-ui/components/checklist.js.map +1 -1
- package/dist/auto-ui/components/form.d.ts +1 -1
- package/dist/auto-ui/components/form.d.ts.map +1 -1
- package/dist/auto-ui/components/form.js +2 -2
- package/dist/auto-ui/components/form.js.map +1 -1
- package/dist/auto-ui/components/list.d.ts +1 -1
- package/dist/auto-ui/components/list.d.ts.map +1 -1
- package/dist/auto-ui/components/list.js +1 -1
- package/dist/auto-ui/components/list.js.map +1 -1
- package/dist/auto-ui/components/progress.d.ts +1 -1
- package/dist/auto-ui/components/progress.d.ts.map +1 -1
- package/dist/auto-ui/components/progress.js +1 -1
- package/dist/auto-ui/components/progress.js.map +1 -1
- package/dist/auto-ui/components/table.d.ts +1 -1
- package/dist/auto-ui/components/table.d.ts.map +1 -1
- package/dist/auto-ui/components/table.js +1 -1
- package/dist/auto-ui/components/table.js.map +1 -1
- package/dist/auto-ui/components/tree.d.ts +1 -1
- package/dist/auto-ui/components/tree.d.ts.map +1 -1
- package/dist/auto-ui/components/tree.js +1 -1
- package/dist/auto-ui/components/tree.js.map +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts +1 -0
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
- package/dist/auto-ui/streamable-http-transport.js +40 -5
- package/dist/auto-ui/streamable-http-transport.js.map +1 -1
- package/dist/auto-ui/ui-resolver.d.ts +12 -1
- package/dist/auto-ui/ui-resolver.d.ts.map +1 -1
- package/dist/auto-ui/ui-resolver.js +19 -3
- package/dist/auto-ui/ui-resolver.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +13 -5
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/ps.d.ts +4 -0
- package/dist/cli/commands/ps.d.ts.map +1 -1
- package/dist/cli/commands/ps.js +19 -5
- package/dist/cli/commands/ps.js.map +1 -1
- package/dist/daemon/manager.d.ts +8 -0
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +46 -9
- package/dist/daemon/manager.js.map +1 -1
- package/dist/deploy/cloudflare.d.ts.map +1 -1
- package/dist/deploy/cloudflare.js +55 -7
- package/dist/deploy/cloudflare.js.map +1 -1
- package/dist/resource-server.d.ts.map +1 -1
- package/dist/resource-server.js +5 -2
- package/dist/resource-server.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +206 -14
- package/dist/server.js.map +1 -1
- package/dist/tsx-compiler.d.ts +65 -5
- package/dist/tsx-compiler.d.ts.map +1 -1
- package/dist/tsx-compiler.js +531 -52
- package/dist/tsx-compiler.js.map +1 -1
- package/package.json +3 -3
- package/templates/cloudflare/worker.ts.template +60 -0
package/dist/server.js
CHANGED
|
@@ -121,6 +121,83 @@ function uiSiblingMime(ext) {
|
|
|
121
121
|
return 'application/octet-stream';
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
function splitServerRoutePath(pathname) {
|
|
125
|
+
const normalized = pathname === '/' ? '/' : pathname.replace(/\/+$/, '');
|
|
126
|
+
if (normalized === '/')
|
|
127
|
+
return [];
|
|
128
|
+
return normalized.split('/').filter(Boolean);
|
|
129
|
+
}
|
|
130
|
+
function serverWebRouteMatches(routePath, requestPath) {
|
|
131
|
+
if (routePath === requestPath)
|
|
132
|
+
return true;
|
|
133
|
+
const routeParts = splitServerRoutePath(routePath);
|
|
134
|
+
const requestParts = splitServerRoutePath(requestPath);
|
|
135
|
+
if (routeParts.length !== requestParts.length)
|
|
136
|
+
return false;
|
|
137
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
138
|
+
const routePart = routeParts[i];
|
|
139
|
+
const requestPart = requestParts[i];
|
|
140
|
+
if (routePart.startsWith(':')) {
|
|
141
|
+
if (!requestPart)
|
|
142
|
+
return false;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (routePart !== requestPart)
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
function serverWebRouteScore(routePath) {
|
|
151
|
+
return splitServerRoutePath(routePath).reduce((score, part) => score + (part.startsWith(':') ? 1 : 10), routePath === '/' ? 100 : 0);
|
|
152
|
+
}
|
|
153
|
+
function findServerWebRoute(routes, method, requestPath) {
|
|
154
|
+
if (!routes?.length || !method)
|
|
155
|
+
return undefined;
|
|
156
|
+
const wantedMethod = method.toUpperCase();
|
|
157
|
+
return routes
|
|
158
|
+
.filter((route) => route.method.toUpperCase() === wantedMethod &&
|
|
159
|
+
serverWebRouteMatches(route.path, requestPath))
|
|
160
|
+
.sort((a, b) => serverWebRouteScore(b.path) - serverWebRouteScore(a.path))[0];
|
|
161
|
+
}
|
|
162
|
+
function uiAssetPath(asset) {
|
|
163
|
+
return asset.resolvedPath || asset.path || '';
|
|
164
|
+
}
|
|
165
|
+
function isTsxUiAsset(asset) {
|
|
166
|
+
return uiAssetPath(asset).endsWith('.tsx');
|
|
167
|
+
}
|
|
168
|
+
function selectServerClientAppUi(photon) {
|
|
169
|
+
const uiAssets = photon?.assets?.ui || [];
|
|
170
|
+
const linkedUi = photon?.appEntry?.linkedUi;
|
|
171
|
+
if (linkedUi) {
|
|
172
|
+
const linkedAsset = uiAssets.find((ui) => ui.id === linkedUi);
|
|
173
|
+
if (!linkedAsset || isTsxUiAsset(linkedAsset))
|
|
174
|
+
return linkedUi;
|
|
175
|
+
}
|
|
176
|
+
const namedApp = uiAssets.find((ui) => ui.id === 'app' && isTsxUiAsset(ui));
|
|
177
|
+
if (namedApp)
|
|
178
|
+
return namedApp.id;
|
|
179
|
+
const tsxAssets = uiAssets.filter(isTsxUiAsset);
|
|
180
|
+
if (tsxAssets.length === 1)
|
|
181
|
+
return tsxAssets[0].id;
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
function selectServerWebAppUrl(photon) {
|
|
185
|
+
if (!photon?.name)
|
|
186
|
+
return undefined;
|
|
187
|
+
const hasWebRoot = photon._httpRoutes?.some((route) => route.method === 'GET' && route.path === '/');
|
|
188
|
+
if (!hasWebRoot && !selectServerClientAppUi(photon))
|
|
189
|
+
return undefined;
|
|
190
|
+
return `/web/${photon.name}/`;
|
|
191
|
+
}
|
|
192
|
+
function shouldFallbackToServerClientApp(pathname, searchParams, route) {
|
|
193
|
+
if (route)
|
|
194
|
+
return false;
|
|
195
|
+
if (searchParams.get('legacy') === '1')
|
|
196
|
+
return false;
|
|
197
|
+
if (pathname === '/mcp' || pathname.startsWith('/mcp/'))
|
|
198
|
+
return false;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
124
201
|
function findFreePort(preferred = 0) {
|
|
125
202
|
return new Promise((resolve, reject) => {
|
|
126
203
|
const srv = createServer();
|
|
@@ -171,6 +248,12 @@ class BeamCompatTransport {
|
|
|
171
248
|
'x-photon-icon': this.photonMeta.icon || '⚡',
|
|
172
249
|
'x-photon-stateful': this.photonMeta.stateful || false,
|
|
173
250
|
'x-photon-has-settings': this.photonMeta.hasSettings || false,
|
|
251
|
+
...(this.photonMeta.webUrl
|
|
252
|
+
? {
|
|
253
|
+
'x-web-url': this.photonMeta.webUrl,
|
|
254
|
+
'x-web-description': this.photonMeta.webDescription || this.photonMeta.description || '',
|
|
255
|
+
}
|
|
256
|
+
: {}),
|
|
174
257
|
}));
|
|
175
258
|
// Append sub-photon tools (each with their own x-photon-* metadata)
|
|
176
259
|
for (const sub of this.subPhotons) {
|
|
@@ -183,6 +266,12 @@ class BeamCompatTransport {
|
|
|
183
266
|
'x-photon-icon': sub.icon,
|
|
184
267
|
'x-photon-stateful': sub.stateful,
|
|
185
268
|
'x-photon-has-settings': sub.hasSettings,
|
|
269
|
+
...(sub.webUrl
|
|
270
|
+
? {
|
|
271
|
+
'x-web-url': sub.webUrl,
|
|
272
|
+
'x-web-description': sub.webDescription || sub.description,
|
|
273
|
+
}
|
|
274
|
+
: {}),
|
|
186
275
|
};
|
|
187
276
|
// Add UI linking metadata if this tool has a linked UI
|
|
188
277
|
const meta = buildToolMCPMeta(tool, {
|
|
@@ -2140,6 +2229,62 @@ export class PhotonServer {
|
|
|
2140
2229
|
}
|
|
2141
2230
|
res.writeHead(404).end('Not Found');
|
|
2142
2231
|
}
|
|
2232
|
+
async serveTopLevelUiAsset(req, res, uiId, corsOrigin) {
|
|
2233
|
+
const ui = this.mcp?.assets?.ui.find((asset) => asset.id === uiId);
|
|
2234
|
+
if (!ui?.resolvedPath)
|
|
2235
|
+
return false;
|
|
2236
|
+
try {
|
|
2237
|
+
// Non-.tsx assets: serve the file as-is (unchanged behaviour).
|
|
2238
|
+
if (!ui.resolvedPath.endsWith('.tsx')) {
|
|
2239
|
+
const content = await readText(ui.resolvedPath);
|
|
2240
|
+
const uiHeaders = { 'Content-Type': 'text/html' };
|
|
2241
|
+
if (corsOrigin)
|
|
2242
|
+
uiHeaders['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2243
|
+
if (detectIsolationMode(req) === 'standalone') {
|
|
2244
|
+
uiHeaders['Cross-Origin-Opener-Policy'] = 'same-origin';
|
|
2245
|
+
uiHeaders['Cross-Origin-Embedder-Policy'] = 'require-corp';
|
|
2246
|
+
}
|
|
2247
|
+
res.writeHead(200, uiHeaders);
|
|
2248
|
+
res.end(content);
|
|
2249
|
+
return true;
|
|
2250
|
+
}
|
|
2251
|
+
const { compileTsxCached, tsxHttpResponse } = await import('./tsx-compiler.js');
|
|
2252
|
+
const compiled = await compileTsxCached(ui.resolvedPath);
|
|
2253
|
+
// This mount doubles as the SPA fallback (any unmatched GET), so the
|
|
2254
|
+
// browser's relative `./<hash>.js` request may arrive at an arbitrary
|
|
2255
|
+
// depth. The hashed filename is unique, so match it by basename to
|
|
2256
|
+
// serve the immutable bundle; everything else gets the shell.
|
|
2257
|
+
const reqPath = (req.url ?? '').split('?')[0];
|
|
2258
|
+
const lastSeg = decodeURIComponent(reqPath.slice(reqPath.lastIndexOf('/') + 1));
|
|
2259
|
+
const restPath = compiled.jsFileName && lastSeg === compiled.jsFileName ? lastSeg : '';
|
|
2260
|
+
const r = tsxHttpResponse(compiled, restPath);
|
|
2261
|
+
// Cheap revalidation: 304 when the shell hash is unchanged.
|
|
2262
|
+
const inm = req.headers['if-none-match'];
|
|
2263
|
+
if (r.headers['ETag'] && inm && inm === r.headers['ETag']) {
|
|
2264
|
+
const notMod = { ETag: r.headers['ETag'] };
|
|
2265
|
+
if (corsOrigin)
|
|
2266
|
+
notMod['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2267
|
+
res.writeHead(304, notMod);
|
|
2268
|
+
res.end();
|
|
2269
|
+
return true;
|
|
2270
|
+
}
|
|
2271
|
+
const uiHeaders = { ...r.headers };
|
|
2272
|
+
if (corsOrigin)
|
|
2273
|
+
uiHeaders['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2274
|
+
if (!restPath && detectIsolationMode(req) === 'standalone') {
|
|
2275
|
+
uiHeaders['Cross-Origin-Opener-Policy'] = 'same-origin';
|
|
2276
|
+
uiHeaders['Cross-Origin-Embedder-Policy'] = 'require-corp';
|
|
2277
|
+
}
|
|
2278
|
+
if (restPath)
|
|
2279
|
+
uiHeaders['Cross-Origin-Resource-Policy'] = 'same-origin';
|
|
2280
|
+
res.writeHead(r.status, uiHeaders);
|
|
2281
|
+
res.end(r.body);
|
|
2282
|
+
return true;
|
|
2283
|
+
}
|
|
2284
|
+
catch {
|
|
2285
|
+
return false;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2143
2288
|
/**
|
|
2144
2289
|
* Start server with SSE transport (HTTP)
|
|
2145
2290
|
*/
|
|
@@ -2156,11 +2301,15 @@ export class PhotonServer {
|
|
|
2156
2301
|
let beamTransport = null;
|
|
2157
2302
|
{
|
|
2158
2303
|
const photonName = this.mcp?.name || 'photon';
|
|
2304
|
+
const photonWebUrl = selectServerWebAppUrl(this.mcp);
|
|
2159
2305
|
beamTransport = new BeamCompatTransport(photonName, {
|
|
2160
2306
|
description: this.mcp?.description,
|
|
2161
2307
|
icon: this.mcp?.icon,
|
|
2162
2308
|
stateful: !!this.mcp?.stateful,
|
|
2163
2309
|
hasSettings: !!this.mcp?.hasSettings,
|
|
2310
|
+
...(photonWebUrl
|
|
2311
|
+
? { webUrl: photonWebUrl, webDescription: this.mcp?.description || `${photonName} MCP` }
|
|
2312
|
+
: {}),
|
|
2164
2313
|
});
|
|
2165
2314
|
this.capabilityNegotiator.interceptTransportForRawCapabilities(beamTransport, this.server, (msg) => this.channelManager.interceptPermissionRequest(msg));
|
|
2166
2315
|
await this.server.connect(beamTransport);
|
|
@@ -2176,6 +2325,7 @@ export class PhotonServer {
|
|
|
2176
2325
|
// these two callsites still need the narrower runtime cast until
|
|
2177
2326
|
// the loader's return type widens to PhotonClassWithMeta.
|
|
2178
2327
|
const hasSettings = !!loaded.settingsSchema?.hasSettings;
|
|
2328
|
+
const webUrl = selectServerWebAppUrl(loaded);
|
|
2179
2329
|
// Convert PhotonTool[] to MCP tool format with UI linking
|
|
2180
2330
|
const uiAssets = loaded.assets?.ui || [];
|
|
2181
2331
|
const tools = loaded.tools
|
|
@@ -2195,6 +2345,7 @@ export class PhotonServer {
|
|
|
2195
2345
|
icon,
|
|
2196
2346
|
stateful,
|
|
2197
2347
|
hasSettings,
|
|
2348
|
+
...(webUrl ? { webUrl, webDescription: loaded.description || `${loaded.name} MCP` } : {}),
|
|
2198
2349
|
tools,
|
|
2199
2350
|
});
|
|
2200
2351
|
}
|
|
@@ -2225,6 +2376,11 @@ export class PhotonServer {
|
|
|
2225
2376
|
}
|
|
2226
2377
|
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
2227
2378
|
const corsOrigin = getCorsOrigin(req);
|
|
2379
|
+
let matchedRoute = null;
|
|
2380
|
+
const route = findServerWebRoute(this.mcp?._httpRoutes, req.method, url.pathname);
|
|
2381
|
+
if (route)
|
|
2382
|
+
matchedRoute = { handler: route.handler, format: route.format };
|
|
2383
|
+
const clientAppUi = selectServerClientAppUi(this.mcp);
|
|
2228
2384
|
// Handle CORS preflight
|
|
2229
2385
|
if (req.method === 'OPTIONS') {
|
|
2230
2386
|
const preflightHeaders = {
|
|
@@ -2262,7 +2418,7 @@ export class PhotonServer {
|
|
|
2262
2418
|
return;
|
|
2263
2419
|
}
|
|
2264
2420
|
// Health check / info endpoint
|
|
2265
|
-
if (req.method === 'GET' && url.pathname === '/') {
|
|
2421
|
+
if (req.method === 'GET' && url.pathname === '/' && !matchedRoute && !clientAppUi) {
|
|
2266
2422
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2267
2423
|
const endpoints = {
|
|
2268
2424
|
sse: `http://localhost:${port}${ssePath}`,
|
|
@@ -2529,14 +2685,31 @@ export class PhotonServer {
|
|
|
2529
2685
|
}
|
|
2530
2686
|
if (ui?.resolvedPath) {
|
|
2531
2687
|
try {
|
|
2532
|
-
let content;
|
|
2533
2688
|
if (ui.resolvedPath.endsWith('.tsx')) {
|
|
2534
|
-
const { compileTsxCached } = await import('./tsx-compiler.js');
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2689
|
+
const { compileTsxCached, tsxHttpResponse } = await import('./tsx-compiler.js');
|
|
2690
|
+
const compiled = await compileTsxCached(ui.resolvedPath);
|
|
2691
|
+
const r = tsxHttpResponse(compiled, '');
|
|
2692
|
+
const inm = req.headers['if-none-match'];
|
|
2693
|
+
if (r.headers['ETag'] && inm && inm === r.headers['ETag']) {
|
|
2694
|
+
const notMod = { ETag: r.headers['ETag'] };
|
|
2695
|
+
if (corsOrigin)
|
|
2696
|
+
notMod['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2697
|
+
res.writeHead(304, notMod);
|
|
2698
|
+
res.end();
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const tsxHeaders = { ...r.headers };
|
|
2702
|
+
if (corsOrigin)
|
|
2703
|
+
tsxHeaders['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2704
|
+
if (detectIsolationMode(req) === 'standalone') {
|
|
2705
|
+
tsxHeaders['Cross-Origin-Opener-Policy'] = 'same-origin';
|
|
2706
|
+
tsxHeaders['Cross-Origin-Embedder-Policy'] = 'require-corp';
|
|
2707
|
+
}
|
|
2708
|
+
res.writeHead(r.status, tsxHeaders);
|
|
2709
|
+
res.end(r.body);
|
|
2710
|
+
return;
|
|
2539
2711
|
}
|
|
2712
|
+
const content = await readText(ui.resolvedPath);
|
|
2540
2713
|
const uiHeaders = { 'Content-Type': 'text/html' };
|
|
2541
2714
|
if (corsOrigin)
|
|
2542
2715
|
uiHeaders['Access-Control-Allow-Origin'] = corsOrigin;
|
|
@@ -2560,6 +2733,25 @@ export class PhotonServer {
|
|
|
2560
2733
|
res.writeHead(404).end('UI not found');
|
|
2561
2734
|
return;
|
|
2562
2735
|
}
|
|
2736
|
+
// Compiled .tsx bundle: the shell references `./<base>.<hash>.js`,
|
|
2737
|
+
// which lands here as a sub-path. Serve it from the compile cache
|
|
2738
|
+
// with an immutable cache policy (the hash is the cache key).
|
|
2739
|
+
if (ui?.resolvedPath?.endsWith('.tsx')) {
|
|
2740
|
+
const { compileTsxCached, tsxHttpResponse } = await import('./tsx-compiler.js');
|
|
2741
|
+
const compiled = await compileTsxCached(ui.resolvedPath);
|
|
2742
|
+
const r = tsxHttpResponse(compiled, restPath);
|
|
2743
|
+
if (r.status === 200) {
|
|
2744
|
+
const jsHeaders = { ...r.headers };
|
|
2745
|
+
if (corsOrigin)
|
|
2746
|
+
jsHeaders['Access-Control-Allow-Origin'] = corsOrigin;
|
|
2747
|
+
jsHeaders['Cross-Origin-Resource-Policy'] = 'same-origin';
|
|
2748
|
+
res.writeHead(200, jsHeaders);
|
|
2749
|
+
res.end(r.body);
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
// Not the bundle (e.g. a static sibling shipped beside the .tsx) —
|
|
2753
|
+
// fall through to filesystem resolution below.
|
|
2754
|
+
}
|
|
2563
2755
|
// Sub-path: directory-style sibling resolution. Try the filesystem
|
|
2564
2756
|
// first (dev mode), then the embedded asset tree (compiled binary).
|
|
2565
2757
|
if (ui?.resolvedPath) {
|
|
@@ -2733,13 +2925,6 @@ export class PhotonServer {
|
|
|
2733
2925
|
// @get / @post HTTP routes — dispatch to photon method, public (no auth).
|
|
2734
2926
|
// Track C: when none match, fall through to the auto-RPC table built
|
|
2735
2927
|
// from @expose tags below.
|
|
2736
|
-
const httpRoutes = this.mcp?._httpRoutes;
|
|
2737
|
-
let matchedRoute = null;
|
|
2738
|
-
if (httpRoutes?.length && req.method) {
|
|
2739
|
-
const route = httpRoutes.find((r) => r.method === req.method && r.path === url.pathname);
|
|
2740
|
-
if (route)
|
|
2741
|
-
matchedRoute = { handler: route.handler, format: route.format };
|
|
2742
|
-
}
|
|
2743
2928
|
// Track C: auto-RPC. POST /api/<kebab-method> dispatches to @expose'd
|
|
2744
2929
|
// methods. Explicit @get/@post takes precedence (matchedRoute already
|
|
2745
2930
|
// set above) so a user can override path/verb for any @expose'd
|
|
@@ -2871,6 +3056,13 @@ export class PhotonServer {
|
|
|
2871
3056
|
return;
|
|
2872
3057
|
}
|
|
2873
3058
|
}
|
|
3059
|
+
if (req.method === 'GET' &&
|
|
3060
|
+
clientAppUi &&
|
|
3061
|
+
shouldFallbackToServerClientApp(url.pathname, url.searchParams, matchedRoute ?? undefined)) {
|
|
3062
|
+
const served = await this.serveTopLevelUiAsset(req, res, clientAppUi, corsOrigin);
|
|
3063
|
+
if (served)
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
2874
3066
|
res.writeHead(404).end('Not Found');
|
|
2875
3067
|
})();
|
|
2876
3068
|
});
|