Merge pull request #341 from kinode-dao/develop

Develop 0.7.3
This commit is contained in:
doria 2024-05-14 14:44:25 +09:00 committed by GitHub
commit 9222de17fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 1451 additions and 1022 deletions

98
Cargo.lock generated
View File

@ -78,7 +78,7 @@ name = "alias"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -649,7 +649,7 @@ dependencies = [
"alloy-sol-types 0.7.0", "alloy-sol-types 0.7.0",
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?rev=1ea6eda)", "kinode_process_lib 0.7.1",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
@ -1160,7 +1160,7 @@ name = "cat"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -1223,7 +1223,7 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.22.0", "base64 0.22.0",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"pleco", "pleco",
"serde", "serde",
"serde_json", "serde_json",
@ -1935,7 +1935,7 @@ name = "download"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -1966,7 +1966,7 @@ name = "echo"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -2186,7 +2186,7 @@ version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
@ -2346,7 +2346,7 @@ dependencies = [
name = "get_block" name = "get_block"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -2546,7 +2546,7 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
name = "hi" name = "hi"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -2576,7 +2576,7 @@ version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -2887,7 +2887,7 @@ name = "install"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -3151,52 +3151,8 @@ dependencies = [
[[package]] [[package]]
name = "kinode_process_lib" name = "kinode_process_lib"
version = "0.6.1" version = "0.7.1"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.6.1#37a20b0249dc2c86ae6c2c69cfb199fb177f1520" source = "git+https://github.com/kinode-dao/process_lib?tag=v0.7.2#61a8de975fd0a812a0a033ee0975fb83dd166224"
dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"alloy-primitives 0.6.4",
"alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"alloy-transport 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=6f8ebb4)",
"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.7.0"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.7.0#b7c0abee62df683e6c78069c87072a1d52602408"
dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-primitives 0.7.0",
"alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-transport 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"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.7.0"
source = "git+https://github.com/kinode-dao/process_lib?rev=1ea6eda#1ea6edad610b02a6fecb307c67dfa51fba0fb0de"
dependencies = [ dependencies = [
"alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)", "alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=cad7935)",
"alloy-primitives 0.7.0", "alloy-primitives 0.7.0",
@ -3292,7 +3248,7 @@ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"hex", "hex",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?rev=1ea6eda)", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"serde_json", "serde_json",
@ -3493,7 +3449,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
@ -3625,7 +3581,7 @@ dependencies = [
name = "namehash_to_name" name = "namehash_to_name"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"wit-bindgen", "wit-bindgen",
@ -3653,7 +3609,7 @@ dependencies = [
name = "net_diagnostics" name = "net_diagnostics"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"wit-bindgen", "wit-bindgen",
@ -3946,7 +3902,7 @@ dependencies = [
name = "peer" name = "peer"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"wit-bindgen", "wit-bindgen",
@ -3956,7 +3912,7 @@ dependencies = [
name = "peers" name = "peers"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"wit-bindgen", "wit-bindgen",
@ -4965,7 +4921,7 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.22.0", "base64 0.22.0",
"bincode", "bincode",
"kinode_process_lib 0.6.1", "kinode_process_lib 0.7.1",
"rmp-serde", "rmp-serde",
"serde", "serde",
"serde_json", "serde_json",
@ -5184,7 +5140,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
name = "state" name = "state"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -5350,7 +5306,7 @@ version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"serde", "serde",
@ -5364,7 +5320,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -5378,7 +5334,7 @@ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"indexmap", "indexmap",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -5619,7 +5575,7 @@ name = "top"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -5941,7 +5897,7 @@ name = "uninstall"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"wit-bindgen", "wit-bindgen",
@ -6636,7 +6592,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)", "kinode_process_lib 0.7.1",
"serde", "serde",
"serde_json", "serde_json",
"url", "url",

View File

@ -11,7 +11,7 @@ alloy-primitives = "0.7.0"
alloy-sol-types = "0.7.0" alloy-sol-types = "0.7.0"
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "1ea6eda" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8" rand = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8" rand = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,8 +14,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/main:app_store:sys/assets/index-kpw1YN6W.js"></script> <script type="module" crossorigin src="/main:app_store:sys/assets/index-BxGs27ah.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-i4SytJ9j.css"> <link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-umttRNrr.css">
</head> </head>
<body> <body>

View File

@ -1,5 +1,5 @@
{ {
"name": "kit-ui", "name": "kinode-app-store",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",

View File

@ -110,7 +110,7 @@ function App() {
const props = { provider, packageAbi }; const props = { provider, packageAbi };
return ( return (
<div className="flex flex-col c h-screen w-screen"> <div className="flex flex-col c h-screen w-screen max-h-screen max-w-screen overflow-x-hidden special-appstore-background">
<Web3ReactProvider connectors={connectors}> <Web3ReactProvider connectors={connectors}>
<Router basename={BASE_URL}> <Router basename={BASE_URL}>
<Routes> <Routes>

View File

@ -1,27 +1,19 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps"; import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store"; import UpdateButton from "./UpdateButton";
import Modal from "./Modal"; import DownloadButton from "./DownloadButton";
import { getAppName } from "../utils/app"; import InstallButton from "./InstallButton";
import Loader from "./Loader"; import LaunchButton from "./LaunchButton";
import classNames from "classnames"; import { FaCheck } from "react-icons/fa6";
import { useNavigate } from "react-router-dom";
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> { interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo; app: AppInfo;
isIcon?: boolean;
} }
export default function ActionButton({ app, ...props }: ActionButtonProps) { export default function ActionButton({ app, isIcon = false, ...props }: ActionButtonProps) {
const { updateApp, downloadApp, installApp, getCaps, getMyApp } = const [incrementNumber, setIncrementNumber] = useState(0);
useAppsStore(); const { installed, downloaded, updatable } = useMemo(() => {
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [caps, setCaps] = useState<string[]>([]);
const [launchPath, setLaunchPath] = useState('');
const [loading, setLoading] = useState("");
const { clean, installed, downloaded, updatable } = useMemo(() => {
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {}); const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1]; const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
@ -31,217 +23,46 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
const updatable = const updatable =
Boolean(app.state?.our_version && latestHash) && Boolean(app.state?.our_version && latestHash) &&
app.state?.our_version !== latestHash && app.state?.our_version !== latestHash &&
app.publisher !== window.our.node; app.publisher !== (window as any).our.node;
return { return {
clean: !installed && !downloaded && !updatable,
installed, installed,
downloaded, downloaded,
updatable, updatable,
}; };
}, [app]); }, [app, incrementNumber]);
const [launchPath, setLaunchPath] = useState('');
useEffect(() => { useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
useEffect(() => {
if (installed) {
fetch('/apps').then(data => data.json()) fetch('/apps').then(data => data.json())
.then((data: Array<{ package_name: string, path: string }>) => { .then((data: Array<{ package_name: string, path: string }>) => {
// console.log(data)
if (Array.isArray(data)) { if (Array.isArray(data)) {
// console.log('is array')
const homepageAppData = data.find(otherApp => app.package === otherApp.package_name) const homepageAppData = data.find(otherApp => app.package === otherApp.package_name)
if (homepageAppData) { if (homepageAppData) {
// console.log('found the good appness', homepageAppData.package_name, homepageAppData.path);
setLaunchPath(homepageAppData.path) setLaunchPath(homepageAppData.path)
} }
} }
}) })
} }, [app, incrementNumber])
}, [installed])
const onClick = useCallback(async () => {
if (installed && !updatable && launchPath) {
window.location.href = `/${launchPath.replace('/', '')}`
return;
} else {
if (downloaded) {
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
}
setShowModal(true);
}
}, [app, installed, downloaded, updatable, setShowModal, getCaps, launchPath]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
const appName = getAppName(app);
return ( return (
<> <>
<button {(installed && launchPath)
{...props} ? <LaunchButton app={app} {...props} isIcon={isIcon} launchPath={launchPath} />
type="button" : (installed && updatable)
className={classNames("text-sm min-w-[100px] px-2 py-1 self-start", props.className, { ? <UpdateButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
'bg-orange': installed, : !downloaded
'hidden': installed && !updatable && !launchPath ? <DownloadButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
})} : !installed
onClick={onClick} ? <InstallButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: isIcon
? <button
className="pointer-events none icon clear absolute top-0 right-0"
> >
{installed && updatable <FaCheck />
? "Update"
: installed && launchPath
? "Launch"
: installed
? "Installed"
: downloaded
? "Install"
: "Download"}
</button> </button>
<Modal show={showModal} hide={() => setShowModal(false)}> : <div>Installed</div>}
{loading ? (
<Loader msg={loading} />
) : clean ? (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5 style={{ margin: 0 }}>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
) : downloaded ? (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
</> </>
); );
} }

View File

@ -4,25 +4,40 @@ import AppHeader from "./AppHeader";
import ActionButton from "./ActionButton"; import ActionButton from "./ActionButton";
import { AppInfo } from "../types/Apps"; import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app"; import { appId } from "../utils/app";
import MoreActions from "./MoreActions";
import { isMobileCheck } from "../utils/dimensions"; import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames"; import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { APP_DETAILS_PATH } from "../constants/path";
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> { interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo; app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large";
} }
export default function AppEntry({ app, ...props }: AppEntryProps) { export default function AppEntry({ app, size = "medium", overrideImageSize, ...props }: AppEntryProps) {
const isMobile = isMobileCheck() const isMobile = isMobileCheck()
const navigate = useNavigate()
return ( return (
<div {...props} key={appId(app)} className={classNames("flex justify-between w-full rounded hover:bg-white/10 card", { <div
'flex-wrap gap-2': isMobile {...props}
})}> key={appId(app)}
<AppHeader app={app} size="small" /> className={classNames("flex justify-between rounded-lg hover:bg-white/10 card cursor-pointer", props.className, {
<div className="flex mr-1 items-start"> 'flex-wrap gap-2': isMobile,
<ActionButton app={app} className="mr-2" /> 'flex-col relative': size !== 'large'
<MoreActions app={app} /> })}
</div> onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
>
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
<ActionButton
app={app}
isIcon={size !== 'large'}
className={classNames({
'absolute top-0 right-0': size !== 'large',
'bg-orange text-lg min-w-1/5': size === 'large',
'ml-auto': size === 'large' && isMobile
})} />
</div> </div>
); );
} }

View File

@ -1,54 +1,84 @@
import React from "react"; import React from "react";
import { AppInfo } from "../types/Apps"; import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app"; import { appId } from "../utils/app";
import { useNavigate } from "react-router-dom";
import classNames from "classnames"; import classNames from "classnames";
import { APP_DETAILS_PATH } from "../constants/path";
import ColorDot from "./ColorDot"; import ColorDot from "./ColorDot";
import { isMobileCheck } from "../utils/dimensions"; import { isMobileCheck } from "../utils/dimensions";
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> { interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo; app: AppInfo;
size?: "small" | "medium" | "large"; size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large"
} }
export default function AppHeader({ export default function AppHeader({
app, app,
size = "medium", size = "medium",
overrideImageSize,
...props ...props
}: AppHeaderProps) { }: AppHeaderProps) {
const navigate = useNavigate()
const isMobile = isMobileCheck() const isMobile = isMobileCheck()
return ( const appName = <div
<div className={classNames({
{...props} 'text-3xl font-[OpenSans]': !isMobile && size === 'large',
className={classNames('flex w-full justify-content-start', size, props.className, { 'cursor-pointer': size !== 'large' })} 'text-xl': !isMobile && size !== 'large',
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)} 'text-lg': isMobile
})}
> >
{app.metadata?.name || appId(app)}
</div>
const imageSize = overrideImageSize || size
return <div
{...props}
className={classNames('flex w-full justify-content-start', size, props.className, {
'flex-col': size === 'small',
'gap-2': isMobile,
'gap-4': !isMobile,
'gap-6': !isMobile && size === 'large'
})}
>
{size === 'small' && appName}
{app.metadata?.image {app.metadata?.image
? <img ? <img
src={app.metadata.image} src={app.metadata.image}
alt="app icon" alt="app icon"
className={classNames('mr-2', { 'h-32 rounded-md': size === 'large', 'h-12 rounded': size !== 'large' })} className={classNames('object-cover', {
'rounded': !imageSize,
'rounded-lg': imageSize === 'small',
'rounded-xl': imageSize === 'medium',
'rounded-2xl': imageSize === 'large',
'h-32': imageSize === 'large' || imageSize === 'small',
'h-20': imageSize === 'medium',
})}
/> />
: <ColorDot : <ColorDot
num={app.metadata_hash} num={app.metadata_hash}
dotSize={size} dotSize={imageSize}
className={classNames('mr-2')}
/>} />}
<div className={classNames("flex flex-col", { 'gap-2 max-w-3/4': isMobile })}> <div className={classNames("flex flex-col", {
'gap-2': isMobile,
'gap-4 max-w-3/4': isMobile && size !== 'small'
})}>
{size !== 'small' && appName}
{app.metadata?.description && (
<div <div
className={classNames("whitespace-nowrap overflow-hidden text-ellipsis", { 'text-3xl': size === 'large', })} style={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
className={classNames({
'text-2xl': size === 'large'
})}
> >
{app.metadata?.name || appId(app)} {app.metadata.description}
</div>
{app.metadata?.description && size !== "large" && (
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
{app.metadata?.description?.slice(0, 100)}
</div> </div>
)} )}
</div> </div>
</div> </div>
);
} }

View File

@ -33,8 +33,8 @@ const ColorDot: React.FC<ColorDotProps> = ({
<div {...props} className={classNames('flex', props.className)}> <div {...props} className={classNames('flex', props.className)}>
<div <div
className={classNames('m-0 align-self-center border rounded-full outline-black', { className={classNames('m-0 align-self-center border rounded-full outline-black', {
'h-20 w-20': !isMobile && dotSize === 'large', 'h-32 w-32': !isMobile && dotSize === 'large',
'h-16 w-16': !isMobile && dotSize === 'medium', 'h-18 w-18': !isMobile && dotSize === 'medium',
'h-12 w-12': isMobile || dotSize === 'small', 'h-12 w-12': isMobile || dotSize === 'small',
'border-4': !isMobile, 'border-4': !isMobile,
'border-2': isMobile, 'border-2': isMobile,

View File

@ -0,0 +1,111 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { FaDownload } from "react-icons/fa6";
interface DownloadButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function DownloadButton({ app, isIcon = false, callback, ...props }: DownloadButtonProps) {
const { downloadApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [loading, setLoading] = useState("");
useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setShowModal(true);
}, [app, setShowModal, getCaps]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const appName = getAppName(app);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, { 'icon clear': isIcon })}
onClick={onClick}
>
{isIcon ? <FaDownload /> : 'Download'}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,19 @@
import classNames from "classnames";
import React from "react"
import { FaHome } from "react-icons/fa"
import { isMobileCheck } from "../utils/dimensions";
const HomeButton: React.FC = () => {
const isMobile = isMobileCheck()
return <button
className={classNames("clear absolute p-2", {
'top-2 left-2': isMobile,
'top-8 left-8': !isMobile
})}
onClick={() => window.location.href = '/'}
>
<FaHome size={24} />
</button>
}
export default HomeButton;

View File

@ -0,0 +1,88 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { FaI } from "react-icons/fa6";
interface InstallButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function InstallButton({ app, isIcon = false, callback, ...props }: InstallButtonProps) {
const { installApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onClick}
>
{isIcon ? <FaI /> : "Install"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,33 @@
import React, { useCallback, useEffect, useState } from "react";
import { AppInfo } from "../types/Apps";
import classNames from "classnames";
import { FaPlay } from "react-icons/fa6";
interface LaunchButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
launchPath: string;
isIcon?: boolean;
}
export default function LaunchButton({ app, launchPath, isIcon = false, ...props }: LaunchButtonProps) {
const onLaunch = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
window.location.href = `/${launchPath.replace('/', '')}`
return;
}, [app, launchPath]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onLaunch}
>
{isIcon ? <FaPlay /> : "Launch"}
</button>
</>
);
}

View File

@ -27,7 +27,7 @@ const Modal: React.FC<ModalProps> = ({
return ( return (
<div <div
className={classNames(`bg-black/25 fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`, className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`,
{ show } { show }
)} )}
onClick={hide} onClick={hide}

View File

@ -5,11 +5,12 @@ import {
FaDownload, FaDownload,
FaMagnifyingGlass, FaMagnifyingGlass,
FaUpload, FaUpload,
FaX,
} from "react-icons/fa6"; } from "react-icons/fa6";
import { MY_APPS_PATH, PUBLISH_PATH } from "../constants/path"; import { MY_APPS_PATH, PUBLISH_PATH } from "../constants/path";
import classNames from "classnames"; import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "./HomeButton";
import { FaHome } from "react-icons/fa"; import { FaHome } from "react-icons/fa";
interface SearchHeaderProps { interface SearchHeaderProps {
@ -34,67 +35,73 @@ export default function SearchHeader({
const canGoBack = location.key !== "default"; const canGoBack = location.key !== "default";
const isMyAppsPage = location.pathname === MY_APPS_PATH; const isMyAppsPage = location.pathname === MY_APPS_PATH;
const isMobile = isMobileCheck()
return ( return (
<div className="flex justify-between"> <div className={classNames("flex justify-between", {
{location.pathname !== '/' ? ( "gap-4": isMobile,
<button className="flex flex-col c mr-2 icon" onClick={() => { "gap-8": !isMobile
})}>
{location.pathname !== '/'
? <button
className="flex flex-col c icon icon-orange"
onClick={() => {
if (onBack) { if (onBack) {
onBack() onBack()
} else { } else {
canGoBack ? navigate(-1) : navigate('/') canGoBack ? navigate(-1) : navigate('/')
} }
}}> }}
>
<FaArrowLeft /> <FaArrowLeft />
</button> </button>
) : ( : isMobile
<button ? <button
className="flex flex-col c mr-2 icon" className={classNames("icon icon-orange", {
})}
onClick={() => window.location.href = '/'} onClick={() => window.location.href = '/'}
> >
<FaHome /> <FaHome />
</button> </button>
)} : <></>}
{!hidePublish && <button {!hidePublish && <button
className="flex flex-col c mr-2 icon" className="flex flex-col c icon icon-orange"
onClick={() => navigate(PUBLISH_PATH)} onClick={() => navigate(PUBLISH_PATH)}
> >
<FaUpload /> <FaUpload />
</button>} </button>}
{!hideSearch && ( {!hideSearch && (
<div className="flex mr-2 flex-1 rounded-md"> <div className="flex flex-1 rounded-md relative">
<button
className="icon mr-2"
type="button"
onClick={() => inputRef.current?.focus()}
>
<FaMagnifyingGlass />
</button>
<input <input
type="text" type="text"
ref={inputRef} ref={inputRef}
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
value={value} value={value}
placeholder="Search for apps..." placeholder="Search for apps..."
className="w-full mr-2" className="w-full self-stretch grow"
/> />
{value.length > 0 && <button
className="icon"
onClick={() => onChange("")}
>
<FaX />
</button>}
</div>
)}
<div className="flex">
<button <button
className={classNames("flex alt")} className={classNames("icon border-0 absolute top-1/2 -translate-y-1/2", {
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))} 'right-2': isMobile,
'right-4': !isMobile
})}
type="button"
onClick={() => inputRef.current?.focus()}
> >
<FaDownload className="mr-2" /> <FaMagnifyingGlass />
<span>My Apps</span>
</button> </button>
</div> </div>
)}
<button
className={classNames("flex c", {
"gap-4": isMobile,
"gap-8 basis-1/5": !isMobile
})}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
{!isMobile && <span>My Apps</span>}
<FaDownload />
</button>
</div> </div>
); );
} }

View File

@ -0,0 +1,94 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void
}
export default function UpdateButton({ app, callback, isIcon = false, ...props }: UpdateButtonProps) {
const { updateApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className)}
onClick={onClick}
>
Update
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -60,7 +60,7 @@ h6 {
button, button,
button[type="submit"], button[type="submit"],
.button { .button {
@apply flex m-0 py-2 px-6 rounded border-orange bg-orange border-2 cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans]; @apply flex m-0 py-2 px-6 rounded border-orange bg-orange border cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans];
} }
.clear { .clear {
@ -80,7 +80,15 @@ button[type="submit"],
} }
.icon.alt { .icon.alt {
@apply border-black/25 hover:border-white/25 @apply border-black/25 hover:border-white/25;
}
.icon.icon-orange {
@apply border-orange bg-orange/25;
}
.icon.clear {
@apply border-0;
} }
body { body {
@ -98,7 +106,7 @@ textarea,
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="checkbox"] { input[type="checkbox"] {
@apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-orange border-2; @apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-solid border-orange;
} }
input[type="checkbox"] { input[type="checkbox"] {
@ -133,3 +141,8 @@ button:disabled {
.c { .c {
@apply place-items-center place-content-center; @apply place-items-center place-content-center;
} }
.special-appstore-background {
background-color: #22211f;
background-image: radial-gradient(circle at -20% -68%, #f75a2991 36%, transparent 56.05%), linear-gradient(37deg, #86000185 19%, transparent 45.05%), linear-gradient(-36deg, #8600016e 26%, transparent 50.05%);
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useCallback } from "react"; import React, { useState, useEffect, useMemo, useCallback, ReactElement } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { AppInfo } from "../types/Apps"; import { AppInfo } from "../types/Apps";
@ -9,6 +9,10 @@ import SearchHeader from "../components/SearchHeader";
import { PageProps } from "../types/Page"; import { PageProps } from "../types/Page";
import { appId } from "../utils/app"; import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path"; import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
import { FaGlobe, FaPeopleGroup, FaStar } from "react-icons/fa6";
interface AppPageProps extends PageProps { } interface AppPageProps extends PageProps { }
@ -48,80 +52,104 @@ export default function AppPage() {
app?.state?.our_version || app?.state?.our_version ||
(versions[(versions.length || 1) - 1] || ["", ""])[1]; (versions[(versions.length || 1) - 1] || ["", ""])[1];
const isMobile = isMobileCheck()
const appDetails: Array<{ top: ReactElement, middle: ReactElement, bottom: ReactElement }> = [
{
top: <div className={classNames({ 'text-sm': isMobile })}>0 ratings</div>,
middle: <span className="text-2xl">5.0</span>,
bottom: <div className={classNames("flex-center gap-1", {
'text-sm': isMobile
})}>
<FaStar />
<FaStar />
<FaStar />
<FaStar />
<FaStar />
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Developer</div>,
middle: <FaPeopleGroup size={36} />,
bottom: <div className={classNames({ 'text-sm': isMobile })}>
{app?.publisher}
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Version</div>,
middle: <span className="text-2xl">{version}</span>,
bottom: <div className={classNames({ 'text-xs': isMobile })}>
{hash.slice(0, 5)}...{hash.slice(-5)}
</div>
},
{
top: <div className={classNames({ 'text-sm': isMobile })}>Mirrors</div>,
middle: <FaGlobe size={36} />,
bottom: <div className={classNames({ 'text-sm': isMobile })}>
{app?.metadata?.properties?.mirrors?.length || 0}
</div>
}
]
return ( return (
<div className="flex flex-col w-full max-w-[900px]"> <div className={classNames("flex flex-col w-full h-screen",
<SearchHeader value="" onChange={() => null} hideSearch /> {
<div className="card mt1"> 'gap-4 p-2 max-w-screen': isMobile,
{app ? ( 'gap-8 max-w-[900px]': !isMobile,
<> })}
<div className="flex justify-between"> >
<AppHeader app={app} size="large" /> {!isMobile && <HomeButton />}
<ActionButton app={app} className="mr-1" /> <SearchHeader
value=""
onChange={() => null}
hideSearch
hidePublish
/>
<div className={classNames("flex-col-center card !rounded-3xl", {
'p-12 gap-4 grow overflow-y-auto': isMobile,
'p-24 gap-8': !isMobile,
})}>
{app ? <>
<AppHeader app={app} size={isMobile ? "medium" : "large"} />
<div className="w-5/6 h-0 border border-orange" />
<div className={classNames("flex items-start text-xl", {
'gap-4 flex-wrap': isMobile,
'gap-8': !isMobile,
})}>
{appDetails.map((detail, index) => <>
<div
className={classNames("flex-col-center gap-2 justify-between self-stretch", {
'rounded-lg bg-white/10 p-1 min-w-1/4 grow': isMobile,
'opacity-50': !isMobile,
})}
key={index}
>
{detail.top}
{detail.middle}
{detail.bottom}
</div> </div>
<div className="flex flex-col mt-2"> {!isMobile && index !== appDetails.length - 1 && <div className="h-3/4 w-0 border border-orange self-center" />}
<div className="flex mt-1 items-start"> </>)}
<div className="w-1/4">Description</div>
<div className="mb-1 w-3/4">
{(app.metadata?.description || "No description given").slice(
0,
2000
)}
</div> </div>
</div> {Array.isArray(app.metadata?.properties?.screenshots)
<div className="flex mt-1 items-start"> && app.metadata?.properties.screenshots.length > 0
<div className="w-1/4">Publisher</div> && <div className="flex flex-wrap overflow-x-auto max-w-full">
<div className="mb-1 w-3/4">{app.publisher}</div> {app.metadata.properties.screenshots.map(
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Version</div>
<div className="mb-1 w-3/4">{version}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Mirrors</div>
<div className="w-3/4 flex flex-col">
{(app.metadata?.properties?.mirrors || []).map(
(mirror, index) => (
<div key={index + mirror} className="mb-1">
{mirror}
</div>
)
)}
</div>
</div>
{/* <div className="flex mt-1 items-start">
<div className="w-1/4">Permissions</div>
<div className="w-3/4 flex flex-col">
{app.permissions?.map((permission, index) => (
<div key={index + permission} className="mb-1">{permission}</div>
))}
</div>
</div> */}
<div className="flex mt-1 items-start">
<div className="w-1/4">Hash</div>
<div className="w-3/4 break-all">
{hash}
</div>
</div>
</div>
<div className="app-screenshots flex mt-2 overflow-x-auto max-w-full">
{(app.metadata?.properties?.screenshots || []).map(
(screenshot, index) => ( (screenshot, index) => (
<img key={index + screenshot} src={screenshot} className="mr-2 max-h-20 max-w-full rounded border border-black" /> <img key={index + screenshot} src={screenshot} className="mr-2 max-h-20 max-w-full rounded border border-black" />
) )
)} )}
</div> </div>}
{app.installed && ( <ActionButton app={app} className={classNames("self-center bg-orange text-lg px-12")} />
{app.installed && app.state?.mirroring && (
<button type="button" onClick={goToPublish}> <button type="button" onClick={goToPublish}>
Publish Publish
</button> </button>
)} )}
</> </> : <>
) : (
<>
<h4>App details not found for </h4> <h4>App details not found for </h4>
<h4>{params.id}</h4> <h4>{params.id}</h4>
</> </>}
)}
</div> </div>
</div> </div>
); );

View File

@ -9,6 +9,7 @@ import { PageProps } from "../types/Page";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { appId } from "../utils/app"; import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path"; import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
export default function MyAppsPage() { // eslint-disable-line export default function MyAppsPage() { // eslint-disable-line
const { myApps, getMyApps } = useAppsStore() const { myApps, getMyApps } = useAppsStore()
@ -53,6 +54,7 @@ export default function MyAppsPage() { // eslint-disable-line
return ( return (
<div className="flex flex-col w-full max-w-[900px]"> <div className="flex flex-col w-full max-w-[900px]">
<HomeButton />
<SearchHeader value={searchQuery} onChange={searchMyApps} /> <SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="flex justify-between items-center mt-2"> <div className="flex justify-between items-center mt-2">
<h3>My Packages</h3> <h3>My Packages</h3>

View File

@ -16,6 +16,9 @@ import { AppInfo } from "../types/Apps";
import Checkbox from "../components/Checkbox"; import Checkbox from "../components/Checkbox";
import Jazzicon from "../components/Jazzicon"; import Jazzicon from "../components/Jazzicon";
import { Tooltip } from "../components/Tooltip"; import { Tooltip } from "../components/Tooltip";
import HomeButton from "../components/HomeButton";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
const { useIsActivating } = hooks; const { useIsActivating } = hooks;
@ -207,16 +210,22 @@ export default function PublishPage({
} }
}, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]); }, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]);
const isMobile = isMobileCheck()
return ( return (
<div className="max-w-[900px] w-full"> <div className={classNames("w-full flex flex-col gap-2", {
'max-w-[900px]': !isMobile,
'p-2 h-screen w-screen': isMobile
})}>
{!isMobile && <HomeButton />}
<SearchHeader <SearchHeader
hideSearch hideSearch
hidePublish hidePublish
onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined} onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined}
/> />
<div className="flex justify-between items-center my-2"> <div className="flex-center justify-between">
<h4>Publish Package</h4> <h4>Publish Package</h4>
{Boolean(account) && <div className="card flex items-center"> {Boolean(account) && <div className="card flex-center">
<span>Publishing as:</span> <span>Publishing as:</span>
<Jazzicon address={account!} className="mx-2" /> <Jazzicon address={account!} className="mx-2" />
<span className="font-mono">{account?.slice(0, 4)}...{account?.slice(-4)}</span> <span className="font-mono">{account?.slice(0, 4)}...{account?.slice(-4)}</span>
@ -224,20 +233,20 @@ export default function PublishPage({
</div> </div>
{loading ? ( {loading ? (
<div className="flex flex-col items-center"> <div className="flex-col-center">
<Loader msg={loading} /> <Loader msg={loading} />
</div> </div>
) : publishSuccess ? ( ) : publishSuccess ? (
<div className="flex flex-col items-center"> <div className="flex-col-center gap-2">
<h4 className="mb-2">Package Published!</h4> <h4>Package Published!</h4>
<div className="mb-2"> <div>
<strong>Package Name:</strong> {publishSuccess.packageName} <strong>Package Name:</strong> {publishSuccess.packageName}
</div> </div>
<div className="mb-2"> <div>
<strong>Publisher ID:</strong> {publishSuccess.publisherId} <strong>Publisher ID:</strong> {publishSuccess.publisherId}
</div> </div>
<button <button
className={`flex ml-2 mt-2`} className={`flex ml-2`}
onClick={() => setPublishSuccess(undefined)} onClick={() => setPublishSuccess(undefined)}
> >
Publish Another Package Publish Another Package
@ -247,7 +256,7 @@ export default function PublishPage({
<MetadataForm {...{ packageName, publisherId, app: state?.app }} goBack={() => setShowMetadataForm(false)} /> <MetadataForm {...{ packageName, publisherId, app: state?.app }} goBack={() => setShowMetadataForm(false)} />
) : !account || !isActive ? ( ) : !account || !isActive ? (
<> <>
<h4 style={{}}>Please connect your wallet to publish a package</h4> <h4>Please connect your wallet {isMobile && <br />} to publish a package</h4>
<button className={`connect-wallet row`} onClick={connectWallet}> <button className={`connect-wallet row`} onClick={connectWallet}>
Connect Wallet Connect Wallet
</button> </button>
@ -256,7 +265,7 @@ export default function PublishPage({
<Loader msg="Approve connection in your wallet" /> <Loader msg="Approve connection in your wallet" />
) : ( ) : (
<form <form
className="flex flex-col flex-1 overflow-y-auto" className="flex flex-col flex-1 overflow-y-auto gap-2"
onSubmit={publishPackage} onSubmit={publishPackage}
> >
<div <div
@ -270,7 +279,7 @@ export default function PublishPage({
Update existing package Update existing package
</label> </label>
</div> </div>
<div className="flex flex-col mb-2"> <div className="flex flex-col">
<label htmlFor="package-name">Package Name</label> <label htmlFor="package-name">Package Name</label>
<input <input
id="package-name" id="package-name"
@ -282,7 +291,7 @@ export default function PublishPage({
onBlur={checkIfUpdate} onBlur={checkIfUpdate}
/> />
</div> </div>
<div className="flex flex-col mb-2"> <div className="flex flex-col">
<label htmlFor="publisher-id">Publisher ID</label> <label htmlFor="publisher-id">Publisher ID</label>
<input <input
id="publisher-id" id="publisher-id"
@ -293,7 +302,7 @@ export default function PublishPage({
onBlur={checkIfUpdate} onBlur={checkIfUpdate}
/> />
</div> </div>
<div className="flex flex-col mb-2"> <div className="flex flex-col gap-2">
<label htmlFor="metadata-url"> <label htmlFor="metadata-url">
Metadata URL Metadata URL
</label> </label>
@ -306,7 +315,7 @@ export default function PublishPage({
onBlur={calculateMetadataHash} onBlur={calculateMetadataHash}
placeholder="https://github/my-org/my-repo/metadata.json" placeholder="https://github/my-org/my-repo/metadata.json"
/> />
<div className="mt-2"> <div>
Metadata is a JSON file that describes your package. Metadata is a JSON file that describes your package.
<br /> You can{" "} <br /> You can{" "}
<a onClick={() => setShowMetadataForm(true)} <a onClick={() => setShowMetadataForm(true)}
@ -317,7 +326,7 @@ export default function PublishPage({
. .
</div> </div>
</div> </div>
<div className="flex flex-col mb-2"> <div className="flex flex-col">
<label htmlFor="metadata-hash">Metadata Hash</label> <label htmlFor="metadata-hash">Metadata Hash</label>
<input <input
readOnly readOnly
@ -334,7 +343,7 @@ export default function PublishPage({
</form> </form>
)} )}
<div className="flex flex-col my-2 mt-4"> <div className="flex flex-col">
<h4>Packages You Own</h4> <h4>Packages You Own</h4>
{myPublishedApps.length > 0 ? ( {myPublishedApps.length > 0 ? (
<div className="flex flex-col"> <div className="flex flex-col">
@ -344,11 +353,9 @@ export default function PublishPage({
<Jazzicon address={app.publisher} className="mr-2" /> <Jazzicon address={app.publisher} className="mr-2" />
<span>{app.package}</span> <span>{app.package}</span>
</div> </div>
{/* <Tooltip content="View Package"> */}
<button className="flex items-center" onClick={() => unpublishPackage(app.package, app.publisher)}> <button className="flex items-center" onClick={() => unpublishPackage(app.package, app.publisher)}>
<span>Unpublish</span> <span>Unpublish</span>
</button> </button>
{/* </Tooltip> */}
</div> </div>
))} ))}
</div> </div>

View File

@ -9,6 +9,8 @@ import { PageProps } from "../types/Page";
import { appId } from "../utils/app"; import { appId } from "../utils/app";
import classNames from 'classnames'; import classNames from 'classnames';
import { FaArrowRotateRight } from "react-icons/fa6"; import { FaArrowRotateRight } from "react-icons/fa6";
import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "../components/HomeButton";
interface StorePageProps extends PageProps { } interface StorePageProps extends PageProps { }
@ -21,6 +23,7 @@ export default function StorePage() {
const [searchQuery, setSearchQuery] = useState<string>(""); const [searchQuery, setSearchQuery] = useState<string>("");
const [displayedApps, setDisplayedApps] = useState<AppInfo[]>(listedApps); const [displayedApps, setDisplayedApps] = useState<AppInfo[]>(listedApps);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [tags, setTags] = useState<string[]>([])
const pages = useMemo( const pages = useMemo(
() => () =>
@ -42,6 +45,14 @@ export default function StorePage() {
getListedApps() getListedApps()
.then((apps) => { .then((apps) => {
setDisplayedApps(Object.values(apps)); setDisplayedApps(Object.values(apps));
let _tags: string[] = [];
for (const app of Object.values(apps)) {
_tags = _tags.concat((app.metadata as any || {}).tags || [])
}
if (_tags.length === 0) {
_tags = ['App', 'Tags', 'Coming', 'Soon', 'tm'];
}
setTags(Array.from(new Set(_tags)))
}) })
.catch((error) => console.error(error)); .catch((error) => console.error(error));
}, []); // eslint-disable-line }, []); // eslint-disable-line
@ -112,25 +123,48 @@ export default function StorePage() {
} }
}, [rebuildIndex]); }, [rebuildIndex]);
const isMobile = isMobileCheck()
return ( return (
<div className="max-w-[900px] w-full"> <div className={classNames("flex flex-col w-full max-h-screen", {
'gap-4 max-w-screen p-2': isMobile,
'gap-6 max-w-[900px]': !isMobile
})}>
{!isMobile && <HomeButton />}
<SearchHeader value={searchQuery} onChange={searchApps} /> <SearchHeader value={searchQuery} onChange={searchApps} />
<div className="flex justify-between items-center my-2 mx-0"> <div className={classNames("flex items-center self-stretch justify-between", {
<h4>New</h4> 'gap-4 flex-wrap': isMobile,
'gap-8 grow': !isMobile
})}>
<button <button
className="flex flex-col c mr-auto ml-1 icon" className="flex flex-col c icon icon-orange"
onClick={tryRebuildIndex} onClick={tryRebuildIndex}
title="Rebuild index" title="Rebuild index"
> >
<FaArrowRotateRight /> <FaArrowRotateRight />
</button> </button>
{tags.slice(0, isMobile ? 3 : 6).map(tag => (
<button
key={tag}
className="clear flex c rounded-full !bg-white/10 !hover:bg-white/25"
onClick={() => {
console.log('clicked tag', tag)
}}
>
{tag}
</button>
))}
<select <select
value={resultsSort} value={resultsSort}
onChange={(e) => { onChange={(e) => {
setResultsSort(e.target.value); setResultsSort(e.target.value);
sortApps(e.target.value); sortApps(e.target.value);
}} }}
className={classNames({
'basis-1/5': !isMobile
})}
> >
<option>Recently published</option> <option>Recently published</option>
<option>Most popular</option> <option>Most popular</option>
@ -138,13 +172,54 @@ export default function StorePage() {
<option>Recently updated</option> <option>Recently updated</option>
</select> </select>
</div> </div>
<div className="flex flex-col flex-1 overflow-y-auto gap-2 max-h-[80vh]"> {!searchQuery ? <div className={classNames("flex flex-col", {
{displayedApps.map((app) => ( 'grow overflow-y-auto gap-4 items-center px-2': isMobile
})}>
<h2>Top apps this week...</h2>
<div className={classNames("flex gap-2", {
'flex-col': isMobile
})}>
{displayedApps.slice(0, 4).map((app) => (
<AppEntry <AppEntry
key={appId(app) + (app.state?.our_version || "")} key={appId(app) + (app.state?.our_version || "")}
size={'medium'}
app={app} app={app}
className={classNames("grow", {
'w-1/4': !isMobile,
'w-full': isMobile
})}
/> />
))} ))}
</div>
<h2>Must-have apps!</h2>
<div className={classNames("flex gap-2", {
'flex-col': isMobile
})}>
{displayedApps.slice(0, 6).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size={isMobile ? 'medium' : 'small'}
app={app}
overrideImageSize={isMobile ? 'medium' : 'large'}
className={classNames("grow", {
'w-1/6': !isMobile,
'w-full': isMobile
})}
/>
))}
</div>
</div> : <div className={classNames("flex-col-center grow", {
'gap-2': isMobile,
'gap-4': !isMobile,
})}>
{displayedApps.map(app => <AppEntry
size='large'
app={app}
className="self-stretch items-center"
overrideImageSize="medium"
/>)}
</div>}
<div className="flex flex-col gap-2 overflow-y-auto">
{pages.length > 1 && ( {pages.length > 1 && (
<div className="flex self-center"> <div className="flex self-center">
{page !== pages[0] && ( {page !== pages[0] && (

View File

@ -14,7 +14,7 @@ export enum AppType {
export const getAppType = (app: AppInfo) => { export const getAppType = (app: AppInfo) => {
if (app.publisher === 'sys') { if (app.publisher === 'sys') {
return AppType.System return AppType.System
} else if (app.state?.our_version && !app.state?.capsApproved) { } else if (app.state?.our_version && !app.state?.caps_approved) {
return AppType.Downloaded return AppType.Downloaded
} else if (!app.metadata) { } else if (!app.metadata) {
return AppType.Local return AppType.Local

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0" anyhow = "1.0"
base64 = "0.22.0" base64 = "0.22.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
pleco = "0.5" pleco = "0.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-CiPfZ2kc.js"></script> <script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css"> <link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head> </head>
<body> <body>

View File

@ -9,8 +9,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-CiPfZ2kc.js"></script> <script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css"> <link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head> </head>
<body> <body>

View File

@ -1,5 +1,5 @@
{ {
"name": "ui", "name": "kinode-homepage-ui",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",

View File

@ -7,10 +7,11 @@ const AllApps: React.FC<{ expanded: boolean }> = ({ expanded }) => {
const { apps } = useHomepageStore() const { apps } = useHomepageStore()
const isMobile = isMobileCheck() const isMobile = isMobileCheck()
return <div className={classNames('flex-center flex-wrap gap-4 overflow-y-auto self-stretch relative', { return <div className={classNames('flex-center flex-wrap overflow-y-auto fixed h-screen w-screen backdrop-blur-md transition transition-all ease-in-out duration-500', {
'max-h-0': !expanded, 'top-[100vh]': !expanded,
'p-8 max-h-[1000px]': expanded, 'top-0': expanded,
'placeholder': isMobile 'gap-4 p-8': isMobile,
'gap-8 p-16': !isMobile,
})}> })}>
{apps.length === 0 {apps.length === 0
? <div>Loading apps...</div> ? <div>Loading apps...</div>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import KinodeText from '../components/KinodeText' import KinodeText from '../components/KinodeText'
import KinodeBird from '../components/KinodeBird' import KinodeBird from '../components/KinodeBird'
import useHomepageStore from '../store/homepageStore' import useHomepageStore, { HomepageApp } from '../store/homepageStore'
import { FaChevronDown, FaChevronUp, FaScrewdriverWrench, FaV } from 'react-icons/fa6' import { FaChevronDown, FaChevronUp, FaScrewdriverWrench, FaV } from 'react-icons/fa6'
import AppsDock from '../components/AppsDock' import AppsDock from '../components/AppsDock'
import AllApps from '../components/AllApps' import AllApps from '../components/AllApps'
@ -20,37 +20,56 @@ interface AppStoreApp {
function Homepage() { function Homepage() {
const [our, setOur] = useState('') const [our, setOur] = useState('')
const [allAppsExpanded, setAllAppsExpanded] = useState(false) const [allAppsExpanded, setAllAppsExpanded] = useState(false)
const { apps, setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore() const { setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const isMobile = isMobileCheck() const isMobile = isMobileCheck()
useEffect(() => { const getAppPathsAndIcons = () => {
fetch('/apps') Promise.all([
.then(res => res.json()) fetch('/apps').then(res => res.json() as any as HomepageApp[]),
.then(data => setApps(data)) fetch('/main:app_store:sys/apps').then(res => res.json())
.then(() => { ]).then(([appsData, appStoreData]) => {
fetch('/main:app_store:sys/apps') console.log({ appsData, appStoreData })
.then(res => res.json()) const appz = appsData.map(app => ({
.then(data => { ...app,
const appz = [...apps] is_favorite: false, // Assuming initial state for all apps
data.forEach((app: AppStoreApp) => { }));
if (!appz.find(a => a.package_name === app.package)) {
appStoreData.forEach((appStoreApp: AppStoreApp) => {
const existingAppIndex = appz.findIndex(a => a.package_name === appStoreApp.package);
if (existingAppIndex === -1) {
appz.push({ appz.push({
package_name: app.package, package_name: appStoreApp.package,
path: '', path: '',
label: app.package, label: appStoreApp.package,
state: app.state, state: appStoreApp.state,
is_favorite: false is_favorite: false
}) });
} else { } else {
const i = appz.findIndex(a => a.package_name === app.package) appz[existingAppIndex] = {
if (i !== -1) { ...appz[existingAppIndex],
appz[i] = { ...appz[i], state: app.state } state: appStoreApp.state
};
} }
});
setApps(appz);
// TODO: be less dumb about this edge case!
for (
let i = 0;
i < 5 && appz.find(a => a.package_name === 'app_store' && !a.base64_icon);
i++
) {
getAppPathsAndIcons();
} }
}) });
setApps(appz) }
})
}) useEffect(() => {
getAppPathsAndIcons();
}, [our]);
useEffect(() => {
fetch('/our') fetch('/our')
.then(res => res.text()) .then(res => res.text())
.then(data => { .then(data => {
@ -95,13 +114,14 @@ function Homepage() {
<AppsDock /> <AppsDock />
<Widgets /> <Widgets />
<button <button
className={classNames("clear flex-center self-center", { className={classNames("fixed alt clear flex-center self-center z-20", {
'-mb-1': !allAppsExpanded, 'bottom-2 right-2': isMobile,
'bottom-8 right-8': !isMobile,
})} })}
onClick={() => setAllAppsExpanded(!allAppsExpanded)} onClick={() => setAllAppsExpanded(!allAppsExpanded)}
> >
{allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />} {allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />}
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All installed apps'}</span> <span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
</button> </button>
<AllApps expanded={allAppsExpanded} /> <AllApps expanded={allAppsExpanded} />
{showWidgetsSettings && <WidgetsSettingsModal />} {showWidgetsSettings && <WidgetsSettingsModal />}

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
url = "2.5.0" url = "2.5.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -12,7 +12,7 @@ alloy-primitives = "0.7.0"
alloy-sol-types = "0.7.0" alloy-sol-types = "0.7.0"
bincode = "1.3.3" bincode = "1.3.3"
hex = "0.4.3" hex = "0.4.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "1ea6eda" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag="v0.7.2" }
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -12,45 +12,58 @@
<body> <body>
<h1>system diagnostics & settings</h1> <h1>system diagnostics & settings</h1>
<main> <main>
<article id="net-diagnostics">
<h2>networking diagnostics</h2>
<p id="diagnostics"></p>
</article>
<article id="node-info"> <article id="node-info">
<p>node info</p> <h2>node info</h2>
<p id="node-name"></p> <p id="node-name"></p>
<p id="net-key"></p> <p id="net-key"></p>
<p id="ip-ports"></p> <p id="ip-ports"></p>
<p id="routers"></p> <p id="routers"></p>
<button>reset networking key</button> <button>reset networking key (TODO)</button>
<button>adjust networking info</button> <button>adjust networking info (TODO)</button>
<button id="shutdown">shut down node</button> <button id="shutdown">shut down node</button>
</article> </article>
<article id="net-diagnostics">
<p>networking diagnostics</p>
<p id="diagnostics"></p>
</article>
<article id="pings"> <article id="pings">
<p>fetch PKI data</p> <h2>fetch PKI data</h2>
<form id="get-peer-pki"> <form id="get-peer-pki">
<input type="text" name="peer" placeholder="peer-name.os"> <input type="text" name="peer" placeholder="peer-name.os">
<button type="submit">get peer info</button> <button type="submit">get peer info</button>
</form> </form>
<p id="peer-pki-response"></p> <p id="peer-pki-response"></p>
<p>ping a node</p> <h2>ping a node</h2>
<form id="ping-peer"> <form id="ping-peer">
<input type="text" name="peer" placeholder="peer-name.os"> <input type="text" name="peer" placeholder="peer-name.os">
<input type="text" name="content" placeholder="message"> <input type="text" name="content" placeholder="message">
<label><input type="number" name="timeout">timeout (seconds)</label> <input type="number" name="timeout" placeholder="timeout (seconds)">
<button type="submit">ping</button> <button type="submit">ping</button>
</form> </form>
<p id="peer-ping-response"></p> <p id="peer-ping-response"></p>
</article> </article>
<article id="eth-rpc-providers"> <article id="eth-rpc-providers">
<p>ETH RPC providers</p> <h2>ETH RPC providers</h2>
<div id="provider-edits">
<form id="add-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">add provider</button>
</form>
<form id="remove-eth-provider">
<input type="number" name="chain-id" placeholder="1">
<input type="text" name="rpc-url" placeholder="wss://rpc-url.com">
<button type="submit">remove provider</button>
</form>
</div>
<ul id="providers"></ul> <ul id="providers"></ul>
</article> </article>
<article id="eth-rpc-settings"> <article id="eth-rpc-settings">
<p>ETH RPC settings</p> <h2>ETH RPC settings</h2>
<p id="public"></p> <p id="public"></p>
<div> <div>
<p>nodes allowed to connect:</p> <p>nodes allowed to connect:</p>
@ -63,7 +76,7 @@
</article> </article>
<article id="kernel"> <article id="kernel">
<p>running processes:</p> <h2>running processes</h2>
<p>(TODO)</p> <p>(TODO)</p>
<ul></ul> <ul></ul>
</article> </article>

View File

@ -56,17 +56,17 @@ function populate_eth_rpc_providers(providers) {
ul.innerHTML = ''; ul.innerHTML = '';
providers.forEach(provider => { providers.forEach(provider => {
const li = document.createElement('li'); const li = document.createElement('li');
li.innerHTML = `<li>${JSON.stringify(provider)}</li>`; li.innerHTML = `${JSON.stringify(provider, undefined, 2)}`;
ul.appendChild(li); ul.appendChild(li);
}); });
} }
function populate_eth_rpc_settings(settings) { function populate_eth_rpc_settings(settings) {
if (settings.public) { if (settings.public) {
document.getElementById('public').innerText = 'public'; document.getElementById('public').innerText = 'status: public';
document.getElementById('allowed-nodes').style.display = 'none'; document.getElementById('allowed-nodes').style.display = 'none';
} else { } else {
document.getElementById('public').innerText = 'private'; document.getElementById('public').innerText = 'status: private';
const ul = document.getElementById('allowed-nodes'); const ul = document.getElementById('allowed-nodes');
ul.innerHTML = ''; ul.innerHTML = '';
if (settings.allow.length === 0) { if (settings.allow.length === 0) {
@ -119,7 +119,8 @@ document.getElementById('get-peer-pki').addEventListener('submit', (e) => {
if (data === null) { if (data === null) {
document.getElementById('peer-pki-response').innerText = "no pki data for peer"; document.getElementById('peer-pki-response').innerText = "no pki data for peer";
} else { } else {
document.getElementById('peer-pki-response').innerText = JSON.stringify(data); e.target.reset();
document.getElementById('peer-pki-response').innerText = JSON.stringify(data, undefined, 2);
} }
}); });
}) })
@ -143,6 +144,7 @@ document.getElementById('ping-peer').addEventListener('submit', (e) => {
}).then(response => response.json()) }).then(response => response.json())
.then(data => { .then(data => {
if (data === null) { if (data === null) {
e.target.reset();
document.getElementById('peer-ping-response').innerText = "ping successful!"; document.getElementById('peer-ping-response').innerText = "ping successful!";
} else if (data === "HiTimeout") { } else if (data === "HiTimeout") {
document.getElementById('peer-ping-response').innerText = "node timed out"; document.getElementById('peer-ping-response').innerText = "node timed out";
@ -152,6 +154,66 @@ document.getElementById('ping-peer').addEventListener('submit', (e) => {
}); });
}) })
document.getElementById('add-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const rpc_url = data.get('rpc-url');
// validate rpc url
if (!rpc_url.startsWith('wss://') && !rpc_url.startsWith('ws://')) {
alert('Invalid RPC URL');
return;
}
const body = {
"EthConfig": {
"AddProvider": {
chain_id: Number(data.get('chain-id')),
trusted: false,
provider: { "RpcUrl": rpc_url },
}
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
document.getElementById('remove-eth-provider').addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(e.target);
const body = {
"EthConfig": {
"RemoveProvider": [Number(data.get('chain-id')), data.get('rpc-url')]
}
};
fetch(APP_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then(response => response.json())
.then(data => {
if (data === null) {
e.target.reset();
return;
} else {
alert(data);
}
});
})
// Setup WebSocket connection // Setup WebSocket connection
const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://'; const wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/"); const ws = new WebSocket(wsProtocol + location.host + "/settings:settings:sys/");

View File

@ -82,14 +82,58 @@ h1 {
} }
main { main {
margin: 0 auto;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: 1fr 1fr 1fr;
gap: 20px; gap: 20px 20px;
grid-auto-flow: row;
grid-template-areas:
"diagnostics diagnostics diagnostics"
"node-info pings pings"
"eth-rpc-providers eth-rpc-providers eth-rpc-settings"
"kernel kernel kernel";
padding: 20px; padding: 20px;
max-width: 1200px; max-width: 1100px;
min-width: 300px; min-width: 300px;
} }
article#net-diagnostics {
grid-area: diagnostics;
}
p#diagnostics,
p#peer-pki-response,
p#peer-ping-response {
white-space: pre-wrap;
}
article#node-info {
grid-area: node-info;
}
article#pings {
grid-area: pings;
}
article#eth-rpc-providers {
grid-area: eth-rpc-providers;
}
article#eth-rpc-settings {
grid-area: eth-rpc-settings;
}
article#kernel {
grid-area: kernel;
}
div#provider-edits {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px 20px;
grid-auto-flow: row;
}
article { article {
background-color: #333; background-color: #333;
border: 1px solid #444; border: 1px solid #444;
@ -129,11 +173,20 @@ button {
border-radius: 4px; border-radius: 4px;
} }
button#shutdown {
background-color: #f44336;
}
button:hover { button:hover {
background-color: white; background-color: white;
color: #4CAF50; color: #4CAF50;
} }
button#shutdown:hover {
background-color: white;
color: #f44336;
}
input[type="text"], input[type="text"],
input[type="number"], input[type="number"],
select, select,
@ -178,4 +231,5 @@ li {
background-color: #2c2c2c; background-color: #2c2c2c;
border-radius: 4px; border-radius: 4px;
word-wrap: break-word; word-wrap: break-word;
white-space: pre-wrap;
} }

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0" anyhow = "1.0"
base64 = "0.22.0" base64 = "0.22.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.6.1" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag="v0.7.2" }
rmp-serde = "1.2.0" rmp-serde = "1.2.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -163,12 +163,6 @@ fn initialize(our: Address) {
// Grab our state, then enter the main event loop. // Grab our state, then enter the main event loop.
let mut state: SettingsState = SettingsState::new(our); let mut state: SettingsState = SettingsState::new(our);
match state.fetch() {
Ok(()) => {}
Err(e) => {
println!("failed to fetch initial state: {e}");
}
}
main_loop(&mut state); main_loop(&mut state);
} }
@ -249,16 +243,18 @@ fn handle_http_request(
state: &mut SettingsState, state: &mut SettingsState,
http_request: &http::IncomingHttpRequest, http_request: &http::IncomingHttpRequest,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
state.fetch()?;
match http_request.method()?.as_str() { match http_request.method()?.as_str() {
"GET" => Ok(http::send_response( "GET" => {
state.fetch()?;
Ok(http::send_response(
http::StatusCode::OK, http::StatusCode::OK,
Some(HashMap::from([( Some(HashMap::from([(
String::from("Content-Type"), String::from("Content-Type"),
String::from("application/json"), String::from("application/json"),
)])), )])),
serde_json::to_vec(&state)?, serde_json::to_vec(&state)?,
)), ))
}
"POST" => { "POST" => {
let Some(blob) = get_blob() else { let Some(blob) = get_blob() else {
return Ok(http::send_response( return Ok(http::send_response(
@ -269,6 +265,7 @@ fn handle_http_request(
}; };
let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?; let request = serde_json::from_slice::<SettingsRequest>(&blob.bytes)?;
let response = handle_settings_request(state, request); let response = handle_settings_request(state, request);
state.fetch()?;
state.ws_update(); state.ws_update();
Ok(http::send_response( Ok(http::send_response(
http::StatusCode::OK, http::StatusCode::OK,

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
clap = "4.4.18" clap = "4.4.18"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
regex = "1.10.3" regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
simulation-mode = [] simulation-mode = []
[dependencies] [dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
rand = "0.8" rand = "0.8"
regex = "1.10.3" regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -8,7 +8,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
wit-bindgen = "0.24.0" wit-bindgen = "0.24.0"

View File

@ -9,7 +9,7 @@ simulation-mode = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"

View File

@ -10,7 +10,7 @@ simulation-mode = []
anyhow = "1.0" anyhow = "1.0"
bincode = "1.3.3" bincode = "1.3.3"
indexmap = "2.1" indexmap = "2.1"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" } kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"

View File

@ -63,18 +63,26 @@ impl ActiveProviders {
kns_update, kns_update,
use_as_provider, use_as_provider,
} => { } => {
self.nodes.push(NodeProvider { self.remove_provider(&kns_update.name);
self.nodes.insert(
0,
NodeProvider {
trusted: new.trusted, trusted: new.trusted,
usable: use_as_provider, usable: use_as_provider,
kns_update, kns_update,
}); },
);
} }
NodeOrRpcUrl::RpcUrl(url) => { NodeOrRpcUrl::RpcUrl(url) => {
self.urls.push(UrlProvider { self.remove_provider(&url);
self.urls.insert(
0,
UrlProvider {
trusted: new.trusted, trusted: new.trusted,
url, url,
pubsub: None, pubsub: None,
}); },
);
} }
} }
} }

View File

@ -33,6 +33,23 @@ pub async fn create_new_subscription(
) )
.await .await
{ {
// if building the subscription fails, send an error message to the target
Ok(Err(e)) => {
error_message(&our, km_id, target.clone(), e, &send_to_loop).await;
}
// if building the subscription times out, send an error message to the target
Err(_) => {
error_message(
&our,
km_id,
target.clone(),
EthError::RpcTimeout,
&send_to_loop,
)
.await;
}
// if building the subscription is successful, start the subscription
// and in this task, maintain and clean up after it.
Ok(Ok(maybe_raw_sub)) => { Ok(Ok(maybe_raw_sub)) => {
// send a response to the target that the subscription was successful // send a response to the target that the subscription was successful
kernel_message( kernel_message(
@ -49,30 +66,30 @@ pub async fn create_new_subscription(
let mut subs = active_subscriptions let mut subs = active_subscriptions
.entry(target.clone()) .entry(target.clone())
.or_insert(HashMap::new()); .or_insert(HashMap::new());
let active_subscriptions = active_subscriptions.clone();
match maybe_raw_sub {
Ok(rx) => {
let our = our.clone(); let our = our.clone();
let send_to_loop = send_to_loop.clone(); let send_to_loop = send_to_loop.clone();
let print_tx = print_tx.clone(); let print_tx = print_tx.clone();
let active_subscriptions = active_subscriptions.clone();
match maybe_raw_sub {
Ok(rx) => {
subs.insert( subs.insert(
sub_id, sub_id,
// this is a local sub, as in, we connect to the rpc endpoint // this is a local sub, as in, we connect to the rpc endpoint
ActiveSub::Local(tokio::spawn(async move { ActiveSub::Local(tokio::spawn(async move {
// await the subscription error and kill it if so // await the subscription error and kill it if so
if let Err(e) = maintain_local_subscription( let e = maintain_local_subscription(
&our, &our,
sub_id, sub_id,
rx, rx,
&target, &target,
&rsvp, &rsvp,
&send_to_loop, &send_to_loop,
&active_subscriptions,
) )
.await .await;
{
verbose_print( verbose_print(
&print_tx, &print_tx,
"eth: closed local subscription due to error", &format!("eth: closed local subscription due to error {e:?}"),
) )
.await; .await;
kernel_message( kernel_message(
@ -86,10 +103,6 @@ pub async fn create_new_subscription(
&send_to_loop, &send_to_loop,
) )
.await; .await;
active_subscriptions.entry(target).and_modify(|sub_map| {
sub_map.remove(&km_id);
});
}
})), })),
); );
} }
@ -100,16 +113,13 @@ pub async fn create_new_subscription(
let (keepalive_err_sender, keepalive_err_receiver) = let (keepalive_err_sender, keepalive_err_receiver) =
tokio::sync::mpsc::channel(1); tokio::sync::mpsc::channel(1);
response_channels.insert(keepalive_km_id, keepalive_err_sender); response_channels.insert(keepalive_km_id, keepalive_err_sender);
let our = our.clone();
let send_to_loop = send_to_loop.clone();
let print_tx = print_tx.clone();
let response_channels = response_channels.clone(); let response_channels = response_channels.clone();
subs.insert( subs.insert(
remote_sub_id, remote_sub_id,
ActiveSub::Remote { ActiveSub::Remote {
provider_node: provider_node.clone(), provider_node: provider_node.clone(),
handle: tokio::spawn(async move { handle: tokio::spawn(async move {
if let Err(e) = maintain_remote_subscription( let e = maintain_remote_subscription(
&our, &our,
&provider_node, &provider_node,
remote_sub_id, remote_sub_id,
@ -119,12 +129,13 @@ pub async fn create_new_subscription(
keepalive_err_receiver, keepalive_err_receiver,
&target, &target,
&send_to_loop, &send_to_loop,
&active_subscriptions,
&response_channels,
) )
.await .await;
{
verbose_print( verbose_print(
&print_tx, &print_tx,
"eth: closed subscription with provider node due to error", &format!("eth: closed subscription with provider node due to error {e:?}"),
) )
.await; .await;
kernel_message( kernel_message(
@ -138,11 +149,6 @@ pub async fn create_new_subscription(
&send_to_loop, &send_to_loop,
) )
.await; .await;
active_subscriptions.entry(target).and_modify(|sub_map| {
sub_map.remove(&sub_id);
});
response_channels.remove(&keepalive_km_id);
}
}), }),
sender, sender,
}, },
@ -150,19 +156,6 @@ pub async fn create_new_subscription(
} }
} }
} }
Ok(Err(e)) => {
error_message(&our, km_id, target.clone(), e, &send_to_loop).await;
}
Err(_) => {
error_message(
&our,
km_id,
target.clone(),
EthError::RpcTimeout,
&send_to_loop,
)
.await;
}
} }
}); });
} }
@ -311,13 +304,18 @@ async fn maintain_local_subscription(
target: &Address, target: &Address,
rsvp: &Option<Address>, rsvp: &Option<Address>,
send_to_loop: &MessageSender, send_to_loop: &MessageSender,
) -> Result<(), EthSubError> { active_subscriptions: &ActiveSubscriptions,
) -> EthSubError {
while let Ok(value) = rx.recv().await { while let Ok(value) = rx.recv().await {
let result: SubscriptionResult = let result: SubscriptionResult = match serde_json::from_str(value.get()) {
serde_json::from_str(value.get()).map_err(|e| EthSubError { Ok(res) => res,
Err(e) => {
return EthSubError {
id: sub_id, id: sub_id,
error: e.to_string(), error: e.to_string(),
})?; }
}
};
kernel_message( kernel_message(
our, our,
rand::random(), rand::random(),
@ -330,10 +328,15 @@ async fn maintain_local_subscription(
) )
.await; .await;
} }
Err(EthSubError { active_subscriptions
.entry(target.clone())
.and_modify(|sub_map| {
sub_map.remove(&sub_id);
});
EthSubError {
id: sub_id, id: sub_id,
error: "subscription closed unexpectedly".to_string(), error: "subscription closed unexpectedly".to_string(),
}) }
} }
/// handle the subscription updates from a remote provider, /// handle the subscription updates from a remote provider,
@ -352,12 +355,14 @@ async fn maintain_remote_subscription(
mut net_error_rx: ProcessMessageReceiver, mut net_error_rx: ProcessMessageReceiver,
target: &Address, target: &Address,
send_to_loop: &MessageSender, send_to_loop: &MessageSender,
) -> Result<(), EthSubError> { active_subscriptions: &ActiveSubscriptions,
response_channels: &ResponseChannels,
) -> EthSubError {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(30)); let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(30));
let mut last_received = tokio::time::Instant::now(); let mut last_received = tokio::time::Instant::now();
let two_hours = tokio::time::Duration::from_secs(2 * 3600); let two_hours = tokio::time::Duration::from_secs(2 * 3600);
loop { let e = loop {
tokio::select! { tokio::select! {
incoming = rx.recv() => { incoming = rx.recv() => {
match incoming { match incoming {
@ -380,16 +385,16 @@ async fn maintain_remote_subscription(
.await; .await;
} }
Some(EthSubResult::Err(e)) => { Some(EthSubResult::Err(e)) => {
return Err(EthSubError { break EthSubError {
id: sub_id, id: sub_id,
error: e.error, error: e.error,
}); };
} }
None => { None => {
return Err(EthSubError { break EthSubError {
id: sub_id, id: sub_id,
error: "subscription closed unexpectedly".to_string(), error: "subscription closed unexpectedly".to_string(),
}); };
} }
} }
} }
@ -406,20 +411,25 @@ async fn maintain_remote_subscription(
&send_to_loop, &send_to_loop,
).await; ).await;
} }
incoming = net_error_rx.recv() => { _incoming = net_error_rx.recv() => {
if let Some(Err(_net_error)) = incoming { break EthSubError {
return Err(EthSubError {
id: sub_id, id: sub_id,
error: "subscription node-provider failed keepalive".to_string(), error: "subscription node-provider failed keepalive".to_string(),
}); };
}
} }
_ = tokio::time::sleep_until(last_received + two_hours) => { _ = tokio::time::sleep_until(last_received + two_hours) => {
return Err(EthSubError { break EthSubError {
id: sub_id, id: sub_id,
error: "No updates received for 2 hours, subscription considered dead.".to_string(), error: "No updates received for 2 hours, subscription considered dead.".to_string(),
};
}
}
};
active_subscriptions
.entry(target.clone())
.and_modify(|sub_map| {
sub_map.remove(&remote_sub_id);
}); });
} response_channels.remove(&keepalive_km_id);
} e
}
} }

View File

@ -1141,88 +1141,18 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
</style> </style>
</head> </head>
<body><noscript>You need to enable JavaScript to run this app.</noscript> <body
style="background-color: #22211f;
background-image: linear-gradient(-55deg, #f75a2977 0%, transparent 72.05%),linear-gradient(-11deg, #86000172 3%, transparent 57.05%);">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"> <div id="root">
<svg width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: fixed;
top: -300px;
left: -350px;
z-index: -1;
filter: blur(100px);
transform: scale(3);">
<g clip-path="url(#clip0_152_8506)">
<rect width="1440" height="2777" transform="translate(0 -5)" fill="#22211F" />
<path
d="M2122.93 951.101C1925.12 1458.86 1658.87 2101.89 1376.85 1992.03C601.547 2045.26 -152.056 1158.27 434.949 891.737C1021.95 625.205 1605.89 -327.704 1970.46 -167.208C2361.3 4.85831 2320.74 443.343 2122.93 951.101Z"
fill="url(#paint0_radial_152_8506)" />
<path
d="M274.686 1388.42C15.0266 1578.85 -501.874 1262.71 -695.499 998.693C-889.123 734.676 -935.603 448.185 -675.943 257.757C-416.284 67.3282 500.306 639.9 693.93 903.918C887.555 1167.94 534.346 1197.99 274.686 1388.42Z"
fill="url(#paint1_linear_152_8506)" />
<path
d="M673.921 -604.363C829.107 -41.0363 308.158 505 -81.9719 505C-472.102 505 -788.365 72.0217 -788.365 -462.084C-788.365 -996.189 -1002.49 -1551 -612.361 -1551C-222.231 -1551 673.921 -1138.47 673.921 -604.363Z"
fill="#F75A29" fill-opacity="0.7" />
<g filter="url(#filter0_b_152_8506)">
<rect width="1464" height="2823" transform="translate(0 -5)" fill="#22211F" fill-opacity="0.01" />
</g>
</g>
<defs>
<filter id="filter0_b_152_8506" x="-314" y="-319" width="2092" height="3451" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="157" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_152_8506" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_152_8506" result="shape" />
</filter>
<radialGradient id="paint0_radial_152_8506" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="translate(1444 1488.65) rotate(-86.9561) scale(699.105 590.175)">
<stop offset="0.635" stop-color="#F35422" stop-opacity="0.7" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.35" />
</radialGradient>
<linearGradient id="paint1_linear_152_8506" x1="-389.52" y1="620.673" x2="747.127" y2="934.639"
gradientUnits="userSpaceOnUse">
<stop stop-color="#F35422" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.1" />
</linearGradient>
<clipPath id="clip0_152_8506">
<rect width="1440" height="2777" fill="white" transform="translate(0 -5)" />
</clipPath>
</defs>
</svg>
<div id="signup-page" class="flex flex-col place-items-center place-content-center h-screen font-sans"> <div id="signup-page" class="flex flex-col place-items-center place-content-center h-screen font-sans">
<div id="home-form-header" class="flex flex-col place-content-center place-items-center" <div id="home-form-header" class="flex flex-col place-content-center place-items-center"
style="gap: 24px; margin: 32px 0 56px;"> style="gap: 24px; margin: 32px 0 56px;">
<h3 style="text-align: center;">Login to</h3> <h3 style="text-align: center;">Login to</h3>
<svg width="289.5" height="36" viewBox="0 0 580 72" fill="none" xmlns="http://www.w3.org/2000/svg"> <h1 style="font-family: 'Futura', sans-serif; letter-spacing: 0.4em; font-size: xxx-large; margin-left: 1em;">
<g clip-path="url(#clip0_6_641)"> KINODE<span style="font-size: small">&reg;</span>
<path d="M0.824922 1.07031L0.794922 70.0703H14.7949L14.8049 1.07031H0.824922Z" fill="#FFF5D9"></path> </h1>
<path d="M16.5947 36.8803L41.2547 1.07031H58.2447L33.1647 36.8803L61.2447 70.0703H42.9947L16.5947 36.8803Z"
fill="#FFF5D9"></path>
<path d="M119.885 1.07031H105.765V70.0703H119.885V1.07031Z" fill="#FFF5D9"></path>
<path
d="M173.185 1.07031V70.0703H186.775V26.8303L224.045 70.0703H234.825V1.07031H221.325V45.6803L183.445 1.07031H173.185Z"
fill="#FFF5D9"></path>
<path
d="M342.465 8.86C333.025 0.15 321.645 0 318.535 0C315.475 0 303.575 0.22 294.005 9.52C283.845 19.4 283.805 32.24 283.795 35.66C283.785 39.3 283.895 49.03 290.805 57.99C300.855 71.02 316.695 71.31 318.535 71.32C321.375 71.32 334.185 71 343.965 60.66C353.065 51.04 353.265 39.4 353.275 35.66C353.275 32.49 353.305 18.86 342.455 8.86H342.465ZM318.435 58.01C307.095 58.01 297.895 47.95 297.895 35.54C297.895 23.13 307.085 13.07 318.435 13.07C329.785 13.07 338.975 23.13 338.975 35.54C338.975 47.95 329.785 58.01 318.435 58.01Z"
fill="#FFF5D9"></path>
<path
d="M450.495 12.0802C444.975 5.46023 437.135 0.990234 427.955 0.990234C417.555 0.990234 405.295 1.07023 402.295 1.07023V69.9802C405.285 69.9802 417.555 70.0602 427.955 70.0602C445.525 70.0602 458.445 53.4102 459.065 36.8602C459.395 28.0102 456.185 18.9002 450.495 12.0802ZM440.085 49.9502C436.895 53.8702 432.705 56.6902 427.665 57.5602C424.025 58.1902 420.095 57.8302 416.405 57.8302C416.405 50.4002 416.405 42.9802 416.405 35.5502V13.2202C423.795 13.2202 430.525 12.7002 436.605 17.6002C440.275 20.5602 442.925 24.7102 444.165 29.2402C444.525 30.5402 444.765 31.8802 444.875 33.2302C445.395 39.3702 443.995 45.1402 440.085 49.9502Z"
fill="#FFF5D9"></path>
<path
d="M508.135 0.990234V70.0602H552.715V57.9302H522.035V40.4202H547.125V28.0702H521.995V13.3202H552.715V0.990234H508.135Z"
fill="#FFF5D9"></path>
<path
d="M574.835 66.0398H572.745L571.015 63.0698H569.845V66.0398H567.805V57.5498H571.765C572.845 57.5498 573.865 57.9298 574.425 58.9398C575.205 60.3698 574.665 62.3798 573.105 63.0298C573.725 64.1198 574.225 64.9498 574.845 66.0398H574.835ZM570.375 61.0798H570.845C571.335 61.0798 572.365 61.0798 572.365 60.2898C572.365 59.5598 571.335 59.5598 570.845 59.5598H570.375V61.0798Z"
fill="#FFF5D9"></path>
<path
d="M570.964 69.0002C574.913 69.0002 578.114 65.799 578.114 61.8502C578.114 57.9014 574.913 54.7002 570.964 54.7002C567.016 54.7002 563.814 57.9014 563.814 61.8502C563.814 65.799 567.016 69.0002 570.964 69.0002Z"
stroke="#FFF5D9" stroke-width="2.2" stroke-miterlimit="10"></path>
</g>
<defs>
<clipPath id="clip0_6_641">
<rect width="578.41" height="71.32" fill="white" transform="translate(0.794922)"></rect>
</clipPath>
</defs>
</svg>
<svg style="margin-top: 0.5em;" width="67.5" height="48" viewBox="0 0 122 81" fill="none" <svg style="margin-top: 0.5em;" width="67.5" height="48" viewBox="0 0 122 81" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_651)"> <g clip-path="url(#clip0_6_651)">
@ -1240,7 +1170,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
<form id="signup-form" class="flex flex-col"> <form id="signup-form" class="flex flex-col">
<h3 class="flex flex-col ml-2 text-lg"> ${node} </h3> <h3 class="flex flex-col ml-2 text-lg"> ${node} </h3>
<div class="flex mt-2 mb-2"> Enter Password </div> <div class="flex mt-2 mb-2"> Enter Password </div>
<input type="password" id="password" required="" minlength="6" name="password" placeholder="Password" <input autofocus type="password" id="password" required="" minlength="6" name="password" placeholder="Password"
oninput="document.getElementById('password-err').style.display = 'none';" value="" class="self-stretch mb-2"> oninput="document.getElementById('password-err').style.display = 'none';" value="" class="self-stretch mb-2">
<div id="password-err" class="login-row flex mb-2" style="display: none;"> Incorrect Password </div> <div id="password-err" class="login-row flex mb-2" style="display: none;"> Incorrect Password </div>
<div class="flex flex-col leading-6 self-stretch mb-2"> <div class="flex flex-col leading-6 self-stretch mb-2">
@ -1260,7 +1190,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
if ('${fake}' === 'true') { if ('${fake}' === 'true') {
document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!"; document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!";
} else { } else {
document.getElementById("fake-or-not").innerHTML = "Restart node to change networking info"; document.getElementById("fake-or-not").innerHTML = "To change your networking info, please restart your node.";
} }
async function login(password) { async function login(password) {

View File

@ -86,7 +86,7 @@ async fn main() {
Err(_) => serde_json::from_str(DEFAULT_ETH_PROVIDERS).unwrap(), Err(_) => serde_json::from_str(DEFAULT_ETH_PROVIDERS).unwrap(),
}; };
if let Some(rpc) = rpc { if let Some(rpc) = rpc {
eth_provider_config.push(lib::eth::ProviderConfig { eth_provider_config.insert(lib::eth::ProviderConfig {
chain_id: CHAIN_ID, chain_id: CHAIN_ID,
trusted: true, trusted: true,
provider: lib::eth::NodeOrRpcUrl::RpcUrl(rpc.to_string()), provider: lib::eth::NodeOrRpcUrl::RpcUrl(rpc.to_string()),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-DzEOiZ-5.js"></script> <script type="module" crossorigin src="/assets/index-CqsVFnwl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B00cPdAQ.css"> <link rel="stylesheet" crossorigin href="/assets/index-B00cPdAQ.css">
</head> </head>

View File

@ -1,5 +1,5 @@
{ {
"name": "register", "name": "kinode-register-ui",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"proxy": "http://localhost:8080", "proxy": "http://localhost:8080",
@ -27,7 +27,7 @@
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eth-ens-namehash": "^2.0.8", "eth-ens-namehash": "^2.0.8",
"ethers": "^5.7.2", "ethers": "^5.7.2",
"idna-uts46-hx": "^2.3.1", "idna-uts46-hx": "^6.0.4",
"is-valid-domain": "^0.1.6", "is-valid-domain": "^0.1.6",
"jazzicon": "^1.5.0", "jazzicon": "^1.5.0",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -65,7 +65,7 @@ function App() {
const openConnect = () => setConnectOpen(true) const openConnect = () => setConnectOpen(true)
const closeConnect = () => setConnectOpen(false) const closeConnect = () => setConnectOpen(false)
const rpcUrl = useMemo(() => provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_OPTIMISM_RPC_URL, [provider]) const rpcUrl = useMemo(() => provider?.network?.chainId === ChainId.SEPOLIA ? import.meta.env.REACT_APP_SEPOLIA_RPC_URL : import.meta.env.REACT_APP_OPTIMISM_RPC_URL, [provider])
const [dotOs, setDotOs] = useState<DotOsRegistrar>( const [dotOs, setDotOs] = useState<DotOsRegistrar>(
DotOsRegistrar__factory.connect( DotOsRegistrar__factory.connect(
@ -83,7 +83,7 @@ function App() {
KNSEnsEntry__factory.connect( KNSEnsEntry__factory.connect(
provider?.network?.chainId === ChainId.SEPOLIA ? KNS_ENS_ENTRY_ADDRESSES[ChainId.SEPOLIA] : KNS_ENS_ENTRY_ADDRESSES[ChainId.MAINNET], provider?.network?.chainId === ChainId.SEPOLIA ? KNS_ENS_ENTRY_ADDRESSES[ChainId.SEPOLIA] : KNS_ENS_ENTRY_ADDRESSES[ChainId.MAINNET],
// set rpc url based on chain id // set rpc url based on chain id
new ethers.providers.JsonRpcProvider(provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_MAINNET_RPC_URL)) new ethers.providers.JsonRpcProvider(provider?.network?.chainId === ChainId.SEPOLIA ? import.meta.env.REACT_APP_SEPOLIA_RPC_URL : import.meta.env.REACT_APP_MAINNET_RPC_URL))
); );
const [knsEnsExit, setKnsEnsExit] = useState<KNSEnsExit>( const [knsEnsExit, setKnsEnsExit] = useState<KNSEnsExit>(
@ -192,11 +192,11 @@ function App() {
)) ))
setNameWrapper(NameWrapper__factory.connect( setNameWrapper(NameWrapper__factory.connect(
NAMEWRAPPER_ADDRESSES[ChainId.MAINNET], NAMEWRAPPER_ADDRESSES[ChainId.MAINNET],
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL) new ethers.providers.JsonRpcProvider(import.meta.env.REACT_APP_MAINNET_RPC_URL)
)) ))
setEnsRegistry(ENSRegistry__factory.connect( setEnsRegistry(ENSRegistry__factory.connect(
ENS_REGISTRY_ADDRESSES[ChainId.MAINNET], ENS_REGISTRY_ADDRESSES[ChainId.MAINNET],
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL) new ethers.providers.JsonRpcProvider(import.meta.env.REACT_APP_MAINNET_RPC_URL)
)) ))
} }
}) })

View File

@ -0,0 +1,12 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
readonly REACT_APP_MAINNET_RPC_URL: string;
readonly REACT_APP_SEPOLIA_RPC_URL: string;
// Add other environment variables as needed
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -5318,6 +5318,13 @@ idna-uts46-hx@^2.3.1:
dependencies: dependencies:
punycode "2.1.0" punycode "2.1.0"
idna-uts46-hx@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-6.0.4.tgz#25f9f8af628bd4150b36340e99a1f9532c815905"
integrity sha512-sI5p40O39Mev3pk7dmG4m2vaSBQcpn8ZETN72ftaIPaDvY3MwgOj2psh20lYA9aVgQ5eS/Wcmo6l+USXyepffg==
dependencies:
tr46 "^5.0.0"
ieee754@^1.1.13, ieee754@^1.2.1: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
@ -6601,9 +6608,9 @@ punycode@2.1.0:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz"
integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA== integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==
punycode@^2.1.1: punycode@^2.1.1, punycode@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
q@^1.1.2: q@^1.1.2:
@ -7426,6 +7433,13 @@ totalist@^3.0.0:
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
tr46@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec"
integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==
dependencies:
punycode "^2.3.1"
tr46@~0.0.3: tr46@~0.0.3:
version "0.0.3" version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"

View File

@ -125,8 +125,16 @@ pub async fn register(
} else { } else {
"wss://optimism-rpc.publicnode.com".to_string() "wss://optimism-rpc.publicnode.com".to_string()
}; };
let connector = WsConnect { url, auth: None }; println!(
let Ok(client) = ClientBuilder::default().ws(connector).await else { "Connecting to Optimism RPC at {url}\n\
Specify a different RPC URL with the --rpc flag."
);
// this fails occasionally in certain networking environments. i'm not sure why.
// frustratingly, the exact same call does not fail in the eth module. more investigation needed.
let Ok(client) = ClientBuilder::default()
.ws(WsConnect { url, auth: None })
.await
else {
panic!( panic!(
"Error: runtime could not connect to ETH RPC.\n\ "Error: runtime could not connect to ETH RPC.\n\
This is necessary in order to verify node identity onchain.\n\ This is necessary in order to verify node identity onchain.\n\
@ -134,6 +142,7 @@ pub async fn register(
the --rpc flag, and you are connected to the internet." the --rpc flag, and you are connected to the internet."
); );
}; };
println!("Connected to Optimism RPC");
let provider = Arc::new(Provider::new_with_client(client)); let provider = Arc::new(Provider::new_with_client(client));

View File

@ -1583,7 +1583,7 @@ pub enum NetResponse {
Verified(bool), Verified(bool),
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
pub struct KnsUpdate { pub struct KnsUpdate {
pub name: String, // actual username / domain name pub name: String, // actual username / domain name
pub owner: String, pub owner: String,

View File

@ -141,17 +141,17 @@ pub struct AccessSettings {
pub deny: HashSet<String>, // blacklist for access (always used) pub deny: HashSet<String>, // blacklist for access (always used)
} }
pub type SavedConfigs = Vec<ProviderConfig>; pub type SavedConfigs = HashSet<ProviderConfig>;
/// Provider config. Can currently be a node or a ws provider instance. /// Provider config. Can currently be a node or a ws provider instance.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
pub struct ProviderConfig { pub struct ProviderConfig {
pub chain_id: u64, pub chain_id: u64,
pub trusted: bool, pub trusted: bool,
pub provider: NodeOrRpcUrl, pub provider: NodeOrRpcUrl,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
pub enum NodeOrRpcUrl { pub enum NodeOrRpcUrl {
Node { Node {
kns_update: crate::core::KnsUpdate, kns_update: crate::core::KnsUpdate,