All code blocks remain unmodified to preserve architectural intent.
API Key & Base Configuration
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.openweathermap.org/data/2.5';
async function getCurrentWeather(city) {
const url = `${BASE_URL}/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`;
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) throw new Error(`City "${city}" not found`);
if (response.status === 401) throw new Error('Invalid API key');
throw new Error(`Weather API error: ${response.status}`);
}
return response.json();
}
// Response structure:
// {
// name: "San Francisco",
// sys: { country: "US" },
// weather: [{ main: "Clear", description: "clear sky", icon: "01d" }],
// main: { temp: 72, feels_like: 70, humidity: 65, temp_min: 68, temp_max: 75 },
// wind: { speed: 12, deg: 270 },
// visibility: 10000,
// dt: 1699123456 // Unix timestamp
// }
Unit System Abstraction
const UNITS = {
imperial: { temp: 'Β°F', speed: 'mph', param: 'imperial' },
metric: { temp: 'Β°C', speed: 'm/s', param: 'metric' },
standard: { temp: 'K', speed: 'm/s', param: 'standard' }
};
async function getWeather(city, unitSystem = 'imperial') {
const { param } = UNITS[unitSystem];
const url = `${BASE_URL}/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=${param}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
return {
city: data.name,
country: data.sys.country,
temp: Math.round(data.main.temp),
feelsLike: Math.round(data.main.feels_like),
humidity: data.main.humidity,
description: data.weather[0].description,
icon: data.weather[0].icon,
windSpeed: Math.round(data.wind.speed),
unitLabel: UNITS[unitSystem].temp,
speedLabel: UNITS[unitSystem].speed,
};
}
Forecast Aggregation Logic
async function getForecast(city, days = 3) {
const url = `${BASE_URL}/forecast?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// Group by day (take the noon reading for each day)
const dailyMap = new Map();
data.list.forEach(item => {
const date = new Date(item.dt * 1000);
const dayKey = date.toLocaleDateString();
const hour = date.getHours();
// Prefer readings around noon (12-15h)
if (!dailyMap.has(dayKey) || Math.abs(hour - 13) < Math.abs(dailyMap.get(dayKey).hour - 13)) {
dailyMap.set(dayKey, {
hour,
date,
temp: Math.round(item.main.temp),
tempMin: Math.round(item.main.temp_min),
tempMax: Math.round(item.main.temp_max),
description: item.weather[0].description,
icon: item.weather[0].icon,
humidity: item.main.humidity,
});
}
});
// Skip today, return next N days
const today = new Date().toLocaleDateString();
return Array.from(dailyMap.values())
.filter(d => d.date.toLocaleDateString() !== today)
.slice(0, days);
}
Icon Resolution & Geolocation Fallback
function getIconUrl(iconCode) {
return `https://openweathermap.org/img/wn/${iconCode}@2x.png`;
}
// Or map to your own emoji/SVG for better control:
const ICON_MAP = {
'01d': 'βοΈ', // Clear sky day
'01n': 'π', // Clear sky night
'02d': 'β
', // Few clouds day
'02n': 'βοΈ', // Few clouds night
'03d': 'βοΈ', // Scattered clouds
'03n': 'βοΈ',
'04d': 'βοΈ', // Broken clouds
'04n': 'βοΈ',
'09d': 'π§οΈ', // Shower rain
'09n': 'π§οΈ',
'10d': 'π¦οΈ', // Rain day
'10n': 'π§οΈ', // Rain night
'11d': 'βοΈ', // Thunderstorm
'11n': 'βοΈ',
'13d': 'βοΈ', // Snow
'13n': 'βοΈ',
'50d': 'π«οΈ', // Mist
'50n': 'π«οΈ',
};
async function getWeatherByCoords(lat, lon) {
const url = `${BASE_URL}/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=imperial`;
const res = await fetch(url);
return res.json();
}
async function autoDetectWeather() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation not supported'));
return;
}
navigator.geolocation.getCurrentPosition(
async (position) => {
const data = await getWeatherByCoords(
position.coords.latitude,
position.coords.longitude
);
resolve(data);
},
(error) => reject(new Error(`Geolocation: ${error.message}`)),
{ timeout: 10000 }
);
});
}
Secure Storage & Rate Limiting
async function getApiKey() {
const { apiKey } = await browser.storage.local.get('apiKey');
if (!apiKey) throw new Error('No API key configured');
return apiKey;
}
async function setApiKey(key) {
await browser.storage.local.set({ apiKey: key });
}
// In your build process:
const OWM_KEY = process.env.OWM_API_KEY;
const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes
let lastFetch = 0;
async function fetchWeatherIfStale(city) {
const now = Date.now();
if (now - lastFetch < REFRESH_INTERVAL) {
return getCachedWeather();
}
lastFetch = now;
return getWeather(city);
}
Pitfall Guide
- Hardcoding API Keys in Source: Embedding keys directly in extension bundles exposes them to decompilation and forces shared quota usage across all users. Always delegate credential management to
browser.storage.local or inject via build-time environment variables.
- Ignoring Rate Limit & Cache Boundaries: The free tier enforces 60 requests/minute. Naive polling on every tab switch or window focus triggers 429 errors. Implement a stale-while-revalidate pattern with configurable
REFRESH_INTERVAL to stay within quota limits.
- Naive Forecast Interval Parsing: OWM's
/forecast endpoint returns data in 3-hour chunks. Direct mapping creates duplicate day entries and timezone misalignment. Use a Map-based aggregation strategy that prefers midday readings (12-15h) to stabilize daily min/max temperatures.
- Unmanaged Geolocation Permissions:
navigator.geolocation prompts can be denied, ignored, or timeout. Failing to catch Promise rejections or handle POSITION_UNAVAILABLE breaks the extension flow. Always provide a manual city-input fallback and set explicit timeout thresholds.
- Unit System Inconsistency: Mixing imperial, metric, and standard units across components causes UI layout shifts and calculation drift. Centralize unit mapping in a configuration object and apply consistent rounding (
Math.round) before rendering.
- Generic HTTP Error Handling: Treating all non-200 responses as identical failures obscures root causes. Implement granular status checking (401 for invalid keys, 404 for missing cities, 5xx for server issues) to enable precise user feedback and automated retry logic.
Deliverables
- π OWM Extension Integration Blueprint: A system architecture diagram detailing the data flow from geolocation fallback β API consumer β smart cache β UI renderer, including state management boundaries and error propagation paths.
- β
Pre-Launch Validation Checklist: Step-by-step verification protocol covering API key validation, rate limit stress testing (simulated 60 req/min bursts), geolocation permission denial handling, unit conversion accuracy, and cross-browser compatibility (Firefox Manifest V2/V3).
- βοΈ Configuration Templates: Ready-to-use
manifest.json permission scaffolding ("permissions": ["storage", "geolocation"]), browser.storage.local schema definitions, and build-time environment variable injection scripts for secure API key management.