Skip to content

feat: add real shared worker #1684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading