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)
|
- [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
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
|
|
||||||
for (const header of expectedReceivedHeaders) {
|
const response = await fetch(url, { headers: headersToSend });
|
||||||
const headerValue = response.headers.get(header);
|
if (response.ok) {
|
||||||
if (header) res.set(header, headerValue);
|
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) {
|
} 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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,29 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
|
|||||||
requestUrl = url;
|
requestUrl = url;
|
||||||
request.headers = getHeadersToSend(requestHeaders);
|
request.headers = getHeadersToSend(requestHeaders);
|
||||||
}
|
}
|
||||||
const response = await fetch(requestUrl, request);
|
|
||||||
const data = await response.text();
|
|
||||||
|
|
||||||
if (type === "xml") {
|
try {
|
||||||
return new DOMParser().parseFromString(data, "text/html");
|
const response = await fetch(requestUrl, request);
|
||||||
} else {
|
if (response.ok) {
|
||||||
if (!data || !data.length > 0) return undefined;
|
const data = await response.text();
|
||||||
|
|
||||||
const dataResponse = JSON.parse(data);
|
if (type === "xml") {
|
||||||
if (!dataResponse.headers) {
|
return new DOMParser().parseFromString(data, "text/html");
|
||||||
dataResponse.headers = getHeadersFromResponse(expectedResponseHeaders, response);
|
} 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
|
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.
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user