contacts FE

This commit is contained in:
dr-frmr 2024-10-16 17:10:03 -04:00
parent d7a94beac3
commit c2cce9d376
No known key found for this signature in database
12 changed files with 229 additions and 76 deletions

26
Cargo.lock generated
View File

@ -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",

View File

@ -129,6 +129,7 @@ The distro userspace packages are:
- `app_store:sys`
- `chess:sys`
- `contacts:sys`
- `homepage:sys`
- `kino_updates:sys`
- `kns_indexer:sys`

View File

@ -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",

View File

@ -26,6 +26,7 @@ interface contacts {
add-field,
remove-contact,
remove-field,
error(string),
}
}

View File

@ -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"

View File

@ -1 +1 @@
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTM2IiBoZWlnaHQ9IjEzNiIgdmlld0JveD0iMCAwIDEzNiAxMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMzYiIGhlaWdodD0iMTM2IiByeD0iNjgiIGZpbGw9IiNGRkY1RDkiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xODIxXzkwOTMpIj4KPHBhdGggZD0iTTY3LjQ4NTUgOTQuOTg4MUM2NS44MTYxIDk0Ljk4ODEgNjQuMTQ2NyA5NC45NjQzIDYyLjQ3NzIgOTQuOTg4MUM2MS41NjAyIDk1LjAxMTkgNjEuMDQyOSA5NC40MTc0IDYwLjkzNzEgOTMuNjQ0N0M2MC42NjY3IDkxLjU1MjMgNjAuMzQ5MyA4OS40NTk4IDYwLjA2NzEgODcuMzY3NEM2MC4wMjAxIDg3LjAxMDggNTkuODc5IDg2Ljc5NjggNTkuNTM4MSA4Ni42NTQxQzU4LjIzMzEgODYuMDgzNCA1Ny4wMjIyIDg1LjM1ODIgNTUuODgxOCA4NC40OTA0QzU1LjYyMzEgODQuMjg4MiA1NS4zOTk4IDg0LjI2NDUgNTUuMDk0MSA4NC4zOTUyQzUzLjE1NDMgODUuMjAzNyA1MS4yMTQ0IDg1Ljk4ODMgNDkuMjc0NiA4Ni43NzNDNDguMjc1MyA4Ny4xNzcyIDQ3Ljc5MzMgODcuMDIyNyA0Ny4yNTI1IDg2LjA5NTNDNDUuNTcxMyA4My4xNTg4IDQzLjg5MDEgODAuMjEwNCA0Mi4yMDg5IDc3LjI2MkM0MS44MDkyIDc2LjU3MjUgNDEuOTI2NyA3NS45NDIzIDQyLjU0OTggNzUuNDQzQzQ0LjIzMSA3NC4wOTk2IDQ1LjkxMjIgNzIuNzU2MiA0Ny42MDUyIDcxLjQzNjVDNDcuODk5MSA3MS4yMTA2IDQ4LjAwNDkgNzAuOTYxIDQ3Ljk1NzkgNzAuNTkyNEM0Ny43ODE1IDY5LjIwMTQgNDcuNzkzMyA2Ny43OTg2IDQ3Ljk1NzkgNjYuNDA3NkM0OC4wMDQ5IDY2LjA1MDkgNDcuOTEwOCA2NS44MTMxIDQ3LjYyODcgNjUuNTg3M0M0NS45OTQ1IDY0LjMwMzMgNDQuMzYwNCA2My4wMDc0IDQyLjczNzkgNjEuNzExNUM0MS44Nzk3IDYxLjAzMzkgNDEuNzczOSA2MC41MTA4IDQyLjMxNDcgNTkuNTcxNkM0NC4wMDc3IDU2LjYxMTMgNDUuNjg4OCA1My42MzkxIDQ3LjM5MzYgNTAuNjc4OEM0Ny43NDYyIDUwLjA3MjUgNDguMzgxMSA0OS44NTg1IDQ5LjA3NDcgNTAuMTQzOEM1MC40MjY3IDUwLjY5MDcgNTEuNzc4OCA1MS4yMzc1IDUzLjEzMDggNTEuNzk2M0M1My44MjQ0IDUyLjA4MTcgNTQuNTE4IDUyLjM1NTEgNTUuMjExNyA1Mi42NTIzQzU1LjQyMzMgNTIuNzQ3NCA1NS41ODc5IDUyLjczNTUgNTUuNzc2IDUyLjU5MjlDNTYuOTg2OSA1MS42NjU1IDU4LjMwMzYgNTAuODkyOCA1OS43MDI3IDUwLjI4NjVDNTkuOTQ5NiA1MC4xNzk1IDYwLjAyMDEgNDkuOTg5MiA2MC4wNDM2IDQ5Ljc1MTVDNjAuMzE0IDQ3LjgxMzYgNjAuNTg0NCA0NS44NzU3IDYwLjg1NDggNDMuOTM3OUM2MC44OTAxIDQzLjcxMiA2MC45MTM2IDQzLjQ4NjEgNjAuOTQ4OSA0My4yNjAyQzYxLjA5IDQyLjQxNjEgNjEuNTYwMiA0MiA2Mi4zNzE0IDQyQzY0LjQ0MDYgNDIgNjYuNTA5NyA0MiA2OC41Nzg5IDQyQzY5LjkwNzQgNDIgNzEuMjQ3NiA0MiA3Mi41NzYxIDQyQzczLjQ0NjEgNDIgNzMuOTI4MSA0Mi40NTE4IDc0LjA1NzUgNDMuMzMxNUM3NC4zNTE0IDQ1LjQzNTggNzQuNjQ1MyA0Ny41NDAyIDc0LjkyNzQgNDkuNjQ0NUM3NC45NzQ1IDQ5Ljk4OTIgNzUuMTE1NSA1MC4xNzk1IDc1LjQzMyA1MC4zMzRDNzYuNzM4IDUwLjkxNjYgNzcuOTcyNCA1MS42Mjk5IDc5LjExMjggNTIuNTA5NkM3OS4zNTk3IDUyLjY5OTkgNzkuNTU5NSA1Mi43MjM2IDc5Ljg0MTcgNTIuNjA0OEM4MS44MDUgNTEuNzk2MyA4My43Njg0IDUwLjk4NzkgODUuNzQzNSA1MC4xOTEzQzg2LjY3MjMgNDkuODEwOSA4Ny4yMTMxIDUwLjAxMyA4Ny43MTg2IDUwLjg5MjhDODkuMzc2MyA1My44MDU1IDkxLjAzMzkgNTYuNzE4MyA5Mi42OTE2IDU5LjYxOTFDOTMuMTczNiA2MC40NTEzIDkzLjA2NzggNjEuMDIyIDkyLjMxNTQgNjEuNjI4M0M5MC42NDYgNjIuOTU5OCA4OC45NzY1IDY0LjI3OTUgODcuMjk1NCA2NS41ODczQzg3LjAyNDkgNjUuODAxMyA4Ni45NDI3IDY2LjAyNzEgODYuOTc3OSA2Ni4zNkM4Ny4xNTQzIDY3Ljc4NjcgODcuMTU0MyA2OS4yMTMzIDg2Ljk3NzkgNzAuNjRDODYuOTQyNyA3MC45NjEgODcuMDI1IDcxLjE4NjkgODcuMjgzNiA3MS4zODlDODguOTI5NSA3Mi42ODQ4IDkwLjU3NTQgNzMuOTkyNiA5Mi4yMjE0IDc1LjMwMDRDOTMuMDU2MSA3NS45NjYxIDkzLjE2MTkgNzYuNDg5MiA5Mi42MzI4IDc3LjQxNjZDOTAuOTc1MiA4MC4zNDEyIDg5LjMwNTcgODMuMjUzOSA4Ny42MzYzIDg2LjE2NjdDODcuMTg5NSA4Ni45NTEzIDg2LjYwMTcgODcuMTUzNCA4NS43NjcgODYuODA4N0M4My44MDM3IDg2LjAxMjEgODEuODUyMSA4NS4yMTU2IDc5LjkwMDUgODQuNDA3MUM3OS41NzEzIDg0LjI2NDUgNzkuMzI0NCA4NC4yODgyIDc5LjAzMDUgODQuNTE0MUM3Ny45MDE5IDg1LjM5MzkgNzYuNjc5MiA4Ni4wOTUzIDc1LjM3NDIgODYuNjY2Qzc1LjA2ODUgODYuNzk2OCA3NC45MzkyIDg2Ljk4NyA3NC44OTIyIDg3LjMwOEM3NC42MSA4OS40MDA0IDc0LjMxNjEgOTEuNDkyOCA3NC4wMTA0IDkzLjU4NTJDNzMuOTYzNCA5My45MDYyIDczLjgzNDEgOTQuMjI3MiA3My42Njk1IDk0LjUwMDdDNzMuNDM0NCA5NC44NTczIDczLjA1ODEgOTQuOTg4MSA3Mi42MjMxIDk0Ljk4ODFDNzAuOTA2NyA5NC45ODgxIDY5LjE3ODUgOTQuOTg4MSA2Ny40NjIgOTQuOTg4MUg2Ny40ODU1Wk02Ny40NzM4IDU5LjE1NTVDNjIuNDMwMiA1OS4xNTU1IDU4LjI2ODQgNjMuMzUyMiA1OC4yNTY2IDY4LjQ2NDNDNTguMjQ0OSA3My42MDAzIDYyLjQwNjcgNzcuODIwOCA2Ny40ODU1IDc3LjgyMDhDNzIuNTUyNiA3Ny44MjA4IDc2LjcxNDQgNzMuNjEyMiA3Ni43MTQ0IDY4LjQ4ODFDNzYuNzE0NCA2My4zNDAzIDcyLjU2NDQgNTkuMTU1NSA2Ny40NjIgNTkuMTQzNkw2Ny40NzM4IDU5LjE1NTVaIiBmaWxsPSIjRkZGNUQ5Ii8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTgyMV85MDkzIj4KPHJlY3Qgd2lkdGg9IjUxIiBoZWlnaHQ9IjUzIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDIgNDIpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiByeD0iNDgwIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjA5MV8xMDI4MikiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSI5NjAiIHJ4PSI0ODAiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzIwOTFfMTAyODIpIiBzdHJva2Utd2lkdGg9IjQwIi8+CjxwYXRoIGQ9Ik01ODEuMTE2IDU4NS43ODNMNjY0LjUzOCA2NjkuMTJDNjE3LjY5OSA3MTUuNzU4IDU1Ni42MjUgNzM5IDQ5NS4yNDUgNzM5QzQzMy44NjUgNzM5IDM3Mi42MzggNzE1LjYwNSAzMjYuMTA1IDY2OS4xMkMyODAuNzk3IDYyMy44NTggMjU2IDU2My45MTcgMjU2IDUwMEMyNTYgNDM2LjA4MyAyODAuOTUgMzc2LjE0MiAzMjYuMTA1IDMzMC44OEMzNzEuMjYgMjg1LjYxOSA0MzEuMjYyIDI2MSA0OTUuMjQ1IDI2MUM1NTkuMjI3IDI2MSA2MTkuMjMgMjg1LjkyNCA2NjQuNTM4IDMzMC44OEw1ODAuOTYzIDQxNC41MjNDNTQ1LjYwNCAzODMuOTQgNTA1LjE5NCAzNzQuNzY2IDQ2MS4yNjQgMzkxLjEyN0MzOTQuOTg1IDQxNS44OTkgMzY2LjgyMSA0ODkuMjk2IDM5Ny40MzUgNTUyLjc1NEMzOTguNjU5IDU1NS4yMDEgMzk4Ljk2NSA1NTguNzE4IDM5OC4zNTMgNTYxLjMxN0MzOTUuNzUxIDU3Mi40OCAzOTIuNjg5IDU4My40OSAzODkuNzgxIDU5NC4zNDZDMzg2LjEwNyA2MDcuOTU1IDM5My45MTQgNjE1Ljc1NCA0MDcuNTM3IDYxMi4yMzdDNDE4LjU1OCA2MDkuMzMxIDQyOS41NzkgNjA2LjEyIDQ0MC42IDYwMy42NzRDNDQzLjM1NSA2MDIuOTA5IDQ0Ny4wMjkgNjAzLjIxNSA0NDkuNjMxIDYwNC40MzhDNDkwLjUgNjIzLjM5OSA1MjkuOTkxIDYyMC44IDU2Ny43OTkgNTk2LjE4MUM1NzIuNTQ0IDU5My4xMjMgNTc2LjY3NyA1ODkuNDUzIDU4MS4xMTYgNTg1Ljc4M1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMDkxXzEwMjgyKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIwOTFfMTAyODIiIHgxPSI1MDAiIHkxPSIwIiB4Mj0iNTAwIiB5Mj0iMTAwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8yMDkxXzEwMjgyIiB4MT0iNzgyLjUiIHkxPSI3My41IiB4Mj0iMTg1LjUiIHkyPSI4OTQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0E1MzEwQyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Ml9saW5lYXJfMjA5MV8xMDI4MiIgeDE9Ijc1NCIgeTE9Ijg5LjUiIHgyPSIyNTYiIHkyPSIxMDE1LjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YzNTQyMiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNBNzMyMEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K

View File

@ -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<NodeId, Contact>);
#[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<ContactsResponse> {
) {
// 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::<SettingsRequest>(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<LazyLoadBlob>) {
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::<ContactsRequest>(&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<LazyLoadBlob>) {
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::<ContactsRequest>(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::<serde_json::Value>(&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<LazyLoadBlob>)> {
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,
))
}
}

View File

@ -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"
],

View File

@ -47,6 +47,18 @@
<body>
<h1>contacts</h1>
<main>
<article id="edit">
<h2>Contacts</h2>
<form id="add-contact">
<input type="text" name="node">
<button type="submit">add contact</button>
</form>
</article>
<article id="contacts-article">
<ul id="contacts"></ul>
</article>
<script src="/contacts:contacts:sys/script.js"></script>
</main>
</body>

View File

@ -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);
};

View File

@ -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",

View File

@ -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"