mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-04-24 06:47:07 +00:00
## 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 🙂
138 lines
4.3 KiB
JSON
138 lines
4.3 KiB
JSON
{
|
|
"name": "magicmirror",
|
|
"version": "2.35.0-develop",
|
|
"description": "The open source modular smart mirror platform.",
|
|
"keywords": [
|
|
"magic mirror",
|
|
"magicmirror",
|
|
"smart mirror",
|
|
"mirror UI",
|
|
"modular"
|
|
],
|
|
"homepage": "https://magicmirror.builders",
|
|
"bugs": {
|
|
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
|
|
},
|
|
"repository": {
|
|
"type": "git",
|
|
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
|
|
},
|
|
"license": "MIT",
|
|
"author": "Michael Teeuw",
|
|
"contributors": [
|
|
{
|
|
"name": "MagicMirror contributors",
|
|
"url": "https://github.com/MagicMirrorOrg/MagicMirror/graphs/contributors"
|
|
}
|
|
],
|
|
"type": "commonjs",
|
|
"imports": {
|
|
"#module_functions": {
|
|
"default": "./js/module_functions.js"
|
|
},
|
|
"#server_functions": {
|
|
"default": "./js/server_functions.js"
|
|
},
|
|
"#http_fetcher": {
|
|
"default": "./js/http_fetcher.js"
|
|
}
|
|
},
|
|
"main": "js/electron.js",
|
|
"scripts": {
|
|
"config:check": "node js/check_config.js",
|
|
"postinstall": "git clean -df fonts vendor",
|
|
"install-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev",
|
|
"install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier && npx playwright install chromium",
|
|
"lint:css": "stylelint 'css/main.css' 'css/roboto.css' 'css/font-awesome.css' 'modules/default/**/*.css' --fix",
|
|
"lint:js": "eslint --fix",
|
|
"lint:markdown": "markdownlint-cli2 . --fix",
|
|
"lint:prettier": "prettier . --write",
|
|
"prepare": "[ -f node_modules/.bin/husky ] && husky || echo no husky installed.",
|
|
"server": "node ./serveronly",
|
|
"server:watch": "node ./serveronly/watcher.js",
|
|
"start": "node --run start:wayland",
|
|
"start:dev": "node --run start:wayland -- dev",
|
|
"start:wayland": "WAYLAND_DISPLAY=\"${WAYLAND_DISPLAY:=wayland-1}\" ./node_modules/.bin/electron js/electron.js --ozone-platform=wayland",
|
|
"start:wayland:dev": "node --run start:wayland -- dev",
|
|
"start:windows": ".\\node_modules\\.bin\\electron js\\electron.js",
|
|
"start:windows:dev": "node --run start:windows -- dev",
|
|
"start:x11": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
|
|
"start:x11:dev": "node --run start:x11 -- dev",
|
|
"test": "vitest run",
|
|
"test:calendar": "node ./modules/default/calendar/debug.js",
|
|
"test:coverage": "vitest run --coverage",
|
|
"test:css": "stylelint 'css/main.css' 'css/roboto.css' 'css/font-awesome.css' 'modules/default/**/*.css'",
|
|
"test:e2e": "vitest run tests/e2e",
|
|
"test:electron": "vitest run tests/electron",
|
|
"test:js": "eslint",
|
|
"test:markdown": "markdownlint-cli2 .",
|
|
"test:prettier": "prettier . --check",
|
|
"test:spelling": "cspell . --gitignore",
|
|
"test:ui": "vitest --ui",
|
|
"test:unit": "vitest run tests/unit",
|
|
"test:watch": "vitest"
|
|
},
|
|
"lint-staged": {
|
|
"*": "prettier --ignore-unknown --write",
|
|
"*.js": "eslint --fix",
|
|
"*.css": "stylelint --fix"
|
|
},
|
|
"dependencies": {
|
|
"@fontsource/roboto": "^5.2.9",
|
|
"@fontsource/roboto-condensed": "^5.2.8",
|
|
"@fortawesome/fontawesome-free": "^7.1.0",
|
|
"ajv": "^8.17.1",
|
|
"animate.css": "^4.1.1",
|
|
"console-stamp": "^3.1.2",
|
|
"croner": "^9.1.0",
|
|
"envsub": "^4.1.0",
|
|
"eslint": "^9.39.2",
|
|
"express": "^5.2.1",
|
|
"feedme": "^2.0.2",
|
|
"helmet": "^8.1.0",
|
|
"html-to-text": "^9.0.5",
|
|
"iconv-lite": "^0.7.1",
|
|
"ipaddr.js": "^2.3.0",
|
|
"moment": "^2.30.1",
|
|
"moment-timezone": "^0.6.0",
|
|
"node-ical": "^0.23.1",
|
|
"nunjucks": "^3.2.4",
|
|
"pm2": "^6.0.14",
|
|
"socket.io": "^4.8.3",
|
|
"suncalc": "^1.9.0",
|
|
"systeminformation": "^5.30.1",
|
|
"undici": "^7.18.2",
|
|
"weathericons": "^2.1.0"
|
|
},
|
|
"devDependencies": {
|
|
"@stylistic/eslint-plugin": "^5.6.1",
|
|
"@vitest/coverage-v8": "^4.0.16",
|
|
"@vitest/eslint-plugin": "^1.6.6",
|
|
"@vitest/ui": "^4.0.16",
|
|
"cspell": "^9.4.0",
|
|
"eslint-plugin-import-x": "^4.16.1",
|
|
"eslint-plugin-jsdoc": "^61.5.0",
|
|
"eslint-plugin-package-json": "^0.88.1",
|
|
"eslint-plugin-playwright": "^2.4.0",
|
|
"express-basic-auth": "^1.2.1",
|
|
"husky": "^9.1.7",
|
|
"jsdom": "^27.4.0",
|
|
"lint-staged": "^16.2.7",
|
|
"markdownlint-cli2": "^0.20.0",
|
|
"msw": "^2.12.7",
|
|
"playwright": "^1.57.0",
|
|
"prettier": "^3.7.4",
|
|
"prettier-plugin-jinja-template": "^2.1.0",
|
|
"stylelint": "^16.26.1",
|
|
"stylelint-config-standard": "^39.0.1",
|
|
"stylelint-prettier": "^5.0.3",
|
|
"vitest": "^4.0.16"
|
|
},
|
|
"optionalDependencies": {
|
|
"electron": "^39.2.7"
|
|
},
|
|
"engines": {
|
|
"node": ">=22.21.1 <23 || >=24"
|
|
}
|
|
}
|