Skip to content

Commit

Permalink
feat: add real shared worker (#1684)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick <[email protected]>
  • Loading branch information
AMIRKHANEF and Nick-1979 authored Dec 6, 2024
1 parent 89a5609 commit b190fc1
Show file tree
Hide file tree
Showing 14 changed files with 494 additions and 144 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ _book
docs/html
.idea
.vscode
.env.sentry-build-plugin
2 changes: 1 addition & 1 deletion packages/extension-polkagate/src/components/contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ToastContext = React.createContext<({ show: (message: string) => void })>(
const UserAddedChainContext = React.createContext<UserAddedChains>({});
const GenesisHashOptionsContext = React.createContext<DropdownOption[]>([]);
const AccountIconThemeContext = React.createContext<AccountIconThemeContextType>({ accountIconTheme: undefined, setAccountIconTheme: noop });
const WorkerContext = React.createContext<Worker | undefined>(undefined);
const WorkerContext = React.createContext<MessagePort | undefined>(undefined);

export { AccountContext,
AccountIconThemeContext,
Expand Down
61 changes: 32 additions & 29 deletions packages/extension-polkagate/src/hooks/useAssetsBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const FUNCTIONS = ['getAssetOnRelayChain', 'getAssetOnAssetHub', 'getAssetOnMult
* @param addresses a list of users accounts' addresses
* @returns a list of assets balances on different selected chains and a fetching timestamp
*/
export default function useAssetsBalances (accounts: AccountJson[] | null, setAlerts: Dispatch<SetStateAction<AlertType[]>>, genesisOptions: DropdownOption[], userAddedEndpoints: UserAddedChains, worker?: Worker): SavedAssets | undefined | null {
export default function useAssetsBalances (accounts: AccountJson[] | null, setAlerts: Dispatch<SetStateAction<AlertType[]>>, genesisOptions: DropdownOption[], userAddedEndpoints: UserAddedChains, worker?: MessagePort): SavedAssets | undefined | null {
const { t } = useTranslation();

const isTestnetEnabled = useIsTestnetEnabled();
Expand Down Expand Up @@ -282,22 +282,33 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
}

setFetchedAssets((fetchedAssets) => {
const combinedAsset = fetchedAssets || DEFAULT_SAVED_ASSETS;
// Create a new object reference each time
const combinedAsset = {
...(fetchedAssets || DEFAULT_SAVED_ASSETS),
balances: {
...(fetchedAssets?.balances || DEFAULT_SAVED_ASSETS.balances)
}
};

Object.keys(assets).forEach((address) => {
if (combinedAsset.balances[address] === undefined) {
if (!combinedAsset.balances[address]) {
combinedAsset.balances[address] = {};
}

/** to group assets by their chain's genesisHash */
const { genesisHash } = assets[address][0];

combinedAsset.balances[address][genesisHash] = assets[address];
// Create a new reference for this specific balances entry
combinedAsset.balances[address] = {
...(combinedAsset.balances[address] || {}),
[genesisHash]: assets[address]
};
});

combinedAsset.timeStamp = Date.now();

return combinedAsset;
// Ensure a new timestamp and object reference
return {
...combinedAsset,
timeStamp: Date.now()
};
});
}, [addresses]);

Expand All @@ -306,8 +317,8 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
return;
}

worker.onmessage = (e: MessageEvent<string>) => {
const message = e.data;
const handleMessage = (messageEvent: MessageEvent<string>) => {
const message = messageEvent.data;

if (!message) {
return; // may receive unknown messages!
Expand Down Expand Up @@ -372,6 +383,12 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl

combineAndSetAssets(_assets);
};

worker.addEventListener('message', handleMessage);

return () => {
worker.removeEventListener('message', handleMessage);
};
}, [combineAndSetAssets, handleRequestCount, worker]);

const fetchAssetOnRelayChain = useCallback((_addresses: string[], chainName: string) => {
Expand All @@ -382,13 +399,7 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
const functionName = 'getAssetOnRelayChain';

worker.postMessage({ functionName, parameters: { address: _addresses, chainName, userAddedEndpoints } });

worker.onerror = (err) => {
console.log(err);
};

handleWorkerMessages();
}, [handleWorkerMessages, userAddedEndpoints, worker]);
}, [userAddedEndpoints, worker]);

const fetchAssetOnAssetHubs = useCallback((_addresses: string[], chainName: string, assetsToBeFetched?: Asset[]) => {
if (!worker) {
Expand All @@ -398,10 +409,6 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
const functionName = 'getAssetOnAssetHub';

worker.postMessage({ functionName, parameters: { address: _addresses, assetsToBeFetched, chainName, userAddedEndpoints } });

worker.onerror = (err) => {
console.log(err);
};
}, [userAddedEndpoints, worker]);

const fetchAssetOnMultiAssetChain = useCallback((addresses: string[], chainName: string) => {
Expand All @@ -412,13 +419,7 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
const functionName = 'getAssetOnMultiAssetChain';

worker.postMessage({ functionName, parameters: { addresses, chainName, userAddedEndpoints } });

worker.onerror = (err) => {
console.log(err);
};

handleWorkerMessages();
}, [handleWorkerMessages, userAddedEndpoints, worker]);
}, [userAddedEndpoints, worker]);

