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("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 checked_features_out_path = out_dir.join("checked_features");

View File

@ -6,72 +6,73 @@
const processIpcMessage = __RAW_process_ipc_message_fn__
const osName = __TEMPLATE_os_name__
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', {
value: (message) => {
const { cmd, callback, error, payload, options } = message
// on Linux we only use the custom-protocol-based IPC if the the linux-ipc-protocol Cargo feature is enabled
// on Android we never use it because Android does not have support to reading the request body
const canUseCustomProtocol =
osName === 'linux' ? linuxIpcProtocolEnabled : osName !== 'android'
// use custom protocol for IPC if:
// - the flag is set to true or
// - the command is the fetch data command or
// - when not on Linux/Android
// AND
// - when not on macOS with an https URL
if (
(useCustomProtocol ||
cmd === fetchChannelDataCommand ||
!(osName === 'linux' || osName === 'android')) &&
!(
(osName === 'macos' || osName === 'ios') &&
location.protocol === 'https:'
)
) {
const { contentType, data } = processIpcMessage(payload)
fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), {
method: 'POST',
body: data,
headers: {
'Content-Type': contentType,
'Tauri-Callback': callback,
'Tauri-Error': error,
...options?.headers
function sendIpcMessage(message) {
const { cmd, callback, error, payload, options } = message
if (
!customProtocolIpcFailed &&
(canUseCustomProtocol || cmd === fetchChannelDataCommand)
) {
const { contentType, data } = processIpcMessage(payload)
fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), {
method: 'POST',
body: data,
headers: {
'Content-Type': contentType,
'Tauri-Callback': callback,
'Tauri-Error': error,
...options?.headers
}
})
.then((response) => {
const cb = response.ok ? callback : error
// we need to split here because on Android the content-type gets duplicated
switch ((response.headers.get('content-type') || '').split(',')[0]) {
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((response) => {
const cb = response.ok ? callback : error
// we need to split here because on Android the content-type gets duplicated
switch (
(response.headers.get('content-type') || '').split(',')[0]
) {
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
.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.`
)
}
})
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,
os_name: &'a str,
fetch_channel_data_command: &'a str,
use_custom_protocol: bool,
linux_ipc_protocol_enabled: bool,
}
/// 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,
os_name: std::env::consts::OS,
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())
.unwrap()

View File

@ -21,7 +21,6 @@ use crate::{webview::Webview, Runtime, StateManager};
mod authority;
pub(crate) mod channel;
mod command;
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
pub(crate) mod format_callback;
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_ERROR_HEADER_NAME: &str = "Tauri-Error";
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
pub fn message_handler<R: Runtime>(
manager: Arc<AppManager<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) {
if let Some(webview) = manager.get_webview(label) {
#[cfg(feature = "tracing")]
@ -374,15 +372,21 @@ fn parse_invoke_request<R: Runtime>(
.decode_utf8_lossy()
.to_string();
// the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it
#[cfg(all(feature = "isolation", ipc_custom_protocol))]
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
// on Android and on Linux (without the linux-ipc-protocol Cargo feature) we cannot read the request body
// so we must ignore it because some commands use the IPC for faster response
let has_payload = !body.is_empty();
body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body)
.and_then(|raw| crypto_keys.decrypt(raw))
.map_err(|e| e.to_string())?;
#[cfg(feature = "isolation")]
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
// 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(
@ -420,12 +424,12 @@ fn parse_invoke_request<R: Runtime>(
let body = if content_type == mime::APPLICATION_OCTET_STREAM {
body.into()
} 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)
.map_err(|e| e.to_string())?
.into()
} 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()
}
} else {

View File

@ -496,12 +496,9 @@ impl<R: Runtime> WebviewManager<R> {
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
// but we do respect user-specification