mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-12-01 10:31:45 +00:00
[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:
@@ -15,6 +15,7 @@ planned for 2026-01-01
|
||||
|
||||
- [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)
|
||||
- [weather] add error handling to fetch functions including cors (#3791)
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -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.
|
||||
* @param {Request} req - the request
|
||||
* @param {Response} res - the result
|
||||
* @returns {Promise<void>} A promise that resolves when the response is sent
|
||||
*/
|
||||
async function cors (req, res) {
|
||||
try {
|
||||
@@ -40,29 +41,32 @@ async function cors (req, res) {
|
||||
if (!match) {
|
||||
url = `invalid url: ${req.url}`;
|
||||
Log.error(url);
|
||||
res.send(url);
|
||||
return res.status(400).send(url);
|
||||
} else {
|
||||
url = match[1];
|
||||
|
||||
const headersToSend = getHeadersToSend(req.url);
|
||||
const expectedReceivedHeaders = geExpectedReceivedHeaders(req.url);
|
||||
|
||||
Log.log(`cors url: ${url}`);
|
||||
const response = await fetch(url, { headers: headersToSend });
|
||||
|
||||
for (const header of expectedReceivedHeaders) {
|
||||
const headerValue = response.headers.get(header);
|
||||
if (header) res.set(header, headerValue);
|
||||
const response = await fetch(url, { headers: headersToSend });
|
||||
if (response.ok) {
|
||||
for (const header of expectedReceivedHeaders) {
|
||||
const headerValue = response.headers.get(header);
|
||||
if (header) res.set(header, headerValue);
|
||||
}
|
||||
const data = await response.text();
|
||||
res.send(data);
|
||||
} else {
|
||||
throw new Error(`Response status: ${response.status}`);
|
||||
}
|
||||
const data = await response.text();
|
||||
res.send(data);
|
||||
}
|
||||
} catch (error) {
|
||||
// Only log errors in non-test environments to keep test output clean
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,29 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
|
||||
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;
|
||||
try {
|
||||
const response = await fetch(requestUrl, request);
|
||||
if (response.ok) {
|
||||
const data = await response.text();
|
||||
|
||||
const dataResponse = JSON.parse(data);
|
||||
if (!dataResponse.headers) {
|
||||
dataResponse.headers = getHeadersFromResponse(expectedResponseHeaders, response);
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Response status: ${response.status}`);
|
||||
}
|
||||
return dataResponse;
|
||||
} catch (error) {
|
||||
Log.error(`Error fetching data from ${url}: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,38 +22,36 @@ WeatherProvider.register("pirateweather", {
|
||||
lon: 0
|
||||
},
|
||||
|
||||
fetchCurrentWeather () {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
|
||||
// No usable data?
|
||||
return;
|
||||
}
|
||||
async fetchCurrentWeather () {
|
||||
try {
|
||||
const data = await this.fetchData(this.getUrl());
|
||||
if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
|
||||
throw new Error("No usable data received from Pirate Weather API.");
|
||||
}
|
||||
|
||||
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("[weatherprovider.pirateweather] Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable());
|
||||
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
} catch (error) {
|
||||
Log.error("Could not load data ... ", error);
|
||||
} finally {
|
||||
this.updateAvailable();
|
||||
}
|
||||
},
|
||||
|
||||
fetchWeatherForecast () {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
if (!data || !data.daily || !data.daily.data.length) {
|
||||
// No usable data?
|
||||
return;
|
||||
}
|
||||
async fetchWeatherForecast () {
|
||||
try {
|
||||
const data = await this.fetchData(this.getUrl());
|
||||
if (!data || !data.daily || !data.daily.data.length) {
|
||||
throw new Error("No usable data received from Pirate Weather API.");
|
||||
}
|
||||
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
|
||||
this.setWeatherForecast(forecast);
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("[weatherprovider.pirateweather] Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable());
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
|
||||
this.setWeatherForecast(forecast);
|
||||
} catch (error) {
|
||||
Log.error("Could not load data ... ", error);
|
||||
} finally {
|
||||
this.updateAvailable();
|
||||
}
|
||||
},
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
|
||||
@@ -16,7 +16,8 @@ describe("server_functions tests", () => {
|
||||
headers: {
|
||||
get: fetchResponseHeadersGet
|
||||
},
|
||||
text: fetchResponseHeadersText
|
||||
text: fetchResponseHeadersText,
|
||||
ok: true
|
||||
};
|
||||
|
||||
fetch = vi.fn();
|
||||
@@ -26,7 +27,12 @@ describe("server_functions tests", () => {
|
||||
|
||||
corsResponse = {
|
||||
set: vi.fn(() => {}),
|
||||
send: vi.fn(() => {})
|
||||
send: vi.fn(() => {}),
|
||||
status: vi.fn(function (code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
}),
|
||||
json: vi.fn(() => {})
|
||||
};
|
||||
|
||||
request = {
|
||||
@@ -91,15 +97,11 @@ describe("server_functions tests", () => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
let sentData;
|
||||
corsResponse.send = vi.fn((input) => {
|
||||
sentData = input;
|
||||
});
|
||||
|
||||
await cors(request, corsResponse);
|
||||
|
||||
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 () => {
|
||||
|
||||
Reference in New Issue
Block a user