Open4

外部ドメインページを表示したTauriのWebViewから、RustアプリケーションとIPCするまでのお話

8beeeaaat8beeeaaat

メインのwindowでアプリケーションのUIを表示しつつ、外部ドメインページを開いた別windowからアプリケーションに対してIPCしたい。

メインwindowのフロントエンドからアプリケーションに通信する手段としては @tauri-apps/api の invoke などライブラリのAPIを利用すれば良いが、外部コンテンツを表示している間はTauriのAPIを実行できなくなってしまう。

WebViewにiframeを設けてコンテンツを表示するという手もあるが、 X-Frame-Options:Deny を有効にしているコンテンツはiframeで描画できないのでイマイチ。
どうにか抜け道を探る。

8beeeaaat8beeeaaat

Tauriには Rust側から任意のJavaScriptを実行できる様に Electronで言うところの contents.executeJavaScript 相当の実装として tauri::window::Window::eval が用意されている。

evalの引数にJavaScriptを渡しwindowに実行させることでRustプロセス側から任意のJavaScriptを実行させることが可能。

https://docs.rs/tauri/1.5.2/tauri/window/struct.Window.html#method.eval

サンプルとしてGoogleを開いたWebViewで console.log で延々と名前を読み上げるJSを実行させてみる。
以下 tauri x viteをベースに実装したサンプル。
https://tauri.app/v1/guides/getting-started/setup/vite/

main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::{App, WindowBuilder};

#[tauri::command]
// jsからinvoke('greet', { name: string })で呼び出される
fn greet(name: &str) -> String {
    println!("greeting from: {}", name);
    return format!("Hello, {}! You've been greeted from Rust!", name);
}

fn create_external_window(app: &App) {
    let external = WindowBuilder::new(
        app,
        "external",
        tauri::WindowUrl::External("https://google.com/".parse().unwrap()),
    )
    .build()
    .expect("error while building external");

    let js_eval = format!(
        r#"
            const names = ['jo', 'doe', 'foo', 'bar'];
            let count = 0;
            setInterval(async () => {{
                console.log(names[count]);
                count++;
                if (count === names.length) {{
                    count = 0;
                }}
            }}, 1000);
        "#
    );

    external.eval(&js_eval).unwrap();
}

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            create_external_window(&app);
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![greet]) // invokeで実行可能なcommandを登録
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

こいつ、動くぞ!

8beeeaaat8beeeaaat

実はTauriアプリケーションで作られるWebViewからはグローバル関数に登録されている window.__TAURI__ を実行することで各種APIが利用できる様になっている。
これを利用してWebView側からIPCできないか試みる。

試しに invoke に相当する await window.__TAURI_INVOKE__("greet", {name: 'joe'}) をconsoleから実行してみた。
メインウィンドウでは期待通り動作し、Rustプロセスのprintlnも出力された。

invokeによって実行されるRust側のgreet関数
fn greet(name: &str) -> String {
    println!("greeting from: {}", name); // 端末に出力
    return format!("Hello, {}! You've been greeted from Rust!", name); // invokeを実行したWebViewへのレスポンス
}

しかし、Googleを開いたWebView (external) 側は失敗。どうやらscopeの設定が甘いらしい。

Scope not defined for window `external` and URL `https://www.google.com/`. See https://tauri.app/v1/api/config/#securityconfig.dangerousremotedomainipcaccess and https://docs.rs/tauri/1/tauri/scope/struct.IpcScope.html#method.configure_remote_access
8beeeaaat8beeeaaat

どうやら外部ドメインからIPCできないようにスコープを切りつつホワイトリストに追加する必要があるようだ。
tauri.conf.jsonsecurity.dangerousRemoteDomainIpcAccess プロパティを設定してみる。

外部ドメイン側からTauriアプリケーションに対してアクセス可能となるため、ドキュメントには信頼の置けないドメインに対して設定しない旨注意書きがされている。

WARNING: Only use this option if you either have internal checks against malicious external sites or you can trust the allowed external sites. You application might be vulnerable to dangerous Tauri command related attacks otherwise.

https://tauri.app/v1/api/config/#securityconfig

tauri.conf.json 抜粋
{
  "tauri": {
    "security": {
      "csp": null,
      "dangerousRemoteDomainIpcAccess": [
        {
          "domain": "www.google.com", // 実行を許容するドメイン
          "windows": ["external"] // 外部ドメインサイトを表示するwindowのlabelを指定する
        }
      ]
    }
}

やったね