mirror of https://github.com/tauri-apps/tauri
chore(merge) feature/event-system (#12)
* feat(event-system) prototype * feat(event-system) prepare two way communication * feat(event-system) fASLR * feat(event-system) answer salt * feat(event-system) simplify communication and enable multi-level two way message passing
This commit is contained in:
parent
fcd0c7fc35
commit
b5927e4711
|
@ -27,9 +27,10 @@ flate2 = "1"
|
|||
hyper-old-types = "0.11.0"
|
||||
sysinfo = "0.9"
|
||||
webbrowser = "0.5.1"
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
lazy_static = "1.3.0"
|
||||
|
||||
[features]
|
||||
api = []
|
||||
all-api = []
|
||||
readTextFile = []
|
||||
readBinaryFile = []
|
||||
|
@ -39,3 +40,4 @@ listDirs = []
|
|||
setTitle = []
|
||||
execute = []
|
||||
open = []
|
||||
answer = []
|
||||
|
|
|
@ -4,8 +4,6 @@ use proton_ui::WebView;
|
|||
|
||||
#[allow(unused_variables)]
|
||||
pub fn handler<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> bool {
|
||||
#[cfg(feature = "api")]
|
||||
{
|
||||
use cmd::Cmd::*;
|
||||
match serde_json::from_str(arg) {
|
||||
Err(_) => false,
|
||||
|
@ -64,18 +62,57 @@ pub fn handler<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> bool {
|
|||
error,
|
||||
} => {
|
||||
super::command::call(webview, command, args, callback, error);
|
||||
},
|
||||
}
|
||||
#[cfg(any(feature = "all-api", feature = "open"))]
|
||||
Open { uri } => {
|
||||
super::spawn(move || {
|
||||
webbrowser::open(&uri).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
ValidateSalt {
|
||||
salt,
|
||||
callback,
|
||||
error,
|
||||
} => {
|
||||
crate::salt::validate(webview, salt, callback, error);
|
||||
}
|
||||
AddEventListener {
|
||||
event,
|
||||
handler,
|
||||
once,
|
||||
} => {
|
||||
webview
|
||||
.eval(&format!(
|
||||
"
|
||||
if (window['{obj}'] === void 0) {{
|
||||
window['{obj}'] = {{}}
|
||||
}}
|
||||
if (window['{obj}']['{evt}'] === void 0) {{
|
||||
window['{obj}']['{evt}'] = []
|
||||
}}
|
||||
window['{obj}']['{evt}'].push({{
|
||||
handler: window['{handler}'],
|
||||
once: {once_flag}
|
||||
}})
|
||||
",
|
||||
obj = crate::event::event_listeners_object_name(),
|
||||
evt = event,
|
||||
handler = handler,
|
||||
once_flag = if once { "true" } else { "false" }
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(any(feature = "all-api", feature = "answer"))]
|
||||
Answer {
|
||||
event_id,
|
||||
payload,
|
||||
salt,
|
||||
} => {
|
||||
crate::event::answer(event_id, payload, salt);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "api"))]
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#[derive(Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
#[cfg(feature = "api")]
|
||||
pub enum Cmd {
|
||||
#[cfg(any(feature = "all-api", feature = "readTextFile"))]
|
||||
ReadTextFile {
|
||||
|
@ -43,5 +42,21 @@ pub enum Cmd {
|
|||
error: String,
|
||||
},
|
||||
#[cfg(any(feature = "all-api", feature = "open"))]
|
||||
Open { uri: String }
|
||||
Open { uri: String },
|
||||
ValidateSalt {
|
||||
salt: String,
|
||||
callback: String,
|
||||
error: String,
|
||||
},
|
||||
AddEventListener {
|
||||
event: String,
|
||||
handler: String,
|
||||
once: bool,
|
||||
},
|
||||
#[cfg(any(feature = "all-api", feature = "answer"))]
|
||||
Answer {
|
||||
event_id: String,
|
||||
payload: String,
|
||||
salt: String,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use proton_ui::WebView;
|
|||
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
use super::run_async;
|
||||
use crate::execute_promise;
|
||||
|
||||
pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> Result<String, String> {
|
||||
Command::new(cmd)
|
||||
|
@ -65,7 +65,7 @@ pub fn call<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
|| {
|
||||
get_output(command, args, Stdio::piped())
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use tempfile;
|
||||
|
||||
mod utils;
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
use proton_ui::{Handle, WebView};
|
||||
use std::boxed::Box;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct EventHandler {
|
||||
on_event: Box<dyn FnOnce(String)>,
|
||||
}
|
||||
|
||||
thread_local!(static LISTENERS: Arc<Mutex<HashMap<String, EventHandler>>> = Arc::new(Mutex::new(HashMap::new())));
|
||||
|
||||
lazy_static! {
|
||||
static ref PROMPT_FUNCTION_NAME: String = uuid::Uuid::new_v4().to_string();
|
||||
static ref EVENT_LISTENERS_OBJECT_NAME: String = uuid::Uuid::new_v4().to_string();
|
||||
}
|
||||
|
||||
pub fn prompt_function_name() -> String {
|
||||
PROMPT_FUNCTION_NAME.to_string()
|
||||
}
|
||||
|
||||
pub fn event_listeners_object_name() -> String {
|
||||
EVENT_LISTENERS_OBJECT_NAME.to_string()
|
||||
}
|
||||
|
||||
pub fn prompt<T: 'static, F: FnOnce(String) + 'static>(
|
||||
webview: &mut WebView<'_, T>,
|
||||
id: &'static str,
|
||||
payload: String,
|
||||
handler: F,
|
||||
) {
|
||||
LISTENERS.with(|listeners| {
|
||||
let mut l = listeners.lock().unwrap();
|
||||
l.insert(
|
||||
id.to_string(),
|
||||
EventHandler {
|
||||
on_event: Box::new(handler),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
trigger(webview.handle(), id, payload);
|
||||
}
|
||||
|
||||
pub fn trigger<T: 'static>(webview_handle: Handle<T>, id: &'static str, mut payload: String) {
|
||||
let salt = crate::salt::generate();
|
||||
if payload == "" {
|
||||
payload = "void 0".to_string();
|
||||
}
|
||||
|
||||
webview_handle
|
||||
.dispatch(move |_webview| {
|
||||
_webview.eval(&format!(
|
||||
"window['{}']({{type: '{}', payload: {}}}, '{}')",
|
||||
prompt_function_name(),
|
||||
id,
|
||||
payload,
|
||||
salt
|
||||
))
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn answer(id: String, data: String, salt: String) {
|
||||
if crate::salt::is_valid(salt) {
|
||||
LISTENERS.with(|l| {
|
||||
let mut listeners = l.lock().unwrap();
|
||||
|
||||
let key = id.clone();
|
||||
|
||||
if listeners.contains_key(&id) {
|
||||
let handler = listeners.remove(&id).unwrap();
|
||||
(handler.on_event)(data);
|
||||
}
|
||||
|
||||
listeners.remove(&key);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use proton_ui::WebView;
|
||||
|
||||
use crate::dir;
|
||||
use crate::execute_promise;
|
||||
use crate::file;
|
||||
use crate::run_async;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
@ -13,7 +13,7 @@ pub fn list<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
dir::walk_dir(path.to_string())
|
||||
|
@ -30,7 +30,7 @@ pub fn list_dirs<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
dir::list_dir_contents(&path)
|
||||
|
@ -48,7 +48,7 @@ pub fn write_file<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
File::create(file)
|
||||
|
@ -70,7 +70,7 @@ pub fn read_text_file<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
file::read_string(path).and_then(|f| {
|
||||
|
@ -90,7 +90,7 @@ pub fn read_binary_file<T: 'static>(
|
|||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
run_async(
|
||||
execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
file::read_binary(path).and_then(|f| {
|
||||
|
|
|
@ -4,15 +4,20 @@ extern crate serde_derive;
|
|||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod api;
|
||||
pub mod command;
|
||||
pub mod dir;
|
||||
pub mod event;
|
||||
pub mod file;
|
||||
pub mod file_system;
|
||||
pub mod http;
|
||||
pub mod platform;
|
||||
pub mod process;
|
||||
pub mod rpc;
|
||||
pub mod salt;
|
||||
pub mod tcp;
|
||||
pub mod updater;
|
||||
pub mod version;
|
||||
|
@ -23,9 +28,7 @@ use threadpool::ThreadPool;
|
|||
|
||||
thread_local!(static POOL: ThreadPool = ThreadPool::new(4));
|
||||
|
||||
pub fn spawn<F: FnOnce() -> () + Send + 'static>(
|
||||
what: F
|
||||
) {
|
||||
pub fn spawn<F: FnOnce() -> () + Send + 'static>(what: F) {
|
||||
POOL.with(|thread| {
|
||||
thread.execute(move || {
|
||||
what();
|
||||
|
@ -33,7 +36,13 @@ pub fn spawn<F: FnOnce() -> () + Send + 'static>(
|
|||
});
|
||||
}
|
||||
|
||||
pub fn run_async<T: 'static, F: FnOnce() -> Result<String, String> + Send + 'static>(
|
||||
pub fn run_async<F: FnOnce() -> () + Send + 'static>(what: F) {
|
||||
POOL.with(|thread| {
|
||||
thread.execute(move || what());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn execute_promise<T: 'static, F: FnOnce() -> Result<String, String> + Send + 'static>(
|
||||
webview: &mut WebView<'_, T>,
|
||||
what: F,
|
||||
callback: String,
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
use proton_ui::WebView;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct Salt {
|
||||
value: String,
|
||||
one_time: bool,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SALTS: Mutex<Vec<Salt>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
pub fn generate() -> String {
|
||||
let salt = Uuid::new_v4();
|
||||
SALTS.lock().unwrap().push(Salt {
|
||||
value: salt.to_string(),
|
||||
one_time: true,
|
||||
});
|
||||
return salt.to_string();
|
||||
}
|
||||
|
||||
pub fn generate_static() -> String {
|
||||
let salt = Uuid::new_v4();
|
||||
SALTS.lock().unwrap().push(Salt {
|
||||
value: salt.to_string(),
|
||||
one_time: false,
|
||||
});
|
||||
return salt.to_string();
|
||||
}
|
||||
|
||||
pub fn is_valid(salt: String) -> bool {
|
||||
let mut salts = SALTS.lock().unwrap();
|
||||
match salts.iter().position(|s| s.value == salt) {
|
||||
Some(index) => {
|
||||
if salts[index].one_time {
|
||||
salts.remove(index);
|
||||
}
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate<T: 'static>(
|
||||
webview: &mut WebView<'_, T>,
|
||||
salt: String,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
webview,
|
||||
move || {
|
||||
if is_valid(salt) {
|
||||
Ok("'VALID'".to_string())
|
||||
} else {
|
||||
Err("'INVALID SALT'".to_string())
|
||||
}
|
||||
},
|
||||
callback,
|
||||
error,
|
||||
);
|
||||
}
|
|
@ -123,9 +123,46 @@ fn main() {
|
|||
.build()
|
||||
.unwrap();
|
||||
|
||||
webview
|
||||
.handle()
|
||||
.dispatch(move |_webview| {
|
||||
_webview
|
||||
.eval(&format!(
|
||||
"window['{fn}'] = (payload, salt) => {{
|
||||
window.proton.promisified({{
|
||||
cmd: 'validateSalt',
|
||||
salt
|
||||
}}).then(() => {{
|
||||
const listeners = (window['{obj}'] && window['{obj}'][payload.type]) || []
|
||||
for (let i = listeners.length - 1; i >= 0; i--) {{
|
||||
const listener = listeners[i]
|
||||
if (listener.once)
|
||||
listeners.splice(i, 1)
|
||||
const response = listener.handler(payload)
|
||||
response && response
|
||||
.then(result => {{
|
||||
window.proton.invoke({{
|
||||
cmd: 'answer',
|
||||
event_id: payload.type,
|
||||
payload: result,
|
||||
salt: '{salt}'
|
||||
}})
|
||||
}})
|
||||
}}
|
||||
}})
|
||||
}}",
|
||||
fn = proton::event::prompt_function_name(),
|
||||
obj = proton::event::event_listeners_object_name(),
|
||||
salt = proton::salt::generate_static()
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(feature = "dev"))]
|
||||
{
|
||||
|
||||
#[cfg(not(feature = "serverless"))]
|
||||
{
|
||||
thread::spawn(move || {
|
||||
|
|
Loading…
Reference in New Issue