# JavaScript Web APIs — Deep Dive

> Comprehensive research on browser APIs beyond vanilla JavaScript.

---

## 1. Fetch API

### Beyond `fetch()`

```javascript
// Basic usage with error handling
async function jsonFetch(url, options = {}) {
  const response = await fetch(url, {
    headers: { 'Accept': 'application/json' },
    ...options,
  });

  if (!response.ok) {
    throw new HttpError(response.status, await response.text());
  }

  return response.json();
}

// Request with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const data = await fetch('/api/data', {
    signal: controller.signal,
  }).then(r => r.json());
} finally {
  clearTimeout(timeoutId);
}
```

### Request Cloning

```javascript
// You can only read a response body once
const response = await fetch('/api/data');

// Clone to read multiple times
const clone = response.clone();
const text = await response.text();
const json = await clone.json();  // Works on clone
```

### Request/Response Objects

```javascript
// Create request manually
const request = new Request('/api/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' }),
});

// Modify a request
const modified = new Request(request, {
  headers: { ...request.headers, 'Authorization': 'Bearer token' },
});

// Create response
const response = new Response(JSON.stringify({ ok: true }), {
  status: 200,
  headers: { 'Content-Type': 'application/json' },
});
```

---

## 2. Streams API

### Readable Streams

```javascript
// Read file as stream
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
const stream = file.stream();  // ReadableStream

const reader = stream.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log('Chunk:', value);  // Uint8Array
}
```

### Transform Streams

```javascript
// TextDecoder transform
const decoder = new TextDecoderStream();
const reader = file.stream().pipeThrough(decoder).getReader();

// Custom transform — uppercase text
const upperCase = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toString().toUpperCase());
  },
});

// Compression
const compressed = await compressStream(response.body, 'deflate');

// Pipeline
const result = await fetch('/data.json')
  .then(res => res.body)
  .then(body => body.pipeThrough(new TextDecoderStream()))
  .then(stream => stream.pipeTo(destination));
```

### Writing Streams

```javascript
// Write to a blob
const writable = new WritableStream({
  write(chunk) {
    // Process each chunk
  },
  close() {
    console.log('Stream complete');
  },
  abort(err) {
    console.error('Stream aborted:', err);
  },
});

const writer = writable.getWriter();
writer.write('Hello');
writer.write(' World');
writer.close();
```

---

## 3. Service Workers

### Lifecycle

```
Register → Install → Activate → Control pages → Handle fetch events
```

### Basic Service Worker

```javascript
// sw.js
const CACHE_NAME = 'app-v1';
const ASSETS = ['/', '/index.html', '/styles.css', '/app.js'];

// Install — cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
  );
  self.skipWaiting();  // Activate immediately
});

// Activate — clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      )
    )
  );
  self.clients.claim();  // Claim all pages
});

// Fetch — cache-first with network fallback
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      if (cached) return cached;
      
      return fetch(event.request).then(response => {
        // Don't cache non-successful responses
        if (!response.ok) return response;
        
        const clone = response.clone();
        caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
        return response;
      });
    })
  );
});
```

### Registering

```javascript
// main.js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then(reg => console.log('SW registered:', reg.scope))
    .catch(err => console.error('SW failed:', err));
}
```

### Push Notifications

```javascript
// Request permission
Notification.requestPermission().then(permission => {
  if (permission === 'granted') {
    // Subscribe
    navigator.serviceWorker.ready.then(reg => {
      reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(VAPID_KEY),
      });
    });
  }
});

// Handle push event in service worker
self.addEventListener('push', (event) => {
  const data = event.data?.json() || { title: 'Notification', body: 'Hello' };
  
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icon.png',
      badge: '/badge.png',
      actions: [
        { action: 'view', title: 'View' },
        { action: 'dismiss', title: 'Dismiss' },
      ],
    })
  );
});
```

---

## 4. WebSockets

### Real-Time Communication

