LifeRPG_v2.0/modern/frontend/public/sw.js
TLimoges33 2b961611fd
🚀 Major Enhancement: Complete AI-Powered LifeRPG Platform with Git LFS
 New Features:
- AI-powered habit creation with natural language processing
- HuggingFace transformers integration for sentiment analysis (tracked via Git LFS)
- Advanced predictive analytics and behavioral insights
- Voice & image input capabilities for hands-free habit tracking
- Real-time notifications and community features
- Plugin system with extensible architecture

🔧 Technical Improvements:
- Comprehensive FastAPI backend with 30+ endpoints
- React frontend with PWA capabilities
- Advanced authentication with 2FA support
- RBAC authorization system
- Comprehensive security features (CSRF, rate limiting, audit logging)
- Database migrations and health monitoring
- Docker containerization support
- Git LFS configured for large AI model files (2+ GB)

📚 Documentation & DevOps:
- Complete deployment guides for multiple platforms
- Professional README with feature highlights
- GitHub Actions CI/CD workflows
- Comprehensive API documentation
- Security audit roadmap and compliance framework
- Setup scripts for development environment

🧪 Testing & Quality:
- Comprehensive test suite with 20+ test modules
- Setup verification scripts
- Working development environment with both backend and frontend
- Health checks and monitoring systems

🌟 Ready for:
- Portfolio showcasing
- Community contributions
- Production deployment
- Professional presentation
2025-09-28 21:29:19 +00:00

419 lines
11 KiB
JavaScript

