I was playing around with the newsfeed notification system
(`ARTICLE_MORE_DETAILS`, `ARTICLE_TOGGLE_FULL`, …) and discovered some
issues with the full article view:
The iframe was loading the CORS proxy URL instead of the actual article
URL, which could cause blank screens depending on the feed. Also, many
news sites block iframes entirely (`X-Frame-Options: DENY`) and the user
got no feedback at all — just an empty page. On top of that, scrolling
used `window.scrollTo()` which moved the entire MagicMirror page instead
of just the article.
This PR cleans that up:
- Use the raw article URL for the iframe (CORS proxy is only needed for
server-side feed fetching)
- Check `X-Frame-Options` / `Content-Security-Policy` headers
server-side before showing the iframe — if the site blocks it, show a
brief "Article cannot be displayed here." message and return to normal
view
- Show the iframe as a fixed full-screen overlay so other modules aren't
affected, scroll via `container.scrollTop`
- Keep the progressive disclosure behavior for `ARTICLE_MORE_DETAILS`
(title → description → iframe → scroll)
- Delete `fullarticle.njk`, replace with `getDom()` override
- Fix `ARTICLE_INFO_RESPONSE` returning proxy URL instead of real URL
- A few smaller fixes (negative scroll, null guard)
- Add `NEWSFEED_ARTICLE_UNAVAILABLE` translation to all 47 language
files
- Add e2e tests for the notification handlers (`ARTICLE_NEXT`,
`ARTICLE_PREVIOUS`, `ARTICLE_INFO_REQUEST`, `ARTICLE_LESS_DETAILS`)
## What this means for users
- The full article view now works reliably across different feeds
- If a news site blocks iframes, the user sees a brief message instead
of a blank screen
- Additional e2e tests make the module more robust and less likely to
break silently in future MagicMirror versions
## 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 🙂
Hello and thank you for wanting to contribute to the MagicMirror²
project!
**Please make sure that you have followed these 4 rules before
submitting your Pull Request:**
> 1. Base your pull requests against the `develop` branch.
> 2. Include these infos in the description:
>
> - Does the pull request solve a **related** issue?
> - If so, can you reference the issue like this `Fixes
#<issue_number>`?
> - What does the pull request accomplish? Use a list if needed.
> - If it includes major visual changes please add screenshots.
>
> 3. Please run `npm run lint:prettier` before submitting so that
> style issues are fixed.
> 4. Don't forget to add an entry about your changes to
> the CHANGELOG.md file.
**Note**: Sometimes the development moves very fast. It is highly
recommended that you update your branch of `develop` before creating a
pull request to send us your changes. This makes everyone's lives
easier (including yours) and helps us out on the development team.
Thanks again and have a nice day!