romance_core/addon/
websocket.rs1use crate::addon::Addon;
2use anyhow::Result;
3use std::path::Path;
4
5pub struct WebsocketAddon;
6
7impl Addon for WebsocketAddon {
8 fn name(&self) -> &str {
9 "websocket"
10 }
11
12 fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
13 super::check_romance_project(project_root)
14 }
15
16 fn is_already_installed(&self, project_root: &Path) -> bool {
17 project_root.join("backend/src/ws.rs").exists()
18 }
19
20 fn install(&self, project_root: &Path) -> Result<()> {
21 install_websocket(project_root)
22 }
23
24 fn uninstall(&self, project_root: &Path) -> Result<()> {
25 use colored::Colorize;
26
27 println!("{}", "Uninstalling WebSocket support...".bold());
28
29 if super::remove_file_if_exists(&project_root.join("backend/src/ws.rs"))? {
31 println!(" {} backend/src/ws.rs", "delete".red());
32 }
33 if super::remove_file_if_exists(
34 &project_root.join("frontend/src/lib/useWebSocket.ts"),
35 )? {
36 println!(" {} frontend/src/lib/useWebSocket.ts", "delete".red());
37 }
38
39 super::remove_mod_from_main(project_root, "ws")?;
41
42 super::remove_line_from_file(
44 &project_root.join("backend/src/routes/mod.rs"),
45 "ws_handler",
46 )?;
47
48 super::remove_feature_flag(project_root, "websocket")?;
50
51 crate::ai_context::regenerate(project_root).ok();
53
54 println!();
55 println!(
56 "{}",
57 "WebSocket support uninstalled successfully.".green().bold()
58 );
59
60 Ok(())
61 }
62}
63
64fn install_websocket(project_root: &Path) -> Result<()> {
65 use crate::template::TemplateEngine;
66 use crate::utils;
67 use colored::Colorize;
68 use tera::Context;
69
70 println!("{}", "Installing WebSocket support...".bold());
71
72 let engine = TemplateEngine::new()?;
73 let ctx = Context::new();
74
75 let content = engine.render("addon/websocket/ws.rs.tera", &ctx)?;
77 utils::write_file(&project_root.join("backend/src/ws.rs"), &content)?;
78 println!(" {} backend/src/ws.rs", "create".green());
79
80 let content = engine.render("addon/websocket/useWebSocket.ts.tera", &ctx)?;
82 utils::write_file(
83 &project_root.join("frontend/src/lib/useWebSocket.ts"),
84 &content,
85 )?;
86 println!(" {} frontend/src/lib/useWebSocket.ts", "create".green());
87
88 super::add_mod_to_main(project_root, "ws")?;
90
91 utils::insert_at_marker(
93 &project_root.join("backend/src/routes/mod.rs"),
94 "// === ROMANCE:MIDDLEWARE ===",
95 " .route(\"/ws\", axum::routing::get(crate::ws::ws_handler))",
96 )?;
97 println!(
98 " {} backend/src/routes/mod.rs (added /ws route)",
99 "update".green()
100 );
101
102 let routes_path = project_root.join("backend/src/routes/mod.rs");
104 let routes_content = std::fs::read_to_string(&routes_path)?;
105
106 if !routes_content.contains("pub ws:") {
107 let routes_content = if !routes_content.contains("use crate::ws::WebSocketState;") {
109 routes_content.replace(
110 "use crate::events::EventBus;",
111 "use crate::events::EventBus;\nuse crate::ws::WebSocketState;",
112 )
113 } else {
114 routes_content
115 };
116
117 let routes_content = routes_content.replace(
119 " pub event_bus: EventBus,\n}",
120 " pub event_bus: EventBus,\n pub ws: WebSocketState,\n}",
121 );
122
123 let routes_content = routes_content.replace(
125 " let event_bus = EventBus::new();\n let state = AppState { db, event_bus };",
126 " let event_bus = EventBus::new();\n let ws = WebSocketState::new();\n\n // Bridge entity events to WebSocket clients\n tokio::spawn(crate::ws::bridge_events(event_bus.clone(), ws.clone()));\n\n let state = AppState { db, event_bus, ws };",
127 );
128
129 std::fs::write(&routes_path, routes_content)?;
130 println!(
131 " {} backend/src/routes/mod.rs (added WebSocketState to AppState)",
132 "update".green()
133 );
134 }
135
136 let cargo_path = project_root.join("backend/Cargo.toml");
140 let cargo_content = std::fs::read_to_string(&cargo_path)?;
141 if cargo_content.contains("axum") && !cargo_content.contains("\"ws\"") {
142 let new_content = cargo_content.replace(
143 r#"features = ["json"]"#,
144 r#"features = ["json", "ws"]"#,
145 );
146 std::fs::write(&cargo_path, new_content)?;
147 println!(
148 " {} backend/Cargo.toml (added ws feature to axum)",
149 "update".green()
150 );
151 }
152
153 super::update_feature_flag(project_root, "websocket", true)?;
155
156 println!();
157 println!(
158 "{}",
159 "WebSocket support installed successfully!".green().bold()
160 );
161 println!(" Backend: WebSocket endpoint at /ws");
162 println!(" Frontend: import {{ useWebSocket }} from '@/lib/useWebSocket'");
163 println!(" Entity events are automatically broadcast to connected clients.");
164 println!();
165 println!(" Usage example (frontend):");
166 println!(" const {{ messages, sendMessage, isConnected }} = useWebSocket('ws://localhost:3000/ws');");
167
168 Ok(())
169}