Files
MagicMirror/js/http_fetcher.js

349 lines
12 KiB
JavaScript
Raw Permalink Normal View History

[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
const { EventEmitter } = require("node:events");
const { fetch: undiciFetch, Agent } = require("undici");
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
const Log = require("logger");
const { getUserAgent } = require("#server_functions");
const FIFTEEN_MINUTES = 15 * 60 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000;
const MAX_SERVER_BACKOFF = 3;
const DEFAULT_TIMEOUT = 30000; // 30 seconds
/**
* Maps errorType to MagicMirror translation keys.
* This allows HTTPFetcher to provide ready-to-use translation keys,
* eliminating the need to call NodeHelper.checkFetchError().
*/
const ERROR_TYPE_TO_TRANSLATION = {
AUTH_FAILURE: "MODULE_ERROR_UNAUTHORIZED",
RATE_LIMITED: "MODULE_ERROR_RATE_LIMITED",
SERVER_ERROR: "MODULE_ERROR_SERVER_ERROR",
CLIENT_ERROR: "MODULE_ERROR_CLIENT_ERROR",
NETWORK_ERROR: "MODULE_ERROR_NO_CONNECTION",
UNKNOWN_ERROR: "MODULE_ERROR_UNSPECIFIED"
};
/**
* HTTPFetcher - Centralized HTTP fetching with intelligent error handling
*
* Features:
* - Automatic retry strategies based on HTTP status codes
* - Exponential backoff for server errors
* - Retry-After header parsing for rate limiting
* - Authentication support (Basic, Bearer)
* - Self-signed certificate support
* @augments EventEmitter
* @fires HTTPFetcher#response - When fetch succeeds with ok response
* @fires HTTPFetcher#error - When fetch fails or returns non-ok response
* @example
* const fetcher = new HTTPFetcher(url, { reloadInterval: 60000 });
* fetcher.on('response', (response) => { ... });
* fetcher.on('error', (errorInfo) => { ... });
* fetcher.startPeriodicFetch();
*/
class HTTPFetcher extends EventEmitter {
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
/**
* Calculates exponential backoff delay for retries
* @param {number} attempt - Attempt number (1-based)
* @param {object} options - Configuration options
* @param {number} [options.baseDelay] - Initial delay in ms (default: 15s)
* @param {number} [options.maxDelay] - Maximum delay in ms (default: 5min)
* @returns {number} Delay in milliseconds
* @example
* HTTPFetcher.calculateBackoffDelay(1) // 15000 (15s)
* HTTPFetcher.calculateBackoffDelay(2) // 30000 (30s)
* HTTPFetcher.calculateBackoffDelay(3) // 60000 (60s)
* HTTPFetcher.calculateBackoffDelay(6) // 300000 (5min, capped)
*/
static calculateBackoffDelay (attempt, { baseDelay = 15000, maxDelay = 300000 } = {}) {
return Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
}
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
/**
* Creates a new HTTPFetcher instance
* @param {string} url - The URL to fetch
* @param {object} options - Configuration options
* @param {number} [options.reloadInterval] - Time in ms between fetches (default: 5 min)
* @param {object} [options.auth] - Authentication options
* @param {string} [options.auth.method] - 'basic' or 'bearer'
* @param {string} [options.auth.user] - Username for basic auth
* @param {string} [options.auth.pass] - Password or token
* @param {boolean} [options.selfSignedCert] - Accept self-signed certificates
* @param {object} [options.headers] - Additional headers to send
* @param {number} [options.maxRetries] - Max retries for 5xx errors (default: 3)
* @param {number} [options.timeout] - Request timeout in ms (default: 30000)
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
* @param {string} [options.logContext] - Optional context for log messages (e.g., provider name)
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
*/
constructor (url, options = {}) {
super();
this.url = url;
this.reloadInterval = options.reloadInterval || 5 * 60 * 1000;
this.auth = options.auth || null;
this.selfSignedCert = options.selfSignedCert || false;
this.customHeaders = options.headers || {};
this.maxRetries = options.maxRetries || MAX_SERVER_BACKOFF;
this.timeout = options.timeout || DEFAULT_TIMEOUT;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
this.logContext = options.logContext ? `[${options.logContext}] ` : "";
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
this.reloadTimer = null;
this.serverErrorCount = 0;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
this.networkErrorCount = 0;
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
}
/**
* Clears any pending reload timer
*/
clearTimer () {
if (this.reloadTimer) {
clearTimeout(this.reloadTimer);
this.reloadTimer = null;
}
}
/**
* Schedules the next fetch.
* If no delay is provided, uses reloadInterval.
* If delay is provided but very short (< 1 second), clamps to reloadInterval
* to prevent hammering servers.
* @param {number} [delay] - Delay in milliseconds
*/
scheduleNextFetch (delay) {
let nextDelay = delay ?? this.reloadInterval;
// Only clamp if delay is unreasonably short (< 1 second)
// This allows respecting Retry-After headers while preventing abuse
if (nextDelay < 1000) {
nextDelay = this.reloadInterval;
}
// Don't schedule in test mode
if (process.env.mmTestMode === "true") {
return;
}
this.reloadTimer = setTimeout(() => this.fetch(), nextDelay);
}
/**
* Starts periodic fetching
*/
startPeriodicFetch () {
this.fetch();
}
/**
* Builds the options object for fetch
* @returns {object} Options object containing headers (and dispatcher if needed)
*/
getRequestOptions () {
const headers = {
"User-Agent": getUserAgent(),
...this.customHeaders
};
const options = { headers };
if (this.selfSignedCert) {
options.dispatcher = new Agent({
connect: {
rejectUnauthorized: false
}
});
}
if (this.auth) {
if (this.auth.method === "bearer") {
headers.Authorization = `Bearer ${this.auth.pass}`;
} else {
headers.Authorization = `Basic ${Buffer.from(`${this.auth.user}:${this.auth.pass}`).toString("base64")}`;
}
}
return options;
}
/**
* Parses the Retry-After header value
* @param {string} retryAfter - The Retry-After header value
* @returns {number|null} Milliseconds to wait or null if parsing failed
*/
#parseRetryAfter (retryAfter) {
// Try parsing as seconds
const seconds = Number(retryAfter);
if (!Number.isNaN(seconds) && seconds >= 0) {
return seconds * 1000;
}
// Try parsing as HTTP-date
const retryDate = Date.parse(retryAfter);
if (!Number.isNaN(retryDate)) {
return Math.max(0, retryDate - Date.now());
}
return null;
}
/**
* Determines the retry delay for a non-ok response
* @param {Response} response - The fetch Response object
* @returns {{delay: number, errorInfo: object}} Computed retry delay and error info
*/
#getDelayForResponse (response) {
const { status } = response;
let delay = this.reloadInterval;
let message;
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
let errorType = "UNKNOWN_ERROR";
if (status === 401 || status === 403) {
errorType = "AUTH_FAILURE";
delay = Math.max(this.reloadInterval * 5, THIRTY_MINUTES);
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
message = `Authentication failed (${status}). Check your API key. Waiting ${Math.round(delay / 60000)} minutes before retry.`;
Log.error(`${this.logContext}${this.url} - ${message}`);
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
} else if (status === 429) {
errorType = "RATE_LIMITED";
const retryAfter = response.headers.get("retry-after");
const parsed = retryAfter ? this.#parseRetryAfter(retryAfter) : null;
delay = parsed !== null ? Math.max(parsed, this.reloadInterval) : Math.max(this.reloadInterval * 2, FIFTEEN_MINUTES);
message = `Rate limited (429). Retrying in ${Math.round(delay / 60000)} minutes.`;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
Log.warn(`${this.logContext}${this.url} - ${message}`);
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
} else if (status >= 500) {
errorType = "SERVER_ERROR";
this.serverErrorCount = Math.min(this.serverErrorCount + 1, this.maxRetries);
delay = this.reloadInterval * Math.pow(2, this.serverErrorCount);
message = `Server error (${status}). Retry #${this.serverErrorCount} in ${Math.round(delay / 60000)} minutes.`;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
Log.error(`${this.logContext}${this.url} - ${message}`);
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
} else if (status >= 400) {
errorType = "CLIENT_ERROR";
delay = Math.max(this.reloadInterval * 2, FIFTEEN_MINUTES);
message = `Client error (${status}). Retrying in ${Math.round(delay / 60000)} minutes.`;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
Log.error(`${this.logContext}${this.url} - ${message}`);
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
} else {
message = `Unexpected HTTP status ${status}.`;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
Log.error(`${this.logContext}${this.url} - ${message}`);
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
}
return {
delay,
errorInfo: this.#createErrorInfo(message, status, errorType, delay)
};
}
/**
* Creates a standardized error info object
* @param {string} message - Error message
* @param {number|null} status - HTTP status code or null for network errors
* @param {string} errorType - Error type: AUTH_FAILURE, RATE_LIMITED, SERVER_ERROR, CLIENT_ERROR, NETWORK_ERROR
* @param {number} retryAfter - Delay until next retry in ms
* @param {Error} [originalError] - The original error if any
* @returns {object} Error info object with translationKey for direct use
*/
#createErrorInfo (message, status, errorType, retryAfter, originalError = null) {
return {
message,
status,
errorType,
translationKey: ERROR_TYPE_TO_TRANSLATION[errorType] || "MODULE_ERROR_UNSPECIFIED",
retryAfter,
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
retryCount: errorType === "NETWORK_ERROR" ? this.networkErrorCount : this.serverErrorCount,
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
url: this.url,
originalError
};
}
/**
* Performs the HTTP fetch and emits appropriate events
* @fires HTTPFetcher#response
* @fires HTTPFetcher#error
*/
async fetch () {
this.clearTimer();
let nextDelay = this.reloadInterval;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const requestOptions = this.getRequestOptions();
// Use undici.fetch when a custom dispatcher is present (e.g. selfSignedCert),
// because Node's global fetch and npm undici@8 Agents are incompatible.
// For regular requests, use globalThis.fetch so MSW and other interceptors work.
const fetchFn = requestOptions.dispatcher ? undiciFetch : globalThis.fetch;
const response = await fetchFn(this.url, {
...requestOptions,
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
signal: controller.signal
});
if (!response.ok) {
const { delay, errorInfo } = this.#getDelayForResponse(response);
nextDelay = delay;
this.emit("error", errorInfo);
} else {
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
// Reset error counts on success
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
this.serverErrorCount = 0;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
this.networkErrorCount = 0;
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
/**
* Response event - fired when fetch succeeds
* @event HTTPFetcher#response
* @type {Response}
*/
this.emit("response", response);
}
} catch (error) {
const isTimeout = error.name === "AbortError";
const message = isTimeout ? `Request timeout after ${this.timeout}ms` : `Network error: ${error.message}`;
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
// Apply exponential backoff for network errors
this.networkErrorCount = Math.min(this.networkErrorCount + 1, this.maxRetries);
const backoffDelay = HTTPFetcher.calculateBackoffDelay(this.networkErrorCount, {
maxDelay: this.reloadInterval
});
nextDelay = backoffDelay;
// Truncate URL for cleaner logs
let shortUrl = this.url;
try {
const urlObj = new URL(this.url);
shortUrl = `${urlObj.origin}${urlObj.pathname}${urlObj.search.length > 50 ? "?..." : urlObj.search}`;
} catch {
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
// If URL parsing fails, use original URL
}
// Gradual log-level escalation: WARN for first 2 attempts, ERROR after
const retryMessage = `Retry #${this.networkErrorCount} in ${Math.round(nextDelay / 1000)}s.`;
if (this.networkErrorCount <= 2) {
Log.warn(`${this.logContext}${shortUrl} - ${message} ${retryMessage}`);
} else {
Log.error(`${this.logContext}${shortUrl} - ${message} ${retryMessage}`);
}
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
const errorInfo = this.#createErrorInfo(
message,
null,
"NETWORK_ERROR",
[weather] refactor: migrate to server-side providers with centralized HTTPFetcher (#4032) This migrates the Weather module from client-side fetching to use the server-side centralized HTTPFetcher (introduced in #4016), following the same pattern as the Calendar and Newsfeed modules. ## Motivation This brings consistent error handling and better maintainability and completes the refactoring effort to centralize HTTP error handling across all default modules. Migrating to server-side providers with HTTPFetcher brings: - **Centralized error handling**: Inherits smart retry strategies (401/403, 429, 5xx backoff) and timeout handling (30s) - **Consistency**: Same architecture as Calendar and Newsfeed modules - **Security**: Possibility to hide API keys/secrets from client-side - **Performance**: Reduced API calls in multi-client setups - one server fetch instead of one per client - **Enabling possible future features**: e.g. server-side caching, rate limit monitoring, and data sharing with third-party modules ## Changes - All 10 weather providers now use HTTPFetcher for server-side fetching - Consistent error handling like Calendar and Newsfeed modules ## Breaking Changes None. Existing configurations continue to work. ## Testing To ensure proper functionality, I obtained API keys and credentials for all providers that require them. I configured all 10 providers in a carousel setup and tested each one individually. Screenshots for each provider are attached below demonstrating their working state. I even requested developer access from the Tempest/WeatherFlow team to properly test this provider. **Comprehensive test coverage**: A major advantage of the server-side architecture is the ability to thoroughly test providers with unit tests using real API response snapshots. Don't be alarmed by the many lines added in this PR - they are primarily test files and real-data mocks that ensure provider reliability. ## Review Notes I know this is an enormous change - I've been working on this for quite some time. Unfortunately, breaking it into smaller incremental PRs wasn't feasible due to the interdependencies between providers and the shared architecture. Given the scope, it's nearly impossible to manually review every change. To ensure quality, I've used both CodeRabbit and GitHub Copilot to review the code multiple times in my fork, and both provided extensive and valuable feedback. Most importantly, my test setup with all 10 providers working successfully is very encouraging. ## Related Part of the HTTPFetcher migration #4016. ## Screenshots <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-06-54" src="https://github.com/user-attachments/assets/2139f4d2-2a9b-4e49-8d0a-e4436983ed6e" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-02" src="https://github.com/user-attachments/assets/880f7ce2-4e44-42d5-bfe4-5ce475cca7c2" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-07" src="https://github.com/user-attachments/assets/abd89933-fe03-40ab-8a7c-41ae1ff99255" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-12" src="https://github.com/user-attachments/assets/22225852-f0a9-4d33-87ab-0733ba30fad3" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-17" src="https://github.com/user-attachments/assets/7a7192a5-f237-4060-85d7-6f50b9bef5af" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-22" src="https://github.com/user-attachments/assets/df84d9f1-e531-4995-8da8-d6f2601b6a08" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-27" src="https://github.com/user-attachments/assets/4cf391ac-db43-4b52-95f4-f5eadc5ea34d" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-32" src="https://github.com/user-attachments/assets/8dd8e688-d47f-4815-87f6-7f2630f15d58" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-37" src="https://github.com/user-attachments/assets/ee84a8bc-6b35-405a-b311-88658d9268dd" /> <img width="1920" height="1080" alt="Ekrankopio de 2026-02-08 13-07-42" src="https://github.com/user-attachments/assets/f941f341-453f-4d4d-a8d9-6b9158eb2681" /> Provider "Weather API" added later: <img width="1910" height="1080" alt="Ekrankopio de 2026-02-15 19-39-06" src="https://github.com/user-attachments/assets/3f0c8ba3-105c-4f90-8b2e-3a1be543d3d2" />
2026-02-23 10:27:29 +01:00
nextDelay,
[core] refactor: extract and centralize HTTP fetcher (#4016) ## Summary PR [#3976](https://github.com/MagicMirrorOrg/MagicMirror/pull/3976) introduced smart HTTP error handling for the Calendar module. This PR extracts that HTTP logic into a central `HTTPFetcher` class. Calendar is the first module to use it. Follow-up PRs would migrate Newsfeed and maybe even Weather. **Before this change:** - ❌ Each module had to implemented its own `fetch()` calls - ❌ No centralized retry logic or backoff strategies - ❌ No timeout handling for hanging requests - ❌ Error detection relied on fragile string parsing **What this PR adds:** - ✅ Unified HTTPFetcher class with intelligent retry strategies - ✅ Modern AbortController with configurable timeout (default 30s) - ✅ Proper undici Agent for self-signed certificates - ✅ Structured error objects with translation keys - ✅ Calendar module migrated as first consumer - ✅ Comprehensive unit tests with msw (Mock Service Worker) ## Architecture **Before - Decentralized HTTP handling:** ``` Calendar Module Newsfeed Module Weather Module ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ fetch() own │ │ fetch() own │ │ fetch() own │ │ retry logic │ │ basic error │ │ no retry │ │ error parse │ │ handling │ │ client-side │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ ▼ External APIs ``` **After - Centralized with HTTPFetcher:** ``` ┌─────────────────────────────────────────────────────┐ │ HTTPFetcher │ │ • Unified retry strategies (401/403, 429, 5xx) │ │ • AbortController timeout (30s) │ │ • Structured errors with translation keys │ │ • undici Agent for self-signed certs │ └────────────┬──────────────┬──────────────┬──────────┘ │ │ │ ┌───────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐ │ Calendar │ │ Newsfeed │ │ Weather │ │ ✅ This PR │ │ future │ │ future │ └───────────────┘ └──────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ▼ External APIs ``` ## Complexity Considerations **Does HTTPFetcher add complexity?** Even if it may look more complex, it actually **reduces overall complexity**: - **Calendar already has this logic** (PR #3976) - we're extracting, not adding - **Alternative is worse:** Each module implementing own logic = 3× the code - **Better testability:** 443 lines of tests once vs. duplicating tests for each module - **Standards-based:** Retry-After is RFC 7231, not custom logic ## Future Benefits **Weather migration (future PR):** Moving Weather from client-side to server-side will enable: - **Same robust error handling** - Weather gets 429 rate-limiting, 5xx backoff for free - **Simpler architecture** - No proxy layer needed Moving the weather modules from client-side to server-side will be a big undertaking, but I think it's a good strategy. Even if we only move the calendar and newsfeed to the new HTTP fetcher and leave the weather as it is, this PR still makes sense, I think. ## Breaking Changes **None** ---- I am eager to hear your opinion on this :slightly_smiling_face:
2026-01-22 19:24:37 +01:00
error
);
/**
* Error event - fired when fetch fails
* @event HTTPFetcher#error
* @type {object}
* @property {string} message - Error description
* @property {number|null} statusCode - HTTP status or null for network errors
* @property {number} retryDelay - Ms until next retry
* @property {number} retryCount - Number of consecutive server errors
* @property {string} url - The URL that was fetched
* @property {Error|null} originalError - The original error
*/
this.emit("error", errorInfo);
} finally {
clearTimeout(timeoutId);
}
this.scheduleNextFetch(nextDelay);
}
}
module.exports = HTTPFetcher;