Skip to content

Commit

Permalink
refactor(core): use webview's URI schemes for IPC (#7170)
Browse files Browse the repository at this point in the history
Co-authored-by: chip <[email protected]>
  • Loading branch information
lucasfernog and chippers committed Aug 10, 2023
1 parent 85efd0a commit fbeb5b9
Show file tree
Hide file tree
Showing 80 changed files with 2,066 additions and 1,146 deletions.
5 changes: 5 additions & 0 deletions .changes/channel-rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---

Added `Channel::new` allowing communication from a mobile plugin with Rust.
6 changes: 6 additions & 0 deletions .changes/ipc-custom-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri": patch:enhance
"tauri-utils": patch:enhance
---

Use custom protocols on the IPC implementation to enhance performance.
6 changes: 6 additions & 0 deletions .changes/ipc-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri": patch:breaking
"tauri-macros": patch:breaking
---

Moved `tauri::api::ipc` to `tauri::ipc` and refactored all types.
5 changes: 5 additions & 0 deletions .changes/linux-ipc-body-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---

Removed the `linux-protocol-headers` feature (now always enabled) and added `linux-ipc-protocol`.
5 changes: 5 additions & 0 deletions .changes/linux-protocol-body-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-runtime-wry": patch:breaking
---

Removed the `linux-headers` feature (now always enabled) and added `linux-protocol-body`.
6 changes: 6 additions & 0 deletions .changes/migrate-csp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-cli": patch:enhance
"@tauri-apps/cli": patch:enhance
---

Update migrate command to update the configuration CSP to include `ipc:` on the `connect-src` directive, needed by the new IPC using custom protocols.
2 changes: 1 addition & 1 deletion .github/workflows/lint-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
clippy:
- { args: '', key: 'empty' }
- {
args: '--features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test',
args: '--features compression,wry,isolation,custom-protocol,system-tray,test',
key: 'all'
}
- { args: '--features custom-protocol', key: 'custom-protocol' }
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/test-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
key: no-default
}
- {
args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test,
args: --features compression,wry,isolation,custom-protocol,system-tray,test,
key: all
}

Expand All @@ -98,6 +98,11 @@ jobs:
workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }}

- name: Downgrade crates with MSRV conflict
# The --precise flag can only be used once per invocation.
run: |
cargo update -p time --precise 0.3.23
- name: test
uses: actions-rs/cargo@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion core/tauri-macros/src/command/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
use #root::command::private::*;
// prevent warnings when the body is a `compile_error!` or if the command has no arguments
#[allow(unused_variables)]
let #root::Invoke { message: #message, resolver: #resolver } = $invoke;
let #root::ipc::Invoke { message: #message, resolver: #resolver } = $invoke;

#body
}};
Expand Down
2 changes: 1 addition & 1 deletion core/tauri-runtime-wry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ macos-private-api = [
"tauri-runtime/macos-private-api"
]
objc-exception = [ "wry/objc-exception" ]
linux-headers = [ ]
linux-protocol-body = [ "wry/linux-body", "webkit2gtk/v2_40" ]
35 changes: 18 additions & 17 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3085,17 +3085,14 @@ fn create_webview<T: UserEvent>(
webview_attributes,
uri_scheme_protocols,
mut window_builder,
ipc_handler,
label,
ipc_handler,
url,
menu_ids,
#[cfg(target_os = "android")]
on_webview_created,
..
} = pending;
let webview_id_map = context.webview_id_map.clone();
#[cfg(windows)]
let proxy = context.proxy.clone();

let window_event_listeners = WindowEventListeners::default();

