Skip to content

Commit

Permalink
Namadillo: Checks for indexer and keychain compatibility (#1449)
Browse files Browse the repository at this point in the history
* feat: checks for indexer and keychain compatibility

* refactor: decoupling code a bit

* feat: improving code readability
  • Loading branch information
pedrorezende authored Dec 27, 2024
1 parent 7a5430a commit 2a7cd74
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 10 deletions.
23 changes: 23 additions & 0 deletions apps/namadillo/src/App/Common/FixedWarningBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ReactNode } from "react";
import { IoWarning } from "react-icons/io5";

type FixedWarningBannerProps = {
errorMessage: ReactNode;
};

export const FixedWarningBanner = ({
errorMessage,
}: FixedWarningBannerProps): JSX.Element => {
if (!errorMessage) return <></>;

return (
<div className="fixed bottom-0 left-0 w-full bg-yellow z-[9999]">
<div className="flex flex-row justify-center items-center gap-1 px-12 py-3 text-sm [&_a]:underline">
<strong className="inline-flex items-center">
<IoWarning /> WARNING:{" "}
</strong>
<div>{errorMessage}</div>
</div>
</div>
);
};
4 changes: 4 additions & 0 deletions apps/namadillo/src/App/Layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FixedWarningBanner } from "App/Common/FixedWarningBanner";
import { useCompatibilityErrors } from "hooks/useCompatibilityErrors";
import { ReactNode, useState } from "react";
import { twMerge } from "tailwind-merge";
import { AppHeader } from "./AppHeader";
Expand All @@ -10,6 +12,7 @@ export const AppLayout = ({
children: ReactNode;
}): JSX.Element => {
const [displayNavigation, setDisplayNavigation] = useState(false);
const compatibilityErrors = useCompatibilityErrors();

return (
<div className="custom-container pb-2">
Expand Down Expand Up @@ -42,6 +45,7 @@ export const AppLayout = ({
</aside>
<main className="min-h-full">{children}</main>
</div>
<FixedWarningBanner errorMessage={compatibilityErrors} />
</div>
);
};
13 changes: 8 additions & 5 deletions apps/namadillo/src/atoms/settings/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SettingsStorage } from "types";
import {
clearShieldedContext,
fetchDefaultTomlConfig,
isIndexerAlive,
getIndexerHealth,
isMaspIndexerAlive,
isRpcAlive,
} from "./services";
Expand Down Expand Up @@ -176,7 +176,10 @@ export const maspIndexerUrlAtom = atom((get) => {
export const updateIndexerUrlAtom = atomWithMutation(() => {
return {
mutationKey: ["update-indexer-url"],
mutationFn: changeSettingsUrl("indexerUrl", isIndexerAlive),
mutationFn: changeSettingsUrl(
"indexerUrl",
async (url: string): Promise<boolean> => !!(await getIndexerHealth(url))
),
};
});

Expand All @@ -201,9 +204,9 @@ export const indexerHeartbeatAtom = atomWithQuery((get) => {
refetchOnWindowFocus: true,
refetchInterval: 10_000,
queryFn: async () => {
const valid = await isIndexerAlive(indexerUrl);
if (!valid) throw "Unable to verify indexer heartbeat";
return true;
const indexerInfo = await getIndexerHealth(indexerUrl);
if (!indexerInfo) throw "Unable to verify indexer heartbeat";
return indexerInfo;
},
};
});
Expand Down
16 changes: 11 additions & 5 deletions apps/namadillo/src/atoms/settings/services.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Configuration, DefaultApi } from "@namada/indexer-client";
import { isUrlValid } from "@namada/utils";
import toml from "toml";
import { SettingsTomlOptions } from "types";
import { SettingsTomlOptions, TempIndexerHealthType } from "types";
import { getSdkInstance } from "utils/sdk";

export const isIndexerAlive = async (url: string): Promise<boolean> => {
export const getIndexerHealth = async (
url: string
): Promise<TempIndexerHealthType | undefined> => {
if (!isUrlValid(url)) {
return false;
return;
}

try {
const configuration = new Configuration({ basePath: url });
const api = new DefaultApi(configuration);
const response = await api.healthGet();
return response.status === 200;

// TODO:update when indexer swagger is fixed
// @ts-expect-error Indexer swagger is out of date
return response.data as TempIndexerHealthType;
} catch {
return false;
return;
}
};

Expand Down
4 changes: 4 additions & 0 deletions apps/namadillo/src/compatibility.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"keychain": "0.3.x",
"indexer": "1.1.x"
}
47 changes: 47 additions & 0 deletions apps/namadillo/src/hooks/useCompatibilityErrors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { indexerHeartbeatAtom } from "atoms/settings";
import { useAtomValue } from "jotai";
import { useEffect, useState } from "react";
import {
checkIndexerCompatibilityErrors,
checkKeychainCompatibilityError,
} from "utils/compatibility";
import { useNamadaKeychain } from "./useNamadaKeychain";

export const useCompatibilityErrors = (): React.ReactNode | undefined => {
const indexerHealth = useAtomValue(indexerHeartbeatAtom);
const keychain = useNamadaKeychain();
const [errorMessage, setErrorMessage] = useState<
React.ReactNode | undefined
>();

const verifyKeychainVersion = async (): Promise<void> => {
const namadaKeychain = await keychain.namadaKeychain.get();
if (namadaKeychain) {
const version = namadaKeychain.version();
const versionErrorMessage = checkKeychainCompatibilityError(version);
if (versionErrorMessage) {
setErrorMessage(versionErrorMessage);
}
}
};

const verifyIndexerVersion = async (): Promise<void> => {
const versionErrorMessage = checkIndexerCompatibilityErrors(
indexerHealth.data?.version || ""
);

if (versionErrorMessage) {
setErrorMessage(versionErrorMessage);
}
};

useEffect(() => {
verifyKeychainVersion();
}, [keychain]);

useEffect(() => {
indexerHealth.isSuccess && verifyIndexerVersion();
}, [indexerHealth]);

return errorMessage;
};
6 changes: 6 additions & 0 deletions apps/namadillo/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,9 @@ export type LocalnetToml = {
chain_1_channel: string;
chain_2_channel: string;
};

// TODO: remove this after indexer swagger gets fixed
export type TempIndexerHealthType = {
version: string;
commit: string;
};
104 changes: 104 additions & 0 deletions apps/namadillo/src/utils/compatibility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { routes } from "App/routes";
import compatibilty from "compatibility.json";
import { wallets } from "integrations";
import { Link } from "react-router-dom";
import semverSatisfies from "semver/functions/satisfies";
import semverLtr from "semver/ranges/ltr";
import { isFirefox } from "./etc";

enum CompatibilityOutput {
IncompatibleVersion = -1,
Compatible = 0,
InterfaceOutdated = 1,
}

// Checks if the versions are compatible using semantic versioning (semver).
// Returns true if the versions are compatible, -1 if the version is outdated,
// or 1 if a required version is lower than the version provided.
const checkVersionsCompatible = (
currentVersion: string,
requiredVersion: string
): CompatibilityOutput => {
if (semverSatisfies(currentVersion, requiredVersion)) {
return CompatibilityOutput.Compatible;
}
return semverLtr(currentVersion, requiredVersion) ?
CompatibilityOutput.IncompatibleVersion
: CompatibilityOutput.InterfaceOutdated;
};

export const checkIndexerCompatibilityErrors = (
indexerVersion: string
): React.ReactNode => {
const requiredVersion = compatibilty.indexer;
const checkResult = checkVersionsCompatible(indexerVersion, requiredVersion);

if (checkResult === CompatibilityOutput.IncompatibleVersion) {
return (
<>
You&apos;re using an outdated version of Namada Indexer. Please update
your indexer URL in the{" "}
<Link to={routes.settingsAdvanced}>Advanced Settings</Link> section.
</>
);
}

if (checkResult === CompatibilityOutput.InterfaceOutdated) {
return (
<>
Your Namadillo version is not compatible with the current Namada
Indexer. Please upgrade your web interface or pick a different one from
the <a href="https://namada.net/apps#interfaces">Namada Apps</a> list.
</>
);
}

return "";
};

export const checkKeychainCompatibilityError = (
keychainVersion: string
): React.ReactNode => {
const targetKeychainVersion = compatibilty.keychain;
const checkResult = checkVersionsCompatible(
keychainVersion,
targetKeychainVersion
);

if (checkResult === CompatibilityOutput.IncompatibleVersion) {
return (
<>
Your Namada Keychain version is outdated. Please upgrade it using{" "}
{isFirefox() ?
<a
href={wallets.namada.downloadUrl.firefox}
target="_blank"
rel="nofollow noreferrer"
>
Firefox addons
</a>
: <a
href={wallets.namada.downloadUrl.chrome}
target="_blank"
rel="nofollow noreferrer"
>
Chrome store
</a>
}{" "}
or websites.
</>
);
}

if (checkResult === CompatibilityOutput.InterfaceOutdated) {
return (
<>
Your Namadillo version is not compatible with the keychain installed.
Please upgrade your web interface or pick a different one from the{" "}
<a href="https://namada.net/apps#interfaces">Namada Apps</a> list.
</>
);
}

return "";
};
1 change: 1 addition & 0 deletions apps/namadillo/src/utils/etc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isFirefox = (): boolean => /firefox/i.test(navigator.userAgent);

1 comment on commit 2a7cd74

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.