fix(ipc): fallback to postMessage if protocol fails, closes #8476 (#9038)

This commit is contained in:
Lucas Fernandes Nogueira 2024-02-29 18:24:43 -03:00 committed by GitHub
parent c68218b362
commit a77be97474
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 88 additions and 87 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---
Fallback to the postMessage IPC interface if we cannot reach the IPC custom protocol.

View File

@ -217,11 +217,6 @@ fn main() {
alias("desktop", !mobile); alias("desktop", !mobile);
alias("mobile", mobile); alias("mobile", mobile);
alias(
"ipc_custom_protocol",
target_os != "android" && (target_os != "linux" || has_feature("linux-ipc-protocol")),
);
let out_dir = PathBuf::from(var("OUT_DIR").unwrap()); let out_dir = PathBuf::from(var("OUT_DIR").unwrap());
let checked_features_out_path = out_dir.join("checked_features"); let checked_features_out_path = out_dir.join("checked_features");

View File

@ -6,72 +6,73 @@
const processIpcMessage = __RAW_process_ipc_message_fn__ const processIpcMessage = __RAW_process_ipc_message_fn__
const osName = __TEMPLATE_os_name__ const osName = __TEMPLATE_os_name__
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__ const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
const useCustomProtocol = __TEMPLATE_use_custom_protocol__ const linuxIpcProtocolEnabled = __TEMPLATE_linux_ipc_protocol_enabled__
let customProtocolIpcFailed = false
Object.defineProperty(window.__TAURI_INTERNALS__, 'postMessage', { // on Linux we only use the custom-protocol-based IPC if the the linux-ipc-protocol Cargo feature is enabled
value: (message) => { // on Android we never use it because Android does not have support to reading the request body
const { cmd, callback, error, payload, options } = message const canUseCustomProtocol =
osName === 'linux' ? linuxIpcProtocolEnabled : osName !== 'android'
// use custom protocol for IPC if: function sendIpcMessage(message) {
// - the flag is set to true or const { cmd, callback, error, payload, options } = message
// - the command is the fetch data command or
// - when not on Linux/Android if (
// AND !customProtocolIpcFailed &&
// - when not on macOS with an https URL (canUseCustomProtocol || cmd === fetchChannelDataCommand)
if ( ) {
(useCustomProtocol || const { contentType, data } = processIpcMessage(payload)
cmd === fetchChannelDataCommand || fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), {
!(osName === 'linux' || osName === 'android')) && method: 'POST',
!( body: data,
(osName === 'macos' || osName === 'ios') && headers: {
location.protocol === 'https:' 'Content-Type': contentType,
) 'Tauri-Callback': callback,
) { 'Tauri-Error': error,
const { contentType, data } = processIpcMessage(payload) ...options?.headers
fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), { }
method: 'POST', })
body: data, .then((response) => {
headers: { const cb = response.ok ? callback : error
'Content-Type': contentType, // we need to split here because on Android the content-type gets duplicated
'Tauri-Callback': callback, switch ((response.headers.get('content-type') || '').split(',')[0]) {
'Tauri-Error': error, case 'application/json':
...options?.headers return response.json().then((r) => [cb, r])
case 'text/plain':
return response.text().then((r) => [cb, r])
default:
return response.arrayBuffer().then((r) => [cb, r])
} }
}) })
.then((response) => { .then(([cb, data]) => {
const cb = response.ok ? callback : error if (window[`_${cb}`]) {
// we need to split here because on Android the content-type gets duplicated window[`_${cb}`](data)
switch ( } else {
(response.headers.get('content-type') || '').split(',')[0] console.warn(
) { `[TAURI] Couldn't find callback id {cb} in window. This might happen when the app is reloaded while Rust is running an asynchronous operation.`
case 'application/json': )
return response.json().then((r) => [cb, r]) }
case 'text/plain':
return response.text().then((r) => [cb, r])
default:
return response.arrayBuffer().then((r) => [cb, r])
}
})
.then(([cb, data]) => {
if (window[`_${cb}`]) {
window[`_${cb}`](data)
} else {
console.warn(
`[TAURI] Couldn't find callback id {cb} in window. This might happen when the app is reloaded while Rust is running an asynchronous operation.`
)
}
})
} else {
// otherwise use the postMessage interface
const { data } = processIpcMessage({
cmd,
callback,
error,
options,
payload
}) })
window.ipc.postMessage(data) .catch(() => {
} // failed to use the custom protocol IPC (either the webview blocked a custom protocol or it was a CSP error)
// so we need to fallback to the postMessage interface
customProtocolIpcFailed = true
sendIpcMessage(message)
})
} else {
// otherwise use the postMessage interface
const { data } = processIpcMessage({
cmd,
callback,
error,
options,
payload
})
window.ipc.postMessage(data)
} }
}
Object.defineProperty(window.__TAURI_INTERNALS__, 'postMessage', {
value: sendIpcMessage
}) })
})() })()

