refactor(core): return 200 on any IPC call, closes #10286 (#10585)

* refactor(core): return 200 on any IPC call, closes #10286

By default the webview prints a `Failed to load resource: the server responded with a status of 400 (Bad Request) ipc://localhost` error message when a command returns an error, which is confusing to users.

This changes the IPC to return status 200 on any call, with a header to indicate whether the result was ok or not. This removes the console error, which would only log the actual error result if it isn't caught by the user.

* add change file

* apply code review changes
This commit is contained in:
Lucas Fernandes Nogueira 2024-08-13 08:39:20 -03:00 committed by GitHub
parent b1d9ffa1ab
commit fedf93eb7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 39 additions and 14 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch:changes
---
Change how IPC handles errors to simplify what's logged in the console.

View File

@ -42,7 +42,8 @@
} }
}) })
.then((response) => { .then((response) => {
const cb = response.ok ? callback : error const cb =
response.headers.get('Tauri-Response') === 'ok' ? callback : error
// we need to split here because on Android the content-type gets duplicated // we need to split here because on Android the content-type gets duplicated
switch ((response.headers.get('content-type') || '').split(',')[0]) { switch ((response.headers.get('content-type') || '').split(',')[0]) {
case 'application/json': case 'application/json':

View File

@ -10,7 +10,10 @@ use crate::{
Runtime, Runtime,
}; };
use http::{ use http::{
header::{ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}, header::{
ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
CONTENT_TYPE,
},
HeaderValue, Method, Request, StatusCode, HeaderValue, Method, Request, StatusCode,
}; };
use url::Url; use url::Url;
@ -21,6 +24,10 @@ const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error"; const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key"; const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key";
const TAURI_RESPONSE_HEADER_NAME: &str = "Tauri-Response";
const TAURI_RESPONSE_HEADER_ERROR: &str = "error";
const TAURI_RESPONSE_HEADER_OK: &str = "ok";
pub fn message_handler<R: Runtime>( pub fn message_handler<R: Runtime>(
manager: Arc<AppManager<R>>, manager: Arc<AppManager<R>>,
) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> { ) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {
@ -44,6 +51,10 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
response response
.headers_mut() .headers_mut()
.insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
response.headers_mut().insert(
ACCESS_CONTROL_EXPOSE_HEADERS,
HeaderValue::from_static(TAURI_RESPONSE_HEADER_NAME),
);
responder.respond(response); responder.respond(response);
}; };
@ -81,6 +92,11 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
) )
.entered(); .entered();
let response_header = match &response {
InvokeResponse::Ok(_) => TAURI_RESPONSE_HEADER_OK,
InvokeResponse::Err(_) => TAURI_RESPONSE_HEADER_ERROR,
};
let (mut response, mime_type) = match response { let (mut response, mime_type) = match response {
InvokeResponse::Ok(InvokeBody::Json(v)) => ( InvokeResponse::Ok(InvokeBody::Json(v)) => (
http::Response::new(serde_json::to_vec(&v).unwrap().into()), http::Response::new(serde_json::to_vec(&v).unwrap().into()),
@ -90,14 +106,16 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
http::Response::new(v.into()), http::Response::new(v.into()),
mime::APPLICATION_OCTET_STREAM, mime::APPLICATION_OCTET_STREAM,
), ),
InvokeResponse::Err(e) => { InvokeResponse::Err(e) => (
let mut response = http::Response::new(serde_json::to_vec(&e.0).unwrap().into()),
http::Response::new(serde_json::to_vec(&e.0).unwrap().into()); mime::APPLICATION_JSON,
*response.status_mut() = StatusCode::BAD_REQUEST; ),
(response, mime::APPLICATION_JSON)
}
}; };
response
.headers_mut()
.insert(TAURI_RESPONSE_HEADER_NAME, response_header.parse().unwrap());
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
response_span.record("mime_type", mime_type.essence_str()); response_span.record("mime_type", mime_type.essence_str());
@ -113,7 +131,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
Err(e) => { Err(e) => {
respond( respond(
http::Response::builder() http::Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::INTERNAL_SERVER_ERROR)
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
.body(e.as_bytes().to_vec().into()) .body(e.as_bytes().to_vec().into())
.unwrap(), .unwrap(),
@ -123,7 +141,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
} else { } else {
respond( respond(
http::Response::builder() http::Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::INTERNAL_SERVER_ERROR)
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
.body( .body(
"failed to acquire webview reference" "failed to acquire webview reference"
@ -140,6 +158,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
let mut r = http::Response::new(Vec::new().into()); let mut r = http::Response::new(Vec::new().into());
r.headers_mut() r.headers_mut()
.insert(ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static("*")); .insert(ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static("*"));
respond(r); respond(r);
} }

View File

@ -16,7 +16,7 @@ pub fn get(scope: scope::fs::Scope, window_origin: String) -> UriSchemeProtocolH
Ok(response) => responder.respond(response), Ok(response) => responder.respond(response),
Err(e) => responder.respond( Err(e) => responder.respond(
http::Response::builder() http::Response::builder()
.status(http::StatusCode::BAD_REQUEST) .status(http::StatusCode::INTERNAL_SERVER_ERROR)
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
.header("Access-Control-Allow-Origin", &window_origin) .header("Access-Control-Allow-Origin", &window_origin)
.body(e.to_string().as_bytes().to_vec()) .body(e.to_string().as_bytes().to_vec())

View File

@ -77,7 +77,7 @@ pub fn get<R: Runtime>(
} else { } else {
responder.respond( responder.respond(
http::Response::builder() http::Response::builder()
.status(http::StatusCode::BAD_REQUEST) .status(http::StatusCode::INTERNAL_SERVER_ERROR)
.body("failed to get response".as_bytes().to_vec()) .body("failed to get response".as_bytes().to_vec())
.unwrap(), .unwrap(),
); );

View File

@ -54,7 +54,7 @@ pub fn get<R: Runtime>(
Ok(response) => responder.respond(response), Ok(response) => responder.respond(response),
Err(e) => responder.respond( Err(e) => responder.respond(
HttpResponse::builder() HttpResponse::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::INTERNAL_SERVER_ERROR)
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
.header("Access-Control-Allow-Origin", &window_origin) .header("Access-Control-Allow-Origin", &window_origin)
.body(e.to_string().as_bytes().to_vec()) .body(e.to_string().as_bytes().to_vec())

View File

@ -42,7 +42,7 @@ fn main() {
Ok(http_response) => responder.respond(http_response), Ok(http_response) => responder.respond(http_response),
Err(e) => responder.respond( Err(e) => responder.respond(
ResponseBuilder::new() ResponseBuilder::new()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::INTERNAL_SERVER_ERROR)
.header(CONTENT_TYPE, "text/plain") .header(CONTENT_TYPE, "text/plain")
.body(e.to_string().as_bytes().to_vec()) .body(e.to_string().as_bytes().to_vec())
.unwrap(), .unwrap(),