5 Commits

Author SHA1 Message Date
Kristjan ESPERANTO
2e97e29ab5 fix(http_fetcher): use undici.fetch when dispatcher is present (#4097)
### What's the problem?

The `selfSignedCert` option passes an undici `Agent` as `dispatcher` to
`fetch()`. But Node's built-in `fetch()` and undici@8's `Agent` use
different internal handler APIs - passing them together throws:

```
invalid onRequestStart method
```

### What's the fix?

When `selfSignedCert` is enabled (i.e. a `dispatcher` is set), use
undici's own `fetch()` instead of the global one. For all other
requests, keep using `globalThis.fetch`.

```js
const fetchFn = requestOptions.dispatcher ? undiciFetch : globalThis.fetch;
```

### Why not just always use undici's fetch?

That would fix the crash - but it would break some tests. MSW (Mock
Service Worker), which is used in our test suite to intercept HTTP
requests, only hooks into `globalThis.fetch`. Undici's fetch bypasses
those interceptors entirely, so tests would start making real network
requests instead of getting the mocked responses. We could rewrite all
tests to use undici-compatible mocking instead - but that would be a
massive change for no real benefit.

----

Fixes #4093
2026-04-08 18:42:30 +02:00
Kristjan ESPERANTO
8e1630e8bf refactor: enable ESLint rule "no-unused-vars" and handle related issues (#4080)
In PR #4072 GitHub Bot complained about an unused var. Instead of just
removing that one, I checked why ESLint hadn't complained about it: We
had disabled the rule for it.

So I enabled rule and resolved the issues that ESLint then detected.

Related to #4073
2026-04-02 08:56:27 +02:00
Kristjan ESPERANTO
3ea3f0a605 chore: upgrade ESLint to v10 and fix newly surfaced issues (#4057)
`eslint-plugin-import-x` was the last thing blocking the ESLint v10
upgrade - it just got v10 support. So here we go.

The upgrade itself is tiny. The rest of the diff is cleanup from issues
ESLint v10 now catches: a few `let` declarations with initial values
that were immediately overwritten anyway (`no-useless-assignment`), and
`Translator` listed in `/* global */` in `main.js` and `module.js`.

Working through those `no-useless-assignment` warnings also surfaced a
dead default in `openmeteo`: `maxEntries: 5` in the constructor, which
was never actually doing anything - `openmeteo` never reads
`this.config.maxEntries` anywhere. And `weather.js` already sets that
default for all providers, so it was just a redundant duplicate. Removed
that too.

No runtime behavior changes.
2026-03-12 11:58:26 +01:00
Kristjan ESPERANTO
8ce0cda7bf [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
Kristjan ESPERANTO
34913bfb9f [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 🙂
2026-01-22 19:24:37 +01:00