diff --git a/moxie-app/.gitignore b/moxie-app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/moxie-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/moxie-app/.vscode/extensions.json b/moxie-app/.vscode/extensions.json new file mode 100644 index 0000000..24d7cc6 --- /dev/null +++ b/moxie-app/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/moxie-app/README.md b/moxie-app/README.md new file mode 100644 index 0000000..441faf7 --- /dev/null +++ b/moxie-app/README.md @@ -0,0 +1,7 @@ +# Tauri + Vanilla + +This template should help get you started developing with Tauri in vanilla HTML, CSS and Javascript. + +## Recommended IDE Setup + +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/moxie-app/package-lock.json b/moxie-app/package-lock.json new file mode 100644 index 0000000..ff8bedc --- /dev/null +++ b/moxie-app/package-lock.json @@ -0,0 +1,232 @@ +{ + "name": "moxie-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "moxie-app", + "version": "0.1.0", + "devDependencies": { + "@tauri-apps/cli": "^2" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz", + "integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.2", + "@tauri-apps/cli-darwin-x64": "2.11.2", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.2", + "@tauri-apps/cli-linux-arm64-musl": "2.11.2", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-musl": "2.11.2", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.2", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.2", + "@tauri-apps/cli-win32-x64-msvc": "2.11.2" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz", + "integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz", + "integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz", + "integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz", + "integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz", + "integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz", + "integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz", + "integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz", + "integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz", + "integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz", + "integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz", + "integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/moxie-app/package.json b/moxie-app/package.json new file mode 100644 index 0000000..c8048fd --- /dev/null +++ b/moxie-app/package.json @@ -0,0 +1,14 @@ +{ + "name": "moxie-app", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "tauri": "tauri", + "dev": "tauri dev", + "build": "tauri build -- --no-bundle" + }, + "devDependencies": { + "@tauri-apps/cli": "^2" + } +} \ No newline at end of file diff --git a/moxie-app/src-tauri/.gitignore b/moxie-app/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/moxie-app/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/moxie-app/src-tauri/Cargo.toml b/moxie-app/src-tauri/Cargo.toml new file mode 100644 index 0000000..e16213a --- /dev/null +++ b/moxie-app/src-tauri/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "moxie-app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com +name = "moxie_app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio-tungstenite = "0.24" +futures-util = "0.3" + +# 👇 Replaced the old tokio configuration with the full feature flags list +tokio = { version = "1", features = ["net", "rt", "sync", "macros"] } diff --git a/moxie-app/src-tauri/build.rs b/moxie-app/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/moxie-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/moxie-app/src-tauri/capabilities/default.json b/moxie-app/src-tauri/capabilities/default.json new file mode 100644 index 0000000..28373b8 --- /dev/null +++ b/moxie-app/src-tauri/capabilities/default.json @@ -0,0 +1,16 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Standard app capabilities", + "platforms": [ + "windows", + "macOS", + "linux" + ], + "windows": [ + "main" + ], + "permissions": [ + "core:default" + ] +} \ No newline at end of file diff --git a/moxie-app/src-tauri/extWebview.js b/moxie-app/src-tauri/extWebview.js new file mode 100644 index 0000000..5638872 --- /dev/null +++ b/moxie-app/src-tauri/extWebview.js @@ -0,0 +1,29 @@ +// src-tauri/inject.js +window.addEventListener('DOMContentLoaded', () => { + console.log('Initializing local background socket link...') + const ws = new WebSocket('ws://127.0.0.1:8080') + + ws.onopen = () => { + console.log('WebSocket connected successfully!') + const payload = { + command: 'remote_action', + message: 'Hello automatically from: ' + window.location.href + } + ws.send(JSON.stringify(payload)) + } + + ws.onmessage = event => { + console.log('📩 RECEIVED REPLY FROM TAURI BACKEND:', JSON.parse(event.data)) + ws.send( + JSON.stringify({ + localsession: window.localStorage.getItem('token') || 'no-session' + }) + ) + } + + ws.onerror = err => { + console.error('Connection failed:', err) + } + + window.localAppSocket = ws +}) diff --git a/moxie-app/src-tauri/icons/128x128.png b/moxie-app/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/moxie-app/src-tauri/icons/128x128.png differ diff --git a/moxie-app/src-tauri/icons/128x128@2x.png b/moxie-app/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/moxie-app/src-tauri/icons/128x128@2x.png differ diff --git a/moxie-app/src-tauri/icons/32x32.png b/moxie-app/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/moxie-app/src-tauri/icons/32x32.png differ diff --git a/moxie-app/src-tauri/icons/Square107x107Logo.png b/moxie-app/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square107x107Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square142x142Logo.png b/moxie-app/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square142x142Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square150x150Logo.png b/moxie-app/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/moxie-app/src-tauri/icons/Square150x150Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square284x284Logo.png b/moxie-app/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/moxie-app/src-tauri/icons/Square284x284Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square30x30Logo.png b/moxie-app/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square30x30Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square310x310Logo.png b/moxie-app/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square310x310Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square44x44Logo.png b/moxie-app/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square44x44Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square71x71Logo.png b/moxie-app/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/moxie-app/src-tauri/icons/Square71x71Logo.png differ diff --git a/moxie-app/src-tauri/icons/Square89x89Logo.png b/moxie-app/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/moxie-app/src-tauri/icons/Square89x89Logo.png differ diff --git a/moxie-app/src-tauri/icons/StoreLogo.png b/moxie-app/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/moxie-app/src-tauri/icons/StoreLogo.png differ diff --git a/moxie-app/src-tauri/icons/icon.icns b/moxie-app/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/moxie-app/src-tauri/icons/icon.icns differ diff --git a/moxie-app/src-tauri/icons/icon.ico b/moxie-app/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/moxie-app/src-tauri/icons/icon.ico differ diff --git a/moxie-app/src-tauri/icons/icon.png b/moxie-app/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/moxie-app/src-tauri/icons/icon.png differ diff --git a/moxie-app/src-tauri/src/lib.rs b/moxie-app/src-tauri/src/lib.rs new file mode 100644 index 0000000..279945d --- /dev/null +++ b/moxie-app/src-tauri/src/lib.rs @@ -0,0 +1,53 @@ +use tauri::Manager; +use tauri::{AppHandle, WebviewUrl, WebviewWindowBuilder}; + +// 1. Declare the new server module file 👇 +mod server; + +#[tauri::command] +async fn greet(app_handle: AppHandle, name: String) -> Result { + let window_label = "remote-target-window"; + + let target_url = match name.parse() { + Ok(url) => WebviewUrl::External(url), + Err(_) => { + return Err(format!("Error: '{}' is not a valid URL format.", name)); + } + }; + + if let Some(existing_window) = app_handle.get_webview_window(window_label) { + let _ = existing_window.set_focus(); + return Ok(format!("Focused existing window for {}", name)); + } + + let app_clone = app_handle.clone(); + let url_clone = target_url.clone(); + + let script_to_inject = include_str!("../extWebview.js"); + + app_handle.run_on_main_thread(move || { + let _new_window = WebviewWindowBuilder::new(&app_clone, window_label, url_clone) + .title("Remote Session") + .inner_size(1024.0, 768.0) + .resizable(true) + .focused(true) + .initialization_script(script_to_inject) + .build(); + }).map_err(|e| format!("Failed to dispatch to main thread: {}", e))?; + + Ok(format!("Dispatched window spawn for: {}", name)) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .setup(|_app| { + // 2. Call the server function using module referencing 👇 + tauri::async_runtime::spawn(server::start_websocket_server()); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![greet]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/moxie-app/src-tauri/src/main.rs b/moxie-app/src-tauri/src/main.rs new file mode 100644 index 0000000..3110dcb --- /dev/null +++ b/moxie-app/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + moxie_app_lib::run() +} diff --git a/moxie-app/src-tauri/src/server.rs b/moxie-app/src-tauri/src/server.rs new file mode 100644 index 0000000..ac51f5a --- /dev/null +++ b/moxie-app/src-tauri/src/server.rs @@ -0,0 +1,57 @@ +use tokio::net::TcpListener; +use tokio_tungstenite::accept_async; +use futures_util::stream::StreamExt; +use futures_util::sink::SinkExt; +use tokio::sync::broadcast; // 1. Bring Tokio's broadcast module into scope + +pub async fn start_websocket_server() { + let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); + println!("Bypassed IPC Bridge: Listening for remote commands on ws://127.0.0.1:8080"); + + // 2. Create a broadcast channel that holds up to 16 messages in the queue + let (tx, _rx) = broadcast::channel::(16); + + while let Ok((stream, _)) = listener.accept().await { + // Clone the transmitter handle for this specific client connection task + let tx = tx.clone(); + // Create a dedicated receiver handle for this client connection task + let mut rx = tx.subscribe(); + + tauri::async_runtime::spawn(async move { + if let Ok(ws_stream) = accept_async(stream).await { + let (mut ws_sender, mut ws_receiver) = ws_stream.split(); + + loop { + tokio::select! { + // Branch A: Listen for incoming text sent FROM this specific web view client + incoming_msg = ws_receiver.next() => { + match incoming_msg { + Some(Ok(msg)) => { + if msg.is_text() { + let text = msg.to_text().unwrap().to_string(); + println!("⚡ Routing cross-window message: {}", text); + + // Broadcast this message out to ALL connected windows + let _ = tx.send(text); + } + } + _ => break, // Disconnection or error handled cleanly + } + } + // Branch B: Listen for broadcast messages pushed from OTHER windows via Rust + broadcast_msg = rx.recv() => { + if let Ok(text_payload) = broadcast_msg { + // Push the broadcasted packet down to this window's browser console + let msg_frame = tokio_tungstenite::tungstenite::Message::Text(text_payload.into()); + if let Err(e) = ws_sender.send(msg_frame).await { + eprintln!("Failed to relay data down the socket: {}", e); + break; + } + } + } + } + } + } + }); + } +} diff --git a/moxie-app/src-tauri/tauri.conf.json b/moxie-app/src-tauri/tauri.conf.json new file mode 100644 index 0000000..56b42b4 --- /dev/null +++ b/moxie-app/src-tauri/tauri.conf.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://tauri.app", + "productName": "moxie-app", + "version": "0.1.0", + "identifier": "com.dev.moxie-app", + "build": { + "frontendDist": "../src" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "moxie-app", + "width": 800, + "height": 600 + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": false, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} \ No newline at end of file diff --git a/moxie-app/src/assets/javascript.svg b/moxie-app/src/assets/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/moxie-app/src/assets/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/moxie-app/src/assets/tauri.svg b/moxie-app/src/assets/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/moxie-app/src/assets/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/moxie-app/src/index.html b/moxie-app/src/index.html new file mode 100644 index 0000000..c50e6ea --- /dev/null +++ b/moxie-app/src/index.html @@ -0,0 +1,36 @@ + + + + + + + + Tauri App + + + + +
+

