DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Handling Offline Mode in Firefox Browser Extensions

Handling Offline Mode in Firefox Browser Extensions

When building extensions that depend on network data — like my Weather & Clock Dashboard — offline handling is critical. Users notice immediately when your extension shows stale data or broken UI.

The Problem

Browser extensions run at browser startup. If the user is on a train, airplane, or just has spotty WiFi, your fetch calls will fail. Without proper handling, users see errors or empty states.

Detecting Online Status

The browser gives you two ways to check connectivity:

// Passive check
if (!navigator.onLine) {
  showCachedData();
  return;
}

// Listen for changes
window.addEventListener('online', () => {
  console.log('Back online — refreshing data');
  fetchFreshData();
});

window.addEventListener('offline', () => {
  console.log('Gone offline — switching to cache');
  showOfflineIndicator();
});
Enter fullscreen mode Exit fullscreen mode

Note: navigator.onLine only tells you if you have a network connection — not if that connection actually works. You can be "online" but unable to reach your API.

A Robust Fetch Wrapper

Here's a pattern I use for all API calls in the extension:

async function fetchWithFallback(url, options = {}) {
  const CACHE_KEY = `cache_${url}`;
  const CACHE_TTL_KEY = `cache_ttl_${url}`;
  const TTL_MS = 3600000; // 1 hour

  try {
    // Attempt network fetch with timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout

    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(timeoutId);

    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const data = await response.json();

    // Cache the fresh data
    const cacheEntry = {
      data,
      timestamp: Date.now()
    };
    localStorage.setItem(CACHE_KEY, JSON.stringify(cacheEntry));

    return { data, fromCache: false };

  } catch (error) {
    console.warn(`Fetch failed for ${url}:`, error.message);

    // Try cache
    const cached = localStorage.getItem(CACHE_KEY);
    if (cached) {
      const { data, timestamp } = JSON.parse(cached);
      const age = Date.now() - timestamp;
      const ageHours = Math.round(age / 3600000);

      return {
        data,
        fromCache: true,
        cacheAge: age,
        stale: age > TTL_MS
      };
    }

    // No cache available
    throw new Error('No data available (offline and no cache)');
  }
}
Enter fullscreen mode Exit fullscreen mode

Showing Stale Data Indicators

Users prefer seeing old data over an error. But they deserve to know it's stale:

async function updateWeather() {
  const statusEl = document.getElementById('weather-status');

  try {
    const { data, fromCache, cacheAge } = await fetchWithFallback(WEATHER_API_URL);

    renderWeather(data);

    if (fromCache) {
      const hours = Math.floor(cacheAge / 3600000);
      const minutes = Math.floor((cacheAge % 3600000) / 60000);
      statusEl.textContent = hours > 0 
        ? `Last updated ${hours}h ago (offline)` 
        : `Last updated ${minutes}m ago (offline)`;
      statusEl.className = 'status-stale';
    } else {
      statusEl.textContent = `Updated just now`;
      statusEl.className = 'status-fresh';
    }

  } catch (error) {
    statusEl.textContent = 'Weather unavailable';
    statusEl.className = 'status-error';
  }
}
Enter fullscreen mode Exit fullscreen mode

Using the Service Worker Cache API

For heavier caching needs, consider the Cache API (available in extensions with the right permissions):

const CACHE_NAME = 'weather-data-v1';

async function cacheResponse(url, data) {
  const cache = await caches.open(CACHE_NAME);
  const response = new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' }
  });
  await cache.put(url, response);
}

async function getCachedResponse(url) {
  const cache = await caches.open(CACHE_NAME);
  const response = await cache.match(url);
  if (response) {
    return response.json();
  }
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Testing Offline Behavior

Chrome/Firefox DevTools let you simulate offline mode:

  1. Open DevTools → Network tab
  2. Change "No throttling" dropdown to "Offline"
  3. Reload your extension's new tab page

For automated testing:

// In Playwright tests
await context.setOffline(true);
await page.reload();

// Should show cached data
const weatherText = await page.textContent('#weather-temp');
expect(weatherText).not.toBe('');

// Should show stale indicator
const statusText = await page.textContent('#weather-status');
expect(statusText).toContain('offline');
Enter fullscreen mode Exit fullscreen mode

Background Refresh Pattern

For new tab extensions, pre-fetch data in the background script so it's ready before the tab opens:

// background.js
chrome.alarms.create('weatherRefresh', { periodInMinutes: 30 });

chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name === 'weatherRefresh') {
    try {
      const city = await getStoredCity();
      const data = await fetchWeatherData(city);
      await browser.storage.local.set({ 
        weatherCache: data,
        weatherCacheTime: Date.now()
      });
    } catch (e) {
      // Silently fail — cache stays valid
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Then in your new tab page, read from storage first:

async function loadWeather() {
  const { weatherCache, weatherCacheTime } = await browser.storage.local.get(['weatherCache', 'weatherCacheTime']);

  if (weatherCache) {
    // Show cache immediately for instant load
    renderWeather(weatherCache);
    showCacheAge(weatherCacheTime);
  }

  // Then try to refresh in the background
  fetchFreshWeather().then(data => {
    renderWeather(data);
    clearCacheIndicator();
  }).catch(() => {
    // Keep showing cached data
  });
}
Enter fullscreen mode Exit fullscreen mode

This gives users instant perceived performance even on slow connections.

The Weather & Clock Dashboard Approach

In my extension, I use localStorage for persistence with a 1-hour TTL. The offline indicator shows as a small cloud icon next to the temperature when the data is stale.

Try it out: Weather & Clock Dashboard on AMO

What's your approach to offline handling in extensions? Let me know in the comments!


Part of a series on building Firefox browser extensions.

firefox #javascript #webdev #browserextension #pwa

Top comments (0)