mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 23:27:14 +03:00
wip full new kit install downloads flow
This commit is contained in:
parent
beeae033c9
commit
6753de511a
@ -11,6 +11,7 @@ interface downloads {
|
|||||||
progress(progress-update),
|
progress(progress-update),
|
||||||
size(size-update),
|
size(size-update),
|
||||||
get-files(option<package-id>),
|
get-files(option<package-id>),
|
||||||
|
add-download(add-download-request),
|
||||||
}
|
}
|
||||||
|
|
||||||
record download-request {
|
record download-request {
|
||||||
@ -45,6 +46,13 @@ interface downloads {
|
|||||||
files: list<entry>,
|
files: list<entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// part of new-package-request local-only flow.
|
||||||
|
record add-download-request {
|
||||||
|
package-id: package-id,
|
||||||
|
version-hash: string,
|
||||||
|
mirror: bool,
|
||||||
|
}
|
||||||
|
|
||||||
record progress-update {
|
record progress-update {
|
||||||
package-id: package-id,
|
package-id: package-id,
|
||||||
version-hash: string,
|
version-hash: string,
|
||||||
@ -115,6 +123,13 @@ interface main {
|
|||||||
use chain.{on-chain-metadata};
|
use chain.{on-chain-metadata};
|
||||||
|
|
||||||
|
|
||||||
|
// general error type used by both chain: and downloads:
|
||||||
|
// todo: add variant for types to match on
|
||||||
|
|
||||||
|
record error {
|
||||||
|
reason: string,
|
||||||
|
}
|
||||||
|
|
||||||
variant request {
|
variant request {
|
||||||
local(local-request),
|
local(local-request),
|
||||||
}
|
}
|
||||||
@ -138,7 +153,7 @@ interface main {
|
|||||||
|
|
||||||
variant local-request {
|
variant local-request {
|
||||||
new-package(new-package-request),
|
new-package(new-package-request),
|
||||||
install(package-id),
|
install(install-package-request),
|
||||||
uninstall(package-id),
|
uninstall(package-id),
|
||||||
apis,
|
apis,
|
||||||
get-api(package-id),
|
get-api(package-id),
|
||||||
@ -146,9 +161,14 @@ interface main {
|
|||||||
|
|
||||||
record new-package-request {
|
record new-package-request {
|
||||||
package-id: package-id,
|
package-id: package-id,
|
||||||
metadata: on-chain-metadata,
|
|
||||||
mirror: bool,
|
mirror: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record install-package-request {
|
||||||
|
package-id: package-id,
|
||||||
|
metadata: option<on-chain-metadata>, // if None == local sideload package.
|
||||||
|
version-hash: string,
|
||||||
|
}
|
||||||
|
|
||||||
variant local-response {
|
variant local-response {
|
||||||
new-package-response(new-package-response),
|
new-package-response(new-package-response),
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
kinode::process::downloads::{DownloadRequest, DownloadResponse},
|
kinode::process::downloads::{DownloadRequest, DownloadResponse, Downloads},
|
||||||
|
kinode::process::main::Error,
|
||||||
state::{MirrorCheck, PackageState, State},
|
state::{MirrorCheck, PackageState, State},
|
||||||
|
Resp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
http::server,
|
http::server,
|
||||||
http::{self, Method, StatusCode},
|
http::{self, Method, StatusCode},
|
||||||
Address, LazyLoadBlob, NodeId, PackageId, Request,
|
Address, LazyLoadBlob, PackageId, Request,
|
||||||
};
|
};
|
||||||
use kinode_process_lib::{SendError, SendErrorKind};
|
use kinode_process_lib::{SendError, SendErrorKind};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -206,9 +208,15 @@ pub fn handle_http_request(
|
|||||||
bytes: body,
|
bytes: body,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Err(_e) => (
|
Err(e) => (
|
||||||
server::HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR),
|
server::HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
None,
|
Some(LazyLoadBlob {
|
||||||
|
mime: None,
|
||||||
|
bytes: serde_json::to_vec(&Error {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,6 +230,13 @@ fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<Packag
|
|||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_version_hash(url_params: &HashMap<String, String>) -> anyhow::Result<String> {
|
||||||
|
let Some(version_hash) = url_params.get("version_hash") else {
|
||||||
|
return Err(anyhow::anyhow!("Missing version_hash"));
|
||||||
|
};
|
||||||
|
Ok(version_hash.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_package_info(id: &PackageId, state: &PackageState) -> serde_json::Value {
|
fn gen_package_info(id: &PackageId, state: &PackageState) -> serde_json::Value {
|
||||||
// installed package.
|
// installed package.
|
||||||
// on-chain info combined somewhere else.
|
// on-chain info combined somewhere else.
|
||||||
@ -265,20 +280,50 @@ fn serve_paths(
|
|||||||
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
|
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
|
||||||
}
|
}
|
||||||
"/downloads" => {
|
"/downloads" => {
|
||||||
// TODO
|
// get all local downloads!
|
||||||
Ok((StatusCode::OK, None, vec![]))
|
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||||
|
let resp = Request::new()
|
||||||
|
.target(downloads)
|
||||||
|
.body(serde_json::to_vec(&Downloads::GetFiles(None))?)
|
||||||
|
.send_and_await_response(5)??;
|
||||||
|
|
||||||
|
let msg = serde_json::from_slice::<Resp>(resp.body())?;
|
||||||
|
println!("downlaods response: {:?}", msg);
|
||||||
|
// shouldn't really return status code
|
||||||
|
Ok((StatusCode::OK, None, resp.body().to_vec()))
|
||||||
}
|
}
|
||||||
"/installed" => {
|
"/installed" => {
|
||||||
// TODO
|
// TODO format json for UI
|
||||||
Ok((StatusCode::OK, None, vec![]))
|
println!("/installed response: {:?}", state.packages);
|
||||||
|
|
||||||
|
let body = serde_json::to_vec(&state.packages)?;
|
||||||
|
Ok((StatusCode::OK, None, body))
|
||||||
}
|
}
|
||||||
"/ourapps" => {
|
"/ourapps" => {
|
||||||
// TODO
|
// TODO, fetch from state
|
||||||
Ok((StatusCode::OK, None, vec![]))
|
Ok((StatusCode::OK, None, vec![]))
|
||||||
}
|
}
|
||||||
"/downloads/:id" => {
|
"/downloads/:id" => {
|
||||||
// TODO
|
// get downloads for a specific app
|
||||||
Ok((StatusCode::OK, None, vec![]))
|
let Ok(package_id) = get_package_id(url_params) else {
|
||||||
|
return Ok((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
None,
|
||||||
|
format!("Missing id").into_bytes(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
// fix
|
||||||
|
let package_id = crate::kinode::process::main::PackageId::from_process_lib(package_id);
|
||||||
|
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||||
|
let resp = Request::new()
|
||||||
|
.target(downloads)
|
||||||
|
.body(serde_json::to_vec(&Downloads::GetFiles(Some(package_id)))?)
|
||||||
|
.send_and_await_response(5)??;
|
||||||
|
|
||||||
|
let msg = serde_json::from_slice::<Resp>(resp.body())?;
|
||||||
|
println!("downlaods response: {:?}", msg);
|
||||||
|
// shouldn't really return status code
|
||||||
|
Ok((StatusCode::OK, None, resp.body().to_vec()))
|
||||||
}
|
}
|
||||||
// GET detail about a specific app
|
// GET detail about a specific app
|
||||||
// update a downloaded app: PUT
|
// update a downloaded app: PUT
|
||||||
@ -367,7 +412,8 @@ fn serve_paths(
|
|||||||
.body(serde_json::to_vec(&download_request).unwrap())
|
.body(serde_json::to_vec(&download_request).unwrap())
|
||||||
.send_and_await_response(5)??;
|
.send_and_await_response(5)??;
|
||||||
|
|
||||||
let response: DownloadResponse = serde_json::from_slice(&response.body())?;
|
let response: Resp = serde_json::from_slice(&response.body())?;
|
||||||
|
println!("got download response: {:?}", response);
|
||||||
Ok((StatusCode::OK, None, serde_json::to_vec(&response)?))
|
Ok((StatusCode::OK, None, serde_json::to_vec(&response)?))
|
||||||
}
|
}
|
||||||
// POST /apps/:id/update
|
// POST /apps/:id/update
|
||||||
@ -383,7 +429,7 @@ fn serve_paths(
|
|||||||
|
|
||||||
// TODO send to downloads:
|
// TODO send to downloads:
|
||||||
// // actually just install something, we go get it from vfs or ask for it from downloads?
|
// // actually just install something, we go get it from vfs or ask for it from downloads?
|
||||||
|
// yeah this should be obsolete
|
||||||
match method {
|
match method {
|
||||||
_ => Ok((
|
_ => Ok((
|
||||||
StatusCode::METHOD_NOT_ALLOWED,
|
StatusCode::METHOD_NOT_ALLOWED,
|
||||||
@ -403,7 +449,24 @@ fn serve_paths(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
match crate::utils::install(&package_id, &our.to_string()) {
|
let Ok(version_hash) = get_version_hash(url_params) else {
|
||||||
|
return Ok((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
None,
|
||||||
|
format!("Missing version_hash").into_bytes(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let process_package_id =
|
||||||
|
crate::kinode::process::main::PackageId::from_process_lib(package_id);
|
||||||
|
|
||||||
|
match crate::utils::install(
|
||||||
|
&process_package_id,
|
||||||
|
None,
|
||||||
|
&version_hash,
|
||||||
|
state,
|
||||||
|
&our.to_string(),
|
||||||
|
) {
|
||||||
Ok(_) => Ok((StatusCode::CREATED, None, vec![])),
|
Ok(_) => Ok((StatusCode::CREATED, None, vec![])),
|
||||||
Err(e) => Ok((
|
Err(e) => Ok((
|
||||||
StatusCode::SERVICE_UNAVAILABLE,
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
//! - set to automatically update if a new version is available
|
//! - set to automatically update if a new version is available
|
||||||
use crate::kinode::process::downloads::{DownloadResponse, ProgressUpdate};
|
use crate::kinode::process::downloads::{DownloadResponse, ProgressUpdate};
|
||||||
use crate::kinode::process::main::{
|
use crate::kinode::process::main::{
|
||||||
ApisResponse, GetApiResponse, InstallResponse, LocalRequest, LocalResponse, NewPackageRequest,
|
ApisResponse, Error, GetApiResponse, InstallPackageRequest, InstallResponse, LocalRequest,
|
||||||
NewPackageResponse, UninstallResponse,
|
LocalResponse, NewPackageRequest, NewPackageResponse, UninstallResponse,
|
||||||
};
|
};
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, get_blob, http, println, vfs, Address, LazyLoadBlob, Message,
|
await_message, call_init, get_blob, http, println, vfs, Address, LazyLoadBlob, Message,
|
||||||
@ -56,6 +56,7 @@ pub enum Req {
|
|||||||
pub enum Resp {
|
pub enum Resp {
|
||||||
LocalResponse(LocalResponse),
|
LocalResponse(LocalResponse),
|
||||||
Download(DownloadResponse),
|
Download(DownloadResponse),
|
||||||
|
Err(Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
call_init!(init);
|
call_init!(init);
|
||||||
@ -118,6 +119,31 @@ fn handle_message(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Req::Progress(progress) => {
|
||||||
|
if !message.is_local(&our) {
|
||||||
|
return Err(anyhow::anyhow!("http_server from non-local node"));
|
||||||
|
}
|
||||||
|
println!("progress: {:?}", progress);
|
||||||
|
http_server.ws_push_all_channels(
|
||||||
|
"/",
|
||||||
|
http::server::WsMessageType::Text,
|
||||||
|
LazyLoadBlob {
|
||||||
|
mime: Some("application/json".to_string()),
|
||||||
|
bytes: serde_json::json!({
|
||||||
|
"kind": "progress",
|
||||||
|
"data": {
|
||||||
|
"package_id": progress.package_id,
|
||||||
|
"version_hash": progress.version_hash,
|
||||||
|
"downloaded": progress.downloaded,
|
||||||
|
"total": progress.total,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -140,11 +166,8 @@ fn handle_local_request(
|
|||||||
request: LocalRequest,
|
request: LocalRequest,
|
||||||
) -> (LocalResponse, Option<LazyLoadBlob>) {
|
) -> (LocalResponse, Option<LazyLoadBlob>) {
|
||||||
match request {
|
match request {
|
||||||
LocalRequest::NewPackage(NewPackageRequest {
|
LocalRequest::NewPackage(NewPackageRequest { package_id, mirror }) => {
|
||||||
package_id,
|
// note, use metadata and mirror?
|
||||||
metadata,
|
|
||||||
mirror,
|
|
||||||
}) => {
|
|
||||||
let Some(blob) = get_blob() else {
|
let Some(blob) = get_blob() else {
|
||||||
return (
|
return (
|
||||||
LocalResponse::NewPackageResponse(NewPackageResponse::NoBlob),
|
LocalResponse::NewPackageResponse(NewPackageResponse::NoBlob),
|
||||||
@ -152,16 +175,29 @@ fn handle_local_request(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
match utils::new_package(&package_id.to_process_lib(), state, blob.bytes) {
|
match utils::new_package(package_id, mirror, blob.bytes) {
|
||||||
Ok(()) => LocalResponse::NewPackageResponse(NewPackageResponse::Success),
|
Ok(()) => LocalResponse::NewPackageResponse(NewPackageResponse::Success),
|
||||||
Err(_) => LocalResponse::NewPackageResponse(NewPackageResponse::InstallFailed),
|
Err(_) => LocalResponse::NewPackageResponse(NewPackageResponse::InstallFailed),
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LocalRequest::Install(package_id) => (
|
LocalRequest::Install(InstallPackageRequest {
|
||||||
match utils::install(&package_id.to_process_lib(), &our.to_string()) {
|
package_id,
|
||||||
Ok(()) => LocalResponse::InstallResponse(InstallResponse::Success),
|
metadata,
|
||||||
|
version_hash,
|
||||||
|
}) => (
|
||||||
|
match utils::install(
|
||||||
|
&package_id,
|
||||||
|
metadata,
|
||||||
|
&version_hash,
|
||||||
|
state,
|
||||||
|
&our.to_string(),
|
||||||
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
println!("successfully installed package:");
|
||||||
|
LocalResponse::InstallResponse(InstallResponse::Success)
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("error installing package: {e}");
|
println!("error installing package: {e}");
|
||||||
LocalResponse::InstallResponse(InstallResponse::Failure)
|
LocalResponse::InstallResponse(InstallResponse::Failure)
|
||||||
|
@ -77,31 +77,6 @@ impl State {
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// if package_bytes is None, we already have the package downloaded
|
|
||||||
/// in VFS and this is being called to rebuild our process state
|
|
||||||
pub fn add_downloaded_package(
|
|
||||||
&mut self,
|
|
||||||
package_id: &PackageId,
|
|
||||||
mut package_state: PackageState,
|
|
||||||
package_zip_bytes: Option<Vec<u8>>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let Some(listing) = self.packages.get_mut(package_id) else {
|
|
||||||
return Err(anyhow::anyhow!("package not found"));
|
|
||||||
};
|
|
||||||
// if passed zip bytes, make drive
|
|
||||||
if let Some(package_bytes) = package_zip_bytes {
|
|
||||||
let manifest_hash = utils::create_package_drive(package_id, package_bytes)?;
|
|
||||||
package_state.manifest_hash = Some(manifest_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(extracted) = utils::extract_api(package_id) {
|
|
||||||
if extracted {
|
|
||||||
self.installed_apis.insert(package_id.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// returns True if the package was found and updated, False otherwise
|
// /// returns True if the package was found and updated, False otherwise
|
||||||
// pub fn update_downloaded_package(
|
// pub fn update_downloaded_package(
|
||||||
// &mut self,
|
// &mut self,
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
use {
|
use {
|
||||||
crate::state::{PackageState, State},
|
crate::{
|
||||||
crate::VFS_TIMEOUT,
|
kinode::process::{
|
||||||
|
chain::{Chains, GetAppResponse, OnChainMetadata},
|
||||||
|
downloads::{AddDownloadRequest, DownloadResponse, Downloads},
|
||||||
|
},
|
||||||
|
state::{PackageState, State},
|
||||||
|
VFS_TIMEOUT,
|
||||||
|
},
|
||||||
kinode_process_lib::{
|
kinode_process_lib::{
|
||||||
get_blob, kernel_types as kt, println, vfs, Address, LazyLoadBlob, PackageId, ProcessId,
|
get_blob, kernel_types as kt, println, vfs, Address, LazyLoadBlob, PackageId, ProcessId,
|
||||||
Request,
|
Request,
|
||||||
},
|
},
|
||||||
std::collections::HashSet,
|
std::{collections::HashSet, str::FromStr},
|
||||||
};
|
};
|
||||||
|
|
||||||
// quite annoyingly, we must convert from our gen'd version of PackageId
|
// quite annoyingly, we must convert from our gen'd version of PackageId
|
||||||
@ -67,38 +73,66 @@ pub fn fetch_package_manifest(
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_package_metadata(package_id: &PackageId) -> anyhow::Result<kt::Erc721Metadata> {
|
pub fn fetch_package_metadata(
|
||||||
vfs_request(
|
package_id: &crate::kinode::process::main::PackageId,
|
||||||
format!("/{package_id}/pkg/metadata.json"), // OK BIG todo: figure out why tf this is not here...
|
) -> anyhow::Result<OnChainMetadata> {
|
||||||
vfs::VfsAction::Read,
|
let chain = Address::from_str("our@chain:app_store:sys")?;
|
||||||
)
|
let resp = Request::new()
|
||||||
.send_and_await_response(VFS_TIMEOUT)??;
|
.target(chain)
|
||||||
let Some(blob) = get_blob() else {
|
.body(serde_json::to_vec(&Chains::GetApp(package_id.clone())).unwrap())
|
||||||
return Err(anyhow::anyhow!("no blob"));
|
.send_and_await_response(5)??;
|
||||||
|
|
||||||
|
let resp = serde_json::from_slice::<GetAppResponse>(&resp.body())?;
|
||||||
|
let app = match resp.app {
|
||||||
|
Some(app) => app,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No app data found in response from chain:app_store:sys"
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(serde_json::from_slice::<kt::Erc721Metadata>(&blob.bytes)?)
|
let metadata = match app.metadata {
|
||||||
|
Some(metadata) => metadata,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No metadata found in response from chain:app_store:sys"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_package(
|
pub fn new_package(
|
||||||
package_id: &PackageId,
|
package_id: crate::kinode::process::main::PackageId,
|
||||||
state: &mut State,
|
mirror: bool,
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// add to listings
|
|
||||||
|
|
||||||
// TODO: add? to uhh listings at all?
|
|
||||||
// state.insert_package(package_id, metadata);
|
|
||||||
|
|
||||||
// set the version hash for this new local package
|
// set the version hash for this new local package
|
||||||
let our_version_hash = sha_256_hash(&bytes);
|
let version_hash = sha_256_hash(&bytes);
|
||||||
|
|
||||||
let package_state = PackageState {
|
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||||
our_version_hash,
|
let resp = Request::new()
|
||||||
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
|
.target(downloads)
|
||||||
caps_approved: true, // TODO see if we want to auto-approve local installs
|
.body(serde_json::to_vec(&Downloads::AddDownload(
|
||||||
manifest_hash: None, // generated in the add fn
|
AddDownloadRequest {
|
||||||
|
package_id: package_id.clone(),
|
||||||
|
version_hash: version_hash.clone(),
|
||||||
|
mirror,
|
||||||
|
},
|
||||||
|
))?)
|
||||||
|
.blob_bytes(bytes)
|
||||||
|
.send_and_await_response(5)??;
|
||||||
|
|
||||||
|
let download_resp = serde_json::from_slice::<DownloadResponse>(&resp.body())?;
|
||||||
|
println!("got download resp: {:?}", download_resp);
|
||||||
|
if !download_resp.success {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"failed to add download: {:?}",
|
||||||
|
download_resp.error
|
||||||
|
));
|
||||||
};
|
};
|
||||||
state.add_downloaded_package(&package_id, package_state, Some(bytes))
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create a new package drive in VFS and add the package zip to it.
|
/// create a new package drive in VFS and add the package zip to it.
|
||||||
@ -138,6 +172,7 @@ pub fn create_package_drive(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// be careful, this is technically a duplicate.. but..
|
||||||
// save the zip file itself in VFS for sharing with other nodes
|
// save the zip file itself in VFS for sharing with other nodes
|
||||||
// call it <package_id>.zip
|
// call it <package_id>.zip
|
||||||
let zip_path = format!("{}/{}.zip", drive_name, package_id);
|
let zip_path = format!("{}/{}.zip", drive_name, package_id);
|
||||||
@ -174,7 +209,8 @@ pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// given a `PackageId`, interact with VFS and kernel to get manifest,
|
/// given a `PackageId`, interact with VFS and kernel to get {package_hash}.zip,
|
||||||
|
/// unzip the manifest and pkg,
|
||||||
/// grant the capabilities in manifest, then initialize and start
|
/// grant the capabilities in manifest, then initialize and start
|
||||||
/// the processes in manifest.
|
/// the processes in manifest.
|
||||||
///
|
///
|
||||||
@ -182,13 +218,49 @@ pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {
|
|||||||
/// which we can only do if we were the process to create that drive.
|
/// which we can only do if we were the process to create that drive.
|
||||||
/// note also that each capability will only be granted if we, the process
|
/// note also that each capability will only be granted if we, the process
|
||||||
/// using this function, own that capability ourselves.
|
/// using this function, own that capability ourselves.
|
||||||
pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
pub fn install(
|
||||||
// get the package manifest
|
package_id: &crate::kinode::process::main::PackageId,
|
||||||
let drive_path = format!("/{package_id}/pkg");
|
metadata: Option<OnChainMetadata>,
|
||||||
let manifest = fetch_package_manifest(package_id)?;
|
version_hash: &str,
|
||||||
|
state: &mut State,
|
||||||
|
our_node: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let process_package_id = package_id.clone().to_process_lib();
|
||||||
|
let file = vfs::open_file(
|
||||||
|
&format!("/app_store:sys/downloads/{process_package_id}/{version_hash}.zip"),
|
||||||
|
false,
|
||||||
|
Some(VFS_TIMEOUT),
|
||||||
|
)?;
|
||||||
|
let bytes = file.read()?;
|
||||||
|
let manifest_hash = create_package_drive(&process_package_id, bytes)?;
|
||||||
|
|
||||||
|
let package_state = PackageState {
|
||||||
|
our_version_hash: version_hash.to_string(),
|
||||||
|
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
|
||||||
|
caps_approved: true, // TODO see if we want to auto-approve local installs
|
||||||
|
manifest_hash: Some(manifest_hash),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(extracted) = extract_api(&process_package_id) {
|
||||||
|
if extracted {
|
||||||
|
state.installed_apis.insert(process_package_id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.packages
|
||||||
|
.insert(process_package_id.clone(), package_state);
|
||||||
|
|
||||||
|
// get the package manifest
|
||||||
|
let drive_path = format!("/{process_package_id}/pkg");
|
||||||
|
let manifest = fetch_package_manifest(&process_package_id)?;
|
||||||
|
// get wit version from metadata if local or chain if remote.
|
||||||
|
let metadata = if let Some(metadata) = metadata {
|
||||||
|
metadata
|
||||||
|
} else {
|
||||||
|
fetch_package_metadata(&package_id)?
|
||||||
|
};
|
||||||
|
|
||||||
// get wit version from metadata:
|
|
||||||
let metadata = fetch_package_metadata(package_id)?;
|
|
||||||
let wit_version = metadata.properties.wit_version;
|
let wit_version = metadata.properties.wit_version;
|
||||||
|
|
||||||
// first, for each process in manifest, initialize it
|
// first, for each process in manifest, initialize it
|
||||||
@ -201,11 +273,11 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
format!("/{}", entry.process_wasm_path)
|
format!("/{}", entry.process_wasm_path)
|
||||||
};
|
};
|
||||||
let wasm_path = format!("{}{}", drive_path, wasm_path);
|
let wasm_path = format!("{}{}", drive_path, wasm_path);
|
||||||
|
println!("wasm path: {wasm_path}");
|
||||||
let process_id = ProcessId::new(
|
let process_id = ProcessId::new(
|
||||||
Some(&entry.process_name),
|
Some(&entry.process_name),
|
||||||
package_id.package(),
|
process_package_id.package(),
|
||||||
package_id.publisher(),
|
process_package_id.publisher(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// kill process if it already exists
|
// kill process if it already exists
|
||||||
@ -236,10 +308,11 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
) else {
|
) else {
|
||||||
return Err(anyhow::anyhow!("failed to initialize process"));
|
return Err(anyhow::anyhow!("failed to initialize process"));
|
||||||
};
|
};
|
||||||
|
// println!("kernel process gave us something back.");
|
||||||
|
|
||||||
// build initial caps from manifest
|
// // build initial caps from manifest
|
||||||
let mut requested_capabilities = parse_capabilities(our_node, &entry.request_capabilities);
|
let mut requested_capabilities = parse_capabilities(our_node, &entry.request_capabilities);
|
||||||
|
println!("parsed caps.");
|
||||||
if entry.request_networking {
|
if entry.request_networking {
|
||||||
requested_capabilities.push(kt::Capability {
|
requested_capabilities.push(kt::Capability {
|
||||||
issuer: Address::new(our_node, ("kernel", "distro", "sys")),
|
issuer: Address::new(our_node, ("kernel", "distro", "sys")),
|
||||||
@ -247,6 +320,8 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("requested caps: {:?}", requested_capabilities);
|
||||||
|
|
||||||
// always grant read/write to their drive, which we created for them
|
// always grant read/write to their drive, which we created for them
|
||||||
requested_capabilities.push(kt::Capability {
|
requested_capabilities.push(kt::Capability {
|
||||||
issuer: Address::new(our_node, ("vfs", "distro", "sys")),
|
issuer: Address::new(our_node, ("vfs", "distro", "sys")),
|
||||||
@ -265,6 +340,7 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NOTE.. this crashes...
|
||||||
kernel_request(kt::KernelCommand::GrantCapabilities {
|
kernel_request(kt::KernelCommand::GrantCapabilities {
|
||||||
target: process_id.clone(),
|
target: process_id.clone(),
|
||||||
capabilities: requested_capabilities,
|
capabilities: requested_capabilities,
|
||||||
@ -278,8 +354,8 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
for entry in &manifest {
|
for entry in &manifest {
|
||||||
let process_id = ProcessId::new(
|
let process_id = ProcessId::new(
|
||||||
Some(&entry.process_name),
|
Some(&entry.process_name),
|
||||||
package_id.package(),
|
process_package_id.package(),
|
||||||
package_id.publisher(),
|
process_package_id.publisher(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for value in &entry.grant_capabilities {
|
for value in &entry.grant_capabilities {
|
||||||
@ -340,6 +416,7 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
|||||||
) else {
|
) else {
|
||||||
return Err(anyhow::anyhow!("failed to start process"));
|
return Err(anyhow::anyhow!("failed to start process"));
|
||||||
};
|
};
|
||||||
|
println!("started the process!");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use std::{collections::HashMap, str::FromStr};
|
|||||||
use crate::kinode::process::chain::{
|
use crate::kinode::process::chain::{
|
||||||
Chains, GetAppResponse, OnChainApp, OnChainMetadata, OnChainProperties,
|
Chains, GetAppResponse, OnChainApp, OnChainMetadata, OnChainProperties,
|
||||||
};
|
};
|
||||||
|
use crate::kinode::process::main::Error;
|
||||||
use alloy_primitives::keccak256;
|
use alloy_primitives::keccak256;
|
||||||
use alloy_sol_types::SolEvent;
|
use alloy_sol_types::SolEvent;
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
@ -93,6 +94,14 @@ fn init(our: Address) {
|
|||||||
Ok(message) => {
|
Ok(message) => {
|
||||||
if let Err(e) = handle_message(&our, &mut state, &message) {
|
if let Err(e) = handle_message(&our, &mut state, &message) {
|
||||||
println!("error handling message: {:?}", e);
|
println!("error handling message: {:?}", e);
|
||||||
|
let _ = Response::new()
|
||||||
|
.body(
|
||||||
|
serde_json::to_vec(&Error {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,21 @@
|
|||||||
//! manages downloading and sharing of versioned packages.
|
//! manages downloading and sharing of versioned packages.
|
||||||
//!
|
//!
|
||||||
use crate::kinode::process::downloads::{
|
use crate::kinode::process::downloads::{
|
||||||
AvailableFiles, DownloadRequest, DownloadResponse, Downloads, Entry, ProgressUpdate,
|
AddDownloadRequest, AvailableFiles, DownloadRequest, DownloadResponse, Downloads, Entry,
|
||||||
|
ProgressUpdate,
|
||||||
};
|
};
|
||||||
|
use crate::kinode::process::main::Error;
|
||||||
use std::{collections::HashSet, str::FromStr};
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
use ft_worker_lib::{spawn_receive_transfer, spawn_send_transfer};
|
use ft_worker_lib::{spawn_receive_transfer, spawn_send_transfer};
|
||||||
use kinode::process::standard::print_to_terminal;
|
use kinode_process_lib::get_blob;
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init,
|
await_message, call_init,
|
||||||
http::{
|
http::{
|
||||||
self,
|
self,
|
||||||
client::{HttpClientError, HttpClientResponse},
|
client::{HttpClientError, HttpClientResponse},
|
||||||
},
|
},
|
||||||
println,
|
print_to_terminal, println,
|
||||||
vfs::{self, DirEntry, Directory, File, SeekFrom},
|
vfs::{self, DirEntry, Directory, File, SeekFrom},
|
||||||
Address, Message, PackageId, ProcessId, Request, Response,
|
Address, Message, PackageId, ProcessId, Request, Response,
|
||||||
};
|
};
|
||||||
@ -56,23 +58,28 @@ fn init(our: Address) {
|
|||||||
mirroring: HashSet::new(),
|
mirroring: HashSet::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// should the files be actively in state? probably not due to updates?
|
// /log/.log file for debugs
|
||||||
// just install the downloads directory.
|
vfs::create_drive(our.package_id(), "log", None).expect("could not create /log drive");
|
||||||
|
// /downloads/ for downloads
|
||||||
|
vfs::create_drive(our.package_id(), "downloads", None)
|
||||||
|
.expect("could not create /downloads drive");
|
||||||
|
|
||||||
let mut logfile =
|
let mut logfile =
|
||||||
vfs::open_file("/app_store:sys/.log", true, Some(5)).expect("could not open logfile");
|
vfs::open_file("/app_store:sys/log/.log", true, Some(5)).expect("could not open logfile");
|
||||||
logfile
|
logfile
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.expect("could not seek to end of logfile");
|
.expect("could not seek to end of logfile");
|
||||||
|
// FIX this api... first creates (fails if already exists. second shouldn't fail like this... like files.)
|
||||||
|
let _ = vfs::open_dir("/app_store:sys/downloads", true, None);
|
||||||
let mut downloads =
|
let mut downloads =
|
||||||
vfs::open_dir("/app_store:sys/downloads", true, None).expect("could not open downloads");
|
vfs::open_dir("/app_store:sys/downloads", false, None).expect("could not open downloads");
|
||||||
let mut tmp = vfs::open_dir("/tmp", true, None).expect("could not open tmp");
|
let _ = vfs::open_dir("/app_store:sys/downloads/tmp", true, None);
|
||||||
|
let mut tmp =
|
||||||
|
vfs::open_dir("/app_store:sys/downloads/tmp", false, None).expect("could not open tmp");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match await_message() {
|
match await_message() {
|
||||||
Err(send_error) => {
|
Err(send_error) => {
|
||||||
// TODO handle these based on what they are triggered by
|
|
||||||
println!("got network error: {send_error}");
|
println!("got network error: {send_error}");
|
||||||
}
|
}
|
||||||
Ok(message) => {
|
Ok(message) => {
|
||||||
@ -85,6 +92,14 @@ fn init(our: Address) {
|
|||||||
&mut tmp,
|
&mut tmp,
|
||||||
) {
|
) {
|
||||||
println!("error handling message: {:?}", e);
|
println!("error handling message: {:?}", e);
|
||||||
|
let _ = Response::new()
|
||||||
|
.body(
|
||||||
|
serde_json::to_vec(&Error {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,25 +165,6 @@ fn handle_message(
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
// http_server.ws_push_all_channels(
|
|
||||||
// "/",
|
|
||||||
// http::server::WsMessageType::Text,
|
|
||||||
// LazyLoadBlob {
|
|
||||||
// mime: Some("application/json".to_string()),
|
|
||||||
// bytes: serde_json::json!({
|
|
||||||
// "kind": "progress",
|
|
||||||
// "data": {
|
|
||||||
// "file_name": file_name,
|
|
||||||
// "chunks_received": chunks_received,
|
|
||||||
// "total_chunks": total_chunks,
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .to_string()
|
|
||||||
// .as_bytes()
|
|
||||||
// .to_vec(),
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
Downloads::GetFiles(maybe_id) => {
|
Downloads::GetFiles(maybe_id) => {
|
||||||
// if not local, throw to the boonies. (could also implement and discovery protocol here..)
|
// if not local, throw to the boonies. (could also implement and discovery protocol here..)
|
||||||
@ -194,6 +190,35 @@ fn handle_message(
|
|||||||
.body(serde_json::to_vec(&AvailableFiles { files })?)
|
.body(serde_json::to_vec(&AvailableFiles { files })?)
|
||||||
.send()?;
|
.send()?;
|
||||||
}
|
}
|
||||||
|
Downloads::AddDownload(add_req) => {
|
||||||
|
if !message.is_local(our) {
|
||||||
|
// todo figure out full error throwing for http pathways.
|
||||||
|
return Err(anyhow::anyhow!("not local"));
|
||||||
|
}
|
||||||
|
let Some(blob) = get_blob() else {
|
||||||
|
return Err(anyhow::anyhow!("could not get blob"));
|
||||||
|
};
|
||||||
|
let bytes = blob.bytes;
|
||||||
|
|
||||||
|
let package_dir = format!(
|
||||||
|
"{}/{}",
|
||||||
|
downloads.path,
|
||||||
|
add_req.package_id.to_process_lib().to_string()
|
||||||
|
);
|
||||||
|
let _ = vfs::open_dir(&package_dir, true, None);
|
||||||
|
let file = vfs::create_file(
|
||||||
|
&format!("{}/{}.zip", package_dir, add_req.version_hash),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
let _ = file.write(bytes.as_slice())?;
|
||||||
|
|
||||||
|
Response::new()
|
||||||
|
.body(serde_json::to_vec(&Resp::Download(DownloadResponse {
|
||||||
|
success: true,
|
||||||
|
error: None,
|
||||||
|
}))?)
|
||||||
|
.send()?;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,13 +34,13 @@ fn init(our: Address) {
|
|||||||
let Ok(Ok(Message::Response { body, .. })) =
|
let Ok(Ok(Message::Response { body, .. })) =
|
||||||
Request::to((our.node(), ("main", "app_store", "sys")))
|
Request::to((our.node(), ("main", "app_store", "sys")))
|
||||||
.body(
|
.body(
|
||||||
serde_json::to_vec(&LocalRequest::Install(
|
b"temp", // serde_json::to_vec(&LocalRequest::Install(
|
||||||
crate::kinode::process::main::PackageId {
|
// crate::kinode::process::main::PackageId {
|
||||||
package_name: package_id.package_name.clone(),
|
// package_name: package_id.package_name.clone(),
|
||||||
publisher_node: package_id.publisher_node.clone(),
|
// publisher_node: package_id.publisher_node.clone(),
|
||||||
},
|
// },
|
||||||
))
|
// ))
|
||||||
.unwrap(),
|
// .unwrap(),
|
||||||
)
|
)
|
||||||
.send_and_await_response(5)
|
.send_and_await_response(5)
|
||||||
else {
|
else {
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
"request_capabilities": [
|
"request_capabilities": [
|
||||||
"http_client:distro:sys",
|
"http_client:distro:sys",
|
||||||
"http_server:distro:sys",
|
"http_server:distro:sys",
|
||||||
|
"main:app_store:sys",
|
||||||
|
"chain:app_store:sys",
|
||||||
"vfs:distro:sys",
|
"vfs:distro:sys",
|
||||||
{
|
{
|
||||||
"process": "vfs:distro:sys",
|
"process": "vfs:distro:sys",
|
||||||
@ -22,6 +24,35 @@
|
|||||||
],
|
],
|
||||||
"public": false
|
"public": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"process_name": "chain",
|
||||||
|
"process_wasm_path": "/chain.wasm",
|
||||||
|
"on_exit": "Restart",
|
||||||
|
"request_networking": true,
|
||||||
|
"request_capabilities": [
|
||||||
|
"main:app_store:sys",
|
||||||
|
"downloads:app_store:sys",
|
||||||
|
"vfs:distro:sys",
|
||||||
|
"kns_indexer:kns_indexer:sys",
|
||||||
|
"eth:distro:sys",
|
||||||
|
"http_server:distro:sys",
|
||||||
|
"http_client:distro:sys",
|
||||||
|
{
|
||||||
|
"process": "vfs:distro:sys",
|
||||||
|
"params": {
|
||||||
|
"root": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grant_capabilities": [
|
||||||
|
"http_server:distro:sys",
|
||||||
|
"kns_indexer:kns_indexer:sys",
|
||||||
|
"vfs:distro:sys",
|
||||||
|
"http_client:distro:sys",
|
||||||
|
"eth:distro:sys"
|
||||||
|
],
|
||||||
|
"public": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"process_name": "main",
|
"process_name": "main",
|
||||||
"process_wasm_path": "/app_store.wasm",
|
"process_wasm_path": "/app_store.wasm",
|
||||||
@ -34,6 +65,8 @@
|
|||||||
"http_server:distro:sys",
|
"http_server:distro:sys",
|
||||||
"http_client:distro:sys",
|
"http_client:distro:sys",
|
||||||
"net:distro:sys",
|
"net:distro:sys",
|
||||||
|
"downloads:app_store:sys",
|
||||||
|
"chain:app_store:sys",
|
||||||
"vfs:distro:sys",
|
"vfs:distro:sys",
|
||||||
"kernel:distro:sys",
|
"kernel:distro:sys",
|
||||||
"eth:distro:sys",
|
"eth:distro:sys",
|
||||||
@ -59,6 +92,7 @@
|
|||||||
"net:distro:sys",
|
"net:distro:sys",
|
||||||
"http_client:distro:sys",
|
"http_client:distro:sys",
|
||||||
"http_server:distro:sys",
|
"http_server:distro:sys",
|
||||||
|
"kernel:distro:sys",
|
||||||
"kns_indexer:kns_indexer:sys",
|
"kns_indexer:kns_indexer:sys",
|
||||||
"terminal:terminal:sys",
|
"terminal:terminal:sys",
|
||||||
"vfs:distro:sys"
|
"vfs:distro:sys"
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
|
import Header from "./components/Header";
|
||||||
|
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||||
|
|
||||||
import StorePage from "./pages/StorePage";
|
import StorePage from "./pages/StorePage";
|
||||||
import AppPage from "./pages/AppPage";
|
import AppPage from "./pages/AppPage";
|
||||||
import PublishPage from "./pages/PublishPage";
|
import PublishPage from "./pages/PublishPage";
|
||||||
import Header from "./components/Header";
|
import Testing from "./pages/Testing";
|
||||||
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
|
||||||
|
|
||||||
|
|
||||||
const BASE_URL = import.meta.env.BASE_URL;
|
const BASE_URL = import.meta.env.BASE_URL;
|
||||||
@ -18,6 +20,7 @@ function App() {
|
|||||||
<Router basename={BASE_URL}>
|
<Router basename={BASE_URL}>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route path="/testing" element={<Testing />} />
|
||||||
<Route path={STORE_PATH} element={<StorePage />} />
|
<Route path={STORE_PATH} element={<StorePage />} />
|
||||||
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
|
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
|
||||||
<Route path={PUBLISH_PATH} element={<PublishPage />} />
|
<Route path={PUBLISH_PATH} element={<PublishPage />} />
|
||||||
|
42
kinode/packages/app_store/ui/src/pages/Testing.tsx
Normal file
42
kinode/packages/app_store/ui/src/pages/Testing.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import useAppsStore from '../store'
|
||||||
|
|
||||||
|
const Testing: React.FC = () => {
|
||||||
|
const { getDownloads, getInstalledApps, getOurApps, getDownloadsForApp } = useAppsStore()
|
||||||
|
const [result, setResult] = useState<any>(null)
|
||||||
|
const [appId, setAppId] = useState('')
|
||||||
|
|
||||||
|
const handleAction = async (action: () => Promise<any>) => {
|
||||||
|
try {
|
||||||
|
const data = await action()
|
||||||
|
setResult(JSON.stringify(data, null, 2))
|
||||||
|
} catch (error) {
|
||||||
|
setResult(`Error: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Testing Page</h1>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => handleAction(getDownloads)}>Get Downloads</button>
|
||||||
|
<button onClick={() => handleAction(getInstalledApps)}>Get Installed Apps</button>
|
||||||
|
<button onClick={() => handleAction(getOurApps)}>Get Our Apps</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={appId}
|
||||||
|
onChange={(e) => setAppId(e.target.value)}
|
||||||
|
placeholder="Enter App ID"
|
||||||
|
/>
|
||||||
|
<button onClick={() => handleAction(() => getDownloadsForApp(appId))}>
|
||||||
|
Get Downloads for App
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre>{result}</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Testing
|
@ -24,6 +24,12 @@ interface AppsStore {
|
|||||||
setMirroring: (app: AppInfo, mirroring: boolean) => Promise<void>
|
setMirroring: (app: AppInfo, mirroring: boolean) => Promise<void>
|
||||||
setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise<void>
|
setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise<void>
|
||||||
rebuildIndex: () => Promise<void>
|
rebuildIndex: () => Promise<void>
|
||||||
|
// new.
|
||||||
|
getDownloads: () => Promise<any>
|
||||||
|
getInstalledApps: () => Promise<any>
|
||||||
|
getOurApps: () => Promise<any>
|
||||||
|
getDownloadsForApp: (id: string) => Promise<any>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAppsStore = create<AppsStore>()(
|
const useAppsStore = create<AppsStore>()(
|
||||||
@ -166,6 +172,62 @@ const useAppsStore = create<AppsStore>()(
|
|||||||
throw new Error('Failed to rebuild index')
|
throw new Error('Failed to rebuild index')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDownloads: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}/downloads`)
|
||||||
|
const data = await res.json()
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Error fetching downloads:', data)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching downloads:', error)
|
||||||
|
return { error: 'Failed to get downloads' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInstalledApps: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}/installed`)
|
||||||
|
const data = await res.json()
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Error fetching installed apps:', data)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching installed apps:', error)
|
||||||
|
return { error: 'Failed to get installed apps' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getOurApps: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}/ourapps`)
|
||||||
|
const data = await res.json()
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Error fetching our apps:', data)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching our apps:', error)
|
||||||
|
return { error: 'Failed to get our apps' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getDownloadsForApp: async (id: string) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}/downloads/${id}`)
|
||||||
|
const data = await res.json()
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(`Error fetching downloads for app ${id}:`, data)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching downloads for app ${id}:`, error)
|
||||||
|
return { error: `Failed to get downloads for app: ${id}` }
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'app_store',
|
name: 'app_store',
|
||||||
|
Loading…
Reference in New Issue
Block a user