mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-11 00:23:37 +08:00
fix(pwa): recover CORS image cache reads (#627)
This commit is contained in:
@@ -4,6 +4,7 @@ import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategi
|
||||
import { ExpirationPlugin } from 'workbox-expiration'
|
||||
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
|
||||
import * as navigationPreload from 'workbox-navigation-preload'
|
||||
import { corsSafeCachePlugin } from '@/utils/serviceWorkerCache'
|
||||
|
||||
// Service Worker 类型声明
|
||||
declare let self: ServiceWorkerGlobalScope & {
|
||||
@@ -97,6 +98,7 @@ registerRoute(
|
||||
new CacheFirst({
|
||||
cacheName: `image-cache-${RESOURCE_VERSION}`,
|
||||
plugins: [
|
||||
corsSafeCachePlugin,
|
||||
new CacheableResponsePlugin({
|
||||
statuses: [0, 200],
|
||||
}),
|
||||
@@ -131,6 +133,7 @@ registerRoute(
|
||||
new CacheFirst({
|
||||
cacheName: `tmdb-image-cache-${RESOURCE_VERSION}`,
|
||||
plugins: [
|
||||
corsSafeCachePlugin,
|
||||
new CacheableResponsePlugin({
|
||||
statuses: [0, 200],
|
||||
}),
|
||||
@@ -157,7 +160,7 @@ registerRoute(
|
||||
!url.pathname.includes('/api/v1/mfa/') && // 多因素认证接口
|
||||
!url.pathname.includes('/api/v1/auth/') && // 登录认证入口与票据交换
|
||||
!url.pathname.includes('/api/v1/dashboard/') && // Dashboard实时监控数据
|
||||
!url.pathname.includes('/api/v1/plugin/')&& // 插件接口
|
||||
!url.pathname.includes('/api/v1/plugin/') && // 插件接口
|
||||
!url.pathname.includes('/api/v1/subscribe/'), // 订阅接口
|
||||
new NetworkFirst({
|
||||
cacheName: `api-cache-${CACHE_VERSION}`,
|
||||
|
||||
53
src/utils/__tests__/serviceWorkerCache.spec.ts
Normal file
53
src/utils/__tests__/serviceWorkerCache.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { corsSafeCachePlugin, selectCorsSafeCachedResponse } from '../serviceWorkerCache'
|
||||
|
||||
function createResponse(type: ResponseType) {
|
||||
const response = new Response('image')
|
||||
Object.defineProperty(response, 'type', { value: type })
|
||||
return response
|
||||
}
|
||||
|
||||
describe('Service Worker cache CORS boundary', () => {
|
||||
it('rejects an opaque cache entry for a CORS image request', () => {
|
||||
const request = new Request('https://image.example/wallpaper.jpg', { mode: 'cors' })
|
||||
const cachedResponse = createResponse('opaque')
|
||||
|
||||
expect(selectCorsSafeCachedResponse(request, cachedResponse)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps a CORS-clean cache entry for a CORS image request', () => {
|
||||
const request = new Request('https://image.example/wallpaper.jpg', { mode: 'cors' })
|
||||
const cachedResponse = createResponse('cors')
|
||||
|
||||
expect(selectCorsSafeCachedResponse(request, cachedResponse)).toBe(cachedResponse)
|
||||
})
|
||||
|
||||
it('keeps an opaque cache entry for an ordinary no-cors image request', () => {
|
||||
const request = new Request('https://image.example/poster.jpg', { mode: 'no-cors' })
|
||||
const cachedResponse = createResponse('opaque')
|
||||
|
||||
expect(selectCorsSafeCachedResponse(request, cachedResponse)).toBe(cachedResponse)
|
||||
})
|
||||
|
||||
it('returns a cache miss unchanged so CacheFirst can use its network and offline policy', () => {
|
||||
const request = new Request('https://image.example/wallpaper.jpg', { mode: 'cors' })
|
||||
|
||||
expect(selectCorsSafeCachedResponse(request, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('exposes the boundary through the Workbox cached response lifecycle', async () => {
|
||||
const request = new Request('https://image.example/wallpaper.jpg', { mode: 'cors' })
|
||||
const cachedResponse = createResponse('opaque')
|
||||
|
||||
await expect(
|
||||
corsSafeCachePlugin.cachedResponseWillBeUsed?.({
|
||||
cacheName: 'image-cache',
|
||||
cachedResponse,
|
||||
event: new Event('fetch') as ExtendableEvent,
|
||||
matchOptions: {},
|
||||
request,
|
||||
}),
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
21
src/utils/serviceWorkerCache.ts
Normal file
21
src/utils/serviceWorkerCache.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { WorkboxPlugin } from 'workbox-core'
|
||||
|
||||
/**
|
||||
* CORS 请求不得采用 opaque 响应,否则浏览器无法读取其内容。
|
||||
* 普通 no-cors 请求仍保留 opaque 缓存能力;缓存未命中时继续交给策略决定网络或离线回退。
|
||||
*/
|
||||
export function selectCorsSafeCachedResponse(
|
||||
request: Request,
|
||||
cachedResponse: Response | undefined,
|
||||
): Response | undefined {
|
||||
if (request.mode === 'cors' && cachedResponse?.type === 'opaque') return undefined
|
||||
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
/** 在 Workbox 采用缓存前执行响应的 CORS 可读性边界。 */
|
||||
export const corsSafeCachePlugin: WorkboxPlugin = {
|
||||
async cachedResponseWillBeUsed({ cachedResponse, request }) {
|
||||
return selectCorsSafeCachedResponse(request, cachedResponse)
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user