[weather] add error handling to weather fetch functions, including cors (#3791)

Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
fixes #3687
This commit is contained in:
sam detweiler
2025-11-08 07:21:31 -06:00
committed by GitHub
parent 3b79791a6b
commit c1aaea5913
5 changed files with 71 additions and 56 deletions

View File

@@ -15,6 +15,7 @@ planned for 2026-01-01
- [weather] feat: add configurable forecast date format option (#3918) - [weather] feat: add configurable forecast date format option (#3918)
- [core] Add new `server:watch` script to run MagicMirror² server-only with automatic restarts when files (defined in `config.watchTargets`) change (#3920) - [core] Add new `server:watch` script to run MagicMirror² server-only with automatic restarts when files (defined in `config.watchTargets`) change (#3920)
- [weather] add error handling to fetch functions including cors (#3791)
### Removed ### Removed

View File

@@ -30,6 +30,7 @@ function getStartup (req, res) {
* Only the url-param of the input request url is required. It must be the last parameter. * Only the url-param of the input request url is required. It must be the last parameter.
* @param {Request} req - the request * @param {Request} req - the request
* @param {Response} res - the result * @param {Response} res - the result
* @returns {Promise<void>} A promise that resolves when the response is sent
*/ */
async function cors (req, res) { async function cors (req, res) {
try { try {
@@ -40,29 +41,32 @@ async function cors (req, res) {
if (!match) { if (!match) {
url = `invalid url: ${req.url}`; url = `invalid url: ${req.url}`;
Log.error(url); Log.error(url);
res.send(url); return res.status(400).send(url);
} else { } else {
url = match[1]; url = match[1];
const headersToSend = getHeadersToSend(req.url); const headersToSend = getHeadersToSend(req.url);
const expectedReceivedHeaders = geExpectedReceivedHeaders(req.url); const expectedReceivedHeaders = geExpectedReceivedHeaders(req.url);
Log.log(`cors url: ${url}`); Log.log(`cors url: ${url}`);
const response = await fetch(url, { headers: headersToSend });
const response = await fetch(url, { headers: headersToSend });
if (response.ok) {
for (const header of expectedReceivedHeaders) { for (const header of expectedReceivedHeaders) {
const headerValue = response.headers.get(header); const headerValue = response.headers.get(header);
if (header) res.set(header, headerValue); if (header) res.set(header, headerValue);
} }
const data = await response.text(); const data = await response.text();
res.send(data); res.send(data);
} else {
throw new Error(`Response status: ${response.status}`);
}
} }
} catch (error) { } catch (error) {
// Only log errors in non-test environments to keep test output clean // Only log errors in non-test environments to keep test output clean
if (process.env.mmTestMode !== "true") { if (process.env.mmTestMode !== "true") {
Log.error(error); Log.error(`Error in CORS request: ${error}`);
} }
res.send(error); res.status(500).json({ error: error.message });
} }
} }

View File

@@ -17,7 +17,10 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
requestUrl = url; requestUrl = url;
request.headers = getHeadersToSend(requestHeaders); request.headers = getHeadersToSend(requestHeaders);
} }
try {
const response = await fetch(requestUrl, request); const response = await fetch(requestUrl, request);
if (response.ok) {
const data = await response.text(); const data = await response.text();
if (type === "xml") { if (type === "xml") {
@@ -31,6 +34,13 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
} }
return dataResponse; return dataResponse;
} }
} else {
throw new Error(`Response status: ${response.status}`);
}
} catch (error) {
Log.error(`Error fetching data from ${url}: ${error}`);
return undefined;
}
} }
/** /**

View File

@@ -22,38 +22,36 @@ WeatherProvider.register("pirateweather", {
lon: 0 lon: 0
}, },
fetchCurrentWeather () { async fetchCurrentWeather () {
this.fetchData(this.getUrl()) try {
.then((data) => { const data = await this.fetchData(this.getUrl());
if (!data || !data.currently || typeof data.currently.temperature === "undefined") { if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
// No usable data? throw new Error("No usable data received from Pirate Weather API.");
return;
} }
const currentWeather = this.generateWeatherDayFromCurrentWeather(data); const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
this.setCurrentWeather(currentWeather); this.setCurrentWeather(currentWeather);
}) } catch (error) {
.catch(function (request) { Log.error("Could not load data ... ", error);
Log.error("[weatherprovider.pirateweather] Could not load data ... ", request); } finally {
}) this.updateAvailable();
.finally(() => this.updateAvailable()); }
}, },
fetchWeatherForecast () { async fetchWeatherForecast () {
this.fetchData(this.getUrl()) try {
.then((data) => { const data = await this.fetchData(this.getUrl());
if (!data || !data.daily || !data.daily.data.length) { if (!data || !data.daily || !data.daily.data.length) {
// No usable data? throw new Error("No usable data received from Pirate Weather API.");
return;
} }
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data); const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
this.setWeatherForecast(forecast); this.setWeatherForecast(forecast);
}) } catch (error) {
.catch(function (request) { Log.error("Could not load data ... ", error);
Log.error("[weatherprovider.pirateweather] Could not load data ... ", request); } finally {
}) this.updateAvailable();
.finally(() => this.updateAvailable()); }
}, },
// Create a URL from the config and base URL. // Create a URL from the config and base URL.

View File

@@ -16,7 +16,8 @@ describe("server_functions tests", () => {
headers: { headers: {
get: fetchResponseHeadersGet get: fetchResponseHeadersGet
}, },
text: fetchResponseHeadersText text: fetchResponseHeadersText,
ok: true
}; };
fetch = vi.fn(); fetch = vi.fn();
@@ -26,7 +27,12 @@ describe("server_functions tests", () => {
corsResponse = { corsResponse = {
set: vi.fn(() => {}), set: vi.fn(() => {}),
send: vi.fn(() => {}) send: vi.fn(() => {}),
status: vi.fn(function (code) {
this.statusCode = code;
return this;
}),
json: vi.fn(() => {})
}; };
request = { request = {
@@ -91,15 +97,11 @@ describe("server_functions tests", () => {
throw error; throw error;
}); });
let sentData;
corsResponse.send = vi.fn((input) => {
sentData = input;
});
await cors(request, corsResponse); await cors(request, corsResponse);
expect(fetchResponseHeadersText.mock.calls).toHaveLength(1); expect(fetchResponseHeadersText.mock.calls).toHaveLength(1);
expect(sentData).toBe(error); expect(corsResponse.status).toHaveBeenCalledWith(500);
expect(corsResponse.json).toHaveBeenCalledWith({ error: error.message });
}); });
it("Fetches with user agent by default", async () => { it("Fetches with user agent by default", async () => {