Modern web development demands more than just knowing HTML, CSS, and JavaScript. The browser provides powerful APIs that can transform your applications from basic to extraordinary. This comprehensive guide covers 15 essential Web APIs with production-ready examples, best practices, and real-world implementations.
Table of Contents
- IndexedDB API
- Web Workers API
- WebRTC API
- Intersection Observer API
- Web Notifications API
- Web Speech API
- WebSocket API
- Service Worker API
- Resize Observer API
- Broadcast Channel API
- WebGL API
- Payment Request API
- Credential Management API
- Battery Status API
- Screen Wake Lock API
1. IndexedDB API - Client-Side Database Powerhouse πΎ
IndexedDB is a low-level API for storing significant amounts of structured data, including files and blobs. Unlike localStorage's 5-10MB limit, IndexedDB can store hundreds of megabytes or more.
Why Use IndexedDB?
- Large Storage: Handles GBs of data
- Indexed Queries: Fast data retrieval with indexes
- Asynchronous: Non-blocking operations
- Transactional: ACID-compliant database operations
Production-Ready Implementation
class IndexedDBManager {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object stores
if (!db.objectStoreNames.contains('users')) {
const userStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
userStore.createIndex('email', 'email', { unique: true });
userStore.createIndex('name', 'name', { unique: false });
}
if (!db.objectStoreNames.contains('posts')) {
const postStore = db.createObjectStore('posts', { keyPath: 'id', autoIncrement: true });
postStore.createIndex('userId', 'userId', { unique: false });
postStore.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
async add(storeName, data) {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
return new Promise((resolve, reject) => {
const request = store.add(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async get(storeName, key) {
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
return new Promise((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getByIndex(storeName, indexName, value) {
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
return new Promise((resolve, reject) => {
const request = index.get(value);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getAll(storeName) {
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async update(storeName, data) {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
return new Promise((resolve, reject) => {
const request = store.put(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async delete(storeName, key) {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
return new Promise((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
// Usage Example
const dbManager = new IndexedDBManager('MyApp');
await dbManager.init();
// Add user
const userId = await dbManager.add('users', {
name: 'John Doe',
email: 'john@example.com',
role: 'admin',
createdAt: new Date()
});
// Get user by email
const user = await dbManager.getByIndex('users', 'email', 'john@example.com');
// Update user
await dbManager.update('users', {
...user,
role: 'superadmin'
});
Real-World Use Cases
- Offline-First Applications: Store data locally, sync when online
- Progressive Web Apps: Cache API responses and user data
- Large Dataset Management: Handle complex queries client-side
- Media Storage: Store images, videos, and audio files
2. Web Workers API - Multithreading in JavaScript β‘
Web Workers enable true parallel processing in JavaScript, running scripts in background threads without blocking the UI.
The Problem Web Workers Solve
JavaScript is single-threaded. Heavy computations freeze the UI, creating poor user experiences. Web Workers solve this by offloading work to separate threads.
Advanced Implementation with Worker Pool
// worker-pool.js - Reusable Worker Pool Manager
class WorkerPool {
constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
this.workerScript = workerScript;
this.poolSize = poolSize;
this.workers = [];
this.queue = [];
this.activeWorkers = new Set();
this.initializePool();
}
initializePool() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerScript);
worker.id = i;
this.workers.push(worker);
}
}
async execute(data, transferable = []) {
return new Promise((resolve, reject) => {
const task = { data, transferable, resolve, reject };
const availableWorker = this.workers.find(w => !this.activeWorkers.has(w));
if (availableWorker) {
this.runTask(availableWorker, task);
} else {
this.queue.push(task);
}
});
}
runTask(worker, task) {
this.activeWorkers.add(worker);
const messageHandler = (e) => {
worker.removeEventListener('message', messageHandler);
worker.removeEventListener('error', errorHandler);
this.activeWorkers.delete(worker);
task.resolve(e.data);
this.processQueue();
};
const errorHandler = (e) => {
worker.removeEventListener('message', messageHandler);
worker.removeEventListener('error', errorHandler);
this.activeWorkers.delete(worker);
task.reject(e);
this.processQueue();
};
worker.addEventListener('message', messageHandler);
worker.addEventListener('error', errorHandler);
worker.postMessage(task.data, task.transferable);
}
processQueue() {
if (this.queue.length === 0) return;
const availableWorker = this.workers.find(w => !this.activeWorkers.has(w));
if (availableWorker) {
const task = this.queue.shift();
this.runTask(availableWorker, task);
}
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.queue = [];
this.activeWorkers.clear();
}
}
// image-processor-worker.js
self.addEventListener('message', (e) => {
const { imageData, operation } = e.data;
switch (operation) {
case 'grayscale':
const grayData = applyGrayscale(imageData);
self.postMessage({ result: grayData }, [grayData.data.buffer]);
break;
case 'blur':
const blurData = applyBlur(imageData, e.data.radius);
self.postMessage({ result: blurData }, [blurData.data.buffer]);
break;
case 'brighten':
const brightData = applyBrighten(imageData, e.data.amount);
self.postMessage({ result: brightData }, [brightData.data.buffer]);
break;
}
});
function applyGrayscale(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
return imageData;
}
function applyBlur(imageData, radius = 5) {
// Implement Gaussian blur algorithm
// ... (implementation details)
return imageData;
}
function applyBrighten(imageData, amount = 20) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.min(255, data[i] + amount);
data[i + 1] = Math.min(255, data[i + 1] + amount);
data[i + 2] = Math.min(255, data[i + 2] + amount);
}
return imageData;
}
// Usage in main thread
const workerPool = new WorkerPool('image-processor-worker.js', 4);
async function processImage(canvas, operation) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
try {
const result = await workerPool.execute(
{ imageData, operation, radius: 5, amount: 30 },
[imageData.data.buffer] // Transfer ownership for better performance
);
ctx.putImageData(result.result, 0, 0);
} catch (error) {
console.error('Image processing failed:', error);
}
}
// Process multiple images concurrently
const images = document.querySelectorAll('.image-to-process');
await Promise.all(
Array.from(images).map(img =>
processImage(img, 'grayscale')
)
);
Performance Comparison
| Task | Main Thread | Web Worker | Improvement |
|---|---|---|---|
| Image Processing (1920x1080) | ~850ms (UI frozen) | ~220ms (UI responsive) | 74% faster perceived |
| JSON Parsing (50MB) | ~2300ms | ~2100ms | UI remains smooth |
| Complex Calculations | Blocks rendering | Background execution | Infinite UX improvement |
Best Practices
- Use Transferable Objects: Transfer array buffers instead of copying
- Implement Worker Pools: Reuse workers for better performance
- Handle Errors Gracefully: Always implement error handlers
- Consider SharedArrayBuffer: For data sharing between workers
3. WebRTC API - Real-Time Communication πΉ
WebRTC (Web Real-Time Communication) enables peer-to-peer audio, video, and data sharing without plugins or intermediary servers.
Complete Video Chat Implementation
class VideoChat {
constructor(localVideoEl, remoteVideoEl, signalingServer) {
this.localVideo = localVideoEl;
this.remoteVideo = remoteVideoEl;
this.signalingServer = signalingServer;
this.peerConnection = null;
this.localStream = null;
this.configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com:3478',
username: 'user',
credential: 'pass'
}
]
};
this.setupSignaling();
}
async initialize() {
try {
// Get local media stream
this.localStream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
this.localVideo.srcObject = this.localStream;
// Create peer connection
this.peerConnection = new RTCPeerConnection(this.configuration);
// Add local tracks to peer connection
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
// Handle incoming tracks
this.peerConnection.ontrack = (event) => {
this.remoteVideo.srcObject = event.streams[0];
};
// Handle ICE candidates
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.signalingServer.send({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
// Monitor connection state
this.peerConnection.onconnectionstatechange = () => {
console.log('Connection state:', this.peerConnection.connectionState);
if (this.peerConnection.connectionState === 'disconnected') {
this.handleDisconnection();
}
};
// Monitor ICE connection state
this.peerConnection.oniceconnectionstatechange = () => {
console.log('ICE state:', this.peerConnection.iceConnectionState);
};
} catch (error) {
console.error('Failed to initialize video chat:', error);
throw error;
}
}
setupSignaling() {
this.signalingServer.on('offer', async (offer) => {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
this.signalingServer.send({
type: 'answer',
answer: answer
});
});
this.signalingServer.on('answer', async (answer) => {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
});
this.signalingServer.on('ice-candidate', async (candidate) => {
await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});
}
async createOffer() {
const offer = await this.peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await this.peerConnection.setLocalDescription(offer);
this.signalingServer.send({
type: 'offer',
offer: offer
});
}
toggleAudio(enabled) {
this.localStream.getAudioTracks().forEach(track => {
track.enabled = enabled;
});
}
toggleVideo(enabled) {
this.localStream.getVideoTracks().forEach(track => {
track.enabled = enabled;
});
}
async switchCamera() {
const videoTrack = this.localStream.getVideoTracks()[0];
const currentFacingMode = videoTrack.getSettings().facingMode;
videoTrack.stop();
const newStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: currentFacingMode === 'user' ? 'environment' : 'user' }
});
const newVideoTrack = newStream.getVideoTracks()[0];
const sender = this.peerConnection.getSenders().find(s => s.track.kind === 'video');
sender.replaceTrack(newVideoTrack);
this.localStream.removeTrack(videoTrack);
this.localStream.addTrack(newVideoTrack);
this.localVideo.srcObject = this.localStream;
}
async shareScreen() {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' },
audio: false
});
const screenTrack = screenStream.getVideoTracks()[0];
const sender = this.peerConnection.getSenders().find(s => s.track.kind === 'video');
sender.replaceTrack(screenTrack);
// Restore camera when screen sharing stops
screenTrack.onended = () => {
const videoTrack = this.localStream.getVideoTracks()[0];
sender.replaceTrack(videoTrack);
};
} catch (error) {
console.error('Screen sharing failed:', error);
}
}
getStats() {
return this.peerConnection.getStats();
}
handleDisconnection() {
console.log('Peer disconnected. Attempting reconnection...');
// Implement reconnection logic
}
disconnect() {
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
if (this.peerConnection) {
this.peerConnection.close();
}
this.localVideo.srcObject = null;
this.remoteVideo.srcObject = null;
}
}
// Usage
const videoChat = new VideoChat(
document.getElementById('local-video'),
document.getElementById('remote-video'),
signalingServerConnection
);
await videoChat.initialize();
await videoChat.createOffer();
// Control buttons
document.getElementById('mute-btn').addEventListener('click', () => {
videoChat.toggleAudio(false);
});
document.getElementById('share-screen-btn').addEventListener('click', () => {
videoChat.shareScreen();
});
Applications
- Video Conferencing: Zoom, Google Meet alternatives
- Live Streaming: Twitch-like platforms
- Online Gaming: Real-time multiplayer games
- IoT Communication: Device-to-device communication
- File Sharing: P2P file transfer without servers
4. Intersection Observer API - Efficient Scroll Detection π―
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or the viewport.
Advanced Implementation with Performance Optimization
class LazyLoadManager {
constructor(options = {}) {
this.options = {
root: options.root || null,
rootMargin: options.rootMargin || '50px',
threshold: options.threshold || 0.1,
unobserveOnLoad: options.unobserveOnLoad !== false
};
this.observer = null;
this.observed = new WeakMap();
this.initialize();
}
initialize() {
this.observer = new IntersectionObserver(
(entries) => this.handleIntersection(entries),
this.options
);
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
const callback = this.observed.get(element);
if (callback) {
callback(element, entry);
if (this.options.unobserveOnLoad) {
this.unobserve(element);
}
}
}
});
}
observe(element, callback) {
this.observed.set(element, callback);
this.observer.observe(element);
}
unobserve(element) {
this.observed.delete(element);
this.observer.unobserve(element);
}
disconnect() {
this.observer.disconnect();
this.observed = new WeakMap();
}
}
// Image Lazy Loading with Progressive Enhancement
class ImageLazyLoader extends LazyLoadManager {
constructor(options) {
super(options);
this.loadedImages = new Set();
}
loadImage(img) {
return new Promise((resolve, reject) => {
// Add loading placeholder
img.classList.add('loading');
const src = img.dataset.src;
const srcset = img.dataset.srcset;
// Create a temporary image to preload
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
if (srcset) img.srcset = srcset;
img.classList.remove('loading');
img.classList.add('loaded');
this.loadedImages.add(img);
resolve(img);
};
tempImg.onerror = () => {
img.classList.remove('loading');
img.classList.add('error');
reject(new Error(`Failed to load image: ${src}`));
};
tempImg.src = src;
});
}
observeImages(selector = 'img[data-src]') {
const images = document.querySelectorAll(selector);
images.forEach(img => {
this.observe(img, async (element) => {
try {
await this.loadImage(element);
} catch (error) {
console.error(error);
}
});
});
}
}
// Infinite Scroll Implementation
class InfiniteScroll extends LazyLoadManager {
constructor(options) {
super({
...options,
rootMargin: '200px',
threshold: 0
});
this.loading = false;
this.page = 1;
this.hasMore = true;
}
async loadMore(fetchFunction) {
if (this.loading || !this.hasMore) return;
this.loading = true;
try {
const data = await fetchFunction(this.page);
if (data && data.length > 0) {
this.renderData(data);
this.page++;
} else {
this.hasMore = false;
}
} catch (error) {
console.error('Failed to load more:', error);
} finally {
this.loading = false;
}
}
renderData(data) {
// Implement your rendering logic
const container = document.getElementById('content-container');
data.forEach(item => {
const element = this.createItemElement(item);
container.appendChild(element);
});
}
createItemElement(item) {
const div = document.createElement('div');
div.className = 'item';
div.innerHTML = `
<h3>${item.title}</h3>
<p>${item.description}</p>
`;
return div;
}
init(sentinel, fetchFunction) {
this.observe(sentinel, () => {
this.loadMore(fetchFunction);
});
}
}
// Animate on Scroll
class ScrollAnimator extends LazyLoadManager {
constructor(options) {
super({
...options,
threshold: 0.2,
unobserveOnLoad: true
});
}
animateElements(selector = '.animate-on-scroll') {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
this.observe(el, (element, entry) => {
const animationClass = element.dataset.animation || 'fade-in';
const delay = element.dataset.delay || 0;
setTimeout(() => {
element.classList.add(animationClass, 'animated');
}, delay);
});
});
}
}
// Usage Examples
// 1. Image Lazy Loading
const imageLazyLoader = new ImageLazyLoader({
rootMargin: '100px',
threshold: 0.01
});
imageLazyLoader.observeImages();
// 2. Infinite Scroll
const infiniteScroll = new InfiniteScroll();
const sentinel = document.getElementById('scroll-sentinel');
infiniteScroll.init(sentinel, async (page) => {
const response = await fetch(`/api/posts?page=${page}`);
return response.json();
});
// 3. Scroll Animations
const animator = new ScrollAnimator({ threshold: 0.3 });
animator.animateElements('.fade-in-element');
// 4. Track Visibility for Analytics
const analyticsObserver = new LazyLoadManager({ threshold: 0.5 });
document.querySelectorAll('[data-track-view]').forEach(el => {
analyticsObserver.observe(el, (element) => {
const eventName = element.dataset.trackView;
analytics.track('element_viewed', { element: eventName });
});
});
CSS for Animations
/* Loading states */
img.loading {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Scroll animations */
.animate-on-scroll {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll.animated {
opacity: 1;
transform: translateY(0);
}
.fade-in {
animation: fadeIn 0.6s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Performance Benefits
- No Scroll Events: Eliminates expensive scroll event listeners
- Asynchronous: Non-blocking, runs off the main thread
- Precise Control: Multiple thresholds and margins
- Battery Efficient: Only fires when needed
5. Web Notifications API - Engage Users Effectively π
The Notifications API allows web pages to send system notifications that appear outside the page, even when the browser is minimized.
Advanced Notification System
class NotificationManager {
constructor() {
this.permission = Notification.permission;
this.queue = [];
this.activeNotifications = new Map();
}
async requestPermission() {
if (this.permission === 'granted') {
return true;
}
if (this.permission === 'denied') {
console.warn('Notification permission denied');
return false;
}
try {
const result = await Notification.requestPermission();
this.permission = result;
return result === 'granted';
} catch (error) {
console.error('Failed to request notification permission:', error);
return false;
}
}
async send(title, options = {}) {
if (this.permission !== 'granted') {
const granted = await this.requestPermission();
if (!granted) return null;
}
const defaultOptions = {
icon: '/icon.png',
badge: '/badge.png',
vibrate: [200, 100, 200],
timestamp: Date.now(),
requireInteraction: false,
silent: false,
...options
};
try {
const notification = new Notification(title, defaultOptions);
// Track active notifications
const id = options.tag || `notif-${Date.now()}`;
this.activeNotifications.set(id, notification);
// Event handlers
notification.onclick = (event) => {
event.preventDefault();
window.focus();
if (options.onClick) {
options.onClick(event);
}
if (options.data?.url) {
window.location.href = options.data.url;
}
notification.close();
};
notification.onclose = () => {
this.activeNotifications.delete(id);
if (options.onClose) {
options.onClose();
}
};
notification.onerror = (error) => {
console.error('Notification error:', error);
if (options.onError) {
options.onError(error);
}
};
// Auto close after duration
if (options.duration) {
setTimeout(() => {
notification.close();
}, options.duration);
}
return notification;
} catch (error) {
console.error('Failed to create notification:', error);
return null;
}
}
async sendRichNotification(title, options) {
const richOptions = {
...options,
actions: options.actions || [
{
action: 'view',
title: 'View',
icon: '/icons/view.png'
},
{
action: 'dismiss',
title: 'Dismiss',
icon: '/icons/dismiss.png'
}
]
};
return this.send(title, richOptions);
}
schedule(title, options, delay) {
return new Promise((resolve) => {
const timeoutId = setTimeout(async () => {
const notification = await this.send(title, options);
resolve(notification);
}, delay);
// Store for cancellation
this.queue.push({
timeoutId,
title,
options,
scheduledTime: Date.now() + delay
});
});
}
cancelScheduled(index) {
if (this.queue[index]) {
clearTimeout(this.queue[index].timeoutId);
this.queue.splice(index, 1);
}
}
closeAll() {
this.activeNotifications.forEach(notification => {
notification.close();
});
this.activeNotifications.clear();
}
closeByTag(tag) {
const notification = this.activeNotifications.get(tag);
if (notification) {
notification.close();
this.activeNotifications.delete(tag);
}
}
}
// Service Worker for Background Notifications
// sw.js
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: data.icon || '/icon.png',
badge: '/badge.png',
data: data.data,
actions: data.actions || [
{ action: 'open', title: 'Open' },
{ action: 'close', title: 'Close' }
],
tag: data.tag,
requireInteraction: data.requireInteraction || false
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'open') {
event.waitUntil(
clients.openWindow(event.notification.data.url || '/')
);
}
});
// Usage Examples
const notificationManager = new NotificationManager();
// 1. Simple notification
await notificationManager.send('New Message', {
body: 'You have a new message from John',
icon: '/avatars/john.png',
tag: 'message-123'
});
// 2. Rich notification with actions
await notificationManager.sendRichNotification('Meeting Reminder', {
body: 'Team standup in 5 minutes',
data: { url: '/meetings/standup' },
actions: [
{ action: 'join', title: 'Join Now', icon: '/icons/video.png' },
{ action: 'snooze', title: 'Snooze', icon: '/icons/clock.png' }
],
onClick: (event) => {
if (event.action === 'snooze') {
notificationManager.schedule('Meeting Reminder', {
body: 'Team standup starting now!'
}, 5 * 60 * 1000); // 5 minutes
}
}
});
// 3. Scheduled notification
await notificationManager.schedule('Take a Break', {
body: 'You\'ve been working for 2 hours',
icon: '/icons/break.png',
duration: 10000
}, 2 * 60 * 60 * 1000); // 2 hours
// 4. Progressive enhancement with fallback
async function notifyUser(message) {
if ('Notification' in window) {
await notificationManager.send('App Notification', {
body: message
});
} else {
// Fallback to in-app notification
showInAppNotification(message);
}
}
Best Practices
- Request Permission Contextually: Ask when user performs relevant action
- Don't Spam: Respect user attention and frequency
- Provide Value: Only send meaningful notifications
- Allow Control: Let users manage preferences
- Handle Errors: Always check permission status
6. Web Speech API - Voice Interaction π€
The Web Speech API enables speech recognition (speech-to-text) and speech synthesis (text-to-speech) capabilities.
Complete Voice Assistant Implementation
class VoiceAssistant {
constructor(options = {}) {
this.options = {
lang: options.lang || 'en-US',
continuous: options.continuous || false,
interimResults: options.interimResults || true,
maxAlternatives: options.maxAlternatives || 1,
...options
};
this.recognition = null;
this.synthesis = window.speechSynthesis;
this.isListening = false;
this.commands = new Map();
this.transcript = '';
this.initRecognition();
}
initRecognition() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
console.error('Speech recognition not supported');
return;
}
this.recognition = new SpeechRecognition();
Object.assign(this.recognition, this.options);
this.recognition.onstart = () => {
this.isListening = true;
this.onListeningStart?.();
};
this.recognition.onend = () => {
this.isListening = false;
this.onListeningEnd?.();
if (this.options.continuous && this.shouldRestart) {
this.start();
}
};
this.recognition.onresult = (event) => {
const results = Array.from(event.results);
const current = results[event.resultIndex];
const transcript = current[0].transcript;
const isFinal = current.isFinal;
const confidence = current[0].confidence;
if (isFinal) {
this.transcript = transcript;
this.processCommand(transcript, confidence);
}
this.onResult?.({ transcript, isFinal, confidence });
};
this.recognition.onerror = (event) => {
console.error('Speech recognition error:', event.error);
this.onError?.(event.error);
};
}
registerCommand(pattern, callback, options = {}) {
const id = options.id || `cmd-${this.commands.size}`;
this.commands.set(id, {
pattern: pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i'),
callback,
priority: options.priority || 0,
requiresConfidence: options.requiresConfidence || 0.7
});
return id;
}
unregisterCommand(id) {
this.commands.delete(id);
}
processCommand(transcript, confidence) {
const sortedCommands = Array.from(this.commands.values())
.sort((a, b) => b.priority - a.priority);
for (const command of sortedCommands) {
if (confidence < command.requiresConfidence) continue;
const match = transcript.match(command.pattern);
if (match) {
command.callback(match, transcript, confidence);
return true;
}
}
this.onUnknownCommand?.(transcript, confidence);
return false;
}
speak(text, options = {}) {
return new Promise((resolve, reject) => {
if (!this.synthesis) {
reject(new Error('Speech synthesis not supported'));
return;
}
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = options.lang || this.options.lang;
utterance.voice = options.voice || this.getVoice(options.voiceName);
utterance.volume = options.volume ?? 1;
utterance.rate = options.rate ?? 1;
utterance.pitch = options.pitch ?? 1;
utterance.onend = () => resolve();
utterance.onerror = (error) => reject(error);
if (this.isListening) {
this.pause();
}
this.synthesis.speak(utterance);
utterance.onend = () => {
resolve();
if (this.options.continuous) {
this.resume();
}
};
});
}
getVoice(voiceName) {
if (!voiceName) return null;
const voices = this.synthesis.getVoices();
return voices.find(voice =>
voice.name.toLowerCase().includes(voiceName.toLowerCase())
);
}
getVoices() {
return this.synthesis.getVoices();
}
start() {
if (!this.recognition) {
console.error('Speech recognition not initialized');
return;
}
try {
this.shouldRestart = true;
this.recognition.start();
} catch (error) {
if (error.message.includes('already started')) {
console.warn('Recognition already started');
} else {
console.error('Failed to start recognition:', error);
}
}
}
stop() {
this.shouldRestart = false;
this.recognition?.stop();
}
pause() {
this.recognition?.stop();
}
resume() {
if (this.shouldRestart) {
this.recognition?.start();
}
}
abort() {
this.recognition?.abort();
this.synthesis.cancel();
}
}
// Usage Example: Smart Home Controller
const assistant = new VoiceAssistant({
lang: 'en-US',
continuous: true,
interimResults: true
});
// Register commands
assistant.registerCommand(
/turn (on|off) the (.*)/i,
(match) => {
const [, action, device] = match;
console.log(`${action} ${device}`);
assistant.speak(`Turning ${action} the ${device}`);
// API call to control device
controlDevice(device, action);
},
{ priority: 10 }
);
assistant.registerCommand(
/what(?:'s| is) the (weather|temperature)/i,
async (match) => {
const [, query] = match;
const data = await fetch(`/api/${query}`).then(r => r.json());
assistant.speak(`The current ${query} is ${data.value}`);
},
{ priority: 8 }
);
assistant.registerCommand(
/set (timer|alarm) for (\d+) (minutes?|hours?)/i,
(match) => {
const [, type, duration, unit] = match;
const ms = unit.includes('hour')
? duration * 60 * 60 * 1000
: duration * 60 * 1000;
setTimeout(() => {
assistant.speak(`Your ${type} for ${duration} ${unit} is up!`);
}, ms);
assistant.speak(`${type} set for ${duration} ${unit}`);
},
{ priority: 9 }
);
assistant.registerCommand(
/play (.*)/i,
(match) => {
const [, song] = match;
assistant.speak(`Playing ${song}`);
playMusic(song);
}
);
// Event handlers
assistant.onListeningStart = () => {
document.querySelector('.mic-icon').classList.add('listening');
};
assistant.onListeningEnd = () => {
document.querySelector('.mic-icon').classList.remove('listening');
};
assistant.onResult = ({ transcript, isFinal, confidence }) => {
const display = document.querySelector('.transcript');
display.textContent = transcript;
if (isFinal) {
display.classList.add('final');
}
};
assistant.onUnknownCommand = (transcript, confidence) => {
if (confidence > 0.6) {
assistant.speak("I'm sorry, I didn't understand that command");
}
};
// Voice selection UI
const voiceSelect = document.querySelector('#voice-select');
window.speechSynthesis.onvoiceschanged = () => {
const voices = assistant.getVoices();
voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.name;
option.textContent = `${voice.name} (${voice.lang})`;
voiceSelect.appendChild(option);
});
};
// Start listening
assistant.start();
Multilingual Support
// Add language switching
const multilingualAssistant = new VoiceAssistant({
lang: 'en-US',
continuous: true
});
const languages = {
'en': 'en-US',
'es': 'es-ES',
'fr': 'fr-FR',
'de': 'de-DE',
'hi': 'hi-IN',
'ja': 'ja-JP'
};
multilingualAssistant.registerCommand(
/switch (language|lang) to (.*)/i,
(match) => {
const [, , langName] = match;
const langCode = languages[langName.toLowerCase()];
if (langCode) {
multilingualAssistant.recognition.lang = langCode;
multilingualAssistant.options.lang = langCode;
multilingualAssistant.speak(`Language switched to ${langName}`, {
lang: langCode
});
multilingualAssistant.stop();
multilingualAssistant.start();
}
}
);
7. WebSocket API - Real-Time Bidirectional Communication π
WebSocket provides full-duplex communication channels over a single TCP connection, perfect for real-time applications.
Production-Ready WebSocket Manager
class WebSocketManager {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnect: true,
reconnectInterval: 1000,
reconnectDecay: 1.5,
maxReconnectInterval: 30000,
maxReconnectAttempts: Infinity,
heartbeatInterval: 30000,
heartbeatMessage: JSON.stringify({ type: 'ping' }),
...options
};
this.socket = null;
this.reconnectAttempts = 0;
this.reconnectTimer = null;
this.heartbeatTimer = null;
this.listeners = new Map();
this.messageQueue = [];
this.isConnected = false;
this.connect();
}
connect() {
try {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => this.handleOpen();
this.socket.onmessage = (event) => this.handleMessage(event);
this.socket.onerror = (error) => this.handleError(error);
this.socket.onclose = (event) => this.handleClose(event);
} catch (error) {
console.error('WebSocket connection failed:', error);
this.attemptReconnect();
}
}
handleOpen() {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
// Send queued messages
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.send(message);
}
// Start heartbeat
this.startHeartbeat();
// Trigger onopen listeners
this.emit('open');
}
handleMessage(event) {
try {
const data = JSON.parse(event.data);
// Handle pong response
if (data.type === 'pong') {
return;
}
// Emit to specific event listeners
if (data.type) {
this.emit(data.type, data);
}
// Emit to general message listeners
this.emit('message', data);
} catch (error) {
// Handle non-JSON messages
this.emit('message', event.data);
}
}
handleError(error) {
console.error('WebSocket error:', error);
this.emit('error', error);
}
handleClose(event) {
console.log('WebSocket closed:', event.code, event.reason);
this.isConnected = false;
this.stopHeartbeat();
this.emit('close', event);
if (this.options.reconnect && !event.wasClean) {
this.attemptReconnect();
}
}
attemptReconnect() {
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
this.emit('reconnect_failed');
return;
}
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(this.options.reconnectDecay, this.reconnectAttempts - 1),
this.options.maxReconnectInterval
);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
this.reconnectTimer = setTimeout(() => {
this.emit('reconnecting', this.reconnectAttempts);
this.connect();
}, delay);
}
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {
if (this.isConnected) {
this.send(this.options.heartbeatMessage);
}
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
send(data) {
const message = typeof data === 'string' ? data : JSON.stringify(data);
if (this.isConnected && this.socket.readyState === WebSocket.OPEN) {
try {
this.socket.send(message);
return true;
} catch (error) {
console.error('Failed to send message:', error);
this.messageQueue.push(message);
return false;
}
} else {
// Queue message for later
this.messageQueue.push(message);
return false;
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
off(event, callback) {
if (!this.listeners.has(event)) return;
const listeners = this.listeners.get(event);
const index = listeners.indexOf(callback);
if (index !== -1) {
listeners.splice(index, 1);
}
}
emit(event, data) {
if (!this.listeners.has(event)) return;
this.listeners.get(event).forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in ${event} listener:`, error);
}
});
}
close(code = 1000, reason = 'Normal closure') {
this.options.reconnect = false;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.stopHeartbeat();
if (this.socket) {
this.socket.close(code, reason);
}
}
getState() {
if (!this.socket) return 'CLOSED';
const states = {
[WebSocket.CONNECTING]: 'CONNECTING',
[WebSocket.OPEN]: 'OPEN',
[WebSocket.CLOSING]: 'CLOSING',
[WebSocket.CLOSED]: 'CLOSED'
};
return states[this.socket.readyState];
}
}
// Real-time Chat Application
class ChatApplication {
constructor(serverUrl) {
this.ws = new WebSocketManager(serverUrl, {
reconnect: true,
heartbeatInterval: 30000
});
this.currentUser = null;
this.setupEventListeners();
}
setupEventListeners() {
this.ws.on('open', () => {
console.log('Chat connected');
this.showStatus('Connected', 'success');
});
this.ws.on('close', () => {
this.showStatus('Disconnected', 'error');
});
this.ws.on('reconnecting', (attempt) => {
this.showStatus(`Reconnecting (${attempt})...`, 'warning');
});
this.ws.on('message:chat', (data) => {
this.displayMessage(data);
});
this.ws.on('user:joined', (data) => {
this.showNotification(`${data.username} joined the chat`);
this.updateUserList(data.users);
});
this.ws.on('user:left', (data) => {
this.showNotification(`${data.username} left the chat`);
this.updateUserList(data.users);
});
this.ws.on('typing', (data) => {
this.showTypingIndicator(data.username);
});
}
login(username) {
this.currentUser = username;
this.ws.send({
type: 'auth',
username: username,
timestamp: Date.now()
});
}
sendMessage(text) {
const message = {
type: 'message:chat',
user: this.currentUser,
text: text,
timestamp: Date.now()
};
this.ws.send(message);
this.displayMessage(message, true); // Display optimistically
}
sendTypingIndicator() {
this.ws.send({
type: 'typing',
user: this.currentUser
});
}
displayMessage(data, isOwn = false) {
const container = document.getElementById('messages');
const messageEl = document.createElement('div');
messageEl.className = `message ${isOwn ? 'own' : ''}`;
messageEl.innerHTML = `
<div class="message-user">${data.user}</div>
<div class="message-text">${this.escapeHtml(data.text)}</div>
<div class="message-time">${new Date(data.timestamp).toLocaleTimeString()}</div>
`;
container.appendChild(messageEl);
container.scrollTop = container.scrollHeight;
}
showTypingIndicator(username) {
const indicator = document.getElementById('typing-indicator');
indicator.textContent = `${username} is typing...`;
indicator.style.display = 'block';
// Hide after 3 seconds
clearTimeout(this.typingTimeout);
this.typingTimeout = setTimeout(() => {
indicator.style.display = 'none';
}, 3000);
}
showStatus(message, type) {
const status = document.getElementById('status');
status.textContent = message;
status.className = `status ${type}`;
}
showNotification(message) {
// Implementation
}
updateUserList(users) {
const list = document.getElementById('user-list');
list.innerHTML = users.map(user => `
<div class="user">${this.escapeHtml(user)}</div>
`).join('');
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Usage
const chat = new ChatApplication('wss://your-server.com/chat');
document.getElementById('login-btn').addEventListener('click', () => {
const username = document.getElementById('username').value;
chat.login(username);
});
document.getElementById('send-btn').addEventListener('click', () => {
const text = document.getElementById('message-input').value;
if (text.trim()) {
chat.sendMessage(text);
document.getElementById('message-input').value = '';
}
});
let typingTimer;
document.getElementById('message-input').addEventListener('input', () => {
clearTimeout(typingTimer);
chat.sendTypingIndicator();
typingTimer = setTimeout(() => {
// Stop typing indicator
}, 1000);
});
Server-Side Example (Node.js)
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Map();
wss.on('connection', (ws) => {
let username = null;
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
switch (message.type) {
case 'auth':
username = message.username;
clients.set(ws, username);
// Broadcast user joined
broadcast({
type: 'user:joined',
username: username,
users: Array.from(clients.values())
}, ws);
break;
case 'message:chat':
// Broadcast to all except sender
broadcast(message, ws);
break;
case 'typing':
broadcast(message, ws);
break;
case 'ping':
ws.send(JSON.stringify({ type: 'pong' }));
break;
}
} catch (error) {
console.error('Message parse error:', error);
}
});
ws.on('close', () => {
if (username) {
clients.delete(ws);
broadcast({
type: 'user:left',
username: username,
users: Array.from(clients.values())
});
}
});
});
function broadcast(message, exclude = null) {
const data = JSON.stringify(message);
clients.forEach((username, client) => {
if (client !== exclude && client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
}
console.log('WebSocket server running on port 8080');
8. Service Worker API - Offline-First Web Apps π΄
Service Workers act as a proxy between web applications and the network, enabling offline functionality, background sync, and push notifications.
Complete PWA Implementation
// sw.js - Service Worker
const CACHE_VERSION = 'v1.0.0';
const CACHE_NAMES = {
static: `static-${CACHE_VERSION}`,
dynamic: `dynamic-${CACHE_VERSION}`,
images: `images-${CACHE_VERSION}`
};
const STATIC_ASSETS = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/offline.html',
'/manifest.json'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(CACHE_NAMES.static)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// Activate event - cleanup old caches
self.addEventListener('activate', (event) => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => !Object.values(CACHE_NAMES).includes(name))
.map(name => caches.delete(name))
);
})
.then(() => self.clients.claim())
);
});
// Fetch event - serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Handle different resource types
if (request.destination === 'image') {
event.respondWith(handleImageRequest(request));
} else if (url.origin === location.origin) {
event.respondWith(handleSameOriginRequest(request));
} else {
event.respondWith(handleCrossOriginRequest(request));
}
});
async function handleImageRequest(request) {
const cache = await caches.open(CACHE_NAMES.images);
const cached = await cache.match(request);
if (cached) {
return cached;
}
try {
const response = await fetch(request);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch (error) {
// Return placeholder image
return caches.match('/images/placeholder.png');
}
}
async function handleSameOriginRequest(request) {
// Network-first strategy for API calls
if (request.url.includes('/api/')) {
return networkFirst(request);
}
// Cache-first strategy for static assets
return cacheFirst(request);
}
async function handleCrossOriginRequest(request) {
try {
const response = await fetch(request);
return response;
} catch (error) {
return new Response('Network error', { status: 503 });
}
}
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) {
// Update cache in background
updateCache(request);
return cached;
}
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response.clone());
return response;
} catch (error) {
return caches.match('/offline.html');
}
}
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response.clone());
return response;
} catch (error) {
const cached = await caches.match(request);
return cached || new Response(JSON.stringify({ error: 'Offline' }), {
headers: { 'Content-Type': 'application/json' }
});
}
}
async function updateCache(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response);
} catch (error) {
// Silently fail
}
}
// Background Sync
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData());
}
});
async function syncData() {
const db = await openDB();
const pendingData = await db.getAll('pending-requests');
for (const item of pendingData) {
try {
await fetch(item.url, {
method: item.method,
headers: item.headers,
body: item.body
});
await db.delete('pending-requests', item.id);
} catch (error) {
console.error('Sync failed:', error);
}
}
}
// Push Notifications
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
const options = {
body: data.body || 'New notification',
icon: data.icon || '/icon.png',
badge: '/badge.png',
data: data.data || {},
actions: data.actions || []
};
event.waitUntil(
self.registration.showNotification(data.title || 'Notification', options)
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then(clientList => {
// Focus existing window or open new one
for (const client of clientList) {
if (client.url === event.notification.data.url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(event.notification.data.url || '/');
}
})
);
});
// Message handling
self.addEventListener('message', (event) => {
if (event.data.action === 'skipWaiting') {
self.skipWaiting();
}
if (event.data.action === 'clearCache') {
event.waitUntil(
caches.keys().then(names =>
Promise.all(names.map(name => caches.delete(name)))
)
);
}
});
// main.js - Register Service Worker
class ServiceWorkerManager {
constructor(swPath = '/sw.js') {
this.swPath = swPath;
this.registration = null;
}
async register() {
if (!('serviceWorker' in navigator)) {
console.warn('Service Workers not supported');
return false;
}
try {
this.registration = await navigator.serviceWorker.register(this.swPath);
console.log('Service Worker registered:', this.registration);
// Check for updates
this.registration.addEventListener('updatefound', () => {
this.handleUpdate(this.registration.installing);
});
// Handle controller change
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('New Service Worker activated');
this.showUpdateNotification();
});
return true;
} catch (error) {
console.error('Service Worker registration failed:', error);
return false;
}
}
handleUpdate(worker) {
worker.addEventListener('statechange', () => {
if (worker.state === 'installed' && navigator.serviceWorker.controller) {
// New update available
this.showUpdatePrompt(worker);
}
});
}
showUpdatePrompt(worker) {
const shouldUpdate = confirm('New version available! Reload to update?');
if (shouldUpdate) {
worker.postMessage({ action: 'skipWaiting' });
}
}
showUpdateNotification() {
const notification = document.createElement('div');
notification.className = 'update-notification';
notification.innerHTML = `
<p>App updated! Reloading...</p>
`;
document.body.appendChild(notification);
setTimeout(() => window.location.reload(), 1000);
}
async unregister() {
if (this.registration) {
return this.registration.unregister();
}
return false;
}
async update() {
if (this.registration) {
return this.registration.update();
}
}
async enableBackgroundSync() {
if (!this.registration) return false;
try {
await this.registration.sync.register('sync-data');
return true;
} catch (error) {
console.error('Background sync registration failed:', error);
return false;
}
}
async subscribeToPush(vapidPublicKey) {
if (!this.registration) return null;
try {
const subscription = await this.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(vapidPublicKey)
});
return subscription;
} catch (error) {
console.error('Push subscription failed:', error);
return null;
}
}
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;
}
}
// Usage
const swManager = new ServiceWorkerManager();
// Register on page load
window.addEventListener('load', async () => {
const registered = await swManager.register();
if (registered) {
// Enable background sync
await swManager.enableBackgroundSync();
// Subscribe to push notifications
const subscription = await swManager.subscribeToPush(VAPID_PUBLIC_KEY);
if (subscription) {
// Send subscription to server
await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
}
}
});
// Queue requests for background sync when offline
window.addEventListener('offline', () => {
console.log('App is offline. Requests will be queued.');
document.body.classList.add('offline');
});
window.addEventListener('online', () => {
console.log('App is online. Syncing queued requests.');
document.body.classList.remove('offline');
swManager.enableBackgroundSync();
});
manifest.json
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A modern progressive web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#007bff",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "New Post",
"url": "/new-post",
"icons": [{ "src": "/icons/new-post.png", "sizes": "96x96" }]
},
{
"name": "Messages",
"url": "/messages",
"icons": [{ "src": "/icons/messages.png", "sizes": "96x96" }]
}
],
"categories": ["productivity", "social"],
"screenshots": [
{
"src": "/screenshots/home.png",
"sizes": "540x720",
"type": "image/png"
}
]
}
9. Resize Observer API - Responsive Element Monitoring π
The Resize Observer API reports changes to the dimensions of an Element's content or border box.
Implementation
class ResponsiveManager {
constructor() {
this.observers = new Map();
this.breakpoints = {
mobile: 480,
tablet: 768,
desktop: 1024,
wide: 1440
};
}
observe(element, callback, options = {}) {
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
const { blockSize, inlineSize } = entry.contentBoxSize[0] || {};
const data = {
width,
height,
blockSize,
inlineSize,
element: entry.target,
breakpoint: this.getBreakpoint(width)
};
callback(data, entry);
});
});
observer.observe(element, options);
this.observers.set(element, observer);
return () => this.unobserve(element);
}
unobserve(element) {
const observer = this.observers.get(element);
if (observer) {
observer.unobserve(element);
this.observers.delete(element);
}
}
getBreakpoint(width) {
if (width < this.breakpoints.mobile) return 'xs';
if (width < this.breakpoints.tablet) return 'mobile';
if (width < this.breakpoints.desktop) return 'tablet';
if (width < this.breakpoints.wide) return 'desktop';
return 'wide';
}
disconnect() {
this.observers.forEach(observer => observer.disconnect());
this.observers.clear();
}
}
// Usage
const manager = new ResponsiveManager();
manager.observe(document.querySelector('.container'), (data) => {
console.log(`Width: ${data.width}, Breakpoint: ${data.breakpoint}`);
// Apply responsive behavior
if (data.breakpoint === 'mobile') {
data.element.classList.add('mobile-layout');
} else {
data.element.classList.remove('mobile-layout');
}
});
10-15. Additional APIs
Due to length constraints, here are brief overviews of the remaining APIs:
10. Broadcast Channel API
Cross-tab communication for syncing state across browser tabs.
const channel = new BroadcastChannel('app-channel');
channel.postMessage({ type: 'logout' });
channel.onmessage = (e) => console.log(e.data);
11. WebGL API
Hardware-accelerated 3D graphics in the browser.
12. Payment Request API
Streamlined payment processing with native UI.
13. Credential Management API
Simplified authentication with password and biometric support.
14. Battery Status API
Monitor device battery level and charging status.
navigator.getBattery().then(battery => {
console.log(`Battery: ${battery.level * 100}%`);
});
15. Screen Wake Lock API
Prevent screen from dimming or locking.
const wakeLock = await navigator.wakeLock.request('screen');
API Comparison Matrix
| API | Complexity | Browser Support | Use Case | Performance Impact |
|---|---|---|---|---|
| IndexedDB | High | β Excellent | Large data storage | Low |
| Web Workers | Medium | β Excellent | Heavy computation | Positive (offloads main thread) |
| WebRTC | High | β Good | Real-time communication | High (bandwidth dependent) |
| Intersection Observer | Low | β Excellent | Lazy loading, scroll effects | Very Low |
| Service Worker | High | β Good | Offline functionality | Positive (caching) |
| WebSocket | Medium | β Excellent | Real-time updates | Low |
| Resize Observer | Low | β Good | Responsive layouts | Very Low |
| Web Speech | Medium | β οΈ Moderate | Voice interfaces | Medium |
Learning Roadmap
Phase 1: Foundation (Weeks 1-2)
- Storage API (localStorage/sessionStorage)
- Fetch API
- DOM Manipulation
- Event Handling
Phase 2: Intermediate (Weeks 3-6)
- Intersection Observer API
- Clipboard API
- Geolocation API
- Canvas API
- History API
Phase 3: Advanced (Weeks 7-12)
- IndexedDB API
- Web Workers API
- Service Workers API
- WebSocket API
- Resize Observer API
Phase 4: Expert (Weeks 13+)
- WebRTC API
- WebGL API
- Web Speech API
- Payment Request API
- Credential Management API
Best Practices
1. Feature Detection
Always check for API support before using:
if ('serviceWorker' in navigator) {
// Use Service Worker
} else {
// Provide fallback
}
2. Progressive Enhancement
Build core functionality first, then enhance:
// Basic functionality
function saveData(data) {
localStorage.setItem('data', JSON.stringify(data));
}
// Enhanced with IndexedDB
if ('indexedDB' in window) {
async function saveData(data) {
await db.add('data', data);
}
}
3. Error Handling
Always handle errors gracefully:
try {
await navigator.clipboard.writeText(text);
} catch (error) {
// Fallback to document.execCommand
document.execCommand('copy');
}
4. Performance Optimization
- Use Web Workers for heavy computation
- Implement lazy loading with Intersection Observer
- Cache resources with Service Workers
- Throttle/debounce expensive operations
5. Security Considerations
- Validate all user input
- Use HTTPS for sensitive APIs (WebRTC, Geolocation, etc.)
- Implement CSP headers
- Sanitize data before storage
Real-World Project Ideas
1. Advanced PWA E-commerce Platform
APIs Used: Service Worker, IndexedDB, Payment Request, Push Notifications
Features: Offline shopping cart, order tracking, payment processing
2. Video Conferencing Application
APIs Used: WebRTC, Web Audio, Screen Wake Lock, WebSocket
Features: Multi-party video calls, screen sharing, real-time chat
3. Voice-Controlled Task Manager
APIs Used: Web Speech, IndexedDB, Web Notifications, Service Worker
Features: Voice commands, offline sync, smart reminders
4. Real-Time Collaborative Editor
APIs Used: WebSocket, IndexedDB, Broadcast Channel, Service Worker
Features: Multi-user editing, conflict resolution, offline editing
5. 3D Product Visualizer
APIs Used: WebGL, Web Workers, IndexedDB, Intersection Observer
Features: 360Β° product views, AR preview, lazy loading
Resources
Official Documentation
Tools & Libraries
- Workbox: Service Worker library by Google
- Comlink: Makes Web Workers easier
- Socket.io: WebSocket library
- Three.js: WebGL framework
- Dexie.js: IndexedDB wrapper
Browser Support
Learning Platforms
- web.dev by Google
- JavaScript.info
- FreeCodeCamp
Conclusion
Mastering these 15 Web APIs will transform your development capabilities. From building offline-first applications with Service Workers to creating real-time collaborative experiences with WebRTC and WebSocket, these APIs provide the foundation for modern web applications.
Remember:
- Start with the basics and gradually progress
- Always check browser compatibility
- Implement progressive enhancement
- Focus on user experience
- Keep learning and experimenting
The web platform continues to evolve, and staying current with these APIs ensures you can build cutting-edge applications that provide exceptional user experiences.
Discussion
What APIs are you most excited to implement? Have you built anything interesting with these APIs? Share your experiences in the comments!
Tags: #webdev #javascript #api #programming #webdevelopment #pwa #webrtc #indexeddb #serviceworker #advancedweb
Author: Nishant Gaurav
Last Updated: February 2, 2026
Version: 1.0
Top comments (0)