fix: handle frozen page lifecycle state (#1658)
parent
f7a8d471a6
commit
32cfe6371f
|
@ -1,7 +1,7 @@
|
|||
import type { MaybeComputedRef, RemovableRef } from '@vueuse/core'
|
||||
import type { Ref } from 'vue'
|
||||
import { del, get, set, update } from 'idb-keyval'
|
||||
import type { UseIDBOptions } from '@vueuse/integrations/useIDBKeyval'
|
||||
import { del, get, set, update } from '~/utils/elk-idb'
|
||||
|
||||
const isIDBSupported = !process.test && typeof indexedDB !== 'undefined'
|
||||
|
||||
|
|
|
@ -106,6 +106,9 @@ export function useStreaming(
|
|||
stream.value = cb(client.value)
|
||||
})
|
||||
|
||||
if (process.client && !process.test)
|
||||
useNuxtApp().$pageLifecycle.addFrozenListener(cleanup)
|
||||
|
||||
tryOnBeforeUnmount(() => isActive.value = false)
|
||||
|
||||
if (controls)
|
||||
|
|
|
@ -3,6 +3,8 @@ export const APP_NAME = 'Elk'
|
|||
export const DEFAULT_POST_CHARS_LIMIT = 500
|
||||
export const DEFAULT_FONT_SIZE = '15px'
|
||||
|
||||
export const ELK_PAGE_LIFECYCLE_FROZEN = 'elk-frozen'
|
||||
|
||||
export const STORAGE_KEY_DRAFTS = 'elk-drafts'
|
||||
export const STORAGE_KEY_USERS = 'elk-users'
|
||||
export const STORAGE_KEY_SERVERS = 'elk-servers'
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"js-yaml": "^4.1.0",
|
||||
"lru-cache": "^7.14.1",
|
||||
"masto": "^5.6.1",
|
||||
"page-lifecycle": "^0.1.2",
|
||||
"pinia": "^2.0.29",
|
||||
"shiki": "^0.12.1",
|
||||
"shiki-es": "^0.2.0",
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
declare module 'page-lifecycle/dist/lifecycle.mjs' {
|
||||
type PageLifecycleState = 'pageshow' | 'resume' | 'focus' | 'blur' | 'pagehide' | 'unload' | 'visibilitychange' | 'freeze'
|
||||
|
||||
interface PageLifecycleEvent extends Event {
|
||||
newState: PageLifecycleState
|
||||
oldState: PageLifecycleState
|
||||
}
|
||||
interface PageLifecycle extends EventTarget {
|
||||
get state(): PageLifecycleState
|
||||
get pageWasDiscarded(): boolean
|
||||
addUnsavedChanges: (id: Symbol | any) => void
|
||||
removeUnsavedChanges: (id: Symbol | any) => void
|
||||
addEventListener: (type: string, listener: (evt: PageLifecycleEvent) => void) => void
|
||||
}
|
||||
const lifecycle: PageLifecycle
|
||||
export default lifecycle
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
|
||||
import { ELK_PAGE_LIFECYCLE_FROZEN } from '~/constants'
|
||||
import { closeDatabases } from '~/utils/elk-idb'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const state = ref(lifecycle.state)
|
||||
const frozenListeners: (() => void)[] = []
|
||||
|
||||
lifecycle.addEventListener('statechange', (evt) => {
|
||||
if (evt.newState === 'freeze')
|
||||
frozenListeners.forEach(listener => listener())
|
||||
else
|
||||
state.value = evt.newState
|
||||
})
|
||||
|
||||
const addFrozenListener = (listener: () => void) => {
|
||||
frozenListeners.push(listener)
|
||||
}
|
||||
|
||||
if (useAppConfig().pwaEnabled) {
|
||||
addFrozenListener(() => {
|
||||
if (navigator.serviceWorker.controller)
|
||||
navigator.serviceWorker.controller.postMessage(ELK_PAGE_LIFECYCLE_FROZEN)
|
||||
|
||||
closeDatabases()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
pageLifecycle: reactive({
|
||||
state,
|
||||
addFrozenListener,
|
||||
}),
|
||||
},
|
||||
}
|
||||
})
|
|
@ -94,6 +94,7 @@ importers:
|
|||
nuxt: 3.1.1
|
||||
nuxt-security: ^0.10.1
|
||||
nuxt-vitest: ^0.6.4
|
||||
page-lifecycle: ^0.1.2
|
||||
pinia: ^2.0.29
|
||||
postcss-nested: ^6.0.0
|
||||
prettier: ^2.8.3
|
||||
|
@ -155,6 +156,7 @@ importers:
|
|||
js-yaml: 4.1.0
|
||||
lru-cache: 7.14.1
|
||||
masto: 5.6.1
|
||||
page-lifecycle: 0.1.2
|
||||
pinia: 2.0.29_typescript@4.9.5
|
||||
shiki: 0.12.1
|
||||
shiki-es: 0.2.0
|
||||
|
@ -9667,6 +9669,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/page-lifecycle/0.1.2:
|
||||
resolution: {integrity: sha512-+3uccYgL0CXG0KSXRxZi4uc2E6mqFWV5HqiJJgcnaJCiS0LqiuJ4vB420N21NFuLvuvLB4Jr5drgQ2NXAXF9Iw==}
|
||||
dev: false
|
||||
|
||||
/param-case/3.0.4:
|
||||
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
||||
dependencies:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { get } from 'idb-keyval'
|
||||
import { closeDatabases, get } from '../utils/elk-idb'
|
||||
import type { MastoNotification, NotificationInfo, PushPayload, UserLogin } from './types'
|
||||
|
||||
export const findNotification = async (
|
||||
|
@ -104,3 +104,7 @@ function htmlToPlainText(html: string) {
|
|||
return decodeURIComponent(html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n').replace(/<[^>]*>/g, ''))
|
||||
}
|
||||
*/
|
||||
|
||||
export function closeDatabaseConnections() {
|
||||
closeDatabases()
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
/// <reference lib="WebWorker" />
|
||||
/// <reference types="vite/client" />
|
||||
import { createNotificationOptions, findNotification } from './notification'
|
||||
import { ELK_PAGE_LIFECYCLE_FROZEN } from '../constants'
|
||||
import {
|
||||
closeDatabaseConnections,
|
||||
createNotificationOptions,
|
||||
findNotification,
|
||||
} from './notification'
|
||||
import type { PushPayload } from '~/service-worker/types'
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data === ELK_PAGE_LIFECYCLE_FROZEN)
|
||||
closeDatabaseConnections()
|
||||
})
|
||||
|
||||
export const onPush = (event: PushEvent) => {
|
||||
const promise = isClientFocused().then((isFocused) => {
|
||||
if (isFocused)
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import {
|
||||
type UseStore,
|
||||
del as delIdb,
|
||||
get as getIdb,
|
||||
promisifyRequest,
|
||||
set as setIdb,
|
||||
update as updateIdb,
|
||||
} from 'idb-keyval'
|
||||
|
||||
const databases: IDBOpenDBRequest[] = []
|
||||
|
||||
function createStore(): UseStore {
|
||||
const storeName = 'keyval'
|
||||
const request = indexedDB.open('keyval-store')
|
||||
databases.push(request)
|
||||
request.onupgradeneeded = () => request.result.createObjectStore(storeName)
|
||||
const dbp = promisifyRequest(request)
|
||||
return (txMode, callback) => dbp.then(db => callback(db.transaction(storeName, txMode).objectStore(storeName)))
|
||||
}
|
||||
|
||||
let defaultGetStoreFunc: UseStore | undefined
|
||||
function defaultGetStore() {
|
||||
if (!defaultGetStoreFunc)
|
||||
defaultGetStoreFunc = createStore()
|
||||
|
||||
return defaultGetStoreFunc
|
||||
}
|
||||
|
||||
export function get<T = any>(key: IDBValidKey) {
|
||||
return getIdb<T>(key, defaultGetStore())
|
||||
}
|
||||
|
||||
export function set(key: IDBValidKey, value: any) {
|
||||
return setIdb(key, value, defaultGetStore())
|
||||
}
|
||||
|
||||
export function update<T = any>(key: IDBValidKey, updater: (oldValue: T | undefined) => T) {
|
||||
return updateIdb(key, updater, defaultGetStore())
|
||||
}
|
||||
|
||||
export function del(key: IDBValidKey) {
|
||||
return delIdb(key, defaultGetStore())
|
||||
}
|
||||
|
||||
export function closeDatabases() {
|
||||
databases.forEach((db) => {
|
||||
if (db.result)
|
||||
db.result.close()
|
||||
})
|
||||
defaultGetStoreFunc = undefined
|
||||
}
|
Loading…
Reference in New Issue