🎯 Рекомендуемые коллекции

Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать

Service Workers для Офлайн Веб-приложений

Service Workers для офлайн функциональности, фоновой синхронизации и перехвата сети

💻 Базовая Настройка Service Worker javascript

🟢 simple ⭐⭐

Регистрация service worker и включение офлайн кэширования

⏱️ 15 min 🏷️ service worker, offline, cache, pwa
Prerequisites: JavaScript, Basic web development
// Basic Service Worker Setup
// service-worker.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icon.png'
];

// Install event - cache files
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

// Fetch event - serve from cache when offline
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // Clone the request
        const fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(response => {
          // Check if valid response
          if(!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }

          // Clone the response
          const responseToCache = response.clone();

          caches.open(CACHE_NAME)
            .then(cache => {
              cache.put(event.request, responseToCache);
            });

          return response;
        });
      })
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            console.log('Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// main.js - Register service worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then(registration => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);

        // Check for updates
        registration.addEventListener('updatefound', () => {
          const newWorker = registration.installing;

          newWorker.addEventListener('statechange', () => {
            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
              // New content is available, show update notification
              if (confirm('New version available! Reload to update?')) {
                window.location.reload();
              }
            }
          });
        });

      })
      .catch(err => {
        console.log('ServiceWorker registration failed: ', err);
      });
  });

  // Listen for controlling service worker
  navigator.serviceWorker.addEventListener('controllerchange', () => {
    console.log('ServiceWorker controller changed');
    window.location.reload();
  });
}

💻 Фоновая Синхронизация javascript

🟡 intermediate ⭐⭐⭐

Синхронизация данных при восстановлении сетевого соединения

⏱️ 25 min 🏷️ background sync, offline, pwa, indexeddb
Prerequisites: JavaScript, Service Workers, IndexedDB
// Background Sync Service Worker
// service-worker.js
self.addEventListener('sync', event => {
  if (event.tag === 'background-sync') {
    event.waitUntil(syncData());
  }
});

async function syncData() {
  try {
    // Get all pending sync requests from IndexedDB
    const pendingRequests = await getPendingRequests();

    for (const request of pendingRequests) {
      try {
        const response = await fetch(request.url, {
          method: request.method,
          headers: request.headers,
          body: request.body
        });

        if (response.ok) {
          // Remove successful request from pending
          await removePendingRequest(request.id);
          console.log('Sync successful for request:', request.id);
        } else {
          console.error('Sync failed for request:', request.id);
        }
      } catch (error) {
        console.error('Network error during sync:', error);
        throw error; // This will retry the sync
      }
    }

    // Show notification if sync completed
    if (await getPendingRequestsCount() === 0) {
      self.registration.showNotification('Sync Complete', {
        body: 'All your data has been synchronized',
        icon: '/sync-icon.png'
      });
    }
  } catch (error) {
    console.error('Background sync failed:', error);
  }
}

// IndexedDB helpers for pending requests
async function getPendingRequests() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sync-db', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => {
      const db = request.result;
      const transaction = db.transaction(['requests'], 'readonly');
      const store = transaction.objectStore('requests');
      const getAll = store.getAll();

      getAll.onsuccess = () => resolve(getAll.result);
      getAll.onerror = () => reject(getAll.error);
    };

    request.onupgradeneeded = () => {
      const db = request.result;
      db.createObjectStore('requests', { keyPath: 'id' });
    };
  });
}

async function removePendingRequest(id) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sync-db', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => {
      const db = request.result;
      const transaction = db.transaction(['requests'], 'readwrite');
      const store = transaction.objectStore('requests');
      const deleteRequest = store.delete(id);

      deleteRequest.onsuccess = () => resolve();
      deleteRequest.onerror = () => reject(deleteRequest.error);
    };
  });
}

// main.js - Register background sync
class BackgroundSyncManager {
  constructor() {
    this.registration = null;
  }

  async init() {
    this.registration = await navigator.serviceWorker.ready;
  }

  async registerSync() {
    if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
      try {
        await this.registration.sync.register('background-sync');
        console.log('Background sync registered');
        return true;
      } catch (error) {
        console.error('Background sync registration failed:', error);
        return false;
      }
    } else {
      console.warn('Background sync not supported');
      return false;
    }
  }

  async addPendingRequest(requestData) {
    const request = {
      id: Date.now().toString(),
      ...requestData,
      timestamp: Date.now()
    };

    // Save to IndexedDB
    const db = await this.openDB();
    const transaction = db.transaction(['requests'], 'readwrite');
    const store = transaction.objectStore('requests');
    await store.add(request);

    // Try to register background sync
    const syncRegistered = await this.registerSync();

    if (!syncRegistered) {
      // Fallback: try immediate sync
      this.tryImmediateSync(request);
    }

    return request.id;
  }

  async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('sync-db', 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = () => {
        const db = request.result;
        db.createObjectStore('requests', { keyPath: 'id' });
      };
    });
  }

  async tryImmediateSync(request) {
    try {
      const response = await fetch(request.url, {
        method: request.method,
        headers: request.headers,
        body: request.body
      });

      if (response.ok) {
        await this.removePendingRequest(request.id);
        console.log('Immediate sync successful');
      } else {
        console.log('Immediate sync failed, keeping in pending queue');
      }
    } catch (error) {
      console.log('Network unavailable, keeping in pending queue');
    }
  }
}

