sw.js

7.78 KB
05/11/2024 07:20
JS
sw.js
const CACHE_NAME = 'bakery-cache-v1';
const STATIC_CACHE_NAME = 'bakery-static-v1';
const DYNAMIC_CACHE_NAME = 'bakery-dynamic-v1';
const FONT_CACHE_NAME = 'bakery-fonts-v1';
const IMAGE_CACHE_NAME = 'bakery-images-v1';

// Assets to cache on install
const STATIC_ASSETS = [
  '/',
  'index.html',
  'manifest.json',
  'data/products.json',
  'assets/css/main.css',
  'assets/css/animations.css',
  'assets/css/cart.css',
  'assets/css/products.css',
  'assets/css/modal.css',
  'assets/css/additional.css',
  'assets/js/config.js',
  'assets/js/utils.js',
  'assets/js/customer-manager.js',
  'assets/js/cart.js',
  'assets/js/auth.js',
  'assets/js/products.js',
  'assets/js/modal-manager.js',
  'assets/js/payment-manager.js',
  'assets/js/notification-service.js',
  'assets/js/order-manager.js',
  'assets/images/logo.svg',
  'assets/images/hero-banner.svg',
];

// Font files to cache
const FONT_FILES = [
  'https://fonts.googleapis.com/css2?family=Prompt:wght@300;400;500;600;700&display=swap',
  'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css',
  'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/webfonts/fa-solid-900.woff2',
  'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/webfonts/fa-regular-400.woff2',
  'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/webfonts/fa-brands-400.woff2'
];

// API routes that should be cached
const API_ROUTES = [
  '/api/products',
  '/api/categories'
];

// Install event - cache static assets
self.addEventListener('install', event => {
  event.waitUntil(
    Promise.all([
      // Cache static assets
      caches.open(STATIC_CACHE_NAME).then(cache => {
        console.log('Caching static assets');
        return cache.addAll(STATIC_ASSETS);
      }),
      // Cache fonts
      caches.open(FONT_CACHE_NAME).then(cache => {
        console.log('Caching fonts');
        return cache.addAll(FONT_FILES);
      })
    ])
      .then(() => self.skipWaiting())
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    Promise.all([
      // Remove old caches
      caches.keys().then(cacheNames => {
        return Promise.all(
          cacheNames
            .filter(cacheName => {
              return cacheName.startsWith('bakery-') &&
                ![STATIC_CACHE_NAME, DYNAMIC_CACHE_NAME,
                  FONT_CACHE_NAME, IMAGE_CACHE_NAME].includes(cacheName);
            })
            .map(cacheName => caches.delete(cacheName))
        );
      }),
      // Claim clients
      self.clients.claim()
    ])
  );
});

// Fetch event - handle requests
self.addEventListener('fetch', event => {
  const request = event.request;
  const url = new URL(request.url);

  // Handle different types of requests
  if (request.method !== 'GET') {
    // Don't cache non-GET requests
    return event.respondWith(fetch(request));
  }

  // Font files
  if (FONT_FILES.some(font => request.url.includes(font))) {
    return event.respondWith(handleFontRequest(request));
  }

  // API requests
  if (API_ROUTES.some(route => url.pathname.includes(route))) {
    return event.respondWith(handleApiRequest(request));
  }

  // Image requests
  if (request.destination === 'image') {
    return event.respondWith(handleImageRequest(request));
  }

  // Static assets
  if (STATIC_ASSETS.includes(url.pathname)) {
    return event.respondWith(handleStaticRequest(request));
  }

  // Dynamic content
  return event.respondWith(handleDynamicRequest(request));
});

// Handle font requests
async function handleFontRequest(request) {
  try {
    // Try network first
    const response = await fetch(request);
    const cache = await caches.open(FONT_CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    // Fallback to cache
    const cachedResponse = await caches.match(request);
    return cachedResponse || Promise.reject('Font not found in cache');
  }
}

// Handle API requests
async function handleApiRequest(request) {
  try {
    // Network first for API requests
    const response = await fetch(request);
    const cache = await caches.open(DYNAMIC_CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    // If no cached response, return offline fallback
    return new Response(
      JSON.stringify({
        error: 'ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้',
        offline: true
      }),
      {
        headers: {'Content-Type': 'application/json'}
      }
    );
  }
}

// Handle image requests
async function handleImageRequest(request) {
  try {
    // Try cache first for images
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    // If not in cache, fetch from network
    const response = await fetch(request);
    const cache = await caches.open(IMAGE_CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    // Return placeholder image if fetch fails
    return new Response(
      '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">' +
      '<rect width="100" height="100" fill="#eee"/>' +
      '<text x="50" y="50" text-anchor="middle" dy=".3em" fill="#999">' +
      'Image</text></svg>',
      {
        headers: {'Content-Type': 'image/svg+xml'}
      }
    );
  }
}

// Handle static requests
async function handleStaticRequest(request) {
  // Cache first strategy for static assets
  const cachedResponse = await caches.match(request);
  if (cachedResponse) {
    return cachedResponse;
  }
  // If not in cache, fetch from network
  try {
    const response = await fetch(request);
    const cache = await caches.open(STATIC_CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    // Return offline page for navigation requests
    if (request.mode === 'navigate') {
      return caches.match('offline.html');
    }
    return Promise.reject('Failed to fetch');
  }
}

// Handle dynamic requests
async function handleDynamicRequest(request) {
  // Network first strategy for dynamic content
  try {
    const response = await fetch(request);
    const cache = await caches.open(DYNAMIC_CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    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');
    }
    return Promise.reject('Failed to fetch');
  }
}

// Handle sync events for offline actions
self.addEventListener('sync', event => {
  if (event.tag === 'order-sync') {
    event.waitUntil(syncOrders());
  }
});

// Sync pending orders when online
async function syncOrders() {
  try {
    const pendingOrders = await getPendingOrders();
    for (const order of pendingOrders) {
      await sendOrder(order);
    }
  } catch (error) {
    console.error('Order sync failed:', error);
  }
}

// Push notification event
self.addEventListener('push', event => {
  if (event.data) {
    const data = event.data.json();
    const options = {
      body: data.message,
      icon: 'assets/images/icons/icon-192x192.png',
      badge: 'assets/images/icons/badge-72x72.png',
      vibrate: [100, 50, 100],
      data: {
        url: data.url
      }
    };

    event.waitUntil(
      self.registration.showNotification(data.title, options)
    );
  }
});

// Notification click event
self.addEventListener('notificationclick', event => {
  event.notification.close();

  if (event.notification.data.url) {
    event.waitUntil(
      clients.openWindow(event.notification.data.url)
    );
  }
});