JavaScript Network Information API Complete Guide

Master network status detection: connection type identification, bandwidth estimation, adaptive loading, and offline handling strategies

JavaScript Network Information API Complete Guide

The Network Information API allows web pages to access device network connection information. This article covers its usage and practical applications.

Basic Concepts

Detecting Support

// Check API support
if ('connection' in navigator) {
  console.log('Network Information API is supported');
  const connection = navigator.connection;
  console.log(connection);
} else {
  console.log('Network Information API is not supported');
}

// Compatibility handling
const connection = navigator.connection || 
                   navigator.mozConnection || 
                   navigator.webkitConnection;

Connection Properties

const connection = navigator.connection;

if (connection) {
  // Effective connection type: slow-2g, 2g, 3g, 4g
  console.log('Effective type:', connection.effectiveType);
  
  // Actual connection type: bluetooth, cellular, ethernet, wifi, wimax, other, unknown
  console.log('Connection type:', connection.type);
  
  // Downlink bandwidth estimate (Mbps)
  console.log('Downlink:', connection.downlink, 'Mbps');
  
  // Round-trip time estimate (milliseconds)
  console.log('RTT:', connection.rtt, 'ms');
  
  // Whether data saver mode is enabled
  console.log('Save data:', connection.saveData);
  
  // Maximum downlink speed (Mbps)
  console.log('Max downlink:', connection.downlinkMax, 'Mbps');
}

Listening to Changes

const connection = navigator.connection;

if (connection) {
  connection.addEventListener('change', () => {
    console.log('Network status changed:');
    console.log('  Type:', connection.effectiveType);
    console.log('  Bandwidth:', connection.downlink, 'Mbps');
    console.log('  RTT:', connection.rtt, 'ms');
  });
}

Network Manager

Wrapper Class

class NetworkManager {
  constructor() {
    this.connection = navigator.connection || 
                      navigator.mozConnection || 
                      navigator.webkitConnection;
    this.listeners = new Set();
    this.isOnline = navigator.onLine;
    
    this.init();
  }
  
  init() {
    // Listen for network status changes
    if (this.connection) {
      this.connection.addEventListener('change', () => {
        this.notify('connectionChange', this.getStatus());
      });
    }
    
    // Listen for online/offline status
    window.addEventListener('online', () => {
      this.isOnline = true;
      this.notify('online', this.getStatus());
    });
    
    window.addEventListener('offline', () => {
      this.isOnline = false;
      this.notify('offline', this.getStatus());
    });
  }
  
  get isSupported() {
    return !!this.connection;
  }
  
  getStatus() {
    if (!this.connection) {
      return {
        isOnline: this.isOnline,
        effectiveType: 'unknown',
        type: 'unknown',
        downlink: null,
        rtt: null,
        saveData: false
      };
    }
    
    return {
      isOnline: this.isOnline,
      effectiveType: this.connection.effectiveType,
      type: this.connection.type,
      downlink: this.connection.downlink,
      rtt: this.connection.rtt,
      saveData: this.connection.saveData,
      downlinkMax: this.connection.downlinkMax
    };
  }
  
  // Check if connection is fast
  isFastConnection() {
    if (!this.connection) return true; // Assume fast by default
    
    const { effectiveType, downlink } = this.connection;
    return effectiveType === '4g' && downlink >= 5;
  }
  
  // Check if connection is slow
  isSlowConnection() {
    if (!this.connection) return false;
    
    const { effectiveType } = this.connection;
    return effectiveType === 'slow-2g' || effectiveType === '2g';
  }
  
  // Check if data should be saved
  shouldSaveData() {
    if (!this.connection) return false;
    return this.connection.saveData || this.isSlowConnection();
  }
  
  onChange(callback) {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  }
  
  notify(event, data) {
    this.listeners.forEach(callback => callback(event, data));
  }
}

// Usage
const network = new NetworkManager();

console.log('Current network status:', network.getStatus());

network.onChange((event, status) => {
  console.log('Network event:', event);
  console.log('Status:', status);
});

Connection Quality Assessment

class ConnectionQuality {
  constructor() {
    this.network = new NetworkManager();
  }
  
  // Get quality level: excellent, good, fair, poor
  getQuality() {
    const status = this.network.getStatus();
    
    if (!status.isOnline) return 'offline';
    
    const { effectiveType, downlink, rtt } = status;
    
    // Evaluate based on multiple factors
    if (effectiveType === '4g' && downlink >= 10 && rtt < 50) {
      return 'excellent';
    }
    
    if (effectiveType === '4g' && downlink >= 5) {
      return 'good';
    }
    
    if (effectiveType === '3g' || (effectiveType === '4g' && downlink < 5)) {
      return 'fair';
    }
    
    return 'poor';
  }
  
