@ionyx-apps/cli 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ionyx.exe +0 -0
- package/ionyx-cli/src/templates/angular/src-ionyx/Cargo.toml +30 -0
- package/ionyx-cli/src/templates/angular/src-ionyx/build.rs +5 -0
- package/ionyx-cli/src/templates/angular/src-ionyx/main.rs +48 -0
- package/ionyx-cli/src/templates/angular/src-ionyx/src/lib.rs +267 -0
- package/ionyx-cli/src/templates/basic/ionyx.config.json +3 -0
- package/ionyx-cli/src/templates/basic/package.json +6 -1
- package/ionyx-cli/src/templates/basic/src-ionyx/Cargo.toml +14 -12
- package/ionyx-cli/src/templates/basic/src-ionyx/main.rs +46 -3
- package/ionyx-cli/src/templates/leptos/package.json +6 -1
- package/ionyx-cli/src/templates/leptos/src-ionyx/Cargo.toml +14 -8
- package/ionyx-cli/src/templates/leptos/src-ionyx/main.rs +46 -3
- package/ionyx-cli/src/templates/mod.rs +1 -5
- package/ionyx-cli/src/templates/react/ionyx.config.json +4 -1
- package/ionyx-cli/src/templates/react/package.json +6 -1
- package/ionyx-cli/src/templates/react/src-ionyx/Cargo.toml +14 -12
- package/ionyx-cli/src/templates/react/src-ionyx/main.rs +46 -3
- package/ionyx-cli/src/templates/src-ionyx/Cargo.toml +15 -5
- package/ionyx-cli/src/templates/src-ionyx/bridge.rs +380 -0
- package/ionyx-cli/src/templates/src-ionyx/fusion.rs +47 -0
- package/ionyx-cli/src/templates/src-ionyx/ionyx.js +100 -0
- package/ionyx-cli/src/templates/src-ionyx/ipc_bridge.rs +206 -0
- package/ionyx-cli/src/templates/src-ionyx/lib.rs +204 -114
- package/ionyx-cli/src/templates/src-ionyx/main.rs +268 -3
- package/ionyx-cli/src/templates/src-ionyx/protocol.rs +98 -0
- package/ionyx-cli/src/templates/src-ionyx/window.rs +13 -0
- package/ionyx-cli/src/templates/svelte/package.json +6 -1
- package/ionyx-cli/src/templates/svelte/src-ionyx/Cargo.toml +14 -12
- package/ionyx-cli/src/templates/svelte/src-ionyx/main.rs +46 -3
- package/ionyx-cli/src/templates/vanilla/ionyx.config.json +3 -0
- package/ionyx-cli/src/templates/vanilla/package.json +6 -1
- package/ionyx-cli/src/templates/vanilla/src-ionyx/Cargo.toml +14 -12
- package/ionyx-cli/src/templates/vanilla/src-ionyx/main.rs +46 -3
- package/ionyx-cli/src/templates/vue/ionyx.config.json +4 -1
- package/ionyx-cli/src/templates/vue/package.json +6 -1
- package/ionyx-cli/src/templates/vue/src-ionyx/Cargo.toml +14 -12
- package/ionyx-cli/src/templates/vue/src-ionyx/main.rs +46 -3
- package/package.json +1 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
use serde_json::{json, Value};
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
use tokio::sync::RwLock;
|
|
7
|
+
use tracing::{error, info};
|
|
8
|
+
|
|
9
|
+
use crate::config::Config;
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
12
|
+
pub struct IpcBridgeRequest {
|
|
13
|
+
pub source_app: String,
|
|
14
|
+
pub target_app: String,
|
|
15
|
+
pub command: String,
|
|
16
|
+
pub payload: Value,
|
|
17
|
+
pub id: String,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
21
|
+
pub struct IpcBridgeResponse {
|
|
22
|
+
pub id: String,
|
|
23
|
+
pub success: bool,
|
|
24
|
+
pub data: Option<Value>,
|
|
25
|
+
pub error: Option<String>,
|
|
26
|
+
pub source_app: String,
|
|
27
|
+
pub target_app: String,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
31
|
+
pub struct AppInfo {
|
|
32
|
+
pub name: String,
|
|
33
|
+
pub version: String,
|
|
34
|
+
pub pid: u32,
|
|
35
|
+
pub capabilities: Vec<String>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub struct IpcBridgeManager {
|
|
39
|
+
apps: Arc<RwLock<HashMap<String, AppInfo>>>,
|
|
40
|
+
config: Config,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
impl IpcBridgeManager {
|
|
44
|
+
pub fn new(config: Config, _app_name: String) -> Self {
|
|
45
|
+
let apps = Arc::new(RwLock::new(HashMap::new()));
|
|
46
|
+
|
|
47
|
+
Self { apps, config }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub async fn register_app(&self, app_info: AppInfo) -> Result<()> {
|
|
51
|
+
info!("Registering app: {} (PID: {})", app_info.name, app_info.pid);
|
|
52
|
+
|
|
53
|
+
let mut apps = self.apps.write().await;
|
|
54
|
+
apps.insert(app_info.name.clone(), app_info);
|
|
55
|
+
|
|
56
|
+
info!("App registered successfully. Total apps: {}", apps.len());
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub async fn unregister_app(&self, app_name: &str) -> Result<()> {
|
|
61
|
+
info!("Unregistering app: {}", app_name);
|
|
62
|
+
|
|
63
|
+
let mut apps = self.apps.write().await;
|
|
64
|
+
apps.remove(app_name);
|
|
65
|
+
|
|
66
|
+
info!("App unregistered successfully. Total apps: {}", apps.len());
|
|
67
|
+
Ok(())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub async fn get_registered_apps(&self) -> Result<Vec<AppInfo>> {
|
|
71
|
+
let apps = self.apps.read().await;
|
|
72
|
+
Ok(apps.values().cloned().collect())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub async fn send_to_app(&self, request: IpcBridgeRequest) -> Result<IpcBridgeResponse> {
|
|
76
|
+
info!(
|
|
77
|
+
"Sending IPC request from {} to {}: {}",
|
|
78
|
+
request.source_app, request.target_app, request.command
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
let source_app = request.source_app.clone();
|
|
82
|
+
let target_app = request.target_app.clone();
|
|
83
|
+
|
|
84
|
+
// Check if target app exists
|
|
85
|
+
let apps = self.apps.read().await;
|
|
86
|
+
if !apps.contains_key(&target_app) {
|
|
87
|
+
return Err(anyhow::anyhow!("Target app '{}' not found", target_app));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if source app has permission to send to target app
|
|
91
|
+
if !self
|
|
92
|
+
.check_permissions(&source_app, &target_app, &request.command)
|
|
93
|
+
.await?
|
|
94
|
+
{
|
|
95
|
+
return Err(anyhow::anyhow!(
|
|
96
|
+
"Permission denied for command '{}' from '{}' to '{}'",
|
|
97
|
+
request.command,
|
|
98
|
+
source_app,
|
|
99
|
+
target_app
|
|
100
|
+
));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Process the request
|
|
104
|
+
let response = match self.process_bridge_request(&request).await {
|
|
105
|
+
Ok(data) => IpcBridgeResponse {
|
|
106
|
+
id: request.id,
|
|
107
|
+
success: true,
|
|
108
|
+
data: Some(data),
|
|
109
|
+
error: None,
|
|
110
|
+
source_app,
|
|
111
|
+
target_app,
|
|
112
|
+
},
|
|
113
|
+
Err(e) => IpcBridgeResponse {
|
|
114
|
+
id: request.id,
|
|
115
|
+
success: false,
|
|
116
|
+
data: None,
|
|
117
|
+
error: Some(e.to_string()),
|
|
118
|
+
source_app,
|
|
119
|
+
target_app,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
info!(
|
|
124
|
+
"IPC request processed: {} -> {} ({})",
|
|
125
|
+
response.source_app, response.target_app, response.success
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
Ok(response)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pub async fn send_to_app_direct(&self, request: IpcBridgeRequest) -> Result<IpcBridgeResponse> {
|
|
132
|
+
self.process_bridge_request(&request)
|
|
133
|
+
.await
|
|
134
|
+
.map(|data| IpcBridgeResponse {
|
|
135
|
+
id: request.id,
|
|
136
|
+
success: true,
|
|
137
|
+
data: Some(data),
|
|
138
|
+
error: None,
|
|
139
|
+
source_app: request.source_app,
|
|
140
|
+
target_app: request.target_app,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async fn check_permissions(
|
|
145
|
+
&self,
|
|
146
|
+
_source_app: &str,
|
|
147
|
+
_target_app: &str,
|
|
148
|
+
_command: &str,
|
|
149
|
+
) -> Result<bool> {
|
|
150
|
+
if self.config.permissions.bridge {
|
|
151
|
+
Ok(true)
|
|
152
|
+
} else {
|
|
153
|
+
error!("Bridge permission is disabled in config");
|
|
154
|
+
Ok(false)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async fn process_bridge_request(&self, request: &IpcBridgeRequest) -> Result<Value> {
|
|
159
|
+
match request.command.as_str() {
|
|
160
|
+
"app.getInfo" => {
|
|
161
|
+
let apps = self.apps.read().await;
|
|
162
|
+
if let Some(app_info) = apps.get(&request.target_app) {
|
|
163
|
+
Ok(json!({
|
|
164
|
+
"name": app_info.name,
|
|
165
|
+
"version": app_info.version,
|
|
166
|
+
"pid": app_info.pid,
|
|
167
|
+
"capabilities": app_info.capabilities
|
|
168
|
+
}))
|
|
169
|
+
} else {
|
|
170
|
+
Err(anyhow::anyhow!("App '{}' not found", request.target_app))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
"app.list" => {
|
|
174
|
+
let apps = self.apps.read().await;
|
|
175
|
+
let app_list: Vec<Value> = apps
|
|
176
|
+
.values()
|
|
177
|
+
.map(|app| {
|
|
178
|
+
json!({
|
|
179
|
+
"name": app.name,
|
|
180
|
+
"version": app.version,
|
|
181
|
+
"pid": app.pid,
|
|
182
|
+
"capabilities": app.capabilities
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
.collect();
|
|
186
|
+
Ok(json!(app_list))
|
|
187
|
+
}
|
|
188
|
+
"system.broadcast" => {
|
|
189
|
+
// Broadcast message to all registered apps
|
|
190
|
+
info!(
|
|
191
|
+
"Broadcasting message from {}: {:?}",
|
|
192
|
+
request.source_app, request.payload
|
|
193
|
+
);
|
|
194
|
+
Ok(json!({
|
|
195
|
+
"broadcast_sent": true,
|
|
196
|
+
"message": "Message broadcasted to all apps",
|
|
197
|
+
"recipients": self.apps.read().await.len()
|
|
198
|
+
}))
|
|
199
|
+
}
|
|
200
|
+
_ => Err(anyhow::anyhow!(
|
|
201
|
+
"Unknown bridge command: {}",
|
|
202
|
+
request.command
|
|
203
|
+
)),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -1,136 +1,226 @@
|
|
|
1
1
|
use anyhow::Result;
|
|
2
|
-
use
|
|
2
|
+
use serde_json::json;
|
|
3
|
+
use std::env;
|
|
4
|
+
use std::path::PathBuf;
|
|
3
5
|
use std::sync::{Arc, Mutex};
|
|
4
|
-
use
|
|
5
|
-
use tracing::{
|
|
6
|
-
use
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy};
|
|
7
|
+
use tracing::{error, info};
|
|
8
|
+
use wry::{WebContext, WebViewBuilder};
|
|
9
|
+
|
|
10
|
+
mod bridge;
|
|
11
|
+
mod config;
|
|
12
|
+
mod ipc_bridge;
|
|
13
|
+
mod protocol;
|
|
14
|
+
mod security;
|
|
15
|
+
mod window;
|
|
16
|
+
mod fusion;
|
|
17
|
+
|
|
18
|
+
use fusion::FusionManager;
|
|
19
|
+
|
|
20
|
+
use bridge::IpcBridge;
|
|
21
|
+
use config::Config;
|
|
22
|
+
|
|
23
|
+
// IPC Dispatcher for resolving frontend promises
|
|
24
|
+
#[derive(Debug, Clone)]
|
|
25
|
+
pub struct IpcDispatcher {
|
|
26
|
+
proxy: EventLoopProxy<String>,
|
|
13
27
|
}
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
pub
|
|
17
|
-
|
|
18
|
-
pub success: bool,
|
|
19
|
-
pub data: Option<serde_json::Value>,
|
|
20
|
-
pub error: Option<String>,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
pub struct IpcBridge {
|
|
24
|
-
config: crate::config::Config,
|
|
25
|
-
security: SecurityManager,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
impl IpcBridge {
|
|
29
|
-
pub fn new(config: crate::config::Config) -> Self {
|
|
30
|
-
Self {
|
|
31
|
-
config,
|
|
32
|
-
security: SecurityManager::new(config.clone()),
|
|
33
|
-
}
|
|
29
|
+
impl IpcDispatcher {
|
|
30
|
+
pub fn new(proxy: EventLoopProxy<String>) -> Self {
|
|
31
|
+
Self { proxy }
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
pub
|
|
34
|
+
pub fn resolve_promise(
|
|
37
35
|
&self,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return Err(anyhow::anyhow!("Command
|
|
50
|
-
|
|
51
|
-
not allowed", ipc_request.command));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Process command
|
|
55
|
-
let response = match self.process_command(&ipc_request).await {
|
|
56
|
-
Ok(data) => IpcResponse {
|
|
57
|
-
id: ipc_request.id,
|
|
58
|
-
success: true,
|
|
59
|
-
data: Some(data),
|
|
60
|
-
error: None,
|
|
61
|
-
},
|
|
62
|
-
Err(e) => IpcResponse {
|
|
63
|
-
id: ipc_request.id,
|
|
64
|
-
success: false,
|
|
65
|
-
data: None,
|
|
66
|
-
error: Some(e.to_string()),
|
|
67
|
-
},
|
|
68
|
-
};
|
|
36
|
+
request_id: &str,
|
|
37
|
+
success: bool,
|
|
38
|
+
data: Option<serde_json::Value>,
|
|
39
|
+
error: Option<String>,
|
|
40
|
+
) {
|
|
41
|
+
let response = json!({
|
|
42
|
+
"id": request_id,
|
|
43
|
+
"success": success,
|
|
44
|
+
"data": data,
|
|
45
|
+
"error": error
|
|
46
|
+
});
|
|
69
47
|
|
|
70
|
-
// Send response back to frontend
|
|
71
|
-
let response_json = serde_json::to_string(&response)?;
|
|
72
48
|
let js_code = format!(
|
|
73
|
-
"if (window.ionyx && window.ionyx.resolveResponse) {{ window.ionyx.resolveResponse(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
response.id,
|
|
77
|
-
response_json
|
|
49
|
+
"if (window.ionyx && window.ionyx.resolveResponse) {{ window.ionyx.resolveResponse('{}', {}); }}",
|
|
50
|
+
request_id,
|
|
51
|
+
response
|
|
78
52
|
);
|
|
79
53
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
info!("✅ IPC response sent for: {}", ipc_request.command);
|
|
84
|
-
Ok(())
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async fn process_command(&self, request: &IpcRequest) -> Result<serde_json::Value> {
|
|
88
|
-
match request.command.as_str() {
|
|
89
|
-
"app.getVersion" => self.handle_get_version().await,
|
|
90
|
-
"app.getConfig" => self.handle_get_config().await,
|
|
91
|
-
"fs.exists" => self.handle_fs_exists(&request.payload).await,
|
|
92
|
-
"os.info" => self.handle_os_info().await,
|
|
93
|
-
_ => Err(anyhow::anyhow!("Unknown command: {}", request.command)),
|
|
54
|
+
if let Err(e) = self.proxy.send_event(js_code) {
|
|
55
|
+
error!("Failed to send IPC response to event loop: {}", e);
|
|
94
56
|
}
|
|
95
57
|
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub struct Builder;
|
|
96
61
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"version": "1.0.0",
|
|
101
|
-
"platform": std::env::consts::OS,
|
|
102
|
-
"arch": std::env::consts::ARCH
|
|
103
|
-
}))
|
|
62
|
+
impl Default for Builder {
|
|
63
|
+
fn default() -> Self {
|
|
64
|
+
Self::new()
|
|
104
65
|
}
|
|
66
|
+
}
|
|
105
67
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"permissions": self.config.ionyx.permissions,
|
|
110
|
-
"security": self.config.ionyx.security
|
|
111
|
-
}))
|
|
68
|
+
impl Builder {
|
|
69
|
+
pub fn new() -> Self {
|
|
70
|
+
Self
|
|
112
71
|
}
|
|
113
72
|
|
|
114
|
-
|
|
115
|
-
let
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
73
|
+
pub fn run(self) -> Result<()> {
|
|
74
|
+
let rt = tokio::runtime::Runtime::new()?;
|
|
75
|
+
rt.block_on(async {
|
|
76
|
+
self.run_async().await
|
|
77
|
+
})
|
|
78
|
+
}
|
|
119
79
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
80
|
+
async fn run_async(self) -> Result<()> {
|
|
81
|
+
// Initialize logging
|
|
82
|
+
tracing_subscriber::fmt::init();
|
|
83
|
+
|
|
84
|
+
info!("Starting Ionyx Framework Application");
|
|
85
|
+
|
|
86
|
+
// Load configuration
|
|
87
|
+
let config = Config::load().await?;
|
|
88
|
+
info!("Loaded configuration: {}", config.app.name);
|
|
89
|
+
|
|
90
|
+
// Create event loop
|
|
91
|
+
let event_loop: EventLoop<String> = EventLoopBuilder::with_user_event().build();
|
|
92
|
+
let _web_context = WebContext::new(Some(Default::default()));
|
|
93
|
+
|
|
94
|
+
// Create window
|
|
95
|
+
let window = config
|
|
96
|
+
.window
|
|
97
|
+
.apply_to_builder(tao::window::WindowBuilder::new())
|
|
98
|
+
.build(&event_loop)?;
|
|
99
|
+
|
|
100
|
+
// Get the current working directory
|
|
101
|
+
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
102
|
+
let dist_dir = if current_dir.ends_with("dist") {
|
|
103
|
+
// If we're already in the dist directory, use current directory
|
|
104
|
+
current_dir.clone()
|
|
105
|
+
} else {
|
|
106
|
+
// Otherwise, look for dist subdirectory
|
|
107
|
+
current_dir.join("dist")
|
|
108
|
+
};
|
|
124
109
|
|
|
125
|
-
|
|
126
|
-
|
|
110
|
+
info!("Current working directory: {:?}", current_dir);
|
|
111
|
+
info!("Dist directory: {:?}", dist_dir);
|
|
112
|
+
|
|
113
|
+
// Create Fusion Manager
|
|
114
|
+
let fusion_manager = Arc::new(FusionManager::new());
|
|
115
|
+
// Initialize with basic app info (Inspired by NW.js context)
|
|
116
|
+
fusion_manager.set("app_name", serde_json::Value::String(config.app.name.clone()));
|
|
117
|
+
fusion_manager.set("app_version", serde_json::Value::String(config.app.version.clone()));
|
|
118
|
+
fusion_manager.set("os", serde_json::Value::String(os_info::get().os_type().to_string()));
|
|
119
|
+
|
|
120
|
+
// Create IPC bridge
|
|
121
|
+
let ipc_bridge = std::sync::Arc::new(IpcBridge::new(
|
|
122
|
+
config.clone(),
|
|
123
|
+
IpcDispatcher::new(event_loop.create_proxy()),
|
|
124
|
+
fusion_manager.clone(),
|
|
125
|
+
));
|
|
126
|
+
|
|
127
|
+
// Import Windows-specific trait for WebView extensions
|
|
128
|
+
#[cfg(target_os = "windows")]
|
|
129
|
+
use wry::WebViewBuilderExtWindows;
|
|
130
|
+
|
|
131
|
+
// Determine webview URL based on build mode
|
|
132
|
+
let webview = if cfg!(debug_assertions) {
|
|
133
|
+
// Development mode: use IONYX_URL from environment
|
|
134
|
+
let dev_url = std::env::var("IONYX_URL")
|
|
135
|
+
.unwrap_or_else(|_| {
|
|
136
|
+
format!("http://127.0.0.1:{}", config.development.port)
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
info!("Development mode: Connecting to {}", dev_url);
|
|
140
|
+
|
|
141
|
+
let builder = WebViewBuilder::new()
|
|
142
|
+
.with_initialization_script(include_str!("ionyx.js"))
|
|
143
|
+
.with_initialization_script(&fusion_manager.generate_sync_script())
|
|
144
|
+
.with_ipc_handler(move |request| {
|
|
145
|
+
let bridge = ipc_bridge.clone();
|
|
146
|
+
let request_body = request.body().clone();
|
|
147
|
+
tokio::spawn(async move {
|
|
148
|
+
if let Err(e) = bridge.handle_request_with_response(request_body).await {
|
|
149
|
+
error!("IPC request failed: {}", e);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
})
|
|
153
|
+
.with_navigation_handler(|url| {
|
|
154
|
+
// Allow localhost and file:// protocols in debug mode
|
|
155
|
+
url.starts_with("http://localhost:") ||
|
|
156
|
+
url.starts_with("https://localhost:") ||
|
|
157
|
+
url.starts_with("http://127.0.0.1:") ||
|
|
158
|
+
url.starts_with("https://127.0.0.1:") ||
|
|
159
|
+
url.starts_with("file://") ||
|
|
160
|
+
url.starts_with("ionyx://")
|
|
161
|
+
})
|
|
162
|
+
.with_url(&dev_url)
|
|
163
|
+
.with_devtools(true);
|
|
164
|
+
|
|
165
|
+
#[cfg(target_os = "windows")]
|
|
166
|
+
let builder = builder.with_additional_browser_args("--enable-unsafe-webgpu --enable-features=WebGPU");
|
|
167
|
+
|
|
168
|
+
builder
|
|
169
|
+
} else {
|
|
170
|
+
// Release mode: serve embedded assets from binary
|
|
171
|
+
info!("Release mode: Loading embedded assets from binary");
|
|
172
|
+
|
|
173
|
+
let builder = WebViewBuilder::new()
|
|
174
|
+
.with_initialization_script(include_str!("ionyx.js"))
|
|
175
|
+
.with_initialization_script(&fusion_manager.generate_sync_script())
|
|
176
|
+
.with_ipc_handler(move |request| {
|
|
177
|
+
let bridge = ipc_bridge.clone();
|
|
178
|
+
let request_body = request.body().clone();
|
|
179
|
+
tokio::spawn(async move {
|
|
180
|
+
if let Err(e) = bridge.handle_request_with_response(request_body).await {
|
|
181
|
+
error!("IPC request failed: {}", e);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
})
|
|
185
|
+
.with_custom_protocol("ionyx".to_string(), move |_, request| {
|
|
186
|
+
let handler = crate::protocol::CustomProtocolHandler::new();
|
|
187
|
+
handler.handle_request(request)
|
|
188
|
+
})
|
|
189
|
+
.with_url("ionyx://index.html");
|
|
190
|
+
|
|
191
|
+
#[cfg(target_os = "windows")]
|
|
192
|
+
let builder = builder.with_additional_browser_args("--enable-unsafe-webgpu --enable-features=WebGPU");
|
|
193
|
+
|
|
194
|
+
builder
|
|
195
|
+
};
|
|
127
196
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
197
|
+
let webview = Arc::new(Mutex::new(webview.build(&window)?));
|
|
198
|
+
|
|
199
|
+
// Run event loop
|
|
200
|
+
event_loop.run(move |event, _, control_flow| {
|
|
201
|
+
*control_flow = ControlFlow::Wait;
|
|
202
|
+
|
|
203
|
+
match event {
|
|
204
|
+
tao::event::Event::UserEvent(js_code) => {
|
|
205
|
+
// Execute JavaScript code from IPC dispatcher
|
|
206
|
+
info!("Executing JavaScript code: {}", js_code);
|
|
207
|
+
if let Ok(webview_guard) = webview.lock() {
|
|
208
|
+
if let Err(e) = webview_guard.evaluate_script(&js_code) {
|
|
209
|
+
error!("Failed to execute JavaScript code: {}", e);
|
|
210
|
+
} else {
|
|
211
|
+
info!("JavaScript code executed successfully");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
tao::event::Event::WindowEvent {
|
|
216
|
+
event: tao::event::WindowEvent::CloseRequested,
|
|
217
|
+
..
|
|
218
|
+
} => {
|
|
219
|
+
info!("Window close requested");
|
|
220
|
+
*control_flow = ControlFlow::Exit;
|
|
221
|
+
}
|
|
222
|
+
_ => (),
|
|
223
|
+
}
|
|
224
|
+
});
|
|
135
225
|
}
|
|
136
|
-
}
|
|
226
|
+
}
|