// Usage example
const syncManager = new BackgroundSyncManager();

// Initialize when app loads
window.addEventListener('load', async () => {
  await syncManager.init();
});

// Example: Save data with background sync
async function saveDataOffline(data) {
  const requestData = {
    url: '/api/save',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  };

  const requestId = await syncManager.addPendingRequest(requestData);

  // Show user that data will be synced
  alert('Your data will be synced when you're back online');
}

💻 Push-уведомления javascript

🟡 intermediate ⭐⭐⭐⭐

Получение и обработка push-уведомлений с service workers

⏱️ 30 min 🏷️ push notifications, pwa, service worker, messaging
Prerequisites: JavaScript, Service Workers, HTTPS setup
// Push Notifications with Service Worker
// service-worker.js
self.addEventListener('push', event => {
  const options = {
    body: event.data ? event.data.text() : 'New notification',
    icon: '/notification-icon.png',
    badge: '/notification-badge.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: 1
    },
    actions: [
      {
        action: 'explore',
        title: 'Explore this new world',
        icon: '/images/checkmark.png'
      },
      {
        action: 'close',
        title: 'Close notification',
        icon: '/images/xmark.png'
      }
    ]
  };

  event.waitUntil(
    self.registration.showNotification('Push Notification', options)
  );
});

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

  if (event.action === 'explore') {
    // Open the app to specific page
    event.waitUntil(
      clients.openWindow('/explore')
    );
  } else if (event.action === 'close') {
    // Just close the notification
    console.log('Notification closed');
  } else {
    // Default action - open main app
    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('/');
        }
      })
    );
  }
});

// Handle notification close
self.addEventListener('notificationclose', event => {
  console.log('Notification closed:', event.notification);
});

// main.js - Push notification subscription
class PushNotificationManager {
  constructor() {
    this.subscription = null;
    this.publicKey = 'YOUR_VAPID_PUBLIC_KEY';
  }

  async init() {
    // Check for service worker support
    if (!('serviceWorker' in navigator)) {
      console.error('Service Worker not supported');
      return false;
    }

    // Check for push notification support
    if (!('PushManager' in window)) {
      console.error('Push notifications not supported');
      return false;
    }

    // Register service worker
    const registration = await navigator.serviceWorker.register('/push-service-worker.js');
    console.log('Service Worker registered');

    this.registration = registration;
    return true;
  }

  async subscribeToPush() {
    try {
      // Subscribe to push notifications
      this.subscription = await this.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: this.urlBase64ToUint8Array(this.publicKey)
      });

      console.log('User is subscribed:', this.subscription);

      // Send subscription to server
      await this.sendSubscriptionToServer(this.subscription);

      return this.subscription;
    } catch (error) {
      console.error('Failed to subscribe to push notifications:', error);
      return null;
    }
  }

  async unsubscribeFromPush() {
    try {
      const result = await this.subscription.unsubscribe();
      console.log('Unsubscribed:', result);

      // Remove subscription from server
      await this.removeSubscriptionFromServer(this.subscription);

      this.subscription = null;
      return result;
    } catch (error) {
      console.error('Failed to unsubscribe:', error);
      return false;
    }
  }

  async sendSubscriptionToServer(subscription) {
    try {
      const response = await fetch('/api/subscribe', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(subscription)
      });

      if (!response.ok) {
        throw new Error('Failed to send subscription to server');
      }

      return await response.json();
    } catch (error) {
      console.error('Error sending subscription to server:', error);
      throw error;
    }
  }

  async removeSubscriptionFromServer(subscription) {
    try {
      const response = await fetch('/api/unsubscribe', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(subscription)
      });

      if (!response.ok) {
        throw new Error('Failed to remove subscription from server');
      }
    } catch (error) {
      console.error('Error removing subscription from server:', error);
      throw error;
    }
  }

  async requestNotificationPermission() {
    const permission = await Notification.requestPermission();

    if (permission === 'granted') {
      console.log('Notification permission granted');
      return true;
    } else {
      console.log('Notification permission denied');
      return false;
    }
  }

  // Helper function to convert VAPID key
  urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
      .replace(/-/g, '+')
      .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
      outputArray[i] = rawData.charCodeAt(i);
    }

    return outputArray;
  }

  async checkSubscriptionStatus() {
    this.subscription = await this.registration.pushManager.getSubscription();

    if (this.subscription) {
      console.log('User is already subscribed');
      return this.subscription;
    } else {
      console.log('User is not subscribed');
      return null;
    }
  }
}

