mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 11:53:31 +03:00
update frontend-serving apps to new process_lib http::server utils
This commit is contained in:
parent
6c15904d76
commit
779018b84d
94
Cargo.lock
generated
94
Cargo.lock
generated
@ -61,7 +61,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy 0.7.35",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -117,9 +117,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-chains"
|
name = "alloy-chains"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47ff94ce0f141c2671c23d02c7b88990dd432856639595c5d010663d017c2c58"
|
checksum = "3312b2a48f29abe7c3ea7c7fbc1f8cc6ea09b85d74b6232e940df35f2f3826fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"strum",
|
"strum",
|
||||||
@ -1162,9 +1162,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.7.0"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fca2be1d5c43812bae364ee3f30b3afcb7877cf59f4aeb94c66f313a41d2fac9"
|
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -2078,9 +2078,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dunce"
|
name = "dunce"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
@ -2255,9 +2255,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.30"
|
version = "1.0.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libz-ng-sys",
|
"libz-ng-sys",
|
||||||
@ -3313,7 +3313,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "kinode_process_lib"
|
name = "kinode_process_lib"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#51800f9c144b3b69ed52406b4b2ae4c5aa078aec"
|
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#ddc4c11f5e2cfd6461d6d8f44d154146f3dbc087"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
@ -4197,11 +4197,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.18"
|
version = "0.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f"
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy 0.6.6",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4562,9 +4562,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.5"
|
version = "1.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -4671,7 +4671,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls-pemfile 2.1.2",
|
"rustls-pemfile 2.1.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -4880,9 +4880,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "2.1.2"
|
version = "2.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@ -5048,9 +5048,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.121"
|
version = "1.0.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609"
|
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -5470,12 +5470,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.10.1"
|
version = "3.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
@ -6822,11 +6823,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6878,6 +6879,15 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -7199,34 +7209,14 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.6.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"zerocopy-derive 0.6.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive 0.7.35",
|
"byteorder",
|
||||||
]
|
"zerocopy-derive",
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.6.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.72",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7336,7 +7326,7 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-safe 7.2.0",
|
"zstd-safe 7.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7351,18 +7341,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-safe"
|
name = "zstd-safe"
|
||||||
version = "7.2.0"
|
version = "7.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
|
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-sys",
|
"zstd-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-sys"
|
name = "zstd-sys"
|
||||||
version = "2.0.12+zstd.1.5.6"
|
version = "2.0.13+zstd.1.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use crate::state::{MirrorCheckFile, PackageListing, State};
|
use crate::state::{MirrorCheckFile, PackageListing, State};
|
||||||
use crate::DownloadResponse;
|
use crate::DownloadResponse;
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
http::{
|
http::server,
|
||||||
bind_http_path, bind_ws_path, send_response, serve_ui, IncomingHttpRequest, Method,
|
http::{self, Method, StatusCode},
|
||||||
StatusCode,
|
Address, LazyLoadBlob, NodeId, PackageId, Request,
|
||||||
},
|
|
||||||
println, Address, NodeId, PackageId, ProcessId, Request,
|
|
||||||
};
|
};
|
||||||
use kinode_process_lib::{SendError, SendErrorKind};
|
use kinode_process_lib::{SendError, SendErrorKind};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -15,7 +13,9 @@ const ICON: &str = include_str!("icon");
|
|||||||
|
|
||||||
/// Bind static and dynamic HTTP paths for the app store,
|
/// Bind static and dynamic HTTP paths for the app store,
|
||||||
/// bind to our WS updates path, and add icon and widget to homepage.
|
/// bind to our WS updates path, and add icon and widget to homepage.
|
||||||
pub fn init_frontend(our: &Address) {
|
pub fn init_frontend(our: &Address, http_server: &mut server::HttpServer) {
|
||||||
|
let config = server::HttpBindingConfig::default();
|
||||||
|
|
||||||
for path in [
|
for path in [
|
||||||
"/apps",
|
"/apps",
|
||||||
"/apps/:id",
|
"/apps/:id",
|
||||||
@ -27,30 +27,30 @@ pub fn init_frontend(our: &Address) {
|
|||||||
"/apps/rebuild-index",
|
"/apps/rebuild-index",
|
||||||
"/mirrorcheck/:node",
|
"/mirrorcheck/:node",
|
||||||
] {
|
] {
|
||||||
bind_http_path(path, true, false).expect("failed to bind http path");
|
http_server
|
||||||
|
.bind_http_path(path, config.clone())
|
||||||
|
.expect("failed to bind http path");
|
||||||
}
|
}
|
||||||
serve_ui(&our, "ui", true, false, vec!["/", "/app/:id", "/publish"])
|
http_server
|
||||||
|
.serve_ui(
|
||||||
|
&our,
|
||||||
|
"ui",
|
||||||
|
vec!["/", "/app/:id", "/publish"],
|
||||||
|
config.clone(),
|
||||||
|
)
|
||||||
.expect("failed to serve static UI");
|
.expect("failed to serve static UI");
|
||||||
|
|
||||||
bind_ws_path("/", true, true).expect("failed to bind ws path");
|
http_server
|
||||||
|
.bind_ws_path("/", server::WsBindingConfig::default())
|
||||||
|
.expect("failed to bind ws path");
|
||||||
|
|
||||||
// add ourselves to the homepage
|
// add ourselves to the homepage
|
||||||
Request::to(("our", "homepage", "homepage", "sys"))
|
kinode_process_lib::homepage::add_to_homepage(
|
||||||
.body(
|
"App Store",
|
||||||
serde_json::json!({
|
Some(ICON),
|
||||||
"Add": {
|
Some("/"),
|
||||||
"label": "App Store",
|
Some(&make_widget()),
|
||||||
"icon": ICON,
|
);
|
||||||
"path": "/",
|
|
||||||
"widget": make_widget()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_widget() -> String {
|
fn make_widget() -> String {
|
||||||
@ -183,20 +183,23 @@ fn make_widget() -> String {
|
|||||||
/// - stop auto-updating a downloaded app: DELETE /apps/:id/auto-update
|
/// - stop auto-updating a downloaded app: DELETE /apps/:id/auto-update
|
||||||
///
|
///
|
||||||
/// - RebuildIndex: POST /apps/rebuild-index
|
/// - RebuildIndex: POST /apps/rebuild-index
|
||||||
pub fn handle_http_request(state: &mut State, req: &IncomingHttpRequest) -> anyhow::Result<()> {
|
pub fn handle_http_request(
|
||||||
|
state: &mut State,
|
||||||
|
req: &server::IncomingHttpRequest,
|
||||||
|
) -> (server::HttpResponse, Option<LazyLoadBlob>) {
|
||||||
match serve_paths(state, req) {
|
match serve_paths(state, req) {
|
||||||
Ok((status_code, _headers, body)) => send_response(
|
Ok((status_code, _headers, body)) => (
|
||||||
status_code,
|
server::HttpResponse::new(status_code).header("Content-Type", "application/json"),
|
||||||
Some(HashMap::from([(
|
Some(LazyLoadBlob {
|
||||||
String::from("Content-Type"),
|
mime: None,
|
||||||
String::from("application/json"),
|
bytes: body,
|
||||||
)])),
|
}),
|
||||||
body,
|
),
|
||||||
|
Err(_e) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
Err(_e) => send_response(StatusCode::INTERNAL_SERVER_ERROR, None, vec![]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<PackageId> {
|
fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<PackageId> {
|
||||||
@ -236,8 +239,8 @@ fn gen_package_info(id: &PackageId, listing: &PackageListing) -> serde_json::Val
|
|||||||
|
|
||||||
fn serve_paths(
|
fn serve_paths(
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
req: &IncomingHttpRequest,
|
req: &server::IncomingHttpRequest,
|
||||||
) -> anyhow::Result<(StatusCode, Option<HashMap<String, String>>, Vec<u8>)> {
|
) -> anyhow::Result<(http::StatusCode, Option<HashMap<String, String>>, Vec<u8>)> {
|
||||||
let method = req.method()?;
|
let method = req.method()?;
|
||||||
|
|
||||||
let bound_path: &str = req.bound_path(Some(&state.our.process.to_string()));
|
let bound_path: &str = req.bound_path(Some(&state.our.process.to_string()));
|
||||||
|
@ -22,9 +22,8 @@ use ft_worker_lib::{
|
|||||||
spawn_receive_transfer, spawn_transfer, FTWorkerCommand, FTWorkerResult, FileTransferContext,
|
spawn_receive_transfer, spawn_transfer, FTWorkerCommand, FTWorkerResult, FileTransferContext,
|
||||||
};
|
};
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, eth, get_blob,
|
await_message, call_init, eth, get_blob, http, kimap, println, vfs, Address, LazyLoadBlob,
|
||||||
http::{self, WsMessageType},
|
Message, PackageId, Request, Response,
|
||||||
kimap, println, vfs, Address, LazyLoadBlob, Message, PackageId, Request, Response,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use state::{AppStoreLogError, PackageState, RequestedPackage, State};
|
use state::{AppStoreLogError, PackageState, RequestedPackage, State};
|
||||||
@ -71,7 +70,7 @@ pub enum Req {
|
|||||||
FTWorkerCommand(FTWorkerCommand),
|
FTWorkerCommand(FTWorkerCommand),
|
||||||
FTWorkerResult(FTWorkerResult),
|
FTWorkerResult(FTWorkerResult),
|
||||||
Eth(eth::EthSubResult),
|
Eth(eth::EthSubResult),
|
||||||
Http(http::HttpServerRequest),
|
Http(http::server::HttpServerRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@ -80,14 +79,15 @@ pub enum Resp {
|
|||||||
LocalResponse(LocalResponse),
|
LocalResponse(LocalResponse),
|
||||||
RemoteResponse(RemoteResponse),
|
RemoteResponse(RemoteResponse),
|
||||||
FTWorkerResult(FTWorkerResult),
|
FTWorkerResult(FTWorkerResult),
|
||||||
HttpClient(Result<http::HttpClientResponse, http::HttpClientError>),
|
HttpClient(Result<http::client::HttpClientResponse, http::client::HttpClientError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
call_init!(init);
|
call_init!(init);
|
||||||
fn init(our: Address) {
|
fn init(our: Address) {
|
||||||
println!("started");
|
println!("started");
|
||||||
|
|
||||||
http_api::init_frontend(&our);
|
let mut http_server = http::server::HttpServer::new(5);
|
||||||
|
http_api::init_frontend(&our, &mut http_server);
|
||||||
|
|
||||||
println!("indexing on contract address {}", KIMAP_ADDRESS);
|
println!("indexing on contract address {}", KIMAP_ADDRESS);
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ fn init(our: Address) {
|
|||||||
println!("got network error: {send_error}");
|
println!("got network error: {send_error}");
|
||||||
}
|
}
|
||||||
Ok(message) => {
|
Ok(message) => {
|
||||||
if let Err(e) = handle_message(&mut state, &message) {
|
if let Err(e) = handle_message(&mut state, &mut http_server, &message) {
|
||||||
println!("error handling message: {:?}", e);
|
println!("error handling message: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +117,11 @@ fn init(our: Address) {
|
|||||||
/// function defined for each kind of message. check whether the source
|
/// function defined for each kind of message. check whether the source
|
||||||
/// of the message is allowed to send that kind of message to us.
|
/// of the message is allowed to send that kind of message to us.
|
||||||
/// finally, fire a response if expected from a request.
|
/// finally, fire a response if expected from a request.
|
||||||
fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
|
fn handle_message(
|
||||||
|
state: &mut State,
|
||||||
|
http_server: &mut http::server::HttpServer,
|
||||||
|
message: &Message,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
if message.is_request() {
|
if message.is_request() {
|
||||||
match serde_json::from_slice::<Req>(message.body())? {
|
match serde_json::from_slice::<Req>(message.body())? {
|
||||||
Req::LocalRequest(local_request) => {
|
Req::LocalRequest(local_request) => {
|
||||||
@ -148,23 +152,24 @@ fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
|
|||||||
total_chunks,
|
total_chunks,
|
||||||
}) => {
|
}) => {
|
||||||
// forward progress to UI
|
// forward progress to UI
|
||||||
let ws_blob = LazyLoadBlob {
|
http_server.ws_push_all_channels(
|
||||||
mime: Some("application/json".to_string()),
|
"/",
|
||||||
bytes: serde_json::json!({
|
http::server::WsMessageType::Text,
|
||||||
"kind": "progress",
|
LazyLoadBlob {
|
||||||
"data": {
|
mime: Some("application/json".to_string()),
|
||||||
"file_name": file_name,
|
bytes: serde_json::json!({
|
||||||
"chunks_received": chunks_received,
|
"kind": "progress",
|
||||||
"total_chunks": total_chunks,
|
"data": {
|
||||||
}
|
"file_name": file_name,
|
||||||
})
|
"chunks_received": chunks_received,
|
||||||
.to_string()
|
"total_chunks": total_chunks,
|
||||||
.as_bytes()
|
}
|
||||||
.to_vec(),
|
})
|
||||||
};
|
.to_string()
|
||||||
for channel_id in state.ui_ws_channels.iter() {
|
.as_bytes()
|
||||||
http::send_ws_push(*channel_id, WsMessageType::Text, ws_blob.clone());
|
.to_vec(),
|
||||||
}
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Req::FTWorkerResult(r) => {
|
Req::FTWorkerResult(r) => {
|
||||||
println!("got weird ft_worker result: {r:?}");
|
println!("got weird ft_worker result: {r:?}");
|
||||||
@ -186,19 +191,19 @@ fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
|
|||||||
.subscribe_loop(1, utils::app_store_filter(state));
|
.subscribe_loop(1, utils::app_store_filter(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Req::Http(incoming) => {
|
Req::Http(server_request) => {
|
||||||
if !message.is_local(&state.our)
|
if !message.is_local(&state.our)
|
||||||
|| message.source().process != "http_server:distro:sys"
|
|| message.source().process != "http_server:distro:sys"
|
||||||
{
|
{
|
||||||
return Err(anyhow::anyhow!("http_server from non-local node"));
|
return Err(anyhow::anyhow!("http_server from non-local node"));
|
||||||
}
|
}
|
||||||
if let http::HttpServerRequest::Http(req) = incoming {
|
http_server.handle_request(
|
||||||
http_api::handle_http_request(state, &req)?;
|
server_request,
|
||||||
} else if let http::HttpServerRequest::WebSocketOpen { channel_id, .. } = incoming {
|
|incoming| http_api::handle_http_request(state, &incoming),
|
||||||
state.ui_ws_channels.insert(channel_id);
|
|_channel_id, _message_type, _blob| {
|
||||||
} else if let http::HttpServerRequest::WebSocketClose { 0: channel_id } = incoming {
|
// not expecting any websocket messages from FE currently
|
||||||
state.ui_ws_channels.remove(&channel_id);
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -208,7 +213,10 @@ fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
|
|||||||
Some(context) => std::str::from_utf8(context).unwrap_or_default(),
|
Some(context) => std::str::from_utf8(context).unwrap_or_default(),
|
||||||
None => return Err(anyhow::anyhow!("http_client response without context")),
|
None => return Err(anyhow::anyhow!("http_client response without context")),
|
||||||
};
|
};
|
||||||
if let Ok(http::HttpClientResponse::Http(http::HttpResponse { status, .. })) = resp
|
if let Ok(http::client::HttpClientResponse::Http(http::client::HttpResponse {
|
||||||
|
status,
|
||||||
|
..
|
||||||
|
})) = resp
|
||||||
{
|
{
|
||||||
if status == 200 {
|
if status == 200 {
|
||||||
handle_receive_download(state, &name)?;
|
handle_receive_download(state, &name)?;
|
||||||
@ -435,23 +443,12 @@ pub fn start_download(
|
|||||||
};
|
};
|
||||||
// if `from` is a node, send a request to it
|
// if `from` is a node, send a request to it
|
||||||
// but if it is a url, use http_client to GET it
|
// but if it is a url, use http_client to GET it
|
||||||
|
let Ok(url) = url::Url::parse(&from) else {
|
||||||
|
return DownloadResponse::Denied(Reason::NotMirroring);
|
||||||
|
};
|
||||||
if from.starts_with("http") {
|
if from.starts_with("http") {
|
||||||
// use http_client to GET it
|
// use http_client to GET it
|
||||||
Request::to(("our", "http_client", "distro", "sys"))
|
http::client::send_request(http::Method::GET, url, None, Some(60), vec![]);
|
||||||
.body(
|
|
||||||
serde_json::to_vec(&http::HttpClientAction::Http(http::OutgoingHttpRequest {
|
|
||||||
method: "GET".to_string(),
|
|
||||||
version: None,
|
|
||||||
url: from.clone(),
|
|
||||||
headers: std::collections::HashMap::new(),
|
|
||||||
}))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.context(package_id.to_string().as_bytes())
|
|
||||||
.expects_response(60)
|
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return DownloadResponse::Started;
|
return DownloadResponse::Started;
|
||||||
} else {
|
} else {
|
||||||
if let Ok(Ok(Message::Response { body, .. })) =
|
if let Ok(Ok(Message::Response { body, .. })) =
|
||||||
|
@ -116,8 +116,6 @@ pub struct State {
|
|||||||
pub requested_packages: HashMap<PackageId, RequestedPackage>,
|
pub requested_packages: HashMap<PackageId, RequestedPackage>,
|
||||||
/// the APIs we have outstanding requests to download (not persisted)
|
/// the APIs we have outstanding requests to download (not persisted)
|
||||||
pub requested_apis: HashMap<PackageId, RequestedPackage>,
|
pub requested_apis: HashMap<PackageId, RequestedPackage>,
|
||||||
/// UI websocket connected channel_IDs
|
|
||||||
pub ui_ws_channels: HashSet<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -153,7 +151,6 @@ impl State {
|
|||||||
downloaded_apis: s.downloaded_apis,
|
downloaded_apis: s.downloaded_apis,
|
||||||
requested_packages: HashMap::new(),
|
requested_packages: HashMap::new(),
|
||||||
requested_apis: HashMap::new(),
|
requested_apis: HashMap::new(),
|
||||||
ui_ws_channels: HashSet::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +165,6 @@ impl State {
|
|||||||
downloaded_apis: HashSet::new(),
|
downloaded_apis: HashSet::new(),
|
||||||
requested_packages: HashMap::new(),
|
requested_packages: HashMap::new(),
|
||||||
requested_apis: HashMap::new(),
|
requested_apis: HashMap::new(),
|
||||||
ui_ws_channels: HashSet::new(),
|
|
||||||
};
|
};
|
||||||
state.populate_packages_from_filesystem()?;
|
state.populate_packages_from_filesystem()?;
|
||||||
Ok(state)
|
Ok(state)
|
||||||
@ -380,7 +376,7 @@ impl State {
|
|||||||
let block_number: u64 = log.block_number.ok_or(AppStoreLogError::NoBlockNumber)?;
|
let block_number: u64 = log.block_number.ok_or(AppStoreLogError::NoBlockNumber)?;
|
||||||
|
|
||||||
let note: kimap::Note =
|
let note: kimap::Note =
|
||||||
kimap::decode_note_log(&log).map_err(AppStoreLogError::DecodeLogError)?;
|
kimap::decode_note_log(&log).map_err(|e| AppStoreLogError::DecodeLogError(e))?;
|
||||||
|
|
||||||
let package_id = note
|
let package_id = note
|
||||||
.parent_path
|
.parent_path
|
||||||
|
@ -131,7 +131,7 @@ pub fn fetch_metadata_from_url(
|
|||||||
) -> Result<kt::Erc721Metadata, AppStoreLogError> {
|
) -> Result<kt::Erc721Metadata, AppStoreLogError> {
|
||||||
if let Ok(url) = url::Url::parse(metadata_url) {
|
if let Ok(url) = url::Url::parse(metadata_url) {
|
||||||
if let Ok(_) =
|
if let Ok(_) =
|
||||||
http::send_request_await_response(http::Method::GET, url, None, timeout, vec![])
|
http::client::send_request_await_response(http::Method::GET, url, None, timeout, vec![])
|
||||||
{
|
{
|
||||||
if let Some(body) = get_blob() {
|
if let Some(body) = get_blob() {
|
||||||
let hash = keccak_256_hash(&body.bytes);
|
let hash = keccak_256_hash(&body.bytes);
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
|
use crate::kinode::process::chess::{
|
||||||
|
MoveRequest, NewGameRequest, Request as ChessRequest, Response as ChessResponse,
|
||||||
|
};
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, get_blob, get_typed_state, http, println, set_state, Address,
|
await_message, call_init, get_blob, get_typed_state, http, http::server, println, set_state,
|
||||||
LazyLoadBlob, Message, NodeId, Request, Response,
|
Address, LazyLoadBlob, Message, NodeId, Request, Response,
|
||||||
};
|
};
|
||||||
use pleco::Board;
|
use pleco::Board;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
extern crate base64;
|
|
||||||
|
|
||||||
use crate::kinode::process::chess::{
|
|
||||||
MoveRequest, NewGameRequest, Request as ChessRequest, Response as ChessResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ICON: &str = include_str!("icon");
|
const ICON: &str = include_str!("icon");
|
||||||
|
|
||||||
@ -30,6 +28,7 @@ struct Game {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct ChessState {
|
struct ChessState {
|
||||||
|
pub our: Address,
|
||||||
pub games: HashMap<String, Game>, // game is by opposing player id
|
pub games: HashMap<String, Game>, // game is by opposing player id
|
||||||
pub clients: HashSet<u32>, // doesn't get persisted
|
pub clients: HashSet<u32>, // doesn't get persisted
|
||||||
}
|
}
|
||||||
@ -43,14 +42,16 @@ fn save_chess_state(state: &ChessState) {
|
|||||||
set_state(&bincode::serialize(&state.games).unwrap());
|
set_state(&bincode::serialize(&state.games).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_chess_state() -> ChessState {
|
fn load_chess_state(our: Address) -> ChessState {
|
||||||
match get_typed_state(|bytes| bincode::deserialize::<HashMap<String, Game>>(bytes)) {
|
match get_typed_state(|bytes| bincode::deserialize::<HashMap<String, Game>>(bytes)) {
|
||||||
Some(games) => ChessState {
|
Some(games) => ChessState {
|
||||||
|
our,
|
||||||
games,
|
games,
|
||||||
clients: HashSet::new(),
|
clients: HashSet::new(),
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let state = ChessState {
|
let state = ChessState {
|
||||||
|
our,
|
||||||
games: HashMap::new(),
|
games: HashMap::new(),
|
||||||
clients: HashSet::new(),
|
clients: HashSet::new(),
|
||||||
};
|
};
|
||||||
@ -60,28 +61,20 @@ fn load_chess_state() -> ChessState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_ws_update(our: &Address, game: &Game, open_channels: &HashSet<u32>) -> anyhow::Result<()> {
|
fn send_ws_update(http_server: &mut server::HttpServer, game: &Game) {
|
||||||
for channel in open_channels {
|
http_server.ws_push_all_channels(
|
||||||
Request::new()
|
"/",
|
||||||
.target((&our.node, "http_server", "distro", "sys"))
|
server::WsMessageType::Binary,
|
||||||
.body(serde_json::to_vec(
|
LazyLoadBlob {
|
||||||
&http::HttpServerAction::WebSocketPush {
|
mime: Some("application/json".to_string()),
|
||||||
channel_id: *channel,
|
bytes: serde_json::json!({
|
||||||
message_type: http::WsMessageType::Binary,
|
"kind": "game_update",
|
||||||
},
|
"data": game,
|
||||||
)?)
|
|
||||||
.blob(LazyLoadBlob {
|
|
||||||
mime: Some("application/json".to_string()),
|
|
||||||
bytes: serde_json::json!({
|
|
||||||
"kind": "game_update",
|
|
||||||
"data": game,
|
|
||||||
})
|
|
||||||
.to_string()
|
|
||||||
.into_bytes(),
|
|
||||||
})
|
})
|
||||||
.send()?;
|
.to_string()
|
||||||
}
|
.into_bytes(),
|
||||||
Ok(())
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boilerplate: generate the wasm bindings for a process
|
// Boilerplate: generate the wasm bindings for a process
|
||||||
@ -91,6 +84,7 @@ wit_bindgen::generate!({
|
|||||||
generate_unused_types: true,
|
generate_unused_types: true,
|
||||||
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize],
|
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize],
|
||||||
});
|
});
|
||||||
|
|
||||||
// After generating bindings, use this macro to define the Component struct
|
// After generating bindings, use this macro to define the Component struct
|
||||||
// and its init() function, which the kernel will look for on startup.
|
// and its init() function, which the kernel will look for on startup.
|
||||||
call_init!(initialize);
|
call_init!(initialize);
|
||||||
@ -99,38 +93,34 @@ fn initialize(our: Address) {
|
|||||||
println!("started");
|
println!("started");
|
||||||
|
|
||||||
// add ourselves to the homepage
|
// add ourselves to the homepage
|
||||||
Request::to(("our", "homepage", "homepage", "sys"))
|
kinode_process_lib::homepage::add_to_homepage("Chess", Some(ICON), Some("/"), None);
|
||||||
.body(
|
|
||||||
serde_json::json!({
|
// create an HTTP server struct with which to manipulate `http_server:distro:sys`
|
||||||
"Add": {
|
let mut http_server = server::HttpServer::new(5);
|
||||||
"label": "Chess",
|
let http_config = server::HttpBindingConfig::default();
|
||||||
"icon": ICON,
|
|
||||||
"path": "/", // just our root
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Serve the index.html and other UI files found in pkg/ui at the root path.
|
// Serve the index.html and other UI files found in pkg/ui at the root path.
|
||||||
// authenticated=true, local_only=false
|
// authenticated=true, local_only=false
|
||||||
http::serve_ui(&our, "ui", true, false, vec!["/"]).unwrap();
|
http_server
|
||||||
|
.serve_ui(&our, "ui", http_config.clone())
|
||||||
|
.expect("failed to serve ui");
|
||||||
|
|
||||||
// Allow HTTP requests to be made to /games; they will be handled dynamically.
|
// Allow HTTP requests to be made to /games; they will be handled dynamically.
|
||||||
http::bind_http_path("/games", true, false).unwrap();
|
http_server
|
||||||
|
.bind_http_path("/games", http_config.clone())
|
||||||
|
.expect("failed to bind /games");
|
||||||
|
|
||||||
// Allow websockets to be opened at / (our process ID will be prepended).
|
// Allow websockets to be opened at / (our process ID will be prepended).
|
||||||
http::bind_ws_path("/", true, false).unwrap();
|
http_server
|
||||||
|
.bind_ws_path("/", server::WsBindingConfig::default())
|
||||||
|
.expect("failed to bind ws");
|
||||||
|
|
||||||
// Grab our state, then enter the main event loop.
|
// Grab our state, then enter the main event loop.
|
||||||
let mut state: ChessState = load_chess_state();
|
let mut state: ChessState = load_chess_state(our);
|
||||||
main_loop(&our, &mut state);
|
main_loop(&mut state, &mut http_server);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(our: &Address, state: &mut ChessState) {
|
fn main_loop(state: &mut ChessState, http_server: &mut server::HttpServer) {
|
||||||
loop {
|
loop {
|
||||||
// Call await_message() to wait for any incoming messages.
|
// Call await_message() to wait for any incoming messages.
|
||||||
// If we get a network error, make a print and throw it away.
|
// If we get a network error, make a print and throw it away.
|
||||||
@ -141,96 +131,71 @@ fn main_loop(our: &Address, state: &mut ChessState) {
|
|||||||
println!("got network error: {send_error:?}");
|
println!("got network error: {send_error:?}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Ok(message) => match handle_request(&our, &message, state) {
|
Ok(message) => match handle_request(&message, state, http_server) {
|
||||||
Ok(()) => continue,
|
Ok(()) => continue,
|
||||||
Err(e) => println!("error handling request: {:?}", e),
|
Err(e) => println!("error handling request: {e}"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle chess protocol messages from ourself *or* other nodes.
|
/// Handle chess protocol messages from ourself *or* other nodes.
|
||||||
fn handle_request(our: &Address, message: &Message, state: &mut ChessState) -> anyhow::Result<()> {
|
fn handle_request(
|
||||||
|
message: &Message,
|
||||||
|
state: &mut ChessState,
|
||||||
|
http_server: &mut server::HttpServer,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
// Throw away responses. We never expect any responses *here*, because for every
|
// Throw away responses. We never expect any responses *here*, because for every
|
||||||
// chess protocol request, we *await* its response in-place. This is appropriate
|
// chess protocol request, we *await* its response in-place. This is appropriate
|
||||||
// for direct node<>node comms, less appropriate for other circumstances...
|
// for direct node-to-node comms, less appropriate for other circumstances...
|
||||||
if !message.is_request() {
|
if !message.is_request() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request is from another node, handle it as an incoming request.
|
// If the request is from another node, handle it as an incoming request.
|
||||||
// Note that we can enforce the ProcessId as well, but it shouldn't be a trusted
|
// Note that we can enforce the ProcessId as well, but it shouldn't be a trusted
|
||||||
// piece of information, since another node can easily spoof any ProcessId on a request.
|
// piece of information, since another node can easily spoof any ProcessId on a request.
|
||||||
// It can still be useful simply as a protocol-level switch to handle different kinds of
|
// It can still be useful simply as a protocol-level switch to handle different kinds of
|
||||||
// requests from the same node, with the knowledge that the remote node can finagle with
|
// requests from the same node, with the knowledge that the remote node can finagle with
|
||||||
// which ProcessId a given message can be from. It's their code, after all.
|
// which ProcessId a given message can be from. It's their code, after all.
|
||||||
if message.source().node != our.node {
|
if message.source().node != state.our.node {
|
||||||
// Deserialize the request IPC to our format, and throw it away if it
|
// Deserialize the request IPC to our format, and throw it away if it
|
||||||
// doesn't fit.
|
// doesn't fit.
|
||||||
let Ok(chess_request) = serde_json::from_slice::<ChessRequest>(message.body()) else {
|
let Ok(chess_request) = serde_json::from_slice::<ChessRequest>(message.body()) else {
|
||||||
return Err(anyhow::anyhow!("invalid chess request"));
|
return Err(anyhow::anyhow!("invalid chess request"));
|
||||||
};
|
};
|
||||||
handle_chess_request(our, &message.source().node, state, &chess_request)
|
handle_chess_request(&message.source().node, state, http_server, &chess_request)
|
||||||
|
}
|
||||||
// ...and if the request is from ourselves, handle it as our own!
|
// ...and if the request is from ourselves, handle it as our own!
|
||||||
// Note that since this is a local request, we *can* trust the ProcessId.
|
// Note that since this is a local request, we *can* trust the ProcessId.
|
||||||
// Here, we'll accept messages from the local terminal so as to make this a "CLI" app.
|
else {
|
||||||
} else if message.source().node == our.node
|
// Here, we accept messages *from any local process that can message this one*.
|
||||||
&& message.source().process == "terminal:terminal:sys"
|
// Since the manifest specifies that this process is *public*, any local process
|
||||||
{
|
// can "play chess" for us.
|
||||||
|
//
|
||||||
|
// If you wanted to restrict this privilege, you could check for a specific process,
|
||||||
|
// package, and/or publisher here, *or* change the manifest to only grant messaging
|
||||||
|
// capabilities to specific processes.
|
||||||
|
|
||||||
|
// if the message is from the HTTP server runtime module, we should handle it
|
||||||
|
// as an HTTP request and not a chess request
|
||||||
|
if message.source().process == "http_server:distro:sys" {
|
||||||
|
return handle_http_request(state, http_server, message);
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(chess_request) = serde_json::from_slice::<ChessRequest>(message.body()) else {
|
let Ok(chess_request) = serde_json::from_slice::<ChessRequest>(message.body()) else {
|
||||||
return Err(anyhow::anyhow!("invalid chess request"));
|
return Err(anyhow::anyhow!("invalid chess request"));
|
||||||
};
|
};
|
||||||
handle_local_request(our, state, &chess_request)
|
let _game = handle_local_request(state, &chess_request)?;
|
||||||
} else if message.source().node == our.node
|
Ok(())
|
||||||
&& message.source().process == "http_server:distro:sys"
|
|
||||||
{
|
|
||||||
// receive HTTP requests and websocket connection messages from our server
|
|
||||||
match serde_json::from_slice::<http::HttpServerRequest>(message.body())? {
|
|
||||||
http::HttpServerRequest::Http(ref incoming) => {
|
|
||||||
match handle_http_request(our, state, incoming) {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
http::send_response(
|
|
||||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
|
||||||
None,
|
|
||||||
"Service Unavailable".to_string().as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
Err(anyhow::anyhow!("error handling http request: {e:?}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http::HttpServerRequest::WebSocketOpen { channel_id, .. } => {
|
|
||||||
// We know this is authenticated and unencrypted because we only
|
|
||||||
// bound one path, the root path. So we know that client
|
|
||||||
// frontend opened a websocket and can send updates
|
|
||||||
state.clients.insert(channel_id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
http::HttpServerRequest::WebSocketClose(channel_id) => {
|
|
||||||
// client frontend closed a websocket
|
|
||||||
state.clients.remove(&channel_id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
http::HttpServerRequest::WebSocketPush { .. } => {
|
|
||||||
// client frontend sent a websocket message
|
|
||||||
// we don't expect this! we only use websockets to push updates
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If we get a request from ourselves that isn't from the terminal, we'll just
|
|
||||||
// throw it away. This is a good place to put a printout to show that we've
|
|
||||||
// received a request from ourselves that we don't know how to handle.
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"got request from not-the-terminal, ignoring"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle chess protocol messages from other nodes.
|
/// Handle chess protocol messages from other nodes.
|
||||||
fn handle_chess_request(
|
fn handle_chess_request(
|
||||||
our: &Address,
|
|
||||||
source_node: &NodeId,
|
source_node: &NodeId,
|
||||||
state: &mut ChessState,
|
state: &mut ChessState,
|
||||||
|
http_server: &mut server::HttpServer,
|
||||||
action: &ChessRequest,
|
action: &ChessRequest,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
println!("handling action from {source_node}: {action:?}");
|
println!("handling action from {source_node}: {action:?}");
|
||||||
@ -258,7 +223,7 @@ fn handle_chess_request(
|
|||||||
// The simplest and most trivial way to keep state. You'll want to
|
// The simplest and most trivial way to keep state. You'll want to
|
||||||
// use a database or something in a real app, and consider performance
|
// use a database or something in a real app, and consider performance
|
||||||
// when doing intensive data-based operations.
|
// when doing intensive data-based operations.
|
||||||
send_ws_update(&our, &game, &state.clients)?;
|
send_ws_update(http_server, &game);
|
||||||
state.games.insert(game_id.to_string(), game);
|
state.games.insert(game_id.to_string(), game);
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
// Send a response to tell them we've accepted the game.
|
// Send a response to tell them we've accepted the game.
|
||||||
@ -293,7 +258,7 @@ fn handle_chess_request(
|
|||||||
}
|
}
|
||||||
// Persist state.
|
// Persist state.
|
||||||
game.board = board.fen();
|
game.board = board.fen();
|
||||||
send_ws_update(&our, &game, &state.clients)?;
|
send_ws_update(http_server, &game);
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
// Send a response to tell them we've accepted the move.
|
// Send a response to tell them we've accepted the move.
|
||||||
Response::new()
|
Response::new()
|
||||||
@ -307,7 +272,7 @@ fn handle_chess_request(
|
|||||||
match state.games.get_mut(game_id) {
|
match state.games.get_mut(game_id) {
|
||||||
Some(game) => {
|
Some(game) => {
|
||||||
game.ended = true;
|
game.ended = true;
|
||||||
send_ws_update(&our, &game, &state.clients)?;
|
send_ws_update(http_server, &game);
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
@ -318,18 +283,18 @@ fn handle_chess_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle actions we are performing. Here's where we'll send_and_await various requests.
|
/// Handle actions we are performing. Here's where we'll send_and_await various requests.
|
||||||
fn handle_local_request(
|
fn handle_local_request(state: &mut ChessState, action: &ChessRequest) -> anyhow::Result<Game> {
|
||||||
our: &Address,
|
|
||||||
state: &mut ChessState,
|
|
||||||
action: &ChessRequest,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
match action {
|
match action {
|
||||||
ChessRequest::NewGame(NewGameRequest { white, black }) => {
|
ChessRequest::NewGame(NewGameRequest { white, black }) => {
|
||||||
// Create a new game. We'll enforce that one of the two players is us.
|
// Create a new game. We'll enforce that one of the two players is us.
|
||||||
if white != &our.node && black != &our.node {
|
if white != &state.our.node && black != &state.our.node {
|
||||||
return Err(anyhow::anyhow!("cannot start a game without us!"));
|
return Err(anyhow::anyhow!("cannot start a game without us!"));
|
||||||
}
|
}
|
||||||
let game_id = if white == &our.node { black } else { white };
|
let game_id = if white == &state.our.node {
|
||||||
|
black
|
||||||
|
} else {
|
||||||
|
white
|
||||||
|
};
|
||||||
// If we already have a game with this player, throw an error.
|
// If we already have a game with this player, throw an error.
|
||||||
if let Some(game) = state.games.get(game_id)
|
if let Some(game) = state.games.get(game_id)
|
||||||
&& !game.ended
|
&& !game.ended
|
||||||
@ -338,11 +303,11 @@ fn handle_local_request(
|
|||||||
};
|
};
|
||||||
// Send the other player a NewGame request
|
// Send the other player a NewGame request
|
||||||
// The request is exactly the same as what we got from terminal.
|
// The request is exactly the same as what we got from terminal.
|
||||||
// We'll give them 5 seconds to respond...
|
// We'll give their node 30 seconds to respond...
|
||||||
let Ok(Message::Response { ref body, .. }) = Request::new()
|
let Ok(Message::Response { ref body, .. }) = Request::new()
|
||||||
.target((game_id, our.process.clone()))
|
.target((game_id, state.our.process.clone()))
|
||||||
.body(serde_json::to_vec(&action)?)
|
.body(serde_json::to_vec(&action)?)
|
||||||
.send_and_await_response(5)?
|
.send_and_await_response(30)?
|
||||||
else {
|
else {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"other player did not respond properly to new game request"
|
"other player did not respond properly to new game request"
|
||||||
@ -361,9 +326,9 @@ fn handle_local_request(
|
|||||||
black: black.to_string(),
|
black: black.to_string(),
|
||||||
ended: false,
|
ended: false,
|
||||||
};
|
};
|
||||||
state.games.insert(game_id.to_string(), game);
|
state.games.insert(game_id.to_string(), game.clone());
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
Ok(())
|
Ok(game)
|
||||||
}
|
}
|
||||||
ChessRequest::Move(MoveRequest { game_id, move_str }) => {
|
ChessRequest::Move(MoveRequest { game_id, move_str }) => {
|
||||||
// Make a move. We'll enforce that it's our turn. The game_id is the
|
// Make a move. We'll enforce that it's our turn. The game_id is the
|
||||||
@ -371,8 +336,8 @@ fn handle_local_request(
|
|||||||
let Some(game) = state.games.get_mut(game_id) else {
|
let Some(game) = state.games.get_mut(game_id) else {
|
||||||
return Err(anyhow::anyhow!("no game with {game_id}"));
|
return Err(anyhow::anyhow!("no game with {game_id}"));
|
||||||
};
|
};
|
||||||
if (game.turns % 2 == 0 && game.white != our.node)
|
if (game.turns % 2 == 0 && game.white != state.our.node)
|
||||||
|| (game.turns % 2 == 1 && game.black != our.node)
|
|| (game.turns % 2 == 1 && game.black != state.our.node)
|
||||||
{
|
{
|
||||||
return Err(anyhow::anyhow!("not our turn!"));
|
return Err(anyhow::anyhow!("not our turn!"));
|
||||||
} else if game.ended {
|
} else if game.ended {
|
||||||
@ -384,11 +349,11 @@ fn handle_local_request(
|
|||||||
}
|
}
|
||||||
// Send the move to the other player, then check if the game is over.
|
// Send the move to the other player, then check if the game is over.
|
||||||
// The request is exactly the same as what we got from terminal.
|
// The request is exactly the same as what we got from terminal.
|
||||||
// We'll give them 5 seconds to respond...
|
// We'll give their node 30 seconds to respond...
|
||||||
let Ok(Message::Response { ref body, .. }) = Request::new()
|
let Ok(Message::Response { ref body, .. }) = Request::new()
|
||||||
.target((game_id, our.process.clone()))
|
.target((game_id, state.our.process.clone()))
|
||||||
.body(serde_json::to_vec(&action)?)
|
.body(serde_json::to_vec(&action)?)
|
||||||
.send_and_await_response(5)?
|
.send_and_await_response(30)?
|
||||||
else {
|
else {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"other player did not respond properly to our move"
|
"other player did not respond properly to our move"
|
||||||
@ -402,8 +367,9 @@ fn handle_local_request(
|
|||||||
game.ended = true;
|
game.ended = true;
|
||||||
}
|
}
|
||||||
game.board = board.fen();
|
game.board = board.fen();
|
||||||
|
let game = game.clone();
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
Ok(())
|
Ok(game)
|
||||||
}
|
}
|
||||||
ChessRequest::Resign(ref with_who) => {
|
ChessRequest::Resign(ref with_who) => {
|
||||||
// Resign from a game with a given player.
|
// Resign from a game with a given player.
|
||||||
@ -412,250 +378,196 @@ fn handle_local_request(
|
|||||||
};
|
};
|
||||||
// send the other player an end game request -- no response expected
|
// send the other player an end game request -- no response expected
|
||||||
Request::new()
|
Request::new()
|
||||||
.target((with_who, our.process.clone()))
|
.target((with_who, state.our.process.clone()))
|
||||||
.body(serde_json::to_vec(&action)?)
|
.body(serde_json::to_vec(&action)?)
|
||||||
.send()?;
|
.send()?;
|
||||||
game.ended = true;
|
game.ended = true;
|
||||||
|
let game = game.clone();
|
||||||
save_chess_state(&state);
|
save_chess_state(&state);
|
||||||
Ok(())
|
Ok(game)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle HTTP requests from our own frontend.
|
/// Handle HTTP requests from our own frontend.
|
||||||
fn handle_http_request(
|
fn handle_http_request(
|
||||||
our: &Address,
|
|
||||||
state: &mut ChessState,
|
state: &mut ChessState,
|
||||||
http_request: &http::IncomingHttpRequest,
|
http_server: &mut server::HttpServer,
|
||||||
|
message: &Message,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if http_request.bound_path(Some(&our.process.to_string())) != "/games" {
|
let request = http_server.parse_request(message.body())?;
|
||||||
http::send_response(
|
|
||||||
http::StatusCode::NOT_FOUND,
|
// the HTTP server helper struct allows us to pass functions that
|
||||||
|
// handle the various types of requests we get from the frontend
|
||||||
|
http_server.handle_request(
|
||||||
|
request,
|
||||||
|
|incoming| {
|
||||||
|
// client frontend sent an HTTP request, process it and
|
||||||
|
// return an HTTP response
|
||||||
|
// these functions can reuse the logic from handle_local_request
|
||||||
|
// after converting the request into the appropriate format!
|
||||||
|
match incoming.method().unwrap_or_default() {
|
||||||
|
http::Method::GET => handle_get(state),
|
||||||
|
http::Method::POST => handle_post(state),
|
||||||
|
http::Method::PUT => handle_put(state),
|
||||||
|
http::Method::DELETE => handle_delete(state, &incoming),
|
||||||
|
_ => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|_channel_id, _message_type, _message| {
|
||||||
|
// client frontend sent a websocket message
|
||||||
|
// we don't expect this! we only use websockets to push updates
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On GET: return all active games
|
||||||
|
fn handle_get(state: &mut ChessState) -> (server::HttpResponse, Option<LazyLoadBlob>) {
|
||||||
|
(
|
||||||
|
server::HttpResponse::new(http::StatusCode::OK),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/json".to_string()),
|
||||||
|
bytes: serde_json::to_vec(&state.games).expect("failed to serialize games!"),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On POST: create a new game
|
||||||
|
fn handle_post(state: &mut ChessState) -> (server::HttpResponse, Option<LazyLoadBlob>) {
|
||||||
|
let Some(blob) = get_blob() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
None,
|
None,
|
||||||
"Not Found".to_string().as_bytes().to_vec(),
|
|
||||||
);
|
);
|
||||||
return Ok(());
|
};
|
||||||
}
|
let Ok(blob_json) = serde_json::from_slice::<serde_json::Value>(&blob.bytes) else {
|
||||||
match http_request.method()?.as_str() {
|
return (
|
||||||
// on GET: give the frontend all of our active games
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
"GET" => Ok(http::send_response(
|
|
||||||
http::StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
String::from("Content-Type"),
|
|
||||||
String::from("application/json"),
|
|
||||||
)])),
|
|
||||||
serde_json::to_vec(&state.games)?,
|
|
||||||
)),
|
|
||||||
// on POST: create a new game
|
|
||||||
"POST" => {
|
|
||||||
let Some(blob) = get_blob() else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let blob_json = serde_json::from_slice::<serde_json::Value>(&blob.bytes)?;
|
|
||||||
let Some(game_id) = blob_json["id"].as_str() else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(game) = state.games.get(game_id)
|
|
||||||
&& !game.ended
|
|
||||||
{
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::CONFLICT,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let player_white = blob_json["white"]
|
|
||||||
.as_str()
|
|
||||||
.unwrap_or(our.node.as_str())
|
|
||||||
.to_string();
|
|
||||||
let player_black = blob_json["black"].as_str().unwrap_or(game_id).to_string();
|
|
||||||
|
|
||||||
// send the other player a new game request
|
|
||||||
let Ok(msg) = Request::new()
|
|
||||||
.target((game_id, our.process.clone()))
|
|
||||||
.body(serde_json::to_vec(&ChessRequest::NewGame(
|
|
||||||
NewGameRequest {
|
|
||||||
white: player_white.clone(),
|
|
||||||
black: player_black.clone(),
|
|
||||||
},
|
|
||||||
))?)
|
|
||||||
.send_and_await_response(5)?
|
|
||||||
else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"other player did not respond properly to new game request"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
// if they accept, create a new game
|
|
||||||
// otherwise, should surface error to FE...
|
|
||||||
if serde_json::from_slice::<ChessResponse>(msg.body())?
|
|
||||||
!= ChessResponse::NewGameAccepted
|
|
||||||
{
|
|
||||||
return Err(anyhow::anyhow!("other player rejected new game request"));
|
|
||||||
}
|
|
||||||
// create a new game
|
|
||||||
let game = Game {
|
|
||||||
id: game_id.to_string(),
|
|
||||||
turns: 0,
|
|
||||||
board: Board::start_pos().fen(),
|
|
||||||
white: player_white,
|
|
||||||
black: player_black,
|
|
||||||
ended: false,
|
|
||||||
};
|
|
||||||
let body = serde_json::to_vec(&game)?;
|
|
||||||
state.games.insert(game_id.to_string(), game);
|
|
||||||
save_chess_state(&state);
|
|
||||||
http::send_response(
|
|
||||||
http::StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
String::from("Content-Type"),
|
|
||||||
String::from("application/json"),
|
|
||||||
)])),
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// on PUT: make a move
|
|
||||||
"PUT" => {
|
|
||||||
let Some(blob) = get_blob() else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let blob_json = serde_json::from_slice::<serde_json::Value>(&blob.bytes)?;
|
|
||||||
let Some(game_id) = blob_json["id"].as_str() else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Some(game) = state.games.get_mut(game_id) else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::NOT_FOUND,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
if (game.turns % 2 == 0 && game.white != our.node)
|
|
||||||
|| (game.turns % 2 == 1 && game.black != our.node)
|
|
||||||
{
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::FORBIDDEN,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
} else if game.ended {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::CONFLICT,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let Some(move_str) = blob_json["move"].as_str() else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let mut board = Board::from_fen(&game.board).unwrap();
|
|
||||||
if !board.apply_uci_move(move_str) {
|
|
||||||
// TODO surface illegal move to player or something here
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// send the move to the other player
|
|
||||||
// check if the game is over
|
|
||||||
// if so, update the records
|
|
||||||
let Ok(msg) = Request::new()
|
|
||||||
.target((game_id, our.process.clone()))
|
|
||||||
.body(serde_json::to_vec(&ChessRequest::Move(MoveRequest {
|
|
||||||
game_id: game_id.to_string(),
|
|
||||||
move_str: move_str.to_string(),
|
|
||||||
}))?)
|
|
||||||
.send_and_await_response(5)?
|
|
||||||
else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"other player did not respond properly to our move"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
if serde_json::from_slice::<ChessResponse>(msg.body())? != ChessResponse::MoveAccepted {
|
|
||||||
return Err(anyhow::anyhow!("other player rejected our move"));
|
|
||||||
}
|
|
||||||
// update the game
|
|
||||||
game.turns += 1;
|
|
||||||
if board.checkmate() || board.stalemate() {
|
|
||||||
game.ended = true;
|
|
||||||
}
|
|
||||||
game.board = board.fen();
|
|
||||||
// update state and return to FE
|
|
||||||
let body = serde_json::to_vec(&game)?;
|
|
||||||
save_chess_state(&state);
|
|
||||||
// return the game
|
|
||||||
http::send_response(
|
|
||||||
http::StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
String::from("Content-Type"),
|
|
||||||
String::from("application/json"),
|
|
||||||
)])),
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// on DELETE: end the game
|
|
||||||
"DELETE" => {
|
|
||||||
let Some(game_id) = http_request.query_params().get("id") else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Some(game) = state.games.get_mut(game_id) else {
|
|
||||||
return Ok(http::send_response(
|
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
// send the other player an end game request
|
|
||||||
Request::new()
|
|
||||||
.target((game_id.as_str(), our.process.clone()))
|
|
||||||
.body(serde_json::to_vec(&ChessRequest::Resign(our.node.clone()))?)
|
|
||||||
.send()?;
|
|
||||||
game.ended = true;
|
|
||||||
let body = serde_json::to_vec(&game)?;
|
|
||||||
save_chess_state(&state);
|
|
||||||
http::send_response(
|
|
||||||
http::StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
String::from("Content-Type"),
|
|
||||||
String::from("application/json"),
|
|
||||||
)])),
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Any other method will be rejected.
|
|
||||||
_ => Ok(http::send_response(
|
|
||||||
http::StatusCode::METHOD_NOT_ALLOWED,
|
|
||||||
None,
|
None,
|
||||||
vec![],
|
);
|
||||||
)),
|
};
|
||||||
|
let Some(game_id) = blob_json["id"].as_str() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let player_white = blob_json["white"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or(state.our.node.as_str())
|
||||||
|
.to_string();
|
||||||
|
let player_black = blob_json["black"].as_str().unwrap_or(game_id).to_string();
|
||||||
|
|
||||||
|
match handle_local_request(
|
||||||
|
state,
|
||||||
|
&ChessRequest::NewGame(NewGameRequest {
|
||||||
|
white: player_white,
|
||||||
|
black: player_black,
|
||||||
|
}),
|
||||||
|
) {
|
||||||
|
Ok(game) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::OK)
|
||||||
|
.header("Content-Type", "application/json"),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/json".to_string()),
|
||||||
|
bytes: serde_json::to_vec(&game).expect("failed to serialize game!"),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/text".to_string()),
|
||||||
|
bytes: e.to_string().into_bytes(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On PUT: make a move
|
||||||
|
fn handle_put(state: &mut ChessState) -> (server::HttpResponse, Option<LazyLoadBlob>) {
|
||||||
|
let Some(blob) = get_blob() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Ok(blob_json) = serde_json::from_slice::<serde_json::Value>(&blob.bytes) else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(game_id) = blob_json["id"].as_str() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Some(move_str) = blob_json["move"].as_str() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
match handle_local_request(
|
||||||
|
state,
|
||||||
|
&ChessRequest::Move(MoveRequest {
|
||||||
|
game_id: game_id.to_string(),
|
||||||
|
move_str: move_str.to_string(),
|
||||||
|
}),
|
||||||
|
) {
|
||||||
|
Ok(game) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::OK)
|
||||||
|
.header("Content-Type", "application/json"),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/json".to_string()),
|
||||||
|
bytes: serde_json::to_vec(&game).expect("failed to serialize game!"),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/text".to_string()),
|
||||||
|
bytes: e.to_string().into_bytes(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On DELETE: end the game
|
||||||
|
fn handle_delete(
|
||||||
|
state: &mut ChessState,
|
||||||
|
request: &server::IncomingHttpRequest,
|
||||||
|
) -> (server::HttpResponse, Option<LazyLoadBlob>) {
|
||||||
|
let Some(game_id) = request.query_params().get("id") else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
match handle_local_request(state, &ChessRequest::Resign(game_id.to_string())) {
|
||||||
|
Ok(game) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::OK)
|
||||||
|
.header("Content-Type", "application/json"),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/json".to_string()),
|
||||||
|
bytes: serde_json::to_vec(&game).expect("failed to serialize game!"),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/text".to_string()),
|
||||||
|
bytes: e.to_string().into_bytes(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,7 @@
|
|||||||
"net:distro:sys",
|
"net:distro:sys",
|
||||||
"vfs:distro:sys"
|
"vfs:distro:sys"
|
||||||
],
|
],
|
||||||
"grant_capabilities": [
|
"grant_capabilities": [],
|
||||||
"http_server:distro:sys"
|
|
||||||
],
|
|
||||||
"public": true
|
"public": true
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -1,15 +1,10 @@
|
|||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
use crate::kinode::process::homepage::{AddRequest, Request as HomepageRequest};
|
use crate::kinode::process::homepage::{AddRequest, Request as HomepageRequest};
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, get_blob,
|
await_message, call_init, get_blob, http, http::server, println, Address, LazyLoadBlob,
|
||||||
http::{
|
|
||||||
bind_http_path, bind_http_static_path, send_response, serve_ui, HttpServerError,
|
|
||||||
HttpServerRequest, Method, StatusCode,
|
|
||||||
},
|
|
||||||
println, Address, Message,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Fetching OS version from main package.. LMK if there's a better way...
|
/// Fetching OS version from main package.. LMK if there's a better way...
|
||||||
const CARGO_TOML: &str = include_str!("../../../../Cargo.toml");
|
const CARGO_TOML: &str = include_str!("../../../../Cargo.toml");
|
||||||
@ -45,34 +40,42 @@ call_init!(init);
|
|||||||
fn init(our: Address) {
|
fn init(our: Address) {
|
||||||
let mut app_data: BTreeMap<String, HomepageApp> = BTreeMap::new();
|
let mut app_data: BTreeMap<String, HomepageApp> = BTreeMap::new();
|
||||||
|
|
||||||
serve_ui(&our, "ui", true, false, vec!["/"]).expect("failed to serve ui");
|
let mut http_server = server::HttpServer::new(5);
|
||||||
|
let http_config = server::HttpBindingConfig::default();
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/our",
|
.serve_ui(&our, "ui", http_config.clone())
|
||||||
false,
|
.expect("failed to serve ui");
|
||||||
false,
|
|
||||||
Some("text/html".to_string()),
|
|
||||||
our.node().into(),
|
|
||||||
)
|
|
||||||
.expect("failed to bind to /our");
|
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/amionline",
|
.bind_http_static_path(
|
||||||
false,
|
"/our",
|
||||||
false,
|
false,
|
||||||
Some("text/html".to_string()),
|
false,
|
||||||
"yes".into(),
|
Some("text/html".to_string()),
|
||||||
)
|
our.node().into(),
|
||||||
.expect("failed to bind to /amionline");
|
)
|
||||||
|
.expect("failed to bind to /our");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/our.js",
|
.bind_http_static_path(
|
||||||
false,
|
"/amionline",
|
||||||
false,
|
false,
|
||||||
Some("application/javascript".to_string()),
|
false,
|
||||||
format!("window.our = {{}}; window.our.node = '{}';", &our.node).into(),
|
Some("text/html".to_string()),
|
||||||
)
|
"yes".into(),
|
||||||
.expect("failed to bind to /our.js");
|
)
|
||||||
|
.expect("failed to bind to /amionline");
|
||||||
|
|
||||||
|
http_server
|
||||||
|
.bind_http_static_path(
|
||||||
|
"/our.js",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Some("application/javascript".to_string()),
|
||||||
|
format!("window.our = {{}}; window.our.node = '{}';", &our.node).into(),
|
||||||
|
)
|
||||||
|
.expect("failed to bind to /our.js");
|
||||||
|
|
||||||
// the base version gets written over on-bootstrap, so we look for
|
// the base version gets written over on-bootstrap, so we look for
|
||||||
// the persisted (user-customized) version first.
|
// the persisted (user-customized) version first.
|
||||||
@ -99,67 +102,156 @@ fn init(our: Address) {
|
|||||||
.write(&stylesheet)
|
.write(&stylesheet)
|
||||||
.expect("failed to write to /persisted-kinode.css");
|
.expect("failed to write to /persisted-kinode.css");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/kinode.css",
|
.bind_http_static_path(
|
||||||
false, // kinode.css is not auth'd so that apps on subdomains can use it too!
|
"/kinode.css",
|
||||||
false,
|
false, // kinode.css is not auth'd so that apps on subdomains can use it too!
|
||||||
Some("text/css".to_string()),
|
false,
|
||||||
stylesheet,
|
Some("text/css".to_string()),
|
||||||
)
|
stylesheet,
|
||||||
.expect("failed to bind /kinode.css");
|
)
|
||||||
|
.expect("failed to bind /kinode.css");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/kinode.svg",
|
.bind_http_static_path(
|
||||||
false, // kinode.svg is not auth'd so that apps on subdomains can use it too!
|
"/kinode.svg",
|
||||||
false,
|
false, // kinode.svg is not auth'd so that apps on subdomains can use it too!
|
||||||
Some("image/svg+xml".to_string()),
|
false,
|
||||||
include_str!("../../pkg/kinode.svg").into(),
|
Some("image/svg+xml".to_string()),
|
||||||
)
|
include_str!("../../pkg/kinode.svg").into(),
|
||||||
.expect("failed to bind /kinode.svg");
|
)
|
||||||
|
.expect("failed to bind /kinode.svg");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/bird-orange.svg",
|
.bind_http_static_path(
|
||||||
false, // bird-orange.svg is not auth'd so that apps on subdomains can use it too!
|
"/bird-orange.svg",
|
||||||
false,
|
false, // bird-orange.svg is not auth'd so that apps on subdomains can use it too!
|
||||||
Some("image/svg+xml".to_string()),
|
false,
|
||||||
include_str!("../../pkg/bird-orange.svg").into(),
|
Some("image/svg+xml".to_string()),
|
||||||
)
|
include_str!("../../pkg/bird-orange.svg").into(),
|
||||||
.expect("failed to bind /bird-orange.svg");
|
)
|
||||||
|
.expect("failed to bind /bird-orange.svg");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/bird-plain.svg",
|
.bind_http_static_path(
|
||||||
false, // bird-plain.svg is not auth'd so that apps on subdomains can use it too!
|
"/bird-plain.svg",
|
||||||
false,
|
false, // bird-plain.svg is not auth'd so that apps on subdomains can use it too!
|
||||||
Some("image/svg+xml".to_string()),
|
false,
|
||||||
include_str!("../../pkg/bird-plain.svg").into(),
|
Some("image/svg+xml".to_string()),
|
||||||
)
|
include_str!("../../pkg/bird-plain.svg").into(),
|
||||||
.expect("failed to bind /bird-plain.svg");
|
)
|
||||||
|
.expect("failed to bind /bird-plain.svg");
|
||||||
|
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/version",
|
.bind_http_static_path(
|
||||||
true,
|
"/version",
|
||||||
false,
|
true,
|
||||||
Some("text/plain".to_string()),
|
false,
|
||||||
version_from_cargo_toml().into(),
|
Some("text/plain".to_string()),
|
||||||
)
|
version_from_cargo_toml().into(),
|
||||||
.expect("failed to bind /version");
|
)
|
||||||
|
.expect("failed to bind /version");
|
||||||
|
|
||||||
bind_http_path("/apps", true, false).expect("failed to bind /apps");
|
http_server
|
||||||
bind_http_path("/favorite", true, false).expect("failed to bind /favorite");
|
.bind_http_path("/apps", http_config.clone())
|
||||||
bind_http_path("/order", true, false).expect("failed to bind /order");
|
.expect("failed to bind /apps");
|
||||||
|
http_server
|
||||||
|
.bind_http_path("/favorite", http_config.clone())
|
||||||
|
.expect("failed to bind /favorite");
|
||||||
|
http_server
|
||||||
|
.bind_http_path("/order", http_config)
|
||||||
|
.expect("failed to bind /order");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Ok(ref message) = await_message() else {
|
let Ok(ref message) = await_message() else {
|
||||||
// we never send requests, so this will never happen
|
// we never send requests, so this will never happen
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Message::Response { source, body, .. } = message
|
if message.source().process == "http_server:distro:sys" {
|
||||||
&& source.process == "http_server:distro:sys"
|
if message.is_request() {
|
||||||
{
|
let Ok(request) = http_server.parse_request(message.body()) else {
|
||||||
match serde_json::from_slice::<Result<(), HttpServerError>>(&body) {
|
continue;
|
||||||
Ok(Ok(())) => continue,
|
};
|
||||||
Ok(Err(e)) => println!("got error from http_server: {e}"),
|
http_server.handle_request(
|
||||||
Err(_e) => println!("got malformed message from http_server!"),
|
request,
|
||||||
|
|incoming| {
|
||||||
|
let path = incoming.bound_path(None);
|
||||||
|
match path {
|
||||||
|
"/apps" => (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
Some(LazyLoadBlob::new(
|
||||||
|
Some("application/json"),
|
||||||
|
serde_json::to_vec(
|
||||||
|
&app_data.values().collect::<Vec<&HomepageApp>>(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
"/favorite" => {
|
||||||
|
let Ok(http::Method::POST) = incoming.method() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(
|
||||||
|
http::StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Some(body) = get_blob() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Ok(favorite_toggle) =
|
||||||
|
serde_json::from_slice::<(String, bool)>(&body.bytes)
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if let Some(app) = app_data.get_mut(&favorite_toggle.0) {
|
||||||
|
app.favorite = favorite_toggle.1;
|
||||||
|
}
|
||||||
|
(server::HttpResponse::new(http::StatusCode::OK), None)
|
||||||
|
}
|
||||||
|
"/order" => {
|
||||||
|
let Ok(http::Method::POST) = incoming.method() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(
|
||||||
|
http::StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Some(body) = get_blob() else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let Ok(order_list) =
|
||||||
|
serde_json::from_slice::<Vec<(String, u32)>>(&body.bytes)
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
server::HttpResponse::new(http::StatusCode::BAD_REQUEST),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
for (app_id, order) in order_list {
|
||||||
|
if let Some(app) = app_data.get_mut(&app_id) {
|
||||||
|
app.order = order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(server::HttpResponse::new(http::StatusCode::OK), None)
|
||||||
|
}
|
||||||
|
_ => (server::HttpResponse::new(http::StatusCode::NOT_FOUND), None),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|_channel_id, _message_type, _message| {
|
||||||
|
// not expecting any websocket messages from FE currently
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle messages to add or remove an app from the homepage.
|
// handle messages to add or remove an app from the homepage.
|
||||||
@ -210,115 +302,18 @@ fn init(our: Address) {
|
|||||||
.write(new_stylesheet_string.as_bytes())
|
.write(new_stylesheet_string.as_bytes())
|
||||||
.expect("failed to write to /persisted-kinode.css");
|
.expect("failed to write to /persisted-kinode.css");
|
||||||
// re-bind
|
// re-bind
|
||||||
bind_http_static_path(
|
http_server
|
||||||
"/kinode.css",
|
.bind_http_static_path(
|
||||||
false, // kinode.css is not auth'd so that apps on subdomains can use it too!
|
"/kinode.css",
|
||||||
false,
|
false, // kinode.css is not auth'd so that apps on subdomains can use it too!
|
||||||
Some("text/css".to_string()),
|
false,
|
||||||
new_stylesheet_string.into(),
|
Some("text/css".to_string()),
|
||||||
)
|
new_stylesheet_string.into(),
|
||||||
.expect("failed to bind /kinode.css");
|
)
|
||||||
|
.expect("failed to bind /kinode.css");
|
||||||
println!("updated kinode.css!");
|
println!("updated kinode.css!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Ok(req) = serde_json::from_slice::<HttpServerRequest>(message.body()) {
|
|
||||||
match req {
|
|
||||||
HttpServerRequest::Http(incoming) => {
|
|
||||||
let path = incoming.bound_path(None);
|
|
||||||
match path {
|
|
||||||
"/apps" => {
|
|
||||||
send_response(
|
|
||||||
StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
"Content-Type".to_string(),
|
|
||||||
"application/json".to_string(),
|
|
||||||
)])),
|
|
||||||
serde_json::to_vec(
|
|
||||||
&app_data.values().collect::<Vec<&HomepageApp>>(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"/favorite" => {
|
|
||||||
let Ok(Method::POST) = incoming.method() else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(body) = get_blob() else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Ok(favorite_toggle) =
|
|
||||||
serde_json::from_slice::<(String, bool)>(&body.bytes)
|
|
||||||
else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let Some(app) = app_data.get_mut(&favorite_toggle.0) {
|
|
||||||
app.favorite = favorite_toggle.1;
|
|
||||||
}
|
|
||||||
send_response(
|
|
||||||
StatusCode::OK,
|
|
||||||
Some(HashMap::from([(
|
|
||||||
"Content-Type".to_string(),
|
|
||||||
"application/json".to_string(),
|
|
||||||
)])),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"/order" => {
|
|
||||||
let Ok(Method::POST) = incoming.method() else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(body) = get_blob() else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Ok(order_list) =
|
|
||||||
serde_json::from_slice::<Vec<(String, u32)>>(&body.bytes)
|
|
||||||
else {
|
|
||||||
send_response(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Some(HashMap::new()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
for (app_id, order) in order_list {
|
|
||||||
if let Some(app) = app_data.get_mut(&app_id) {
|
|
||||||
app.order = order;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
send_response(StatusCode::OK, Some(HashMap::new()), vec![]);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
send_response(StatusCode::NOT_FOUND, Some(HashMap::new()), vec![]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,15 +303,12 @@ fn handle_log(our: &Address, state: &mut State, log: ð::Log) -> anyhow::Resul
|
|||||||
return Err(anyhow::anyhow!("skipping invalid entry"));
|
return Err(anyhow::anyhow!("skipping invalid entry"));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("got parent hash: {parent_hash}, child hash: {child_hash}, name: {name}");
|
|
||||||
|
|
||||||
let full_name = match get_parent_name(&state.names, &parent_hash) {
|
let full_name = match get_parent_name(&state.names, &parent_hash) {
|
||||||
Some(parent_name) => format!("{name}.{parent_name}"),
|
Some(parent_name) => format!("{name}.{parent_name}"),
|
||||||
None => name,
|
None => name,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.names.insert(child_hash.clone(), full_name.clone());
|
state.names.insert(child_hash.clone(), full_name.clone());
|
||||||
println!("inserted child hash: {child_hash}, with full name: {full_name}");
|
|
||||||
state.nodes.insert(
|
state.nodes.insert(
|
||||||
full_name.clone(),
|
full_name.clone(),
|
||||||
net::KnsUpdate {
|
net::KnsUpdate {
|
||||||
|
@ -3,7 +3,7 @@ use kinode_process_lib::{
|
|||||||
LazyLoadBlob, Message, NodeId, ProcessId, Request, Response, SendError, SendErrorKind,
|
LazyLoadBlob, Message, NodeId, ProcessId, Request, Response, SendError, SendErrorKind,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
|
||||||
const ICON: &str = include_str!("icon");
|
const ICON: &str = include_str!("icon");
|
||||||
@ -42,7 +42,6 @@ enum SettingsError {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct SettingsState {
|
struct SettingsState {
|
||||||
pub our: Address,
|
pub our: Address,
|
||||||
pub ws_clients: HashSet<u32>,
|
|
||||||
pub identity: Option<net::Identity>,
|
pub identity: Option<net::Identity>,
|
||||||
pub diagnostics: Option<String>,
|
pub diagnostics: Option<String>,
|
||||||
pub eth_rpc_providers: Option<eth::SavedConfigs>,
|
pub eth_rpc_providers: Option<eth::SavedConfigs>,
|
||||||
@ -55,7 +54,6 @@ impl SettingsState {
|
|||||||
fn new(our: Address) -> Self {
|
fn new(our: Address) -> Self {
|
||||||
Self {
|
Self {
|
||||||
our,
|
our,
|
||||||
ws_clients: HashSet::new(),
|
|
||||||
identity: None,
|
identity: None,
|
||||||
diagnostics: None,
|
diagnostics: None,
|
||||||
eth_rpc_providers: None,
|
eth_rpc_providers: None,
|
||||||
@ -65,17 +63,15 @@ impl SettingsState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ws_update(&self) {
|
fn ws_update(&self, http_server: &http::server::HttpServer) {
|
||||||
for channel in &self.ws_clients {
|
http_server.ws_push_all_channels(
|
||||||
http::send_ws_push(
|
"/",
|
||||||
*channel,
|
http::server::WsMessageType::Text,
|
||||||
http::WsMessageType::Text,
|
LazyLoadBlob {
|
||||||
LazyLoadBlob {
|
mime: Some("application/json".to_string()),
|
||||||
mime: Some("application/json".to_string()),
|
bytes: serde_json::to_vec(self).unwrap(),
|
||||||
bytes: serde_json::to_vec(self).unwrap(),
|
},
|
||||||
},
|
)
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get data that the settings page presents to user
|
/// get data that the settings page presents to user
|
||||||
@ -182,18 +178,27 @@ fn initialize(our: Address) {
|
|||||||
// add ourselves to the homepage
|
// add ourselves to the homepage
|
||||||
homepage::add_to_homepage("Settings", Some(ICON), Some("/"), None);
|
homepage::add_to_homepage("Settings", Some(ICON), Some("/"), None);
|
||||||
|
|
||||||
// Serve the index.html and other UI files found in pkg/ui at the root path.
|
|
||||||
// Serving securely at `settings-sys` subdomain
|
|
||||||
http::secure_serve_ui(&our, "ui", vec!["/"]).unwrap();
|
|
||||||
http::secure_bind_http_path("/ask").unwrap();
|
|
||||||
http::secure_bind_ws_path("/", false).unwrap();
|
|
||||||
|
|
||||||
// Grab our state, then enter the main event loop.
|
// Grab our state, then enter the main event loop.
|
||||||
let mut state: SettingsState = SettingsState::new(our);
|
let mut state: SettingsState = SettingsState::new(our);
|
||||||
main_loop(&mut state);
|
|
||||||
|
let mut http_server = http::server::HttpServer::new(5);
|
||||||
|
|
||||||
|
// Serve the index.html and other UI files found in pkg/ui at the root path.
|
||||||
|
// Serving securely at `settings-sys` subdomain
|
||||||
|
http_server
|
||||||
|
.serve_ui(
|
||||||
|
&state.our,
|
||||||
|
"ui",
|
||||||
|
http::server::HttpBindingConfig::default().secure_subdomain(true),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
http_server.secure_bind_http_path("/ask").unwrap();
|
||||||
|
http_server.secure_bind_ws_path("/").unwrap();
|
||||||
|
|
||||||
|
main_loop(&mut state, &mut http_server);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(state: &mut SettingsState) {
|
fn main_loop(state: &mut SettingsState, http_server: &mut http::server::HttpServer) {
|
||||||
loop {
|
loop {
|
||||||
match await_message() {
|
match await_message() {
|
||||||
Err(send_error) => {
|
Err(send_error) => {
|
||||||
@ -209,7 +214,8 @@ fn main_loop(state: &mut SettingsState) {
|
|||||||
if source.node() != state.our.node {
|
if source.node() != state.our.node {
|
||||||
continue; // ignore messages from other nodes
|
continue; // ignore messages from other nodes
|
||||||
}
|
}
|
||||||
let response = handle_request(&source, &body, state);
|
let response = handle_request(&source, &body, state, http_server);
|
||||||
|
state.ws_update(http_server);
|
||||||
if expects_response.is_some() {
|
if expects_response.is_some() {
|
||||||
Response::new()
|
Response::new()
|
||||||
.body(serde_json::to_vec(&response).unwrap())
|
.body(serde_json::to_vec(&response).unwrap())
|
||||||
@ -222,42 +228,45 @@ fn main_loop(state: &mut SettingsState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(source: &Address, body: &[u8], state: &mut SettingsState) -> SettingsResponse {
|
fn handle_request(
|
||||||
|
source: &Address,
|
||||||
|
body: &[u8],
|
||||||
|
state: &mut SettingsState,
|
||||||
|
http_server: &mut http::server::HttpServer,
|
||||||
|
) -> SettingsResponse {
|
||||||
// source node is ALWAYS ourselves since networking is disabled
|
// source node is ALWAYS ourselves since networking is disabled
|
||||||
if source.process == "http_server:distro:sys" {
|
if source.process == "http_server:distro:sys" {
|
||||||
// receive HTTP requests and websocket connection messages from our server
|
// receive HTTP requests and websocket connection messages from our server
|
||||||
match serde_json::from_slice::<http::HttpServerRequest>(body)
|
let server_request = http_server
|
||||||
.map_err(|_| SettingsError::MalformedRequest)?
|
.parse_request(body)
|
||||||
{
|
.map_err(|_| SettingsError::MalformedRequest)?;
|
||||||
http::HttpServerRequest::Http(ref incoming) => {
|
|
||||||
match handle_http_request(state, incoming) {
|
http_server.handle_request(
|
||||||
Ok(()) => Ok(None),
|
server_request,
|
||||||
|
|req| {
|
||||||
|
let result = handle_http_request(state, &req);
|
||||||
|
match result {
|
||||||
|
Ok((resp, blob)) => (resp, blob),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("error handling HTTP request: {e}");
|
println!("error handling HTTP request: {e}");
|
||||||
http::send_response(
|
(
|
||||||
http::StatusCode::INTERNAL_SERVER_ERROR,
|
http::server::HttpResponse {
|
||||||
None,
|
status: 500,
|
||||||
"Service Unavailable".to_string().as_bytes().to_vec(),
|
headers: HashMap::new(),
|
||||||
);
|
},
|
||||||
Ok(None)
|
Some(LazyLoadBlob {
|
||||||
|
mime: Some("application/text".to_string()),
|
||||||
|
bytes: e.to_string().as_bytes().to_vec(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
http::HttpServerRequest::WebSocketOpen { channel_id, .. } => {
|
|_channel_id, _message_type, _blob| {
|
||||||
state.ws_clients.insert(channel_id);
|
// we don't expect websocket messages
|
||||||
Ok(None)
|
},
|
||||||
}
|
);
|
||||||
http::HttpServerRequest::WebSocketClose(channel_id) => {
|
Ok(None)
|
||||||
// client frontend closed a websocket
|
|
||||||
state.ws_clients.remove(&channel_id);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
http::HttpServerRequest::WebSocketPush { .. } => {
|
|
||||||
// client frontend sent a websocket message
|
|
||||||
// we don't expect this! we only use websockets to push updates
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let settings_request = serde_json::from_slice::<SettingsRequest>(body)
|
let settings_request = serde_json::from_slice::<SettingsRequest>(body)
|
||||||
.map_err(|_| SettingsError::MalformedRequest)?;
|
.map_err(|_| SettingsError::MalformedRequest)?;
|
||||||
@ -268,45 +277,46 @@ fn handle_request(source: &Address, body: &[u8], state: &mut SettingsState) -> S
|
|||||||
/// Handle HTTP requests from our own frontend.
|
/// Handle HTTP requests from our own frontend.
|
||||||
fn handle_http_request(
|
fn handle_http_request(
|
||||||
state: &mut SettingsState,
|
state: &mut SettingsState,
|
||||||
http_request: &http::IncomingHttpRequest,
|
http_request: &http::server::IncomingHttpRequest,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<(http::server::HttpResponse, Option<LazyLoadBlob>)> {
|
||||||
match http_request.method()?.as_str() {
|
match http_request.method()?.as_str() {
|
||||||
"GET" => {
|
"GET" => {
|
||||||
state.fetch()?;
|
state.fetch()?;
|
||||||
Ok(http::send_response(
|
Ok((
|
||||||
http::StatusCode::OK,
|
http::server::HttpResponse::new(http::StatusCode::OK)
|
||||||
Some(HashMap::from([(
|
.header("Content-Type", "application/json"),
|
||||||
String::from("Content-Type"),
|
Some(LazyLoadBlob::new(
|
||||||
String::from("application/json"),
|
Some("application/json"),
|
||||||
)])),
|
serde_json::to_vec(&state)?,
|
||||||
serde_json::to_vec(&state)?,
|
)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
"POST" => {
|
"POST" => {
|
||||||
let Some(blob) = get_blob() else {
|
let Some(blob) = get_blob() else {
|
||||||
return Ok(http::send_response(
|
return Err(anyhow::anyhow!("malformed request"));
|
||||||
http::StatusCode::BAD_REQUEST,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?;
|
let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?;
|
||||||
let response = handle_settings_request(state, request);
|
let response = handle_settings_request(state, request);
|
||||||
Ok(http::send_response(
|
Ok((
|
||||||
http::StatusCode::OK,
|
http::server::HttpResponse::new(http::StatusCode::OK)
|
||||||
None,
|
.header("Content-Type", "application/json"),
|
||||||
match response {
|
match response {
|
||||||
Ok(Some(data)) => serde_json::to_vec(&data)?,
|
Ok(Some(data)) => Some(LazyLoadBlob::new(
|
||||||
Ok(None) => "null".as_bytes().to_vec(),
|
Some("application/json"),
|
||||||
Err(e) => serde_json::to_vec(&e)?,
|
serde_json::to_vec(&data)?,
|
||||||
|
)),
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(e) => Some(LazyLoadBlob::new(
|
||||||
|
Some("application/json"),
|
||||||
|
serde_json::to_vec(&e)?,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// Any other method will be rejected.
|
// Any other method will be rejected.
|
||||||
_ => Ok(http::send_response(
|
_ => Ok((
|
||||||
http::StatusCode::METHOD_NOT_ALLOWED,
|
http::server::HttpResponse::new(http::StatusCode::METHOD_NOT_ALLOWED),
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -421,12 +431,10 @@ fn handle_settings_request(
|
|||||||
.send()
|
.send()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state.stylesheet = Some(stylesheet);
|
state.stylesheet = Some(stylesheet);
|
||||||
state.ws_update();
|
|
||||||
return SettingsResponse::Ok(None);
|
return SettingsResponse::Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.fetch().map_err(|_| SettingsError::StateFetchFailed)?;
|
state.fetch().map_err(|_| SettingsError::StateFetchFailed)?;
|
||||||
state.ws_update();
|
|
||||||
SettingsResponse::Ok(None)
|
SettingsResponse::Ok(None)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user