contacts: make kimap work in sim-mode, improve FE

This commit is contained in:
dr-frmr 2024-10-18 15:42:39 -04:00
parent 5ef0771abd
commit c24d2b51f9
No known key found for this signature in database
3 changed files with 122 additions and 21 deletions

View File

@ -5,9 +5,22 @@ use kinode_process_lib::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr;
const ICON: &str = include_str!("icon"); const ICON: &str = include_str!("icon");
#[cfg(not(feature = "simulation-mode"))]
const CHAIN_ID: u64 = kimap::KIMAP_CHAIN_ID;
#[cfg(feature = "simulation-mode")]
const CHAIN_ID: u64 = 31337; // local
const CHAIN_TIMEOUT: u64 = 60; // 60s
#[cfg(not(feature = "simulation-mode"))]
const KIMAP_ADDRESS: &'static str = kimap::KIMAP_ADDRESS; // optimism
#[cfg(feature = "simulation-mode")]
const KIMAP_ADDRESS: &str = "0xEce71a05B36CA55B895427cD9a440eEF7Cf3669D";
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Contact(HashMap<String, serde_json::Value>); struct Contact(HashMap<String, serde_json::Value>);
@ -17,7 +30,6 @@ struct Contacts(HashMap<NodeId, Contact>);
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct ContactsState { struct ContactsState {
our: Address, our: Address,
kimap: kimap::Kimap,
contacts: Contacts, contacts: Contacts,
} }
@ -25,7 +37,6 @@ impl ContactsState {
fn new(our: Address) -> Self { fn new(our: Address) -> Self {
get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self { get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self {
our, our,
kimap: kimap::Kimap::default(30),
contacts: Contacts(HashMap::new()), contacts: Contacts(HashMap::new()),
}) })
} }
@ -96,6 +107,11 @@ fn initialize(our: Address) {
let mut state: ContactsState = ContactsState::new(our); let mut state: ContactsState = ContactsState::new(our);
let kimap = kimap::Kimap::new(
eth::Provider::new(CHAIN_ID, CHAIN_TIMEOUT),
eth::Address::from_str(KIMAP_ADDRESS).unwrap(),
);
let mut http_server = http::server::HttpServer::new(5); let mut http_server = http::server::HttpServer::new(5);
// serve the frontend on a secure subdomain // serve the frontend on a secure subdomain
@ -110,10 +126,14 @@ fn initialize(our: Address) {
http_server.secure_bind_http_path("/ask").unwrap(); http_server.secure_bind_http_path("/ask").unwrap();
http_server.secure_bind_ws_path("/").unwrap(); http_server.secure_bind_ws_path("/").unwrap();
main_loop(&mut state, &mut http_server); main_loop(&mut state, &kimap, &mut http_server);
} }
fn main_loop(state: &mut ContactsState, http_server: &mut http::server::HttpServer) { fn main_loop(
state: &mut ContactsState,
kimap: &kimap::Kimap,
http_server: &mut http::server::HttpServer,
) {
loop { loop {
match await_message() { match await_message() {
Err(_send_error) => { Err(_send_error) => {
@ -131,7 +151,7 @@ fn main_loop(state: &mut ContactsState, http_server: &mut http::server::HttpServ
if source.node() != state.our.node { if source.node() != state.our.node {
continue; continue;
} }
handle_request(&source, &body, capabilities, state, http_server); handle_request(&source, &body, capabilities, state, kimap, http_server);
} }
_ => continue, // ignore responses _ => continue, // ignore responses
} }
@ -143,6 +163,7 @@ fn handle_request(
body: &[u8], body: &[u8],
capabilities: Vec<Capability>, capabilities: Vec<Capability>,
state: &mut ContactsState, state: &mut ContactsState,
kimap: &kimap::Kimap,
http_server: &mut http::server::HttpServer, http_server: &mut http::server::HttpServer,
) { ) {
// source node is ALWAYS ourselves since networking is disabled // source node is ALWAYS ourselves since networking is disabled
@ -152,14 +173,14 @@ fn handle_request(
http_server.handle_request( http_server.handle_request(
server_request, server_request,
|req| handle_http_request(state, &req), |req| handle_http_request(state, kimap, &req),
|_channel_id, _message_type, _blob| { |_channel_id, _message_type, _blob| {
// we don't expect websocket messages // we don't expect websocket messages
}, },
); );
} else { } else {
// if request is not from frontend, check that it has the required capabilities // if request is not from frontend, check that it has the required capabilities
let (response, blob) = handle_contacts_request(state, body, Some(capabilities)); let (response, blob) = handle_contacts_request(state, kimap, body, Some(capabilities));
let mut response = Response::new().body(serde_json::to_vec(&response).unwrap()); let mut response = Response::new().body(serde_json::to_vec(&response).unwrap());
if let Some(blob) = blob { if let Some(blob) = blob {
response = response.blob(blob); response = response.blob(blob);
@ -172,6 +193,7 @@ fn handle_request(
/// Handle HTTP requests from our own frontend. /// Handle HTTP requests from our own frontend.
fn handle_http_request( fn handle_http_request(
state: &mut ContactsState, state: &mut ContactsState,
kimap: &kimap::Kimap,
http_request: &http::server::IncomingHttpRequest, http_request: &http::server::IncomingHttpRequest,
) -> (http::server::HttpResponse, Option<LazyLoadBlob>) { ) -> (http::server::HttpResponse, Option<LazyLoadBlob>) {
match http_request.method().unwrap().as_str() { match http_request.method().unwrap().as_str() {
@ -185,7 +207,7 @@ fn handle_http_request(
), ),
"POST" => { "POST" => {
let blob = get_blob().unwrap(); let blob = get_blob().unwrap();
let (response, blob) = handle_contacts_request(state, blob.bytes(), None); let (response, blob) = handle_contacts_request(state, kimap, blob.bytes(), None);
if let contacts::Response::Error(e) = response { if let contacts::Response::Error(e) = response {
return ( return (
http::server::HttpResponse::new(http::StatusCode::BAD_REQUEST) http::server::HttpResponse::new(http::StatusCode::BAD_REQUEST)
@ -218,6 +240,7 @@ fn handle_http_request(
fn handle_contacts_request( fn handle_contacts_request(
state: &mut ContactsState, state: &mut ContactsState,
kimap: &kimap::Kimap,
request_bytes: &[u8], request_bytes: &[u8],
capabilities: Option<Vec<Capability>>, capabilities: Option<Vec<Capability>>,
) -> (contacts::Response, Option<LazyLoadBlob>) { ) -> (contacts::Response, Option<LazyLoadBlob>) {
@ -281,14 +304,14 @@ fn handle_contacts_request(
)), )),
), ),
contacts::Request::AddContact(node) => { contacts::Request::AddContact(node) => {
if let Some((response, blob)) = invalid_node(state, &node) { if let Some((response, blob)) = invalid_node(kimap, &node) {
return (response, blob); return (response, blob);
} }
state.add_contact(node); state.add_contact(node);
(contacts::Response::AddContact, None) (contacts::Response::AddContact, None)
} }
contacts::Request::AddField((node, field, value)) => { contacts::Request::AddField((node, field, value)) => {
if let Some((response, blob)) = invalid_node(state, &node) { if let Some((response, blob)) = invalid_node(kimap, &node) {
return (response, blob); return (response, blob);
} }
let Ok(value) = serde_json::from_str::<serde_json::Value>(&value) else { let Ok(value) = serde_json::from_str::<serde_json::Value>(&value) else {
@ -312,11 +335,10 @@ fn handle_contacts_request(
} }
fn invalid_node( fn invalid_node(
state: &ContactsState, kimap: &kimap::Kimap,
node: &str, node: &str,
) -> Option<(contacts::Response, Option<LazyLoadBlob>)> { ) -> Option<(contacts::Response, Option<LazyLoadBlob>)> {
if state if kimap
.kimap
.get(&node) .get(&node)
.map(|(tba, _, _)| tba != eth::Address::ZERO) .map(|(tba, _, _)| tba != eth::Address::ZERO)
.unwrap_or(false) .unwrap_or(false)

View File

@ -9,7 +9,7 @@
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap" rel="stylesheet" /> <link href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap" rel="stylesheet" />
<script src="/our.js"></script> <script src="/our.js"></script>
<script> <script>
document.title = window.our.node + " - contacts"; document.title = "contacts - " + window.our.node;
</script> </script>
<style> <style>
h1, h1,
@ -33,14 +33,58 @@
main { main {
margin: 0 auto; margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
padding: 20px; padding: 20px;
max-width: 960px; max-width: 960px;
min-width: 300px; min-width: 300px;
} }
form.add-contact {
max-width: 400px;
}
#contacts {
list-style: none;
}
.contact:first-of-type {
margin-top: 10px;
}
.contact {
padding: 10px;
border: 1px solid var(--tasteful-dark);
border-radius: 5px;
margin-bottom: 10px;
display: grid;
gap: 10px;
grid-auto-flow: row;
grid-template-areas:
"name name delete"
"fields fields fields"
"add-field add-field add-field";
}
.contact h3 {
grid-area: name;
}
.contact ul {
grid-area: fields;
list-style: none;
}
.contact ul li {
font-size: 1em;
}
form.delete-contact {
grid-area: delete;
}
form.add-field {
grid-area: add-field;
max-width: 400px;
}
</style> </style>
</head> </head>
@ -48,7 +92,6 @@
<h1>contacts</h1> <h1>contacts</h1>
<main> <main>
<article id="edit"> <article id="edit">
<h2>Contacts</h2>
<form id="add-contact"> <form id="add-contact">
<input type="text" name="node"> <input type="text" name="node">
<button type="submit">add contact</button> <button type="submit">add contact</button>

View File

@ -20,9 +20,45 @@ function populate_contacts(contacts) {
ul.innerHTML = ''; ul.innerHTML = '';
Object.entries(contacts).forEach(([node, contact]) => { Object.entries(contacts).forEach(([node, contact]) => {
const li = document.createElement('li'); const li = document.createElement('li');
li.innerHTML = `${JSON.stringify(node, undefined, 2)}`; const div = document.createElement('div');
div.classList.add('contact');
div.innerHTML = `<h3>${node}</h3>
<ul>
${Object.entries(contact).map(([field, value]) => `<li>${field}: ${value}</li>`).join('')}
</ul>
<form class="delete-contact" id="${node}">
<button type="submit">delete</button>
</form>
<form class="add-field" id="${node}">
<input type="text" name="field" placeholder="Field">
<input type="text" name="value" placeholder="Value">
<button type="submit">add</button>
</form>
`;
li.appendChild(div);
ul.appendChild(li); ul.appendChild(li);
}); });
ul.querySelectorAll('.delete-contact').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const node = this.getAttribute('id');
api_call({
"RemoveContact": node
});
});
});
ul.querySelectorAll('.add-field').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const node = this.getAttribute('id');
const data = new FormData(e.target);
api_call({
"AddField": [node, data.get('field'), data.get('value')]
});
});
});
} }
document.getElementById('add-contact').addEventListener('submit', (e) => { document.getElementById('add-contact').addEventListener('submit', (e) => {
@ -39,6 +75,7 @@ document.getElementById('add-contact').addEventListener('submit', (e) => {
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}).then(response => { }).then(response => {
e.target.reset();
if (response.status === 200) { if (response.status === 200) {
return null; return null;
} else { } else {
@ -46,7 +83,6 @@ document.getElementById('add-contact').addEventListener('submit', (e) => {
} }
}).then(data => { }).then(data => {
if (data === null) { if (data === null) {
e.target.reset();
return; return;
} else { } else {
alert(JSON.stringify(data)); alert(JSON.stringify(data));