// Usage example
const pushManager = new PushNotificationManager();

// Initialize push notifications
async function initPushNotifications() {
  const initialized = await pushManager.init();
  if (!initialized) return;

  // Request permission
  const permissionGranted = await pushManager.requestNotificationPermission();
  if (!permissionGranted) return;

  // Check if already subscribed
  const existingSubscription = await pushManager.checkSubscriptionStatus();

  if (!existingSubscription) {
    // Subscribe to push notifications
    await pushManager.subscribeToPush();
  }
}

// Subscribe button handler
document.getElementById('subscribe-btn').addEventListener('click', async () => {
  await initPushNotifications();
});

// Unsubscribe button handler
document.getElementById('unsubscribe-btn').addEventListener('click', async () => {
  await pushManager.unsubscribeFromPush();
});

// Test local notification
document.getElementById('test-notification').addEventListener('click', async () => {
  if (Notification.permission === 'granted') {
    new Notification('Test Notification', {
      body: 'This is a test notification!',
      icon: '/icon.png'
    });
  } else {
    console.log('Notification permission not granted');
  }
});

💻 Продвинутые Стратегии Кэширования javascript

🟡 intermediate ⭐⭐⭐⭐

Реализация различных стратегий кэширования для оптимальной производительности

⏱️ 35 min 🏷️ caching, performance, offline, pwa, strategies
Prerequisites: JavaScript, Service Workers, Performance optimization
// Advanced Service Worker Caching Strategies
// service-worker.js

// Different cache strategies
class CacheStrategies {
  // Cache First - try cache first, fallback to network
  static cacheFirst(request) {
    return caches.match(request)
      .then(response => {
        return response || fetch(request);
      });
  }

  // Network First - try network first, fallback to cache
  static networkFirst(request) {
    return fetch(request)
      .then(response => {
        // Cache successful responses
        if (response.status === 200) {
          const responseClone = response.clone();
          caches.open('dynamic-cache-v1')
            .then(cache => cache.put(request, responseClone));
        }
        return response;
      })
      .catch(() => {
        // Fallback to cache
        return caches.match(request);
      });
  }

  // Stale While Revalidate - serve from cache, update in background
  static staleWhileRevalidate(request) {
    const cachePromise = caches.match(request);
    const networkPromise = fetch(request)
      .then(response => {
        const responseClone = response.clone();
        caches.open('dynamic-cache-v1')
          .then(cache => cache.put(request, responseClone));
        return response;
      });

    return cachePromise
      .then(response => response || networkPromise)
      .catch(() => networkPromise);
  }

  // Network Only - always fetch from network
  static networkOnly(request) {
    return fetch(request);
  }

  // Cache Only - always serve from cache
  static cacheOnly(request) {
    return caches.match(request);
  }
}

// Cache management
class CacheManager {
  constructor() {
    this.caches = {
      static: 'static-cache-v1',
      dynamic: 'dynamic-cache-v1',
      api: 'api-cache-v1',
      images: 'images-cache-v1'
    };

    this.cacheRules = [
      // Static assets - cache first
      {
        match: request =>
          request.destination === 'script' ||
          request.destination === 'style' ||
          request.destination === 'font',
        strategy: 'cacheFirst',
        cacheName: this.caches.static
      },

      // Images - stale while revalidate
      {
        match: request => request.destination === 'image',
        strategy: 'staleWhileRevalidate',
        cacheName: this.caches.images,
        maxEntries: 100,
        maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
      },

      // API calls - network first with timeout
      {
        match: request => request.url.includes('/api/'),
        strategy: 'networkFirst',
        cacheName: this.caches.api,
        timeout: 3000, // 3 seconds
        maxEntries: 50
      },

      // HTML pages - network first
      {
        match: request =>
          request.destination === 'document' ||
          request.mode === 'navigate',
        strategy: 'networkFirst',
        cacheName: this.caches.dynamic
      },

      // External CDN - cache first
      {
        match: request =>
          request.url.includes('cdn.jsdelivr.net') ||
          request.url.includes('cdnjs.cloudflare.com'),
        strategy: 'cacheFirst',
        cacheName: this.caches.static
      }
    ];
  }

