fix: handle frozen page lifecycle state (#1658)

This commit is contained in:
Joaquín Sánchez 2023-02-06 23:22:56 +01:00 committed by GitHub
parent f7a8d471a6
commit 32cfe6371f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 3 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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'

View file

@ -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",

17
page-lifecycle.d.ts vendored Normal file
View file

@ -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
}

View file

@ -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,
}),
},
}
})

View file

@ -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:

View file

@ -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()
}

View file

@ -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)

51
utils/elk-idb.ts Normal file
View file

@ -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
}