```javascript
const ws = new WebSocket('wss://example.com/socket');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
  // Auto-reconnect
  if (event.code !== 1000) {
    setTimeout(() => connect(), 1000);
  }
};
```

### Reconnection with Exponential Backoff

```javascript
class WebSocketClient {
  constructor(url, maxRetries = 5) {
    this.url = url;
    this.maxRetries = maxRetries;
    this.retryCount = 0;
    this.listeners = new Map();
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('Connected');
      this.retryCount = 0;
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.listeners.get(message.type)?.forEach(fn => fn(message.data));
    };

    this.ws.onclose = () => {
      if (this.retryCount < this.maxRetries) {
        const delay = Math.min(1000 * 2 ** this.retryCount, 30000);
        this.retryCount++;
        setTimeout(() => this.connect(), delay);
      }
    };
  }

  on(type, fn) {
    if (!this.listeners.has(type)) this.listeners.set(type, []);
    this.listeners.get(type).push(fn);
    return () => this.off(type, fn);
  }

  off(type, fn) {
    const fns = this.listeners.get(type);
    this.listeners.set(type, fns.filter(f => f !== fn));
  }

  send(type, data) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type, data }));
    }
  }
}

const client = new WebSocketClient('wss://example.com/socket');
client.on('message', console.log);
client.send('ping', { time: Date.now() });
```

---

## 5. Web Workers

### Dedicated Worker

```javascript
// main.js
const worker = new Worker('./math-worker.js');
worker.postMessage({ operation: 'fibonacci', n: 40 });
worker.onmessage = (e) => console.log('Result:', e.data);
worker.onerror = (e) => console.error('Worker error:', e.message);

// math-worker.js
self.onmessage = (e) => {
  const { operation, n } = e.data;
  
  switch (operation) {
    case 'fibonacci':
      self.postMessage(fibonacci(n));
      break;
    case 'prime-sieve':
      self.postMessage(sieveOfEratosthenes(n));
      break;
  }
};

function fibonacci(n) {
  let a = 0, b = 1;
  for (let i = 2; i <= n; i++) {
    [a, b] = [b, a + b];
  }
  return b;
}
```

### Shared Worker

```javascript
// Multiple tabs share a single worker
const worker = new SharedWorker('./shared.js');

worker.port.start();
worker.port.postMessage({ type: 'greet', name: 'Alice' });
worker.port.onmessage = (e) => console.log(e.data);

// shared.js
self.onconnect = (e) => {
  const port = e.ports[0];
  port.onmessage = (event) => {
    if (event.data.type === 'greet') {
      port.postMessage(`Hello, ${event.data.name}!`);
    }
  };
};
```

### Web Worker with `import()` (Module Workers)

```javascript
const worker = new Worker('./worker.mjs', { type: 'module' });

// worker.mjs
import { process } from './lib.js';
self.onmessage = (e) => {
  const result = process(e.data);
  self.postMessage(result);
};
```

---

## 6. Storage APIs

### localStorage / sessionStorage

```javascript
// Typed storage wrapper
const Storage = {
  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  },
  get(key) {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : null;
  },
  remove(key) {
    localStorage.removeItem(key);
  },
  clear() {
    localStorage.clear();
  },
};

Storage.set('user', { name: 'Alice', role: 'admin' });
Storage.get('user');  // { name: 'Alice', role: 'admin' }
```

### IndexedDB

```javascript
class Database {
  constructor(name, version) {
    this.name = name;
    this.version = version;
  }

  open() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.name, this.version);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // Create object stores
        if (!db.objectStoreNames.contains('users')) {
          const store = db.createObjectStore('users', { keyPath: 'id' });
          store.createIndex('email', 'email', { unique: true });
          store.createIndex('name', 'name');
        }
      };
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async get(storeName, key) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, 'readonly');
      const store = tx.objectStore(storeName);
      const req = store.get(key);
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }

  async put(storeName, data) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, 'readwrite');
      tx.objectStore(storeName).put(data);
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  }

  async getAll(storeName) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, 'readonly');
      const req = tx.objectStore(storeName).getAll();
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }
}

// Usage
const db = new Database('MyApp', 1);
await db.put('users', { id: 1, name: 'Alice', email: 'a@example.com' });
const user = await db.get('users', 1);
```

### Cache API

```javascript
// For service workers, but also accessible in main thread
const cache = await caches.open('my-cache');
await cache.put(new Request('/api/data'), response);

// Read from cache
const cached = await cache.match('/api/data');
if (cached) {
  const data = await cached.json();
}

// Delete from cache
await cache.delete('/api/data');

// List all cache names
const names = await caches.keys();  // ['my-cache', 'app-v1']
```

---

## 7. Media APIs

### MediaDevices (Camera & Microphone)

```javascript
// Get user media
const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1280, height: 720, facingMode: 'user' },
  audio: true,
});

// Display in <video> element
const video = document.getElementById('video');
video.srcObject = stream;
video.play();

// Capture photo from video
function takePhoto() {
  const canvas = document.createElement('canvas');
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(video, 0, 0);
  canvas.toBlob(blob => {
    const url = URL.createObjectURL(blob);
    // Download or display
  });
}

// List available devices
const devices = await navigator.mediaDevices.enumerateDevices();
devices.forEach(device => {
  console.log(`${device.kind}: ${device.label || '(no label)'}`);
});
```

### MediaRecorder

```javascript
// Record audio/video
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });

const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
  const blob = new Blob(chunks, { type: 'video/webm' });
  const url = URL.createObjectURL(blob);
  console.log('Recording URL:', url);
};

recorder.start();
// ... later ...
recorder.stop();
```

### AudioContext (Web Audio API)

```javascript
// Create audio context
const ctx = new AudioContext();

// Analyze audio frequency
const analyser = ctx.createAnalyser();
analyser.fftSize = 256;

const source = ctx.createMediaStreamSource(stream);
source.connect(analyser);

// Get frequency data
const bufferLength = analyser.frequencyBinCount;
const data = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(data);

// Visualize
const bars = document.querySelectorAll('.bar');
data.forEach((value, i) => {
  if (bars[i]) {
    bars[i].style.height = `${value / 255 * 100}%`;
  }
});
```

### Media Source Extensions (MSE)

```javascript
// Dynamic video playback
const mediaSource = new MediaSource();
const video = document.getElementById('video');
video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
  
  // Fetch and append video chunks
  fetch('/video.mp4').then(res => res.arrayBuffer()).then(buffer => {
    sourceBuffer.appendBuffer(buffer);
  });
});
```

---

## 8. Performance APIs

### Performance Observer

```javascript
// Observe Largest Contentful Paint
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log(`LCP: ${entry.startTime}ms`);
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });

// Observe Cumulative Layout Shift
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log(`CLS: ${entry.value}`);
    }
  }
}).observe({ type: 'layout-shift', buffered: true });

// Observe First Input Delay
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log(`FID: ${entry.processingStart - entry.startTime}ms`);
  }
}).observe({ type: 'first-input', buffered: true });
```

### User Timing API

```javascript
// Custom performance marks
performance.mark('before-render');
renderComponent();
performance.mark('after-render');

performance.measure('render-time', 'before-render', 'after-render');

const measures = performance.getEntriesByName('render-time');
console.log(`Render took: ${measures[0].duration}ms`);

// Clean up
performance.clearMarks();
performance.clearMeasures();
```

---

## 9. File APIs

### File System Access API

```javascript
// Open file
let fileHandle;
const [handle] = await window.showOpenFilePicker({
  types: [{ description: 'Images', accept: { 'image/*': ['.png', '.jpg'] } }],
  multiple: false,
});
fileHandle = handle;

// Read file
const file = await fileHandle.getFile();
const text = await file.text();
const arrayBuffer = await file.arrayBuffer();

// Write file (save)
const writable = await fileHandle.createWritable();
await writable.write('New content');
await writable.close();

// Show save dialog
const newHandle = await window.showSaveFilePicker({
  suggestedName: 'document.txt',
  types: [{ description: 'Text', accept: { 'text/plain': ['.txt'] } }],
});
```

