halo-theme-dream
Version:
梦之城,童话梦境,动漫类型博客主题。
319 lines (297 loc) • 10.9 kB
JavaScript
(function () {
if (self.document) {
const currentScriptUrl = document.currentScript.src
const uninstall = new URLSearchParams(currentScriptUrl.split('?')[1]).get('uninstall')
if (uninstall) {
console.log('uninstall service worker.')
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
registration.active && registration.active.scriptURL && registration.active.scriptURL.indexOf('/sw.min.js') !== -1 && registration.unregister()
}
})
window.caches && caches.keys && caches.keys().then(function (keys) {
keys.forEach(function (key) {
console.log('delete cache', key)
caches.delete(key)
})
})
} else {
navigator.serviceWorker.register(document.currentScript.src)
.catch(function (error) {
console.log('cache failed with ' + error) // registration failed
})
}
} else {
//可以进行版本修改,删除缓存
const version = '1.0.0'
// 取得缓存名称
const cacheName = `Dream-${version}`
// 取得请求参数
const envParams = new URLSearchParams(location.href.split('?')[1])
// 是否开启多cdn源并发请求
const isCdnConcurrentRequest = envParams.get('concurrent')
// 是否开启全站缓存
const isGlobalCache = envParams.get('cache')
// 主题路径
const themePath = location.origin + '/themes/dream'
// cdn源站点,一行一个
const cdnSource = envParams.get('cdn').split(',').filter(item => item.length > 0 && item.indexOf('http') === 0)
// 禁止被处理(优先级别最高)
// 后端 api 不进行缓存
const notHandleList = [
location.origin + '/api',
]
// 需要走cdn和缓存的请求(cdn优先于缓存)
const cdnAndCacheList = [
themePath,
...cdnSource
]
//对这里面的请求只会走缓存,先缓存后下载
// jsdeliver cdn 不稳定,只走缓存
const onlyCacheList = [
location.origin + '/upload',
'https://cdn.jsdelivr.net/'
]
const cdnHandle = {
theme: {
handleRequest: url => {
if (url.indexOf(themePath) !== 0) return
const path = url.substring(themePath.length)
const version = new URLSearchParams(url.split('?')[1]).get('mew') || 'latest'
return [
url,
...cdnSource.map(value => `${value}/halo-theme-dream@${version}${path}`)
]
},
},
npm: {
handleRequest: url => {
for (let index in cdnSource) {
if (url.indexOf(cdnSource[index]) === 0) {
const path = url.substring(cdnSource[index].length)
return cdnSource.map(value => value + path)
}
}
}
},
}
/**
* 判断ur是否符合list列表中的要求
*
* @param list
* @param url
* @returns {boolean}
*/
function isExitInUrlList(list, url) {
return list.some(function (value) {
return url.indexOf(value) === 0
})
}
/**
* 判断两个url是否属于同一个请求,过滤掉部分参数
*
* @param urla
* @param urlb
* @returns {boolean}
*/
function isSameRequest(urla, urlb) {
// 除了这这些参数,其它的查询参数必须要一致,才认为是同一个请求
const white_query = new Set([
'mew', // 自定义的版本号
'v',
'version',
't',
'time',
'ts',
'timestamp'
])
const a_url = urla.split('?')
const b_url = urlb.split('?')
if (a_url[0] !== b_url[0]) {
return false
}
const a_params = new URLSearchParams('?' + a_url[1])
const b_params = new URLSearchParams('?' + b_url[1])
// 显示所有的键
for (const key of a_params.keys()) {
if (white_query.has(key)) {//对于版本号的key 忽略
continue
}
if (a_params.get(key) !== b_params.get(key)) {//其它key的值必须相等,比如type=POST 这种
return false
}
}
return true
}
//添加缓存
self.addEventListener('install', function (event) {
console.log('install service worker.')
event.waitUntil(self.skipWaiting()) //这样会触发activate事件
})
// 激活
self.addEventListener('activate', function (event) {
console.log('service worker activate.')
const mainCache = [cacheName]
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (mainCache.indexOf(cacheName) === -1) {//没有找到该版本号下面的缓存
// When it doesn't match any condition, delete it.
console.info('version changed, clean the cache, SW: deleting ' + cacheName)
return caches.delete(cacheName)
}
})
)
})
)
return self.clients.claim()
})
// 拦截请求使用缓存的内容
self.addEventListener('fetch', function (event) {
// 非 get 请求不处理,被禁止处理的地址不处理
if (event.request.method !== 'GET'
|| isExitInUrlList(notHandleList, event.request.url)) {
return false
}
const isCdnAndCache = isExitInUrlList(cdnAndCacheList, event.request.url)
const isOnlyCacheList = isExitInUrlList(onlyCacheList, event.request.url)
// cdn并发请求未开启 或 请求没有被任何路由命中
if (!isCdnConcurrentRequest || !(isCdnAndCache || isOnlyCacheList)) {
// 不需要全站离线
if (!isGlobalCache) {
return false
}
// 先发起请求,如果请求失败则读取离线缓存
event.respondWith(caches.open(cacheName)
.then(cache => {
return fetch(event.request)
.then((response) => {
if (response.status === 200) cache.put(event.request, response.clone())
return response
})
.catch(() => cache.match(event.request))
})
)
return true
}
// 劫持 HTTP Request
event.respondWith(
caches.open(cacheName).then(function (cache) {
// 查找缓存
return cache.match(event.request).then(function (cacheResponse) {
// 直接返回缓存
if (cacheResponse) return cacheResponse
return handleRequest(event.request, isCdnAndCache)
.then((response) => {
const responseClone = response.clone()
// ignoreSearch 忽略请求参数进行查找,用于匹配不同版本
cache.matchAll(event.request, {'ignoreSearch': true})
.then(function (cache_response_list) {
// 删除旧版本的缓存文件
if (cache_response_list) {
for (const cache_response of cache_response_list) {
const responseUrl = cache_response.url || cache_response.headers.get('service-worker-origin')
if (isSameRequest(responseUrl, event.request.url)) {
cache.delete(responseUrl)
}
}
}
cache.put(event.request, responseClone)
})
return response
})
.catch(error => {
console.error(error)
return cache.matchAll(event.request, {'ignoreSearch': true})
.then(function (cache_response_list) {
// 从缓存中取得历史版本的文件
if (cache_response_list) {
for (const cache_response of cache_response_list) {
if (isSameRequest(cache_response.url || cache_response.headers.get('service-worker-origin'), event.request.url)) {
return cache_response
}
}
}
})
})
})
})
)
})
/**
* 处理匹配的请求
* @param req
* @param isCdnAndCache
* @returns {Promise<Response>|*}
*/
function handleRequest(req, isCdnAndCache) {
// 不是cdn缓存或者未开启cdn并发,直接进行查询并返回
if (!isCdnAndCache || !isCdnConcurrentRequest) return fetch(req)
// 匹配 cdn
for (const type in cdnHandle) {
const urls = cdnHandle[type].handleRequest(req.url)
if (urls) return fetchAny(req.url, urls)
}
// 没有匹配到url,直接发起请求
return fetch(req)
}
// Promise.any 的 polyfill
function createPromiseAny() {
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
promises = Array.isArray(promises) ? promises : []
let len = promises.length
let errs = []
if (len === 0)
return reject(new AggregateError('All promises were rejected'))
promises.forEach((p) => {
if (!(p instanceof Promise)) return reject(p)
p.then(
(res) => resolve(res),
(err) => {
len--
errs.push(err)
if (len === 0) reject(new AggregateError(errs))
}
)
})
})
}
}
// 发送所有请求
function fetchAny(originUrl, urls) {
// 中断一个或多个请求
const controller = new AbortController()
const signal = controller.signal
// 遍历将所有的请求地址转换为promise
const PromiseAll = urls.map((url) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
fetch(url, {signal})
.then(async res => { // 重新封装响应
const newHeaders = new Headers(res.headers)
newHeaders.set('service-worker-origin', originUrl)
return new Response(await res.arrayBuffer(), {
status: res.status,
headers: newHeaders,
})
})
.then((res) => {
if (res.status !== 200) {
reject(res)
return
}
controller.abort() // 中断
resolve(res)
})
.catch(() => reject(null)) // 去除中断的错误信息
})
})
// 判断浏览器是否支持 Promise.any
if (!Promise.any) createPromiseAny()
// 谁先返回"成功状态"则返回谁的内容
return Promise.any(PromiseAll)
}
}
})()