  async handleRequest(request) {
    const rule = this.findMatchingRule(request);

    if (!rule) {
      return fetch(request);
    }

    try {
      // Apply strategy with timeout if specified
      if (rule.timeout) {
        return await this.withTimeout(
          CacheStrategies[rule.strategy](request),
          rule.timeout,
          caches.match(request)
        );
      } else {
        const response = await CacheStrategies[rule.strategy](request);

        // Apply cache management rules
        if (response && response.ok && rule.cacheName) {
          await this.manageCache(rule.cacheName, request, rule);
        }

        return response;
      }
    } catch (error) {
      console.error('Cache strategy failed:', error);

      // Fallback to cache if available
      return caches.match(request) || new Response('Offline', {
        status: 503,
        statusText: 'Service Unavailable'
      });
    }
  }

  findMatchingRule(request) {
    return this.cacheRules.find(rule => rule.match(request));
  }

  async withTimeout(promise, timeout, fallback) {
    return Promise.race([
      promise,
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Timeout')), timeout)
      )
    ]).catch(error => {
      if (fallback) return fallback;
      throw error;
    });
  }

  async manageCache(cacheName, request, rule) {
    if (rule.maxEntries || rule.maxAge) {
      const cache = await caches.open(cacheName);
      const requests = await cache.keys();

      // Remove old entries if maxEntries exceeded
      if (rule.maxEntries && requests.length > rule.maxEntries) {
        const toDelete = requests.slice(0, requests.length - rule.maxEntries);
        await Promise.all(toDelete.map(req => cache.delete(req)));
      }

      // Remove old entries based on age
      if (rule.maxAge) {
        const now = Date.now();
        for (const cacheRequest of requests) {
          const response = await cache.match(cacheRequest);
          const dateHeader = response.headers.get('date');
          if (dateHeader && (now - new Date(dateHeader).getTime()) > rule.maxAge) {
            cache.delete(cacheRequest);
          }
        }
      }
    }
  }
}

// Initialize cache manager
const cacheManager = new CacheManager();

// Service worker event handlers
self.addEventListener('fetch', event => {
  event.respondWith(cacheManager.handleRequest(event.request));
});

// Install event - precache critical assets
self.addEventListener('install', event => {
  const staticAssets = [
    '/',
    '/index.html',
    '/offline.html',
    '/styles/main.css',
    '/scripts/app.js',
    '/images/logo.png'
  ];

  event.waitUntil(
    caches.open(cacheManager.caches.static)
      .then(cache => cache.addAll(staticAssets))
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => !Object.values(cacheManager.caches).includes(name))
          .map(name => caches.delete(name))
      );
    })
  );
});

// Background sync for offline analytics
self.addEventListener('sync', event => {
  if (event.tag === 'analytics-sync') {
    event.waitUntil(syncAnalytics());
  }
});

async function syncAnalytics() {
  const offlineData = await getOfflineAnalytics();

  for (const data of offlineData) {
    try {
      await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });

      await removeOfflineAnalytics(data.id);
    } catch (error) {
      console.error('Failed to sync analytics:', error);
      break;
    }
  }
}

// IndexedDB helpers for offline analytics
async function getOfflineAnalytics() {
  // Implementation for getting analytics from IndexedDB
  return [];
}

async function removeOfflineAnalytics(id) {
  // Implementation for removing analytics from IndexedDB
}

// main.js - Client-side analytics that works offline
class OfflineAnalytics {
  constructor() {
    this.db = null;
  }

  async init() {
    this.db = await this.openDB();
  }

  async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('analytics-db', 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = () => {
        const db = request.result;
        db.createObjectStore('events', { keyPath: 'id', autoIncrement: true });
      };
    });
  }

  async track(eventName, properties = {}) {
    const event = {
      name: eventName,
      properties: {
        ...properties,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent
      }
    };

    try {
      // Try to send immediately
      const response = await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(event)
      });

      if (!response.ok) throw new Error('Analytics request failed');

    } catch (error) {
      // Store offline
      await this.storeOffline(event);

      // Register background sync if available
      if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
        const registration = await navigator.serviceWorker.ready;
        registration.sync.register('analytics-sync');
      }
    }
  }

  async storeOffline(event) {
    const transaction = this.db.transaction(['events'], 'readwrite');
    const store = transaction.objectStore('events');
    await store.add(event);
  }
}

// Usage
const analytics = new OfflineAnalytics();
analytics.init();

// Track page view
analytics.track('page_view', {
  page: window.location.pathname,
  title: document.title
});

// Track user interaction
document.getElementById('cta-button').addEventListener('click', () => {
  analytics.track('cta_clicked', {
    button_text: 'Get Started',
    location: 'homepage'
  });
});