javascript

exercises

exercises.js⚔
/**
 * Service Workers and PWA Exercises
 *
 * Practice building offline-capable Progressive Web Apps
 */

// =============================================================================
// Exercise 1: Service Worker Registration
// =============================================================================

/**
 * Create a Service Worker registration utility
 */
class SWRegistrar {
  constructor(swPath = '/sw.js') {
    this.swPath = swPath;
    this.registration = null;
  }

  /**
   * Check if Service Workers are supported
   * @returns {boolean}
   */
  isSupported() {
    // TODO: Check if 'serviceWorker' is in navigator
    throw new Error('Not implemented');
  }

  /**
   * Register the Service Worker
   * @param {Object} options - Registration options (scope)
   * @returns {Promise<ServiceWorkerRegistration>}
   */
  async register(options = {}) {
    // TODO:
    // 1. Check if supported
    // 2. Call navigator.serviceWorker.register
    // 3. Store and return the registration
    throw new Error('Not implemented');
  }

  /**
   * Wait for SW to be ready and controlling
   * @returns {Promise<ServiceWorkerRegistration>}
   */
  async waitForReady() {
    // TODO: Return navigator.serviceWorker.ready
    throw new Error('Not implemented');
  }

  /**
   * Get current SW state
   * @returns {string} 'installing' | 'installed' | 'activating' | 'activated' | 'redundant' | null
   */
  getState() {
    // TODO: Return state of active or waiting or installing worker
    throw new Error('Not implemented');
  }

