AdminLTE3
This commit is contained in:
414
.history/public/sw_20251026215851.js
Normal file
414
.history/public/sw_20251026215851.js
Normal file
@@ -0,0 +1,414 @@
|
||||
// Service Worker for SmartSolTech PWA
|
||||
const CACHE_NAME = 'smartsoltech-v1.0.2';
|
||||
const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.2';
|
||||
const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.2';
|
||||
|
||||
// Files to cache immediately
|
||||
const STATIC_FILES = [
|
||||
'/',
|
||||
'/css/main.css',
|
||||
'/css/fixes.css',
|
||||
'/css/dark-theme.css',
|
||||
'/js/main.js',
|
||||
'/vendor/jquery/jquery-3.6.0.min.js',
|
||||
'/vendor/bootstrap/bootstrap.bundle.min.js',
|
||||
'/vendor/bootstrap/bootstrap.min.css',
|
||||
'/vendor/adminlte/adminlte.min.js',
|
||||
'/vendor/adminlte/adminlte.min.css',
|
||||
'/vendor/adminlte/fontawesome.min.css',
|
||||
'/images/logo.png',
|
||||
'/images/icon-192x192.png',
|
||||
'/images/icon-144x144.png',
|
||||
'/manifest.json',
|
||||
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap',
|
||||
'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap',
|
||||
'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css',
|
||||
'https://unpkg.com/aos@2.3.1/dist/aos.css',
|
||||
'https://unpkg.com/aos@2.3.1/dist/aos.js'
|
||||
];
|
||||
|
||||
// Routes to cache dynamically
|
||||
const DYNAMIC_ROUTES = [
|
||||
'/about',
|
||||
'/services',
|
||||
'/portfolio',
|
||||
'/calculator',
|
||||
'/contact'
|
||||
];
|
||||
|
||||
// API endpoints to cache
|
||||
const API_CACHE_PATTERNS = [
|
||||
/^\/api\/portfolio/,
|
||||
/^\/api\/services/,
|
||||
/^\/api\/calculator\/services/
|
||||
];
|
||||
|
||||
// Install event - cache static files
|
||||
self.addEventListener('install', event => {
|
||||
console.log('Service Worker: Installing...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE_NAME)
|
||||
.then(cache => {
|
||||
console.log('Service Worker: Caching static files');
|
||||
return cache.addAll(STATIC_FILES);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Static files cached');
|
||||
return self.skipWaiting();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Service Worker: Error caching static files', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Service Worker: Activating...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (cacheName !== STATIC_CACHE_NAME &&
|
||||
cacheName !== DYNAMIC_CACHE_NAME) {
|
||||
console.log('Service Worker: Deleting old cache', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Activated');
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - serve cached files or fetch from network
|
||||
self.addEventListener('fetch', event => {
|
||||
const request = event.request;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Skip non-GET requests
|
||||
if (request.method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip Chrome extension requests
|
||||
if (url.protocol === 'chrome-extension:') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle different types of requests
|
||||
if (isStaticFile(request.url)) {
|
||||
event.respondWith(cacheFirst(request));
|
||||
} else if (isAPIRequest(request.url)) {
|
||||
event.respondWith(networkFirst(request));
|
||||
} else if (isDynamicRoute(request.url) || url.pathname === '/') {
|
||||
// For main pages, always check network first to ensure language consistency
|
||||
event.respondWith(networkFirst(request));
|
||||
} else {
|
||||
event.respondWith(networkFirst(request));
|
||||
}
|
||||
});
|
||||
|
||||
// Cache strategies
|
||||
async function cacheFirst(request) {
|
||||
try {
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
const networkResponse = await fetch(request);
|
||||
const cache = await caches.open(STATIC_CACHE_NAME);
|
||||
cache.put(request, networkResponse.clone());
|
||||
return networkResponse;
|
||||
} catch (error) {
|
||||
console.error('Cache first strategy failed:', error);
|
||||
return new Response('Offline', { status: 503 });
|
||||
}
|
||||
}
|
||||
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const networkResponse = await fetch(request);
|
||||
|
||||
// Cache successful responses
|
||||
if (networkResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
cache.put(request, networkResponse.clone());
|
||||
}
|
||||
|
||||
return networkResponse;
|
||||
} catch (error) {
|
||||
console.log('Network first: Falling back to cache for', request.url);
|
||||
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Return offline page for navigation requests
|
||||
if (request.mode === 'navigate') {
|
||||
return caches.match('/offline.html') || new Response('Offline', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
}
|
||||
|
||||
return new Response('Network Error', { status: 503 });
|
||||
}
|
||||
}
|
||||
|
||||
async function staleWhileRevalidate(request) {
|
||||
try {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
const cachedResponse = await cache.match(request);
|
||||
|
||||
const fetchPromise = fetch(request).then(networkResponse => {
|
||||
if (networkResponse && networkResponse.ok) {
|
||||
cache.put(request, networkResponse.clone());
|
||||
}
|
||||
return networkResponse;
|
||||
}).catch(error => {
|
||||
console.log('staleWhileRevalidate fetch failed:', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return cachedResponse || fetchPromise || new Response('Not available', { status: 503 });
|
||||
} catch (error) {
|
||||
console.error('staleWhileRevalidate error:', error);
|
||||
return new Response('Service unavailable', { status: 503 });
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function isStaticFile(url) {
|
||||
return url.includes('/css/') ||
|
||||
url.includes('/js/') ||
|
||||
url.includes('/images/') ||
|
||||
url.includes('/fonts/') ||
|
||||
url.includes('googleapis.com') ||
|
||||
url.includes('cdnjs.cloudflare.com');
|
||||
}
|
||||
|
||||
function isAPIRequest(url) {
|
||||
return url.includes('/api/') ||
|
||||
API_CACHE_PATTERNS.some(pattern => pattern.test(url));
|
||||
}
|
||||
|
||||
function isDynamicRoute(url) {
|
||||
const pathname = new URL(url).pathname;
|
||||
return DYNAMIC_ROUTES.includes(pathname) ||
|
||||
pathname.startsWith('/portfolio/') ||
|
||||
pathname.startsWith('/services/');
|
||||
}
|
||||
|
||||
// Background sync for form submissions
|
||||
self.addEventListener('sync', event => {
|
||||
console.log('Service Worker: Background sync triggered', event.tag);
|
||||
|
||||
if (event.tag === 'contact-form-sync') {
|
||||
event.waitUntil(syncContactForms());
|
||||
}
|
||||
});
|
||||
|
||||
async function syncContactForms() {
|
||||
try {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
const requests = await cache.keys();
|
||||
|
||||
const contactRequests = requests.filter(request =>
|
||||
request.url.includes('/api/contact/submit')
|
||||
);
|
||||
|
||||
for (const request of contactRequests) {
|
||||
try {
|
||||
await fetch(request);
|
||||
await cache.delete(request);
|
||||
console.log('Contact form synced successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to sync contact form:', error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Background sync failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Push notification handling
|
||||
self.addEventListener('push', event => {
|
||||
console.log('Service Worker: Push received', event);
|
||||
|
||||
let data = {};
|
||||
if (event.data) {
|
||||
data = event.data.json();
|
||||
}
|
||||
|
||||
const title = data.title || 'SmartSolTech';
|
||||
const options = {
|
||||
body: data.body || 'You have a new notification',
|
||||
icon: '/images/icon-192x192.png',
|
||||
badge: '/images/icon-72x72.png',
|
||||
tag: data.tag || 'default',
|
||||
data: data.url || '/',
|
||||
actions: [
|
||||
{
|
||||
action: 'open',
|
||||
title: '열기',
|
||||
icon: '/images/icon-open.png'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: '닫기',
|
||||
icon: '/images/icon-close.png'
|
||||
}
|
||||
],
|
||||
requireInteraction: data.requireInteraction || false,
|
||||
silent: data.silent || false,
|
||||
vibrate: data.vibrate || [200, 100, 200]
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, options)
|
||||
);
|
||||
});
|
||||
|
||||
// Notification click handling
|
||||
self.addEventListener('notificationclick', event => {
|
||||
console.log('Service Worker: Notification clicked', event);
|
||||
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'close') {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = event.notification.data || '/';
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window' }).then(clientList => {
|
||||
// Check if window is already open
|
||||
for (const client of clientList) {
|
||||
if (client.url === url && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Open new window
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Handle messages from main thread
|
||||
self.addEventListener('message', event => {
|
||||
console.log('Service Worker: Message received', event.data);
|
||||
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
if (event.data && event.data.type === 'CACHE_URLS') {
|
||||
cacheUrls(event.data.urls);
|
||||
}
|
||||
});
|
||||
|
||||
async function cacheUrls(urls) {
|
||||
try {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
await cache.addAll(urls);
|
||||
console.log('URLs cached successfully:', urls);
|
||||
} catch (error) {
|
||||
console.error('Failed to cache URLs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic background sync (if supported)
|
||||
self.addEventListener('periodicsync', event => {
|
||||
console.log('Service Worker: Periodic sync triggered', event.tag);
|
||||
|
||||
if (event.tag === 'content-sync') {
|
||||
event.waitUntil(syncContent());
|
||||
}
|
||||
});
|
||||
|
||||
async function syncContent() {
|
||||
try {
|
||||
// Fetch fresh portfolio and services data
|
||||
const portfolioResponse = await fetch('/api/portfolio?featured=true');
|
||||
const servicesResponse = await fetch('/api/services?featured=true');
|
||||
|
||||
if (portfolioResponse.ok && servicesResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
cache.put('/api/portfolio?featured=true', portfolioResponse.clone());
|
||||
cache.put('/api/services?featured=true', servicesResponse.clone());
|
||||
console.log('Content synced successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Content sync failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache management utilities
|
||||
async function cleanupCaches() {
|
||||
const cacheNames = await caches.keys();
|
||||
const currentCaches = [STATIC_CACHE_NAME, DYNAMIC_CACHE_NAME];
|
||||
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (!currentCaches.includes(cacheName)) {
|
||||
console.log('Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Limit cache size
|
||||
async function limitCacheSize(cacheName, maxItems) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const keys = await cache.keys();
|
||||
|
||||
if (keys.length > maxItems) {
|
||||
const keysToDelete = keys.slice(0, keys.length - maxItems);
|
||||
return Promise.all(keysToDelete.map(key => cache.delete(key)));
|
||||
}
|
||||
}
|
||||
|
||||
// Performance monitoring
|
||||
self.addEventListener('fetch', event => {
|
||||
if (event.request.url.includes('/api/')) {
|
||||
const start = performance.now();
|
||||
|
||||
event.respondWith(
|
||||
fetch(event.request).then(response => {
|
||||
const duration = performance.now() - start;
|
||||
|
||||
// Log slow API requests
|
||||
if (duration > 2000) {
|
||||
console.warn('Slow API request:', event.request.url, duration + 'ms');
|
||||
}
|
||||
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Error tracking
|
||||
self.addEventListener('error', event => {
|
||||
console.error('Service Worker error:', event.error);
|
||||
// Could send to analytics service
|
||||
});
|
||||
|
||||
self.addEventListener('unhandledrejection', event => {
|
||||
console.error('Service Worker unhandled rejection:', event.reason);
|
||||
// Could send to analytics service
|
||||
});
|
||||
Reference in New Issue
Block a user