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, `nodeRestart()` would spawn a detached child and exit. Under
PM2 that's a problem: PM2 also respawns on exit, so both race to bind
the same port.
The fix: When `process.env.pm_id` is set, just exit and let PM2 handle
the restart.
The spawn logic is moved into its own method so it can be tested
cleanly.
Partially fixes#4165
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 adds the weather provider Buienradar for the Netherlands and
Belgium without requiring an API key.
In MagicMirrorOrg/MagicMirror-Documentation#380 @plebcity shared his
implementation for the old browser-side architecture. I used it as a
reference to build this server-side implementation.
### Example screenshot
<img width="969" height="578" alt="Ekrankopio de 2026-05-20 20-38-36"
src="https://github.com/user-attachments/assets/56623ad8-7439-4047-abad-452ba2ebdcb2"
/>
### Example config
```js
{
module: "weather",
position: "top_left",
header: "Buienradar - Current",
config: {
weatherProvider: "buienradar",
type: "current",
locationId: 2747891
}
},
{
module: "weather",
position: "top_right",
header: "Buienradar - Forecast",
config: {
weatherProvider: "buienradar",
type: "forecast",
locationId: 2747891
}
},
{
module: "weather",
position: "bottom_left",
header: "Buienradar - Hourly",
config: {
weatherProvider: "buienradar",
type: "hourly",
locationId: 2747891
}
},
```
----
When this is accepted and merged I'll update the weather docs.
This updates the dependencies and implements a workaround for the
failing ci tests on node 26.1.0 (#4150).
I'm not sure myself whether we should go with this workaround or wait
for an upstream fix.
The restart approach I introduced in #4156 still crashes under Electron:
```
TypeError: Cannot read properties of undefined (reading 'disableHardwareAcceleration')
at electron.js:18
```
`nodeRestart()` hardcodes `node` as the interpreter, but under Electron
`process.argv[0]` is the Electron binary. Spawning `node js/electron.js`
causes `require("electron")` to return a string instead of the API, so
`electron.app` is `undefined`.
This uses `process.argv[0]` as the binary directly, which works for both
Electron and plain Node.
I introduced more variables for more clarity.
Fixes#4154.
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.
Use current process arguments when spawning the restart command so
auto-restart keeps the active runtime mode (for example `start:x11`)
instead of always defaulting to `start`.
This should fix#4154.
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.
The unit tests were full of warnings `ExperimentalWarning: localStorage
is not available because --localstorage-file was not provided.` for node
versions >=25.
Additionally this PR nails node to `v26.0.0` in the automated-tests
workflow until the [playwright
issue](https://github.com/microsoft/playwright/issues/40724) is fixed.
Will revert this after fixed playwright version is available.
We were running three separate lint tools (ESLint, Stylelint,
markdownlint-cli2) and a separate Prettier step. This PR consolidates
everything: `@eslint/css` and `@eslint/markdown` bring CSS and Markdown
linting into ESLint, so one tool now covers JS, CSS, and Markdown. The
Prettier step got merged into the lint scripts too, so there's now just
`test:lint` to check things and `lint:fix` to fix them.
Outcome: fewer tools to maintain, simpler/fewer scripts, fewer config
files.
As a side effect, the dependabot alerts
https://github.com/MagicMirrorOrg/MagicMirror/security/dependabot/140
and
https://github.com/MagicMirrorOrg/MagicMirror/security/dependabot/141
are resolved, as we no longer use the vulnerable `fast-uri` package
(which was a dependency of `stylelint`).
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 switches the calendar fetcher from synchronous to asynchronous ICS
parsing.
It does not necessarily make parsing faster overall, but it avoids long
event-loop stalls with large calendar files (especially on slower
devices and with multiple feeds).
So this does not fully solve #4103 on its own, but it clearly mitigates
it.
It should also combine well with a future pre-filter approach discussed
in the issue:
- with pre-filter = less data to parse (future PR)
- with async parsing = less blocking while parsing (this PR)
Together, that is likely the strongest path to fully address #4103.
additional things:
- removed fix chrome-sandbox permissions (runs without)
- added `npx install-electron` which downloads the electron binaries
new in v42:
> Electron now downloads its binary into node_modules dynamically on
first launch instead of running a postinstall script. Added the
install-electron script to manually trigger the download as well.
https://github.com/electron/electron/pull/49328
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;
...`
## What does this PR accomplish?
The `postinstall` script runs `git clean -df fonts vendor
modules/default`
unconditionally. When `magicmirror` is installed as an npm dependency
(e.g. `npm install magicmirror` or as a transitive dep in another
project),
npm runs `postinstall` in a directory that is **not** a git repository.
This causes:
```
fatal: not a git repository (or any of the parent directories): .git
```
npm treats the non-zero exit as a failure → the entire installation
aborts
with `code 128`. This makes it impossible to use `magicmirror` as an npm
dependency in any third-party project.
The issue has been present since at least **v2.34.0**. In **v2.35.0**
`modules/default` was added to the clean targets, but the root cause
predates that change.
## Fix
Added `scripts/postinstall.js` — a cross-platform Node.js script that
guards the `git clean` call with a `git rev-parse --git-dir` check.
- When running inside a real git repository: behaviour is identical to
before.
- When running outside one (e.g. installed as an npm package): step is
silently skipped.
`package.json` `postinstall` updated from the bare `git clean` call to:
```json
"postinstall": "node scripts/postinstall.js"
```
This approach is cross-platform (Linux, macOS, Windows) and keeps the
logic readable rather than inlined as a one-liner.
Does this solve a related issue?
No existing issue tracked. Discovered while using magicmirror as a
devDependency in a companion tooling project — installation failed
unconditionally on all platforms.
Checklist
- Targets the develop branch
- No visual changes (script + package.json only)
- node --run lint:prettier run — no changes needed for .js script
The regex captured the full fetch output line including the branch name.
Before #4115, the command was built as a shell string, so the shell
split the arguments correctly. After #4115 switched to `execFile`, the
range and branch name were passed as a single argument
(`60e0377..332e429 develop`) - which git rejects as ambiguous.
The fix replaces the regex with column-based line parsing: find the line
for the current branch, take the first column. Falls back to
`<branch>..origin/<branch>` when fetch reports no changes (same behavior
as before).
Also adds unit tests for the new helper and corrects existing test
snapshots that encoded the broken behavior.
Fixes#4137.
While removing unnecessary conditionals (for `compliments` reported in
https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning/27
and for `weather` reported in
https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning/28),
I noticed a bug in the imperial conversion path: falsy property checks
like `if (imperialWeatherObject.temperature)` silently skip the
conversion when the value is `0`. So at exactly 0 °C, the result would
show `0 °F` instead of the correct `32 °F`.
I wrote unit tests for `convertWeatherObjectToImperial` first, which
confirmed the bug, then fixed it by replacing the falsy checks with the
`in` operator. Which I also find more intuitive to read.
Weather APIs deliver floating point values, so hitting exactly `0.0`
might be rare in practice - more likely `0.1` or `-0.1`, which are
truthy and convert fine. That should explain why it went unnoticed by
users.
We can rely on PM2's native restart-on-exit behavior instead of the
programmatic pm2 API.
Fixes
https://github.com/MagicMirrorOrg/MagicMirror/security/dependabot/82 by
removing pm2.
Note: Originally this PR was intended to update pm2, but after
discussion, we decided to replace it instead. See the discussion below.
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.
Open-Meteo updates `current_weather` every 15 minutes, but the hourly
array only has entries at full hours. The old code did an exact
timestamp match - so at 14:15, 14:30 or 14:45 it never found a match and
silently fell back to index 0, showing midnight values for humidity,
feels-like temp, precipitation, etc.
Fix: `findLastIndex((hour) => hour.time <= currentMs)` - the last hourly
entry at or before the current time.
While fixing the bug I found several dead branches left over from the
#4032 refactor: a path for pre-transposed hourly data that
`#parseWeatherApiResponse` makes unreachable, an `Array.isArray` guard
that's always true, and a `Log.debug` inside the dead branch. Removing
those accounts for most of the ~40 deleted lines - the actual fix is one
line.
Fixes#4122.
When a client reconnects while the backend is still in its rate-limit
protection phase, the weather module has no data to show and stays on
`Loading...` until the next scheduled API call. This mainly affects
server mode setups, where the server keeps running while a remote client
temporarily loses its connection and reloads. It was [raised in the
forum](https://forum.magicmirror.builders/topic/20218/request-loop-loading...-in-standard-weather-module-open-meteo-after-update/11?_=1777106416020)
and is worthy of a fix to improve the user experience.
With this PR the node helper caches the last successful `WEATHER_DATA`
payload per instance and replays it immediately on reconnect. The client
gets its last known state right away instead of waiting for the next
fetch. The cache is cleaned up when the provider stops.
Tests are included to cover reconnect with and without cached data, and
the cleanup path.
## 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>
This fixes CodeQL alert
[#16](https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning/16)
by replacing shell-built git commands with `execFile` + `cwd` in
updatenotification’s `git_helper`.
It also includes a small cleanup in `checkUpdates()` (remove unnecessary
async/Promise wrapper) and updates the related unit tests.
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.
While looking at the WeatherFlow provider (to evaluate #4107), I noticed
a few things that weren't quite right.
1. **UV index was broken for most providers in forecast/hourly views.**
The templates read `uv_index`, but only the WeatherAPI provider actually
wrote that key. All other providers (OpenWeatherMap, WeatherFlow,
PirateWeather, etc.) use `uvIndex` - so UV was silently never displayed
for them. This went unnoticed because `showUVIndex` defaults to `false`
and there were no test assertions for it. Standardized everything on
`uvIndex` and added test coverage.
2. **WeatherFlow didn't map precipitation for current weather.** The API
provides `precip_accum_local_day` and `precip_probability`, but they
weren't passed through. While adding them I also noticed the template
used truthiness checks, which hid valid zero values. Fixed both.
3. **`||` vs `??` in WeatherFlow provider.** Several numeric fields used
`|| null`, replacing valid `0` with `null`. Switched to `??` for
correctness.
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>
After the big weather refactor (#4032), OpenWeatherMap was effectively
hard-wired to One Call v3. One Call 2.5 is deprecated and no longer
available, so it looked like v2.5 support was effectively over — but the
classic `/weather` and `/forecast` endpoints were never actually
dropped. This restores support for those.
Fixes#4100.
## What this PR does
- handles OpenWeatherMap responses by endpoint again (`/onecall`,
`/weather`, `/forecast`)
- restores v2.5 current and forecast support (including hourly via
3-hour forecast slots)
- filters outdated hourly entries centrally while keeping the current
hour visible (if available)
## Screenshot
<img width="768" height="481" alt="bildo"
src="https://github.com/user-attachments/assets/9bce3531-3731-4fd7-b41e-e20603afa725"
/>
### 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>
During the server-side migration (PR #4032), the `instanceId` was built
with `Date.now()`, making it unique per client reload. This approach was
fine in the old browser-based architecture where reloads cleaned up
everything automatically. But now the node_helper persists across
reloads, so each reconnect created a new `HTTPFetcher` while the old one
kept polling - silently multiplying API calls over time.
The fix is simple: use `this.identifier` as the `instanceId`, which is
already stable and unique per module slot. This is the same approach the
Calendar module uses.
On the node_helper side, when a provider already exists for the same
`instanceId`, we now skip re-creation and just resend
`WEATHER_INITIALIZED` so the client picks up where it left off.
Fixes https://forum.magicmirror.builders/topic/20199
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.
Previously only master was scanned via the default CodeQL setup. Since
development happens on develop, this PR replaces the default setup with
a custom workflow that covers both branches. This gives an overview of
the security status across the current release (master) and the
development branch (develop).
As a result we should also see issues in the develop branch here:
https://github.com/MagicMirrorOrg/MagicMirror/security/code-scanning