mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
Merge branch 'dev/live-sync' into main
This commit is contained in:
commit
1905593784
7 changed files with 349 additions and 26 deletions
94
src-tauri/Cargo.lock
generated
94
src-tauri/Cargo.lock
generated
|
@ -31,6 +31,41 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes-gcm"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"aes",
|
||||||
|
"cipher",
|
||||||
|
"ctr",
|
||||||
|
"ghash",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
@ -714,6 +749,16 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
@ -1080,6 +1125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1120,6 +1166,15 @@ dependencies = [
|
||||||
"syn 2.0.87",
|
"syn 2.0.87",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctr"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.10"
|
version = "0.20.10"
|
||||||
|
@ -2645,6 +2700,15 @@ dependencies = [
|
||||||
"configparser",
|
"configparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "interpolate_name"
|
name = "interpolate_name"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
@ -3569,6 +3633,12 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.66"
|
version = "0.10.66"
|
||||||
|
@ -3959,6 +4029,18 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polyval"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -4081,6 +4163,7 @@ name = "qopy"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"active-win-pos-rs",
|
"active-win-pos-rs",
|
||||||
|
"aes-gcm",
|
||||||
"applications",
|
"applications",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -4113,6 +4196,7 @@ dependencies = [
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"typenum",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -6469,6 +6553,16 @@ version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
@ -53,6 +53,8 @@ include_dir = "0.7.4"
|
||||||
applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "dev" }
|
applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "dev" }
|
||||||
meta_fetcher = "0.1.1"
|
meta_fetcher = "0.1.1"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
aes-gcm = "0.10.3"
|
||||||
|
typenum = "1.17.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod db;
|
mod db;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
mod sync;
|
||||||
|
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
@ -10,11 +11,13 @@ use tauri::Manager;
|
||||||
use tauri_plugin_aptabase::{ EventTracker, InitOptions };
|
use tauri_plugin_aptabase::{ EventTracker, InitOptions };
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_prevent_default::Flags;
|
use tauri_plugin_prevent_default::Flags;
|
||||||
|
use sync::sync::ClipboardSync;
|
||||||
|
use sync::pairing::PairingManager;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
async fn main() {
|
||||||
let _guard = runtime.enter();
|
|
||||||
|
|
||||||
tauri::Builder
|
tauri::Builder
|
||||||
::default()
|
::default()
|
||||||
.plugin(tauri_plugin_clipboard::init())
|
.plugin(tauri_plugin_clipboard::init())
|
||||||
|
@ -69,38 +72,52 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||||
|
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|
||||||
let app_handle_clone = app_handle.clone();
|
// Create the pool in a separate tokio runtime
|
||||||
tauri::async_runtime::spawn(async move {
|
let pool = tokio::runtime::Runtime
|
||||||
let pool = SqlitePoolOptions::new()
|
::new()
|
||||||
.max_connections(5)
|
.unwrap()
|
||||||
.connect(&db_url).await
|
.block_on(async {
|
||||||
.expect("Failed to create pool");
|
SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect(&db_url).await
|
||||||
|
.expect("Failed to create pool")
|
||||||
|
});
|
||||||
|
|
||||||
app_handle_clone.manage(pool);
|
app_handle.manage(pool);
|
||||||
});
|
|
||||||
|
|
||||||
let main_window = app.get_webview_window("main");
|
let main_window = app.get_webview_window("main");
|
||||||
|
|
||||||
let _ = db::database::setup(app);
|
db::database::setup(app).expect("Failed to setup database");
|
||||||
api::hotkeys::setup(app_handle.clone());
|
api::hotkeys::setup(app_handle.clone());
|
||||||
api::tray::setup(app)?;
|
api::tray::setup(app).expect("Failed to setup tray");
|
||||||
api::clipboard::setup(app.handle());
|
api::clipboard::setup(&app_handle);
|
||||||
let _ = api::clipboard::start_monitor(app_handle.clone());
|
api::clipboard::start_monitor(app_handle.clone()).expect("Failed to start monitor");
|
||||||
|
|
||||||
|
let pairing_manager = PairingManager::new();
|
||||||
|
let encryption_key = pairing_manager.get_encryption_key().clone();
|
||||||
|
let nonce = pairing_manager.get_nonce().clone();
|
||||||
|
app_handle.manage(pairing_manager);
|
||||||
|
|
||||||
|
let clipboard_sync = ClipboardSync::new(&encryption_key, &nonce);
|
||||||
|
let clipboard_sync_arc = Arc::new(Mutex::new(clipboard_sync));
|
||||||
|
app_handle.manage(clipboard_sync_arc.clone());
|
||||||
|
|
||||||
|
let clipboard_sync_clone = clipboard_sync_arc.clone();
|
||||||
|
let app_handle_clone = app_handle.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let sync = clipboard_sync_clone.lock().await;
|
||||||
|
sync.listen_webhook(app_handle_clone, clipboard_sync_clone).await;
|
||||||
|
});
|
||||||
|
|
||||||
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
|
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
|
||||||
main_window
|
let _ = main_window
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| w.hide())
|
.map(|w| w.hide())
|
||||||
.unwrap_or(Ok(()))?;
|
.expect("Failed to hide window");
|
||||||
|
|
||||||
let _ = app.track_event("app_started", None);
|
app.track_event("app_started", None).expect("Failed to track event");
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
api::updater::check_for_updates(app_handle, false).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -124,7 +141,11 @@ fn main() {
|
||||||
db::history::read_image,
|
db::history::read_image,
|
||||||
db::settings::get_setting,
|
db::settings::get_setting,
|
||||||
db::settings::save_setting,
|
db::settings::save_setting,
|
||||||
utils::commands::fetch_page_meta
|
utils::commands::fetch_page_meta,
|
||||||
|
sync::pairing::initiate_pairing,
|
||||||
|
sync::pairing::complete_pairing,
|
||||||
|
sync::sync::send_clipboard_data,
|
||||||
|
sync::sync::receive_clipboard_data
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|
2
src-tauri/src/sync/mod.rs
Normal file
2
src-tauri/src/sync/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod pairing;
|
||||||
|
pub mod sync;
|
112
src-tauri/src/sync/pairing.rs
Normal file
112
src-tauri/src/sync/pairing.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
use aes_gcm::{ Aes256Gcm, KeyInit };
|
||||||
|
use aes_gcm::aead::Aead;
|
||||||
|
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||||
|
use rand::{ Rng, thread_rng };
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use serde::{ Deserialize, Serialize };
|
||||||
|
use tauri::State;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const EMOJI_POOL: &[&str] = &["😀", "😁", "😂", "🤣", "😃", "😄", "😅", "😆", "😉", "😊"];
|
||||||
|
const PAIRING_KEY_LENGTH: usize = 4;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct PairingRequest {
|
||||||
|
pub inviter_id: String,
|
||||||
|
pub invitation_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PairingManager {
|
||||||
|
pairing_key: String,
|
||||||
|
encryption_key: [u8; 32],
|
||||||
|
nonce: [u8; 12],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PairingManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let pairing_key = Self::generate_emoji_sequence(&mut rng);
|
||||||
|
let encryption_key: [u8; 32] = rng.gen();
|
||||||
|
let nonce: [u8; 12] = rng.gen();
|
||||||
|
PairingManager {
|
||||||
|
pairing_key,
|
||||||
|
encryption_key,
|
||||||
|
nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_emoji_sequence(rng: &mut impl Rng) -> String {
|
||||||
|
let key: Vec<&str> = EMOJI_POOL.choose_multiple(rng, PAIRING_KEY_LENGTH).cloned().collect();
|
||||||
|
key.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_pairing(&self, input_key: &str) -> bool {
|
||||||
|
self.pairing_key == input_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_encryption_key(&self) -> &[u8; 32] {
|
||||||
|
&self.encryption_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_nonce(&self) -> &[u8; 12] {
|
||||||
|
&self.nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_invitation_code(&self) -> String {
|
||||||
|
Uuid::new_v4().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_key(&self, key: &[u8; 32]) -> Result<String, String> {
|
||||||
|
let cipher = Aes256Gcm::new(&self.encryption_key.into());
|
||||||
|
let ciphertext = cipher
|
||||||
|
.encrypt(&self.nonce.into(), key.as_ref())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(STANDARD.encode(ciphertext))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_key(&self, encrypted_key: &str) -> Result<[u8; 32], String> {
|
||||||
|
let ciphertext = STANDARD.decode(encrypted_key).map_err(|e| e.to_string())?;
|
||||||
|
let cipher = Aes256Gcm::new(&self.encryption_key.into());
|
||||||
|
let plaintext = cipher
|
||||||
|
.decrypt(&self.nonce.into(), ciphertext.as_ref())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
key.copy_from_slice(&plaintext);
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pairing_request(&self, inviter_id: String) -> PairingRequest {
|
||||||
|
PairingRequest {
|
||||||
|
inviter_id,
|
||||||
|
invitation_code: self.generate_invitation_code(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_pairing_response(&self, response: PairingRequest) -> bool {
|
||||||
|
self.validate_pairing(&response.invitation_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn initiate_pairing(_pairing_manager: State<'_, PairingManager>) -> String {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
PairingManager::generate_emoji_sequence(&mut rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn complete_pairing(
|
||||||
|
input_key: String,
|
||||||
|
pairing_manager: State<'_, PairingManager>
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if pairing_manager.validate_pairing(&input_key) {
|
||||||
|
let _shared_key = pairing_manager.encryption_key.to_vec();
|
||||||
|
Ok("Pairing successful".to_string())
|
||||||
|
} else {
|
||||||
|
Err("Invalid pairing key".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn generate_invitation(pairing_manager: State<'_, PairingManager>) -> String {
|
||||||
|
pairing_manager.generate_invitation_code()
|
||||||
|
}
|
92
src-tauri/src/sync/sync.rs
Normal file
92
src-tauri/src/sync/sync.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||||||
|
use aes_gcm::aead::Aead;
|
||||||
|
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::{AppHandle, Emitter};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use typenum::U12;
|
||||||
|
|
||||||
|
const KVS_URL: &str = "https://kvs.wireway.ch";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct ClipData {
|
||||||
|
content: String,
|
||||||
|
content_type: String,
|
||||||
|
timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClipboardSync {
|
||||||
|
client: Client,
|
||||||
|
cipher: Aes256Gcm,
|
||||||
|
nonce: Nonce<U12>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardSync {
|
||||||
|
pub fn new(encryption_key: &[u8; 32], nonce_bytes: &[u8; 12]) -> Self {
|
||||||
|
let cipher = Aes256Gcm::new(encryption_key.into());
|
||||||
|
let nonce = Nonce::from_slice(nonce_bytes).clone();
|
||||||
|
ClipboardSync {
|
||||||
|
client: Client::new(),
|
||||||
|
cipher,
|
||||||
|
nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_clipboard(&self, clip: ClipData) -> Result<(), String> {
|
||||||
|
let plaintext = serde_json::to_string(&clip).map_err(|e| e.to_string())?;
|
||||||
|
let ciphertext = self.cipher.encrypt(&self.nonce, plaintext.as_bytes()).map_err(|e| e.to_string())?;
|
||||||
|
let encoded = STANDARD.encode(ciphertext);
|
||||||
|
self.client
|
||||||
|
.post(&format!("{}/clipboard", KVS_URL))
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"key": "clipboard",
|
||||||
|
"value": encoded,
|
||||||
|
"expires_in": 60
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_clipboard(&self, app_handle: AppHandle) -> Result<(), String> {
|
||||||
|
let res = self.client.get(&format!("{}/clipboard", KVS_URL)).send().await.map_err(|e| e.to_string())?;
|
||||||
|
if res.status().is_success() {
|
||||||
|
let json: serde_json::Value = res.json().await.map_err(|e| e.to_string())?;
|
||||||
|
if let Some(encoded) = json["value"].as_str() {
|
||||||
|
let ciphertext = STANDARD.decode(encoded).map_err(|e| e.to_string())?;
|
||||||
|
let plaintext = self.cipher.decrypt(&self.nonce, ciphertext.as_ref()).map_err(|e| e.to_string())?;
|
||||||
|
let clip_str = String::from_utf8(plaintext).map_err(|e| e.to_string())?;
|
||||||
|
let clip: ClipData = serde_json::from_str(&clip_str).map_err(|e| e.to_string())?;
|
||||||
|
app_handle.emit("clipboard-update", clip).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn listen_webhook(&self, app_handle: AppHandle, state: Arc<Mutex<Self>>) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Err(_) = state.lock().await.receive_clipboard(app_handle.clone()).await {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn send_clipboard_data(clip: ClipData, sync: tauri::State<'_, Arc<Mutex<ClipboardSync>>>) -> Result<(), String> {
|
||||||
|
let sync = sync.lock().await;
|
||||||
|
sync.send_clipboard(clip).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn receive_clipboard_data(app_handle: AppHandle, sync: tauri::State<'_, Arc<Mutex<ClipboardSync>>>) -> Result<(), String> {
|
||||||
|
let sync = sync.lock().await;
|
||||||
|
sync.receive_clipboard(app_handle).await
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue