@@ -63,6 +63,65 @@ enum NotificationsResponse {
6363 Err ( String ) ,
6464}
6565
66+ /// WebSocket message types sent to frontend
67+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
68+ #[ serde( tag = "kind" , content = "data" ) ]
69+ #[ serde( rename_all = "snake_case" ) ]
70+ enum WsMessage {
71+ AppsUpdate ( Vec < WsApp > ) ,
72+ }
73+
74+ /// App data sent over WebSocket (mirrors HomepageApp in TypeScript)
75+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
76+ struct WsApp {
77+ id : String ,
78+ process : String ,
79+ package_name : String ,
80+ publisher : String ,
81+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
82+ path : Option < String > ,
83+ label : String ,
84+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
85+ base64_icon : Option < String > ,
86+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
87+ widget : Option < String > ,
88+ order : u32 ,
89+ favorite : bool ,
90+ }
91+
92+ impl From < & homepage:: App > for WsApp {
93+ fn from ( app : & homepage:: App ) -> Self {
94+ WsApp {
95+ id : app. id . clone ( ) ,
96+ process : app. process . clone ( ) ,
97+ package_name : app. package_name . clone ( ) ,
98+ publisher : app. publisher . clone ( ) ,
99+ path : app. path . clone ( ) ,
100+ label : app. label . clone ( ) ,
101+ base64_icon : app. base64_icon . clone ( ) ,
102+ widget : app. widget . clone ( ) ,
103+ order : app. order ,
104+ favorite : app. favorite ,
105+ }
106+ }
107+ }
108+
109+ /// Helper function to broadcast app updates to all WebSocket clients
110+ fn broadcast_apps_update (
111+ http_server : & server:: HttpServer ,
112+ app_data : & BTreeMap < String , homepage:: App > ,
113+ ) {
114+ let apps: Vec < WsApp > = app_data. values ( ) . map ( WsApp :: from) . collect ( ) ;
115+ let message = WsMessage :: AppsUpdate ( apps) ;
116+ let json = serde_json:: to_string ( & message) . unwrap ( ) ;
117+
118+ http_server. ws_push_all_channels (
119+ "/" ,
120+ http:: server:: WsMessageType :: Text ,
121+ LazyLoadBlob :: new ( Some ( "application/json" ) , json. into_bytes ( ) ) ,
122+ ) ;
123+ }
124+
66125wit_bindgen:: generate!( {
67126 path: "../target/wit" ,
68127 world: "homepage-sys-v1" ,
@@ -295,6 +354,10 @@ fn init(our: Address) {
295354 . bind_http_path ( "/api/notifications/test-vapid" , http_config)
296355 . expect ( "failed to bind /api/notifications/test-vapid" ) ;
297356
357+ http_server
358+ . bind_ws_path ( "/" , server:: WsBindingConfig :: default ( ) )
359+ . expect ( "failed to bind ws path" ) ;
360+
298361 hyperware_process_lib:: homepage:: add_to_homepage (
299362 "Clock" ,
300363 None ,
@@ -319,6 +382,23 @@ fn init(our: Address) {
319382 let Ok ( request) = http_server. parse_request ( message. body ( ) ) else {
320383 continue ;
321384 } ;
385+ // Handle WebSocket events
386+ match & request {
387+ http:: server:: HttpServerRequest :: WebSocketOpen { path, channel_id } => {
388+ http_server. handle_websocket_open ( path, * channel_id) ;
389+ continue ;
390+ }
391+ http:: server:: HttpServerRequest :: WebSocketClose ( channel_id) => {
392+ http_server. handle_websocket_close ( * channel_id) ;
393+ continue ;
394+ }
395+ http:: server:: HttpServerRequest :: WebSocketPush { .. } => {
396+ continue ;
397+ }
398+ http:: server:: HttpServerRequest :: Http ( _) => {
399+ // Fall through to handle_request for HTTP
400+ }
401+ }
322402 http_server. handle_request (
323403 request,
324404 |incoming| {
@@ -955,11 +1035,13 @@ fn init(our: Address) {
9551035 . contains ( & message. source ( ) . process . to_string ( ) . as_str ( ) ) ,
9561036 } ,
9571037 ) ;
1038+ broadcast_apps_update ( & http_server, & app_data) ;
9581039 }
9591040 homepage:: Request :: Remove => {
9601041 let id = message. source ( ) . process . to_string ( ) ;
9611042 app_data. remove ( & id) ;
9621043 persisted_app_order. remove ( & id) ;
1044+ broadcast_apps_update ( & http_server, & app_data) ;
9631045 }
9641046 homepage:: Request :: RemoveOther ( id) => {
9651047 // caps check
@@ -973,6 +1055,7 @@ fn init(our: Address) {
9731055 // end caps check
9741056 app_data. remove ( & id) ;
9751057 persisted_app_order. remove ( & id) ;
1058+ broadcast_apps_update ( & http_server, & app_data) ;
9761059 }
9771060 homepage:: Request :: GetApps => {
9781061 let apps = app_data. values ( ) . cloned ( ) . collect :: < Vec < homepage:: App > > ( ) ;
0 commit comments