Expand All @@ -3108,6 +3105,8 @@ fn create_webview<T: UserEvent>(

#[cfg(windows)]
let window_theme = window_builder.inner.window.preferred_theme;
#[cfg(windows)]
let proxy = context.proxy.clone();

#[cfg(target_os = "macos")]
{
Expand All @@ -3130,7 +3129,7 @@ fn create_webview<T: UserEvent>(
};
let window = window_builder.inner.build(event_loop).unwrap();

webview_id_map.insert(window.id(), window_id);
context.webview_id_map.insert(window.id(), window_id);

if window_builder.center {
let _ = center_window(&window, window.inner_size());
Expand All @@ -3157,17 +3156,18 @@ fn create_webview<T: UserEvent>(
}

#[cfg(windows)]
if let Some(additional_browser_args) = webview_attributes.additional_browser_args {
webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args);
}
{
if let Some(additional_browser_args) = webview_attributes.additional_browser_args {
webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args);
}

#[cfg(windows)]
if let Some(theme) = window_theme {
webview_builder = webview_builder.with_theme(match theme {
WryTheme::Dark => wry::webview::Theme::Dark,
WryTheme::Light => wry::webview::Theme::Light,
_ => wry::webview::Theme::Light,
});
if let Some(theme) = window_theme {
webview_builder = webview_builder.with_theme(match theme {
WryTheme::Dark => wry::webview::Theme::Dark,
WryTheme::Light => wry::webview::Theme::Light,
_ => wry::webview::Theme::Light,
});
}
}

if let Some(handler) = ipc_handler {
Expand All @@ -3178,6 +3178,7 @@ fn create_webview<T: UserEvent>(
handler,
));
}

for (scheme, protocol) in uri_scheme_protocols {
webview_builder = webview_builder.with_custom_protocol(scheme, move |wry_request| {
protocol(&HttpRequestWrapper::from(wry_request).0)
Expand Down Expand Up @@ -3254,7 +3255,7 @@ fn create_webview<T: UserEvent>(
unsafe {
controller.add_GotFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let _ = proxy_.send_event(Message::Webview(
let _ = proxy.send_event(Message::Webview(
window_id,
WebviewMessage::WebviewEvent(WebviewEvent::Focused(true)),
));
Expand All @@ -3267,7 +3268,7 @@ fn create_webview<T: UserEvent>(
unsafe {
controller.add_LostFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let _ = proxy.send_event(Message::Webview(
let _ = proxy_.send_event(Message::Webview(
window_id,
WebviewMessage::WebviewEvent(WebviewEvent::Focused(false)),
));
Expand Down
50 changes: 37 additions & 13 deletions core/tauri-utils/src/pattern/isolation.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* isolation frame -> main frame = isolation message
*/

;(async function () {
;
(async function () {
/**
* Sends the message to the isolation frame.
* @param {any} message
Expand Down Expand Up @@ -38,34 +39,52 @@
* @return {Promise<{nonce: number[], payload: number[]}>}
*/
async function encrypt(data) {
let algorithm = Object.create(null)
const algorithm = Object.create(null)
algorithm.name = 'AES-GCM'
algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12))

let encoder = new TextEncoder()
let payloadRaw = encoder.encode(__RAW_stringify_ipc_message_fn__(data))
const encoder = new TextEncoder()
const encoded = encoder.encode(__RAW_process_ipc_message_fn__(data).data)

return window.crypto.subtle
.encrypt(algorithm, aesGcmKey, payloadRaw)
.encrypt(algorithm, aesGcmKey, encoded)
.then((payload) => {
let result = Object.create(null)
const result = Object.create(null)
result.nonce = Array.from(new Uint8Array(algorithm.iv))
result.payload = Array.from(new Uint8Array(payload))
return result
})
}

/**
* Detects if a message event is a valid isolation message.
*
* @param {MessageEvent<object>} event - a message event that is expected to be an isolation message
* @return {boolean} - if the event was a valid isolation message
*/
function isIsolationMessage(data) {
if (typeof data === 'object' && typeof data.payload === 'object') {
const keys = data.payload ? Object.keys(data.payload) : []
return (
keys.length > 0 &&
keys.every((key) => key === 'nonce' || key === 'payload')
)
}
return false
}

/**
* Detect if a message event is a valid isolation payload.
*
* @param {MessageEvent<object>} event - a message event that is expected to be an isolation payload
* @return boolean
*/
function isIsolationPayload(event) {
function isIsolationPayload(data) {
return (
typeof event.data === 'object' &&
'callback' in event.data &&
'error' in event.data
typeof data === 'object' &&
'callback' in data &&
'error' in data &&
!isIsolationMessage(data)
)
}

Expand All @@ -74,7 +93,7 @@
* @param {MessageEvent<any>} event
*/
async function payloadHandler(event) {
if (!isIsolationPayload(event)) {
if (!isIsolationPayload(event.data)) {
return
}

Expand All @@ -85,8 +104,13 @@
data = await window.__TAURI_ISOLATION_HOOK__(data)
}

const encrypted = await encrypt(data)
sendMessage(encrypted)
const message = Object.create(null)
message.cmd = data.cmd
message.callback = data.callback
message.error = data.error
message.options = data.options
message.payload = await encrypt(data.payload)
sendMessage(message)
}

window.addEventListener('message', payloadHandler, false)
Expand Down
18 changes: 8 additions & 10 deletions core/tauri-utils/src/pattern/isolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,14 @@ impl Keys {
}

/// Decrypts a message using the generated keys.
pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result<String, Error> {
pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result<Vec<u8>, Error> {
let RawIsolationPayload { nonce, payload } = raw;
let nonce: [u8; 12] = nonce.as_ref().try_into()?;
let bytes = self
self
.aes_gcm
.key
.decrypt(Nonce::from_slice(&nonce), payload.as_ref())
.map_err(|_| self::Error::Aes)?;

String::from_utf8(bytes).map_err(Into::into)
.map_err(|_| self::Error::Aes)
}
}

Expand All @@ -116,11 +114,11 @@ pub struct RawIsolationPayload<'a> {
payload: Cow<'a, [u8]>,
}

impl<'a> TryFrom<&'a str> for RawIsolationPayload<'a> {
impl<'a> TryFrom<&'a Vec<u8>> for RawIsolationPayload<'a> {
type Error = Error;

fn try_from(value: &'a str) -> Result<Self, Self::Error> {
serde_json::from_str(value).map_err(Into::into)
fn try_from(value: &'a Vec<u8>) -> Result<Self, Self::Error> {
serde_json::from_slice(value).map_err(Into::into)
}
}

Expand All @@ -141,9 +139,9 @@ pub struct IsolationJavascriptCodegen {
pub struct IsolationJavascriptRuntime<'a> {
/// The key used on the Rust backend and the Isolation Javascript
pub runtime_aes_gcm_key: &'a [u8; 32],
/// The function that stringifies a IPC message.
/// The function that processes the IPC message.
#[raw]
pub stringify_ipc_message_fn: &'a str,
pub process_ipc_message_fn: &'a str,
}

#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ reqwest = { version = "0.11", default-features = false, features = [ "json", "st
bytes = { version = "1", features = [ "serde" ] }
raw-window-handle = "0.5"
glob = "0.3"
mime = "0.3"
data-url = { version = "0.2", optional = true }
serialize-to-javascript = "=0.1.1"
infer = { version = "0.9", optional = true }
Expand Down Expand Up @@ -118,7 +119,7 @@ test = [ ]
compression = [ "tauri-macros/compression", "tauri-utils/compression" ]
wry = [ "tauri-runtime-wry" ]
objc-exception = [ "tauri-runtime-wry/objc-exception" ]
linux-protocol-headers = [ "tauri-runtime-wry/linux-headers", "webkit2gtk/v2_36" ]
linux-ipc-protocol = [ "tauri-runtime-wry/linux-protocol-body", "webkit2gtk/v2_40" ]
isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ]
custom-protocol = [ "tauri-macros/custom-protocol" ]
native-tls = [ "reqwest/native-tls" ]
Expand Down
5 changes: 5 additions & 0 deletions core/tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ fn main() {
alias("desktop", !mobile);
alias("mobile", mobile);

alias(
"ipc_custom_protocol",
target_os != "android" && (target_os != "linux" || has_feature("linux-ipc-protocol")),
);

let checked_features_out_path = Path::new(&var("OUT_DIR").unwrap()).join("checked_features");
std::fs::write(
checked_features_out_path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Invoke(
val callback: Long,
val error: Long,
private val sendResponse: (callback: Long, data: PluginResult?) -> Unit,
private val sendChannelData: (channelId: Long, data: PluginResult) -> Unit,
val data: JSObject) {

fun resolve(data: JSObject?) {
Expand Down Expand Up @@ -205,6 +206,6 @@ class Invoke(
fun getChannel(name: String): Channel? {
val channelDef = getString(name, "")
val callback = channelDef.substring(CHANNEL_PREFIX.length).toLongOrNull() ?: return null
return Channel(callback) { res -> sendResponse(callback, PluginResult(res)) }
return Channel(callback) { res -> sendChannelData(callback, PluginResult(res)) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ class PluginManager(val activity: AppCompatActivity) {
}
}

@JniMethod
fun postIpcMessage(webView: WebView, pluginId: String, command: String, data: JSObject, callback: Long, error: Long) {
val invoke = Invoke(callback, command, callback, error, { fn, result ->
webView.evaluateJavascript("window['_$fn']($result)", null)
}, data)

dispatchPluginMessage(invoke, pluginId)
}

@JniMethod
fun runCommand(id: Int, pluginId: String, command: String, data: JSObject) {
val successId = 0L
Expand All @@ -107,6 +98,8 @@ class PluginManager(val activity: AppCompatActivity) {
error = result
}
handlePluginResponse(id, success?.toString(), error?.toString())
}, { channelId, payload ->
sendChannelData(channelId, payload.toString())
}, data)

dispatchPluginMessage(invoke, pluginId)
Expand Down Expand Up @@ -140,4 +133,5 @@ class PluginManager(val activity: AppCompatActivity) {
}

private external fun handlePluginResponse(id: Int, success: String?, error: String?)
private external fun sendChannelData(id: Long, data: String)
}
Loading

0 comments on commit fbeb5b9

Please sign in to comment.