sw.js

9.14 KB
30/09/2025 02:03
JS
sw.js
// Service Worker สำหรับระบบจัดการนัดหมาย
// Version 1.0.0

const CACHE_NAME = 'appointment-chat-v1.0.0';
const STATIC_CACHE = 'appointment-static-v1.0.0';
const DYNAMIC_CACHE = 'appointment-dynamic-v1.0.0';

// ไฟล์ที่ต้องการ cache ไว้สำหรับการทำงานแบบออฟไลน์
const STATIC_FILES = [
  '/',
  '/index.html',
  '/manifest.json',
  // Font Awesome
  'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css',
  // Google Fonts
  'https://fonts.googleapis.com/css2?family=Prompt:wght@400;500;600;700&display=swap',
  'https://fonts.gstatic.com/s/prompt/v10/jizfREFUsnUct9P6cDfd4OmnLD0Z4zM.woff2',
  'https://fonts.gstatic.com/s/prompt/v10/jizfREFUsnUct9P6cDfd4OmnLD0Z5DM.woff2',
  // เพิ่มไฟล์อื่นๆ ตามต้องการ
];

// URLs ที่ไม่ต้องการ cache
const EXCLUDED_URLS = [
  '/api.php',
  'chrome-extension://',
  'moz-extension://',
];

// ✅ Install Event - Cache static files
self.addEventListener('install', (event) => {
  console.log('🔧 Service Worker: Installing...');

  event.waitUntil(
    caches.open(STATIC_CACHE)
      .then((cache) => {
        console.log('📦 Service Worker: Caching static files');
        return cache.addAll(STATIC_FILES);
      })
      .catch((error) => {
        console.error('❌ Service Worker: Cache installation failed', error);
      })
  );

  // บังคับให้ service worker ใหม่เข้ามาแทนที่ทันที
  self.skipWaiting();
});

// ✅ 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) => {
            // ลบ cache เก่าที่ไม่ใช้แล้ว
            if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
              console.log('🗑️ Service Worker: Deleting old cache:', cacheName);
              return caches.delete(cacheName);
            }
          })
        );
      })
      .then(() => {
        console.log('✅ Service Worker: Cleanup complete');
        // เข้าควบคุม client ทั้งหมดทันที
        return self.clients.claim();
      })
  );
});

// ✅ Fetch Event - Network First with Cache Fallback
self.addEventListener('fetch', (event) => {
  const {request} = event;
  const url = request.url;

  // ข้าม URLs ที่ไม่ต้องการ cache
  if (EXCLUDED_URLS.some(excludedUrl => url.includes(excludedUrl))) {
    return;
  }

  // ข้าม non-GET requests
  if (request.method !== 'GET') {
    return;
  }

  event.respondWith(
    networkFirstWithCacheFallback(request)
  );
});

// 🌐 Network First Strategy with Cache Fallback
async function networkFirstWithCacheFallback(request) {
  const url = request.url;

  try {
    // พยายามเรียกจาก network ก่อน
    const networkResponse = await fetch(request);

    if (networkResponse.ok) {
      // หาก response ดี ให้ cache ไว้ใน dynamic cache
      const cache = await caches.open(DYNAMIC_CACHE);

      // Cache เฉพาะ static files และ font files
      if (isStaticResource(url) || isFontResource(url)) {
        cache.put(request, networkResponse.clone());
      }

      return networkResponse;
    }

    // หาก network response ไม่ดี ให้ fallback ไป cache
    return getCachedResponse(request);

  } catch (error) {
    // หาก network fail ให้ fallback ไป cache
    console.log('🔌 Service Worker: Network failed, trying cache for:', url);
    return getCachedResponse(request);
  }
}

// 📦 Get response from cache
async function getCachedResponse(request) {
  const url = request.url;

  // ลองหาใน static cache ก่อน
  let cachedResponse = await caches.match(request, {cacheName: STATIC_CACHE});

  if (cachedResponse) {
    console.log('📦 Service Worker: Serving from static cache:', url);
    return cachedResponse;
  }

  // ถ้าไม่มีใน static cache ให้ลองหาใน dynamic cache
  cachedResponse = await caches.match(request, {cacheName: DYNAMIC_CACHE});

  if (cachedResponse) {
    console.log('📦 Service Worker: Serving from dynamic cache:', url);
    return cachedResponse;
  }

  // หาก request เป็น navigation (เช่น หน้าเว็บ) ให้ return index.html
  if (request.mode === 'navigate') {
    console.log('🏠 Service Worker: Serving index.html for navigation');
    return caches.match('/index.html');
  }

  // ถ้าไม่มีใน cache เลย ให้ return offline page หรือ error response
  console.log('❌ Service Worker: No cache found for:', url);
  return new Response('Offline - Content not available', {
    status: 503,
    statusText: 'Service Unavailable',
    headers: {'Content-Type': 'text/plain'}
  });
}

