Merge pull request #578 from kinode-dao/develop

Develop 0.9.8
This commit is contained in:
doria 2024-11-04 22:39:08 +09:00 committed by GitHub
commit 1183cdff3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 4339 additions and 144 deletions

101
Cargo.lock generated
View File

@ -78,7 +78,7 @@ name = "alias"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -1012,7 +1012,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"rand 0.8.5",
"serde",
@ -1383,7 +1383,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"url",
@ -1592,7 +1592,7 @@ name = "cat"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -1656,7 +1656,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"rand 0.8.5",
"serde",
@ -1675,7 +1675,7 @@ version = "0.2.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pleco",
"serde",
"serde_json",
@ -1863,6 +1863,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "contacts"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.4 (git+https://github.com/kinode-dao/process_lib?rev=088a549)",
"process_macros",
"serde",
"serde_json",
"wit-bindgen",
]
[[package]]
name = "convert_case"
version = "0.4.0"
@ -2433,7 +2444,7 @@ name = "download"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"serde",
"serde_json",
@ -2445,7 +2456,7 @@ name = "downloads"
version = "0.5.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"rand 0.8.5",
"serde",
@ -2482,7 +2493,7 @@ dependencies = [
name = "echo"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"wit-bindgen",
]
@ -2721,7 +2732,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"rand 0.8.5",
"serde",
@ -2875,7 +2886,7 @@ dependencies = [
name = "get_block"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -2940,7 +2951,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
name = "globe"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"url",
@ -3067,7 +3078,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
name = "help"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"wit-bindgen",
]
@ -3096,7 +3107,7 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
name = "hi"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -3129,7 +3140,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -3444,7 +3455,7 @@ name = "install"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"serde",
"serde_json",
@ -3621,7 +3632,7 @@ name = "kfetch"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"serde_json",
@ -3633,7 +3644,7 @@ name = "kill"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -3706,9 +3717,8 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
version = "0.9.2"
source = "git+https://github.com/kinode-dao/process_lib.git?rev=9ac9e51#9ac9e513c0228f2dcfe8999ed4ca2c38246ee3db"
dependencies = [
"alloy 0.1.4",
"alloy-primitives",
@ -3729,8 +3739,31 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.2"
source = "git+https://github.com/kinode-dao/process_lib.git?rev=9ac9e51#9ac9e513c0228f2dcfe8999ed4ca2c38246ee3db"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy 0.1.4",
"alloy-primitives",
"alloy-sol-macro",
"alloy-sol-types",
"anyhow",
"bincode",
"http 1.1.0",
"mime_guess",
"rand 0.8.5",
"rmp-serde",
"serde",
"serde_json",
"thiserror",
"url",
"wit-bindgen",
]
[[package]]
name = "kinode_process_lib"
version = "0.9.4"
source = "git+https://github.com/kinode-dao/process_lib?rev=088a549#088a5497257eada697e0869d6a8d7e9ef5e620f6"
dependencies = [
"alloy 0.1.4",
"alloy-primitives",
@ -3794,7 +3827,7 @@ dependencies = [
"alloy-sol-types",
"anyhow",
"hex",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"serde_json",
@ -4023,7 +4056,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex",
"serde",
"serde_json",
@ -4193,7 +4226,7 @@ dependencies = [
name = "net_diagnostics"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"wit-bindgen",
@ -4519,7 +4552,7 @@ dependencies = [
name = "peer"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"wit-bindgen",
@ -4529,7 +4562,7 @@ dependencies = [
name = "peers"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"wit-bindgen",
@ -5570,7 +5603,7 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rmp-serde",
"serde",
"serde_json",
@ -5788,7 +5821,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
name = "state"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -5965,7 +5998,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.8.5",
"regex",
"serde",
@ -5979,7 +6012,7 @@ version = "0.1.1"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"serde",
"serde_json",
@ -6236,7 +6269,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"clap",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
"wit-bindgen",
@ -6567,7 +6600,7 @@ name = "uninstall"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib 0.9.1",
"kinode_process_lib 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"process_macros",
"serde",
"serde_json",

View File

@ -17,6 +17,7 @@ members = [
"kinode/packages/app_store/app_store", "kinode/packages/app_store/ft_worker",
"kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall", "kinode/packages/app_store/downloads", "kinode/packages/app_store/chain",
"kinode/packages/chess/chess",
"kinode/packages/contacts/contacts",
"kinode/packages/homepage/homepage",
"kinode/packages/kino_updates/blog", "kinode/packages/kino_updates/globe",
"kinode/packages/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state",

View File

@ -129,6 +129,7 @@ The distro userspace packages are:
- `app_store:sys`
- `chess:sys`
- `contacts:sys`
- `homepage:sys`
- `kino_updates:sys`
- `kns_indexer:sys`

View File

@ -1617,9 +1617,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -11,7 +11,7 @@ alloy-primitives = "0.7.6"
alloy-sol-types = "0.7.6"
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

View File

@ -9,7 +9,6 @@ use crate::{
},
state::{MirrorCheck, PackageState, State},
};
use kinode_process_lib::{
http::{self, server, Method, StatusCode},
println, Address, LazyLoadBlob, PackageId, Request, SendError, SendErrorKind,
@ -22,7 +21,7 @@ const ICON: &str = include_str!("icon");
/// Bind static and dynamic HTTP paths for the app store,
/// bind to our WS updates path, and add icon and widget to homepage.
pub fn init_frontend(our: &Address, http_server: &mut server::HttpServer) {
let config = server::HttpBindingConfig::default();
let config = server::HttpBindingConfig::default().secure_subdomain(true);
for path in [
"/apps", // all on-chain apps
@ -44,12 +43,19 @@ pub fn init_frontend(our: &Address, http_server: &mut server::HttpServer) {
.bind_http_path(path, config.clone())
.expect("failed to bind http path");
}
// bind /apps path at base domain, in addition to secure subdomain,
// so that widget can access it
http_server
.bind_http_path("/apps-public", config.clone().secure_subdomain(false))
.expect("failed to bind http path");
http_server
.serve_ui(&our, "ui", vec!["/"], config.clone())
.expect("failed to serve static UI");
http_server
.bind_ws_path("/", server::WsBindingConfig::default())
.secure_bind_ws_path("/")
.expect("failed to bind ws path");
// add ourselves to the homepage
@ -136,7 +142,7 @@ fn make_widget() -> String {
<script>
document.addEventListener('DOMContentLoaded', function() {
function fetchApps() {
fetch('/main:app_store:sys/apps', { credentials: 'include' })
fetch('/main:app_store:sys/apps-public', { credentials: 'include' })
.then(response => response.json())
.then(data => {
const container = document.getElementById('latest-apps');
@ -255,7 +261,7 @@ fn serve_paths(
match bound_path {
// GET all apps
"/apps" => {
"/apps" | "/apps-public" => {
let resp = Request::to(("our", "chain", "app_store", "sys"))
.body(serde_json::to_vec(&ChainRequests::GetApps)?)
.send_and_await_response(5)??;

View File

@ -11,7 +11,7 @@ alloy-primitives = "0.7.6"
alloy-sol-types = "0.7.6"
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -2,13 +2,13 @@ import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Header from "./components/Header";
import { APP_DETAILS_PATH, DOWNLOAD_PATH, MY_DOWNLOADS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
import { APP_DETAILS_PATH, DOWNLOAD_PATH, MY_APPS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
import StorePage from "./pages/StorePage";
import AppPage from "./pages/AppPage";
import DownloadPage from "./pages/DownloadPage";
import PublishPage from "./pages/PublishPage";
import MyDownloadsPage from "./pages/MyDownloadsPage";
import MyAppsPage from "./pages/MyAppsPage";
const BASE_URL = import.meta.env.BASE_URL;
@ -22,7 +22,7 @@ function App() {
<Header />
<Routes>
<Route path={STORE_PATH} element={<StorePage />} />
<Route path={MY_DOWNLOADS_PATH} element={<MyDownloadsPage />} />
<Route path={MY_APPS_PATH} element={<MyAppsPage />} />
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
<Route path={PUBLISH_PATH} element={<PublishPage />} />
<Route path={`${DOWNLOAD_PATH}/:id`} element={<DownloadPage />} />

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { STORE_PATH, PUBLISH_PATH, MY_DOWNLOADS_PATH } from '../constants/path';
import { STORE_PATH, PUBLISH_PATH, MY_APPS_PATH } from '../constants/path';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { FaHome } from "react-icons/fa";
@ -9,12 +9,12 @@ const Header: React.FC = () => {
<header className="app-header">
<div className="header-left">
<nav>
<button onClick={() => window.location.href = '/'}>
<button onClick={() => window.location.href = '/'} className="home-button">
<FaHome />
</button>
<Link to={STORE_PATH} className={location.pathname === STORE_PATH ? 'active' : ''}>Apps</Link>
<Link to={PUBLISH_PATH} className={location.pathname === PUBLISH_PATH ? 'active' : ''}>Publish</Link>
<Link to={MY_DOWNLOADS_PATH} className={location.pathname === MY_DOWNLOADS_PATH ? 'active' : ''}>My Downloads</Link>
<Link to={MY_APPS_PATH} className={location.pathname === MY_APPS_PATH ? 'active' : ''}>My Apps</Link>
</nav>
</div>
<div className="header-right">

View File

@ -2,4 +2,4 @@ export const STORE_PATH = '/';
export const PUBLISH_PATH = '/publish';
export const APP_DETAILS_PATH = '/app';
export const DOWNLOAD_PATH = '/download';
export const MY_DOWNLOADS_PATH = '/my-downloads';
export const MY_APPS_PATH = '/my-apps';

View File

@ -136,6 +136,8 @@ td {
.app-icon {
width: 64px;
height: 64px;
min-width: 64px;
min-height: 64px;
object-fit: cover;
border-radius: var(--border-radius);
}
@ -348,6 +350,13 @@ td {
padding-bottom: 1rem;
}
.home-button {
min-width: 48px;
min-height: 48px;
width: 48px;
height: 48px;
}
.app-screenshot {
max-width: 200px;
height: auto;

View File

@ -3,15 +3,41 @@ import { FaFolder, FaFile, FaChevronLeft, FaSync, FaRocket, FaSpinner, FaCheck,
import useAppsStore from "../store";
import { DownloadItem, PackageManifest, PackageState } from "../types/Apps";
export default function MyDownloadsPage() {
const { fetchDownloads, fetchDownloadsForApp, startMirroring, stopMirroring, installApp, removeDownload, fetchInstalled, installed } = useAppsStore();
// Core packages that cannot be uninstalled
const CORE_PACKAGES = [
"app_store:sys",
"contacts:sys",
"kino_updates:sys",
"terminal:sys",
"chess:sys",
"kns_indexer:sys",
"settings:sys",
"homepage:sys"
];
export default function MyAppsPage() {
const {
fetchDownloads,
fetchDownloadsForApp,
startMirroring,
stopMirroring,
installApp,
removeDownload,
fetchInstalled,
installed,
uninstallApp
} = useAppsStore();
const [currentPath, setCurrentPath] = useState<string[]>([]);
const [items, setItems] = useState<DownloadItem[]>([]);
const [isInstalling, setIsInstalling] = useState(false);
const [isUninstalling, setIsUninstalling] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showCapApproval, setShowCapApproval] = useState(false);
const [manifest, setManifest] = useState<PackageManifest | null>(null);
const [selectedItem, setSelectedItem] = useState<DownloadItem | null>(null);
const [showUninstallConfirm, setShowUninstallConfirm] = useState(false);
const [appToUninstall, setAppToUninstall] = useState<any>(null);
useEffect(() => {
loadItems();
@ -33,6 +59,35 @@ export default function MyDownloadsPage() {
}
};
const initiateUninstall = (app: any) => {
const packageId = `${app.package_id.package_name}:${app.package_id.publisher_node}`;
if (CORE_PACKAGES.includes(packageId)) {
setError("Cannot uninstall core system packages");
return;
}
setAppToUninstall(app);
setShowUninstallConfirm(true);
};
const handleUninstall = async () => {
if (!appToUninstall) return;
setIsUninstalling(true);
const packageId = `${appToUninstall.package_id.package_name}:${appToUninstall.package_id.publisher_node}`;
try {
await uninstallApp(packageId);
await fetchInstalled();
await loadItems();
setShowUninstallConfirm(false);
setAppToUninstall(null);
} catch (error) {
console.error('Uninstallation failed:', error);
setError(`Uninstallation failed: ${error instanceof Error ? error.message : String(error)}`);
} finally {
setIsUninstalling(false);
}
};
const navigateToItem = (item: DownloadItem) => {
if (item.Dir) {
setCurrentPath([...currentPath, item.Dir.name]);
@ -85,7 +140,6 @@ export default function MyDownloadsPage() {
if (!versionHash) throw new Error('Invalid file name format');
// Construct packageId by combining currentPath and remaining parts of the filename
const packageId = [...currentPath, ...parts].join(':');
await installApp(packageId, versionHash);
@ -121,8 +175,48 @@ export default function MyDownloadsPage() {
return (
<div className="downloads-page">
<h2>Downloads</h2>
<h2>My Apps</h2>
{/* Installed Apps Section */}
<div className="file-explorer">
<h3>Installed Apps</h3>
<table className="downloads-table">
<thead>
<tr>
<th>Package ID</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{Object.values(installed).map((app) => {
const packageId = `${app.package_id.package_name}:${app.package_id.publisher_node}`;
const isCore = CORE_PACKAGES.includes(packageId);
return (
<tr key={packageId}>
<td>{packageId}</td>
<td>
{isCore ? (
<span className="core-package">Core Package</span>
) : (
<button
onClick={() => initiateUninstall(app)}
disabled={isUninstalling}
>
{isUninstalling ? <FaSpinner className="fa-spin" /> : <FaTrash />}
Uninstall
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* Downloads Section */}
<div className="file-explorer">
<h3>Downloads</h3>
<div className="path-navigation">
{currentPath.length > 0 && (
<button onClick={navigateUp} className="navigate-up">
@ -172,7 +266,8 @@ export default function MyDownloadsPage() {
)}
{isFile && isInstalled && (
<FaCheck className="installed" />
)} </td>
)}
</td>
</tr>
);
})}
@ -186,6 +281,45 @@ export default function MyDownloadsPage() {
</div>
)}
{/* Uninstall Confirmation Modal */}
{showUninstallConfirm && appToUninstall && (
<div className="cap-approval-popup">
<div className="cap-approval-content">
<h3>Confirm Uninstall</h3>
<div className="warning-message">
Are you sure you want to uninstall this app?
</div>
<div className="package-info">
<strong>Package ID:</strong> {`${appToUninstall.package_id.package_name}:${appToUninstall.package_id.publisher_node}`}
</div>
{appToUninstall.metadata?.name && (
<div className="package-info">
<strong>Name:</strong> {appToUninstall.metadata.name}
</div>
)}
<div className="approval-buttons">
<button
onClick={() => {
setShowUninstallConfirm(false);
setAppToUninstall(null);
}}
>
Cancel
</button>
<button
onClick={handleUninstall}
disabled={isUninstalling}
className="danger"
>
{isUninstalling ? <FaSpinner className="fa-spin" /> : 'Confirm Uninstall'}
</button>
</div>
</div>
</div>
)}
{showCapApproval && manifest && (
<div className="cap-approval-popup">
<div className="cap-approval-content">

View File

@ -17,7 +17,7 @@ The format is "/" + "process_name:package_name:publisher_node"
const BASE_URL = `/main:app_store:sys`;
// This is the proxy URL, it must match the node you are developing against
const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace('localhost', '127.0.0.1');
const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080');
console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL);

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -1520,9 +1520,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
pleco = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

3218
kinode/packages/contacts/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
[workspace]
resolver = "2"
members = [
"contacts",
"get_names",
]
[profile.release]
panic = "abort"
opt-level = "s"
lto = true

View File

@ -0,0 +1,36 @@
interface contacts {
enum capability {
read-name-only,
read,
add,
remove,
}
variant request {
get-names, // requires read-names-only
get-all-contacts, // requires read
get-contact(string), // requires read
add-contact(string), // requires add
// tuple<node, field, value>
add-field(tuple<string, string, string>), // requires add
remove-contact(string), // requires remove
// tuple<node, field>
remove-field(tuple<string, string>), // requires remove
}
variant response {
get-names(list<string>),
get-all-contacts, // JSON all-contacts dict in blob
get-contact, // JSON contact dict in blob
add-contact,
add-field,
remove-contact,
remove-field,
err(string), // any failed request will receive this response
}
}
world contacts-sys-v0 {
import contacts;
include process-v0;
}

View File

@ -0,0 +1,20 @@
[package]
name = "contacts"
version = "0.1.0"
edition = "2021"
[features]
simulation-mode = []
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "088a549" }
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -0,0 +1 @@
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiByeD0iNDgwIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjA5MV8xMDI4MikiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSI5NjAiIHJ4PSI0ODAiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzIwOTFfMTAyODIpIiBzdHJva2Utd2lkdGg9IjQwIi8+CjxwYXRoIGQ9Ik01ODEuMTE2IDU4NS43ODNMNjY0LjUzOCA2NjkuMTJDNjE3LjY5OSA3MTUuNzU4IDU1Ni42MjUgNzM5IDQ5NS4yNDUgNzM5QzQzMy44NjUgNzM5IDM3Mi42MzggNzE1LjYwNSAzMjYuMTA1IDY2OS4xMkMyODAuNzk3IDYyMy44NTggMjU2IDU2My45MTcgMjU2IDUwMEMyNTYgNDM2LjA4MyAyODAuOTUgMzc2LjE0MiAzMjYuMTA1IDMzMC44OEMzNzEuMjYgMjg1LjYxOSA0MzEuMjYyIDI2MSA0OTUuMjQ1IDI2MUM1NTkuMjI3IDI2MSA2MTkuMjMgMjg1LjkyNCA2NjQuNTM4IDMzMC44OEw1ODAuOTYzIDQxNC41MjNDNTQ1LjYwNCAzODMuOTQgNTA1LjE5NCAzNzQuNzY2IDQ2MS4yNjQgMzkxLjEyN0MzOTQuOTg1IDQxNS44OTkgMzY2LjgyMSA0ODkuMjk2IDM5Ny40MzUgNTUyLjc1NEMzOTguNjU5IDU1NS4yMDEgMzk4Ljk2NSA1NTguNzE4IDM5OC4zNTMgNTYxLjMxN0MzOTUuNzUxIDU3Mi40OCAzOTIuNjg5IDU4My40OSAzODkuNzgxIDU5NC4zNDZDMzg2LjEwNyA2MDcuOTU1IDM5My45MTQgNjE1Ljc1NCA0MDcuNTM3IDYxMi4yMzdDNDE4LjU1OCA2MDkuMzMxIDQyOS41NzkgNjA2LjEyIDQ0MC42IDYwMy42NzRDNDQzLjM1NSA2MDIuOTA5IDQ0Ny4wMjkgNjAzLjIxNSA0NDkuNjMxIDYwNC40MzhDNDkwLjUgNjIzLjM5OSA1MjkuOTkxIDYyMC44IDU2Ny43OTkgNTk2LjE4MUM1NzIuNTQ0IDU5My4xMjMgNTc2LjY3NyA1ODkuNDUzIDU4MS4xMTYgNTg1Ljc4M1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMDkxXzEwMjgyKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIwOTFfMTAyODIiIHgxPSI1MDAiIHkxPSIwIiB4Mj0iNTAwIiB5Mj0iMTAwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8yMDkxXzEwMjgyIiB4MT0iNzgyLjUiIHkxPSI3My41IiB4Mj0iMTg1LjUiIHkyPSI4OTQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0E1MzEwQyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Ml9saW5lYXJfMjA5MV8xMDI4MiIgeDE9Ijc1NCIgeTE9Ijg5LjUiIHgyPSIyNTYiIHkyPSIxMDE1LjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YzNTQyMiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNBNzMyMEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K

View File

@ -0,0 +1,350 @@
use crate::kinode::process::contacts;
use kinode_process_lib::{
await_message, call_init, eth, get_blob, get_typed_state, homepage, http, kimap, kiprintln,
set_state, Address, Capability, LazyLoadBlob, Message, NodeId, Response,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
wit_bindgen::generate!({
path: "target/wit",
world: "contacts-sys-v0",
generate_unused_types: true,
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize, process_macros::SerdeJsonInto],
});
const ICON: &str = include_str!("icon");
#[cfg(not(feature = "simulation-mode"))]
const CHAIN_ID: u64 = kimap::KIMAP_CHAIN_ID;
#[cfg(feature = "simulation-mode")]
const CHAIN_ID: u64 = 31337; // local
const CHAIN_TIMEOUT: u64 = 60; // 60s
#[cfg(not(feature = "simulation-mode"))]
const KIMAP_ADDRESS: &'static str = kimap::KIMAP_ADDRESS; // optimism
#[cfg(feature = "simulation-mode")]
const KIMAP_ADDRESS: &str = "0xEce71a05B36CA55B895427cD9a440eEF7Cf3669D";
#[derive(Debug, Serialize, Deserialize)]
struct Contact(HashMap<String, serde_json::Value>);
#[derive(Debug, Serialize, Deserialize)]
struct Contacts(HashMap<NodeId, Contact>);
#[derive(Debug, Serialize, Deserialize)]
struct ContactsState {
our: Address,
contacts: Contacts,
}
impl ContactsState {
fn new(our: Address) -> Self {
get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self {
our,
contacts: Contacts(HashMap::new()),
})
}
fn save(&self) {
set_state(&serde_json::to_vec(&self).expect("Failed to serialize contacts state!"));
}
fn contacts(&self) -> &Contacts {
&self.contacts
}
fn get_contact(&self, node: NodeId) -> Option<&Contact> {
self.contacts.0.get(&node)
}
fn add_contact(&mut self, node: NodeId) {
self.contacts.0.insert(node, Contact(HashMap::new()));
self.save();
}
fn remove_contact(&mut self, node: NodeId) {
self.contacts.0.remove(&node);
self.save();
}
fn add_field(&mut self, node: NodeId, field: String, value: serde_json::Value) {
self.contacts
.0
.entry(node)
.or_insert_with(|| Contact(HashMap::new()))
.0
.insert(field, value);
self.save();
}
fn remove_field(&mut self, node: NodeId, field: String) {
if let Some(contact) = self.contacts.0.get_mut(&node) {
contact.0.remove(&field);
}
self.save();
}
fn ws_update(&self, http_server: &mut http::server::HttpServer) {
http_server.ws_push_all_channels(
"/",
http::server::WsMessageType::Text,
LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(self.contacts()).unwrap(),
),
);
}
}
call_init!(initialize);
fn initialize(our: Address) {
kiprintln!("started");
homepage::add_to_homepage("Contacts", Some(ICON), Some("/"), None);
let mut state: ContactsState = ContactsState::new(our);
let kimap = kimap::Kimap::new(
eth::Provider::new(CHAIN_ID, CHAIN_TIMEOUT),
eth::Address::from_str(KIMAP_ADDRESS).unwrap(),
);
let mut http_server = http::server::HttpServer::new(5);
// serve the frontend on a secure subdomain
http_server
.serve_ui(
&state.our,
"ui",
vec!["/"],
http::server::HttpBindingConfig::default().secure_subdomain(true),
)
.unwrap();
http_server.secure_bind_http_path("/ask").unwrap();
http_server.secure_bind_ws_path("/").unwrap();
main_loop(&mut state, &kimap, &mut http_server);
}
fn main_loop(
state: &mut ContactsState,
kimap: &kimap::Kimap,
http_server: &mut http::server::HttpServer,
) {
loop {
match await_message() {
Err(_send_error) => {
// ignore send errors, local-only process
continue;
}
Ok(Message::Request {
source,
body,
capabilities,
..
}) => {
// ignore messages from other nodes -- technically superfluous check
// since manifest does not acquire networking capability
if source.node() != state.our.node {
continue;
}
handle_request(&source, &body, capabilities, state, kimap, http_server);
}
_ => continue, // ignore responses
}
}
}
fn handle_request(
source: &Address,
body: &[u8],
capabilities: Vec<Capability>,
state: &mut ContactsState,
kimap: &kimap::Kimap,
http_server: &mut http::server::HttpServer,
) {
// source node is ALWAYS ourselves since networking is disabled
if source.process == "http_server:distro:sys" {
// receive HTTP requests and websocket connection messages from our server
let server_request = http_server.parse_request(body).unwrap();
http_server.handle_request(
server_request,
|req| handle_http_request(state, kimap, &req),
|_channel_id, _message_type, _blob| {
// we don't expect websocket messages
},
);
} else {
// if request is not from frontend, check that it has the required capabilities
let (response, blob) = handle_contacts_request(state, kimap, body, Some(capabilities));
let mut response = Response::new().body(response);
if let Some(blob) = blob {
response = response.blob(blob);
}
response.send().unwrap();
}
state.ws_update(http_server);
}
/// Handle HTTP requests from our own frontend.
fn handle_http_request(
state: &mut ContactsState,
kimap: &kimap::Kimap,
http_request: &http::server::IncomingHttpRequest,
) -> (http::server::HttpResponse, Option<LazyLoadBlob>) {
match http_request.method().unwrap().as_str() {
"GET" => (
http::server::HttpResponse::new(http::StatusCode::OK)
.header("Content-Type", "application/json"),
Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(state.contacts()).unwrap(),
)),
),
"POST" => {
let blob = get_blob().unwrap();
let (response, blob) = handle_contacts_request(state, kimap, blob.bytes(), None);
if let contacts::Response::Err(e) = response {
return (
http::server::HttpResponse::new(http::StatusCode::BAD_REQUEST)
.header("Content-Type", "application/json"),
Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&e).unwrap(),
)),
);
}
(
http::server::HttpResponse::new(http::StatusCode::OK)
.header("Content-Type", "application/json"),
match blob {
Some(blob) => Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&blob.bytes).unwrap(),
)),
None => None,
},
)
}
// Any other method will be rejected.
_ => (
http::server::HttpResponse::new(http::StatusCode::METHOD_NOT_ALLOWED),
None,
),
}
}
fn handle_contacts_request(
state: &mut ContactsState,
kimap: &kimap::Kimap,
request_bytes: &[u8],
capabilities: Option<Vec<Capability>>,
) -> (contacts::Response, Option<LazyLoadBlob>) {
let Ok(request) = serde_json::from_slice::<contacts::Request>(request_bytes) else {
return (
contacts::Response::Err("Malformed request".to_string()),
None,
);
};
// if request is not from frontend, check capabilities:
// each request requires one of read-name-only, read, add, or remove
if let Some(capabilities) = capabilities {
let required_capability = Capability::new(
&state.our,
serde_json::to_string(&match request {
contacts::Request::GetNames => contacts::Capability::ReadNameOnly,
contacts::Request::GetAllContacts | contacts::Request::GetContact(_) => {
contacts::Capability::Read
}
contacts::Request::AddContact(_) | contacts::Request::AddField(_) => {
contacts::Capability::Add
}
contacts::Request::RemoveContact(_) | contacts::Request::RemoveField(_) => {
contacts::Capability::Remove
}
})
.unwrap(),
);
if !capabilities.contains(&required_capability) {
return (
contacts::Response::Err("Missing capability".to_string()),
None,
);
}
}
match request {
contacts::Request::GetNames => (
contacts::Response::GetNames(
state
.contacts()
.0
.keys()
.map(|node| node.to_string())
.collect(),
),
None,
),
contacts::Request::GetAllContacts => (
contacts::Response::GetAllContacts,
Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(state.contacts()).unwrap(),
)),
),
contacts::Request::GetContact(node) => (
contacts::Response::GetContact,
Some(LazyLoadBlob::new(
Some("application/json"),
serde_json::to_vec(&state.get_contact(node)).unwrap(),
)),
),
contacts::Request::AddContact(node) => {
if let Some((response, blob)) = invalid_node(kimap, &node) {
return (response, blob);
}
state.add_contact(node);
(contacts::Response::AddContact, None)
}
contacts::Request::AddField((node, field, value)) => {
if let Some((response, blob)) = invalid_node(kimap, &node) {
return (response, blob);
}
let Ok(value) = serde_json::from_str::<serde_json::Value>(&value) else {
return (contacts::Response::Err("Malformed value".to_string()), None);
};
state.add_field(node, field, value);
(contacts::Response::AddField, None)
}
contacts::Request::RemoveContact(node) => {
state.remove_contact(node);
(contacts::Response::RemoveContact, None)
}
contacts::Request::RemoveField((node, field)) => {
state.remove_field(node, field);
(contacts::Response::RemoveField, None)
}
}
}
fn invalid_node(
kimap: &kimap::Kimap,
node: &str,
) -> Option<(contacts::Response, Option<LazyLoadBlob>)> {
if kimap
.get(&node)
.map(|(tba, _, _)| tba != eth::Address::ZERO)
.unwrap_or(false)
{
None
} else {
Some((
contacts::Response::Err("Node name invalid or does not exist".to_string()),
None,
))
}
}

View File

@ -0,0 +1,19 @@
[package]
name = "get-names"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.2"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -0,0 +1,35 @@
use crate::kinode::process::contacts;
use kinode_process_lib::{call_init, println, Address, Capability, Request};
wit_bindgen::generate!({
path: "target/wit",
world: "contacts-sys-v0",
generate_unused_types: true,
additional_derives: [serde::Deserialize, serde::Serialize, process_macros::SerdeJsonInto],
});
call_init!(init);
fn init(our: Address) {
let contacts_process = Address::from((our.node(), ("contacts", "contacts", "sys")));
let read_names_cap = Capability::new(
&contacts_process,
serde_json::to_string(&contacts::Capability::ReadNameOnly).unwrap(),
);
let Ok(Ok(response)) = Request::to(&contacts_process)
.body(contacts::Request::GetNames)
.capabilities(vec![read_names_cap])
.send_and_await_response(5)
else {
println!("did not receive expected response from contacts:contacts:sys");
return;
};
let Ok(contacts::Response::GetNames(names)) = response.body().try_into() else {
println!("did not receive GetNames response from contacts:contacts:sys");
return;
};
println!("{names:?}");
}

View File

@ -0,0 +1,18 @@
{
"name": "Contacts",
"description": "Save and manage your Kinode OS contacts.",
"image": "",
"properties": {
"package_name": "contacts",
"current_version": "0.1.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.1.0": ""
},
"wit_version": 0,
"dependencies": []
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -0,0 +1,20 @@
[
{
"process_name": "contacts",
"process_wasm_path": "/contacts.wasm",
"on_exit": "Restart",
"request_networking": false,
"request_capabilities": [
"eth:distro:sys",
"homepage:homepage:sys",
"http_server:distro:sys",
"vfs:distro:sys"
],
"grant_capabilities": [
"eth:distro:sys",
"http_server:distro:sys",
"vfs:distro:sys"
],
"public": false
}
]

View File

@ -0,0 +1,18 @@
{
"get_names.wasm": {
"root": false,
"public": false,
"request_networking": false,
"request_capabilities": [
"contacts:contacts:sys",
{
"process": "contacts:contacts:sys",
"params": "ReadNameOnly"
}
],
"grant_capabilities": [
"contacts:contacts:sys"
],
"wit_version": 0
}
}

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/kinode.css">
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap" rel="stylesheet" />
<script src="/our.js"></script>
<script>
document.title = "contacts - " + window.our.node;
</script>
<style>
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
li {
font-family: 'Kode Mono', monospace;
}
#title {
display: flex;
align-items: center;
padding: 20px;
max-width: 720px;
min-width: 300px;
margin: 0 auto;
}
#title h1 {
margin: 0;
flex-grow: 1;
text-align: right;
}
#title button {
margin-right: 20px;
}
#edit {
max-width: 720px;
margin: 0 auto;
}
#contacts-article {
margin: 20px;
}
main {
margin: 0 auto;
padding: 20px;
max-width: 960px;
min-width: 300px;
}
form.add-contact {
max-width: 400px;
}
#contacts {
list-style: none;
}
.contact:first-of-type {
margin-top: 10px;
}
.contact {
padding: 10px;
border: 1px solid var(--tasteful-dark);
border-radius: 5px;
margin-bottom: 10px;
display: grid;
gap: 10px;
grid-auto-flow: row;
grid-template-areas:
"name name delete"
"fields fields fields"
"add-field add-field add-field";
}
.contact h3 {
grid-area: name;
}
.contact ul {
grid-area: fields;
list-style: none;
max-width: 100%;
min-width: 0;
}
.contact ul li {
font-size: 1em;
}
form.delete-contact {
grid-area: delete;
}
form.add-field {
grid-area: add-field;
max-width: 400px;
}
.remove-field {
background-color: var(--tasteful-red);
font-size: 0.8em;
padding: 3px 10px;
}
</style>
</head>
<body>
<span id="title">
<button id="back-button"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 576 512"
height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<path
d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z">
</path>
</svg></button>
<h1>contacts</h1>
</span>
<main>
<article id="edit">
<form id="add-contact">
<input type="text" name="node" placeholder="node name (e.g. my-friend.os)">
<button type="submit">add new contact</button>
</form>
</article>
<article id="contacts-article">
<ul id="contacts"></ul>
</article>
<script src="/contacts:contacts:sys/script.js"></script>
</main>
</body>
</html>

View File

@ -0,0 +1,132 @@
const APP_PATH = '/contacts:contacts:sys/ask';
function api_call(body) {
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
}
function populate(data) {
console.log(data);
populate_contacts(data);
}
function populate_contacts(contacts) {
const ul = document.getElementById('contacts');
ul.innerHTML = '';
// sort contacts alphabetically by node
Object.entries(contacts).sort((a, b) => a[0].localeCompare(b[0])).forEach(([node, contact]) => {
const li = document.createElement('li');
const div = document.createElement('div');
div.classList.add('contact');
div.innerHTML = `<h3>${node}</h3>
<ul>
${Object.entries(contact).sort((a, b) => a[0].localeCompare(b[0])).map(([field, value]) => `
<li>
${field}: ${JSON.stringify(value)}
<button class="remove-field" onclick="removeField('${node}', '${field}')">X</button>
</li>
`).join('')}
</ul>
<form class="delete-contact" id="${node}">
<button type="submit">delete</button>
</form>
<form class="add-field" id="${node}">
<input type="text" name="field" placeholder="field (e.g. name)">
<input type="text" name="value" placeholder="value (e.g. John Doe)" title="Enter any valid JSON value (e.g. &quot;John Doe&quot;, 42, true, [1,2,3], {&quot;key&quot;:&quot;value&quot;})">
<button type="submit">add</button>
</form>
`;
li.appendChild(div);
ul.appendChild(li);
});
ul.querySelectorAll('.delete-contact').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const node = this.getAttribute('id');
api_call({
"RemoveContact": node
});
});
});
ul.querySelectorAll('.add-field').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const node = this.getAttribute('id');
const data = new FormData(e.target);
let value = data.get('value');
// if value is not valid JSON, wrap it in quotes
try {
JSON.parse(value);
} catch (e) {
// If parsing fails, assume it's a string and wrap it in quotes
value = `"${value}"`;
}
api_call({
"AddField": [node, data.get('field'), value]
});
});
});
}
document.getElementById('back-button').addEventListener('click', () => {
// set page to `/` while also removing the subdomain
const url = new URL(window.location.href);
if (url.hostname.split('.')[0] === 'contacts-sys') {
url.hostname = url.hostname.split('.').slice(1).join('.');
}
url.pathname = '/';
window.location.href = url.toString();
});
document.getElementById('add-contact').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const node = data.get('node');
const body = {
"AddContact": node
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => {
e.target.reset();
if (response.status === 200) {
return null;
} else {
return response.json();
}
}).then(data => {
if (data === null) {
return;
} else {
alert(JSON.stringify(data));
}
}).catch(error => {
console.error('Error:', error);
});
})
function removeField(node, field) {
api_call({
"RemoveField": [node, field]
});
}
// Setup WebSocket connection
const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(wsProtocol + location.host + "/contacts:contacts:sys/");
ws.onmessage = event => {
const data = JSON.parse(event.data);
populate(data);
};

View File

@ -1464,9 +1464,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -1476,9 +1476,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.5.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.5.0"

View File

@ -1462,9 +1462,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -11,7 +11,7 @@ anyhow = "1.0"
alloy-primitives = "0.7.0"
alloy-sol-types = "0.7.0"
hex = "0.4.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -1452,9 +1452,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0"
base64 = "0.22.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -1620,9 +1620,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
wit-bindgen = "0.24.0"
[lib]

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
wit-bindgen = "0.24.0"
[lib]

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
clap = "4.4"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = []
[dependencies]
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0"

View File

@ -5,6 +5,8 @@
"on_exit": "Restart",
"request_networking": true,
"request_capabilities": [
"app_store:app_store:sys",
"contacts:contacts:sys",
"chess:chess:sys",
"eth:distro:sys",
{

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
rand = "0.8"
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
clap = "4.4"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.24.0"

View File

@ -1452,9 +1452,9 @@ dependencies = [
[[package]]
name = "kinode_process_lib"
version = "0.9.1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c5b69ac1fc0cb457c7714ceb8c0a5bdbee4ee00b837f9f16ea711e902bdfe8"
checksum = "c257733fdc158b8223e43d92baeac02fe3d6a06b62953dbaea36e989f861b138"
dependencies = [
"alloy",
"alloy-primitives",

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = "0.9.1"
kinode_process_lib = "0.9.4"
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -231,6 +231,17 @@
}
});
});
// Check for redirect parameter on page load
document.addEventListener("DOMContentLoaded", () => {
const urlParams = new URLSearchParams(window.location.search);
const redirectPath = urlParams.get('redirect');
if (redirectPath) {
// Ensure the redirect path starts with a slash
const path = redirectPath.startsWith('/') ? redirectPath : '/' + redirectPath;
window.location.href = path;
}
});
</script>
</body>

View File

@ -798,7 +798,7 @@ pub async fn kernel(
// networking capabilities.
let Some(persisted) = process_map.get(&kernel_message.target.process) else {
t::Printout::new(
0,
2,
format!(
"event loop: got {} from network for {}, but process does not exist{}",
match kernel_message.message {

View File

@ -21,7 +21,6 @@ function App() {
const [reset, setReset] = useState<boolean>(false);
const [direct, setDirect] = useState<boolean>(false);
const [knsName, setKnsName] = useState<string>('');
const [appSizeOnLoad, setAppSizeOnLoad] = useState<number>(0);
const [networkingKey, setNetworkingKey] = useState<string>('');
const [ipAddress, setIpAddress] = useState<number>(0);
const [ws_port, setWsPort] = useState<number>(0);
@ -36,10 +35,6 @@ function App() {
const openConnect = () => setConnectOpen(true)
const closeConnect = () => setConnectOpen(false)
useEffect(() => setAppSizeOnLoad(
(window.performance.getEntriesByType('navigation') as any)[0].transferSize
), []);
useEffect(() => {
(async () => {
try {
@ -84,7 +79,7 @@ function App() {
// todo, most of these can be removed...
const props = {
direct, setDirect,
key, appSizeOnLoad,
key,
keyFileName, setKeyFileName,
reset, setReset,
pw, setPw,

View File

@ -21,7 +21,6 @@ export interface PageProps {
setReset: React.Dispatch<React.SetStateAction<boolean>>,
pw: string,
setPw: React.Dispatch<React.SetStateAction<string>>,
appSizeOnLoad: number,
nodeChainId: string,
}

View File

@ -7,13 +7,13 @@ import {
import { PageProps } from "../lib/types";
import Loader from "../components/Loader";
import { sha256, toBytes } from "viem";
import { redirectToHomepage } from "../utils/redirect-to-homepage";
interface ImportKeyfileProps extends PageProps { }
function ImportKeyfile({
pw,
setPw,
appSizeOnLoad,
}: ImportKeyfileProps) {
const [localKey, setLocalKey] = useState<Uint8Array | null>(null);
@ -70,24 +70,15 @@ function ImportKeyfile({
if (result.status > 399) {
throw new Error("Incorrect password");
}
redirectToHomepage();
const interval = setInterval(async () => {
const res = await fetch("/", { credentials: 'include' });
if (
res.status < 300 &&
Number(res.headers.get("content-length")) !== appSizeOnLoad
) {
clearInterval(interval);
window.location.replace("/");
}
}, 2000);
}
} catch {
window.alert("An error occurred, please try again.");
setLoading(false);
}
},
[localKey, pw, keyErrs, appSizeOnLoad]
[localKey, pw, keyErrs]
);
return (

View File

@ -4,13 +4,13 @@ import Loader from "../components/Loader";
import { useNavigate } from "react-router-dom";
import { sha256, toBytes } from "viem";
import { Tooltip } from "../components/Tooltip";
import { redirectToHomepage } from "../utils/redirect-to-homepage";
interface LoginProps extends PageProps { }
function Login({
pw,
setPw,
appSizeOnLoad,
routers,
setRouters,
knsName,
@ -57,23 +57,14 @@ function Login({
if (result.status > 399) {
throw new Error(await result.text());
}
redirectToHomepage();
const interval = setInterval(async () => {
const res = await fetch("/", { credentials: 'include' });
if (
res.status < 300 &&
Number(res.headers.get("content-length")) !== appSizeOnLoad
) {
clearInterval(interval);
window.location.replace("/");
}
}, 2000);
} catch (err: any) {
setKeyErrs([String(err)]);
setLoading("");
}
},
[pw, appSizeOnLoad]
[pw]
);
const isDirect = Boolean(routers?.length === 0);

View File

@ -5,6 +5,7 @@ import { Tooltip } from "../components/Tooltip";
import { sha256, toBytes } from "viem";
import { useSignTypedData, useAccount, useChainId } from 'wagmi'
import { KIMAP } from "../abis";
import { redirectToHomepage } from "../utils/redirect-to-homepage";
type SetPasswordProps = {
direct: boolean;
@ -12,7 +13,6 @@ type SetPasswordProps = {
reset: boolean;
knsName: string;
setPw: React.Dispatch<React.SetStateAction<string>>;
appSizeOnLoad: number;
nodeChainId: string;
closeConnect: () => void;
};
@ -23,7 +23,6 @@ function SetPassword({
pw,
reset,
setPw,
appSizeOnLoad,
}: SetPasswordProps) {
const [pw2, setPw2] = useState("");
const [error, setError] = useState("");
@ -103,25 +102,15 @@ function SetPassword({
const base64String = await result.json();
downloadKeyfile(knsName, base64String);
redirectToHomepage();
const interval = setInterval(async () => {
const res = await fetch("/", { credentials: 'include' });
if (
res.status < 300 &&
Number(res.headers.get("content-length")) !== appSizeOnLoad
) {
clearInterval(interval);
window.location.replace("/");
}
}, 2000);
} catch {
alert("There was an error setting your password, please try again.");
setLoading(false);
}
}, 500);
},
[appSizeOnLoad, direct, pw, pw2, reset, knsName]
[direct, pw, pw2, reset, knsName]
);
return (

View File

@ -0,0 +1,9 @@
export const redirectToHomepage = () => {
const interval = setInterval(async () => {
const res = await fetch("/version", { credentials: 'include' });
if (res.status == 200) {
clearInterval(interval);
window.location.replace("/");
}
}, 500);
};