mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 08:58:00 +00:00
171 lines
4.7 KiB
TypeScript
171 lines
4.7 KiB
TypeScript
// Copyright 2017-2026 @pezkuwi/app-settings authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { ApiPromise } from '@pezkuwi/api';
|
|
import type { InjectedExtension, InjectedMetadataKnown, MetadataDef } from '@pezkuwi/extension-inject/types';
|
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import store from 'store';
|
|
|
|
import { createNamedHook, useApi } from '@pezkuwi/react-hooks';
|
|
|
|
interface ExtensionKnown {
|
|
extension: InjectedExtension;
|
|
known: InjectedMetadataKnown[];
|
|
update: (def: MetadataDef) => Promise<boolean>;
|
|
}
|
|
|
|
interface ExtensionInfo extends ExtensionKnown {
|
|
current: InjectedMetadataKnown | null;
|
|
}
|
|
|
|
interface Extensions {
|
|
count: number;
|
|
extensions: ExtensionInfo[];
|
|
}
|
|
|
|
interface ExtensionProperties {
|
|
extensionVersion: string;
|
|
tokenDecimals: number;
|
|
tokenSymbol: string;
|
|
ss58Format?: number;
|
|
}
|
|
|
|
type SavedProperties = Record<string, ExtensionProperties>;
|
|
|
|
type TriggerFn = (counter: number) => void;
|
|
|
|
let triggerCount = 0;
|
|
const triggers = new Map<string, TriggerFn>();
|
|
|
|
function triggerAll (): void {
|
|
[...triggers.values()].forEach((trigger) => trigger(Date.now()));
|
|
}
|
|
|
|
// save the properties for a specific extension
|
|
function saveProperties (api: ApiPromise, { name, version }: InjectedExtension): void {
|
|
const storeKey = `properties:${api.genesisHash.toHex()}`;
|
|
const allProperties = store.get(storeKey, {}) as SavedProperties;
|
|
|
|
allProperties[name] = {
|
|
extensionVersion: version,
|
|
ss58Format: api.registry.chainSS58,
|
|
tokenDecimals: api.registry.chainDecimals[0],
|
|
tokenSymbol: api.registry.chainTokens[0]
|
|
};
|
|
|
|
store.set(storeKey, allProperties);
|
|
}
|
|
|
|
// determines if the extension has current properties
|
|
function hasCurrentProperties (api: ApiPromise, { extension }: ExtensionKnown): boolean {
|
|
const allProperties = store.get(`properties:${api.genesisHash.toHex()}`, {}) as SavedProperties;
|
|
|
|
// when we don't have properties yet, assume nothing has changed and store
|
|
if (!allProperties[extension.name]) {
|
|
saveProperties(api, extension);
|
|
|
|
return true;
|
|
}
|
|
|
|
const { ss58Format, tokenDecimals, tokenSymbol } = allProperties[extension.name];
|
|
|
|
return ss58Format === api.registry.chainSS58 &&
|
|
tokenDecimals === api.registry.chainDecimals[0] &&
|
|
tokenSymbol === api.registry.chainTokens[0];
|
|
}
|
|
|
|
// filter extensions based on the properties we have available
|
|
function filterAll (api: ApiPromise, all: ExtensionKnown[]): Extensions {
|
|
const extensions = all
|
|
.map((info): ExtensionInfo | null => {
|
|
const current = info.known.find(({ genesisHash }) => api.genesisHash.eq(genesisHash)) || null;
|
|
|
|
// if we cannot find it as known, or either the specVersion or properties mismatches, mark it as upgradable
|
|
return !current || api.runtimeVersion.specVersion.gtn(current.specVersion) || !hasCurrentProperties(api, info)
|
|
? { ...info, current }
|
|
: null;
|
|
})
|
|
.filter((info): info is ExtensionInfo => !!info);
|
|
|
|
return {
|
|
count: extensions.length,
|
|
extensions
|
|
};
|
|
}
|
|
|
|
async function getExtensionInfo (api: ApiPromise, extension: InjectedExtension): Promise<ExtensionKnown | null> {
|
|
if (!extension.metadata) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const metadata = extension.metadata;
|
|
const known = await metadata.get();
|
|
|
|
return {
|
|
extension,
|
|
known,
|
|
update: async (def: MetadataDef): Promise<boolean> => {
|
|
let isOk = false;
|
|
|
|
try {
|
|
isOk = await metadata.provide(def);
|
|
|
|
if (isOk) {
|
|
saveProperties(api, extension);
|
|
triggerAll();
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
return isOk;
|
|
}
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function getKnown (api: ApiPromise, extensions: InjectedExtension[], _: number): Promise<ExtensionKnown[]> {
|
|
const all = await Promise.all(
|
|
extensions.map((extension) => getExtensionInfo(api, extension))
|
|
);
|
|
|
|
return all.filter((info): info is ExtensionKnown => !!info);
|
|
}
|
|
|
|
const EMPTY_STATE = { count: 0, extensions: [] };
|
|
|
|
function useExtensionsImpl (): Extensions {
|
|
const { api, extensions, isApiReady, isDevelopment } = useApi();
|
|
const [all, setAll] = useState<ExtensionKnown[] | undefined>();
|
|
const [trigger, setTrigger] = useState(0);
|
|
|
|
useEffect((): () => void => {
|
|
const myId = `${++triggerCount}-${Date.now()}`;
|
|
|
|
triggers.set(myId, setTrigger);
|
|
|
|
return (): void => {
|
|
triggers.delete(myId);
|
|
};
|
|
}, []);
|
|
|
|
useEffect((): void => {
|
|
extensions && getKnown(api, extensions, trigger)
|
|
.then(setAll)
|
|
.catch(console.error);
|
|
}, [api, extensions, trigger]);
|
|
|
|
return useMemo(
|
|
() => isDevelopment || !isApiReady || !all
|
|
? EMPTY_STATE
|
|
: filterAll(api, all),
|
|
[all, api, isApiReady, isDevelopment]
|
|
);
|
|
}
|
|
|
|
export default createNamedHook('useExtensions', useExtensionsImpl);
|