diff --git a/kinode/packages/app_store/api/app_store:sys-v0.wit b/kinode/packages/app_store/api/app_store:sys-v0.wit index bff74091..5ce76dd3 100644 --- a/kinode/packages/app_store/api/app_store:sys-v0.wit +++ b/kinode/packages/app_store/api/app_store:sys-v0.wit @@ -11,6 +11,7 @@ interface downloads { progress(progress-update), size(size-update), get-files(option), + add-download(add-download-request), } record download-request { @@ -45,6 +46,13 @@ interface downloads { files: list, } + // 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,9 +161,14 @@ 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, // if None == local sideload package. + version-hash: string, + } variant local-response { new-package-response(new-package-response), diff --git a/kinode/packages/app_store/app_store/src/http_api.rs b/kinode/packages/app_store/app_store/src/http_api.rs index f901e710..89274f5d 100644 --- a/kinode/packages/app_store/app_store/src/http_api.rs +++ b/kinode/packages/app_store/app_store/src/http_api.rs @@ -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) -> anyhow::Result) -> anyhow::Result { + 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.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.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, diff --git a/kinode/packages/app_store/app_store/src/lib.rs b/kinode/packages/app_store/app_store/src/lib.rs index a8053159..422d7d28 100644 --- a/kinode/packages/app_store/app_store/src/lib.rs +++ b/kinode/packages/app_store/app_store/src/lib.rs @@ -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) { 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) diff --git a/kinode/packages/app_store/app_store/src/state.rs b/kinode/packages/app_store/app_store/src/state.rs index 7bf4d0b9..377741d1 100644 --- a/kinode/packages/app_store/app_store/src/state.rs +++ b/kinode/packages/app_store/app_store/src/state.rs @@ -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>, - ) -> 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, diff --git a/kinode/packages/app_store/app_store/src/utils.rs b/kinode/packages/app_store/app_store/src/utils.rs index f8b3cb33..4f2b25b3 100644 --- a/kinode/packages/app_store/app_store/src/utils.rs +++ b/kinode/packages/app_store/app_store/src/utils.rs @@ -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 { - 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 { + 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::(&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::(&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, ) -> 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::(&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 .zip let zip_path = format!("{}/{}.zip", drive_name, package_id); @@ -174,7 +209,8 @@ pub fn extract_api(package_id: &PackageId) -> anyhow::Result { 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 { /// 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, + 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(()) } diff --git a/kinode/packages/app_store/chain/src/lib.rs b/kinode/packages/app_store/chain/src/lib.rs index 1abd3bf3..d424c2e9 100644 --- a/kinode/packages/app_store/chain/src/lib.rs +++ b/kinode/packages/app_store/chain/src/lib.rs @@ -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(); } } } diff --git a/kinode/packages/app_store/downloads/src/lib.rs b/kinode/packages/app_store/downloads/src/lib.rs index e6e06bed..7709180a 100644 --- a/kinode/packages/app_store/downloads/src/lib.rs +++ b/kinode/packages/app_store/downloads/src/lib.rs @@ -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 { diff --git a/kinode/packages/app_store/install/src/lib.rs b/kinode/packages/app_store/install/src/lib.rs index f000a349..731ab4f3 100644 --- a/kinode/packages/app_store/install/src/lib.rs +++ b/kinode/packages/app_store/install/src/lib.rs @@ -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 { diff --git a/kinode/packages/app_store/pkg/manifest.json b/kinode/packages/app_store/pkg/manifest.json index 24765243..5f322895 100644 --- a/kinode/packages/app_store/pkg/manifest.json +++ b/kinode/packages/app_store/pkg/manifest.json @@ -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" diff --git a/kinode/packages/app_store/ui/src/App.tsx b/kinode/packages/app_store/ui/src/App.tsx index 453cd584..1fcc567f 100644 --- a/kinode/packages/app_store/ui/src/App.tsx +++ b/kinode/packages/app_store/ui/src/App.tsx @@ -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() {
+ } /> } /> } /> } /> diff --git a/kinode/packages/app_store/ui/src/pages/Testing.tsx b/kinode/packages/app_store/ui/src/pages/Testing.tsx new file mode 100644 index 00000000..e347a2fd --- /dev/null +++ b/kinode/packages/app_store/ui/src/pages/Testing.tsx @@ -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(null) + const [appId, setAppId] = useState('') + + const handleAction = async (action: () => Promise) => { + try { + const data = await action() + setResult(JSON.stringify(data, null, 2)) + } catch (error) { + setResult(`Error: ${error.message}`) + } + } + + return ( +
+

Testing Page

+
+ + + +
+
+ setAppId(e.target.value)} + placeholder="Enter App ID" + /> + +
+
{result}
+
+ ) +} + +export default Testing \ No newline at end of file diff --git a/kinode/packages/app_store/ui/src/store/index.ts b/kinode/packages/app_store/ui/src/store/index.ts index 392f4078..a6efba21 100644 --- a/kinode/packages/app_store/ui/src/store/index.ts +++ b/kinode/packages/app_store/ui/src/store/index.ts @@ -24,6 +24,12 @@ interface AppsStore { setMirroring: (app: AppInfo, mirroring: boolean) => Promise setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise rebuildIndex: () => Promise + // new. + getDownloads: () => Promise + getInstalledApps: () => Promise + getOurApps: () => Promise + getDownloadsForApp: (id: string) => Promise + } const useAppsStore = create()( @@ -166,6 +172,62 @@ const useAppsStore = create()( 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',