  // Get recommended media quality
  getRecommendedMediaQuality() {
    const quality = this.getQuality();
    
    switch (quality) {
      case 'excellent':
        return { video: '1080p', image: 'high', prefetch: true };
      case 'good':
        return { video: '720p', image: 'medium', prefetch: true };
      case 'fair':
        return { video: '480p', image: 'low', prefetch: false };
      case 'poor':
        return { video: '360p', image: 'thumbnail', prefetch: false };
      default:
        return { video: 'none', image: 'placeholder', prefetch: false };
    }
  }
  
  // Get recommended request strategy
  getRequestStrategy() {
    const quality = this.getQuality();
    
    switch (quality) {
      case 'excellent':
        return {
          timeout: 5000,
          retries: 2,
          batchSize: 10,
          concurrent: 6
        };
      case 'good':
        return {
          timeout: 10000,
          retries: 3,
          batchSize: 5,
          concurrent: 4
        };
      case 'fair':
        return {
          timeout: 15000,
          retries: 4,
          batchSize: 3,
          concurrent: 2
        };
      case 'poor':
        return {
          timeout: 30000,
          retries: 5,
          batchSize: 1,
          concurrent: 1
        };
      default:
        return {
          timeout: 60000,
          retries: 10,
          batchSize: 1,
          concurrent: 1
        };
    }
  }
}

// Usage
const connectionQuality = new ConnectionQuality();

console.log('Connection quality:', connectionQuality.getQuality());
console.log('Recommended media quality:', connectionQuality.getRecommendedMediaQuality());
console.log('Request strategy:', connectionQuality.getRequestStrategy());

Practical Applications

Adaptive Image Loading

class AdaptiveImageLoader {
  constructor() {
    this.network = new NetworkManager();
  }
  
  // Get optimal image URL
  getOptimalImageUrl(baseUrl, sizes = {}) {
    const status = this.network.getStatus();
    
    // Default size configuration
    const defaultSizes = {
      high: 'large',
      medium: 'medium',
      low: 'small',
      thumbnail: 'thumb'
    };
    
    const sizeSuffixes = { ...defaultSizes, ...sizes };
    
    // Choose based on network conditions
    if (!status.isOnline) {
      return null;
    }
    
    if (this.network.shouldSaveData()) {
      return this.buildUrl(baseUrl, sizeSuffixes.thumbnail);
    }
    
    switch (status.effectiveType) {
      case '4g':
        return this.buildUrl(baseUrl, sizeSuffixes.high);
      case '3g':
        return this.buildUrl(baseUrl, sizeSuffixes.medium);
      case '2g':
        return this.buildUrl(baseUrl, sizeSuffixes.low);
      default:
        return this.buildUrl(baseUrl, sizeSuffixes.thumbnail);
    }
  }
  
  buildUrl(baseUrl, size) {
    // Assume URL format: /images/photo.jpg -> /images/photo-large.jpg
    const ext = baseUrl.substring(baseUrl.lastIndexOf('.'));
    const name = baseUrl.substring(0, baseUrl.lastIndexOf('.'));
    return `${name}-${size}${ext}`;
  }
  
  // Lazy load image
  async loadImage(img, baseUrl) {
    const optimalUrl = this.getOptimalImageUrl(baseUrl);
    
    if (!optimalUrl) {
      img.src = '/images/placeholder.jpg';
      return;
    }
    
    return new Promise((resolve, reject) => {
      const tempImg = new Image();
      
      tempImg.onload = () => {
        img.src = optimalUrl;
        resolve(optimalUrl);
      };
      
      tempImg.onerror = () => {
        img.src = '/images/placeholder.jpg';
        reject(new Error('Failed to load image'));
      };
      
      tempImg.src = optimalUrl;
    });
  }
  
  // Process all images on page
  async processImages(selector = 'img[data-src]') {
    const images = document.querySelectorAll(selector);
    
    for (const img of images) {
      const baseUrl = img.dataset.src;
      if (baseUrl) {
        await this.loadImage(img, baseUrl);
      }
    }
  }
}

// Usage
const imageLoader = new AdaptiveImageLoader();

// Process all lazy-load images
imageLoader.processImages();

// Load single image
const img = document.getElementById('hero-image');
imageLoader.loadImage(img, '/images/hero.jpg');

Adaptive Video Quality

