MagicMirror/modules/default/utils.js
sam detweiler c24de64d77
Release 2.30.0 (#3673)
## [2.30.0] - 2025-01-01

Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel,
@KristjanESPERANTO, @rejas, @sdetweil.

> ⚠️ This release needs nodejs version `v20` or `v22 or higher`, minimum
version is `v20.18.1`

### Added

- [core] Add wayland and windows start options to `package.json` (#3594)
- [docs] Add step for npm publishing in release process (#3595)
- [core] Add GitHub workflow to run spellcheck a few days before each
release (#3623)
- [core] Add test flag to `index.html` to pass to module js for test
mode detection (needed by #3630)
- [core] Add export on animation names (#3644)
- [compliments] Add support for refreshing remote compliments file, and
test cases (#3630)
- [linter] Re-add `eslint-plugin-import`now that it supports ESLint v9
(#3586)
- [linter] Re-activate `eslint-plugin-package-json` to lint
`package.json` (#3643)
- [linter] Add linting for markdown files (#3646)
- [linter] Add some handy ESLint rules.
- [calendar] Add ability to display end date for full date events, where
end is not same day (showEnd=true) (#3650)
- [core] Add text to the config.js.sample file about the locale variable
(#3654, #3655)
- [core] Add fetch timeout for all node_helpers (thru undici, forces
node 20.18.1 minimum) to help on slower systems. (#3660) (3661)

### Changed

- [core] Run code style checks in workflow only once (#3648)
- [core] Fix animations export #3644 only on server side (#3649)
- [core] Use project URL in fallback config (#3656)
- [core] Fix Access Denied crash writing js/positions.js (on synology
nas) #3651. new message, MM starts, but no modules showing (#3652)
- [linter] Switch to 'npx' for lint-staged in pre-commit hook (#3658)

### Removed

- [tests] Remove `node-pty` and `drivelist` from rebuilded test (#3575)
- [deps] Remove `@eslint/js` dependency. Already installed with `eslint`
in deep (#3636)

### Updated

- [repo] Reactivate `stale.yaml` as GitHub action to mark issues as
stale after 60 days and close them 7 days later (if no activity) (#3577,
#3580, #3581)
- [core] Update electron dependency to v32 (test electron rebuild) and
all other dependencies too (#3657)
- [tests] All test configs have been updated to allow full external
access, allowing for easier debugging (especially when running as a
container)
- [core] Run and test with node 23 (#3588)
- [workflow] delete exception `allow-ghsas: GHSA-8hc4-vh64-cxmj` in
`dep-review.yaml` (#3659)

### Fixed

- [updatenotification] Fix pm2 using detection when pm2 script is inside
or outside MagicMirror root folder (#3576) (#3605) (#3626) (#3628)
- [core] Fix loading node_helper of modules: avoid black screen, display
errors and continue loading with next module (#3578)
- [weather] Change default value for weatherEndpoint of provider
openweathermap to "/onecall" (#3574)
- [tests] Fix electron tests with mock dates, the mock on server side
was missing (#3597)
- [tests] Fix testcases with hard coded Date.now (#3597)
- [core] Fix missing `basePath` where `location.host` is used (#3613)
- [compliments] croner library changed filenames used in latest version
(#3624)
- [linter] Fix ESLint ignore pattern which caused that default modules
not to be linted (#3632)
- [core] Fix module path in case of sub/sub folder is used and use
path.resolve for resolve `moduleFolder` and `defaultModuleFolder` in
app.js (#3653)
- [calendar] Update to resolve issues #3098 #3144 #3351 #3422 #3443
#3467 #3537 related to timezone changes
- [calendar] Fix #3267 (styles array), also fixes event with both exdate
AND recurrence(and testcase)
- [calendar] Fix showEndsOnlyWithDuration not working, #3598, applies
ONLY to full day events
- [calendar] Fix showEnd for Full Day events (#3602)
- [tests] Suppress "module is not defined" in e2e tests (#3647)
- [calendar] Fix #3267 (styles array, really this time!)
- [core] Fix #3662 js/positions.js created incorrectly

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Michael Teeuw <michael@xonaymedia.nl>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Karsten Hassel <hassel@gmx.de>
Co-authored-by: Ross Younger <crazyscot@gmail.com>
Co-authored-by: Veeck <github@veeck.de>
Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr>
Co-authored-by: jkriegshauser <joshuakr@nvidia.com>
Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com>
Co-authored-by: vppencilsharpener <tim.pray@gmail.com>
Co-authored-by: veeck <michael.veeck@nebenan.de>
Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com>
Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com>
Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com>
Co-authored-by: Jason Stieber <jrstieber@gmail.com>
Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com>
Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com>
Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com>
Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com>
Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com>
Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com>
Co-authored-by: Pedro Lamas <pedrolamas@gmail.com>
Co-authored-by: veeck <gitkraken@veeck.de>
2025-01-01 08:27:27 -06:00

173 lines
5.8 KiB
JavaScript

/**
* A function to make HTTP requests via the server to avoid CORS-errors.
* @param {string} url the url to fetch from
* @param {string} type what contenttype to expect in the response, can be "json" or "xml"
* @param {boolean} useCorsProxy A flag to indicate
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
* @param {string} basePath, default /
* @returns {Promise} resolved when the fetch is done. The response headers is placed in a headers-property (provided the response does not already contain a headers-property).
*/
async function performWebRequest (url, type = "json", useCorsProxy = false, requestHeaders = undefined, expectedResponseHeaders = undefined, basePath = "/") {
const request = {};
let requestUrl;
if (useCorsProxy) {
requestUrl = getCorsUrl(url, requestHeaders, expectedResponseHeaders, basePath);
} else {
requestUrl = url;
request.headers = getHeadersToSend(requestHeaders);
}
const response = await fetch(requestUrl, request);
const data = await response.text();
if (type === "xml") {
return new DOMParser().parseFromString(data, "text/html");
} else {
if (!data || !data.length > 0) return undefined;
const dataResponse = JSON.parse(data);
if (!dataResponse.headers) {
dataResponse.headers = getHeadersFromResponse(expectedResponseHeaders, response);
}
return dataResponse;
}
}
/**
* Gets a URL that will be used when calling the CORS-method on the server.
* @param {string} url the url to fetch from
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
* @param {string} basePath, default /
* @returns {string} to be used as URL when calling CORS-method on server.
*/
const getCorsUrl = function (url, requestHeaders, expectedResponseHeaders, basePath = "/") {
if (!url || url.length < 1) {
throw new Error(`Invalid URL: ${url}`);
} else {
let corsUrl = `${location.protocol}//${location.host}${basePath}cors?`;
const requestHeaderString = getRequestHeaderString(requestHeaders);
if (requestHeaderString) corsUrl = `${corsUrl}sendheaders=${requestHeaderString}`;
const expectedResponseHeadersString = getExpectedResponseHeadersString(expectedResponseHeaders);
if (requestHeaderString && expectedResponseHeadersString) {
corsUrl = `${corsUrl}&expectedheaders=${expectedResponseHeadersString}`;
} else if (expectedResponseHeadersString) {
corsUrl = `${corsUrl}expectedheaders=${expectedResponseHeadersString}`;
}
if (requestHeaderString || expectedResponseHeadersString) {
return `${corsUrl}&url=${url}`;
}
return `${corsUrl}url=${url}`;
}
};
/**
* Gets the part of the CORS URL that represents the HTTP headers to send.
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
* @returns {string} to be used as request-headers component in CORS URL.
*/
const getRequestHeaderString = function (requestHeaders) {
let requestHeaderString = "";
if (requestHeaders) {
for (const header of requestHeaders) {
if (requestHeaderString.length === 0) {
requestHeaderString = `${header.name}:${encodeURIComponent(header.value)}`;
} else {
requestHeaderString = `${requestHeaderString},${header.name}:${encodeURIComponent(header.value)}`;
}
}
return requestHeaderString;
}
return undefined;
};
/**
* Gets headers and values to attach to the web request.
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
* @returns {object} An object specifying name and value of the headers.
*/
const getHeadersToSend = (requestHeaders) => {
const headersToSend = {};
if (requestHeaders) {
for (const header of requestHeaders) {
headersToSend[header.name] = header.value;
}
}
return headersToSend;
};
/**
* Gets the part of the CORS URL that represents the expected HTTP headers to receive.
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
* @returns {string} to be used as the expected HTTP-headers component in CORS URL.
*/
const getExpectedResponseHeadersString = function (expectedResponseHeaders) {
let expectedResponseHeadersString = "";
if (expectedResponseHeaders) {
for (const header of expectedResponseHeaders) {
if (expectedResponseHeadersString.length === 0) {
expectedResponseHeadersString = `${header}`;
} else {
expectedResponseHeadersString = `${expectedResponseHeadersString},${header}`;
}
}
return expectedResponseHeaders;
}
return undefined;
};
/**
* Gets the values for the expected headers from the response.
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
* @param {Response} response the HTTP response
* @returns {string} to be used as the expected HTTP-headers component in CORS URL.
*/
const getHeadersFromResponse = (expectedResponseHeaders, response) => {
const responseHeaders = [];
if (expectedResponseHeaders) {
for (const header of expectedResponseHeaders) {
const headerValue = response.headers.get(header);
responseHeaders.push({ name: header, value: headerValue });
}
}
return responseHeaders;
};
/**
* Format the time according to the config
* @param {object} config The config of the module
* @param {object} time time to format
* @returns {string} The formatted time string
*/
const formatTime = (config, time) => {
let date = moment(time);
if (config.timezone) {
date = date.tz(config.timezone);
}
if (config.timeFormat !== 24) {
if (config.showPeriod) {
if (config.showPeriodUpper) {
return date.format("h:mm A");
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm");
}
}
return date.format("HH:mm");
};
if (typeof module !== "undefined") module.exports = {
performWebRequest,
formatTime
};