ipWhitelist was only applied to HTTP routes, so Socket.IO module
namespaces could still be reached from disallowed clients.
This adds the same whitelist check to Socket.IO handshakes
(allowRequest), and reuses the same client IP resolution for both HTTP
and Socket.IO (forwarded IP is only trusted for loopback peers).
Also adds tests for handshake allow/deny and forwarded-header behavior.
Fixes: GHSA-w26r-fwg8-rcp3
I suggest to add the commit hash and branch to the system information.
This should help trouble shooting issues from developers. Like in #4165.
### before
```
#### System Information ####
- SYSTEM: manufacturer: Micro-Star International Co., Ltd.; model: MS-7D75; virtual: false; MM: v2.37.0-develop
- OS: ...
...
```
### after
```
#### System Information ####
- MM: version: v2.37.0-develop; git: 03e4eef3d; branch: develop
- SYSTEM: manufacturer: Micro-Star International Co., Ltd.; model: MS-7D75; virtual: false
- OS: ...
...
```
Previously, `electronSwitches` only accepted strings. This PR adds
support for object entries, allowing switches with values:
```js
electronSwitches: [
"no-sandbox",
{ "js-flags": "--max-old-space-size=8192" }
]
```
I decided to put the logic into a helper file so it can be unit-tested
independently, since electron.js itself requires a live Electron
environment and cannot be tested in isolation.
Fixes#4159
This was introduced when running systeminformation in an own thread.
If the main thread ends before (e.g. when config has errors) the
systeminformation thread never ends.
As this can have side effects under pm2 or docker (because the app never
exits) this PR restores the old behavior.
With these changes a few browser-side core files now use native ES
modules. `Loader`, `MMSocket`, `Module` and `MM` can be imported
directly instead of being read off `window`. `main.js` and `loader.js`
are no longer wrapped in IIFEs - they're just normal modules now.
`Module`, `MM` and `MMSocket` are still exposed as globals, so
third-party modules that use the old API keep working.
The changes are mostly structural, behavior should stay the same. A few
internal helpers in `main.js` got an underscore prefix because their
names clashed with public `MM` methods.
## Why
The old setup relied a lot on script order: a file could use `Loader` or
`MMSocket` only because another script happened to put it on `window`
first. Imports make that explicit.
The bigger goal is to move away from the legacy script-loading patterns
- making it easier to understand and easier to test - in other words:
easier to maintain.
More of the core could be "cleaned up" the same way, but that would blow
up this PR.
For reviewing, I recommend to hide the whitespace changes.
This PR rewrites `module.js` to use a native ES6 `class` instead of
`Class.extend()` - the same old inheritance helper that was removed from
`node_helper.js` in #4147.
The normal module API stays the same: modules still use
`Module.register({...})`. Internally, `Module.create()` now creates a
named subclass for each module, copies over a cloned definition, and
only calls `init()` when it is actually a function.
Outcome: one less file in the browser bundle, no more magic `_super()`
wiring, better stack traces, and tests for the module creation path that
did not exist before. `module.js` is also now a plain class with no
external dependencies - an intentional step towards `export default
Module` when browser scripts eventually move to native ES modules.
Since these changes touch the core of how modules are created, I'd
appreciate a close review and any feedback on edge cases I may have
missed.
This PR rewrites `node_helper.js` to use a native ES6 `class` instead of
`Class.extend()` - a manual inheritance helper from 2008, written back
when `class` syntax didn't exist yet. Node.js has supported native
classes since v6, so there's no reason to keep the workaround around.
The public API is unchanged - module authors still write the same
`NodeHelper.create({...})` they always have.
Outcome: same behavior, normal modern JavaScript, better stack traces,
and `node_helper.js` no longer pulls in `class.js` on the server side.
Removing `class.js` altogether is a follow-up (it's still used on
browser side).
**[#24](https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning/24)
– `js/class.js`**
`fnTest` works by serialising a function to a string and checking if
`"xyz"` appears in it - the function is never actually called. The bare
`xyz;` is never executed, so CodeQL is right to flag it. `return xyz;`
makes the intent clear. So this is purely a cosmetic change.
**[#26](https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning/26)
– `tests/e2e/helpers/global-setup.js`**
CodeQL flagged `if (exec) exec;` as a useless expression - and it was
right. But the real find was one level deeper.
`startApplication` hardcoded `const port = 8080`, so `MM_PORT` was
always overwritten before the app started. The test named "Set port 8100
on environment variable MM_PORT" was actually testing port 8080 the
whole time - it just happened to pass anyway.
Removed the dead `exec` parameter, made `startApplication` read
`MM_PORT` from the environment, and fixed the test so it actually checks
what it says it checks.
This PR removes `ajv` from module position validation in `js/utils.js`
and replaces it with straightforward JavaScript checks. The old schema
only covered a few basic structure rules, so using `ajv` here felt
heavier than necessary. The updated validation keeps the same expected
behavior for valid configs and unknown-position warnings, but the code
path is now easier to read and easier to reason about when something
goes wrong.
Outcome: same validation behavior, one less dependency, less lines of
production code and better test coverage.
I noticed that in the System Information output, `used node` and
`installed node` were always identical when starting via Electron. That
usually shouldn't be the case. After digging into it, I found that since
PR #4002, `used node` in the subprocess effectively reported the system
Node version, not the parent process (which runs in Electron) version.
This PR fixes that by passing `used node` from the parent process and
logging it correctly.
**Before:**
`VERSIONS: electron: 41.3.0; used node: 26.0.0; installed node: 26.0.0;
...`
**After:**
`VERSIONS: electron: 41.3.0; used node: 24.15.0; installed node: 26.0.0;
...`
After #4120 was merged, here's a bit of polish on top.
- docs: Optimize JSDoc and comments.
- test: The existing 304 test is moved to the dedicated status code
block and extended with an `errorSpy` to also verify that no `error`
event is emitted.
- refactor: The success branch is moved before the error branch in
`fetch()`. This is more intuitive.
The bottom line is that this PR improves maintainability. For users,
this PR doesn't change anything.
## Summary
This updates `js/http_fetcher.js` so HTTP `304 Not Modified` responses
are emitted through the normal `response` event instead of being treated
as errors.
That aligns the core fetcher with the built-in `yr` weather provider in
`v2.35.0`, which already includes explicit logic to handle `304` and
reuse cached data.
Closes#4119.
## Root cause
`HTTPFetcher` currently treats all non-OK responses as errors. Since
`304` is not an OK response, it never reaches providers listening on the
`response` event.
At the same time, `defaultmodules/weather/providers/yr.js` expects to
receive `304` and handle it by reusing cached data.
## What changed
- special-case HTTP `304` in `HTTPFetcher`
- emit `304` through the normal `response` event
- preserve the existing error path for other non-OK statuses
## Validation
- confirmed `defaultmodules/weather/providers/yr.js` already expects
`response.status === 304`
- compared the unpatched `v2.35.0` `HTTPFetcher` behavior to the
provider logic
- ran `node --check js/http_fetcher.js`
Co-authored-by: sonnyb9 <sonnyb9@users.noreply.github.com>
As reported in #4109, the weather module retries much more frequently
than expected after network errors. #4092 already fixed the main cause
(duplicate fetchers), but the backoff logic in `HTTPFetcher` still has a
gap: once retries are exhausted, `calculateBackoffDelay` keeps returning
a short fixed delay (60s) instead of falling back to `reloadInterval`.
The same problem existed for 5xx errors, where the delay grew to 8× the
configured interval.
Inspired by #4110 (thanks @CodeLine9), this PR makes both error paths
fall back to `reloadInterval` after retries are exhausted. I also
simplified the catch block, extracted a `#shortenUrl()` helper for log
messages, and added tests for the backoff progression.
Fixes#4105
```bash
In JavaScript, standard JSON does not support functions.
If you use JSON.stringify() on an object containing functions,
those functions will be omitted (if they are object properties)
or changed to null (if they are in an array).
```
---------
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Disable secret substitution only for cors=allowAll, the last attempt in
#4102 was to restrictive.
Secret substitution should also work if cors=disabled.
---------
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
### 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
If a module uses this.io.of() to register a custom socket.io namespace,
connections on that namespace trigger the onAny handler in setSocketIO
before config is set, causing a TypeError.
Fixes#4089
I provide docker images with alpine linux and tested the new cors
approach.
It didn't work because after calling
```js
const dispatcher = new Agent({ connect: { lookup: (_h, _o, cb) => cb(null, address, family) } });
```
the dispatcher variable was undefined.
This PR solves this and I tested this under debian too.
The mix of internal fetch and newer undici did not work and alpine needs
additionally the `process.nextTick`.
---------
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
PR #4084 blocked SSRF by checking the IP before `fetch()` — but
`fetch()` resolves DNS again on its own. With DNS rebinding (TTL=0,
alternating IPs) an attacker can slip a private IP through between check
and connection.
Fix: resolve DNS once, validate, pin the validated IP for the
connection.
No second DNS query → no rebinding window. `isPrivateTarget()` is gone,
code is shorter than before.
Not a likely attack for a typical MagicMirror setup, but it doesn't add
complexity so there's no reason not to close the gap.
Resolve target hostname before proxying and reject any address that is
not globally routable (loopback, RFC 1918, link-local, etc.) using
ipaddr.js and dns.lookup().
In PR #4072 the GitHub bot complained about a missing variable
declaration for `config` in `app.js` and suggested adding `let config`.
Applying that suggestion broke the app because `server_functions.js` was
accessing `config` as an implicit global variable - the `let`
declaration made it unreachable from there.
So instead of the `let` declaration, I replaced all bare `config`
references with explicit `global.config`. This makes the dependency on
the global variable visible without changing runtime behavior,
consistent with how other globals like `global.root_path` and
`global.version` are already handled throughout the codebase.
Related to #4073
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
`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.
Remove unnecessary `__dirname` template-literal prefix from relative
`require()` paths. Node.js resolves relative require() paths correctly
without it.
While this may look like nitpicking: `require("./server")` is
transparent to static analysis tools - IDEs can resolve the path and
support go-to-definition. Template literals with `__dirname` are opaque
to them. It also removes another usage of `__dirname`, which has no
native equivalent in ESM and would need to be replaced there anyway
(when we switch to ESM anytime in the future).
The import reordering is a side effect: `import-x/order` treats
template-literal `require()` calls differently from plain strings, so
the previous order was no longer valid.
This PR updates ESLint and the ESLint plugins to their latest versions
and takes advantage of the new versions to simplify the config.
The main cleanup: removed all explicit `plugins: {}` registrations from
`eslint.config.mjs`. When passing direct config objects like
`js.configs.recommended`, the plugin registration is already included –
we were just doing it twice.
Two lint warnings are also fixed:
- A wrong import style for `eslint-plugin-package-json` (named vs.
default)
- `playwright/no-duplicate-hooks` is disabled for e2e tests – the rule
doesn't handle plain `beforeAll()`/`afterAll()` (Vitest style) correctly
and produces false positives. I've created an issue for that:
https://github.com/mskelton/eslint-plugin-playwright/issues/443.
Built-in Node.js imports were manually updated to use the `node:` prefix
(e.g. `require("fs")` → `require("node:fs")`). Minor formatting fixes
were applied automatically by `eslint --fix`.
After #4049 here are two small follow-up improvements to `js/logger.js`.
**1. Simpler bind syntax** —
`Function.prototype.bind.call(console.debug, console)` is an archaic
pattern. The equivalent `console.debug.bind(console)` works fine in all
supported engines (Node.js ≥ 22, modern browsers) and is much easier to
read. Also: `console.timeStamp` exists in all supported environments, so
the conditional fallback to an empty function is no longer needed.
**2. Simpler `setLogLevel`** — instead of iterating over all keys in the
logger object and permanently overwriting them, the method now loops
over the five log-level keys explicitly and rebinds from `console[key]`.
This makes the filtered set obvious at a glance and ensures utility
methods like `group`, `time`, and `timeStamp` are never accidentally
silenced — they're structural helpers, not log levels.
On Node.js v25, the log prefix in the terminal stopped working - instead
of seeing something like:
```
[2026-03-05 23:00:00.000] [LOG] [app] Starting MagicMirror: v2.35.0
```
the output was:
```
[2026-03-05 23:00:00.000] :pre() Starting MagicMirror: v2.35.0
```
Reported in #4048.
## Why did it break?
The logger used the `console-stamp` package to format log output. One
part of that formatting used `styleText("grey", ...)` to color the
caller prefix gray. Node.js v25 dropped `"grey"` as a valid color name
(only `"gray"` with an "a" is accepted now). This caused `styleText` to
throw an error internally - and `console-stamp` silently swallowed that
error and fell back to returning its raw `:pre()` format string as the
prefix. Not ideal.
## What's in this PR?
**1. The actual fix** - `"grey"` → `"gray"`.
**2. Cleaner stack trace approach** - the previous code set
`Error.prepareStackTrace` *after* creating the `Error`, which is fragile
and was starting to behave differently across Node versions. Replaced
with straightforward string parsing of `new Error().stack`.
**3. Removed the `console-stamp` dependency** - all formatting is now
done with plain Node.js built-ins (`node:util` `styleText`). Same visual
result, no external dependency.
**4. Simplified the module wrapper** - the logger was wrapped in a UMD
pattern, which is meant for environments like AMD/RequireJS. MagicMirror
only runs in two places: Node.js and the browser. Replaced with a simple
check (`typeof module !== "undefined"`), which is much easier to follow.
Enable the `require-await` ESLint rule. Async functions without `await`
are just regular functions with extra overhead — marking them `async`
adds implicit Promise wrapping, can hide missing `return` statements,
and misleads readers into expecting asynchronous behavior where there is
none.
While fixing the violations, I removed unnecessary `async` keywords from
source files and from various test callbacks that never used `await`.
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"
/>
and centralize and optimize replace regex.
Another follow up to #4029
With this PR you can use secrets in urls in browser modules if you use
the cors proxy.
## Loading `config.js`
### Previously
Loaded on server-side in `app.js` and in the browser by including
`config.js` in `index.html`. The web server has an endpoint `/config`
providing the content of server loaded `config.js`.
### Now
Loaded only on server-side in `app.js`. The browser loads the content
using the web server endpoint `/config`. So the server has control what
to provide to the clients.
Loading the `config.js` was moved to `Utils.js` so that
`check_config.js` can use the same functions.
## Using environment variables in `config.js`
### Previously
Environment variables were not allowed in `config.js`. The workaround
was to create a `config.js.template` with curly braced bash variables
allowed. While starting the app the `config.js.template` was converted
via `envsub` into a `config.js`.
### Now
Curly braced bash variables are allowed in `config.js`. Because only the
server loads `config.js` he can substitute the variables while loading.
## Secrets in MagicMirror²
To be honest, this is a mess.
### Previously
All content defined in the `config` directory was reachable from the
browser. Everyone with access to the site could see all stuff defined in
the configuration e.g. using the url http://ip:8080/config. This
included api keys and other secrets.
So sharing a MagicMirror² url to others or running MagicMirror² without
authentication as public website was not possible.
### Now
With this PR we add (beta) functionality to protect sensitive data. This
is only possible for modules running with a `node_helper`. For modules
running in the browser only (e.g. default `weather` module), there is no
way to hide data (per construction). This does not mean, that every
module with `node_helper` is safe, e.g. the default `calendar` module is
not safe because it uses the calendar url's as sort of id and sends them
to the client.
For adding more security you have to set `hideConfigSecrets: true` in
`config.js`. With this:
- `config/config.env` is not deliverd to the browser
- the contents of environment variables beginning with `SECRET_` are not
published to the clients
This is a first step to protect sensitive data and you can at least
protect some secrets.
This migrates the Newsfeed module to use the centralized HTTPFetcher
class (introduced in #4016), following the same pattern as the Calendar
module.
This continues the refactoring effort to centralize HTTP error handling
across all modules.
## Changes
**NewsfeedFetcher:**
- Refactored from function constructor to ES6 class (like the calendar
module in #3959)
- Replaced manual fetch() + timer handling with HTTPFetcher composition
- Uses structured error objects with translation keys
- Inherits smart retry strategies (401/403, 429, 5xx backoff)
- Inherits timeout handling (30s) and AbortController
**node_helper.js:**
- Updated error handler to use `errorInfo.translationKey`
- Simplified property access (`fetcher.url`, `fetcher.items`)
**Cleanup:**
- Removed `js/module_functions.js` (`scheduleTimer` no longer needed)
- Removed `#module_functions` import from package.json
## Related
Part of the HTTPFetcher migration effort started in #4016.
Next candidate: Weather module (client-side → server-side migration).
This is another change to cleanup structure, already mentioned in
https://github.com/MagicMirrorOrg/MagicMirror/pull/4019#issuecomment-3792953018
After separating default and 3rd-party modules this PR moves the
`custom.css` from the mm-owned directory `css` into user owned directory
`config`.
It has a built-in function which moves the `css/custom.css` to the new
location `config/custom.css` (if the target not exists).
Let me know if there's a majority in favor of this change.
Since the project's inception, I've missed a clear separation between
default and third-party modules.
This increases complexity within the project (exclude `modules`, but not
`modules/default`), but the mixed use is particularly problematic in
Docker setups.
Therefore, with this pull request, I'm moving the default modules to a
different directory.
~~I've chosen `default/modules`, but I'm not bothered about it;
`defaultmodules` or something similar would work just as well.~~
Changed to `defaultmodules`.
Let me know if there's a majority in favor of this change.
## 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 🙂
In #3407 we already talked about unifying them.
- Create SVG favicon (better then png)
- Replace base64 placeholder in index.html with SVG favicon
- Update electron.js to use SVG favicon instead of mm2.png
- Add favicon.svg to server static routes
- Remove mm2.png
If an error occurs during startup, we request system information from
the user. The problem is that this information is displayed too late,
for example, if the configuration check fails.
My initial idea was to use `await
Utils.logSystemInformation(global.version);`, but this increased the
startup time.
Therefore, the function is now called in a subprocess. This approach
provides the information in all cases and does not increase the startup
time.
The config checker previously only allowed Node.js globals, but since
the config file runs also in the browser context, users should be able
to access browser APIs like `document` or `window` when needed.
This was incorrectly flagged as an error by the `no-undef` ESLint rule.
The fix adds browser globals to the allowed globals in the linter
config.
Fixes#3990.
and remove CHANGELOG.md logic.
This is my attempt to create a draft release instead of editing a
changelog, see discussion on discord.
Logic:
- new github workflow `.github/workflows/release-notes.yaml`
- runs with every push on `develop` (so after PR's are merged)
- collects the commits on `develop` which are newer than the latest tag
- searches the commit messages for keywords defined in an array and
group the messages into categories (this is a first shot, we will update
this ...)
- creates markdown content
- looks for an untagged and unpublished draft release with name
`unreleased`, if it exists, it will be deleted
- creates an untagged and unpublished draft release with name
`unreleased` with markdown content created above
Example created on my fork (this caused having `MagicMirrorOrg` in the
PR-Links):
<img width="952" height="1804" alt="grafik"
src="https://github.com/user-attachments/assets/38687bed-f5da-4dcb-93eb-242c317769df"
/>
Please review this PR, it is a draft release at the moment because I got
problems in my fork where I tested this: The created draft release is
not visible at the moment (they are visible via api). AFAIS this is a
queue problem on GitHub, maybe I flooded their queue while testing ...
So I will test this tomorrow again before removing `draft` here.
1. Convert CalendarFetcher from legacy constructor function pattern to
ES6 class (which simplifies future migration from CommonJS to ES
modules).
2. Implement targeted HTTP error handling with smart retry strategies
for common calendar feed issues:
- 401/403: Extended retry delay (5× interval, min 30 min)
- 429: Retry-After header parsing with 15 min fallback
- 5xx: Exponential backoff (2^count, max 3 retries)
- 4xx: Extended retry (2× interval, min 15 min)
- Add serverErrorCount tracking for exponential backoff
- Error messages now include specific HTTP status codes and calculated
retry delays for better debugging and user feedback
Previously, CalendarFetcher did not respond appropriately to HTTP
errors, continuing to hammer endpoints without backoff, potentially
overloading servers and triggering rate limits. This refactoring
implements respectful retry strategies that adapt to server responses
and reduce unnecessary load.
Maybe we could later centralize the HTTP error handling and use it for
weather and newsfeed as well.
The PR was inspired by having worked on the calendar fetcher for
MMM-CalendarExt2, where there was already better error handling.