Преглед изворни кода

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

Lucas Fernandes Nogueira пре 1 година
родитељ
комит
a77be97474

+ 5 - 0
.changes/ipc-post-message-fallback.md

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

+ 0 - 5
core/tauri/build.rs

@@ -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");

+ 62 - 61
core/tauri/scripts/ipc-protocol.js

@@ -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
   })
 })()

+ 2 - 2
core/tauri/src/app.rs

@@ -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()

+ 0 - 1
core/tauri/src/ipc/mod.rs

@@ -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;
 

+ 15 - 11
core/tauri/src/ipc/protocol.rs

@@ -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))]
+  // 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(
@@ -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 {

+ 3 - 6
core/tauri/src/manager/webview.rs

@@ -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