deckyfx_dioxus_ipc_bridge/
bridge.rs1use crate::platform;
7use crate::plugin::BridgePlugin;
8use std::time::Duration;
9
10#[derive(Clone, PartialEq)]
12pub struct IpcBridge {
13 pub timeout: Duration,
15 pub custom_script: Option<String>,
17 plugins: Vec<String>, }
20
21impl IpcBridge {
22 pub fn builder() -> IpcBridgeBuilder {
24 IpcBridgeBuilder::new()
25 }
26
27 pub fn generate_script(&self) -> String {
31 let script = if let Some(custom) = &self.custom_script {
32 custom.clone()
33 } else {
34 Self::default_bridge_script(self.timeout.as_millis() as u32)
35 };
36
37 script
40 }
41
42 pub fn initialize(&self) {
44 let script = self.generate_script();
45 platform::eval_js(&script);
46 }
47
48 fn default_bridge_script(timeout_ms: u32) -> String {
50 format!(r#"
51 // Complete Dioxus Bridge - Unified IPC API
52 // Preserve existing properties (like IPCBridge from React) if already initialized
53 if (!window.dioxusBridge) {{
54 window.dioxusBridge = {{}};
55 }}
56
57 // Preserve IPCBridge if it was already attached by React
58 const existingIPCBridge = window.dioxusBridge.IPCBridge;
59
60 // Preserve or create callbacks Map (MUST be same reference)
61 if (!window.dioxusBridge.callbacks) {{
62 window.dioxusBridge.callbacks = new Map();
63 }}
64 const callbacks = window.dioxusBridge.callbacks;
65
66 // Add/update core Rust-provided properties
67 Object.assign(window.dioxusBridge, {{
68 // Internal callback storage (previously window.dioxusBridgeCallbacks)
69 callbacks: callbacks,
70
71 // HTTP-like fetch() method for IPC communication
72 fetch: function(url, options = {{}}) {{
73 return new Promise((resolve, reject) => {{
74 const requestId = Math.floor(Math.random() * 1000000);
75
76 // Store callback in namespaced location (use explicit reference)
77 window.dioxusBridge.callbacks.set(requestId, {{resolve, reject}});
78
79 // Build HTTP-like IPC request
80 const request = {{
81 id: requestId,
82 method: options.method || 'GET',
83 url: url,
84 headers: options.headers || {{}},
85 body: options.body
86 }};
87
88 // Send to Rust via dioxus.send()
89 if (typeof dioxus !== 'undefined' && typeof dioxus.send === 'function') {{
90 dioxus.send(request);
91 }} else {{
92 reject(new Error('dioxus.send() not available'));
93 return;
94 }}
95
96 // Configurable timeout
97 setTimeout(() => {{
98 if (window.dioxusBridge && window.dioxusBridge.callbacks && window.dioxusBridge.callbacks.has(requestId)) {{
99 window.dioxusBridge.callbacks.delete(requestId);
100 reject(new Error('Request timeout after {} ms'));
101 }}
102 }}, {});
103 }});
104 }},
105
106 // Rust → React event emitter (previously window.rustEmit)
107 rustEmit: function(channel, data) {{
108 if (window.dioxusBridge.IPCBridge && typeof window.dioxusBridge.IPCBridge.emit === 'function') {{
109 window.dioxusBridge.IPCBridge.emit(channel, data);
110 }} else {{
111 console.warn('[Rust] IPCBridge not available, event not emitted:', channel);
112 }}
113 }},
114
115 // Direct IPC interface
116 ipc: {{
117 send: function(data) {{
118 if (typeof dioxus !== 'undefined' && typeof dioxus.send === 'function') {{
119 dioxus.send(data);
120 }} else {{
121 console.error('[Rust] dioxus.send() not available');
122 }}
123 }},
124 hasIPCBridge: function() {{
125 return typeof window.dioxusBridge.IPCBridge !== 'undefined';
126 }}
127 }},
128
129 // Low-level send wrapper
130 send: function(data) {{
131 if (window.dioxusBridge && window.dioxusBridge.ipc) {{
132 window.dioxusBridge.ipc.send(data);
133 }}
134 }}
135 }});
136
137 // Restore IPCBridge if it existed before
138 if (existingIPCBridge) {{
139 window.dioxusBridge.IPCBridge = existingIPCBridge;
140 }}
141
142 console.log('[Rust] window.dioxusBridge ready (Unified IPC API)');
143 new Promise(() => {{}}); // Keep eval alive
144 "#, timeout_ms, timeout_ms)
145 }
146}
147
148pub struct IpcBridgeBuilder {
150 timeout: Duration,
151 custom_script: Option<String>,
152 plugins: Vec<Box<dyn BridgePlugin>>,
153}
154
155impl IpcBridgeBuilder {
156 pub fn new() -> Self {
158 Self {
159 timeout: Duration::from_secs(10),
160 custom_script: None,
161 plugins: Vec::new(),
162 }
163 }
164
165 pub fn timeout(mut self, duration: Duration) -> Self {
179 self.timeout = duration;
180 self
181 }
182
183 pub fn custom_script(mut self, script: String) -> Self {
187 self.custom_script = Some(script);
188 self
189 }
190
191 pub fn plugin(mut self, plugin: Box<dyn BridgePlugin>) -> Self {
201 self.plugins.push(plugin);
202 self
203 }
204
205 pub fn build(self) -> IpcBridge {
207 IpcBridge {
208 timeout: self.timeout,
209 custom_script: self.custom_script,
210 plugins: self.plugins.iter().map(|p| p.name().to_string()).collect(),
211 }
212 }
213}
214
215impl Default for IpcBridgeBuilder {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221pub fn generate_dioxus_bridge_script() -> &'static str {
250 r#"
252 // Complete Dioxus Bridge - Unified IPC API
253 // Preserve existing properties (like IPCBridge from React) if already initialized
254 if (!window.dioxusBridge) {
255 window.dioxusBridge = {};
256 }
257
258 // Preserve IPCBridge if it was already attached by React
259 const existingIPCBridge = window.dioxusBridge.IPCBridge;
260
261 // Preserve or create callbacks Map (MUST be same reference)
262 if (!window.dioxusBridge.callbacks) {
263 window.dioxusBridge.callbacks = new Map();
264 }
265 const callbacks = window.dioxusBridge.callbacks;
266
267 // Add/update core Rust-provided properties
268 Object.assign(window.dioxusBridge, {
269 // Internal callback storage (previously window.dioxusBridgeCallbacks)
270 callbacks: callbacks,
271
272 // HTTP-like fetch() method for IPC communication
273 fetch: function(url, options = {}) {
274 return new Promise((resolve, reject) => {
275 const requestId = Math.floor(Math.random() * 1000000);
276
277 // Store callback in namespaced location (use explicit reference)
278 window.dioxusBridge.callbacks.set(requestId, {resolve, reject});
279
280 // Build HTTP-like IPC request
281 const request = {
282 id: requestId,
283 method: options.method || 'GET',
284 url: url,
285 headers: options.headers || {},
286 body: options.body
287 };
288
289 // Send to Rust via dioxus.send()
290 if (typeof dioxus !== 'undefined' && typeof dioxus.send === 'function') {
291 dioxus.send(request);
292 } else {
293 reject(new Error('dioxus.send() not available'));
294 return;
295 }
296
297 // 10 second timeout
298 setTimeout(() => {
299 if (window.dioxusBridge && window.dioxusBridge.callbacks && window.dioxusBridge.callbacks.has(requestId)) {
300 window.dioxusBridge.callbacks.delete(requestId);
301 reject(new Error('Request timeout after 10 seconds'));
302 }
303 }, 10000);
304 });
305 },
306
307 // Rust → React event emitter (previously window.rustEmit)
308 rustEmit: function(channel, data) {
309 if (window.dioxusBridge.IPCBridge && typeof window.dioxusBridge.IPCBridge.emit === 'function') {
310 window.dioxusBridge.IPCBridge.emit(channel, data);
311 } else {
312 console.warn('[Rust] IPCBridge not available, event not emitted:', channel);
313 }
314 },
315
316 // Direct IPC interface
317 ipc: {
318 send: function(data) {
319 if (typeof dioxus !== 'undefined' && typeof dioxus.send === 'function') {
320 dioxus.send(data);
321 } else {
322 console.error('[Rust] dioxus.send() not available');
323 }
324 },
325 hasIPCBridge: function() {
326 return typeof window.dioxusBridge.IPCBridge !== 'undefined';
327 }
328 },
329
330 // Low-level send wrapper
331 send: function(data) {
332 if (window.dioxusBridge && window.dioxusBridge.ipc) {
333 window.dioxusBridge.ipc.send(data);
334 }
335 }
336 });
337
338 // Restore IPCBridge if it existed before
339 if (existingIPCBridge) {
340 window.dioxusBridge.IPCBridge = existingIPCBridge;
341 }
342
343 console.log('[Rust] window.dioxusBridge ready (Unified IPC API)');
344 new Promise(() => {}); // Keep eval alive
345 "#
346}
347
348pub fn emit(channel: &str, data: serde_json::Value) {
375 let data_json = data.to_string();
376 let script = format!(
377 r#"
378 if (window.dioxusBridge && window.dioxusBridge.rustEmit) {{
379 window.dioxusBridge.rustEmit('{}', {});
380 }}
381 "#,
382 channel, data_json
383 );
384 platform::eval_js(&script);
385}