class AdaptiveVideoPlayer {
  constructor(videoElement) {
    this.video = videoElement;
    this.network = new NetworkManager();
    this.currentQuality = null;
    
    this.qualities = {
      '1080p': { width: 1920, bitrate: 5000 },
      '720p': { width: 1280, bitrate: 2500 },
      '480p': { width: 854, bitrate: 1000 },
      '360p': { width: 640, bitrate: 500 },
      '240p': { width: 426, bitrate: 250 }
    };
    
    this.init();
  }
  
  init() {
    this.network.onChange((event, status) => {
      if (event === 'connectionChange') {
        this.adjustQuality();
      }
    });
    
    // Initial quality adjustment
    this.adjustQuality();
  }
  
  adjustQuality() {
    const status = this.network.getStatus();
    const recommendedQuality = this.getRecommendedQuality(status);
    
    if (recommendedQuality !== this.currentQuality) {
      this.setQuality(recommendedQuality);
    }
  }
  
  getRecommendedQuality(status) {
    if (!status.isOnline) return '240p';
    
    const { downlink, effectiveType, saveData } = status;
    
    if (saveData) return '360p';
    
    // Choose based on bandwidth
    if (downlink >= 10 && effectiveType === '4g') {
      return '1080p';
    } else if (downlink >= 5) {
      return '720p';
    } else if (downlink >= 2) {
      return '480p';
    } else if (downlink >= 1) {
      return '360p';
    } else {
      return '240p';
    }
  }
  
  setQuality(quality) {
    const currentTime = this.video.currentTime;
    const wasPlaying = !this.video.paused;
    
    // Update video source
    const qualityConfig = this.qualities[quality];
    const newSrc = this.buildVideoUrl(quality);
    
    this.video.src = newSrc;
    this.video.currentTime = currentTime;
    this.currentQuality = quality;
    
    if (wasPlaying) {
      this.video.play();
    }
    
    this.onQualityChange(quality);
  }
  
  buildVideoUrl(quality) {
    const basePath = this.video.dataset.basePath || '';
    return `${basePath}/video-${quality}.mp4`;
  }
  
  onQualityChange(quality) {
    console.log('Video quality changed to:', quality);
    
    // Update UI
    const indicator = document.getElementById('quality-indicator');
    if (indicator) {
      indicator.textContent = quality;
    }
  }
  
  // Manually set quality
  forceQuality(quality) {
    if (this.qualities[quality]) {
      this.setQuality(quality);
    }
  }
  
  // Get current quality
  getCurrentQuality() {
    return this.currentQuality;
  }
}

// Usage
const video = document.getElementById('main-video');
const player = new AdaptiveVideoPlayer(video);

Smart Prefetching

class SmartPrefetcher {
  constructor() {
    this.network = new NetworkManager();
    this.prefetchedUrls = new Set();
  }
  
  // Check if prefetching should occur
  shouldPrefetch() {
    const status = this.network.getStatus();
    
    if (!status.isOnline) return false;
    if (status.saveData) return false;
    if (this.network.isSlowConnection()) return false;
    
    return true;
  }
  
  // Prefetch resource
  async prefetch(url) {
    if (!this.shouldPrefetch()) return false;
    if (this.prefetchedUrls.has(url)) return true;
    
    try {
      const link = document.createElement('link');
      link.rel = 'prefetch';
      link.href = url;
      document.head.appendChild(link);
      
      this.prefetchedUrls.add(url);
      return true;
    } catch (error) {
      console.error('Prefetch failed:', url, error);
      return false;
    }
  }
  
  // Preconnect to origin
  preconnect(origin) {
    if (!this.shouldPrefetch()) return;
    
    const link = document.createElement('link');
    link.rel = 'preconnect';
    link.href = origin;
    document.head.appendChild(link);
  }
  
  // Prefetch multiple resources
  async prefetchMultiple(urls) {
    if (!this.shouldPrefetch()) return [];
    
    const status = this.network.getStatus();
    
    // Limit concurrency based on network
    const concurrentLimit = status.effectiveType === '4g' ? 5 : 2;
    const results = [];
    
    for (let i = 0; i < urls.length; i += concurrentLimit) {
      const batch = urls.slice(i, i + concurrentLimit);
      const batchResults = await Promise.all(
        batch.map(url => this.prefetch(url))
      );
      results.push(...batchResults);
    }
    
    return results;
  }
  
  // Smart prefetch next page
  prefetchNextPage(links) {
    if (!this.shouldPrefetch()) return;
    
    // Decide prefetch count based on network quality
    const status = this.network.getStatus();
    let count = 1;
    
    if (status.effectiveType === '4g' && status.downlink >= 10) {
      count = 3;
    } else if (status.effectiveType === '4g') {
      count = 2;
    }
    
    const urlsToFetch = links.slice(0, count);
    this.prefetchMultiple(urlsToFetch);
  }
}

