123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- import { cacheNames, clientsClaim } from 'workbox-core'
- import type { ManifestEntry } from 'workbox-build'
- declare let self: ServiceWorkerGlobalScope & {
- __WB_MANIFEST: ManifestEntry[]
- }
- const manifest = self.__WB_MANIFEST
- const cacheName = cacheNames.runtime
- const defaultLang = manifest.some((item) => {
- return item.url.includes(navigator.language)
- })
- ? navigator.language
- : 'en-US'
- let userPreferredLang = ''
- let cacheEntries: RequestInfo[] = []
- let cacheManifestURLs: string[] = []
- let manifestURLs: string[] = []
- class LangDB {
- private db: IDBDatabase | undefined
- private databaseName = 'PWA_DB'
- private version = 1
- private storeNames = 'lang'
- constructor() {
- this.initDB()
- }
- private initDB() {
- return new Promise<boolean>((resolve) => {
- const request = indexedDB.open(this.databaseName, this.version)
- request.onsuccess = (event) => {
- this.db = (event.target as IDBOpenDBRequest).result
- resolve(true)
- }
- request.onupgradeneeded = (event) => {
- this.db = (event.target as IDBOpenDBRequest).result
- if (!this.db.objectStoreNames.contains(this.storeNames)) {
- this.db.createObjectStore(this.storeNames, { keyPath: 'id' })
- }
- }
- })
- }
- private async initLang() {
- this.db!.transaction(this.storeNames, 'readwrite')
- .objectStore(this.storeNames)
- .add({ id: 1, lang: defaultLang })
- }
- async getLang() {
- if (!this.db) await this.initDB()
- return new Promise<string>((resolve) => {
- const request = this.db!.transaction(this.storeNames)
- .objectStore(this.storeNames)
- .get(1)
- request.onsuccess = () => {
- if (request.result) {
- resolve(request.result.lang)
- } else {
- this.initLang()
- resolve(defaultLang)
- }
- }
- request.onerror = () => {
- resolve(defaultLang)
- }
- })
- }
- async setLang(lang: string) {
- if (userPreferredLang !== lang) {
- userPreferredLang = lang
- cacheEntries = []
- cacheManifestURLs = []
- manifestURLs = []
- if (!this.db) await this.initDB()
- this.db!.transaction(this.storeNames, 'readwrite')
- .objectStore(this.storeNames)
- .put({ id: 1, lang })
- }
- }
- }
- async function initManifest() {
- userPreferredLang = userPreferredLang || (await langDB.getLang())
- // match the data that needs to be cached
- // NOTE: When the structure of the document dist files changes, it needs to be changed here
- const cacheList = [
- userPreferredLang,
- `assets/(${userPreferredLang}|app|index|style|chunks)`,
- 'images',
- 'android-chrome',
- 'apple-touch-icon',
- 'manifest.webmanifest',
- ]
- const regExp = new RegExp(`^(${cacheList.join('|')})`)
- for (const item of manifest) {
- const url = new URL(item.url, self.location.origin)
- manifestURLs.push(url.href)
- if (regExp.test(item.url) || /^\/$/.test(item.url)) {
- const request = new Request(url.href, { credentials: 'same-origin' })
- cacheEntries.push(request)
- cacheManifestURLs.push(url.href)
- }
- }
- }
- const langDB = new LangDB()
- self.addEventListener('install', (event) => {
- event.waitUntil(
- caches.open(cacheName).then(async (cache) => {
- if (!cacheEntries.length) await initManifest()
- return cache.addAll(cacheEntries)
- })
- )
- })
- self.addEventListener('activate', (event: ExtendableEvent) => {
- // clean up outdated runtime cache
- event.waitUntil(
- caches.open(cacheName).then(async (cache) => {
- if (!cacheManifestURLs.length) await initManifest()
- cache.keys().then((keys) => {
- keys.forEach((request) => {
- // clean up those who are not listed in cacheManifestURLs
- !cacheManifestURLs.includes(request.url) && cache.delete(request)
- })
- })
- })
- )
- })
- self.addEventListener('fetch', (event) => {
- event.respondWith(
- caches.match(event.request).then(async (response) => {
- // when the cache is hit, it returns directly to the cache
- if (response) return response
- if (!manifestURLs.length) await initManifest()
- const requestClone = event.request.clone()
- // otherwise create a new fetch request
- return fetch(requestClone)
- .then((response) => {
- const responseClone = response.clone()
- if (response.type !== 'basic' && response.status !== 200) {
- return response
- }
- // cache the data contained in the manifestURLs list
- manifestURLs.includes(requestClone.url) &&
- caches.open(cacheName).then((cache) => {
- cache.put(requestClone, responseClone)
- })
- return response
- })
- .catch((err) => {
- throw new Error(`Failed to load resource ${requestClone.url}, ${err}`)
- })
- })
- )
- })
- self.addEventListener('message', (event) => {
- if (event.data) {
- if (event.data.type === 'SKIP_WAITING') {
- self.skipWaiting()
- } else if (event.data.type === 'LANG') {
- langDB.setLang(event.data.lang)
- }
- }
- })
- clientsClaim()
|