diff --git a/composables/idb/index.ts b/composables/idb/index.ts
index d71a30ccb..e00396c78 100644
--- a/composables/idb/index.ts
+++ b/composables/idb/index.ts
@@ -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'
diff --git a/composables/masto/masto.ts b/composables/masto/masto.ts
index d64addaa6..f212a0891 100644
--- a/composables/masto/masto.ts
+++ b/composables/masto/masto.ts
@@ -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)
diff --git a/constants/index.ts b/constants/index.ts
index 366946d0b..c7d281329 100644
--- a/constants/index.ts
+++ b/constants/index.ts
@@ -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'
diff --git a/package.json b/package.json
index baa330027..f46e2764c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/page-lifecycle.d.ts b/page-lifecycle.d.ts
new file mode 100644
index 000000000..963a4bea6
--- /dev/null
+++ b/page-lifecycle.d.ts
@@ -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
+}
diff --git a/plugins/page-lifecycle.client.ts b/plugins/page-lifecycle.client.ts
new file mode 100644
index 000000000..8a8d88c15
--- /dev/null
+++ b/plugins/page-lifecycle.client.ts
@@ -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,
+ }),
+ },
+ }
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8533916e0..85a204e0b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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:
diff --git a/service-worker/notification.ts b/service-worker/notification.ts
index c585ac8d3..82b34bd4a 100644
--- a/service-worker/notification.ts
+++ b/service-worker/notification.ts
@@ -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(/
/g, '\n').replace(/<\/p>
/g, '\n\n').replace(/<[^>]*>/g, ''))
}
*/
+
+export function closeDatabaseConnections() {
+ closeDatabases()
+}
diff --git a/service-worker/web-push-notifications.ts b/service-worker/web-push-notifications.ts
index 43a48429d..f1e66cc9d 100644
--- a/service-worker/web-push-notifications.ts
+++ b/service-worker/web-push-notifications.ts
@@ -1,10 +1,20 @@
///