feat(node-player): use Service Worker for auth, enable streaming playback

Add a Service Worker that intercepts /api/* requests and injects the
Bearer token. This allows <audio> and <img> elements to use direct
URLs instead of downloading entire files as blobs first.

- Audio now streams progressively (no full download before playback)
- Cover art loads via regular <img src> (SW adds auth header)
- Remove blob-based preloadStream, fetchCoverBlob, useCoverUrl hook
- Register SW in main.tsx, token synced via postMessage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ultradesu
2026-04-08 17:30:17 +01:00
parent 6b1aa6b5d5
commit d6dd046fad
9 changed files with 61 additions and 64 deletions
+9 -12
View File
@@ -8,8 +8,17 @@ export const furumiApi = axios.create({
baseURL: API_ROOT,
})
function sendTokenToSW(token: string) {
navigator.serviceWorker?.controller?.postMessage({ type: 'SET_TOKEN', token })
// Also send to waiting/installing SW
navigator.serviceWorker?.ready.then((reg) => {
reg.active?.postMessage({ type: 'SET_TOKEN', token })
})
}
export function setAuthToken(token: string) {
furumiApi.defaults.headers.common['Authorization'] = `Bearer ${token}`
sendTokenToSW(token)
}
export function clearAuthToken() {
@@ -37,7 +46,6 @@ furumiApi.interceptors.response.use(
const original = error.config
if (error.response?.status === 401 && !original._retried) {
original._retried = true
// Deduplicate concurrent refresh attempts
if (!refreshPromise) {
refreshPromise = refreshToken().finally(() => { refreshPromise = null })
}
@@ -96,14 +104,3 @@ export async function getRecentPlays(): Promise<RecentPlay[] | null> {
export async function recordPlay(trackSlug: string): Promise<void> {
await furumiApi.post(`/tracks/${trackSlug}/play`).catch(() => null)
}
export async function preloadStream(trackSlug: string) {
return await furumiApi.get(`/stream/${trackSlug}`, { responseType: 'blob' }).catch(() => null)
}
export async function fetchCoverBlob(trackSlug: string): Promise<string | null> {
const res = await furumiApi.get(`/tracks/${trackSlug}/cover`, { responseType: 'blob' }).catch(() => null)
if (!res?.data) return null
return URL.createObjectURL(res.data)
}