Skip to content

Commit

Permalink
feat(provider): tapplet providers (#151)
Browse files Browse the repository at this point in the history
Description
---
Refactor the way we handle provider(s).

Should be single default "internal" TU provider
Every new tapplet inits its own provider (at the moment of launching and
fetching permissions) which is stored in store and removed if a tapplet
is deleted

Motivation and Context
---
Solves #148

How Has This Been Tested?
---

What process can a PR reviewer use to test or verify this change?
---

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->
  • Loading branch information
karczuRF authored Dec 4, 2024
1 parent e225e0d commit b54b5cb
Show file tree
Hide file tree
Showing 35 changed files with 962 additions and 374 deletions.
10 changes: 6 additions & 4 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{
InstalledTappletWithName,
LaunchedTappResult,
RegisteredTappletWithVersion,
TariPermission,
TappletPermissions,
},
progress_tracker::ProgressTracker,
rpc::{ account_create, balances, free_coins, make_request },
Expand Down Expand Up @@ -162,7 +162,7 @@ pub async fn launch_tapplet(
}
}

let permissions: Vec<TariPermission> = match get_tapp_permissions(tapplet_path.clone()) {
let permissions: TappletPermissions = match get_tapp_permissions(tapplet_path.clone()) {
Ok(p) => p,
Err(e) => {
error!(target: LOG_TARGET,"Error getting permissions: {:?}", e);
Expand Down Expand Up @@ -281,6 +281,7 @@ pub async fn fetch_tapplets(app_handle: AppHandle, db_connection: State<'_, Data
for tapplet_manifest in tapplets.registered_tapplets.values() {
let inserted_tapplet = store.create(&CreateTapplet::from(tapplet_manifest))?;

// TODO uncomment if audit data in manifest
// for audit_data in tapplet_manifest.metadata.audits.iter() {
// store.create(
// &(CreateTappletAudit {
Expand Down Expand Up @@ -448,8 +449,9 @@ pub async fn add_dev_tapplet(
package_name: &manifest_res.package_name,
display_name: &manifest_res.display_name,
};

store.create(&new_dev_tapplet)
let dev_tapplet = store.create(&new_dev_tapplet);
info!(target: LOG_TARGET,"✅ Dev tapplet added to db successfully: {:?}", new_dev_tapplet);
dev_tapplet
}

#[tauri::command]
Expand Down
12 changes: 10 additions & 2 deletions src-tauri/src/interface/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,22 @@ pub struct Audit {
pub report_url: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TappletPermissions {
#[serde(rename = "requiredPermissions")]
pub required_permissions: Vec<TariPermission>,
#[serde(rename = "optionalPermissions")]
pub optional_permissions: Vec<TariPermission>,
}

#[derive(Debug, serde::Deserialize)]
pub struct TappletConfig {
#[serde(rename = "packageName")]
pub package_name: String,
pub version: String,
#[serde(rename = "supportedChain")]
pub supported_chain: Vec<String>,
pub permissions: Vec<TariPermission>,
pub permissions: TappletPermissions,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
Expand All @@ -82,5 +90,5 @@ pub enum TariPermission {
#[derive(Debug, Clone, serde::Serialize)]
pub struct LaunchedTappResult {
pub endpoint: String,
pub permissions: Vec<TariPermission>,
pub permissions: TappletPermissions,
}
2 changes: 1 addition & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async fn try_get_tokens() -> (String, String) {
loop {
match permission_token().await {
Ok(tokens) => {
info!(target: LOG_TARGET, "✅ WALLET DAEMON permission token found: {:?}", tokens);
info!(target: LOG_TARGET, "✅ Wallet Daemon permission token found");
return tokens;
}
Err(e) => {
Expand Down
5 changes: 3 additions & 2 deletions src-tauri/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub async fn make_request<T: Serialize>(
) -> Result<serde_json::Value, anyhow::Error> {
let address = SocketAddr::from_str(JSON_CONNECT_ADDRESS).unwrap();
let url = format!("http://{}", address);
let method_name = method.clone();
let client = reqwest::Client::new();
let body = JsonRpcRequest {
id: 0,
Expand All @@ -112,11 +113,11 @@ pub async fn make_request<T: Serialize>(
let resp = builder.json(&body).send().await?.json::<JsonRpcResponse>().await?;
match resp.result {
JsonRpcAnswer::Result(result) => {
info!(target: LOG_TARGET, "👁️‍🗨️ JSON rpc request result: {:?}", result);
info!(target: LOG_TARGET, "👁️‍🗨️ JSON rpc request {:?} completed successfully", method_name);
Ok(result)
}
JsonRpcAnswer::Error(error) => {
error!(target: LOG_TARGET, "🚨 JSON rpc request error: {:?}", error);
error!(target: LOG_TARGET, "🚨 JSON rpc request {:?} error: {:?}", method_name, error);
Err(anyhow::Error::msg(error.to_string()))
}
}
Expand Down
17 changes: 12 additions & 5 deletions src-tauri/src/tapplet_installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::{
database::models::TappletVersion,
error::{ Error::{ self, IOError, JsonParsingError, RequestError }, IOError::*, RequestError::* },
hash_calculator::calculate_checksum,
interface::{ RegisteredTapplets, TappletAssets, TappletConfig, TariPermission },
interface::{ RegisteredTapplets, TappletAssets, TappletConfig, TappletPermissions },
};
use log::error;
use log::{ error, warn };
use crate::constants::TAPPLETS_INSTALLED_DIR;
pub const LOG_TARGET: &str = "tari::universe";

Expand Down Expand Up @@ -153,15 +153,22 @@ pub fn check_files_and_validate_checksum(tapp: TappletVersion, tapp_dir: PathBuf
Ok(is_checksum_valid)
}

pub fn get_tapp_permissions(tapp_path: PathBuf) -> Result<Vec<TariPermission>, Error> {
pub fn get_tapp_permissions(tapp_path: PathBuf) -> Result<TappletPermissions, Error> {
// tapp_dir = universe.tari/tapplets_installed/<tapplet_name>/<version>/package
let tapp_dir: PathBuf = tapp_path.join("package");
let tapp_config = tapp_dir.join("dist").join("tapplet.config.json");
if !tapp_config.exists() {
return Err(IOError(FailedToGetFilePath));
warn!(target: LOG_TARGET, "❌ Failed to get Tapplet permissions. Config file not found.");
return Ok(TappletPermissions {
required_permissions: vec![],
optional_permissions: vec![],
});
}

let config = fs::read_to_string(tapp_config.clone()).unwrap_or_default();
let tapplet: TappletConfig = serde_json::from_str(&config).map_err(|e| JsonParsingError(e))?;
Ok(tapplet.permissions)
Ok(TappletPermissions {
required_permissions: tapplet.permissions.required_permissions,
optional_permissions: tapplet.permissions.optional_permissions,
})
}
5 changes: 3 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Wallet } from "./components/Wallet"
import { BrowserRouter, Routes, Route, Link } from "react-router-dom"
import { TappletsRegistered } from "./components/TappletsRegistered"
import { TappletsInstalled } from "./components/TappletsInstalled"
import { ActiveDevTapplet } from "./components/DevTapplet"
import { ActiveDevTapplet } from "./components/ActiveDevTapplet"
import { Button, Grid, Stack } from "@mui/material"
import React, { useEffect } from "react"
import { useDispatch } from "react-redux"
Expand All @@ -22,12 +22,14 @@ import { invoke } from "@tauri-apps/api/core"
import { errorActions } from "./store/error/error.slice"
import { ErrorSource } from "./store/error/error.types"
import { Account } from "./components/Account"
import { accountActions } from "./store/account/account.slice"

function App() {
const { t } = useTranslation(["navigation", "components"])
const dispatch = useDispatch()
useEffect(() => {
dispatch(providerActions.initializeRequest({}))
dispatch(accountActions.initializeRequest({}))
dispatch(registeredTappletsActions.initializeRequest({}))
dispatch(installedTappletsActions.initializeRequest({}))
dispatch(devTappletsActions.initializeRequest({}))
Expand Down Expand Up @@ -62,7 +64,6 @@ function App() {
<Stack direction="row" gap={4} width="100%" justifyContent="flex-start">
<Account />
</Stack>

<Stack direction="row" gap={4} gridArea="1 / 2 / 2 / 5" width="100%" justifyContent="center">
<Link to={TabKey.WALLET} className="nav-item">
{t("wallet")}
Expand Down
15 changes: 12 additions & 3 deletions src/components/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@ import { useTranslation } from "react-i18next"
import { accountSelector } from "../store/account/account.selector"
import { substateIdToString } from "@tari-project/typescript-bindings"
import { shortenSubstateAddress } from "../helpers/address"
import { tappletProviderSelector } from "../store/tappletProviders/tappletProviders.selector"

// TODO this component is just mvp to displat active account
// TODO this component is just tmp to show and control provider/account
export const Account: React.FC = () => {
const { t } = useTranslation(["components", "common"])
const currentAccount = useSelector(accountSelector.selectAccount)

const tappProviders = useSelector(tappletProviderSelector.getAllTappletProviders)
const accountAddress = substateIdToString(currentAccount?.account.address ?? null)

return (
<>
<Paper variant="outlined" elevation={0} sx={{ padding: 1, borderRadius: 2, width: "100%" }}>
<Stack direction="column" justifyContent="flex-end">
<Typography variant="caption" textAlign="left">{`Name: ${currentAccount?.account.name}`}</Typography>
<Typography
variant="caption"
textAlign="left"
>{`Id: ${currentAccount?.account.key_index} name: ${currentAccount?.account.name}`}</Typography>
<Typography variant="caption" textAlign="left">{`${t("address", {
ns: "common",
})}: ${shortenSubstateAddress(accountAddress)}`}</Typography>
<Typography
variant="caption"
textAlign="left"
>{`Nr of Tapplet Providers: ${tappProviders.ids.length} `}</Typography>
</Stack>
</Paper>
</>
Expand Down
78 changes: 78 additions & 0 deletions src/components/ActiveDevTapplet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Box } from "@mui/material"
import { useLocation } from "react-router-dom"
import { DevTapplet, TappletConfig } from "@type/tapplet"
import { useEffect, useState } from "react"
import { Tapplet } from "./Tapplet"
import { useDispatch, useSelector } from "react-redux"
import { errorActions } from "../store/error/error.slice"
import { ErrorSource } from "../store/error/error.types"
import { tappletProvidersActions } from "../store/tappletProviders/tappletProviders.slice"
import { RootState } from "../store/store"
import { getTappProviderId, selectTappProviderById } from "../helpers/provider"

export function ActiveDevTapplet() {
let { state: devTapplet }: { state: DevTapplet } = useLocation()
const dispatch = useDispatch()
const [isVerified, setIsVerified] = useState<boolean>(false)
const tappProviderId = getTappProviderId({ devTappletId: devTapplet.id })
const tappProvider = useSelector((state: RootState) => selectTappProviderById(state, tappProviderId))

useEffect(() => {
const fetchTappletConfig = async () => {
try {
const config: TappletConfig = await (await fetch(`${devTapplet?.endpoint}/tapplet.config.json`)).json() //TODO add const as path to config

if (config?.packageName === devTapplet?.package_name) {
setIsVerified(true)

if (!config?.permissions) {
dispatch(
errorActions.showError({
message: `failed-to-fetch-tapp-config | error-${"Tapplet permissions undefined"}`,
errorSource: ErrorSource.BACKEND,
})
)
return
}

if (!tappProvider) {
dispatch(
tappletProvidersActions.addTappProviderReq({
id: tappProviderId,
launchedTappParams: {
endpoint: devTapplet?.endpoint,
permissions: config.permissions,
},
})
)
return
}

dispatch(
tappletProvidersActions.updateTappProviderRequest({
id: tappProviderId,
permissions: config.permissions,
})
)
}
} catch (error) {
dispatch(
errorActions.showError({
message: `failed-to-fetch-tapp-config | error-${error}`,
errorSource: ErrorSource.BACKEND,
})
)
}
}

if (devTapplet?.endpoint) {
fetchTappletConfig()
}
}, [])

return (
<Box height="100%">
{isVerified && tappProvider && <Tapplet source={devTapplet.endpoint} provider={tappProvider} />}
</Box>
)
}
44 changes: 36 additions & 8 deletions src/components/ActiveTapplet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,59 @@ import { invoke } from "@tauri-apps/api/core"
import { useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { Tapplet } from "./Tapplet"
import { useDispatch } from "react-redux"
import { useDispatch, useSelector } from "react-redux"
import { errorActions } from "../store/error/error.slice"
import { useTranslation } from "react-i18next"
import { ErrorSource } from "../store/error/error.types"
import { providerActions } from "../store/provider/provider.slice"
import { LaunchedTappResult } from "@type/tapplet"
import { tappletProvidersActions } from "../store/tappletProviders/tappletProviders.slice"
import { RootState } from "../store/store"
import { getTappProviderId, selectTappProviderById } from "../helpers/provider"

export function ActiveTapplet() {
const { t } = useTranslation("components")
const [tappletAddress, setTappletAddress] = useState("")
const { id } = useParams()
const installedTappletId = Number(id)
const dispatch = useDispatch()
const [tappletAddress, setTappletAddress] = useState("")
const installedTappletId = Number(id)
const tappProviderId = getTappProviderId({ installedTappletId: installedTappletId })
const tappProvider = useSelector((state: RootState) => selectTappProviderById(state, tappProviderId))

useEffect(() => {
invoke("launch_tapplet", { installedTappletId })
.then((res: any) => {
const launchedTappParams: LaunchedTappResult = res
setTappletAddress(launchedTappParams.endpoint)
if (launchedTappParams.permissions) {
dispatch(providerActions.updatePermissionsRequest({ permissions: launchedTappParams.permissions }))
} else {

if (!launchedTappParams.permissions) {
dispatch(
errorActions.showError({
message: `failed-to-fetch-tapp-config | error-${"Tapplet permissions undefined"}`,
errorSource: ErrorSource.BACKEND,
})
)
return
}

if (!tappProvider) {
dispatch(
tappletProvidersActions.addTappProviderReq({
id: tappProviderId,
launchedTappParams: {
endpoint: launchedTappParams.endpoint,
permissions: launchedTappParams.permissions,
},
})
)
return
}

dispatch(
tappletProvidersActions.updateTappProviderRequest({
id: tappProviderId,
permissions: launchedTappParams.permissions,
})
)
})
.catch((error: string) => dispatch(errorActions.showError({ message: error, errorSource: ErrorSource.BACKEND })))

Expand All @@ -45,7 +69,11 @@ export function ActiveTapplet() {

return (
<Box height="100%">
{tappletAddress ? <Tapplet source={tappletAddress} /> : <Typography>{t("taplet-obtain-failure")}</Typography>}
{tappletAddress && tappProvider ? (
<Tapplet source={tappletAddress} provider={tappProvider} />
) : (
<Typography>{t("taplet-obtain-failure")}</Typography>
)}
</Box>
)
}
4 changes: 4 additions & 0 deletions src/components/AddDevTappletDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import DialogTitle from "@mui/material/DialogTitle"
import { devTappletsActions } from "../store/devTapplets/devTapplets.slice"
import { useDispatch } from "react-redux"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { TabKey } from "../views/Tabs"

export default function AddDevTappletDialog() {
const { t } = useTranslation(["components", "common"])
const [open, setOpen] = useState(false)
const [hasError, setHasError] = useState(false)
const [errorMsg, setErrorMsg] = useState("")
const dispatch = useDispatch()
const navigate = useNavigate()

const onSubmitHandler = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
Expand All @@ -25,6 +28,7 @@ export default function AddDevTappletDialog() {
try {
dispatch(devTappletsActions.addDevTappletRequest({ endpoint }))
handleClose()
navigate(`/${TabKey.INSTALLED_TAPPLETS}`)
} catch (error) {
setHasError(true)
setErrorMsg(error as string)
Expand Down
Loading

0 comments on commit b54b5cb

Please sign in to comment.