Files
donetick/src/utils/LocalStore.jsx
Mo Tarbin f0e0c0e4fc Add SmartTaskInput with Custom Renderer.
Change to use `@tanstack/react-query`
Add Global ErrorProvider
Add limited Support for offline version
Fix issue with calendar display +1 day
2025-04-24 01:03:40 -04:00

224 lines
5.8 KiB
JavaScript

import { CapacitorSQLite } from '@capacitor-community/sqlite'
const CACHE_TABLE = 'offline_cache'
const QUEUE_TABLE = 'offline_request_queue'
const OFFLINE_TASK = 'offlineTasks' // For storing offline tasks
class LocalStore {
constructor() {
this.db = null
// this.useLocalStorage = !Capacitor.isNativePlatform()
this.useLocalStorage = true // default to localStorage for now.
}
async initDatabase() {
if (this.useLocalStorage) return null
if (this.db) return this.db
const db = await CapacitorSQLite.createConnection({
database: 'offline_data',
version: 1,
})
await db.open()
// Create tables if they don't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS ${CACHE_TABLE} (
key TEXT PRIMARY KEY,
value TEXT,
timestamp INTEGER
);
CREATE TABLE IF NOT EXISTS ${QUEUE_TABLE} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT,
requestBody TEXT
);
`)
this.db = db
return db
}
async saveToCache(key, data) {
const timestamp = Date.now() // Current timestamp in milliseconds
if (this.useLocalStorage) {
localStorage.setItem(key, JSON.stringify({ value: data, timestamp }))
return
}
const db = await this.initDatabase()
await db.run(
`
INSERT OR REPLACE INTO ${CACHE_TABLE} (key, value, timestamp)
VALUES (?, ?, ?);
`,
[key, JSON.stringify(data), timestamp],
)
}
// async saveTemporaryTask(task) {
// const baseURL = apiManager.getApiURL()
// const fullURL = `${baseURL}/chores/${task.tempId}`
// const options = {
// method: 'GET',
// headers: HEADERS(),
// url: fullURL,
// }
// const respond = { res: task }
// const requestId = murmurhash.v3(JSON.stringify({ fullURL, options }))
// if (this.useLocalStorage) {
// this.saveToCache(requestId, respond)
// return
// }
// const db = await this.initDatabase()
// await db.run(
// `
// INSERT INTO ${CACHE_TABLE} (url, requestBody)
// VALUES (?, ?);
// `,
// [
// requestId,
// JSON.stringify({
// url: fullURL,
// options: { method: 'GET', headers: HEADERS() },
// }),
// ],
// )
// console.log('Saved temporary task to queue:', task)
// return
// }
async getFromCache(key, ttl = 0) {
const now = Date.now()
if (this.useLocalStorage) {
const cachedItem = localStorage.getItem(key)
if (!cachedItem) return null
const { value, timestamp } = JSON.parse(cachedItem)
if (ttl > 0 && now - timestamp > ttl) {
localStorage.removeItem(key) // Remove expired item
return null
}
return value
}
const db = await this.initDatabase()
const result = await db.query(
`
SELECT value, timestamp FROM ${CACHE_TABLE} WHERE key = ?;
`,
[key],
)
if (result.values.length === 0) return null
const { value, timestamp } = result.values[0]
if (ttl > 0 && now - timestamp > ttl) {
// Remove expired item
await db.run(`DELETE FROM ${CACHE_TABLE} WHERE key = ?;`, [key])
return null
}
return JSON.parse(value)
}
async cleanExpiredCache(ttl) {
const now = Date.now()
if (this.useLocalStorage) {
const keys = Object.keys(localStorage)
for (const key of keys) {
const cachedItem = localStorage.getItem(key)
if (!cachedItem) continue
const { timestamp } = JSON.parse(cachedItem)
if (now - timestamp > ttl) {
localStorage.removeItem(key) // Remove expired item
}
}
return
}
const db = await this.initDatabase()
await db.run(
`
DELETE FROM ${CACHE_TABLE} WHERE ? - timestamp > ?;
`,
[now, ttl],
)
}
async queueRequest(requestId, requestPayload) {
if (this.useLocalStorage) {
const queue = JSON.parse(localStorage.getItem(QUEUE_TABLE)) || []
console.log('requestPayload', requestPayload)
if (typeof requestPayload?.options?.body['id'] === 'string') {
requestPayload['id'] = null
}
queue.push({ requestId, requestBody: requestPayload })
localStorage.setItem(QUEUE_TABLE, JSON.stringify(queue))
return
}
const db = await this.initDatabase()
await db.run(
`
INSERT INTO ${QUEUE_TABLE} (url, requestBody)
VALUES (?, ?);
`,
[requestId, JSON.stringify(requestPayload)],
)
}
async syncQueuedRequests() {
console.log('Syncing queued requests...')
var queueSize = 0
if (this.useLocalStorage) {
const queue = JSON.parse(localStorage.getItem(QUEUE_TABLE)) || []
console.log('LocalStore: queue: ', queue)
queueSize = queue.length
for (const request of queue) {
try {
await fetch(request.requestBody.url, request.requestBody.options)
console.log('LocalStore: Synced request:', request)
} catch (error) {
console.error('LocalStore: Failed to sync request:', request, error)
}
}
// Clear the queue after syncing
localStorage.removeItem(QUEUE_TABLE)
localStorage.removeItem(OFFLINE_TASK)
return queueSize > 0
}
const db = await this.initDatabase()
const result = await db.query(`SELECT * FROM ${QUEUE_TABLE};`)
queueSize = result.values.length
for (const request of result.values) {
try {
await fetch(
request.requestBody.url,
JSON.parse(request.requestBody.options),
)
console.log('Synced request:', request)
} catch (error) {
console.error('Failed to sync request:', request, error)
}
}
// Clear the queue after syncing
await db.run(`DELETE FROM ${QUEUE_TABLE};`)
return queueSize > 0
}
}
export const localStore = new LocalStore()