Welcome to Tauri

+

 

+ + +

Click on the Tauri logo to learn more about the framework

+ +
+ + + +
+

+
+ + + \ No newline at end of file diff --git a/moxie-app/src/main.js b/moxie-app/src/main.js new file mode 100644 index 0000000..5874713 --- /dev/null +++ b/moxie-app/src/main.js @@ -0,0 +1,41 @@ +const { invoke } = window.__TAURI__.core + +let greetInputEl +let greetMsgEl + +async function greet () { + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ + greetMsgEl.textContent = await invoke('greet', { name: greetInputEl.value }) +} +const mainSocket = new WebSocket('ws://127.0.0.1:8080') + +mainSocket.onopen = ms => { + console.log('Main Control Window connected to the broadcast hub!') +} + +window.addEventListener('DOMContentLoaded', () => { + greetInputEl = document.querySelector('#greet-input') + greetMsgEl = document.querySelector('#greet-msg') + document.querySelector('#greet-form').addEventListener('submit', e => { + e.preventDefault() + greet() + }) + document.querySelector('#wstest').addEventListener('click', () => { + const payload = { + sender: 'local-app-main', + content: 'hi from the webview! 1' + } + + // Ship the data packets straight into the Rust loop + mainSocket.send(JSON.stringify(payload)) + }) +}) + +mainSocket.onmessage = event => { + const msgs = JSON.parse(event.data) + console.log(msgs.type) + if (msgs.localsession) { + const shEl = document.querySelector('#sh') + shEl.textContent = `Session Hash: ${msgs.localsession}` + } +} diff --git a/moxie-app/src/styles.css b/moxie-app/src/styles.css new file mode 100644 index 0000000..e57b8ac --- /dev/null +++ b/moxie-app/src/styles.css @@ -0,0 +1,112 @@ +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #ffe21c); +} +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; +} + +.logo.tauri:hover { + filter: drop-shadow(0 0 2em #24c8db); +} + +.row { + display: flex; + justify-content: center; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +h1 { + text-align: center; +} + +input, +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); +} + +button { + cursor: pointer; +} + +button:hover { + border-color: #396cd8; +} +button:active { + border-color: #396cd8; + background-color: #e8e8e8; +} + +input, +button { + outline: none; +} + +#greet-input { + margin-right: 5px; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } + + a:hover { + color: #24c8db; + } + + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } + button:active { + background-color: #0f0f0f69; + } +} diff --git a/tauri2-docs/cheatsheet.md b/tauri2-docs/cheatsheet.md new file mode 100644 index 0000000..7ad95b0 --- /dev/null +++ b/tauri2-docs/cheatsheet.md @@ -0,0 +1,284 @@ +# 🚀 Tauri 2.0 Definitive Cheat Sheet + +**Tauri 2.0** features a complete architecture overhaul centered around mobile support (iOS/Android), modularized core plugins, and a strict **Access Control List (ACL)** security model. This cheat sheet covers core implementation patterns with up-to-date syntax. + +--- + +## 🛠️ Project Setup & Structure + +Tauri 2.0 splits frontend configurations and introduces modular Rust codebases via `lib.rs`. + +### CLI Management +```bash +# Initialize a new app +npm create tauri-app@latest + +# Run development mode (Hot-reloads UI + Rust) +npm run tauri dev + +# Build production artifacts +npm run tauri build +``` + +### File Hierarchy +```text +my-app/ +├── src/ # Frontend UI (React, Vue, Svelte, etc.) +├── src-tauri/ # Backend environment +│ ├── capabilities/ # NEW: Security capability JSON/TOML files +│ │ └── default.json # Maps app windows to permissions +│ ├── src/ +│ │ ├── main.rs # Minimal platform entry-point +│ │ └── lib.rs # Core Application setup & commands +│ ├── Cargo.toml # Rust dependencies +│ └── tauri.conf.json # Desktop/Mobile global window configurations +``` + +--- + +## 🔒 Security: The Tauri 2.0 Permission System + +Tauri 2.0 enforces explicit privileges. Frontend access to system commands or plugins requires configuring a **Permission** inside a **Capability** map. + +### 1. Identify Plugin Permissions +Official plugins expose granular identifier strings: +* **Core Permissions**: `core:path:allow-home`, `core:event:allow-listen`, `core:event:allow-emit`. +* **Plugin Permissions**: `:allow-` (e.g., `http:allow-request`, `fs:allow-read`). + +### 2. Define Capabilities (`src-tauri/capabilities/default.json`) +Capabilities attach permissions to specific application windows. + +```json +{ + "\$schema": "../gen/schemas/capability.json", + "identifier": "main-window-capability", + "description": "Allowed permissions for the primary UI", + "windows": ["main"], + "permissions": [ + "core:path:allow-home", + "core:event:allow-listen", + "core:event:allow-emit", + "fs:allow-read", + "websocket:allow-connect", + "websocket:allow-send" + ] +} +``` + +### 3. Fine-Grained Scopes +Scopes restrict what a plugin command can touch (e.g., locking file-system access down to a explicit directory). Define these inside your capability entries: + +```json +{ + "identifier": "secure-fs-scope", + "windows": ["main"], + "permissions": [ + { + "identifier": "fs:allow-write", + "allow": [{ "path": "\$APPDATA/logs/*" }] + } + ] +} +``` + +--- + +## 📡 WebSockets in Tauri 2.0 + +Tauri 2.0 approaches WebSockets from two distinct perspectives depending on requirements: utilizing the client plugin to query a external endpoint, or instantiating a native high-performance Rust WebSocket Server. + +### Option A: The Built-in WebSocket Client Plugin +Use this to safely initiate connections to external services from the frontend via a high-performance Rust proxy client. + +#### 1. Setup Backend Setup (`src-tauri/src/lib.rs`) +```rust +// Ensure 'tauri-plugin-websocket' is added to Cargo.toml +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_websocket::init()) // Initialize client plugin + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +#### 2. Frontend Execution (`JavaScript / TypeScript`) +Ensure `websocket:allow-connect` and `websocket:allow-send` are added to your active capability configuration. +```typescript +import WebSocket from '@tauri-apps/plugin-websocket'; + +// Connect to a server +const ws = await WebSocket.connect('ws://127.0.0.1:8080'); + +// Handle incoming messages +ws.addListener((msg) => { + console.log('Received payload:', msg.data); +}); + +// Push data out +await ws.send({ type: 'Text', data: 'Hello World!' }); + +// Disconnect gracefully +await ws.disconnect(); +``` + +### Option B: Built-in Custom Rust WebSocket Server +If you want your Tauri application to **act as a WebSocket Server** (e.g., exposing a local port so mobile apps or local interfaces can stream data directly to your backend), spin up a Tokio-backed server thread inside the Tauri `setup` stage. + +#### 1. Dependencies (`src-tauri/Cargo.toml`) +```toml +[dependencies] +tauri = { version = "2.0", features = [] } +tokio = { version = "1", features = ["full"] } +tokio-tungstenite = "0.21" # Industry standard high-performance WS crate +futures-util = "0.3" +``` + +#### 2. Server Implementation (`src-tauri/src/lib.rs`) +```rust +use tauri::Manager; +use tokio::net::TcpListener; +use futures-util::{StreamExt, SinkExt}; +use tokio_tungstenite::accept_async; + +async fn start_ws_server(port: &str) { + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)) + .await + .expect("Failed to bind port"); + + while let Ok((stream, _)) = listener.accept().await { + tokio::spawn(async move { + if let Ok(mut ws_stream) = accept_async(stream).await { + println!("New connection established to Tauri WS Server!"); + while let Some(Ok(msg)) = ws_stream.next().await { + if msg.is_text() || msg.is_binary() { + // Eco back received payloads + let _ = ws_stream.send(msg).await; + } + } + } + }); + } +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .setup(|app| { + // Spin up native server safely in an isolated async tokio environment + tokio::spawn(start_ws_server("8080")); + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +--- + +## ⚡ Inter-Process Communication (IPC) & State + +### Commands (Invoking Rust from Frontend) +Pass data directly via JSON-RPC structures across the isolation barrier. + +```rust +// src-tauri/src/lib.rs +use std::sync::Mutex; +use tauri::State; + +pub struct AppState { + pub counter: Mutex, +} + +#[tauri::command] +fn increment_counter(state: State<'_, AppState>, value: u32) -> u32 { + let mut counter = state.counter.lock().unwrap(); + *counter += value; + *counter // Return to frontend +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .manage(AppState { counter: Mutex::new(0) }) // Inject State + .invoke_handler(tauri::generate_handler![increment_counter]) + .run(tauri::generate_context!()) + .expect("failed to run app"); +} +``` + +```typescript +// Frontend Invoke Pattern +import { invoke } from '@tauri-apps/api/core'; + +const result = await invoke('increment_counter', { value: 5 }); +console.log(result); // Output: 5 +``` + +### Events (Asynchronous Global Communication) +Ideal for un-prompted data streaming from Rust to UI components. + +```rust +// Backend Emit +use tauri::{Emitter, AppHandle}; + +fn stream_system_status(app_handle: &AppHandle, load: f32) { + // Emits a global event to all active window contexts + app_handle.emit("cpu-status", load).unwrap(); +} +``` + +```typescript +// Frontend Listen +import { listen } from '@tauri-apps/api/event'; + +// Make sure 'core:event:allow-listen' is in capabilities +const unlisten = await listen('cpu-status', (event) => { + console.log(`CPU utilization: ${event.payload}%`); +}); + +// Execute unlisten() later to clean up memory hooks +``` + +--- + +## 📇 Deep Links & Windowing + +### Multiple Window Spawning + +```rust +// Rust implementation +use tauri::WebviewUrl; + +fn create_extra_window(app: &tauri::AppHandle) { + let _window = tauri::WebviewWindowBuilder::new( + app, + "secondary_panel", + WebviewUrl::App("index.html/#/settings".into()) + ) + .title("Settings Panel") + .inner_size(600.0, 400.0) + .build() + .unwrap(); +} +``` + +### Single Instance Lock & Deep Linking +Ensures only one instance runs, piping external application schemas (e.g. `my-app://open?token=xyz`) into the active runtime. + +```rust +// src-tauri/src/lib.rs +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { + println!("Attempted alternative launch instances: {:?}, working dir: {:?}", args, cwd); + // Focus primary window on secondary call triggers + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_focus(); + } + })) + .run(tauri::generate_context!()) + .expect("failed execution"); +} +``` diff --git a/tauri2-docs/tauri2-links.md b/tauri2-docs/tauri2-links.md new file mode 100644 index 0000000..4837729 --- /dev/null +++ b/tauri2-docs/tauri2-links.md @@ -0,0 +1,17 @@ +* Tauri 2.0 Upgrade & Migration Platform Index: + https://v2.tauri.app/start/migrate/ + +* Tauri 2.0 In-Depth Guide for Upgrading from v1.x: + https://v2.tauri.app/start/migrate/from-tauri-1/ + +* Tauri 2.0 Application Architecture Security and Core Capabilities Manual: + https://v2.tauri.app/security/capabilities/ + +* Tauri 2.0 Granular Window Permissions Reference Guide: + https://v2.tauri.app/security/permissions/ + +* Tauri 2.0 Official Configuration Files Layout Schema (`tauri.conf.json`): + https://v2.tauri.app/develop/configuration-files/ + +* Tauri 2.0 Rust API Documentation Engine (Official `tauri` Crate Source Docs): + https://docs.rs diff --git a/tauri2-docs/ws_server_client_inject_guide.md b/tauri2-docs/ws_server_client_inject_guide.md new file mode 100644 index 0000000..e69de29