/** * Lokaler Offline-Speicher für Diagnose-Protokolle. * * Ein Protokoll ist ein in sich geschlossenes JSON-Objekt (inkl. Geräte und * Messungen) und wird als JSON-Blob abgelegt: * - Android: SQLite (@capacitor-community/sqlite) * - Browser-Dev: localStorage * * So bleiben die Daten auf der Baustelle auch ohne Verbindung erhalten und * werden später vom Sync-Dienst zum Dolibarr-Server geschoben. */ import { Capacitor } from '@capacitor/core'; import { CapacitorSQLite, SQLiteConnection, type SQLiteDBConnection } from '@capacitor-community/sqlite'; import type { Protocol } from './types'; const DB_NAME = 'netdiag'; const LS_PREFIX = 'netdiag.protocol.'; /** * Fehlende optionale Felder eines geladenen Protokolls defensiv ergänzen. * Ältere Protokolle (vor den Geräte-Features) haben weder `savedScans` noch * `monitorSessions` — ohne diese Normalisierung liefen Komponenten auf * `undefined.map`. Mutiert das Objekt und gibt es zurück. */ function normalizeProtocol(p: Protocol): Protocol { p.devices ??= []; p.measurements ??= []; p.savedScans ??= []; p.monitorSessions ??= []; p.wifiTrackSessions ??= []; return p; } let useSqlite = false; let db: SQLiteDBConnection | null = null; /** Speicher initialisieren (Tabelle anlegen) */ export async function initDb(): Promise { useSqlite = Capacitor.isNativePlatform(); if (!useSqlite) return; const sqlite = new SQLiteConnection(CapacitorSQLite); // Nach einem WebView-Reload lebt die native SQLite-Verbindung weiter — // createConnection wuerfe dann "Connection netdiag already exists" und der // App-Start haengt ewig auf "NetDiag startet…". Vorhandene wiederverwenden. const exists = (await sqlite.isConnection(DB_NAME, false)).result; db = exists ? await sqlite.retrieveConnection(DB_NAME, false) : await sqlite.createConnection(DB_NAME, false, 'no-encryption', 1, false); if (!(await db.isDBOpen()).result) { await db.open(); } await db.execute(` CREATE TABLE IF NOT EXISTS protocols ( uuid TEXT PRIMARY KEY, json TEXT NOT NULL, dirty INTEGER NOT NULL DEFAULT 1, updated_at INTEGER NOT NULL ); `); } /** Protokoll speichern (anlegen oder ersetzen) */ export async function saveProtocol(p: Protocol): Promise { p.updatedAt = Date.now(); const json = JSON.stringify(p); if (useSqlite && db) { await db.run( 'INSERT OR REPLACE INTO protocols (uuid, json, dirty, updated_at) VALUES (?, ?, ?, ?)', [p.clientUuid, json, p.dirty ? 1 : 0, p.updatedAt], ); } else { localStorage.setItem(LS_PREFIX + p.clientUuid, json); } } /** Einzelnes Protokoll laden */ export async function getProtocol(uuid: string): Promise { if (useSqlite && db) { const res = await db.query('SELECT json FROM protocols WHERE uuid = ?', [uuid]); const row = res.values?.[0]; return row ? normalizeProtocol(JSON.parse(row.json) as Protocol) : null; } const raw = localStorage.getItem(LS_PREFIX + uuid); return raw ? normalizeProtocol(JSON.parse(raw) as Protocol) : null; } /** Alle Protokolle laden (neueste zuerst) */ export async function getAllProtocols(): Promise { let list: Protocol[] = []; if (useSqlite && db) { const res = await db.query('SELECT json FROM protocols ORDER BY updated_at DESC'); list = (res.values ?? []).map((r) => normalizeProtocol(JSON.parse(r.json) as Protocol)); } else { for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key?.startsWith(LS_PREFIX)) { list.push(normalizeProtocol(JSON.parse(localStorage.getItem(key)!) as Protocol)); } } list.sort((a, b) => b.updatedAt - a.updatedAt); } return list; } /** Alle noch nicht synchronisierten Protokolle */ export async function getDirtyProtocols(): Promise { return (await getAllProtocols()).filter((p) => p.dirty); } /** Protokoll löschen */ export async function deleteProtocol(uuid: string): Promise { if (useSqlite && db) { await db.run('DELETE FROM protocols WHERE uuid = ?', [uuid]); } else { localStorage.removeItem(LS_PREFIX + uuid); } }