stacker.news/components/use-indexeddb.js
ekzyis 74ef0076fa
Request persistent storage on wallet save (#2302)
* Also request persistent storage on wallet save

* Request persistent storage on push subscription
2025-07-21 12:46:06 -05:00

181 lines
4.4 KiB
JavaScript

import { useMe } from '@/components/me'
import { useCallback, useMemo } from 'react'
const VERSION = 2
export function useIndexedDB (dbName) {
const { me } = useMe()
if (!dbName) dbName = me?.id ? `app:storage:${me.id}` : 'app:storage'
const set = useCallback(async (storeName, key, value) => {
const db = await _open(dbName, VERSION)
try {
return await _set(db, storeName, key, value)
} finally {
db.close()
}
}, [dbName])
const get = useCallback(async (storeName, key) => {
const db = await _open(dbName, VERSION)
try {
return await _get(db, storeName, key)
} finally {
db.close()
}
}, [dbName])
const deleteDb = useCallback(async () => {
return await _delete(dbName)
}, [dbName])
const open = useCallback(async () => {
return await _open(dbName, VERSION)
}, [dbName])
return useMemo(() => ({ set, get, deleteDb, open }), [set, get, deleteDb, open])
}
async function _open (dbName, version = 1) {
return await new Promise((resolve, reject) => {
if (typeof window.indexedDB === 'undefined') {
return reject(new IndexedDBOpenError('IndexedDB unavailable'))
}
const request = window.indexedDB.open(dbName, version)
request.onupgradeneeded = (event) => {
try {
const db = event.target.result
if (!db.objectStoreNames.contains('vault')) db.createObjectStore('vault')
if (db.objectStoreNames.contains('wallet_logs')) db.deleteObjectStore('wallet_logs')
} catch (error) {
reject(new IndexedDBOpenError(`upgrade failed: ${error?.message}`))
}
}
request.onerror = (event) => {
reject(new IndexedDBOpenError(request.error?.message))
}
request.onsuccess = (event) => {
const db = request.result
resolve(db)
}
})
}
async function _set (db, storeName, key, value) {
return await new Promise((resolve, reject) => {
let request
try {
request = db
.transaction(storeName, 'readwrite')
.objectStore(storeName)
.put(value, key)
} catch (error) {
return reject(new IndexedDBSetError(error?.message))
}
request.onerror = (event) => {
reject(new IndexedDBSetError(event.target?.error?.message))
}
request.onsuccess = () => {
resolve(request.result)
}
})
}
async function _get (db, storeName, key) {
return await new Promise((resolve, reject) => {
let request
try {
request = db
.transaction(storeName)
.objectStore(storeName)
.get(key)
} catch (error) {
return reject(new IndexedDBGetError(error?.message))
}
request.onerror = (event) => {
reject(new IndexedDBGetError(event.target?.error?.message))
}
request.onsuccess = () => {
resolve(request.result)
}
})
}
async function _delete (dbName) {
return await new Promise((resolve, reject) => {
if (typeof window.indexedDB === 'undefined') {
return reject(new IndexedDBOpenError('IndexedDB unavailable'))
}
const request = window.indexedDB.deleteDatabase(dbName)
request.onerror = (event) => {
reject(new IndexedDBDeleteError(event.target?.error?.message))
}
request.onsuccess = () => {
resolve(request.result)
}
})
}
export async function requestPersistentStorage () {
try {
if (!('persisted' in navigator.storage) || !('persist' in navigator.storage)) {
throw new Error('persistent storage not supported')
}
const persisted = await navigator.storage.persisted()
if (!persisted) {
// browser might prompt the user to allow persistent storage
return await navigator.storage.persist()
}
} catch (err) {
console.error('failed to request persistent storage:', err)
}
}
class IndexedDBError extends Error {
constructor (message) {
super(message)
this.name = 'IndexedDBError'
}
}
class IndexedDBOpenError extends IndexedDBError {
constructor (message) {
super(message)
this.name = 'IndexedDBOpenError'
}
}
class IndexedDBSetError extends IndexedDBError {
constructor (message) {
super(message)
this.name = 'IndexedDBSetError'
}
}
class IndexedDBGetError extends IndexedDBError {
constructor (message) {
super(message)
this.name = 'IndexedDBGetError'
}
}
class IndexedDBDeleteError extends IndexedDBError {
constructor (message) {
super(message)
this.name = 'IndexedDBDeleteError'
}
}