feat: enhance offline support with improved network management and sync handling
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Preferences } from '@capacitor/preferences'
|
||||
import { API_URL } from '../Config'
|
||||
import { networkManager } from '../hooks/NetworkManager'
|
||||
import { logout, RefreshToken } from './Fetcher'
|
||||
import {
|
||||
clearAllTokens,
|
||||
@@ -146,15 +147,22 @@ class ApiClient {
|
||||
async request(endpoint, options = {}) {
|
||||
await this.init()
|
||||
const url = `${this.customServerURL}${endpoint}`
|
||||
|
||||
// Abort after 10s so a dead/unreachable server doesn't hang the UI
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10_000)
|
||||
|
||||
const config = {
|
||||
// credentials: 'include',
|
||||
...options,
|
||||
headers: this.getHeaders(options.headers),
|
||||
signal: options.signal ?? controller.signal,
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Initial Request
|
||||
let response = await fetch(url, config)
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
// 2. Check for 401 (Unauthorized)
|
||||
if (response.status === 401) {
|
||||
@@ -222,6 +230,9 @@ class ApiClient {
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
// fetch() threw = network-level failure or timeout — mark server unreachable
|
||||
networkManager.setServerUnreachable()
|
||||
console.error('Request failed', error)
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -26,17 +26,32 @@ class SQLiteBackend {
|
||||
constructor() {
|
||||
this.db = null
|
||||
this.initialized = false
|
||||
this._initPromise = null
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.initialized) return
|
||||
// Return the in-flight promise if init is already underway (prevents double createConnection)
|
||||
if (this._initPromise) return this._initPromise
|
||||
|
||||
this.db = await CapacitorSQLite.createConnection({
|
||||
database: DB_NAME,
|
||||
version: DB_VERSION,
|
||||
encrypted: false,
|
||||
mode: 'no-encryption',
|
||||
this._initPromise = this._doInit().finally(() => {
|
||||
this._initPromise = null
|
||||
})
|
||||
return this._initPromise
|
||||
}
|
||||
|
||||
async _doInit() {
|
||||
try {
|
||||
this.db = await CapacitorSQLite.createConnection({
|
||||
database: DB_NAME,
|
||||
version: DB_VERSION,
|
||||
encrypted: false,
|
||||
mode: 'no-encryption',
|
||||
})
|
||||
} catch (err) {
|
||||
// Connection already open (e.g. React StrictMode double-mount) — reuse it
|
||||
if (!err?.message?.includes('already exists')) throw err
|
||||
}
|
||||
await CapacitorSQLite.open({ database: DB_NAME })
|
||||
|
||||
await CapacitorSQLite.execute({
|
||||
|
||||
@@ -55,6 +55,8 @@ class SyncEngine {
|
||||
// Step 3: Delta sync from server
|
||||
await this._deltaSync()
|
||||
|
||||
// Sync succeeded — server is reachable (only sync success restores online status)
|
||||
networkManager.setServerReachable()
|
||||
this._notify({ syncing: false, lastSync: Date.now() })
|
||||
return true
|
||||
} catch (err) {
|
||||
@@ -182,7 +184,7 @@ class SyncEngine {
|
||||
let hasMore = true
|
||||
let currentCursor = cursor
|
||||
|
||||
while (hasMore && networkManager.isOnline) {
|
||||
while (hasMore && networkManager.deviceOnline) {
|
||||
// Use apiClient.get which handles auth and returns a fetch Response
|
||||
const response = await apiClient.get(
|
||||
`/sync/changes?since=${currentCursor}`,
|
||||
|
||||
Reference in New Issue
Block a user