wip full new kit install downloads flow

This commit is contained in:
bitful-pannul 2024-08-13 03:32:52 +03:00
parent beeae033c9
commit 6753de511a
12 changed files with 476 additions and 130 deletions

View File

@ -11,6 +11,7 @@ interface downloads {
progress(progress-update),
size(size-update),
get-files(option<package-id>),
add-download(add-download-request),
}
record download-request {
@ -45,6 +46,13 @@ interface downloads {
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 {
package-id: package-id,
version-hash: string,
@ -115,6 +123,13 @@ interface main {
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 {
local(local-request),
}
@ -138,7 +153,7 @@ interface main {
variant local-request {
new-package(new-package-request),
install(package-id),
install(install-package-request),
uninstall(package-id),
apis,
get-api(package-id),
@ -146,10 +161,15 @@ interface main {
record new-package-request {
package-id: package-id,
metadata: on-chain-metadata,
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 {
new-package-response(new-package-response),
install-response(install-response),

View File

@ -1,12 +1,14 @@
use crate::{
kinode::process::downloads::{DownloadRequest, DownloadResponse},
kinode::process::downloads::{DownloadRequest, DownloadResponse, Downloads},
kinode::process::main::Error,
state::{MirrorCheck, PackageState, State},
Resp,
};
use kinode_process_lib::{
http::server,
http::{self, Method, StatusCode},
Address, LazyLoadBlob, NodeId, PackageId, Request,
Address, LazyLoadBlob, PackageId, Request,
};
use kinode_process_lib::{SendError, SendErrorKind};
use serde_json::json;
@ -206,9 +208,15 @@ pub fn handle_http_request(
bytes: body,
}),
),
Err(_e) => (
Err(e) => (
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)
}
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 {
// installed package.
// on-chain info combined somewhere else.
@ -265,20 +280,50 @@ fn serve_paths(
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
}
"/downloads" => {
// TODO
Ok((StatusCode::OK, None, vec![]))
// get all local downloads!
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" => {
// TODO
Ok((StatusCode::OK, None, vec![]))
// TODO format json for UI
println!("/installed response: {:?}", state.packages);
let body = serde_json::to_vec(&state.packages)?;
Ok((StatusCode::OK, None, body))
}
"/ourapps" => {
// TODO
// TODO, fetch from state
Ok((StatusCode::OK, None, vec![]))
}
"/downloads/:id" => {
// TODO
Ok((StatusCode::OK, None, vec![]))
// get downloads for a specific app
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
// update a downloaded app: PUT
@ -367,7 +412,8 @@ fn serve_paths(
.body(serde_json::to_vec(&download_request).unwrap())
.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)?))
}
// POST /apps/:id/update
@ -383,7 +429,7 @@ fn serve_paths(
// TODO send to downloads:
// // actually just install something, we go get it from vfs or ask for it from downloads?
// yeah this should be obsolete
match method {
_ => Ok((
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![])),
Err(e) => Ok((
StatusCode::SERVICE_UNAVAILABLE,

View File

@ -18,8 +18,8 @@
//! - set to automatically update if a new version is available
use crate::kinode::process::downloads::{DownloadResponse, ProgressUpdate};
use crate::kinode::process::main::{
ApisResponse, GetApiResponse, InstallResponse, LocalRequest, LocalResponse, NewPackageRequest,
NewPackageResponse, UninstallResponse,
ApisResponse, Error, GetApiResponse, InstallPackageRequest, InstallResponse, LocalRequest,
LocalResponse, NewPackageRequest, NewPackageResponse, UninstallResponse,
};
use kinode_process_lib::{
await_message, call_init, get_blob, http, println, vfs, Address, LazyLoadBlob, Message,
@ -56,6 +56,7 @@ pub enum Req {
pub enum Resp {
LocalResponse(LocalResponse),
Download(DownloadResponse),
Err(Error),
}
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 {
@ -140,11 +166,8 @@ fn handle_local_request(
request: LocalRequest,
) -> (LocalResponse, Option<LazyLoadBlob>) {
match request {
LocalRequest::NewPackage(NewPackageRequest {
package_id,
metadata,
mirror,
}) => {
LocalRequest::NewPackage(NewPackageRequest { package_id, mirror }) => {
// note, use metadata and mirror?
let Some(blob) = get_blob() else {
return (
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),
Err(_) => LocalResponse::NewPackageResponse(NewPackageResponse::InstallFailed),
},
None,
)
}
LocalRequest::Install(package_id) => (
match utils::install(&package_id.to_process_lib(), &our.to_string()) {
Ok(()) => LocalResponse::InstallResponse(InstallResponse::Success),
LocalRequest::Install(InstallPackageRequest {
package_id,
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) => {
println!("error installing package: {e}");
LocalResponse::InstallResponse(InstallResponse::Failure)

View File

@ -77,31 +77,6 @@ impl 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
// pub fn update_downloaded_package(
// &mut self,

View File

@ -1,11 +1,17 @@
use {
crate::state::{PackageState, State},
crate::VFS_TIMEOUT,
crate::{
kinode::process::{
chain::{Chains, GetAppResponse, OnChainMetadata},
downloads::{AddDownloadRequest, DownloadResponse, Downloads},
},
state::{PackageState, State},
VFS_TIMEOUT,
},
kinode_process_lib::{
get_blob, kernel_types as kt, println, vfs, Address, LazyLoadBlob, PackageId, ProcessId,
Request,
},
std::collections::HashSet,
std::{collections::HashSet, str::FromStr},
};
// 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> {
vfs_request(
format!("/{package_id}/pkg/metadata.json"), // OK BIG todo: figure out why tf this is not here...
vfs::VfsAction::Read,
)
.send_and_await_response(VFS_TIMEOUT)??;
let Some(blob) = get_blob() else {
return Err(anyhow::anyhow!("no blob"));
pub fn fetch_package_metadata(
package_id: &crate::kinode::process::main::PackageId,
) -> anyhow::Result<OnChainMetadata> {
let chain = Address::from_str("our@chain:app_store:sys")?;
let resp = Request::new()
.target(chain)
.body(serde_json::to_vec(&Chains::GetApp(package_id.clone())).unwrap())
.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(
package_id: &PackageId,
state: &mut State,
package_id: crate::kinode::process::main::PackageId,
mirror: bool,
bytes: Vec<u8>,
) -> 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
let our_version_hash = sha_256_hash(&bytes);
let version_hash = sha_256_hash(&bytes);
let package_state = PackageState {
our_version_hash,
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: None, // generated in the add fn
let downloads = Address::from_str("our@downloads:app_store:sys")?;
let resp = Request::new()
.target(downloads)
.body(serde_json::to_vec(&Downloads::AddDownload(
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.
@ -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
// call it <package_id>.zip
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)
}
/// 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
/// 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.
/// note also that each capability will only be granted if we, the process
/// using this function, own that capability ourselves.
pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
// get the package manifest
let drive_path = format!("/{package_id}/pkg");
let manifest = fetch_package_manifest(package_id)?;
pub fn install(
package_id: &crate::kinode::process::main::PackageId,
metadata: Option<OnChainMetadata>,
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;
// 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)
};
let wasm_path = format!("{}{}", drive_path, wasm_path);
println!("wasm path: {wasm_path}");
let process_id = ProcessId::new(
Some(&entry.process_name),
package_id.package(),
package_id.publisher(),
process_package_id.package(),
process_package_id.publisher(),
);
// kill process if it already exists
@ -236,10 +308,11 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
) else {
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);
println!("parsed caps.");
if entry.request_networking {
requested_capabilities.push(kt::Capability {
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
requested_capabilities.push(kt::Capability {
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(),
});
// NOTE.. this crashes...
kernel_request(kt::KernelCommand::GrantCapabilities {
target: process_id.clone(),
capabilities: requested_capabilities,
@ -278,8 +354,8 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
for entry in &manifest {
let process_id = ProcessId::new(
Some(&entry.process_name),
package_id.package(),
package_id.publisher(),
process_package_id.package(),
process_package_id.publisher(),
);
for value in &entry.grant_capabilities {
@ -340,6 +416,7 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
) else {
return Err(anyhow::anyhow!("failed to start process"));
};
println!("started the process!");
}
Ok(())
}

View File

@ -8,6 +8,7 @@ use std::{collections::HashMap, str::FromStr};
use crate::kinode::process::chain::{
Chains, GetAppResponse, OnChainApp, OnChainMetadata, OnChainProperties,
};
use crate::kinode::process::main::Error;
use alloy_primitives::keccak256;
use alloy_sol_types::SolEvent;
use kinode_process_lib::{
@ -93,6 +94,14 @@ fn init(our: Address) {
Ok(message) => {
if let Err(e) = handle_message(&our, &mut state, &message) {
println!("error handling message: {:?}", e);
let _ = Response::new()
.body(
serde_json::to_vec(&Error {
reason: e.to_string(),
})
.unwrap(),
)
.send();
}
}
}

View File

@ -3,19 +3,21 @@
//! manages downloading and sharing of versioned packages.
//!
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 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::{
await_message, call_init,
http::{
self,
client::{HttpClientError, HttpClientResponse},
},
println,
print_to_terminal, println,
vfs::{self, DirEntry, Directory, File, SeekFrom},
Address, Message, PackageId, ProcessId, Request, Response,
};
@ -56,23 +58,28 @@ fn init(our: Address) {
mirroring: HashSet::new(),
};
// should the files be actively in state? probably not due to updates?
// just install the downloads directory.
// /log/.log file for debugs
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 =
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
.seek(SeekFrom::End(0))
.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 =
vfs::open_dir("/app_store:sys/downloads", true, None).expect("could not open downloads");
let mut tmp = vfs::open_dir("/tmp", true, None).expect("could not open tmp");
vfs::open_dir("/app_store:sys/downloads", false, None).expect("could not open downloads");
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 {
match await_message() {
Err(send_error) => {
// TODO handle these based on what they are triggered by
println!("got network error: {send_error}");
}
Ok(message) => {
@ -85,6 +92,14 @@ fn init(our: Address) {
&mut tmp,
) {
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(),
)
.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) => {
// 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 })?)
.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 {

View File

@ -34,13 +34,13 @@ fn init(our: Address) {
let Ok(Ok(Message::Response { body, .. })) =
Request::to((our.node(), ("main", "app_store", "sys")))
.body(
serde_json::to_vec(&LocalRequest::Install(
crate::kinode::process::main::PackageId {
package_name: package_id.package_name.clone(),
publisher_node: package_id.publisher_node.clone(),
},
))
.unwrap(),
b"temp", // serde_json::to_vec(&LocalRequest::Install(
// crate::kinode::process::main::PackageId {
// package_name: package_id.package_name.clone(),
// publisher_node: package_id.publisher_node.clone(),
// },
// ))
// .unwrap(),
)
.send_and_await_response(5)
else {

View File

@ -7,6 +7,8 @@
"request_capabilities": [
"http_client:distro:sys",
"http_server:distro:sys",
"main:app_store:sys",
"chain:app_store:sys",
"vfs:distro:sys",
{
"process": "vfs:distro:sys",
@ -22,6 +24,35 @@
],
"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_wasm_path": "/app_store.wasm",
@ -34,6 +65,8 @@
"http_server:distro:sys",
"http_client:distro:sys",
"net:distro:sys",
"downloads:app_store:sys",
"chain:app_store:sys",
"vfs:distro:sys",
"kernel:distro:sys",
"eth:distro:sys",
@ -59,6 +92,7 @@
"net:distro:sys",
"http_client:distro:sys",
"http_server:distro:sys",
"kernel:distro:sys",
"kns_indexer:kns_indexer:sys",
"terminal:terminal:sys",
"vfs:distro:sys"

View File

@ -1,11 +1,13 @@
import React from "react";
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 AppPage from "./pages/AppPage";
import PublishPage from "./pages/PublishPage";
import Header from "./components/Header";
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
import Testing from "./pages/Testing";
const BASE_URL = import.meta.env.BASE_URL;
@ -18,6 +20,7 @@ function App() {
<Router basename={BASE_URL}>
<Header />
<Routes>
<Route path="/testing" element={<Testing />} />
<Route path={STORE_PATH} element={<StorePage />} />
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
<Route path={PUBLISH_PATH} element={<PublishPage />} />

View 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

View File

@ -24,6 +24,12 @@ interface AppsStore {
setMirroring: (app: AppInfo, mirroring: boolean) => Promise<void>
setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise<void>
rebuildIndex: () => Promise<void>
// new.
getDownloads: () => Promise<any>
getInstalledApps: () => Promise<any>
getOurApps: () => Promise<any>
getDownloadsForApp: (id: string) => Promise<any>
}
const useAppsStore = create<AppsStore>()(
@ -166,6 +172,62 @@ const useAppsStore = create<AppsStore>()(
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',