Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/ipc-post-message-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---

Fallback to the postMessage IPC interface if we cannot reach the IPC custom protocol.
5 changes: 0 additions & 5 deletions core/tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
123 changes: 62 additions & 61 deletions core/tauri/scripts/ipc-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

function sendIpcMessage(message) {
const { cmd, callback, error, payload, options } = message

// 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
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(([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.`
)
}
})
.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
.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)
})
window.ipc.postMessage(data)
}
} 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
})
})()
4 changes: 2 additions & 2 deletions core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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()
Expand Down
1 change: 0 additions & 1 deletion core/tauri/src/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
26 changes: 15 additions & 11 deletions core/tauri/src/ipc/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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))]
// 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();

#[cfg(feature = "isolation")]
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
// 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())?;
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(
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 3 additions & 6 deletions core/tauri/src/manager/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading