scry/lib/hooks/useSync.ts
Chris Kruining 83ab4df537
Migrate from .NET MAUI to Expo + Convex
Complete rewrite of Scry using TypeScript stack:

- Expo/React Native with Expo Router (file-based routing)
- Convex backend (serverless functions + real-time database)
- Adaptive camera system (expo-camera in Expo Go, Vision Camera in production)
- React Native Skia + fast-opencv for image processing
- GDPR-compliant auth setup with Zitadel OIDC (pending configuration)

Key features:
- Card recognition pipeline ported to TypeScript
- Perceptual hashing (192-bit color pHash)
- CLAHE preprocessing for lighting normalization
- Local SQLite cache with Convex sync
- Collection management with offline support

Removes all .NET/MAUI code (src/, test/, tools/).

💘 Generated with Crush

Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
2026-02-09 16:16:34 +01:00

102 lines
2.6 KiB
TypeScript

/**
* Hook for managing offline sync with local SQLite cache.
*/
import { useEffect, useState, useCallback, useRef } from "react";
import { useConvex } from "convex/react";
import { createSyncService, type SyncService, type SyncStatus } from "../db/syncService";
import { useHashCache } from "../context";
/**
* Hook to manage sync service lifecycle and status.
* Initializes local database, syncs with Convex, and provides hashes for recognition.
*/
export function useSync() {
const convex = useConvex();
const syncServiceRef = useRef<SyncService | null>(null);
const [status, setStatus] = useState<SyncStatus>({
isInitialized: false,
isSyncing: false,
lastSync: 0,
localCardCount: 0,
});
const { setCardHashes } = useHashCache();
// Initialize sync service
useEffect(() => {
if (!convex || syncServiceRef.current) return;
const service = createSyncService(convex);
syncServiceRef.current = service;
// Subscribe to status changes
const unsubscribe = service.onStatusChange(setStatus);
// Initialize and load cached hashes
const init = async () => {
await service.initialize();
// Load cached hashes into app store
const hashes = await service.getHashes();
if (hashes.length > 0) {
setCardHashes(hashes);
console.log(`[useSync] Loaded ${hashes.length} cached hashes`);
}
// Start background sync
service.sync().then(async () => {
// Reload hashes after sync
const updatedHashes = await service.getHashes();
setCardHashes(updatedHashes);
});
};
init();
return () => {
unsubscribe();
};
}, [convex, setCardHashes]);
// Manual sync trigger
const sync = useCallback(async () => {
if (!syncServiceRef.current) return;
await syncServiceRef.current.sync();
// Reload hashes after sync
const hashes = await syncServiceRef.current.getHashes();
setCardHashes(hashes);
}, [setCardHashes]);
// Clear local cache
const clearCache = useCallback(async () => {
if (!syncServiceRef.current) return;
await syncServiceRef.current.clearLocalCache();
setCardHashes([]);
}, [setCardHashes]);
// Get current hashes from cache
const getHashes = useCallback(async () => {
if (!syncServiceRef.current) return [];
return syncServiceRef.current.getHashes();
}, []);
return {
...status,
sync,
clearCache,
getHashes,
};
}
/**
* Context-free sync initialization for use in _layout.tsx
* Returns a component that initializes sync when mounted.
*/
export function SyncInitializer() {
useSync();
return null;
}