subpath feature
This commit is contained in:
+3
-3
@@ -79,9 +79,9 @@ const swSource = await swBuild.outputs[0]!.text();
|
||||
const precachePaths = precachePathsFromOutputs(
|
||||
shellBuild.outputs.map((output) => relative(DIST, output.path)),
|
||||
[
|
||||
"/inference.worker.js",
|
||||
"/manifest.webmanifest",
|
||||
...ICON_SIZES.map((size) => `/icons/icon-${size}.png`),
|
||||
"inference.worker.js",
|
||||
"manifest.webmanifest",
|
||||
...ICON_SIZES.map((size) => `icons/icon-${size}.png`),
|
||||
],
|
||||
);
|
||||
const version = Bun.hash(
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ export function App() {
|
||||
// main thread (Architecture.md §5). The worker script is bundled by the
|
||||
// server at startup and served at a stable URL (see src/index.ts) because
|
||||
// Bun's bundler does not follow `new Worker(new URL(...))` graphs yet.
|
||||
const worker = new Worker("/inference.worker.js", { type: "module" });
|
||||
const worker = new Worker("inference.worker.js", { type: "module" });
|
||||
worker.addEventListener("message", (event: MessageEvent<unknown>) => {
|
||||
const message = event.data;
|
||||
if (!isWorkerToUiMessage(message)) return;
|
||||
|
||||
@@ -122,7 +122,7 @@ export function login(
|
||||
password: string,
|
||||
fetchFn: FetchLike = defaultFetch,
|
||||
): Promise<AuthUser> {
|
||||
return requestUser(fetchFn, "/api/auth/login", { username, password });
|
||||
return requestUser(fetchFn, "api/auth/login", { username, password });
|
||||
}
|
||||
|
||||
/** POST /api/auth/register → user (auto signed in), or throws (400/409). */
|
||||
@@ -131,7 +131,7 @@ export function register(
|
||||
password: string,
|
||||
fetchFn: FetchLike = defaultFetch,
|
||||
): Promise<AuthUser> {
|
||||
return requestUser(fetchFn, "/api/auth/register", { username, password });
|
||||
return requestUser(fetchFn, "api/auth/register", { username, password });
|
||||
}
|
||||
|
||||
/** Result of the startup /me probe (fetchAuthStatus). */
|
||||
@@ -158,7 +158,7 @@ export async function fetchAuthStatus(
|
||||
fetchFn: FetchLike = defaultFetch,
|
||||
): Promise<AuthStatus> {
|
||||
try {
|
||||
const response = await fetchFn("/api/auth/me", {
|
||||
const response = await fetchFn("api/auth/me", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
@@ -179,7 +179,7 @@ export async function fetchAuthStatus(
|
||||
*/
|
||||
export async function logout(fetchFn: FetchLike = defaultFetch): Promise<void> {
|
||||
try {
|
||||
await fetchFn("/api/auth/logout", {
|
||||
await fetchFn("api/auth/logout", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ export class ServerEngine implements Engine {
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await this.fetchFn(`/api/transcribe?${params}`, {
|
||||
response = await this.fetchFn(`api/transcribe?${params}`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
@@ -327,7 +327,7 @@ export async function fetchServerEngineStatus(
|
||||
fetchFn: FetchLike = defaultFetch,
|
||||
): Promise<ServerEngineStatus> {
|
||||
try {
|
||||
const response = await fetchFn("/api/transcribe/status", {
|
||||
const response = await fetchFn("api/transcribe/status", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
"name": "Whisper Web — on-device speech-to-text",
|
||||
"short_name": "Whisper Web",
|
||||
"description": "Speech-to-text that runs entirely in your browser via OpenAI Whisper. Works offline once installed with a downloaded model.",
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"id": "./",
|
||||
"start_url": "./",
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"theme_color": "#16181d",
|
||||
"background_color": "#16181d",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192.png",
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512.png",
|
||||
"src": "icons/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export function precachePathsFromOutputs(
|
||||
for (const outputPath of outputPaths) {
|
||||
const normalized = outputPath.replaceAll("\\", "/").replace(/^\.?\//, "");
|
||||
if (normalized.endsWith(".map")) continue;
|
||||
paths.add(normalized === "index.html" ? "/" : `/${normalized}`);
|
||||
paths.add(normalized === "index.html" ? "./" : normalized);
|
||||
}
|
||||
for (const extra of extraPaths) paths.add(extra);
|
||||
return [...paths].sort();
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export function registerServiceWorker(options: RegisterOptions): void {
|
||||
if (container.controller) options.onControlled?.();
|
||||
|
||||
container
|
||||
.register("/sw.js")
|
||||
.register("sw.js")
|
||||
.then((registration) => {
|
||||
const notify = (worker: ServiceWorker) => {
|
||||
options.onUpdateReady(() => {
|
||||
|
||||
+5
-4
@@ -18,6 +18,7 @@
|
||||
// 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}`;
|
||||
@@ -118,19 +119,19 @@ sw.addEventListener("fetch", (event) => {
|
||||
if (url.origin !== sw.location.origin) return;
|
||||
// API is network-only (D-007) — auth and the server engine must never be
|
||||
// served stale from cache.
|
||||
if (url.pathname.startsWith("/api/")) return;
|
||||
if (url.pathname.includes("/api/")) return;
|
||||
|
||||
if (url.pathname.startsWith("/ort/")) {
|
||||
if (url.pathname.includes("/ort/")) {
|
||||
event.respondWith(cacheFirst(RUNTIME_CACHE, request));
|
||||
return;
|
||||
}
|
||||
if (PRECACHE.includes(url.pathname)) {
|
||||
if (PRECACHE_URLS.includes(url.pathname)) {
|
||||
event.respondWith(cacheFirst(SHELL_CACHE, request));
|
||||
return;
|
||||
}
|
||||
// Anything else (dev's unhashed shell, navigations to deep links):
|
||||
// network-first with cache fallback; navigations fall back to the shell.
|
||||
event.respondWith(networkFirst(request, request.mode === "navigate" ? "/" : null));
|
||||
event.respondWith(networkFirst(request, request.mode === "navigate" ? "./" : null));
|
||||
});
|
||||
|
||||
export {};
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
// them at /ort/* (src/index.ts; copied into dist/ort by scripts/build.ts)
|
||||
// and the service worker runtime-caches them cache-first (src/pwa/sw.ts).
|
||||
if (env.backends.onnx.wasm) {
|
||||
env.backends.onnx.wasm.wasmPaths = "/ort/";
|
||||
env.backends.onnx.wasm.wasmPaths = "ort/";
|
||||
}
|
||||
|
||||
// `lib: ["DOM"]` types `self` as Window; give it the worker-scope shape we use.
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("fetchAuthStatus (fail-silent /me)", () => {
|
||||
const { fetch, calls } = mockFetch(jsonResponse(200, { user: USER }));
|
||||
expect(await fetchAuthStatus(fetch)).toEqual({ enabled: true, user: USER });
|
||||
// The cookie is HttpOnly — it must ride along via same-origin credentials.
|
||||
expect(calls[0]?.input).toBe("/api/auth/me");
|
||||
expect(calls[0]?.input).toBe("api/auth/me");
|
||||
expect(calls[0]?.init?.credentials).toBe("same-origin");
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ describe("login", () => {
|
||||
expect(await login("alice", "hunter22", fetch)).toEqual(USER);
|
||||
|
||||
const call = calls[0];
|
||||
expect(call?.input).toBe("/api/auth/login");
|
||||
expect(call?.input).toBe("api/auth/login");
|
||||
expect(call?.init?.method).toBe("POST");
|
||||
expect(call?.init?.credentials).toBe("same-origin");
|
||||
expect(
|
||||
@@ -145,7 +145,7 @@ describe("register", () => {
|
||||
test("returns the user on 201", async () => {
|
||||
const { fetch, calls } = mockFetch(jsonResponse(201, { user: USER }));
|
||||
expect(await register("alice", "hunter22", fetch)).toEqual(USER);
|
||||
expect(calls[0]?.input).toBe("/api/auth/register");
|
||||
expect(calls[0]?.input).toBe("api/auth/register");
|
||||
});
|
||||
|
||||
test("surfaces {error} on 409 (username taken)", async () => {
|
||||
@@ -172,7 +172,7 @@ describe("logout", () => {
|
||||
test("POSTs to /api/auth/logout with credentials", async () => {
|
||||
const { fetch, calls } = mockFetch(new Response(null, { status: 204 }));
|
||||
await logout(fetch);
|
||||
expect(calls[0]?.input).toBe("/api/auth/logout");
|
||||
expect(calls[0]?.input).toBe("api/auth/logout");
|
||||
expect(calls[0]?.init?.method).toBe("POST");
|
||||
expect(calls[0]?.init?.credentials).toBe("same-origin");
|
||||
});
|
||||
|
||||
@@ -11,15 +11,15 @@ describe("precachePathsFromOutputs", () => {
|
||||
expect(
|
||||
precachePathsFromOutputs(
|
||||
["index.html", "chunk-abc123.js", "chunk-abc123.js.map", "./index-def456.css"],
|
||||
["/inference.worker.js"],
|
||||
["inference.worker.js"],
|
||||
),
|
||||
).toEqual(["/", "/chunk-abc123.js", "/index-def456.css", "/inference.worker.js"]);
|
||||
).toEqual(["./", "chunk-abc123.js", "index-def456.css", "inference.worker.js"]);
|
||||
});
|
||||
|
||||
test("deduplicates and sorts", () => {
|
||||
expect(precachePathsFromOutputs(["a.js", "a.js"], ["/a.js", "/"])).toEqual([
|
||||
"/",
|
||||
"/a.js",
|
||||
expect(precachePathsFromOutputs(["a.js", "a.js"], ["a.js", "./"])).toEqual([
|
||||
"./",
|
||||
"a.js",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user