View File

@ -1084,7 +1084,7 @@ struct InvokeInitializationScript<'a> {
process_ipc_message_fn: &'a str, process_ipc_message_fn: &'a str,
os_name: &'a str, os_name: &'a str,
fetch_channel_data_command: &'a str, fetch_channel_data_command: &'a str,
use_custom_protocol: bool, linux_ipc_protocol_enabled: bool,
} }
/// Make `Wry` the default `Runtime` for `Builder` /// Make `Wry` the default `Runtime` for `Builder`
@ -1117,7 +1117,7 @@ impl<R: Runtime> Builder<R> {
process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN, process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,
os_name: std::env::consts::OS, os_name: std::env::consts::OS,
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND, fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
use_custom_protocol: cfg!(ipc_custom_protocol), linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"),
} }
.render_default(&Default::default()) .render_default(&Default::default())
.unwrap() .unwrap()

View File

@ -21,7 +21,6 @@ use crate::{webview::Webview, Runtime, StateManager};
mod authority; mod authority;
pub(crate) mod channel; pub(crate) mod channel;
mod command; mod command;
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
pub(crate) mod format_callback; pub(crate) mod format_callback;
pub(crate) mod protocol; pub(crate) mod protocol;

View File

@ -19,7 +19,6 @@ use super::{CallbackFn, InvokeBody, InvokeResponse};
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback"; const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error"; const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
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> {
@ -162,7 +161,6 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
}) })
} }
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, label: &str) { fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, label: &str) {
if let Some(webview) = manager.get_webview(label) { if let Some(webview) = manager.get_webview(label) {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@ -374,15 +372,21 @@ fn parse_invoke_request<R: Runtime>(
.decode_utf8_lossy() .decode_utf8_lossy()
.to_string(); .to_string();
// the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it // on Android and on Linux (without the linux-ipc-protocol Cargo feature) we cannot read the request body
#[cfg(all(feature = "isolation", ipc_custom_protocol))] // so we must ignore it because some commands use the IPC for faster response
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern { let has_payload = !body.is_empty();
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) #[cfg(feature = "isolation")]
.and_then(|raw| crypto_keys.decrypt(raw)) if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
.map_err(|e| e.to_string())?; // if the platform does not support request body, we ignore it
if has_payload {
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body)
.and_then(|raw| crypto_keys.decrypt(raw))
.map_err(|e| e.to_string())?;
}
} }
let callback = CallbackFn( let callback = CallbackFn(
@ -420,12 +424,12 @@ fn parse_invoke_request<R: Runtime>(
let body = if content_type == mime::APPLICATION_OCTET_STREAM { let body = if content_type == mime::APPLICATION_OCTET_STREAM {
body.into() body.into()
} else if content_type == mime::APPLICATION_JSON { } else if content_type == mime::APPLICATION_JSON {
if cfg!(ipc_custom_protocol) { // if the platform does not support request body, we ignore it
if has_payload {
serde_json::from_slice::<serde_json::Value>(&body) serde_json::from_slice::<serde_json::Value>(&body)
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.into() .into()
} else { } else {
// the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it
serde_json::Value::Object(Default::default()).into() serde_json::Value::Object(Default::default()).into()
} }
} else { } else {

View File

@ -496,12 +496,9 @@ impl<R: Runtime> WebviewManager<R> {
manager, manager,
)?; )?;
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))] pending.ipc_handler = Some(crate::ipc::protocol::message_handler(
{ manager.manager_owned(),
pending.ipc_handler = Some(crate::ipc::protocol::message_handler( ));
manager.manager_owned(),
));
}
// in `windows`, we need to force a data_directory // in `windows`, we need to force a data_directory
// but we do respect user-specification // but we do respect user-specification