feat: auto-sync recipes and show sync button only in PWA mode
- Auto-sync recipes every 30 minutes when online in PWA mode - Only show offline sync button when running as installed PWA - Detect standalone mode via display-mode media query and iOS check - Trigger initial sync on PWA install (appinstalled event) - Listen for online event to sync when coming back online - Store last sync time in localStorage to track sync intervals
This commit is contained in:
@@ -19,7 +19,8 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
mounted = true;
|
mounted = true;
|
||||||
await pwaStore.checkAvailability();
|
// Initialize PWA store (checks standalone mode, starts auto-sync if needed)
|
||||||
|
await pwaStore.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleSync() {
|
async function handleSync() {
|
||||||
@@ -147,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{#if mounted}
|
{#if mounted && pwaStore.isStandalone}
|
||||||
<div class="offline-sync">
|
<div class="offline-sync">
|
||||||
<button
|
<button
|
||||||
class="sync-button"
|
class="sync-button"
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
import { isOfflineDataAvailable, getLastSync, clearOfflineData } from '$lib/offline/db';
|
import { isOfflineDataAvailable, getLastSync, clearOfflineData } from '$lib/offline/db';
|
||||||
import { downloadAllRecipes, type SyncResult } from '$lib/offline/sync';
|
import { downloadAllRecipes, type SyncResult } from '$lib/offline/sync';
|
||||||
|
|
||||||
|
const AUTO_SYNC_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
||||||
|
const LAST_SYNC_KEY = 'bocken-last-sync-time';
|
||||||
|
|
||||||
type PWAState = {
|
type PWAState = {
|
||||||
isOfflineAvailable: boolean;
|
isOfflineAvailable: boolean;
|
||||||
isSyncing: boolean;
|
isSyncing: boolean;
|
||||||
lastSyncDate: string | null;
|
lastSyncDate: string | null;
|
||||||
recipeCount: number;
|
recipeCount: number;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
isStandalone: boolean;
|
||||||
|
isInitialized: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createPWAStore() {
|
function createPWAStore() {
|
||||||
@@ -15,10 +21,60 @@ function createPWAStore() {
|
|||||||
isSyncing: false,
|
isSyncing: false,
|
||||||
lastSyncDate: null,
|
lastSyncDate: null,
|
||||||
recipeCount: 0,
|
recipeCount: 0,
|
||||||
error: null
|
error: null,
|
||||||
|
isStandalone: false,
|
||||||
|
isInitialized: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
let autoSyncInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
|
// Check if running as installed PWA (standalone mode)
|
||||||
|
function checkStandaloneMode(): boolean {
|
||||||
|
if (!browser) return false;
|
||||||
|
|
||||||
|
// Check display-mode media query (works on most browsers)
|
||||||
|
const standaloneQuery = window.matchMedia('(display-mode: standalone)');
|
||||||
|
if (standaloneQuery.matches) return true;
|
||||||
|
|
||||||
|
// Check iOS Safari standalone mode
|
||||||
|
if ('standalone' in navigator && (navigator as any).standalone === true) return true;
|
||||||
|
|
||||||
|
// Check if launched from home screen on Android
|
||||||
|
if (document.referrer.includes('android-app://')) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should auto-sync (online and enough time has passed)
|
||||||
|
function shouldAutoSync(): boolean {
|
||||||
|
if (!browser || !navigator.onLine) return false;
|
||||||
|
|
||||||
|
const lastSync = localStorage.getItem(LAST_SYNC_KEY);
|
||||||
|
if (!lastSync) return true; // Never synced, should sync
|
||||||
|
|
||||||
|
const lastSyncTime = parseInt(lastSync, 10);
|
||||||
|
const now = Date.now();
|
||||||
|
return now - lastSyncTime >= AUTO_SYNC_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record sync time
|
||||||
|
function recordSyncTime() {
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem(LAST_SYNC_KEY, Date.now().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-sync if conditions are met
|
||||||
|
async function autoSync() {
|
||||||
|
if (!navigator.onLine || state.isSyncing) return;
|
||||||
|
|
||||||
|
if (shouldAutoSync()) {
|
||||||
|
console.log('[PWA] Auto-syncing recipes...');
|
||||||
|
await store.syncForOffline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = {
|
||||||
get isOfflineAvailable() {
|
get isOfflineAvailable() {
|
||||||
return state.isOfflineAvailable;
|
return state.isOfflineAvailable;
|
||||||
},
|
},
|
||||||
@@ -34,6 +90,75 @@ function createPWAStore() {
|
|||||||
get error() {
|
get error() {
|
||||||
return state.error;
|
return state.error;
|
||||||
},
|
},
|
||||||
|
get isStandalone() {
|
||||||
|
return state.isStandalone;
|
||||||
|
},
|
||||||
|
get isInitialized() {
|
||||||
|
return state.isInitialized;
|
||||||
|
},
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
if (!browser || state.isInitialized) return;
|
||||||
|
|
||||||
|
state.isStandalone = checkStandaloneMode();
|
||||||
|
await this.checkAvailability();
|
||||||
|
|
||||||
|
// Listen for display mode changes (e.g., when installed)
|
||||||
|
const standaloneQuery = window.matchMedia('(display-mode: standalone)');
|
||||||
|
standaloneQuery.addEventListener('change', (e) => {
|
||||||
|
state.isStandalone = e.matches;
|
||||||
|
if (e.matches) {
|
||||||
|
// Just became standalone (installed), trigger initial sync
|
||||||
|
console.log('[PWA] App installed, starting initial sync...');
|
||||||
|
this.syncForOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for app installed event
|
||||||
|
window.addEventListener('appinstalled', () => {
|
||||||
|
console.log('[PWA] App installed event received');
|
||||||
|
state.isStandalone = true;
|
||||||
|
this.syncForOffline();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start auto-sync if in standalone mode
|
||||||
|
if (state.isStandalone) {
|
||||||
|
this.startAutoSync();
|
||||||
|
|
||||||
|
// Do initial sync if needed
|
||||||
|
if (shouldAutoSync()) {
|
||||||
|
this.syncForOffline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for online/offline events
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
if (state.isStandalone && shouldAutoSync()) {
|
||||||
|
autoSync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state.isInitialized = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
startAutoSync() {
|
||||||
|
if (autoSyncInterval) return; // Already running
|
||||||
|
|
||||||
|
// Check every 5 minutes if we should sync
|
||||||
|
autoSyncInterval = setInterval(() => {
|
||||||
|
autoSync();
|
||||||
|
}, 5 * 60 * 1000); // Check every 5 minutes
|
||||||
|
|
||||||
|
console.log('[PWA] Auto-sync enabled (every 30 minutes)');
|
||||||
|
},
|
||||||
|
|
||||||
|
stopAutoSync() {
|
||||||
|
if (autoSyncInterval) {
|
||||||
|
clearInterval(autoSyncInterval);
|
||||||
|
autoSyncInterval = null;
|
||||||
|
console.log('[PWA] Auto-sync disabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async checkAvailability() {
|
async checkAvailability() {
|
||||||
try {
|
try {
|
||||||
@@ -68,6 +193,7 @@ function createPWAStore() {
|
|||||||
state.isOfflineAvailable = true;
|
state.isOfflineAvailable = true;
|
||||||
state.lastSyncDate = new Date().toISOString();
|
state.lastSyncDate = new Date().toISOString();
|
||||||
state.recipeCount = result.recipeCount;
|
state.recipeCount = result.recipeCount;
|
||||||
|
recordSyncTime();
|
||||||
} else {
|
} else {
|
||||||
state.error = result.error || 'Sync failed';
|
state.error = result.error || 'Sync failed';
|
||||||
}
|
}
|
||||||
@@ -85,12 +211,18 @@ function createPWAStore() {
|
|||||||
state.lastSyncDate = null;
|
state.lastSyncDate = null;
|
||||||
state.recipeCount = 0;
|
state.recipeCount = 0;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
|
// Clear sync time so next sync happens immediately
|
||||||
|
if (browser) {
|
||||||
|
localStorage.removeItem(LAST_SYNC_KEY);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to clear offline data:', error);
|
console.error('Failed to clear offline data:', error);
|
||||||
state.error = error instanceof Error ? error.message : 'Failed to clear data';
|
state.error = error instanceof Error ? error.message : 'Failed to clear data';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pwaStore = createPWAStore();
|
export const pwaStore = createPWAStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user