mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-07-06 22:32:31 -07:00
4b4a59534f
## Release Notes Thanks to: @angeldeejay, @egeekial, @khassel, @KristjanESPERANTO, @MikeBishop, @rejas > ⚠️ This release needs nodejs version >=22.21.1 <23 || >=24 (no change to previous release) [Compare to previous Release v2.36.0](https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.36.0...develop) ### [core] - Prepare Release 2.37.0 (#4193) - fix(electron): map IPv6 :: wildcard to localhost (#4188) - refactor(main): modernize DOM update flow with async/await (#4186) - refactor(main): simplify _updateDom with async/await (#4185) - fix(security): prevent unauthorized secret expansion in socket payloads (#4184) - refactor(main): simplify updateDomWithContent async flow (#4182) - fix: modules losing data after HTTP 304 responses (#4180) - chore: add missing core defaults (#4181) - fix(server): enforce ipWhitelist for Socket.IO too (#4169) - feat(systeminfo): include Git hash and branch in system information log (#4167) - feat(electron): support object-based electronSwitches (#4161) - systeminformation thread not ending: move error handling from utils to app (#4160) - fix systeminformation thread not ending (#4155) - refactor: use ES module imports in browser core (#4158) - refactor(core): remove old Object.assign polyfill (#4157) - refactor: rewrite Module as an ES6 class (#4151) - refactor: rewrite NodeHelper as an ES6 class (#4147) - update eletron to v42 (#4144) - refactor(utils): drop ajv dependency (#4142) - fix(systeminformation): output right 'used node' version (from parent process) (#4141) - fix: skip postinstall git clean when not in a git repository (#4139) - Remove unnecessary conditionals and fix falsy property check in imperial conversion (#4135) - update version in package.json ### [dependencies] - update dependencies (#4191) - Bump actions/checkout from 6 to 7 (#4190) - chore: update dependencies and adjust import path for SunCalc (#4189) - update dependencies incl. electron and revert yauzl-electron-install-fix (#4183) - update dependencies, add electron fix in package.json (#4175) - chore: update dependencies (#4162) - Bump actions/dependency-review-action from 4 to 5 (#4152) - Unify linting: replace Stylelint and markdownlint with ESLint (#4148) - update dependencies and workflows to node v26 (#4140) ### [modules/alert] - CodeQL cleanup for alerts #18, #19, #20 (#4153) - fix: resolve CodeQL alerts #24 and #26 (#4145) - fix(electron): resolve CodeQL alerts #22 and #25 in electron.js (#4136) ### [modules/calendar] - perf(calendar): pre-filter ICS data before parsing (#4168) - perf(calendar): use async ICS parsing to avoid blocking event loop (#4143) ### [modules/newsfeed] - [newsfeed] add allowBasicHtmlTags option for basic emphasis (#4176) ### [modules/updatenotification] - fix(updatenotification): don't spawn a child process when running under PM2 (#4166) - fix(updatenotification): use process.argv[0] as restart binary (#4163) - fix(updatenotification): preserve start mode on restart (#4156) - fix(updatenotification): fix ref diff parsing for fetch --dry-run (#4138) - refactor(updatenotification): replace pm2 usage with node logic (#4134) ### [modules/weather] - feat(weather): add Buienradar provider (#4164) ### [testing] - remove warning in unit tests (for nodejs >= v25) (#4149) - polish HTTP 304 docs/test/handling (#4129) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: BugHaver <43462320+bughaver@users.noreply.github.com> Co-authored-by: BugHaver <43462320+lsaadeh@users.noreply.github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Koen Konst <koenspero@gmail.com> Co-authored-by: Koen Konst <c.h.konst@avisi.nl> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: dathbe <github@beffa.us> Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marcel <m-idler@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: Kevin G. <crazylegstoo@gmail.com> Co-authored-by: Jboucly <33218155+jboucly@users.noreply.github.com> Co-authored-by: Jboucly <contact@jboucly.fr> Co-authored-by: Jarno <54169345+jarnoml@users.noreply.github.com> Co-authored-by: Jordan Welch <JordanHWelch@gmail.com> Co-authored-by: Blackspirits <blackspirits@gmail.com> Co-authored-by: Samed Ozdemir <samed@xsor.io> Co-authored-by: in-voker <58696565+in-voker@users.noreply.github.com> Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com> Co-authored-by: cgillinger <christian.gillinger@gmail.com> Co-authored-by: Sonny B <43247590+sonnyb9@users.noreply.github.com> Co-authored-by: sonnyb9 <sonnyb9@users.noreply.github.com> Co-authored-by: Morgan McBee <egeekial@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Mike Bishop <mbishop@evequefou.be>
142 lines
4.3 KiB
JavaScript
142 lines
4.3 KiB
JavaScript
const ical = require("node-ical");
|
|
const Log = require("logger");
|
|
const CalendarFetcherUtils = require("./calendarfetcherutils");
|
|
const HTTPFetcher = require("#http_fetcher");
|
|
|
|
/**
|
|
* CalendarFetcher - Fetches and parses iCal calendar data
|
|
* Uses HTTPFetcher for HTTP handling with intelligent error handling
|
|
* @class
|
|
*/
|
|
class CalendarFetcher {
|
|
|
|
/**
|
|
* Creates a new CalendarFetcher instance
|
|
* @param {string} url - The URL of the calendar to fetch
|
|
* @param {number} reloadInterval - Time in ms between fetches
|
|
* @param {string[]} excludedEvents - Event titles to exclude
|
|
* @param {number} maximumEntries - Maximum number of events to return
|
|
* @param {number} maximumNumberOfDays - Maximum days in the future to fetch
|
|
* @param {object} auth - Authentication options {method: 'basic'|'bearer', user, pass}
|
|
* @param {boolean} includePastEvents - Whether to include past events
|
|
* @param {boolean} selfSignedCert - Whether to accept self-signed certificates
|
|
*/
|
|
constructor (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents, selfSignedCert) {
|
|
this.url = url;
|
|
this.excludedEvents = excludedEvents;
|
|
this.maximumEntries = maximumEntries;
|
|
this.maximumNumberOfDays = maximumNumberOfDays;
|
|
this.includePastEvents = includePastEvents;
|
|
|
|
this.events = [];
|
|
this.lastFetch = null;
|
|
this.fetchFailedCallback = () => {};
|
|
this.eventsReceivedCallback = () => {};
|
|
|
|
// Use HTTPFetcher for HTTP handling (Composition)
|
|
this.httpFetcher = new HTTPFetcher(url, {
|
|
reloadInterval,
|
|
auth,
|
|
selfSignedCert
|
|
});
|
|
|
|
// Wire up HTTPFetcher events
|
|
this.httpFetcher.on("response", (response) => this.#handleResponse(response));
|
|
this.httpFetcher.on("error", (errorInfo) => this.fetchFailedCallback(this, errorInfo));
|
|
}
|
|
|
|
/**
|
|
* Handles successful HTTP response
|
|
* @param {Response} response - The fetch Response object
|
|
*/
|
|
async #handleResponse (response) {
|
|
try {
|
|
// 304 Not Modified has no body: keep previously fetched events and just re-broadcast them.
|
|
if (response.status === 304) {
|
|
this.lastFetch = Date.now();
|
|
this.broadcastEvents();
|
|
return;
|
|
}
|
|
|
|
const responseData = await response.text();
|
|
|
|
const filteredData = await CalendarFetcherUtils.preFilterICS(responseData, {
|
|
includePastEvents: this.includePastEvents,
|
|
maximumNumberOfDays: this.maximumNumberOfDays
|
|
});
|
|
|
|
const parsed = await ical.async.parseICS(filteredData);
|
|
|
|
Log.debug(`Parsed iCal data from ${this.url} with ${Object.keys(parsed).length} entries.`);
|
|
|
|
this.events = CalendarFetcherUtils.filterEvents(parsed, {
|
|
excludedEvents: this.excludedEvents,
|
|
includePastEvents: this.includePastEvents,
|
|
maximumEntries: this.maximumEntries,
|
|
maximumNumberOfDays: this.maximumNumberOfDays
|
|
});
|
|
|
|
this.lastFetch = Date.now();
|
|
this.broadcastEvents();
|
|
} catch (error) {
|
|
Log.error(`${this.url} - iCal parsing failed: ${error.message}`);
|
|
this.fetchFailedCallback(this, {
|
|
message: `iCal parsing failed: ${error.message}`,
|
|
status: null,
|
|
errorType: "PARSE_ERROR",
|
|
translationKey: "MODULE_ERROR_UNSPECIFIED",
|
|
retryAfter: this.httpFetcher.reloadInterval,
|
|
retryCount: 0,
|
|
url: this.url,
|
|
originalError: error
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts fetching calendar data
|
|
*/
|
|
fetchCalendar () {
|
|
this.httpFetcher.startPeriodicFetch();
|
|
}
|
|
|
|
/**
|
|
* Check if enough time has passed since the last fetch to warrant a new one.
|
|
* Uses reloadInterval as the threshold to respect user's configured fetchInterval.
|
|
* @returns {boolean} True if a new fetch should be performed
|
|
*/
|
|
shouldRefetch () {
|
|
if (!this.lastFetch) {
|
|
return true;
|
|
}
|
|
const timeSinceLastFetch = Date.now() - this.lastFetch;
|
|
return timeSinceLastFetch >= this.httpFetcher.reloadInterval;
|
|
}
|
|
|
|
/**
|
|
* Broadcasts the current events to listeners
|
|
*/
|
|
broadcastEvents () {
|
|
Log.info(`Broadcasting ${this.events.length} events from ${this.url}.`);
|
|
this.eventsReceivedCallback(this);
|
|
}
|
|
|
|
/**
|
|
* Sets the callback for successful event fetches
|
|
* @param {(fetcher: CalendarFetcher) => void} callback - Called when events are received
|
|
*/
|
|
onReceive (callback) {
|
|
this.eventsReceivedCallback = callback;
|
|
}
|
|
|
|
/**
|
|
* Sets the callback for fetch failures
|
|
* @param {(fetcher: CalendarFetcher, error: Error) => void} callback - Called when a fetch fails
|
|
*/
|
|
onError (callback) {
|
|
this.fetchFailedCallback = callback;
|
|
}
|
|
}
|
|
|
|
module.exports = CalendarFetcher;
|