### Drag and Drop

```javascript
const dropzone = document.getElementById('dropzone');

dropzone.addEventListener('dragover', (e) => {
  e.preventDefault();
  dropzone.classList.add('drag-over');
});

dropzone.addEventListener('dragleave', () => {
  dropzone.classList.remove('drag-over');
});

dropzone.addEventListener('drop', async (e) => {
  e.preventDefault();
  dropzone.classList.remove('drag-over');
  
  for (const file of e.dataTransfer.files) {
    console.log('Dropped:', file.name, file.type, file.size);
  }
});

// Data transfer
const draggable = document.querySelector('.draggable');
draggable.addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', draggable.dataset.id);
  e.dataTransfer.effectAllowed = 'move';
});
```

---

## 10. Navigation API (Experimental)

```javascript
// Intercept navigations
navigator.navigation.addEventListener('navigate', (event) => {
  console.log('Navigating to:', event.destination.url);
  
  // Optionally handle with SPA routing
  event.intercept({
    handler: () => {
      // Render new content
      return Response.json({ content: '<h1>New Page</h1>' });
    },
  });
});

// Track current entry
const entry = navigator.navigation.currentEntry;
console.log('URL:', entry.key, entry.url);
```

---

## 11. Credentials API

```javascript
// Get saved credentials
const credentials = await navigator.credentials.get({ password: true });
if (credentials) {
  console.log('User:', credentials.username, credentials.password);
}

// Store credentials
await navigator.credentials.store(new PasswordCredential({
  id: 'alice@example.com',
  password: 'secret123',
}));

// Prevent auto-fill
await navigator.credentials.preventSilentAccess();
```

---

## 12. Web Crypto API

```javascript
// Generate key pair
const keyPair = await crypto.subtle.generateKey(
  { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
  true,  // extractable
  ['encrypt', 'decrypt']
);

// Encrypt
const encoded = new TextEncoder().encode('secret message');
const encrypted = await crypto.subtle.encrypt(
  { name: 'RSA-OAEP' },
  keyPair.publicKey,
  encoded
);

// Decrypt
const decrypted = await crypto.subtle.decrypt(
  { name: 'RSA-OAEP' },
  keyPair.privateKey,
  encrypted
);
console.log(new TextDecoder().decode(decrypted));  // "secret message"

// Derive key from password
const key = await crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('password'),
  'PBKDF2',
  false,
  ['deriveBits', 'deriveKey']
);

const derivedKey = await crypto.subtle.deriveKey(
  { name: 'PBKDF2', salt: randomSalt, iterations: 100000, hash: 'SHA-256' },
  key,
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);
```

---

## Quick Reference: API Selection

| Need | API |
|------|-----|
| HTTP requests | Fetch API |
| Streaming data | Streams API |
| Offline support | Service Workers + Cache API |
| Real-time updates | WebSockets |
| Background processing | Web Workers |
| Persistent storage | IndexedDB |
| Simple storage | localStorage / sessionStorage |
| Camera/Microphone | MediaDevices |
| Record media | MediaRecorder |
| Audio processing | Web Audio API |
| Performance metrics | Performance Observer |
| Timing code | User Timing API |
| File read/write | File System Access API |
| Drag and drop | Drag & Drop API |
| Navigation control | Navigation API (experimental) |
| Credential management | Credentials API |
| Encryption | Web Crypto API |
| Notifications | Push API + Notification API |
| Geolocation | Geolocation API |
| Device orientation | DeviceOrientation API |
| Battery status | Battery Status API |
| Vibration | Vibration API |
| Clipboard | Clipboard API |
| Payment | Payment Request API |
