subpath bugfixes
This commit is contained in:
+44
-24
@@ -58,16 +58,19 @@ async function serveOrtFile(dir: string, name: string): Promise<Response> {
|
||||
|
||||
async function handleApiRequest(request: Request): Promise<Response> {
|
||||
const { pathname } = new URL(request.url);
|
||||
const apiIndex = pathname.indexOf("/api/");
|
||||
const apiPath = apiIndex !== -1 ? pathname.slice(apiIndex) : pathname;
|
||||
|
||||
// Runtime feature flag (D-015): with auth disabled, the endpoints don't
|
||||
// exist — the client's /me probe reads the 404 as "feature off". The
|
||||
// server engine (M5) is session-gated, so it disappears along with auth.
|
||||
if (
|
||||
!AUTH_ENABLED &&
|
||||
(pathname.startsWith("/api/auth/") || pathname.startsWith("/api/transcribe"))
|
||||
(apiPath.startsWith("/api/auth/") || apiPath.startsWith("/api/transcribe"))
|
||||
) {
|
||||
return NOT_FOUND();
|
||||
}
|
||||
switch (`${request.method} ${pathname}`) {
|
||||
switch (`${request.method} ${apiPath}`) {
|
||||
case "POST /api/auth/register":
|
||||
return auth.register(request);
|
||||
case "POST /api/auth/login":
|
||||
@@ -103,11 +106,20 @@ async function productionServeOptions(): Promise<ServeOptions> {
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
// API routes are dynamic (M4/M5) and network-only in the SW (D-007).
|
||||
if (url.pathname.startsWith("/api/")) return handleApiRequest(request);
|
||||
if (url.pathname.includes("/api/")) return handleApiRequest(request);
|
||||
|
||||
const relative = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.slice(1));
|
||||
const path = normalize(join(dist, relative));
|
||||
if (!path.startsWith(dist + sep)) return NOT_FOUND(); // traversal guard
|
||||
let relative = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.slice(1));
|
||||
if (relative.endsWith("/")) relative += "index.html";
|
||||
|
||||
let path = normalize(join(dist, relative));
|
||||
|
||||
// Auto-strip proxy subpaths for static assets
|
||||
while (relative.includes("/") && !(await Bun.file(path).exists())) {
|
||||
relative = relative.substring(relative.indexOf("/") + 1);
|
||||
path = normalize(join(dist, relative));
|
||||
}
|
||||
|
||||
if (!path.startsWith(dist + sep) && path !== dist) return NOT_FOUND(); // traversal guard
|
||||
|
||||
const response = await serveFile(
|
||||
path,
|
||||
@@ -166,27 +178,35 @@ async function developmentServeOptions(): Promise<ServeOptions> {
|
||||
const scriptHeaders = { "Content-Type": "text/javascript; charset=utf-8" };
|
||||
|
||||
return {
|
||||
routes: {
|
||||
"/inference.worker.js": new Response(workerScript, { headers: scriptHeaders }),
|
||||
"/sw.js": new Response(swScript, {
|
||||
// The browser's SW update check must always see fresh bytes.
|
||||
headers: { ...scriptHeaders, "Cache-Control": "no-cache" },
|
||||
}),
|
||||
"/manifest.webmanifest": () =>
|
||||
serveFile(join(import.meta.dir, "pwa/manifest.webmanifest")),
|
||||
"/icons/:file": (request) => {
|
||||
const name = request.params.file ?? "";
|
||||
return /^icon-\d+\.png$/.test(name)
|
||||
? serveFile(join(ICONS_DIR, name))
|
||||
: NOT_FOUND();
|
||||
},
|
||||
"/ort/:file": (request) => serveOrtFile(ORT_DIST, request.params.file ?? ""),
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// API routes go through the same dispatcher production uses.
|
||||
"/api/*": handleApiRequest,
|
||||
if (pathname.includes("/api/")) return handleApiRequest(request);
|
||||
|
||||
if (pathname.endsWith("/inference.worker.js")) {
|
||||
return new Response(workerScript, { headers: scriptHeaders });
|
||||
}
|
||||
if (pathname.endsWith("/sw.js")) {
|
||||
return new Response(swScript, { headers: { ...scriptHeaders, "Cache-Control": "no-cache" } });
|
||||
}
|
||||
if (pathname.endsWith("/manifest.webmanifest")) {
|
||||
return serveFile(join(import.meta.dir, "pwa/manifest.webmanifest"));
|
||||
}
|
||||
|
||||
const iconsMatch = pathname.match(/\/icons\/(icon-\d+\.png)$/);
|
||||
if (iconsMatch) {
|
||||
return serveFile(join(ICONS_DIR, iconsMatch[1]));
|
||||
}
|
||||
|
||||
const ortMatch = pathname.match(/\/ort\/(.*(?:wasm|mjs))$/);
|
||||
if (ortMatch) {
|
||||
return serveOrtFile(ORT_DIST, ortMatch[1]);
|
||||
}
|
||||
|
||||
// Serve the bundled React app for all other routes.
|
||||
"/*": index,
|
||||
// In development, this is a special Response object from Bun's HTML bundler.
|
||||
return index;
|
||||
},
|
||||
|
||||
development: {
|
||||
|
||||
+1
-1
@@ -18,7 +18,6 @@
|
||||
// In dev src/index.ts injects an empty list + "dev"; in production
|
||||
// scripts/build.ts injects the hashed dist asset list + a content hash.
|
||||
const PRECACHE: string[] = JSON.parse("__PRECACHE_MANIFEST__") as string[];
|
||||
const PRECACHE_URLS = PRECACHE.map(path => new URL(path, sw.location.href).pathname);
|
||||
const VERSION = "__CACHE_VERSION__";
|
||||
|
||||
const SHELL_CACHE = `whisper-shell-${VERSION}`;
|
||||
@@ -44,6 +43,7 @@ interface ServiceWorkerScope {
|
||||
addEventListener(type: "message", listener: (event: MessageEvent<unknown>) => void): void;
|
||||
}
|
||||
const sw = self as unknown as ServiceWorkerScope;
|
||||
const PRECACHE_URLS = PRECACHE.map(path => new URL(path, sw.location.href).pathname);
|
||||
|
||||
sw.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
|
||||
Reference in New Issue
Block a user