javascript
exercises
exercises.jsā”javascript
/**
* 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();