const CACHE_NAME = "liferpg-v1.0.0";
const OFFLINE_URL = "/offline.html";
const API_CACHE_NAME = "api-cache-v1";
// Resources to cache immediately
const STATIC_CACHE_URLS = [
"/",
"/static/js/bundle.js",
"/static/css/main.css",
"/manifest.json",
"/icon-72x72.png",
"/icon-96x96.png",
"/icon-128x128.png",
"/icon-144x144.png",
"/icon-152x152.png",
"/icon-192x192.png",
"/icon-384x384.png",
"/icon-512x512.png",
OFFLINE_URL,
];
// API endpoints to cache
const API_CACHE_PATTERNS = [
/\/api\/v1\/habits$/,
/\/api\/v1\/habits\/today$/,
/\/api\/v1\/user\/profile$/,
/\/api\/v1\/analytics/,
/\/api\/v1\/gamification/,
];
// Install event - cache static resources
self.addEventListener("install", (event) => {
console.log("Service Worker: Installing...");
event.waitUntil(
(async () => {
try {
const cache = await caches.open(CACHE_NAME);
console.log("Service Worker: Caching static resources");
await cache.addAll(STATIC_CACHE_URLS);
// Force activation of the new service worker
await self.skipWaiting();
} catch (error) {
console.error(
"Service Worker: Failed to cache static resources",
error
);
}
})()
);
});
// Activate event - clean up old caches
self.addEventListener("activate", (event) => {
console.log("Service Worker: Activating...");
event.waitUntil(
(async () => {
try {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames
.filter(
(cacheName) =>
cacheName !== CACHE_NAME && cacheName !== API_CACHE_NAME
)
.map((cacheName) => {
console.log("Service Worker: Deleting old cache", cacheName);
return caches.delete(cacheName);
})
);
// Take control of all clients
await self.clients.claim();
} catch (error) {
console.error("Service Worker: Failed to activate", error);
}
})()
);
});
// Fetch event - serve cached content when offline
self.addEventListener("fetch", (event) => {
// Skip non-GET requests for modification requests
const url = new URL(event.request.url);
// Skip chrome-extension requests
if (event.request.url.startsWith("chrome-extension://")) return;
event.respondWith(
(async () => {
try {
// Handle API requests
if (url.pathname.startsWith("/api/")) {
return await handleApiRequest(event.request);
}
// Handle navigation requests
if (event.request.mode === "navigate") {
return await handleNavigationRequest(event.request);
}
// Handle static resource requests
return await handleStaticRequest(event.request);
} catch (error) {
console.error("Service Worker: Fetch error", error);
return await handleFallback(event.request);
}
})()
);
});
// Handle API requests with cache-first strategy for GET requests
async function handleApiRequest(request) {
const url = new URL(request.url);
const shouldCache = API_CACHE_PATTERNS.some((pattern) =>
pattern.test(url.pathname)
);
if (shouldCache && request.method === "GET") {
try {
// Try cache first for API requests
const cachedResponse = await caches.match(request);
if (cachedResponse) {
// Return cached response and update in background
updateApiCache(request);
return cachedResponse;
}
// Fetch from network and cache
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(API_CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
// Return cached version if network fails
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
throw error;
}
}
// For non-cached API requests, try network first
try {
const response = await fetch(request);
// If it's a POST/PUT/DELETE request that modifies data, store it for sync
if (["POST", "PUT", "DELETE"].includes(request.method)) {
await storeOfflineAction(request);
}
return response;
} catch (error) {
// Store the action for later sync
if (["POST", "PUT", "DELETE"].includes(request.method)) {
await storeOfflineAction(request);
return new Response(JSON.stringify({ success: true, offline: true }), {
headers: { "Content-Type": "application/json" },
});
}
throw error;
}
}
// Handle navigation requests
async function handleNavigationRequest(request) {
try {
const response = await fetch(request);
return response;
} catch (error) {
// Return cached version or offline page
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const offlineResponse = await caches.match(OFFLINE_URL);
return offlineResponse || new Response("Offline", { status: 200 });
}
}
// Handle static resource requests
async function handleStaticRequest(request) {
// Try cache first for static resources
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// If not in cache, fetch from network
try {
const response = await fetch(request);
// Cache successful responses
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
throw error;
}
}
// Fallback handler
async function handleFallback(request) {
if (request.mode === "navigate") {
const offlineResponse = await caches.match(OFFLINE_URL);
return offlineResponse || new Response("Offline", { status: 200 });
}
return new Response("Resource not available offline", { status: 503 });
}
// Update API cache in background
async function updateApiCache(request) {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(API_CACHE_NAME);
await cache.put(request, response);
}
} catch (error) {
console.log("Background update failed:", error);
}
}
// Store offline actions for later sync
async function storeOfflineAction(request) {
try {
const action = {
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
body: await request.text(),
timestamp: Date.now(),
};
const existingActions = await getStoredActions();
existingActions.push(action);
// Store in IndexedDB or localStorage fallback
const storage = await getOfflineStorage();
await storage.setItem("offline-actions", JSON.stringify(existingActions));
} catch (error) {
console.error("Failed to store offline action:", error);
}
}
// Get stored offline actions
async function getStoredActions() {
try {
const storage = await getOfflineStorage();
const actions = await storage.getItem("offline-actions");
return actions ? JSON.parse(actions) : [];
} catch (error) {
console.error("Failed to get stored actions:", error);
return [];
}
}
// Simple storage abstraction
async function getOfflineStorage() {
// Try to use IndexedDB, fallback to cache storage
return {
async getItem(key) {
const cache = await caches.open("offline-storage");
const response = await cache.match(`/${key}`);
return response ? await response.text() : null;
},
async setItem(key, value) {
const cache = await caches.open("offline-storage");
await cache.put(`/${key}`, new Response(value));
},
};
}
// Background sync event
self.addEventListener("sync", (event) => {
if (event.tag === "background-sync") {
event.waitUntil(syncOfflineActions());
}
});
// Sync offline actions when back online
async function syncOfflineActions() {
try {
const actions = await getStoredActions();
const successfulSyncs = [];
for (const action of actions) {
try {
const request = new Request(action.url, {
method: action.method,
headers: action.headers,
body: action.body || undefined,
});
const response = await fetch(request);
if (response.ok) {
successfulSyncs.push(action);
}
} catch (error) {
console.error("Failed to sync action:", error);
}
}
// Remove successfully synced actions
if (successfulSyncs.length > 0) {
const remainingActions = actions.filter(
(action) => !successfulSyncs.includes(action)
);
const storage = await getOfflineStorage();
await storage.setItem(
"offline-actions",
JSON.stringify(remainingActions)
);
}
} catch (error) {
console.error("Background sync failed:", error);
}
}
// Push notification event
self.addEventListener("push", (event) => {
if (!event.data) return;
try {
const data = event.data.json();
const options = {
body: data.body || "Time to practice your magical habits!",
icon: "/icon-192x192.png",
badge: "/icon-72x72.png",
image: data.image,
vibrate: [200, 100, 200],
data: {
url: data.url || "/",
action: data.action || "open",
},
actions: [
{
action: "complete",
title: "✓ Mark Complete",
icon: "/icon-72x72.png",
},
{
action: "view",
title: "👁 View Details",
icon: "/icon-72x72.png",
},
],
requireInteraction: true,
tag: data.tag || "habit-reminder",
};
event.waitUntil(
self.registration.showNotification(
data.title || "🧙‍♂️ Grimoire Reminder",
options
)
);
} catch (error) {
console.error("Push notification error:", error);
}
});
// Notification click event
self.addEventListener("notificationclick", (event) => {
event.notification.close();
const action = event.action;
const data = event.notification.data;
if (action === "complete") {
// Handle habit completion
event.waitUntil(handleHabitCompletion(data));
} else {
// Open the app
event.waitUntil(clients.openWindow(data.url || "/"));
}
});
// Handle habit completion from notification
async function handleHabitCompletion(data) {
try {
if (data.habitId) {
// Store completion for sync
await storeOfflineAction(
new Request(`/api/v1/habits/${data.habitId}/complete`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completedAt: new Date().toISOString() }),
})
);
// Show success notification
await self.registration.showNotification("✅ Habit Completed!", {
body: "Great job! Your progress has been recorded.",
icon: "/icon-192x192.png",
tag: "completion-success",
});
}
} catch (error) {
console.error("Failed to complete habit:", error);
}
}
console.log("Service Worker: Loaded");