From c2cce9d3764f455b820c886e01e520e861f29e00 Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Wed, 16 Oct 2024 17:10:03 -0400 Subject: [PATCH] contacts FE --- Cargo.lock | 26 ++- README.md | 1 + kinode/packages/contacts/Cargo.lock | 5 +- .../packages/contacts/api/contacts:sys-v0.wit | 1 + kinode/packages/contacts/contacts/Cargo.toml | 2 +- kinode/packages/contacts/contacts/src/icon | 2 +- kinode/packages/contacts/contacts/src/lib.rs | 184 +++++++++++++----- kinode/packages/contacts/pkg/manifest.json | 2 + kinode/packages/contacts/pkg/ui/index.html | 12 ++ kinode/packages/contacts/pkg/ui/script.js | 63 ++++-- kinode/packages/settings/Cargo.lock | 5 +- kinode/packages/settings/settings/Cargo.toml | 2 +- 12 files changed, 229 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01749e34..cea58e23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1867,7 +1867,7 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" name = "contacts" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.9.3", + "kinode_process_lib 0.9.4", "serde", "serde_json", "wit-bindgen", @@ -3759,6 +3759,28 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "kinode_process_lib" +version = "0.9.4" +source = "git+https://github.com/kinode-dao/process_lib?rev=088a549#088a5497257eada697e0869d6a8d7e9ef5e620f6" +dependencies = [ + "alloy 0.1.4", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "bincode", + "http 1.1.0", + "mime_guess", + "rand 0.8.5", + "rmp-serde", + "serde", + "serde_json", + "thiserror", + "url", + "wit-bindgen", +] + [[package]] name = "kit" version = "0.7.7" @@ -5580,7 +5602,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "bincode", - "kinode_process_lib 0.9.3", + "kinode_process_lib 0.9.4", "rmp-serde", "serde", "serde_json", diff --git a/README.md b/README.md index c86f767b..d4b1fc2b 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ The distro userspace packages are: - `app_store:sys` - `chess:sys` +- `contacts:sys` - `homepage:sys` - `kino_updates:sys` - `kns_indexer:sys` diff --git a/kinode/packages/contacts/Cargo.lock b/kinode/packages/contacts/Cargo.lock index ad63c9ae..9df40bfb 100644 --- a/kinode/packages/contacts/Cargo.lock +++ b/kinode/packages/contacts/Cargo.lock @@ -1462,9 +1462,8 @@ dependencies = [ [[package]] name = "kinode_process_lib" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7722aef4bff0625445fafda89a02f82ce0e16c7def6024e1317ae55a632ad331" +version = "0.9.4" +source = "git+https://github.com/kinode-dao/process_lib?rev=088a549#088a5497257eada697e0869d6a8d7e9ef5e620f6" dependencies = [ "alloy", "alloy-primitives", diff --git a/kinode/packages/contacts/api/contacts:sys-v0.wit b/kinode/packages/contacts/api/contacts:sys-v0.wit index a689775b..a567bf26 100644 --- a/kinode/packages/contacts/api/contacts:sys-v0.wit +++ b/kinode/packages/contacts/api/contacts:sys-v0.wit @@ -26,6 +26,7 @@ interface contacts { add-field, remove-contact, remove-field, + error(string), } } diff --git a/kinode/packages/contacts/contacts/Cargo.toml b/kinode/packages/contacts/contacts/Cargo.toml index 54c6bada..b848209f 100644 --- a/kinode/packages/contacts/contacts/Cargo.toml +++ b/kinode/packages/contacts/contacts/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" simulation-mode = [] [dependencies] -kinode_process_lib = "0.9.3" +kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "088a549" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" wit-bindgen = "0.24.0" diff --git a/kinode/packages/contacts/contacts/src/icon b/kinode/packages/contacts/contacts/src/icon index d5143fe3..f67b371b 100644 --- a/kinode/packages/contacts/contacts/src/icon +++ b/kinode/packages/contacts/contacts/src/icon @@ -1 +1 @@ -data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTM2IiBoZWlnaHQ9IjEzNiIgdmlld0JveD0iMCAwIDEzNiAxMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMzYiIGhlaWdodD0iMTM2IiByeD0iNjgiIGZpbGw9IiNGRkY1RDkiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xODIxXzkwOTMpIj4KPHBhdGggZD0iTTY3LjQ4NTUgOTQuOTg4MUM2NS44MTYxIDk0Ljk4ODEgNjQuMTQ2NyA5NC45NjQzIDYyLjQ3NzIgOTQuOTg4MUM2MS41NjAyIDk1LjAxMTkgNjEuMDQyOSA5NC40MTc0IDYwLjkzNzEgOTMuNjQ0N0M2MC42NjY3IDkxLjU1MjMgNjAuMzQ5MyA4OS40NTk4IDYwLjA2NzEgODcuMzY3NEM2MC4wMjAxIDg3LjAxMDggNTkuODc5IDg2Ljc5NjggNTkuNTM4MSA4Ni42NTQxQzU4LjIzMzEgODYuMDgzNCA1Ny4wMjIyIDg1LjM1ODIgNTUuODgxOCA4NC40OTA0QzU1LjYyMzEgODQuMjg4MiA1NS4zOTk4IDg0LjI2NDUgNTUuMDk0MSA4NC4zOTUyQzUzLjE1NDMgODUuMjAzNyA1MS4yMTQ0IDg1Ljk4ODMgNDkuMjc0NiA4Ni43NzNDNDguMjc1MyA4Ny4xNzcyIDQ3Ljc5MzMgODcuMDIyNyA0Ny4yNTI1IDg2LjA5NTNDNDUuNTcxMyA4My4xNTg4IDQzLjg5MDEgODAuMjEwNCA0Mi4yMDg5IDc3LjI2MkM0MS44MDkyIDc2LjU3MjUgNDEuOTI2NyA3NS45NDIzIDQyLjU0OTggNzUuNDQzQzQ0LjIzMSA3NC4wOTk2IDQ1LjkxMjIgNzIuNzU2MiA0Ny42MDUyIDcxLjQzNjVDNDcuODk5MSA3MS4yMTA2IDQ4LjAwNDkgNzAuOTYxIDQ3Ljk1NzkgNzAuNTkyNEM0Ny43ODE1IDY5LjIwMTQgNDcuNzkzMyA2Ny43OTg2IDQ3Ljk1NzkgNjYuNDA3NkM0OC4wMDQ5IDY2LjA1MDkgNDcuOTEwOCA2NS44MTMxIDQ3LjYyODcgNjUuNTg3M0M0NS45OTQ1IDY0LjMwMzMgNDQuMzYwNCA2My4wMDc0IDQyLjczNzkgNjEuNzExNUM0MS44Nzk3IDYxLjAzMzkgNDEuNzczOSA2MC41MTA4IDQyLjMxNDcgNTkuNTcxNkM0NC4wMDc3IDU2LjYxMTMgNDUuNjg4OCA1My42MzkxIDQ3LjM5MzYgNTAuNjc4OEM0Ny43NDYyIDUwLjA3MjUgNDguMzgxMSA0OS44NTg1IDQ5LjA3NDcgNTAuMTQzOEM1MC40MjY3IDUwLjY5MDcgNTEuNzc4OCA1MS4yMzc1IDUzLjEzMDggNTEuNzk2M0M1My44MjQ0IDUyLjA4MTcgNTQuNTE4IDUyLjM1NTEgNTUuMjExNyA1Mi42NTIzQzU1LjQyMzMgNTIuNzQ3NCA1NS41ODc5IDUyLjczNTUgNTUuNzc2IDUyLjU5MjlDNTYuOTg2OSA1MS42NjU1IDU4LjMwMzYgNTAuODkyOCA1OS43MDI3IDUwLjI4NjVDNTkuOTQ5NiA1MC4xNzk1IDYwLjAyMDEgNDkuOTg5MiA2MC4wNDM2IDQ5Ljc1MTVDNjAuMzE0IDQ3LjgxMzYgNjAuNTg0NCA0NS44NzU3IDYwLjg1NDggNDMuOTM3OUM2MC44OTAxIDQzLjcxMiA2MC45MTM2IDQzLjQ4NjEgNjAuOTQ4OSA0My4yNjAyQzYxLjA5IDQyLjQxNjEgNjEuNTYwMiA0MiA2Mi4zNzE0IDQyQzY0LjQ0MDYgNDIgNjYuNTA5NyA0MiA2OC41Nzg5IDQyQzY5LjkwNzQgNDIgNzEuMjQ3NiA0MiA3Mi41NzYxIDQyQzczLjQ0NjEgNDIgNzMuOTI4MSA0Mi40NTE4IDc0LjA1NzUgNDMuMzMxNUM3NC4zNTE0IDQ1LjQzNTggNzQuNjQ1MyA0Ny41NDAyIDc0LjkyNzQgNDkuNjQ0NUM3NC45NzQ1IDQ5Ljk4OTIgNzUuMTE1NSA1MC4xNzk1IDc1LjQzMyA1MC4zMzRDNzYuNzM4IDUwLjkxNjYgNzcuOTcyNCA1MS42Mjk5IDc5LjExMjggNTIuNTA5NkM3OS4zNTk3IDUyLjY5OTkgNzkuNTU5NSA1Mi43MjM2IDc5Ljg0MTcgNTIuNjA0OEM4MS44MDUgNTEuNzk2MyA4My43Njg0IDUwLjk4NzkgODUuNzQzNSA1MC4xOTEzQzg2LjY3MjMgNDkuODEwOSA4Ny4yMTMxIDUwLjAxMyA4Ny43MTg2IDUwLjg5MjhDODkuMzc2MyA1My44MDU1IDkxLjAzMzkgNTYuNzE4MyA5Mi42OTE2IDU5LjYxOTFDOTMuMTczNiA2MC40NTEzIDkzLjA2NzggNjEuMDIyIDkyLjMxNTQgNjEuNjI4M0M5MC42NDYgNjIuOTU5OCA4OC45NzY1IDY0LjI3OTUgODcuMjk1NCA2NS41ODczQzg3LjAyNDkgNjUuODAxMyA4Ni45NDI3IDY2LjAyNzEgODYuOTc3OSA2Ni4zNkM4Ny4xNTQzIDY3Ljc4NjcgODcuMTU0MyA2OS4yMTMzIDg2Ljk3NzkgNzAuNjRDODYuOTQyNyA3MC45NjEgODcuMDI1IDcxLjE4NjkgODcuMjgzNiA3MS4zODlDODguOTI5NSA3Mi42ODQ4IDkwLjU3NTQgNzMuOTkyNiA5Mi4yMjE0IDc1LjMwMDRDOTMuMDU2MSA3NS45NjYxIDkzLjE2MTkgNzYuNDg5MiA5Mi42MzI4IDc3LjQxNjZDOTAuOTc1MiA4MC4zNDEyIDg5LjMwNTcgODMuMjUzOSA4Ny42MzYzIDg2LjE2NjdDODcuMTg5NSA4Ni45NTEzIDg2LjYwMTcgODcuMTUzNCA4NS43NjcgODYuODA4N0M4My44MDM3IDg2LjAxMjEgODEuODUyMSA4NS4yMTU2IDc5LjkwMDUgODQuNDA3MUM3OS41NzEzIDg0LjI2NDUgNzkuMzI0NCA4NC4yODgyIDc5LjAzMDUgODQuNTE0MUM3Ny45MDE5IDg1LjM5MzkgNzYuNjc5MiA4Ni4wOTUzIDc1LjM3NDIgODYuNjY2Qzc1LjA2ODUgODYuNzk2OCA3NC45MzkyIDg2Ljk4NyA3NC44OTIyIDg3LjMwOEM3NC42MSA4OS40MDA0IDc0LjMxNjEgOTEuNDkyOCA3NC4wMTA0IDkzLjU4NTJDNzMuOTYzNCA5My45MDYyIDczLjgzNDEgOTQuMjI3MiA3My42Njk1IDk0LjUwMDdDNzMuNDM0NCA5NC44NTczIDczLjA1ODEgOTQuOTg4MSA3Mi42MjMxIDk0Ljk4ODFDNzAuOTA2NyA5NC45ODgxIDY5LjE3ODUgOTQuOTg4MSA2Ny40NjIgOTQuOTg4MUg2Ny40ODU1Wk02Ny40NzM4IDU5LjE1NTVDNjIuNDMwMiA1OS4xNTU1IDU4LjI2ODQgNjMuMzUyMiA1OC4yNTY2IDY4LjQ2NDNDNTguMjQ0OSA3My42MDAzIDYyLjQwNjcgNzcuODIwOCA2Ny40ODU1IDc3LjgyMDhDNzIuNTUyNiA3Ny44MjA4IDc2LjcxNDQgNzMuNjEyMiA3Ni43MTQ0IDY4LjQ4ODFDNzYuNzE0NCA2My4zNDAzIDcyLjU2NDQgNTkuMTU1NSA2Ny40NjIgNTkuMTQzNkw2Ny40NzM4IDU5LjE1NTVaIiBmaWxsPSIjRkZGNUQ5Ii8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTgyMV85MDkzIj4KPHJlY3Qgd2lkdGg9IjUxIiBoZWlnaHQ9IjUzIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDIgNDIpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg== \ No newline at end of file +data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiByeD0iNDgwIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjA5MV8xMDI4MikiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSI5NjAiIHJ4PSI0ODAiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzIwOTFfMTAyODIpIiBzdHJva2Utd2lkdGg9IjQwIi8+CjxwYXRoIGQ9Ik01ODEuMTE2IDU4NS43ODNMNjY0LjUzOCA2NjkuMTJDNjE3LjY5OSA3MTUuNzU4IDU1Ni42MjUgNzM5IDQ5NS4yNDUgNzM5QzQzMy44NjUgNzM5IDM3Mi42MzggNzE1LjYwNSAzMjYuMTA1IDY2OS4xMkMyODAuNzk3IDYyMy44NTggMjU2IDU2My45MTcgMjU2IDUwMEMyNTYgNDM2LjA4MyAyODAuOTUgMzc2LjE0MiAzMjYuMTA1IDMzMC44OEMzNzEuMjYgMjg1LjYxOSA0MzEuMjYyIDI2MSA0OTUuMjQ1IDI2MUM1NTkuMjI3IDI2MSA2MTkuMjMgMjg1LjkyNCA2NjQuNTM4IDMzMC44OEw1ODAuOTYzIDQxNC41MjNDNTQ1LjYwNCAzODMuOTQgNTA1LjE5NCAzNzQuNzY2IDQ2MS4yNjQgMzkxLjEyN0MzOTQuOTg1IDQxNS44OTkgMzY2LjgyMSA0ODkuMjk2IDM5Ny40MzUgNTUyLjc1NEMzOTguNjU5IDU1NS4yMDEgMzk4Ljk2NSA1NTguNzE4IDM5OC4zNTMgNTYxLjMxN0MzOTUuNzUxIDU3Mi40OCAzOTIuNjg5IDU4My40OSAzODkuNzgxIDU5NC4zNDZDMzg2LjEwNyA2MDcuOTU1IDM5My45MTQgNjE1Ljc1NCA0MDcuNTM3IDYxMi4yMzdDNDE4LjU1OCA2MDkuMzMxIDQyOS41NzkgNjA2LjEyIDQ0MC42IDYwMy42NzRDNDQzLjM1NSA2MDIuOTA5IDQ0Ny4wMjkgNjAzLjIxNSA0NDkuNjMxIDYwNC40MzhDNDkwLjUgNjIzLjM5OSA1MjkuOTkxIDYyMC44IDU2Ny43OTkgNTk2LjE4MUM1NzIuNTQ0IDU5My4xMjMgNTc2LjY3NyA1ODkuNDUzIDU4MS4xMTYgNTg1Ljc4M1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMDkxXzEwMjgyKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIwOTFfMTAyODIiIHgxPSI1MDAiIHkxPSIwIiB4Mj0iNTAwIiB5Mj0iMTAwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8yMDkxXzEwMjgyIiB4MT0iNzgyLjUiIHkxPSI3My41IiB4Mj0iMTg1LjUiIHkyPSI4OTQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0E1MzEwQyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Ml9saW5lYXJfMjA5MV8xMDI4MiIgeDE9Ijc1NCIgeTE9Ijg5LjUiIHgyPSIyNTYiIHkyPSIxMDE1LjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YzNTQyMiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNBNzMyMEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K \ No newline at end of file diff --git a/kinode/packages/contacts/contacts/src/lib.rs b/kinode/packages/contacts/contacts/src/lib.rs index e34203f6..25a72e7d 100644 --- a/kinode/packages/contacts/contacts/src/lib.rs +++ b/kinode/packages/contacts/contacts/src/lib.rs @@ -1,7 +1,7 @@ use crate::kinode::process::contacts::{ContactsRequest, ContactsResponse}; use kinode_process_lib::{ - await_message, call_init, get_blob, get_typed_state, homepage, http, println, set_state, - Address, LazyLoadBlob, Message, NodeId, Response, + await_message, call_init, eth, get_blob, get_typed_state, homepage, http, kimap, kiprintln, + set_state, Address, LazyLoadBlob, Message, NodeId, Response, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -17,6 +17,7 @@ struct Contacts(HashMap); #[derive(Debug, Serialize, Deserialize)] struct ContactsState { our: Address, + kimap: kimap::Kimap, contacts: Contacts, } @@ -24,6 +25,7 @@ impl ContactsState { fn new(our: Address) -> Self { get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self { our, + kimap: kimap::Kimap::default(30), contacts: Contacts(HashMap::new()), }) } @@ -42,10 +44,12 @@ impl ContactsState { fn add_contact(&mut self, node: NodeId) { self.contacts.0.insert(node, Contact(HashMap::new())); + self.save(); } fn remove_contact(&mut self, node: NodeId) { self.contacts.0.remove(&node); + self.save(); } fn add_field(&mut self, node: NodeId, field: String, value: serde_json::Value) { @@ -55,12 +59,25 @@ impl ContactsState { .or_insert_with(|| Contact(HashMap::new())) .0 .insert(field, value); + self.save(); } fn remove_field(&mut self, node: NodeId, field: String) { if let Some(contact) = self.contacts.0.get_mut(&node) { contact.0.remove(&field); } + self.save(); + } + + fn ws_update(&self, http_server: &mut http::server::HttpServer) { + http_server.ws_push_all_channels( + "/", + http::server::WsMessageType::Text, + LazyLoadBlob::new( + Some("application/json"), + serde_json::to_vec(self.contacts()).unwrap(), + ), + ); } } @@ -73,6 +90,8 @@ wit_bindgen::generate!({ call_init!(initialize); fn initialize(our: Address) { + kiprintln!("started"); + homepage::add_to_homepage("Contacts", Some(ICON), Some("/"), None); let mut state: ContactsState = ContactsState::new(our); @@ -101,24 +120,13 @@ fn main_loop(state: &mut ContactsState, http_server: &mut http::server::HttpServ // ignore send errors, local-only process continue; } - Ok(Message::Request { - source, - body, - expects_response, - .. - }) => { + Ok(Message::Request { source, body, .. }) => { + // ignore messages from other nodes -- technically superfluous check + // since manifest does not acquire networking capability if source.node() != state.our.node { - continue; // ignore messages from other nodes - } - let response_and_blob = handle_request(&source, &body, state, http_server); - // state.ws_update(http_server); - if expects_response.is_some() && response_and_blob.is_some() { - let (response, blob) = response_and_blob.unwrap(); - Response::new() - .body(serde_json::to_vec(&response).unwrap()) - .send() - .unwrap(); + continue; } + handle_request(&source, &body, state, http_server); } _ => continue, // ignore responses } @@ -130,7 +138,7 @@ fn handle_request( body: &[u8], state: &mut ContactsState, http_server: &mut http::server::HttpServer, -) -> Option { +) { // source node is ALWAYS ourselves since networking is disabled if source.process == "http_server:distro:sys" { // receive HTTP requests and websocket connection messages from our server @@ -143,13 +151,15 @@ fn handle_request( // we don't expect websocket messages }, ); - None } else { - // let settings_request = serde_json::from_slice::(body) - // .map_err(|_| SettingsError::MalformedRequest)?; - // handle_settings_request(state, settings_request) - None + let (response, blob) = handle_contacts_request(state, body); + let mut response = Response::new().body(serde_json::to_vec(&response).unwrap()); + if let Some(blob) = blob { + response = response.blob(blob); + } + response.send().unwrap(); } + state.ws_update(http_server); } /// Handle HTTP requests from our own frontend. @@ -158,21 +168,27 @@ fn handle_http_request( http_request: &http::server::IncomingHttpRequest, ) -> (http::server::HttpResponse, Option) { match http_request.method().unwrap().as_str() { - "GET" => { - // state.fetch().unwrap(); - ( - http::server::HttpResponse::new(http::StatusCode::OK) - .header("Content-Type", "application/json"), - Some(LazyLoadBlob::new( - Some("application/json"), - serde_json::to_vec(&state).unwrap(), - )), - ) - } + "GET" => ( + http::server::HttpResponse::new(http::StatusCode::OK) + .header("Content-Type", "application/json"), + Some(LazyLoadBlob::new( + Some("application/json"), + serde_json::to_vec(state.contacts()).unwrap(), + )), + ), "POST" => { let blob = get_blob().unwrap(); - let request = serde_json::from_slice::(&blob.bytes).unwrap(); - let (_response, blob) = handle_contacts_request(state, request); + let (response, blob) = handle_contacts_request(state, blob.bytes()); + if let ContactsResponse::Error(e) = response { + return ( + http::server::HttpResponse::new(http::StatusCode::BAD_REQUEST) + .header("Content-Type", "application/json"), + Some(LazyLoadBlob::new( + Some("application/json"), + serde_json::to_vec(&e).unwrap(), + )), + ); + } ( http::server::HttpResponse::new(http::StatusCode::OK) .header("Content-Type", "application/json"), @@ -195,23 +211,83 @@ fn handle_http_request( fn handle_contacts_request( state: &mut ContactsState, - request: ContactsRequest, + request_bytes: &[u8], ) -> (ContactsResponse, Option) { - let response = match request { - ContactsRequest::GetNames => ContactsResponse::GetNames( - state - .contacts() - .0 - .keys() - .map(|node| node.to_string()) - .collect(), - ), - ContactsRequest::GetAllContacts => ContactsResponse::GetAllContacts, - ContactsRequest::GetContact(node) => ContactsResponse::GetContact, - ContactsRequest::AddContact(node) => ContactsResponse::AddContact, - ContactsRequest::AddField((node, field, value)) => ContactsResponse::AddField, - ContactsRequest::RemoveContact(node) => ContactsResponse::RemoveContact, - ContactsRequest::RemoveField((node, field)) => ContactsResponse::RemoveField, + let Ok(request) = serde_json::from_slice::(request_bytes) else { + return ( + ContactsResponse::Error("Malformed request".to_string()), + None, + ); }; - (response, None) + match request { + ContactsRequest::GetNames => ( + ContactsResponse::GetNames( + state + .contacts() + .0 + .keys() + .map(|node| node.to_string()) + .collect(), + ), + None, + ), + ContactsRequest::GetAllContacts => ( + ContactsResponse::GetAllContacts, + Some(LazyLoadBlob::new( + Some("application/json"), + serde_json::to_vec(state.contacts()).unwrap(), + )), + ), + ContactsRequest::GetContact(node) => ( + ContactsResponse::GetContact, + Some(LazyLoadBlob::new( + Some("application/json"), + serde_json::to_vec(&state.get_contact(node)).unwrap(), + )), + ), + ContactsRequest::AddContact(node) => { + if let Some((response, blob)) = invalid_node(state, &node) { + return (response, blob); + } + state.add_contact(node); + (ContactsResponse::AddContact, None) + } + ContactsRequest::AddField((node, field, value)) => { + if let Some((response, blob)) = invalid_node(state, &node) { + return (response, blob); + } + let Ok(value) = serde_json::from_str::(&value) else { + return (ContactsResponse::Error("Malformed value".to_string()), None); + }; + state.add_field(node, field, value); + (ContactsResponse::AddField, None) + } + ContactsRequest::RemoveContact(node) => { + state.remove_contact(node); + (ContactsResponse::RemoveContact, None) + } + ContactsRequest::RemoveField((node, field)) => { + state.remove_field(node, field); + (ContactsResponse::RemoveField, None) + } + } +} + +fn invalid_node( + state: &ContactsState, + node: &str, +) -> Option<(ContactsResponse, Option)> { + if state + .kimap + .get(&node) + .map(|(tba, _, _)| tba != eth::Address::ZERO) + .unwrap_or(false) + { + None + } else { + Some(( + ContactsResponse::Error("Node name invalid or does not exist".to_string()), + None, + )) + } } diff --git a/kinode/packages/contacts/pkg/manifest.json b/kinode/packages/contacts/pkg/manifest.json index 4d28b723..03dd9b26 100644 --- a/kinode/packages/contacts/pkg/manifest.json +++ b/kinode/packages/contacts/pkg/manifest.json @@ -5,11 +5,13 @@ "on_exit": "Restart", "request_networking": false, "request_capabilities": [ + "eth:distro:sys", "homepage:homepage:sys", "http_server:distro:sys", "vfs:distro:sys" ], "grant_capabilities": [ + "eth:distro:sys", "http_server:distro:sys", "vfs:distro:sys" ], diff --git a/kinode/packages/contacts/pkg/ui/index.html b/kinode/packages/contacts/pkg/ui/index.html index e6dcb5ad..bad57611 100644 --- a/kinode/packages/contacts/pkg/ui/index.html +++ b/kinode/packages/contacts/pkg/ui/index.html @@ -47,6 +47,18 @@

contacts

+
+

Contacts

+
+ + +
+
+ +
+
    +
    +
    diff --git a/kinode/packages/contacts/pkg/ui/script.js b/kinode/packages/contacts/pkg/ui/script.js index 8f1376c5..830ffe14 100644 --- a/kinode/packages/contacts/pkg/ui/script.js +++ b/kinode/packages/contacts/pkg/ui/script.js @@ -1,16 +1,16 @@ -const APP_PATH = '/contacts:contacts:sys/'; +const APP_PATH = '/contacts:contacts:sys/ask'; // Fetch initial data and populate the UI function init() { - fetch(APP_PATH + 'get') + fetch(APP_PATH) .then(response => response.json()) .then(data => { populate(data); }); } -function api_call(path, body) { - fetch(APP_PATH + path, { +function api_call(body) { + fetch(APP_PATH, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -21,17 +21,58 @@ function api_call(path, body) { function populate(data) { console.log(data); + populate_contacts(data); } +function populate_contacts(contacts) { + const ul = document.getElementById('contacts'); + ul.innerHTML = ''; + Object.entries(contacts).forEach(([node, contact]) => { + const li = document.createElement('li'); + li.innerHTML = `${JSON.stringify(node, undefined, 2)}`; + ul.appendChild(li); + }); +} + +document.getElementById('add-contact').addEventListener('submit', (e) => { + e.preventDefault(); + const data = new FormData(e.target); + const node = data.get('node'); + const body = { + "AddContact": node + }; + fetch(APP_PATH, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }).then(response => { + if (response.status === 200) { + return null; + } else { + return response.json(); + } + }).then(data => { + if (data === null) { + e.target.reset(); + return; + } else { + alert(JSON.stringify(data)); + } + }).catch(error => { + console.error('Error:', error); + }); +}) + // Call init to start the application init(); // Setup WebSocket connection -// const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://'; -// const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/"); -// ws.onmessage = event => { -// const data = JSON.parse(event.data); -// console.log(data); -// populate(data); -// }; +const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://'; +const ws = new WebSocket(wsProtocol + location.host + "/contacts:contacts:sys/"); +ws.onmessage = event => { + const data = JSON.parse(event.data); + populate(data); +}; diff --git a/kinode/packages/settings/Cargo.lock b/kinode/packages/settings/Cargo.lock index 8c57f800..93b2abe5 100644 --- a/kinode/packages/settings/Cargo.lock +++ b/kinode/packages/settings/Cargo.lock @@ -1452,9 +1452,8 @@ dependencies = [ [[package]] name = "kinode_process_lib" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8" +version = "0.9.4" +source = "git+https://github.com/kinode-dao/process_lib?rev=088a549#088a5497257eada697e0869d6a8d7e9ef5e620f6" dependencies = [ "alloy", "alloy-primitives", diff --git a/kinode/packages/settings/settings/Cargo.toml b/kinode/packages/settings/settings/Cargo.toml index b3bfdb8d..83ad7e7f 100644 --- a/kinode/packages/settings/settings/Cargo.toml +++ b/kinode/packages/settings/settings/Cargo.toml @@ -10,7 +10,7 @@ simulation-mode = [] anyhow = "1.0" base64 = "0.22.0" bincode = "1.3.3" -kinode_process_lib = "0.9.1" +kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "088a549" } rmp-serde = "1.2.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"