5309 Commits

Author SHA1 Message Date
Kristjan ESPERANTO
21d1e7472a refactor: simplify internal require() calls (#4056)
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.
2026-03-12 10:35:42 +01:00
Kristjan ESPERANTO
9fe7d1eb75 test(calendar): fix hardcoded date in event shape test (#4055)
While looking into #4053 I noticed that one of the calendar tests had
stopped working. The cause was simple: the test had `2026-03-10`
hardcoded, and since that date is now in the past, the event was
silently filtered out by `includePastEvents: false`.

Instead of bumping the date to some future value (which would only delay
the same problem), I made it relative so it always lies in the future.

Command to test:

```bash
npx vitest run tests/unit/modules/default/calendar/
```
2026-03-12 00:40:23 +01:00
Karsten Hassel
0ca91f7292 weather: fixes for templates (#4054)
Follow up for #4051 

- fix loading default weather.css (the construction with `./weather.css`
gave a 404)
- accept `themeDir` with and without trailing slash
2026-03-09 23:29:48 +01:00
Karsten Hassel
35cd4d8759 weather: add possibility to override njk's and css (#4051)
This is an approach for #2909

It adds 2 values to the config:

```js
		themeDir: "./",
		themeCustomScripts: []
```

`themeDir` must be specified relative to the weather dir.

Example config:

```js
		{
			module: "weather",
			position: "top_center",
			config: {
				weatherProvider: "openmeteo",
				type: "current",
				lat: 40.776676,
				lon: -73.971321,
				themeDir: "../../../modules/MyWeatherTemplate/",
				themeCustomScripts: [ "skycons.js", "weathertheme.js" ],
			}
		},
```

The `themeDir` must contain the 4 files
- current.njk
- forecast.njk
- hourly.njk
- weather.css

You can add more files (if needed) and add them to the
`themeCustomScripts` so they are loaded as script.

There are 2 methods inserted which are called if defined:
- initWeatherTheme: For doing special things when starting the module
- updateWeatherTheme: For doing special things before updating the dom

I see this as a simple approach for overriding the default njk templates
and css. I did already convert my
[MMM-WeatherBridge](https://gitlab.com/khassel/MMM-WeatherBridge) into a
template.
2026-03-08 10:34:14 +01:00
Kristjan ESPERANTO
cb61aebb5a chore: update ESLint and plugins, simplify config, apply new rules (#4052)
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`.
2026-03-07 08:34:28 -07:00
Kristjan ESPERANTO
e7503a457b refactor: further logger clean-up (#4050)
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.
2026-03-06 18:56:16 +01:00
Kristjan ESPERANTO
3eb3745dd3 Fix Node.js v25 logging prefix and modernize logger (#4049)
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.
2026-03-06 13:10:59 +01:00
Kristjan ESPERANTO
ab3108fc14 [calendar] refactor: delegate event expansion to node-ical's expandRecurringEvent (#4047)
node-ical 0.25.x added `expandRecurringEvent()` — a proper API for
expanding both recurring and non-recurring events, including EXDATE
filtering and RECURRENCE-ID overrides. This PR replaces our hand-rolled
equivalent with it.

`calendarfetcherutils.js` loses ~125 lines of code. What's left only
deals with MagicMirror-specific concerns: timezone conversion,
config-based filtering, and output formatting. The extra lines in the
diff come from new tests.

## What was removed

- `getMomentsFromRecurringEvent()` — manual rrule.js wrapping with
custom date extraction
- `isFullDayEvent()` — heuristic with multiple fallback checks
- `isFacebookBirthday` workaround — patched years < 1900 and
special-cased `@facebook.com` UIDs
- The `if (event.rrule) / else` split — all events now go through a
single code path

## Bugs fixed along the way

Both were subtle enough to go unnoticed before:

- **`[object Object]` in event titles/description/location** — node-ical
represents ICS properties with parameters (e.g.
`DESCRIPTION;LANGUAGE=de:Text`) as `{val, params}` objects. The old code
passed them straight through. Mainly affected multilingual Exchange/O365
setups. Fixed with `unwrapParameterValue()`.

- **`excludedEvents` with `until` never worked** —
`shouldEventBeExcluded()` returned `{ excluded, until }` but the caller
destructured it as `{ excluded, eventFilterUntil }`, so the until date
was always `undefined` and events were never hidden. Fixed by correcting
the destructuring key.

The expansion loop also gets error isolation: a single broken event is
logged and skipped instead of aborting the whole feed.

## Other clean-ups

- Replaced `this.shouldEventBeExcluded` with
`CalendarFetcherUtils.shouldEventBeExcluded` — avoids context-loss bugs
when the method is destructured or called indirectly.
- Replaced deprecated `substr()` with `slice()`.
- Replaced `now < filterUntil` (operator overloading) with
`now.isBefore(filterUntil)` — idiomatic Moment.js comparison.
- Fixed `@returns` JSDoc: `string[]` → `object[]`.
- Moved verbose `Log.debug("Processing entry...")` after the `VEVENT`
type guard to reduce log noise from non-event entries.
- Replaced `JSON.stringify(event)` debug log with a lightweight summary
to avoid unnecessary serialization cost.
- Added comment explaining the 0-duration → end-of-day fallback for
events without DTEND.

## Tests

24 unit tests, all passing (`npx vitest run
tests/unit/modules/default/calendar/`).

New coverage: `excludedEvents` with/without `until`, Facebook birthday
year expansion, output object shape, no-DTEND fallback, error isolation,
`unwrapParameterValue`, `getTitleFromEvent`, ParameterValue properties,
RECURRENCE-ID overrides, DURATION (single and recurring).
2026-03-02 21:31:32 +01:00
Veeck
06b1361457 Use getDateString in openmeteo (#4046)
Fixes #4045 

Otherwise the calculation comes to the result that its yesterday...
2026-03-01 15:28:06 +01:00
Kristjan ESPERANTO
587bc2571e chore: remove obsolete Jest config and unit test global setup (#4044)
The files were overlooked during the migration from jest to vitest
(#3940).
2026-03-01 14:12:15 +01:00
Kristjan ESPERANTO
083953fff5 chore: update dependencies + add exports, files, and sideEffects fields to package.json (#4040)
This updates all dependencies to their latest versions - except two
packages:

- eslint: Some plugins we use here aren't compatible yet to ESLint v10.
- node-ical: The new version has revealed an issue in our calendar
logic. I would prefer to address this in a separate PR.

After updating the dependencies, eslint-plugin-package-json rules
complained about missing fields in the package.json. I added them in the
second commit.
2026-03-01 08:30:24 +01:00
Kristjan ESPERANTO
df8a882966 fix(newsfeed): fix full article view and add framing check (#4039)
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
2026-03-01 00:32:42 +01:00
Kristjan ESPERANTO
729f7f0fd1 [core] refactor: enable ESLint rule require-await and handle detected issues (#4038)
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`.
2026-02-25 10:55:56 +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
Andrés Vanegas Jiménez
80c48791b2 [weather] feat: add Weather API Provider (#4036)
Weather API: https://www.weatherapi.com/docs/
2026-02-21 23:19:22 +01:00
Karsten Hassel
6cb3e24c2a replace template_spec test with config_variables test (#4034)
After introducing new config loading this PR replaces the obsolete
template test with a config test.
2026-02-09 20:35:54 +01:00
Karsten Hassel
1dc3032171 allow environment variables in cors urls (#4033)
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.
2026-02-08 16:18:56 +01:00
Karsten Hassel
172ca18178 fix cors proxy getting binary data (e.g. png, webp) (#4030)
fixes #3266

---------

Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
2026-02-08 12:02:50 +01:00
Karsten Hassel
b9481d27fa fix: correct secret redaction and optimize loadConfig (#4031)
- fix copy/paste typo in redacted replacement
- create redacted content only if hideConfigSecrets is true

follow up for #4029
2026-02-08 00:26:40 +01:00
Karsten Hassel
9dd964e004 change loading config.js, allow variables in config.js and try to protect sensitive data (#4029)
## 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.
2026-02-06 00:21:35 +01:00
Karsten Hassel
f9f3461f13 calendar.js: remove useless hasCalendarURL function (#4028)
Found this while debugging.

The `hasCalendarURL` function does a check if the url is in the config.
But the calendar sees only his own config part, so this check is always
true (tested with more than one calendar module in `config.js`).
2026-02-06 00:11:53 +01:00
Karsten Hassel
f6d559e3dc remove kioskmode (#4027)
Marked as deprecated since 2016.
2026-02-06 00:09:59 +01:00
Jordan Welch
431103437c Add dark theme logo (#4026)
I noticed that this icon that I normally can't read on a dark theme has
a light version on the website as well!

This is using [the HTML `<picture>`
element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture)
that is [supported by GitHub
Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#the-picture-element)

<img width="544" height="432" alt="CleanShot 2026-01-31 at 22 54 42@2x"
src="https://github.com/user-attachments/assets/6ce5dda8-2aaa-4171-9989-7e2ab593e943"
/>
<img width="544" height="432" alt="CleanShot 2026-01-31 at 22 54 58@2x"
src="https://github.com/user-attachments/assets/3ed2944f-77b4-4645-9764-2aaf3cf21621"
/>

![CleanShot 2026-01-31 at 22 55
12](https://github.com/user-attachments/assets/97e1a56d-ae97-4dad-82d2-ebde8d92be2e)
2026-02-01 08:22:46 +01:00
Veeck
751c83bef0 Update node-ical and other deps (#4025) 2026-01-31 23:45:46 +01:00
Kristjan ESPERANTO
5c1cc476f3 [newsfeed] refactor: migrate to centralized HTTPFetcher (#4023)
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).
2026-01-29 19:41:59 +01:00
Kristjan ESPERANTO
2b55b8e0f4 refactor(clientonly): modernize code structure and add comprehensive tests (#4022)
This PR improves `clientonly` start option with better code structure,
validation, and comprehensive test coverage.

### Changes

**Refactoring:**
- Improved parameter handling with explicit function parameter passing
instead of closure
- Added port validation (1-65535) with proper NaN handling
- Removed unnecessary IIFE wrapper (Node.js modules are already scoped)
- Extracted `getCommandLineParameter` as a reusable top-level function
- Enhanced error reporting with better error messages
- Added connection logging for debugging

**Testing:**
- Added comprehensive e2e tests for parameter validation
- Test coverage for missing/incomplete parameters
- Test coverage for local address rejection (localhost, 127.0.0.1, ::1,
::ffff:127.0.0.1)
- Test coverage for port validation (invalid ranges, non-numeric values)
- Test coverage for TLS flag parsing
- Integration test with running server

### Testing

All tests pass:
```bash
npm test -- tests/e2e/clientonly_spec.js
# ✓ 18 tests passed
2026-01-28 20:48:47 +01:00
Karsten Hassel
6324ec2116 move custom.css from css to config (#4020)
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.
2026-01-28 10:50:25 +01:00
Kristjan ESPERANTO
43503e8fff chore: update dependencies (#4021)
This includes a new version of `node-ical` which should resolve a
calendar issue that was reported
[here](https://github.com/MagicMirrorOrg/MagicMirror/pull/4016#issuecomment-3787073856)
and
[here](https://github.com/MagicMirrorOrg/MagicMirror/pull/4010#issuecomment-3798857137).
2026-01-27 22:31:35 +01:00
Karsten Hassel
d44db6ea10 move default modules from /modules/default to /defaultmodules (#4019)
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.
2026-01-27 08:37:52 +01:00
Karsten Hassel
5e0cd28980 update electron to v40, update node versions in workflows (#4018)
- remove param `--enable-features=UseOzonePlatform` in start electron
tests (as we did already in `package.json`)
- update node versions in github workflows, remove `22.21.1`, add `25.x`
- fix formatting in tests
- update dependencies including electron to v40

This is still a draft PR because most calendar electron tests are not
running which is caused by the electron update from `v39.3.0` to
`v40.0.0`. Maybe @KristjanESPERANTO has an idea ...

---------

Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
2026-01-24 06:15:15 -06: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
in-voker
23f0290139 Switch to undici Agent for HTTPS requests (#4015)
Allow the selfSignedCert: true flag in calenders array to work.
2026-01-17 21:34:46 +01:00
Kristjan ESPERANTO
37a8b11112 chore(eslint): migrate from eslint-plugin-vitest to @vitest/eslint-plugin and run rules only on test files (#4014)
This PR makes three small changes to the ESLint setup:

1. Migrate from
[eslint-plugin-vitest](https://www.npmjs.com/package/eslint-plugin-vitest)
to it's successor
[@vitest/eslint-plugin](https://www.npmjs.com/package/@vitest/eslint-plugin).
2. Change the eslint config so that only the test files are checked with
the vitest rules. Previously, it was just unnecessary and inefficient to
check all js files with them.
3. We had defined some of the test rules as warnings, but that is not
really ideal. I changed them to errors.
2026-01-12 09:03:32 +01:00
Kristjan ESPERANTO
2d3a557864 fix(calendar): update to node-ical 0.23.1 and fix full-day recurrence lookup (#4013)
Adapts calendar module to node-ical changes and fixes a bug with moved
full-day recurring events in eastern timezones.

## Changes

### 1. Update node-ical to 0.23.1
- Includes upstream fixes for UNTIL UTC validation errors from CalDAV
servers (reported by @rejas in PR #4010)
- Changes to `getDateKey()` behavior for VALUE=DATE events (now uses
local date components)
- Fixes issue with malformed DURATION values (reported by MagicMirror
user here: https://github.com/jens-maus/node-ical/issues/381)

### 2. Remove dead code
- Removed ineffective UNTIL modification code (rule.options is read-only
in rrule-temporal)
- The code attempted to extend UNTIL for all-day events but had no
effect

### 3. Fix recurrence lookup for full-day events
node-ical changed the behavior of `getDateKey()` - it now uses local
date components for VALUE=DATE events instead of UTC. This broke
recurrence override lookups for full-day events in eastern timezones.

**Why it broke:**
- **before node-ical update:** Both node-ical and MagicMirror used UTC →
keys matched 
- **after node-ical update:** node-ical uses local date (RFC 5545
conform), MagicMirror still used UTC → **mismatch** 

**Example:**
- Full-day recurring event on October 12 in Europe/Berlin (UTC+2)
- node-ical 0.23.1 stores override with key: `"2024-10-12"` (local date)
- MagicMirror looked for key: `"2024-10-11"` (from UTC: Oct 11 22:00)
- **Result:** Moved event not found, appears on wrong date

**Solution:** Adapt to node-ical's new behavior by using local date
components for full-day events, UTC for timed events.

**Note:** This is different from previous timezone fixes - those
addressed event generation, this fixes the lookup of recurrence
overrides.

## Background

node-ical 0.23.0 switched from `rrule` to `rrule-temporal`, introducing
breaking changes. Version 0.23.1 fixed the UNTIL validation issue and
formalized the `getDateKey()` behavior for DATE vs DATE-TIME values,
following RFC 5545 specification that DATE values represent local
calendar dates without timezone context.
2026-01-11 21:27:52 -06:00
Karsten Hassel
82e39a2476 fix systeminformation not displaying electron version (#4012)
Bug was introduced with #4002

Because the sysinfo process runs as own subprocess the
`${process.versions.electron}` variable is always `undefined`.
2026-01-11 23:17:01 +01:00
Kristjan ESPERANTO
3b793bf31b Update node-ical and support it's rrule-temporal changes (#4010)
Updating `node-ical` and adapt logic to new behaviour.

## Problem

node-ical 0.23.0 switched from `rrule.js` to `rrule-temporal`, changing
how recurring event dates are returned. Our code assumed the old
behavior where dates needed manual timezone conversion.

## Solution

Updated `getMomentsFromRecurringEvent()` in `calendarfetcherutils.js`:
- Removed `tzid = null` clearing (no longer needed)
- Simplified timed events: `moment.tz(date, eventTimezone)` instead of
`moment.tz(date, "UTC").tz(eventTimezone, true)`
- Kept UTC component extraction for full-day events to prevent date
shifts
2026-01-10 18:35:46 -06:00
Kristjan ESPERANTO
471dbd80a5 Change default start scripts from X11 to Wayland (#4011)
This PR changes the default `start` and `start:dev` scripts to use
Wayland instead of X11. I think after three years, it's time to take
this step.

### Background

Since Raspberry Pi OS Bookworm (2023), Wayland is the default display
server. As most MagicMirror installations run on Raspberry Pi, this
change aligns with what new users already have installed.

### Benefits

Especially for new users (which install the OS with Wayland) it's easier
- they can simply run `npm start` without needing to understand display
server differences or manually switch scripts.

And for projects in general it's better to rely on modern defaults than
on legacy.

### Breaking Changes

**None** - X11 support is maintained. Users who really use and need X11
can use `node --run start:x11`.
2026-01-11 00:01:55 +01:00
Veeck
8e6701f6fd Update deps as requested by dependabot (#4008) 2026-01-08 20:31:42 +01:00
Karsten Hassel
b847dd7f3f update Collaboration.md and dependencies (#4001)
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
2026-01-08 10:14:47 +01:00
Kristjan ESPERANTO
56fe067afa chore: migrate CI workflows to ubuntu-slim for faster startup times (#4007)
*This type of runner is optimized for automation tasks, issue operations
and short-running jobs. They are not suitable for typical heavyweight
CI/CD builds.*
[[1](https://docs.github.com/en/actions/reference/runners/github-hosted-runners#single-cpu-runners)].

We are not necessarily dependent on faster startups, but that seems to
be becoming the best practice now.
2026-01-07 23:18:24 +01:00
Kristjan ESPERANTO
9731ea28eb refactor: unify favicon for index.html and Electron (#4006)
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
2026-01-05 10:51:43 +01:00
Kristjan ESPERANTO
40301f2a59 fix(calendar): correct day-of-week for full-day recurring events across all timezones (#4004)
Fixes **full-day recurring events showing on wrong day** in timezones
west of UTC (reported in #4003).

**Root cause**: `moment.tz(date, eventTimezone).startOf("day")`
interprets UTC midnight as local time:
- `2025-11-03T00:00:00.000Z` in America/Chicago (UTC-6)
- → Converts to `2025-11-02 18:00:00` (6 hours back)
- → `.startOf("day")` → `2025-11-02 00:00:00`  **Wrong day!**

**Impact**: The bug affects:
- All timezones west of UTC (UTC-1 through UTC-12): Americas, Pacific
- Timezones east of UTC (UTC+1 through UTC+12): Europe, Asia, Africa -
work correctly
- UTC itself - works correctly

The issue was introduced with commit c2ec6fc2 (#3976), which fixed the
time but broke the date. This PR fixes both.

| | Result | Day | Time | Notes |
|----------|--------|-----|------|-------|
| **Before c2ec6fc2** | `2025-11-03 05:00:00 Monday` |  |  | Wrong
time, but correct day |
| **Current (c2ec6fc2)** | `2025-11-02 00:00:00 Sunday` |  (west of
UTC)<br> (east of UTC) |  | Wrong day - visible bug! |
| **This fix** | `2025-11-03 00:00:00 Monday` |  |  | Correct in all
timezones |

Note: While the old logic had incorrect timing, it produced the correct
calendar day due to how it handled UTC offsets. The current logic fixed
the timing issue but introduced the more visible calendar day bug.

### Solution

Extract UTC date components and interpret as local calendar dates:

```javascript
const utcYear = date.getUTCFullYear();
const utcMonth = date.getUTCMonth();
const utcDate = date.getUTCDate();
return moment.tz([utcYear, utcMonth, utcDate], eventTimezone);
```

### Testing

To prevent this from happening again in future refactorings, I wrote a
test for it.

```bash
npm test -- tests/unit/modules/default/calendar/calendar_fetcher_utils_spec.js
```
2026-01-04 06:36:16 -06:00
Karsten Hassel
241921b79c [core] run systeminformation in subprocess so the info is always displayed (#4002)
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.
2026-01-03 01:14:48 +01:00
sam detweiler
950f55197e set next release dev number (#4000)
change develop for next release labels
2026-01-01 16:06:29 +01:00
Karsten Hassel
a4f29f753f Merge branch 'master' into develop 2026-01-01 15:33:56 +01:00
sam detweiler
d5d1441782 Prepare Release 2.34.0 (#3998)
starting 2.34.0 release
2026-01-01 07:51:45 -06:00
Karsten Hassel
0fb6fcbb12 dependency update + adjust Playwright setup + fix linter issue (#3994)
update dependencies before next release

---------

Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
2025-12-28 12:14:31 +01:00
Karsten Hassel
49620781c2 demo with gif (#3995)
add demo.gif to README.md
2025-12-27 18:00:48 -06:00
Kristjan ESPERANTO
9d3b07db12 [core] fix: allow browser globals in config files (#3992)
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.
2025-12-21 12:44:03 +01:00
sam detweiler
9c25b15f6a add checksum to test whether calendar event list changed (#3988)
for issue #3971 add checksum to test if event list changed to
reduce/eliminate no change screen update
fixes #3971

crc32 checksum created in node helper, easy require, vs trying to do in
browser.
added to socket notification payload, used in browser
2025-12-18 20:57:24 +01:00