// Usage
const prefetcher = new SmartPrefetcher();

// Prefetch resource
prefetcher.prefetch('/api/data.json');

// Prefetch multiple resources
prefetcher.prefetchMultiple([
  '/images/hero.jpg',
  '/js/page-2.js',
  '/css/page-2.css'
]);

// Prefetch on hover
document.querySelectorAll('a[data-prefetch]').forEach(link => {
  link.addEventListener('mouseenter', () => {
    prefetcher.prefetch(link.href);
  });
});

Offline-Aware Application

class OfflineAwareApp {
  constructor() {
    this.network = new NetworkManager();
    this.pendingActions = [];
    this.isOffline = !navigator.onLine;
    
    this.init();
  }
  
  init() {
    this.network.onChange((event, status) => {
      if (event === 'online') {
        this.handleOnline();
      } else if (event === 'offline') {
        this.handleOffline();
      }
    });
  }
  
  handleOnline() {
    this.isOffline = false;
    console.log('Network restored, processing pending actions...');
    
    this.showNotification('Network restored', 'success');
    this.syncPendingActions();
  }
  
  handleOffline() {
    this.isOffline = true;
    console.log('Network disconnected');
    
    this.showNotification('Network disconnected, actions will sync when restored', 'warning');
  }
  
  // Perform network-required action
  async performAction(action, data) {
    if (this.isOffline) {
      // Queue action when offline
      this.queueAction(action, data);
      return { queued: true, message: 'Action saved, will execute when online' };
    }
    
    try {
      return await this.executeAction(action, data);
    } catch (error) {
      if (this.isNetworkError(error)) {
        this.queueAction(action, data);
        return { queued: true, message: 'Network error, action saved' };
      }
      throw error;
    }
  }
  
  queueAction(action, data) {
    const queuedAction = {
      id: Date.now(),
      action,
      data,
      timestamp: new Date().toISOString()
    };
    
    this.pendingActions.push(queuedAction);
    this.savePendingActions();
    
    return queuedAction;
  }
  
  async syncPendingActions() {
    const actions = [...this.pendingActions];
    this.pendingActions = [];
    
    for (const item of actions) {
      try {
        await this.executeAction(item.action, item.data);
        console.log('Sync successful:', item.action);
      } catch (error) {
        console.error('Sync failed:', item.action, error);
        this.pendingActions.push(item);
      }
    }
    
    this.savePendingActions();
  }
  
  async executeAction(action, data) {
    // Actual API call
    const response = await fetch(`/api/${action}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    return response.json();
  }
  
  isNetworkError(error) {
    return error.name === 'TypeError' && error.message === 'Failed to fetch';
  }
  
  savePendingActions() {
    localStorage.setItem('pendingActions', JSON.stringify(this.pendingActions));
  }
  
  loadPendingActions() {
    try {
      const saved = localStorage.getItem('pendingActions');
      this.pendingActions = saved ? JSON.parse(saved) : [];
    } catch {
      this.pendingActions = [];
    }
  }
  
  showNotification(message, type) {
    // Display notification UI
    console.log(`[${type}] ${message}`);
  }
  
  getPendingCount() {
    return this.pendingActions.length;
  }
}

// Usage
const app = new OfflineAwareApp();
app.loadPendingActions();

// Perform action
document.getElementById('save-btn').addEventListener('click', async () => {
  const result = await app.performAction('saveData', { content: 'test' });
  console.log('Result:', result);
});

Best Practices Summary

Network Information API Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Adaptive Loading                                  │
│   ├── Adjust media quality based on connection     │
│   ├── Use placeholders on slow networks            │
│   ├── Respect user's data saver preferences        │
│   └── Dynamically adjust prefetch strategy         │
│                                                     │
│   Offline Handling                                  │
│   ├── Listen for online/offline events             │
│   ├── Cache data with local storage                │
│   ├── Implement action queue and sync              │
│   └── Provide clear status feedback                │
│                                                     │
│   Performance Optimization                          │
│   ├── Avoid frequent network status checks         │
│   ├── Use event listeners instead of polling       │
│   ├── Set appropriate timeout and retry strategies │
│   └── Consider API compatibility                   │
│                                                     │
└─────────────────────────────────────────────────────┘
Connection TypeTypical BandwidthRecommended Strategy
4g>10 MbpsHigh quality media, aggressive prefetch
3g1-10 MbpsMedium quality, moderate prefetch
2g50-250 KbpsLow quality, disable prefetch
slow-2g<50 KbpsMinimum quality, text-first

Master the Network Information API to provide optimal network experience for users.