"It should play by default when the Frutiger theme loads in." The embed URL now carries auto_play=true as a second try alongside the immediate play() call, and when the browser still refuses (a reload carries no gesture), the retry rides the next pointerdown/keydown anywhere OUTSIDE the player - the first tap on anything starts the music. A 700ms throttle keeps a flurry of taps from skipping through the set, taps on the player's own controls are left to their handlers (no blip on the x), and an explicit pause or dismiss is respected: no later gesture undoes it. Looking at the player (expanding the circle) starts a still-pending soundtrack too. aero-music-test simulates the refusal (__scAllowPlay) and pins all three paths: one attempt anyway, the next tap starts it, an explicit pause stays. 28 checks. Version 0.7.1, SW v93.
139 lines
4.2 KiB
JavaScript
139 lines
4.2 KiB
JavaScript
/**
|
|
* Freezmo service worker (production only).
|
|
*
|
|
* Strategy, deliberately conservative:
|
|
* - API routes are never cached and never intercepted.
|
|
* - Navigations: network first, falling back to the cached shell, then /offline.
|
|
* - /_next/static + icons: stale-while-revalidate (fast, self-healing).
|
|
* - Everything else: straight to the network.
|
|
*
|
|
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
|
|
*/
|
|
const CACHE_VERSION = "freezmo-v93";
|
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
|
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
|
|
|
const SHELL_ASSETS = [
|
|
"/",
|
|
"/offline",
|
|
"/manifest.webmanifest",
|
|
"/icons/icon-192.png",
|
|
"/icons/icon-512.png",
|
|
"/icons/apple-touch-icon.png",
|
|
];
|
|
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(
|
|
(async () => {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
await Promise.allSettled(SHELL_ASSETS.map((url) => cache.add(new Request(url, { cache: "reload" }))));
|
|
await self.skipWaiting();
|
|
})(),
|
|
);
|
|
});
|
|
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil(
|
|
(async () => {
|
|
const names = await caches.keys();
|
|
await Promise.all(
|
|
names.filter((name) => !name.startsWith(CACHE_VERSION)).map((name) => caches.delete(name)),
|
|
);
|
|
await self.clients.claim();
|
|
})(),
|
|
);
|
|
});
|
|
|
|
function isCacheableAsset(url) {
|
|
return (
|
|
url.pathname.startsWith("/_next/static/") ||
|
|
url.pathname.startsWith("/icons/") ||
|
|
url.pathname === "/manifest.webmanifest" ||
|
|
/\.(?:css|js|png|jpg|jpeg|svg|webp|woff2?)$/.test(url.pathname)
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Push reminders (opt-in; only active when VAPID is configured) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
self.addEventListener("push", (event) => {
|
|
let data = {};
|
|
try {
|
|
data = event.data ? event.data.json() : {};
|
|
} catch {
|
|
data = {};
|
|
}
|
|
event.waitUntil(
|
|
self.registration.showNotification(data.title || "Freezmo", {
|
|
body: data.body || "",
|
|
icon: "/icons/icon-192.png",
|
|
badge: "/icons/icon-192.png",
|
|
data: { url: data.url || "/" },
|
|
}),
|
|
);
|
|
});
|
|
|
|
self.addEventListener("notificationclick", (event) => {
|
|
event.notification.close();
|
|
const url = (event.notification.data && event.notification.data.url) || "/";
|
|
event.waitUntil(
|
|
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
|
|
for (const client of clients) {
|
|
if ("focus" in client) {
|
|
client.navigate(url).catch(() => {});
|
|
return client.focus();
|
|
}
|
|
}
|
|
return self.clients.openWindow(url);
|
|
}),
|
|
);
|
|
});
|
|
|
|
self.addEventListener("fetch", (event) => {
|
|
const request = event.request;
|
|
if (request.method !== "GET") return;
|
|
|
|
const url = new URL(request.url);
|
|
if (url.origin !== self.location.origin) return;
|
|
// Never touch the API: keys, notes and generated decks must always be live.
|
|
if (url.pathname.startsWith("/api/")) return;
|
|
|
|
if (request.mode === "navigate") {
|
|
event.respondWith(
|
|
(async () => {
|
|
try {
|
|
const response = await fetch(request);
|
|
// Only a real page becomes the offline shell: caching a 404 (a typo
|
|
// in the URL, a dead link) would serve the error page to every
|
|
// later offline navigation.
|
|
if (response.ok) {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
cache.put("/", response.clone()).catch(() => {});
|
|
}
|
|
return response;
|
|
} catch {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
return (await cache.match("/")) ?? (await cache.match("/offline")) ?? Response.error();
|
|
}
|
|
})(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (isCacheableAsset(url)) {
|
|
event.respondWith(
|
|
(async () => {
|
|
const cache = await caches.open(ASSET_CACHE);
|
|
const cached = await cache.match(request);
|
|
const network = fetch(request)
|
|
.then((response) => {
|
|
if (response.ok) cache.put(request, response.clone()).catch(() => {});
|
|
return response;
|
|
})
|
|
.catch(() => cached ?? Response.error());
|
|
return cached ?? network;
|
|
})(),
|
|
);
|
|
}
|
|
});
|