Files
agw/src/client/shared/db/migrate-from-localstorage.ts
Hermes e4c7baab73 fix: resolve iOS Safari loading issues with database initialization
- Added comprehensive error handling for PGLite IndexedDB initialization
- Implemented iOS Safari detection and in-memory database fallback
- Added timeout handling to prevent infinite loading states
- Improved error boundaries with user-friendly error messages
- Enhanced migration robustness with detailed error logging
- Provides 'Retry' functionality for transient failures

Fixes iPhone app showing only spinner and TabBar without content loading
2026-03-12 17:54:39 +01:00

124 lines
3.6 KiB
TypeScript

import { STORAGE_KEYS } from "@/shared/lib/constants"
import type { PGlite } from "@electric-sql/pglite"
/** One-time migration: read existing localStorage data, insert into PGlite, remove localStorage keys. */
export async function migrateFromLocalStorage(db: PGlite): Promise<void> {
console.log("Starting localStorage migration...")
try {
// device ID
const deviceId = localStorage.getItem(STORAGE_KEYS.deviceId)
if (deviceId) {
try {
await db.query(
"INSERT INTO device (id) VALUES ($1) ON CONFLICT DO NOTHING",
[deviceId],
)
localStorage.removeItem(STORAGE_KEYS.deviceId)
console.log("Migrated device ID")
} catch (error) {
console.warn("Failed to migrate device ID:", error)
}
}
// follows
const followsRaw = localStorage.getItem(STORAGE_KEYS.follows)
if (followsRaw) {
try {
const follows = JSON.parse(followsRaw) as Array<{
type: string
entity_id: number
label: string
}>
for (const f of follows) {
try {
await db.query(
"INSERT INTO follows (type, entity_id, label) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
[f.type, f.entity_id, f.label],
)
} catch (error) {
console.warn("Failed to migrate follow item:", f, error)
}
}
localStorage.removeItem(STORAGE_KEYS.follows)
console.log("Migrated follows")
} catch (error) {
console.warn("Failed to migrate follows:", error)
}
}
// feed cache
const feedRaw = localStorage.getItem(STORAGE_KEYS.feedCache)
if (feedRaw) {
try {
const parsed = JSON.parse(feedRaw) as {
items: unknown[]
updatedAt: number
}
if (Array.isArray(parsed.items)) {
await db.query(
`INSERT INTO feed_cache (id, data, updated_at) VALUES ('feed_items', $1, to_timestamp($2 / 1000.0))
ON CONFLICT (id) DO UPDATE SET data = $1, updated_at = to_timestamp($2 / 1000.0)`,
[JSON.stringify({ items: parsed.items }), parsed.updatedAt],
)
}
localStorage.removeItem(STORAGE_KEYS.feedCache)
console.log("Migrated feed cache")
} catch (error) {
console.warn("Failed to migrate feed cache:", error)
}
}
// geo cache
const geoRaw = localStorage.getItem(STORAGE_KEYS.geoCache)
if (geoRaw) {
try {
const cache = JSON.parse(geoRaw) as Record<
string,
{ timestamp: number; result: unknown }
>
for (const [bundesland, entry] of Object.entries(cache)) {
try {
await db.query(
`INSERT INTO geo_cache (bundesland, data, cached_at) VALUES ($1, $2, to_timestamp($3 / 1000.0))
ON CONFLICT (bundesland) DO UPDATE SET data = $2, cached_at = to_timestamp($3 / 1000.0)`,
[bundesland, JSON.stringify(entry.result), entry.timestamp],
)
} catch (error) {
console.warn(
"Failed to migrate geo cache entry:",
bundesland,
error,
)
}
}
localStorage.removeItem(STORAGE_KEYS.geoCache)
console.log("Migrated geo cache")
} catch (error) {
console.warn("Failed to migrate geo cache:", error)
}
}
// push enabled
const pushEnabled = localStorage.getItem(STORAGE_KEYS.pushEnabled)
if (pushEnabled) {
try {
await db.query(
`INSERT INTO push_state (key, value) VALUES ('enabled', $1)
ON CONFLICT (key) DO UPDATE SET value = $1`,
[pushEnabled],
)
localStorage.removeItem(STORAGE_KEYS.pushEnabled)
console.log("Migrated push enabled state")
} catch (error) {
console.warn("Failed to migrate push enabled state:", error)
}
}
console.log("localStorage migration completed")
} catch (error) {
console.error("Migration failed with critical error:", error)
// Don't throw - let the app continue with empty database
}
}