const fetchMultiAssetChainAssets = useCallback((chainName: string) => {
return addresses && fetchAssetOnMultiAssetChain(addresses, chainName);
Expand Down Expand Up @@ -480,14 +481,16 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl
!multipleAssetsChainsNames.includes(toCamelCase(text) || '')
);

handleWorkerMessages();

/** Fetch assets for all the selected chains by default */
_selectedChains?.forEach((genesisHash) => {
const isSingleTokenChain = !!singleAssetChains.find(({ value }) => value === genesisHash);
const maybeMultiAssetChainName = multipleAssetsChainsNames.find((chainName) => chainName === getChainName(genesisHash));

fetchAssets(genesisHash, isSingleTokenChain, maybeMultiAssetChainName);
});
}, [FETCH_PATHS, addresses, fetchAssets, worker, isTestnetEnabled, isUpdate, selectedChains, genesisOptions]);
}, [FETCH_PATHS, addresses, fetchAssets, worker, isTestnetEnabled, isUpdate, selectedChains, genesisOptions, handleWorkerMessages]);

return fetchedAssets;
}
48 changes: 28 additions & 20 deletions packages/extension-polkagate/src/hooks/useNFT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
// SPDX-License-Identifier: Apache-2.0

import type { AccountJson } from '@polkadot/extension-base/background/types';
import type { NftItemsType} from '../util/types';
import type { NftItemsType } from '../util/types';

import { useCallback, useEffect, useState } from 'react';

import NftManager from '../class/nftManager';
import { useTranslation } from '../components/translate';
import useAlerts from './useAlerts';
import { useWorker } from './useWorker';

export interface NftItemsWorker {
functionName: string;
results: NftItemsType;
}

const nftManager = new NftManager();
const NFT_FUNCTION_NAME = 'getNFTs';

export default function useNFT (accountsFromContext: AccountJson[] | null) {
const { t } = useTranslation();
const { notify } = useAlerts();
const worker = useWorker();

const [fetching, setFetching] = useState<boolean>(false);

Expand All @@ -27,18 +35,10 @@ export default function useNFT (accountsFromContext: AccountJson[] | null) {

const fetchNFTs = useCallback((addresses: string[]) => {
setFetching(true);
const getNFTsWorker: Worker = new Worker(new URL('../util/workers/getNFTs.js', import.meta.url));

getNFTsWorker.postMessage({ addresses });
worker.postMessage({ functionName: NFT_FUNCTION_NAME, parameters: { addresses } });

getNFTsWorker.onerror = (err) => {
console.error('Worker error:', err);
setFetching(false);
getNFTsWorker.terminate();
};

getNFTsWorker.onmessage = (e: MessageEvent<string>) => {
const NFTs = e.data;
const handleMessage = (messageEvent: MessageEvent<string>) => {
const NFTs = messageEvent.data;

if (!NFTs) {
notify(t('Unable to fetch NFT/Unique items!'), 'info');
Expand All @@ -47,27 +47,35 @@ export default function useNFT (accountsFromContext: AccountJson[] | null) {
return;
}

let parsedNFTsInfo: NftItemsType;
let parsedNFTsInfo: NftItemsWorker;

try {
parsedNFTsInfo = JSON.parse(NFTs) as NftItemsType;
parsedNFTsInfo = JSON.parse(NFTs) as NftItemsWorker;

// console.log('All fetched NFTs:', parsedNFTsInfo);

if (parsedNFTsInfo.functionName !== NFT_FUNCTION_NAME) {
return;
}
} catch (error) {
console.error('Failed to parse NFTs JSON:', error);
// setFetching(false);
getNFTsWorker.terminate();

return;
}

// console.log('All fetched NFTs:', parsedNFTsInfo);

// Save all fetched items to Chrome storage
saveToStorage(parsedNFTsInfo);
saveToStorage(parsedNFTsInfo.results);

// setFetching(false);
getNFTsWorker.terminate();
};
}, [notify, saveToStorage, t]);

worker.addEventListener('message', handleMessage);

return () => {
worker.removeEventListener('message', handleMessage);
};
}, [notify, saveToStorage, t, worker]);

useEffect(() => {
if (!fetching && addresses && addresses.length > 0 && onWhitelistedPath) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
import { closeWebsockets, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils';
import { getAssets } from './getAssets.js';

// @ts-ignore

export async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainName, userAddedEndpoints) {
/**
*
* @param {string[]} addresses
* @param {import('@polkagate/apps-config/assets/types').Asset[]} assetsToBeFetched
* @param {string} chainName
* @param {import('../../types').UserAddedChains} userAddedEndpoints
* @param {MessagePort} port
*/
export async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainName, userAddedEndpoints, port) {
const endpoints = getChainEndpoints(chainName, userAddedEndpoints);
const { api, connections } = await fastestEndpoint(endpoints);

const { metadata } = metadataFromApi(api);

postMessage(JSON.stringify({ functionName: 'getAssetOnAssetHub', metadata }));
console.info('Shared worker, metadata fetched and sent for chain:', chainName);
port.postMessage(JSON.stringify({ functionName: 'getAssetOnAssetHub', metadata }));

const results = await toGetNativeToken(addresses, api, chainName);

Expand All @@ -32,6 +39,7 @@ export async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainNam

await getAssets(addresses, api, nonNativeAssets, chainName, results);

postMessage(JSON.stringify({ functionName: 'getAssetOnAssetHub', results }));
console.info('Shared worker, account assets fetched and send on chain:', chainName);
port.postMessage(JSON.stringify({ functionName: 'getAssetOnAssetHub', results }));
closeWebsockets(connections);
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

// @ts-nocheck

import { getSubstrateAddress } from '../../utils';
// eslint-disable-next-line import/extensions
import { balancifyAsset, closeWebsockets, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils';

export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, chainName, userAddedEndpoints) {
/**
*
* @param {import('@polkagate/apps-config/assets/types').Asset[]} assetsToBeFetched
* @param {string[]} addresses
* @param {string} chainName
* @param {import('../../types').UserAddedChains} userAddedEndpoints
* @param {MessagePort} port
*/
export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, chainName, userAddedEndpoints, port) {
const endpoints = getChainEndpoints(chainName, userAddedEndpoints);
const { api, connections } = await fastestEndpoint(endpoints);

const { metadata } = metadataFromApi(api);

postMessage(JSON.stringify({ functionName: 'getAssetOnMultiAssetChain', metadata }));
console.info('Shared worker, metadata fetched and sent for chain:', chainName);
port.postMessage(JSON.stringify({ functionName: 'getAssetOnMultiAssetChain', metadata }));

const results = await toGetNativeToken(addresses, api, chainName);

const maybeTheAssetOfAddresses = addresses.map((address) => api.query.tokens.accounts.entries(address));
const maybeTheAssetOfAddresses = addresses.map((address) => api.query['tokens']['accounts'].entries(address));
const balanceOfAssetsOfAddresses = await Promise.all(maybeTheAssetOfAddresses);

balanceOfAssetsOfAddresses.flat().forEach((entry) => {
if (!entry.length) {
return;
}

// @ts-ignore
const formatted = entry[0].toHuman()[0];
const storageKey = entry[0].toString();

// @ts-ignore
const foundAsset = assetsToBeFetched.find((_asset) => {
const currencyId = _asset?.extras?.currencyIdScale.replace('0x', '');
const currencyId = _asset?.extras?.['currencyIdScale'].replace('0x', '');

return currencyId && storageKey.endsWith(currencyId);
});

const balance = entry[1];
// @ts-ignore
const totalBalance = balance.free.add(balance.reserved);

if (foundAsset) {
Expand All @@ -52,12 +62,14 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c

const address = getSubstrateAddress(formatted);

// @ts-ignore
results[address]?.push(asset) ?? (results[address] = [asset]);
} else {
console.info(`NOTE: There is an asset on ${chainName} for ${formatted} which is not whitelisted. assetInfo`, storageKey, balance?.toHuman());
}
});

postMessage(JSON.stringify({ functionName: 'getAssetOnMultiAssetChain', results }));
console.info('Shared worker, account assets fetched and send on chain:', chainName);
port.postMessage(JSON.stringify({ functionName: 'getAssetOnMultiAssetChain', results }));
closeWebsockets(connections);
}
Loading

0 comments on commit b190fc1

Please sign in to comment.