  /**
   * Unregister the Service Worker
   * @returns {Promise<boolean>}
   */
  async unregister() {
    // TODO: Unregister and clear this.registration
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise1() {
  const registrar = new SWRegistrar('/test-sw.js');

  console.assert(
    typeof registrar.isSupported() === 'boolean',
    'isSupported should return boolean'
  );

  // Note: Full testing requires browser environment with actual SW file
  console.log('Exercise 1: Register Service Worker - Implementation ready');
}

// =============================================================================
// Exercise 2: Cache Manager
// =============================================================================

/**
 * Implement a Cache Manager for offline storage
 */
class CacheStorage {
  constructor(cacheName = 'app-cache-v1') {
    this.cacheName = cacheName;
  }

  /**
   * Open the cache
   * @returns {Promise<Cache>}
   */
  async open() {
    // TODO: Return caches.open(this.cacheName)
    throw new Error('Not implemented');
  }

  /**
   * Cache multiple resources
   * @param {string[]} urls - URLs to cache
   * @returns {Promise<void>}
   */
  async addAll(urls) {
    // TODO: Open cache and addAll urls
    throw new Error('Not implemented');
  }

  /**
   * Cache a single resource with its response
   * @param {Request|string} request
   * @param {Response} response
   * @returns {Promise<void>}
   */
  async put(request, response) {
    // TODO: Open cache and put request/response
    throw new Error('Not implemented');
  }

  /**
   * Get resource from cache
   * @param {Request|string} request
   * @returns {Promise<Response|undefined>}
   */
  async match(request) {
    // TODO: Return caches.match(request)
    throw new Error('Not implemented');
  }

  /**
   * Delete resource from cache
   * @param {Request|string} request
   * @returns {Promise<boolean>}
   */
  async delete(request) {
    // TODO: Open cache and delete request
    throw new Error('Not implemented');
  }

  /**
   * Get all cached URLs
   * @returns {Promise<string[]>}
   */
  async listCached() {
    // TODO: Open cache, get keys, return URLs
    throw new Error('Not implemented');
  }

  /**
   * Clear the entire cache
   * @returns {Promise<boolean>}
   */
  async clear() {
    // TODO: Delete this cache
    throw new Error('Not implemented');
  }

  /**
   * Get storage usage estimate
   * @returns {Promise<{usage: number, quota: number}>}
   */
  static async getStorageEstimate() {
    // TODO: Use navigator.storage.estimate()
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise2() {
  const cache = new CacheStorage('test-cache');

  // These tests require browser Cache API
  console.log('Exercise 2: Cache Manager - Implementation ready');
}

// =============================================================================
// Exercise 3: Offline Status Handler
// =============================================================================

/**
 * Handle online/offline status changes
 */
class OfflineHandler {
  constructor() {
    this.callbacks = {
      online: new Set(),
      offline: new Set(),
    };
    this.init();
  }

  /**
   * Initialize event listeners
   */
  init() {
    // TODO:
    // 1. Add 'online' event listener
    // 2. Add 'offline' event listener
    // 3. Call appropriate callbacks when events fire
    throw new Error('Not implemented');
  }

  /**
   * Check if currently online
   * @returns {boolean}
   */
  isOnline() {
    // TODO: Return navigator.onLine
    throw new Error('Not implemented');
  }

  /**
   * Register callback for online event
   * @param {Function} callback
   * @returns {Function} Unsubscribe function
   */
  onOnline(callback) {
    // TODO: Add callback to online set, return unsubscribe
    throw new Error('Not implemented');
  }

  /**
   * Register callback for offline event
   * @param {Function} callback
   * @returns {Function} Unsubscribe function
   */
  onOffline(callback) {
    // TODO: Add callback to offline set, return unsubscribe
    throw new Error('Not implemented');
  }

  /**
   * Register callback for any status change
   * @param {Function} callback - Receives boolean (true = online)
   * @returns {Function} Unsubscribe function
   */
  onChange(callback) {
    // TODO: Register for both online and offline
    throw new Error('Not implemented');
  }

  /**
   * Wait for online status
   * @param {number} timeout - Max wait time in ms
   * @returns {Promise<boolean>}
   */
  async waitForOnline(timeout = 30000) {
    // TODO:
    // 1. If already online, return true
    // 2. Otherwise, wait for online event or timeout
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise3() {
  const handler = new OfflineHandler();

  console.assert(
    typeof handler.isOnline() === 'boolean',
    'isOnline should return boolean'
  );

  let onlineCount = 0;
  const unsubscribe = handler.onOnline(() => onlineCount++);
  console.assert(
    typeof unsubscribe === 'function',
    'onOnline should return unsubscribe function'
  );

  console.log('Exercise 3: Offline Handler - Implementation ready');
}

// =============================================================================
// Exercise 4: Install Prompt Manager
// =============================================================================

/**
 * Manage PWA installation prompt
 */
class InstallManager {
  constructor() {
    this.deferredPrompt = null;
    this.installed = false;
    this.listeners = new Set();
    this.init();
  }

  /**
   * Initialize install prompt capture
   */
  init() {
    // TODO:
    // 1. Listen for 'beforeinstallprompt'
    // 2. Prevent default and store the event
    // 3. Notify listeners that install is available
    // 4. Listen for 'appinstalled' event
    throw new Error('Not implemented');
  }

  /**
   * Check if install is available
   * @returns {boolean}
   */
  canInstall() {
    // TODO: Return true if deferredPrompt exists
    throw new Error('Not implemented');
  }

  /**
   * Check if app is installed
   * @returns {boolean}
   */
  isInstalled() {
    // TODO: Check installed flag or display-mode: standalone
    throw new Error('Not implemented');
  }

  /**
   * Prompt user to install
   * @returns {Promise<'accepted'|'dismissed'>}
   */
  async promptInstall() {
    // TODO:
    // 1. Check if prompt is available
    // 2. Call prompt()
    // 3. Wait for userChoice
    // 4. Clear deferredPrompt
    // 5. Return outcome
    throw new Error('Not implemented');
  }

  /**
   * Register callback for when install becomes available
   * @param {Function} callback
   * @returns {Function} Unsubscribe
   */
  onInstallAvailable(callback) {
    // TODO: Add to listeners, call immediately if already available
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise4() {
  const installer = new InstallManager();

  console.assert(
    typeof installer.canInstall() === 'boolean',
    'canInstall should return boolean'
  );
  console.assert(
    typeof installer.isInstalled() === 'boolean',
    'isInstalled should return boolean'
  );

  console.log('Exercise 4: Install Manager - Implementation ready');
}

// =============================================================================
// Exercise 5: Offline Request Queue
// =============================================================================

/**
 * Queue requests when offline for later sync
 */
class RequestQueue {
  constructor(dbName = 'request-queue') {
    this.dbName = dbName;
    this.storeName = 'requests';
  }

  /**
   * Initialize IndexedDB
   * @returns {Promise<IDBDatabase>}
   */
  async initDB() {
    // TODO:
    // 1. Open IndexedDB
    // 2. Create object store in onupgradeneeded
    // 3. Return database
    throw new Error('Not implemented');
  }

  /**
   * Add request to queue
   * @param {string} url
   * @param {Object} options - Fetch options
   * @returns {Promise<string>} Request ID
   */
  async enqueue(url, options = {}) {
    // TODO:
    // 1. Generate unique ID
    // 2. Store request data in IndexedDB
    // 3. Return ID
    throw new Error('Not implemented');
  }

  /**
   * Get all queued requests
   * @returns {Promise<Array>}
   */
  async getAll() {
    // TODO: Return all requests from store
    throw new Error('Not implemented');
  }

  /**
   * Remove request from queue
   * @param {string} id
   * @returns {Promise<void>}
   */
  async remove(id) {
    // TODO: Delete request by ID
    throw new Error('Not implemented');
  }

  /**
   * Process all queued requests
   * @param {Function} onResult - Called with each result
   * @returns {Promise<{success: number, failed: number}>}
   */
  async processQueue(onResult) {
    // TODO:
    // 1. Get all requests
    // 2. For each, try to fetch
    // 3. On success, remove from queue
    // 4. Call onResult with each outcome
    // 5. Return counts
    throw new Error('Not implemented');
  }

  /**
   * Clear all queued requests
   * @returns {Promise<void>}
   */
  async clear() {
    // TODO: Clear object store
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise5() {
  const queue = new RequestQueue('test-queue');

  // Note: Requires browser IndexedDB
  console.log('Exercise 5: Request Queue - Implementation ready');
}

// =============================================================================
// Exercise 6: Service Worker Communication
// =============================================================================

/**
 * Communicate between main thread and Service Worker
 */
class SWMessenger {
  constructor() {
    this.handlers = new Map();
    this.init();
  }

  /**
   * Initialize message listener
   */
  init() {
    // TODO:
    // 1. Listen for 'message' event on navigator.serviceWorker
    // 2. Route to registered handlers by message type
    throw new Error('Not implemented');
  }

  /**
   * Send message to Service Worker
   * @param {Object} message
   * @returns {Promise<void>}
   */
  async send(message) {
    // TODO:
    // 1. Get active SW controller
    // 2. Post message to it
    throw new Error('Not implemented');
  }

  /**
   * Send message and wait for response
   * @param {Object} message
   * @param {number} timeout
   * @returns {Promise<any>}
   */
  async sendAndWait(message, timeout = 5000) {
    // TODO:
    // 1. Create MessageChannel
    // 2. Send message with port
    // 3. Wait for response on port
    throw new Error('Not implemented');
  }

  /**
   * Register handler for message type
   * @param {string} type
   * @param {Function} handler
   */
  on(type, handler) {
    // TODO: Store handler in map
    throw new Error('Not implemented');
  }

  /**
   * Tell SW to skip waiting
   */
  async skipWaiting() {
    // TODO: Send SKIP_WAITING message
    throw new Error('Not implemented');
  }

  /**
   * Request cache clear from SW
   * @param {string} cacheName
   */
  async clearCache(cacheName) {
    // TODO: Send CLEAR_CACHE message
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise6() {
  const messenger = new SWMessenger();

  console.log('Exercise 6: SW Communication - Implementation ready');
}

// =============================================================================
// Exercise 7: Complete PWA Setup
// =============================================================================

/**
 * Complete PWA implementation bringing everything together
 */
class PWAApplication {
  constructor(config = {}) {
    this.config = {
      swPath: '/sw.js',
      cacheName: 'app-cache-v1',
      offlineUrl: '/offline.html',
      ...config,
    };

    this.sw = null;
    this.cache = null;
    this.offline = null;
    this.installer = null;
    this.requestQueue = null;
  }

  /**
   * Initialize all PWA features
   * @returns {Promise<void>}
   */
  async init() {
    // TODO:
    // 1. Create SWRegistrar and register
    // 2. Create CacheStorage
    // 3. Create OfflineHandler
    // 4. Create InstallManager
    // 5. Create RequestQueue
    // 6. Set up event handlers
    throw new Error('Not implemented');
  }

  /**
   * Pre-cache critical resources
   * @param {string[]} urls
   */
  async precache(urls) {
    // TODO: Use cache to addAll urls
    throw new Error('Not implemented');
  }

  /**
   * Fetch with offline fallback
   * @param {string} url
   * @param {Object} options
   */
  async fetch(url, options = {}) {
    // TODO:
    // 1. Try network fetch
    // 2. If offline and POST/PUT/DELETE, queue for later
    // 3. For GET, try cache
    // 4. If all fails, return offline page
    throw new Error('Not implemented');
  }

  /**
   * Show install prompt if available
   */
  async promptInstall() {
    // TODO: Use installer to prompt
    throw new Error('Not implemented');
  }

  /**
   * Check for updates
   */
  async checkForUpdates() {
    // TODO: Use sw to check for updates
    throw new Error('Not implemented');
  }

  /**
   * Get current status
   * @returns {Object}
   */
  getStatus() {
    // TODO: Return object with:
    // - online: boolean
    // - swState: string
    // - cacheSize: number (if available)
    // - canInstall: boolean
    // - isInstalled: boolean
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise7() {
  const app = new PWAApplication({
    swPath: '/test-sw.js',
    cacheName: 'test-cache-v1',
  });

  console.log('Exercise 7: Complete PWA - Implementation ready');
}

// =============================================================================
// Run All Tests
// =============================================================================

async function runAllTests() {
  console.log('Running Service Worker and PWA Exercises...\n');

  try {
    await testExercise1();
    await testExercise2();
    await testExercise3();
    await testExercise4();
    await testExercise5();
    await testExercise6();
    await testExercise7();

    console.log('\nāœ… All exercise implementations ready!');
    console.log(
      'Note: Full testing requires browser environment with Service Worker support'
    );
  } catch (error) {
    console.error('\nāŒ Error:', error.message);
  }
}

// Export
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    SWRegistrar,
    CacheStorage,
    OfflineHandler,
    InstallManager,
    RequestQueue,
    SWMessenger,
    PWAApplication,
    runAllTests,
  };
}

// Uncomment to run
// runAllTests();
Exercises - JavaScript Tutorial | DeepML