// 🔍 Helper Functions
function isStaticResource(url) {
  return url.includes('.css') ||
    url.includes('.js') ||
    url.includes('.png') ||
    url.includes('.jpg') ||
    url.includes('.jpeg') ||
    url.includes('.gif') ||
    url.includes('.svg') ||
    url.includes('.ico') ||
    url.includes('.webp');
}

function isFontResource(url) {
  return url.includes('.woff') ||
    url.includes('.woff2') ||
    url.includes('.ttf') ||
    url.includes('.eot') ||
    url.includes('fonts.googleapis.com') ||
    url.includes('fonts.gstatic.com');
}

// 🔄 Background Sync for offline data
self.addEventListener('sync', (event) => {
  console.log('🔄 Service Worker: Background sync triggered');

  if (event.tag === 'appointment-sync') {
    event.waitUntil(syncAppointments());
  }
});

// 📤 Sync appointments when back online
async function syncAppointments() {
  console.log('📤 Service Worker: Syncing appointments...');

  try {
    // ส่งข้อความไปยัง client ให้ sync ข้อมูล
    const clients = await self.clients.matchAll();
    clients.forEach(client => {
      client.postMessage({
        type: 'SYNC_APPOINTMENTS'
      });
    });

    console.log('✅ Service Worker: Sync message sent to clients');
  } catch (error) {
    console.error('❌ Service Worker: Sync failed', error);
  }
}

// 🔔 Push Notification Handler
self.addEventListener('push', (event) => {
  console.log('🔔 Service Worker: Push notification received');

  const options = {
    body: 'คุณมีนัดหมายใกล้เข้ามาแล้ว',
    icon: '/icon-192.png',
    badge: '/icon-192.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: 1
    },
    actions: [
      {
        action: 'view',
        title: 'ดูนัดหมาย',
        icon: '/icon-192.png'
      },
      {
        action: 'close',
        title: 'ปิด'
      }
    ]
  };

  event.waitUntil(
    self.registration.showNotification('เตือนนัดหมาย', options)
  );
});

// 🔔 Notification Click Handler
self.addEventListener('notificationclick', (event) => {
  console.log('🔔 Service Worker: Notification clicked');

  event.notification.close();

  if (event.action === 'view') {
    // เปิดแอปหรือ focus ไปที่แอป
    event.waitUntil(
      clients.matchAll().then((clientList) => {
        for (const client of clientList) {
          if (client.url === '/' && 'focus' in client) {
            return client.focus();
          }
        }
        if (clients.openWindow) {
          return clients.openWindow('/');
        }
      })
    );
  }
});

// 📨 Message Handler - รับข้อความจาก client
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 === 'GET_VERSION') {
    event.ports[0].postMessage({version: CACHE_NAME});
  }
});

// 🧹 Periodic cleanup of old dynamic cache entries
setInterval(() => {
  cleanupDynamicCache();
}, 24 * 60 * 60 * 1000); // ทุก 24 ชั่วโมง

async function cleanupDynamicCache() {
  try {
    const cache = await caches.open(DYNAMIC_CACHE);
    const requests = await cache.keys();

    // ลบ cache entries ที่เก่าเกิน 7 วัน
    const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);

    for (const request of requests) {
      const response = await cache.match(request);
      const dateHeader = response.headers.get('date');

      if (dateHeader) {
        const responseDate = new Date(dateHeader).getTime();
        if (responseDate < oneWeekAgo) {
          await cache.delete(request);
          console.log('🧹 Service Worker: Cleaned old cache entry:', request.url);
        }
      }
    }
  } catch (error) {
    console.error('❌ Service Worker: Cache cleanup failed', error);
  }
}

console.log('🎉 Service Worker: Script loaded successfully');