From 7b2c864811c96e02f057744604ef2e2726e249cb Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Fri, 10 May 2024 13:58:41 +0800 Subject: [PATCH 01/71] feat: add `getOwnersByContract` api (#11609) * feat: add `getOwnersByContract` api * feat: adjust `getOwnersByContract` params `cursor` --- .../web3-providers/src/SimpleHash/apis/EVM.ts | 19 +++++++++++++++++++ .../web3-providers/src/types/SimpleHash.ts | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/web3-providers/src/SimpleHash/apis/EVM.ts b/packages/web3-providers/src/SimpleHash/apis/EVM.ts index ada9f9dabced..6e6b52f42fda 100644 --- a/packages/web3-providers/src/SimpleHash/apis/EVM.ts +++ b/packages/web3-providers/src/SimpleHash/apis/EVM.ts @@ -94,6 +94,25 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider = {}, + ) { + const chain = resolveChain(NetworkPluginID.PLUGIN_EVM, chainId) + const path = urlcat('/api/v0/nfts/owners/:chain/:contract_address', { + chain, + contract_address: address, + cursor: indicator?.id || undefined, + limit: size, + }) + const response = await fetchFromSimpleHash<{ next_cursor: string; owners: SimpleHash.Owner[] }>(path) + return createPageable( + response.owners, + indicator, + response.next_cursor ? createNextIndicator(indicator, response.next_cursor) : undefined, + ) + } + async getCollectionOverview(chainId: ChainId, id: string): Promise { // SimpleHash collection id is not address if (isValidAddress(id)) return diff --git a/packages/web3-providers/src/types/SimpleHash.ts b/packages/web3-providers/src/types/SimpleHash.ts index f4169a1a7151..77e7cd6f4b51 100644 --- a/packages/web3-providers/src/types/SimpleHash.ts +++ b/packages/web3-providers/src/types/SimpleHash.ts @@ -164,4 +164,13 @@ export namespace SimpleHash { wallet_address: string contracts: Array<{ contract_address: string; token_ids: string[] }> } + + export interface Owner { + nft_id: string + owner_address: string + token_id: string + quantity: number + first_acquired_date: string + last_acquired_date: string + } } From e09739bf84e0423490e166fc2e65472900c1c1a9 Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Fri, 10 May 2024 22:19:28 +0800 Subject: [PATCH 02/71] feat: add `getTopCollectorsByContract` api to `SimpleHashEVM` (#11611) --- .../web3-providers/src/SimpleHash/apis/EVM.ts | 22 +++++++++++++++++++ .../web3-providers/src/types/SimpleHash.ts | 8 +++++++ 2 files changed, 30 insertions(+) diff --git a/packages/web3-providers/src/SimpleHash/apis/EVM.ts b/packages/web3-providers/src/SimpleHash/apis/EVM.ts index 6e6b52f42fda..bb6ce9b03f8f 100644 --- a/packages/web3-providers/src/SimpleHash/apis/EVM.ts +++ b/packages/web3-providers/src/SimpleHash/apis/EVM.ts @@ -113,6 +113,28 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider = {}, + ) { + const chain = resolveChain(NetworkPluginID.PLUGIN_EVM, chainId) + const path = urlcat('/api/v0/nfts/top_collectors/:chain/:contract_address', { + chain, + contract_address: address, + cursor: indicator?.id || undefined, + limit: size, + include_owner_image: '1', + }) + const response = await fetchFromSimpleHash<{ next_cursor: string; top_collectors: SimpleHash.TopCollector[] }>( + path, + ) + return createPageable( + response.top_collectors, + indicator, + response.next_cursor ? createNextIndicator(indicator, response.next_cursor) : undefined, + ) + } + async getCollectionOverview(chainId: ChainId, id: string): Promise { // SimpleHash collection id is not address if (isValidAddress(id)) return diff --git a/packages/web3-providers/src/types/SimpleHash.ts b/packages/web3-providers/src/types/SimpleHash.ts index 77e7cd6f4b51..9ee941309f78 100644 --- a/packages/web3-providers/src/types/SimpleHash.ts +++ b/packages/web3-providers/src/types/SimpleHash.ts @@ -173,4 +173,12 @@ export namespace SimpleHash { first_acquired_date: string last_acquired_date: string } + + export interface TopCollector { + owner_address: string + owner_ens_name: string | null + owner_image: string + distinct_nfts_owned: number + total_copies_owned: number + } } From ce7b7fec0dcd0833039ebd2247bd86c85bb8218e Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Mon, 13 May 2024 15:33:57 +0800 Subject: [PATCH 03/71] fix: fw-555 wallet address is required to claim redpacket (#11614) --- .../RedPacket/src/SiteAdaptor/hooks/useClaimStrategyStatus.ts | 2 +- .../plugins/RedPacket/src/SiteAdaptor/hooks/useSignedMessage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useClaimStrategyStatus.ts b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useClaimStrategyStatus.ts index bf45fc0616ec..89d1d94b4893 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useClaimStrategyStatus.ts +++ b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useClaimStrategyStatus.ts @@ -21,7 +21,7 @@ export function useClaimStrategyStatus(payload: RedPacketJSONPayload | RedPacket enabled: !signedMessage && !!platform, queryKey: ['red-packet', 'claim-strategy', rpid, platform, account, me], queryFn: async () => { - if (!platform) return null + if (!platform || !account) return null return FireflyRedPacket.checkClaimStrategyStatus({ rpid, profile: { diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useSignedMessage.ts b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useSignedMessage.ts index f3cafa985433..f71c1e9fb766 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useSignedMessage.ts +++ b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useSignedMessage.ts @@ -38,7 +38,7 @@ export function useSignedMessage( } catch {} if (version <= 3) return password if (password) return signMessage(account, password).signature - if (!profile) return '' + if (!profile || !account) return '' return FireflyRedPacket.createClaimSignature({ rpid, profile, From 3d045062b19a6d1213a81188433f2c9e0e76d2d9 Mon Sep 17 00:00:00 2001 From: LeifXu Date: Mon, 13 May 2024 20:26:53 +0800 Subject: [PATCH 04/71] feat: export `resolveChainId` from `SimpleHash` --- packages/web3-providers/src/SimpleHash/index.ts | 1 + packages/web3-providers/src/entry.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web3-providers/src/SimpleHash/index.ts b/packages/web3-providers/src/SimpleHash/index.ts index f52e7572c7ee..c0996fbaceaa 100644 --- a/packages/web3-providers/src/SimpleHash/index.ts +++ b/packages/web3-providers/src/SimpleHash/index.ts @@ -1,3 +1,4 @@ export * from './apis/EVM.js' export * from './apis/Solana.js' export * from './constants.js' +export * from './helpers.js' diff --git a/packages/web3-providers/src/entry.ts b/packages/web3-providers/src/entry.ts index 7d32de69813d..d34e02da8ead 100644 --- a/packages/web3-providers/src/entry.ts +++ b/packages/web3-providers/src/entry.ts @@ -20,7 +20,7 @@ export { Multicall } from './Multicall/index.js' export { Lens } from './Lens/index.js' export { RedPacket } from './RedPacket/index.js' export { TheGraphRedPacket } from './TheGraph/index.js' -export { SimpleHashEVM, SimpleHashSolana, SPAM_SCORE } from './SimpleHash/index.js' +export { SimpleHashEVM, SimpleHashSolana, SPAM_SCORE, resolveChainId } from './SimpleHash/index.js' export { SnapshotSearch } from './Snapshot/index.js' export { Snapshot } from './Snapshot/index.js' export { FriendTech } from './FriendTech/index.js' From 2d1701110b42b02ce88ab242ed2f3c88677b970d Mon Sep 17 00:00:00 2001 From: guanbinrui <52657989+guanbinrui@users.noreply.github.com> Date: Mon, 13 May 2024 23:06:08 +0800 Subject: [PATCH 05/71] refactor: export simple hash helpers (#11615) --- packages/web3-providers/src/entry-helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3-providers/src/entry-helpers.ts b/packages/web3-providers/src/entry-helpers.ts index 524e03a2b791..a7f69edac60c 100644 --- a/packages/web3-providers/src/entry-helpers.ts +++ b/packages/web3-providers/src/entry-helpers.ts @@ -20,3 +20,4 @@ export * from './helpers/createWeb3ProviderFromURL.js' export * as trending from './Trending/helpers.js' export * as chainbase from './Chainbase/helpers.js' +export * as SimpleHash from './SimpleHash/helpers.js' From 374fbef5b367cd4dea7220b7d784940163fa2adc Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Mon, 13 May 2024 23:07:42 +0800 Subject: [PATCH 06/71] refactor: revert resolveChainId --- packages/web3-providers/src/SimpleHash/index.ts | 1 - packages/web3-providers/src/entry.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web3-providers/src/SimpleHash/index.ts b/packages/web3-providers/src/SimpleHash/index.ts index c0996fbaceaa..f52e7572c7ee 100644 --- a/packages/web3-providers/src/SimpleHash/index.ts +++ b/packages/web3-providers/src/SimpleHash/index.ts @@ -1,4 +1,3 @@ export * from './apis/EVM.js' export * from './apis/Solana.js' export * from './constants.js' -export * from './helpers.js' diff --git a/packages/web3-providers/src/entry.ts b/packages/web3-providers/src/entry.ts index d34e02da8ead..7d32de69813d 100644 --- a/packages/web3-providers/src/entry.ts +++ b/packages/web3-providers/src/entry.ts @@ -20,7 +20,7 @@ export { Multicall } from './Multicall/index.js' export { Lens } from './Lens/index.js' export { RedPacket } from './RedPacket/index.js' export { TheGraphRedPacket } from './TheGraph/index.js' -export { SimpleHashEVM, SimpleHashSolana, SPAM_SCORE, resolveChainId } from './SimpleHash/index.js' +export { SimpleHashEVM, SimpleHashSolana, SPAM_SCORE } from './SimpleHash/index.js' export { SnapshotSearch } from './Snapshot/index.js' export { Snapshot } from './Snapshot/index.js' export { FriendTech } from './FriendTech/index.js' From b1753de695ca8b0d26cfbbd1f67d1283fc4be963 Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Thu, 23 May 2024 17:37:47 +0800 Subject: [PATCH 07/71] feat: add params contract address to `getAssets` api in `SimpleHashAPI_EVM` (#11643) --- packages/web3-providers/src/SimpleHash/apis/EVM.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/web3-providers/src/SimpleHash/apis/EVM.ts b/packages/web3-providers/src/SimpleHash/apis/EVM.ts index bb6ce9b03f8f..d046cd80b684 100644 --- a/packages/web3-providers/src/SimpleHash/apis/EVM.ts +++ b/packages/web3-providers/src/SimpleHash/apis/EVM.ts @@ -161,7 +161,16 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider = {}) { + async getAssets( + account: string, + { + chainId = ChainId.Mainnet, + indicator, + contractAddress = '', + }: BaseHubOptions & { + contractAddress?: string + } = {}, + ) { const chain = resolveChain(NetworkPluginID.PLUGIN_EVM, chainId) if (!account || !isValidChainId(chainId) || !chain) { return createPageable(EMPTY_LIST, createIndicator(indicator)) @@ -169,7 +178,7 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider Date: Thu, 23 May 2024 23:43:11 +0800 Subject: [PATCH 08/71] fix: simple hash (#11646) --- packages/web3-providers/src/entry-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-providers/src/entry-helpers.ts b/packages/web3-providers/src/entry-helpers.ts index a7f69edac60c..c8a16e0350e8 100644 --- a/packages/web3-providers/src/entry-helpers.ts +++ b/packages/web3-providers/src/entry-helpers.ts @@ -20,4 +20,4 @@ export * from './helpers/createWeb3ProviderFromURL.js' export * as trending from './Trending/helpers.js' export * as chainbase from './Chainbase/helpers.js' -export * as SimpleHash from './SimpleHash/helpers.js' +export * as simplehash from './SimpleHash/helpers.js' From eec90635dce0d38a7ec210e3ab249089356b45e7 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Fri, 24 May 2024 00:07:40 +0800 Subject: [PATCH 09/71] fix: entry helpers --- packages/web3-providers/src/entry-helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web3-providers/src/entry-helpers.ts b/packages/web3-providers/src/entry-helpers.ts index c8a16e0350e8..524e03a2b791 100644 --- a/packages/web3-providers/src/entry-helpers.ts +++ b/packages/web3-providers/src/entry-helpers.ts @@ -20,4 +20,3 @@ export * from './helpers/createWeb3ProviderFromURL.js' export * as trending from './Trending/helpers.js' export * as chainbase from './Chainbase/helpers.js' -export * as simplehash from './SimpleHash/helpers.js' From 3468d4e3053438ba11c77c5025098d3510957755 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Fri, 24 May 2024 00:36:04 +0800 Subject: [PATCH 10/71] fix: navigator is undefined --- packages/shared-base/src/Sniffings/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared-base/src/Sniffings/index.ts b/packages/shared-base/src/Sniffings/index.ts index a79f0eeb391a..a3ddc27f157f 100644 --- a/packages/shared-base/src/Sniffings/index.ts +++ b/packages/shared-base/src/Sniffings/index.ts @@ -3,8 +3,8 @@ enum SiteHost { Facebook = 'facebook.com', } -const navigator_ = process.env.NODE_ENV === 'test' ? null : navigator -const location_ = process.env.NODE_ENV === 'test' ? null : location +const navigator_ = typeof navigator === 'undefined' ? null : navigator +const location_ = typeof location === 'undefined' ? null : location const isChromium = navigator_?.userAgent.includes('Chrome') || navigator_?.userAgent.includes('Chromium') From 17bf9022adefb0aa6f0550a4fe61c63006588a1a Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Mon, 27 May 2024 21:26:20 +0800 Subject: [PATCH 11/71] fix: fix function `createNonFungibleAsset` hasn't owner (#11650) --- packages/web3-providers/src/SimpleHash/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-providers/src/SimpleHash/helpers.ts b/packages/web3-providers/src/SimpleHash/helpers.ts index 7f25702c322f..dd61797b24bf 100644 --- a/packages/web3-providers/src/SimpleHash/helpers.ts +++ b/packages/web3-providers/src/SimpleHash/helpers.ts @@ -56,7 +56,7 @@ export function createNonFungibleAsset(asset: SimpleHash.Asset): NonFungibleAsse address: asset.contract.deployed_by, }, owner: { - address: asset.owners?.[0].owner_address, + address: asset.owners?.[0]?.owner_address, }, priceInToken: asset.last_sale ? From 342617f99a0b96cf76dbc3b20a4f370ce1f27fe2 Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Tue, 28 May 2024 17:22:04 +0800 Subject: [PATCH 12/71] feat: add chain configs (#11651) * feat: add chain configs * feat: add chain icons --- packages/web3-shared/evm/src/assets/linea.svg | 10 ++++++ .../web3-shared/evm/src/assets/zksync-era.svg | 7 ++++ packages/web3-shared/evm/src/assets/zora.png | Bin 0 -> 3898 bytes .../evm/src/constants/descriptors.ts | 33 ++++++++++++++++++ packages/web3-shared/evm/src/types/index.ts | 9 +++++ 5 files changed, 59 insertions(+) create mode 100644 packages/web3-shared/evm/src/assets/linea.svg create mode 100644 packages/web3-shared/evm/src/assets/zksync-era.svg create mode 100644 packages/web3-shared/evm/src/assets/zora.png diff --git a/packages/web3-shared/evm/src/assets/linea.svg b/packages/web3-shared/evm/src/assets/linea.svg new file mode 100644 index 000000000000..581c9687d793 --- /dev/null +++ b/packages/web3-shared/evm/src/assets/linea.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/web3-shared/evm/src/assets/zksync-era.svg b/packages/web3-shared/evm/src/assets/zksync-era.svg new file mode 100644 index 000000000000..da6c2eb25faa --- /dev/null +++ b/packages/web3-shared/evm/src/assets/zksync-era.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/web3-shared/evm/src/assets/zora.png b/packages/web3-shared/evm/src/assets/zora.png new file mode 100644 index 0000000000000000000000000000000000000000..0c4b57376fee574162e3a88bfe7189b21bf29ecc GIT binary patch literal 3898 zcmV-A55@3_P)HD$W-R|zp+}F?V`Tc%(cPO^KGt-7u=QO9kJ2Q9g zy}!?#bAIQXdl%?RSGv-bu5_g9p>c~sdx-;S{3OtdG%Rz(Kw zwv2zLu67(}5Yt%*=6dQf_2J1fxdu!(;YBT=ZdB7pHmd0WiWf1HdXJjEBG=ZgJ8Nk> z2JytRIXn&%U#7DRQWv%xpV|Us;Vh%h#(#$lqD@FG1F|OjGCF{hBq=uU8O7SIJMr%D z5Vq|b#Ym$8lu8K05~{w3f&P9R*Vl_vd#gCDe+G{0se%(k2eKLl(r_zZY6s9g9KMY< zWW%PtSh0Q^{=9l4hN2OKJqb!x7v*XhVY!TQ*^FT+MCkblT^9kr=kqFig8*mG>c>3l z34JrrrYt6)TU+@;OMsMF8GefvWoV?1pFZ*$R=l}`R#GRJh#Dj8%}}XUsTGte6_hJw z8c`Wl&qI~ptMKYVj-E0Xn##dk2Pf0AJ~QVOR7)Y+5V4}=MR#r$&_=ZAS`Yqi6&`zT zE55Wk8O;GNkJJEG2Jw1d|L50>cgI3eWs}ew>CBpILLit|C?TFiTZby!b zb5A}F=N~%&-}hU&q(K6jQ*`ITN4C~8yXgMA7-N2L?=yJ*jd7qnfPmKG1=KKwuhliK zByopYq6(auoO~Zah!bfmq6U1=QO1G

tJTD#NSqZNuQu2tGUSG`e8nsmzwS5Yod% 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 = CHAINS.map((x) => { diff --git a/packages/web3-shared/evm/src/types/index.ts b/packages/web3-shared/evm/src/types/index.ts index 327ee06c1b16..a17b0fcf11a5 100644 --- a/packages/web3-shared/evm/src/types/index.ts +++ b/packages/web3-shared/evm/src/types/index.ts @@ -148,6 +148,12 @@ export enum ChainId { // For any chains not supported yet. Invalid = 0, + + Zora = 7777777, + + ZkSyncEra = 324, + + Linea = 59144, } export enum AddressType { @@ -302,6 +308,9 @@ export enum NetworkType { Moonbeam = 'Moonbeam', XLayer = 'XLayer', CustomNetwork = 'CustomNetwork', + Zora = 'Zora', + ZkSyncEra = 'ZkSyncEra', + Linea = 'Linea', } export enum ProviderType { From fba63a83760676d10ff3b967ffa52ba84733f3bf Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Tue, 28 May 2024 17:58:02 +0800 Subject: [PATCH 13/71] feat: add new chain to resolveChainId (#11652) --- packages/web3-providers/src/SimpleHash/helpers.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/web3-providers/src/SimpleHash/helpers.ts b/packages/web3-providers/src/SimpleHash/helpers.ts index dd61797b24bf..667a893ec78a 100644 --- a/packages/web3-providers/src/SimpleHash/helpers.ts +++ b/packages/web3-providers/src/SimpleHash/helpers.ts @@ -165,6 +165,12 @@ export const resolveChainId: (chainId: string) => ChainId | undefined = memoize( return ChainId.Scroll case 'celo': return ChainId.Celo + case 'zora': + return ChainId.Zora + case 'zksync-era': + return ChainId.ZkSyncEra + case 'linea': + return ChainId.Linea default: return undefined } @@ -182,6 +188,9 @@ const ChainNameMap: Record> = { [ChainId.Base]: 'base', [ChainId.Scroll]: 'scroll', [ChainId.Celo]: 'celo', + [ChainId.Zora]: 'zora', + [ChainId.ZkSyncEra]: 'zksync-era', + [ChainId.Linea]: 'linea', }, [NetworkPluginID.PLUGIN_SOLANA]: { [SolanaChainId.Mainnet]: 'solana', From 85e2b24b107cb3e31999f288aa137fea5520eac9 Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Wed, 29 May 2024 11:17:45 +0800 Subject: [PATCH 14/71] feat: add chain configs (#11654) --- .../web3-shared/evm/src/constants/chains.json | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/web3-shared/evm/src/constants/chains.json b/packages/web3-shared/evm/src/constants/chains.json index 874b180c8a10..ce09e235872c 100644 --- a/packages/web3-shared/evm/src/constants/chains.json +++ b/packages/web3-shared/evm/src/constants/chains.json @@ -1304,5 +1304,77 @@ "url": "https://explorer.emerald.oasis.dev/" } ] + }, + { + "chainId": 7777777, + "name": "Zora", + "type": "Zora", + "network": "mainnet", + "features": [], + "nativeCurrency": { + "chainId": 1, + "name": "Ether", + "symbol": "ETH", + "decimals": 18, + "logoURL": "https://imagedelivery.net/PCnTHRkdRhGodr0AWBAvMA/Assets/blockchains/ethereum/info/logo.png/quality=85" + }, + "defaultGasLimit": "90000", + "minGasLimit": "21000", + "infoURL": "https://explorer.zora.energy", + "shortName": "emerald", + "explorers": [ + { + "name": "Zora", + "url": "https://explorer.zora.energy" + } + ] + }, + { + "chainId": 324, + "name": "ZkSyncEra", + "type": "ZkSyncEra", + "network": "mainnet", + "features": [], + "nativeCurrency": { + "chainId": 1, + "name": "Ether", + "symbol": "ETH", + "decimals": 18, + "logoURL": "https://imagedelivery.net/PCnTHRkdRhGodr0AWBAvMA/Assets/blockchains/ethereum/info/logo.png/quality=85" + }, + "defaultGasLimit": "90000", + "minGasLimit": "21000", + "infoURL": "https://explorer.zksync.io", + "shortName": "emerald", + "explorers": [ + { + "name": "ZkSyncEra", + "url": "https://explorer.zksync.io" + } + ] + }, + { + "chainId": 59144, + "name": "Linea", + "type": "Linea", + "network": "mainnet", + "features": [], + "nativeCurrency": { + "chainId": 1, + "name": "Ether", + "symbol": "ETH", + "decimals": 18, + "logoURL": "https://imagedelivery.net/PCnTHRkdRhGodr0AWBAvMA/Assets/blockchains/ethereum/info/logo.png/quality=85" + }, + "defaultGasLimit": "90000", + "minGasLimit": "21000", + "infoURL": "https://linea.build", + "shortName": "emerald", + "explorers": [ + { + "name": "Linea", + "url": "https://explorer.linea.build" + } + ] } ] From deaae6f0d91bb384bd1a53bebff3fbce356ea88f Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:32:01 +0800 Subject: [PATCH 15/71] feat: new zora icon (#11666) --- packages/web3-shared/evm/src/assets/zora.svg | 12 ++++++++++++ .../web3-shared/evm/src/constants/descriptors.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/web3-shared/evm/src/assets/zora.svg diff --git a/packages/web3-shared/evm/src/assets/zora.svg b/packages/web3-shared/evm/src/assets/zora.svg new file mode 100644 index 000000000000..9bd409a7ec2a --- /dev/null +++ b/packages/web3-shared/evm/src/assets/zora.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/web3-shared/evm/src/constants/descriptors.ts b/packages/web3-shared/evm/src/constants/descriptors.ts index f53dfc579b36..3239afed9aa6 100644 --- a/packages/web3-shared/evm/src/constants/descriptors.ts +++ b/packages/web3-shared/evm/src/constants/descriptors.ts @@ -344,7 +344,7 @@ export const NETWORK_DESCRIPTORS: ReadonlyArray Date: Thu, 13 Jun 2024 19:16:41 +0800 Subject: [PATCH 16/71] feat: add poap event api (#11671) --- packages/web3-providers/src/SimpleHash/apis/EVM.ts | 14 ++++++++++++++ packages/web3-providers/src/SimpleHash/helpers.ts | 1 + packages/web3-providers/src/types/SimpleHash.ts | 1 + packages/web3-shared/base/src/specs/index.ts | 2 ++ 4 files changed, 18 insertions(+) diff --git a/packages/web3-providers/src/SimpleHash/apis/EVM.ts b/packages/web3-providers/src/SimpleHash/apis/EVM.ts index d046cd80b684..459b4e5be703 100644 --- a/packages/web3-providers/src/SimpleHash/apis/EVM.ts +++ b/packages/web3-providers/src/SimpleHash/apis/EVM.ts @@ -113,6 +113,20 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider, 'chainId'> = {}) { + const path = urlcat('/api/v0/nfts/poap_event/:event_id', { + cursor: indicator?.id || undefined, + limit: size, + event_id: eventId, + }) + const response = await fetchFromSimpleHash<{ next_cursor: string; nfts: SimpleHash.Asset[] }>(path) + return createPageable( + response.nfts, + indicator, + response.next_cursor ? createNextIndicator(indicator, response.next_cursor) : undefined, + ) + } + async getTopCollectorsByContract( address: string, { chainId = ChainId.Mainnet, indicator, size = 20 }: BaseHubOptions = {}, diff --git a/packages/web3-providers/src/SimpleHash/helpers.ts b/packages/web3-providers/src/SimpleHash/helpers.ts index 667a893ec78a..75c6e7956847 100644 --- a/packages/web3-providers/src/SimpleHash/helpers.ts +++ b/packages/web3-providers/src/SimpleHash/helpers.ts @@ -89,6 +89,7 @@ export function createNonFungibleAsset(asset: SimpleHash.Asset): NonFungibleAsse ), blurhash: asset.previews.blurhash, mediaURL: asset.image_url || asset.previews.image_large_url, + eventId: asset.extra_metadata?.event_id, }, contract: { chainId, diff --git a/packages/web3-providers/src/types/SimpleHash.ts b/packages/web3-providers/src/types/SimpleHash.ts index 9ee941309f78..7632a8784d48 100644 --- a/packages/web3-providers/src/types/SimpleHash.ts +++ b/packages/web3-providers/src/types/SimpleHash.ts @@ -101,6 +101,7 @@ export namespace SimpleHash { value: string display_type: string | null }> + event_id?: number } } diff --git a/packages/web3-shared/base/src/specs/index.ts b/packages/web3-shared/base/src/specs/index.ts index 9f521b69e1a8..52c885c6dafc 100644 --- a/packages/web3-shared/base/src/specs/index.ts +++ b/packages/web3-shared/base/src/specs/index.ts @@ -367,6 +367,8 @@ export interface NonFungibleTokenMetadata { projectURL?: string /** source type */ source?: SourceType + /** Poap Event Id */ + eventId?: number } export interface SocialLinks { From d7e37a5ff00b240bed73026471586484929e3d00 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Thu, 13 Jun 2024 19:47:48 +0800 Subject: [PATCH 17/71] fix: add collection id (#11672) --- packages/web3-providers/src/SimpleHash/helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3-providers/src/SimpleHash/helpers.ts b/packages/web3-providers/src/SimpleHash/helpers.ts index 75c6e7956847..4d8d52185d5e 100644 --- a/packages/web3-providers/src/SimpleHash/helpers.ts +++ b/packages/web3-providers/src/SimpleHash/helpers.ts @@ -99,6 +99,7 @@ export function createNonFungibleAsset(asset: SimpleHash.Asset): NonFungibleAsse symbol: asset.contract.symbol, }, collection: { + id: asset.collection.collection_id, chainId, name: asset.collection.name || '', slug: asset.contract.name, From 34014b644a5e249e74c974351b52a72c4da498ed Mon Sep 17 00:00:00 2001 From: guanbinrui <52657989+guanbinrui@users.noreply.github.com> Date: Tue, 18 Jun 2024 23:41:33 +0800 Subject: [PATCH 18/71] [Release] Hotfix 2.26.1 => 2.26.2 (patch) (#11673) * chore: bump version to 2.26.2 * refactor: config * refactor: default constants is {} --- package.json | 2 +- packages/mask/.webpack/config.ts | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 24ecefb11c95..2fa28df62d36 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "yarn": ">=999.0.0", "npm": ">=999.0.0" }, - "version": "2.25.0", + "version": "2.26.2", "private": true, "license": "AGPL-3.0-or-later", "scripts": { diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index b2dcc25bdac2..c2411661ffcf 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -41,16 +41,6 @@ export async function createConfiguration(_inputFlags: BuildFlags): Promise files.map((x) => join(patchesDir, x))) - - let WEB3_CONSTANTS_RPC = process.env.WEB3_CONSTANTS_RPC || '' - if (WEB3_CONSTANTS_RPC) { - try { - if (typeof JSON.parse(WEB3_CONSTANTS_RPC) === 'object') { - console.error("Environment variable WEB3_CONSTANTS_RPC should be JSON.stringify'ed twice") - WEB3_CONSTANTS_RPC = JSON.stringify(WEB3_CONSTANTS_RPC) - } - } catch (err) {} - } const baseConfig = { name: 'mask', // to set a correct base path for source map @@ -201,11 +191,10 @@ export async function createConfiguration(_inputFlags: BuildFlags): Promise Date: Wed, 19 Jun 2024 21:48:34 +0800 Subject: [PATCH 19/71] feat: get params `count` to poap event api (#11674) * feat: get params `count` to poap event api * feat: add pageable total --- packages/shared-base/src/Pageable/index.ts | 7 ++++++- packages/web3-providers/src/SimpleHash/apis/EVM.ts | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/shared-base/src/Pageable/index.ts b/packages/shared-base/src/Pageable/index.ts index 857b8ea5dffd..bf7ce3cd46f6 100644 --- a/packages/shared-base/src/Pageable/index.ts +++ b/packages/shared-base/src/Pageable/index.ts @@ -11,6 +11,8 @@ export interface Pageable { nextIndicator?: Indicator /** items data */ data: Item[] + /** items total */ + total?: number } export interface PageIndicator { @@ -51,7 +53,8 @@ export function createPageable( data: Item[], indicator: Indicator, nextIndicator?: Indicator, -) { + total?: number, +): Pageable { // with next page if (typeof nextIndicator !== 'undefined') { return { @@ -59,6 +62,7 @@ export function createPageable( data, indicator, nextIndicator, + total, } } // without next page @@ -66,6 +70,7 @@ export function createPageable( __type__: $Pageable, data, indicator, + total, } } diff --git a/packages/web3-providers/src/SimpleHash/apis/EVM.ts b/packages/web3-providers/src/SimpleHash/apis/EVM.ts index 459b4e5be703..c9cc5d18c0a3 100644 --- a/packages/web3-providers/src/SimpleHash/apis/EVM.ts +++ b/packages/web3-providers/src/SimpleHash/apis/EVM.ts @@ -118,12 +118,16 @@ class SimpleHashAPI_EVM implements NonFungibleTokenAPI.Provider(path) + const response = await fetchFromSimpleHash<{ next_cursor: string; nfts: SimpleHash.Asset[]; count?: number }>( + path, + ) return createPageable( response.nfts, indicator, response.next_cursor ? createNextIndicator(indicator, response.next_cursor) : undefined, + response.count, ) } From 8a41343d8e7002559cff2e66ff3659565e9210d0 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Sat, 22 Jun 2024 19:18:15 +0800 Subject: [PATCH 20/71] fix(Redpacket): address could be empty (#11678) --- packages/web3-providers/src/Firefly/RedPacket.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/web3-providers/src/Firefly/RedPacket.ts b/packages/web3-providers/src/Firefly/RedPacket.ts index e7e204290890..608b9df45283 100644 --- a/packages/web3-providers/src/Firefly/RedPacket.ts +++ b/packages/web3-providers/src/Firefly/RedPacket.ts @@ -6,6 +6,7 @@ import { type PageIndicator, getSiteType, EnhanceableSite, + EMPTY_LIST, } from '@masknet/shared-base' import urlcat from 'urlcat' import { fetchJSON } from '../entry-helpers.js' @@ -179,6 +180,9 @@ export class FireflyRedPacket { platform: FireflyRedPacketAPI.SourceType, indicator?: PageIndicator, ): Promise> { + if (!from) { + return createPageable(EMPTY_LIST, createIndicator(indicator)) + } const url = urlcat(FIREFLY_ROOT_URL, '/v1/redpacket/history', { address: from, redpacketType: actionType, From 1f1305806f2bcd9d076db44bd0facf9f53e8c959 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Sat, 22 Jun 2024 23:22:58 +0800 Subject: [PATCH 21/71] fix(RedPacket): disable share for twitter on Firefly (#11680) --- .../RedPacket/src/SiteAdaptor/RedPacket/OperationFooter.tsx | 4 +++- .../plugins/RedPacket/src/SiteAdaptor/RedPacket/index.tsx | 5 +++-- packages/plugins/RedPacket/src/locales/en-US.json | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/OperationFooter.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/OperationFooter.tsx index f1a44e68a450..cfcf2f718980 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/OperationFooter.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/OperationFooter.tsx @@ -30,6 +30,7 @@ interface OperationFooterProps { chainId?: ChainId canClaim: boolean canRefund: boolean + canShare?: boolean /** Is claiming or checking claim status */ isClaiming: boolean isRefunding: boolean @@ -40,6 +41,7 @@ export function OperationFooter({ chainId, canClaim, canRefund, + canShare = true, isClaiming, isRefunding, onShare, @@ -97,7 +99,7 @@ export function OperationFooter({ return ( - {canRefund ? null : ( + {canRefund || !canShare ? null : ( { if (isOnFirefly) { - const context = hasClaimed ? (`${platform}_claimed` as 'lens_claimed' | 'farcaster_claimed') : platform + const context = hasClaimed ? (`${platform}_claimed` as const) : platform return t.share_on_firefly({ context, sender: handle ?? '', @@ -435,6 +435,7 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) { chainId={payloadChainId} canClaim={canClaim} canRefund={canRefund} + canShare={platform !== 'twitter'} isClaiming={isClaiming || checkingClaimStatus} isRefunding={isRefunding} onShare={handleShare} diff --git a/packages/plugins/RedPacket/src/locales/en-US.json b/packages/plugins/RedPacket/src/locales/en-US.json index 69d5222d6ed0..1325b3302ddc 100644 --- a/packages/plugins/RedPacket/src/locales/en-US.json +++ b/packages/plugins/RedPacket/src/locales/en-US.json @@ -168,8 +168,10 @@ "share_on_firefly$default": "🤑 Check this Lucky Drop 🧧💰✨ sent by @{{- sender }} .\n\nGrow your followers and engagement with Lucky Drop on Firefly mobile app or http://firefly.mask.social !\n\nClaim on: {{- link }}", "share_on_firefly$lens": "🤑 Check this Lucky Drop 🧧💰✨ sent by @{{- sender }} .\n\nGrow your followers and engagement with Lucky Drop on Firefly mobile app or http://firefly.mask.social !\n\nClaim on Lens: {{- link }}", "share_on_firefly$farcaster": "🤑 Check this Lucky Drop 🧧💰✨ sent by @{{- sender }} .\n\nGrow your followers and engagement with Lucky Drop on Firefly mobile app or http://firefly.mask.social !\n\nClaim on Farcaster: {{- link }}", + "share_on_firefly$twitter": "🤑 Check this Lucky Drop 🧧💰✨ sent by @{{- sender }} .\n\nGrow your followers and engagement with Lucky Drop on Firefly mobile app or http://firefly.mask.social !\n\nClaim on Twitter: {{- link }}", "share_on_firefly$lens_claimed": "🤑 Just claimed a #LuckyDrop 🧧💰✨ on https://firefly.mask.social from @{{- sender }} ! \n\nClaim on Lens: {{- link }}", "share_on_firefly$farcaster_claimed": "🤑 Just claimed a #LuckyDrop 🧧💰✨ on https://firefly.mask.social from @{{- sender }} ! \n\nClaim on Farcaster: {{- link }}", + "share_on_firefly$twitter_claimed": "🤑 Just claimed a #LuckyDrop 🧧💰✨ on https://firefly.mask.social from @{{- sender }} ! \n\nClaim on Twitter: {{- link }}", "no_claim_data": "No claims yet for this Lucky Drop", "no_claim_history_data": "No Lucky Drops claimed", "no_sent_history_data": "

No Lucky Drops created.
Select 🎁 when you compose a post to start your first drop.
", From eaa720f3db830434ba45fa7350a3c33374aeecaa Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Sun, 30 Jun 2024 14:42:39 +0800 Subject: [PATCH 22/71] fix: coingecko data (#11683) --- packages/icons/brands/FacebookColored.svg | 8 +++--- .../src/SiteAdaptor/trending/PriceChart.tsx | 27 ++++++++++--------- .../shared/src/hooks/useLineChart/index.ts | 7 +++-- .../web3-providers/src/CoinGecko/constants.ts | 2 ++ .../web3-providers/src/CoinGecko/helpers.ts | 2 +- .../web3-providers/src/GoPlusLabs/rules.ts | 2 +- .../web3-providers/src/Trending/helpers.ts | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/icons/brands/FacebookColored.svg b/packages/icons/brands/FacebookColored.svg index 9ea903e4edda..a12132509696 100644 --- a/packages/icons/brands/FacebookColored.svg +++ b/packages/icons/brands/FacebookColored.svg @@ -1,6 +1,4 @@ - - - - , - + + + diff --git a/packages/plugins/Trader/src/SiteAdaptor/trending/PriceChart.tsx b/packages/plugins/Trader/src/SiteAdaptor/trending/PriceChart.tsx index d20f5b774635..1914750de150 100644 --- a/packages/plugins/Trader/src/SiteAdaptor/trending/PriceChart.tsx +++ b/packages/plugins/Trader/src/SiteAdaptor/trending/PriceChart.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react' +import { useRef, useMemo } from 'react' import { Stack, Typography } from '@mui/material' import { LoadingBase, makeStyles } from '@masknet/theme' import { openWindow } from '@masknet/shared-base-ui' @@ -53,28 +53,29 @@ export function PriceChart(props: PriceChartProps) { const { classes } = useStyles(props, { props }) const rootRef = useRef(null) const svgRef = useRef(null) + const { stats, loading, currency, coin, children } = props useDimension(svgRef, DEFAULT_DIMENSION) - usePriceLineChart( - svgRef, - props.stats.map(([date, price]) => ({ + const data = useMemo(() => { + return stats.map(([date, price]) => ({ date: new Date(date), value: price, - })), - DEFAULT_DIMENSION, - 'x-trader-price-line-chart', - { sign: props.currency.name ?? 'USD' }, - ) + })) + }, [stats]) + + usePriceLineChart(svgRef, data, DEFAULT_DIMENSION, 'x-trader-price-line-chart', { + sign: currency.name ?? 'USD', + }) return (
- {props.loading && props.stats.length ? + {loading && stats.length ? : null} - {props.stats.length ? + {stats.length ? { - props.stats.length && openWindow(props.coin?.platform_url) + stats.length && openWindow(coin?.platform_url) }} /> : {t.plugin_trader_no_data()} } - {props.children} + {children}
) diff --git a/packages/shared/src/hooks/useLineChart/index.ts b/packages/shared/src/hooks/useLineChart/index.ts index 2085616715cd..4a836fbc3efe 100644 --- a/packages/shared/src/hooks/useLineChart/index.ts +++ b/packages/shared/src/hooks/useLineChart/index.ts @@ -1,9 +1,8 @@ +import { alpha, useTheme } from '@mui/material' import * as d3 from 'd3' +import { format } from 'date-fns' import { useEffect, type RefObject } from 'react' -import stringify from 'json-stable-stringify' import type { Dimension } from '../useDimension.js' -import { format } from 'date-fns' -import { alpha, useTheme } from '@mui/material' import { fixOverPosition } from './utils.js' // TODO chart morph transform @@ -244,5 +243,5 @@ export function useLineChart( }) d3.select(svgRef.current).on('mouseleave', hide) - }, [svgRef.current, data.length, stringify(dimension), tickFormat, formatTooltip]) + }, [svgRef.current, data, dimension, tickFormat, formatTooltip]) } diff --git a/packages/web3-providers/src/CoinGecko/constants.ts b/packages/web3-providers/src/CoinGecko/constants.ts index e0d1f2a13e52..9df0d352902a 100644 --- a/packages/web3-providers/src/CoinGecko/constants.ts +++ b/packages/web3-providers/src/CoinGecko/constants.ts @@ -24,6 +24,8 @@ export const COINGECKO_CHAIN_ID_LIST = [ ChainId.Cronos, ChainId.BitTorrent, ChainId.Boba, + ChainId.Metis, + ChainId.Scroll, ] if (process.env.NODE_ENV === 'development') { diff --git a/packages/web3-providers/src/CoinGecko/helpers.ts b/packages/web3-providers/src/CoinGecko/helpers.ts index 9f7d0ff27383..a1e0e830ec80 100644 --- a/packages/web3-providers/src/CoinGecko/helpers.ts +++ b/packages/web3-providers/src/CoinGecko/helpers.ts @@ -6,7 +6,7 @@ import { ChainId as ChainIdSolana } from '@masknet/web3-shared-solana' export const resolveCoinGeckoChainId = createLookupTableResolver( { ethereum: ChainId.Mainnet, - 'binance-smart-chain': ChainId.BSCT, + 'binance-smart-chain': ChainId.BSC, 'polygon-pos': ChainId.Matic, solana: ChainIdSolana.Mainnet, astar: ChainId.Astar, diff --git a/packages/web3-providers/src/GoPlusLabs/rules.ts b/packages/web3-providers/src/GoPlusLabs/rules.ts index 1a8609b10b06..4e40282eb43e 100644 --- a/packages/web3-providers/src/GoPlusLabs/rules.ts +++ b/packages/web3-providers/src/GoPlusLabs/rules.ts @@ -194,7 +194,7 @@ export const SecurityMessages: SecurityMessage[] = [ level: SecurityMessageLevel.Safe, condition: (info: SecurityAPI.TokenSecurityType) => info.transfer_pausable === '0', titleKey: 'risk_no_code_transfer_pausable_title', - messageKey: 'risk_no_code_transfer_pausable_title', + messageKey: 'risk_no_code_transfer_pausable_body', shouldHide: isUnset('transfer_pausable'), }, { diff --git a/packages/web3-providers/src/Trending/helpers.ts b/packages/web3-providers/src/Trending/helpers.ts index a887a7c4bdb2..45013f42b59f 100644 --- a/packages/web3-providers/src/Trending/helpers.ts +++ b/packages/web3-providers/src/Trending/helpers.ts @@ -10,7 +10,7 @@ export function isMirroredKeyword(symbol: string) { export function getCommunityLink(links: string[]): TrendingAPI.CommunityUrls { return links.map((x) => { - if (x.includes('twitter')) return { type: 'twitter', link: x } + if (x.includes('twitter') || x.includes('x.com')) return { type: 'twitter', link: x } if (x.includes('t.me')) return { type: 'telegram', link: x } if (x.includes('facebook')) return { type: 'facebook', link: x } if (x.includes('discord')) return { type: 'discord', link: x } From bb3ae3a9c84539876824b0d45b7f9b567349e282 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Wed, 3 Jul 2024 11:56:42 +0800 Subject: [PATCH 23/71] fix: fw-1508 update repost text for lens (#11685) --- packages/plugins/RedPacket/src/SiteAdaptor/Requirements.tsx | 2 +- packages/plugins/RedPacket/src/locales/en-US.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/Requirements.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/Requirements.tsx index b24cd1069743..788c4e411a70 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/Requirements.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/Requirements.tsx @@ -239,7 +239,7 @@ export const Requirements = forwardRef(function Requireme flexGrow: 0, textTransform: 'capitalize', }}> - {condition.key} + {condition.key === 'repost' ? t.repost({ context: platform }) : condition.key} diff --git a/packages/plugins/RedPacket/src/locales/en-US.json b/packages/plugins/RedPacket/src/locales/en-US.json index 1325b3302ddc..2ce15e04039e 100644 --- a/packages/plugins/RedPacket/src/locales/en-US.json +++ b/packages/plugins/RedPacket/src/locales/en-US.json @@ -148,6 +148,9 @@ "nft_holder": "NFT holder", "nft_holder_description": "Users must hold one NFT from the collection you select.", "repost": "Repost", + "repost$lens": "Mirror", + "repost$twitter": "Repost", + "repost$farcaster": "Repost", "like": "Like", "comment": "Comment", "clear_all_requirements": "Clear all requirements", From 1b1ebcd1bcc7e796877b43db0df6a02281208982 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Thu, 4 Jul 2024 17:58:20 +0800 Subject: [PATCH 24/71] fix: disable history button when wallet not connected (#11679) --- .../RedPacket/src/SiteAdaptor/RedPacketDialog.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx index f651a6e56d88..eec2732951fc 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx @@ -251,7 +251,16 @@ export default function RedPacketDialog(props: RedPacketDialogProps) { !showHistory && !showDetails ) { - return setShowHistory((history) => !history)} /> + return ( + { + if (!account) return + setShowHistory((history) => !history) + }} + /> + ) } if (step === CreateRedPacketPageStep.ClaimRequirementsPage) { From adefeff8fab919c20cec2e65d13d6df582f22a66 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Thu, 4 Jul 2024 18:13:36 +0800 Subject: [PATCH 25/71] fix: fw-1575 only list supported chains in token picker (#11687) * fix: fw-1575 only list supported chains in token picker * fix: follow up reviews --- .../plugin-infra/src/manager/site-adaptor.ts | 8 +++-- .../modals/DonateModal/DonateDialog.tsx | 2 +- .../src/SiteAdaptor/RedPacketDialog.tsx | 4 +-- .../src/SiteAdaptor/RedPacketERC20Form.tsx | 5 +-- .../src/components/TokenSection/index.tsx | 2 +- .../SelectFungibleTokenDialog.tsx | 33 ++++++++++++------- .../modals/SelectFungibleTokenModal/index.tsx | 12 ++++--- 7 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/plugin-infra/src/manager/site-adaptor.ts b/packages/plugin-infra/src/manager/site-adaptor.ts index c0289cdc524b..755ffa0ec7d3 100644 --- a/packages/plugin-infra/src/manager/site-adaptor.ts +++ b/packages/plugin-infra/src/manager/site-adaptor.ts @@ -68,9 +68,10 @@ useActivatedPluginsSiteAdaptor.visibility = { // this should never be used for a normal plugin const TRUE = new ValueRef(true) -export function useIsMinimalMode(pluginID: string) { +const FALSE = new ValueRef(false) +export function useIsMinimalMode(pluginID: string | undefined) { assertLocation() - return useValueRef(minimalModeSub[pluginID] || TRUE) + return useValueRef(pluginID ? minimalModeSub[pluginID] || TRUE : FALSE) } export async function checkIsMinimalMode(pluginID: string) { @@ -86,10 +87,11 @@ export async function checkIsMinimalMode(pluginID: string) { * @param visibility Should invisible plugin included? * @returns */ -export function useActivatedPluginSiteAdaptor(pluginID: string, minimalModeEqualsTo: 'any' | boolean) { +export function useActivatedPluginSiteAdaptor(pluginID: string | undefined, minimalModeEqualsTo: 'any' | boolean) { const plugins = useActivatedPluginsSiteAdaptor(minimalModeEqualsTo) const minimalMode = useIsMinimalMode(pluginID) + if (!pluginID) return undefined const result = plugins.find((x) => x.ID === pluginID) if (!result) return undefined if (minimalModeEqualsTo === 'any') return result diff --git a/packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/DonateDialog.tsx b/packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/DonateDialog.tsx index 86d3d53bf1b8..a507a35c9169 100644 --- a/packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/DonateDialog.tsx +++ b/packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/DonateDialog.tsx @@ -99,7 +99,7 @@ export const DonateDialog = memo(({ grant, ...rest }: DonateDialogProps) => { // #region select token dialog const onSelectTokenChipClick = useCallback(async () => { const picked = await SelectFungibleTokenModal.openAndWaitForClose({ - pluginID: NetworkPluginID.PLUGIN_EVM, + networkPluginID: NetworkPluginID.PLUGIN_EVM, chainId, whitelist: TOKEN_LIST, disableNativeToken: false, diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx index eec2732951fc..cbcf4bda89c9 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketDialog.tsx @@ -253,8 +253,8 @@ export default function RedPacketDialog(props: RedPacketDialogProps) { ) { return ( { if (!account) return setShowHistory((history) => !history) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketERC20Form.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketERC20Form.tsx index 3703cd9e393b..e7b800ae712b 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketERC20Form.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/RedPacketERC20Form.tsx @@ -15,7 +15,7 @@ import { } from '@masknet/web3-shared-base' import { type ChainId, type GasConfig, SchemaType, useRedPacketConstants } from '@masknet/web3-shared-evm' import { useTransactionValue } from '@masknet/web3-hooks-evm' -import { NetworkPluginID } from '@masknet/shared-base' +import { NetworkPluginID, PluginID } from '@masknet/shared-base' import { FungibleTokenInput, PluginWalletStatusBar, @@ -125,7 +125,8 @@ export function RedPacketERC20Form(props: RedPacketFormProps) { disableNativeToken: false, selectedTokens: token ? [token.address] : [], chainId, - pluginID: NetworkPluginID.PLUGIN_EVM, + networkPluginID: NetworkPluginID.PLUGIN_EVM, + pluginID: PluginID.RedPacket, }) if (!picked) return if (chainId !== picked.chainId) { diff --git a/packages/plugins/Tips/src/components/TokenSection/index.tsx b/packages/plugins/Tips/src/components/TokenSection/index.tsx index 4fd4277062dd..1c29a8517497 100644 --- a/packages/plugins/Tips/src/components/TokenSection/index.tsx +++ b/packages/plugins/Tips/src/components/TokenSection/index.tsx @@ -26,7 +26,7 @@ export function TokenSection(props: HTMLProps) { const onSelectTokenChipClick = useCallback(async () => { const picked = await SelectFungibleTokenModal.openAndWaitForClose({ - pluginID, + networkPluginID: pluginID, chainId, disableNativeToken: false, selectedTokens: token ? [token.address] : [], diff --git a/packages/shared/src/UI/modals/SelectFungibleTokenModal/SelectFungibleTokenDialog.tsx b/packages/shared/src/UI/modals/SelectFungibleTokenModal/SelectFungibleTokenDialog.tsx index d07314e9c5e5..b04914b9c865 100644 --- a/packages/shared/src/UI/modals/SelectFungibleTokenModal/SelectFungibleTokenDialog.tsx +++ b/packages/shared/src/UI/modals/SelectFungibleTokenModal/SelectFungibleTokenDialog.tsx @@ -1,16 +1,17 @@ -import { useState, useMemo } from 'react' -import { DialogContent, type Theme, useMediaQuery, inputClasses } from '@mui/material' -import { useNetworkContext, useNativeTokenAddress, useNetworks } from '@masknet/web3-hooks-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { EMPTY_LIST, EnhanceableSite, NetworkPluginID, Sniffings } from '@masknet/shared-base' +import { EMPTY_LIST, EnhanceableSite, NetworkPluginID, type PluginID, Sniffings } from '@masknet/shared-base' import { useRowSize } from '@masknet/shared-base-ui' import { makeStyles, MaskColorVar } from '@masknet/theme' +import type { Web3Helper } from '@masknet/web3-helpers' +import { useNativeTokenAddress, useNetworkContext, useNetworks } from '@masknet/web3-hooks-base' import type { FungibleToken } from '@masknet/web3-shared-base' -import { TokenListMode } from '../../components/FungibleTokenList/type.js' +import { ChainId } from '@masknet/web3-shared-evm' +import { DialogContent, inputClasses, type Theme, useMediaQuery } from '@mui/material' +import { useMemo, useState } from 'react' import { useSharedTrans } from '../../../locales/index.js' -import { InjectedDialog, useBaseUIRuntime } from '../../contexts/index.js' +import { TokenListMode } from '../../components/FungibleTokenList/type.js' import { FungibleTokenList, SelectNetworkSidebar } from '../../components/index.js' -import { ChainId } from '@masknet/web3-shared-evm' +import { InjectedDialog, useBaseUIRuntime } from '../../contexts/index.js' +import { useActivatedPluginSiteAdaptor } from '@masknet/plugin-infra/content-script' interface StyleProps { compact: boolean @@ -56,7 +57,8 @@ const useStyles = makeStyles()((theme, { compact, isList }) => ({ interface SelectFungibleTokenDialogProps { open: boolean enableManage?: boolean - pluginID?: T + networkPluginID?: T + pluginID?: PluginID chainId?: Web3Helper.Definition[T]['ChainId'] keyword?: string whitelist?: string[] @@ -73,6 +75,7 @@ interface SelectFungibleTokenDialogProps((theme) => theme.breakpoints.down('md')) const allNetworks = useNetworks(NetworkPluginID.PLUGIN_EVM, true) + const plugin = useActivatedPluginSiteAdaptor(pluginID, 'any') + + const networks = useMemo(() => { + if (!plugin || !networkPluginID) return allNetworks + const supportedChainIds = plugin.enableRequirement.web3?.[networkPluginID]?.supportedChainIds + if (!supportedChainIds) return allNetworks + return allNetworks.filter((x) => supportedChainIds.includes(x.chainId)) + }, [plugin, allNetworks]) const rowSize = useRowSize() @@ -124,7 +135,7 @@ export function SelectFungibleTokenDialog({ hideAllButton chainId={chainId} onChainChange={(chainId) => setChainId(chainId ?? ChainId.Mainnet)} - networks={allNetworks} + networks={networks} pluginID={NetworkPluginID.PLUGIN_EVM} /> >((props, ref) => { const [enableManage, setEnableManage] = useState() - const [pluginID, setPluginID] = useState() + const [networkPluginID, setNetworkPluginID] = useState() + const [pluginID, setPluginID] = useState() const [chainId, setChainId] = useState() const [keyword, setKeyword] = useState() const [whitelist, setWhitelist] = useState() @@ -39,6 +41,7 @@ export const SelectFungibleTokenModal = forwardRef< const [open, dispatch] = useSingletonModal(ref, { onOpen(props) { setEnableManage(props.enableManage) + setNetworkPluginID(props.networkPluginID) setPluginID(props.pluginID) setChainId(props.chainId) setKeyword(props.keyword) @@ -57,6 +60,7 @@ export const SelectFungibleTokenModal = forwardRef< Date: Thu, 4 Jul 2024 18:13:58 +0800 Subject: [PATCH 26/71] fix: fw-1591 parsing redpacket could get null (#11688) --- .../RedPacket/src/SiteAdaptor/hooks/useAvailabilityComputed.ts | 2 +- packages/web3-providers/src/types/Firefly.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useAvailabilityComputed.ts b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useAvailabilityComputed.ts index 50cb5e5c00eb..6e82f3fb88c2 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useAvailabilityComputed.ts +++ b/packages/plugins/RedPacket/src/SiteAdaptor/hooks/useAvailabilityComputed.ts @@ -59,7 +59,7 @@ export function useAvailabilityComputed(account: string, payload: RedPacketJSONP } const isEmpty = availability.balance === '0' const isExpired = availability.expired - const isClaimed = parsed?.redpacket.isClaimed || availability.claimed_amount !== '0' + const isClaimed = parsed?.redpacket?.isClaimed || availability.claimed_amount !== '0' const isRefunded = isEmpty && availability.claimed < availability.total const isCreator = isSameAddress(payload?.sender.address ?? '', account) const isPasswordValid = !!(password && password !== 'PASSWORD INVALID') diff --git a/packages/web3-providers/src/types/Firefly.ts b/packages/web3-providers/src/types/Firefly.ts index ab7692eec992..d1193ce1523d 100644 --- a/packages/web3-providers/src/types/Firefly.ts +++ b/packages/web3-providers/src/types/Firefly.ts @@ -344,7 +344,7 @@ export namespace FireflyRedPacketAPI { isRefunded: boolean claimedNumber: number claimedAmount: string - } + } | null } export type ParseResponse = Response From 74f4f10a692a2c614a9ac4285cdecb25622f7547 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 16 Jul 2024 00:54:54 +0800 Subject: [PATCH 27/71] refactor: remove sol sdk --- packages/web3-providers/package.json | 5 +- .../src/Web3/Solana/apis/HubNonFungibleAPI.ts | 4 +- .../Web3/Solana/apis/NonFungibleTokenAPI.ts | 103 ----- .../src/Web3/Solana/providers/Sollet.ts | 49 --- .../src/Web3/Solana/providers/index.ts | 2 - packages/web3-shared/solana/package.json | 2 +- .../solana/src/constants/descriptors.ts | 17 +- packages/web3-shared/solana/src/types.ts | 1 - pnpm-lock.yaml | 382 +++--------------- 9 files changed, 51 insertions(+), 514 deletions(-) delete mode 100644 packages/web3-providers/src/Web3/Solana/apis/NonFungibleTokenAPI.ts delete mode 100644 packages/web3-providers/src/Web3/Solana/providers/Sollet.ts diff --git a/packages/web3-providers/package.json b/packages/web3-providers/package.json index 69752bcb6f81..8319cea87948 100644 --- a/packages/web3-providers/package.json +++ b/packages/web3-providers/package.json @@ -32,7 +32,6 @@ } }, "dependencies": { - "@bonfida/spl-name-service": "^0.1.50", "@dimensiondev/mask-wallet-core": "0.1.0-20211013082857-eb62e5f", "@dimensiondev/metamask-extension-provider": "^3.0.6-20221105074301-8806fca", "@masknet/flags": "workspace:^", @@ -49,13 +48,11 @@ "@masknet/web3-shared-solana": "workspace:^", "@masknet/web3-telemetry": "workspace:^", "@metamask/eth-sig-util": "^5.0.2", - "@metaplex-foundation/mpl-token-metadata": "^1.1.0", - "@metaplex/js": "^4.11.7", "@project-serum/sol-wallet-adapter": "^0.2.6", "@servie/events": "^3.0.0", "@solana/buffer-layout": "^4.0.1", "@solana/spl-token": "^0.1.8", - "@solana/web3.js": "^1.75.0", + "@solana/web3.js": "^1.91.2", "@walletconnect/sign-client": "^2.10.5", "@walletconnect/utils": "^2.10.5", "bignumber.js": "9.1.2", diff --git a/packages/web3-providers/src/Web3/Solana/apis/HubNonFungibleAPI.ts b/packages/web3-providers/src/Web3/Solana/apis/HubNonFungibleAPI.ts index d52f186c20eb..a4f49fc2d2d2 100644 --- a/packages/web3-providers/src/Web3/Solana/apis/HubNonFungibleAPI.ts +++ b/packages/web3-providers/src/Web3/Solana/apis/HubNonFungibleAPI.ts @@ -4,7 +4,6 @@ import { BaseHubNonFungible } from '../../Base/apis/HubNonFungible.js' import type { BaseHubOptions } from '../../Base/apis/HubOptions.js' import { SolanaHubOptionsAPI } from './HubOptionsAPI.js' import * as MagicEden from /* webpackDefer: true */ '../../../MagicEden/index.js' -import { SolanaNonFungible } from './NonFungibleTokenAPI.js' import { NFTScanNonFungibleTokenSolana } from '../../../NFTScan/index.js' import { SimpleHashSolana } from '../../../SimpleHash/index.js' import type { NonFungibleTokenAPI } from '../../../entry-types.js' @@ -16,11 +15,10 @@ export class SolanaHubNonFungibleAPI extends BaseHubNonFungible>( { [SourceType.MagicEden]: MagicEden.MagicEden, - [SourceType.Solana]: SolanaNonFungible, [SourceType.NFTScan]: NFTScanNonFungibleTokenSolana, [SourceType.SimpleHash]: SimpleHashSolana, }, - [SimpleHashSolana, NFTScanNonFungibleTokenSolana, MagicEden.MagicEden, SolanaNonFungible], + [SimpleHashSolana, NFTScanNonFungibleTokenSolana, MagicEden.MagicEden], initial, ) } diff --git a/packages/web3-providers/src/Web3/Solana/apis/NonFungibleTokenAPI.ts b/packages/web3-providers/src/Web3/Solana/apis/NonFungibleTokenAPI.ts deleted file mode 100644 index 31a9215aaf10..000000000000 --- a/packages/web3-providers/src/Web3/Solana/apis/NonFungibleTokenAPI.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Connection } from '@metaplex/js' -import { Metadata } from '@metaplex-foundation/mpl-token-metadata' -import { EMPTY_LIST, createIndicator, createPageable, type Pageable } from '@masknet/shared-base' -import { type NonFungibleAsset, TokenType } from '@masknet/web3-shared-base' -import { ChainId, SchemaType } from '@masknet/web3-shared-solana' -import type { GetProgramAccountsResponse, SolanaHubOptions } from '../types/index.js' -import { requestRPC } from '../helpers/index.js' -import { fetchJSON } from '../../../helpers/fetchJSON.js' -import { getAssetFullName } from '../../../helpers/getAssetFullName.js' -import type { NonFungibleTokenAPI } from '../../../entry-types.js' - -interface ExternalMetadata { - name: string - symbol: string - description: string - image?: string - animation?: string - properties: { - files: Array<{ - uri: string - type: LiteralUnion<'image/jpeg', 'image/png'> - }> - category: string - creators: Array<{ - address: string - share: 100 - }> - } -} - -async function getNonFungibleAssets( - chainId: ChainId, - account: string, -): Promise>> { - const data = await requestRPC(chainId, { - method: 'getProgramAccounts', - params: [ - 'https://api.raydium.io/v2/sdk/token/raydium.mainnet.json', - { - encoding: 'jsonParsed', - filters: [ - { - dataSize: 165, - }, - { - memcmp: { - offset: 32, - bytes: account, - }, - }, - ], - }, - ], - }) - if (!data.result?.length) return EMPTY_LIST - const connection = new Connection('mainnet-beta') - const nftTokens = data.result.filter((x) => x.account.data.parsed.info.tokenAmount.decimals === 0) - const promises = nftTokens.map(async (x): Promise | null> => { - const pda = await Metadata.getPDA(x.account.data.parsed.info.mint) - const metadata = await Metadata.load(connection, pda) - if (!metadata) return null - const externalMeta = await fetchJSON(metadata.data.data.uri).catch(() => null) - const pubkey = pda.toBase58() - return { - id: pubkey, - tokenId: pubkey, - chainId, - type: TokenType.NonFungible, - schema: SchemaType.NonFungible, - address: '', - contract: { - chainId, - name: metadata.data.data.name, - symbol: metadata.data.data.symbol, - address: pubkey, - schema: SchemaType.NonFungible, - }, - metadata: { - chainId, - name: getAssetFullName(pubkey, metadata.data.data.name, metadata.data.data.name), - symbol: metadata.data.data.symbol, - description: externalMeta?.description, - mediaURL: externalMeta?.animation ?? externalMeta?.image ?? '', - mediaType: externalMeta?.properties.category || 'Unknown', - }, - } - }) - - const allSettled = await Promise.allSettled(promises) - return allSettled.flatMap((x) => (x.status === 'fulfilled' ? x.value ?? [] : [])) -} - -class SolanaNonFungibleTokenAPI implements NonFungibleTokenAPI.Provider { - async getAssets( - address: string, - options?: SolanaHubOptions, - ): Promise>> { - const tokens = await getNonFungibleAssets(options?.chainId ?? ChainId.Mainnet, address) - - return createPageable(tokens, createIndicator(options?.indicator)) - } -} -export const SolanaNonFungible = new SolanaNonFungibleTokenAPI() diff --git a/packages/web3-providers/src/Web3/Solana/providers/Sollet.ts b/packages/web3-providers/src/Web3/Solana/providers/Sollet.ts deleted file mode 100644 index eb0c53a32abe..000000000000 --- a/packages/web3-providers/src/Web3/Solana/providers/Sollet.ts +++ /dev/null @@ -1,49 +0,0 @@ -import base58 from 'bs58' -import type { Transaction } from '@solana/web3.js' -import * as Wallet from /* webpackDefer: true */ '@project-serum/sol-wallet-adapter' -import { type ChainId, ProviderType } from '@masknet/web3-shared-solana' -import { BaseSolanaWalletProvider } from './Base.js' - -export class SolanaSolletProvider extends BaseSolanaWalletProvider { - private wallet: Wallet.default | null = null - private providerURL = 'https://www.sollet.io' - private get solanaProvider() { - if (!this.wallet) throw new Error('No sollet connection.') - return this.wallet - } - private set solanaProvider(newWallet: Wallet.default) { - this.wallet = newWallet - } - - override async signMessage(message: string) { - const data = new TextEncoder().encode(message) - const { signature } = await this.solanaProvider.sign(data, 'uft8') - return base58.encode(signature) - } - - override signTransaction(transaction: Transaction) { - return this.solanaProvider.signTransaction(transaction) - } - - override signTransactions(transactions: Transaction[]) { - return this.solanaProvider.signAllTransactions(transactions) - } - - override async connect(chainId: ChainId) { - this.solanaProvider = new Wallet.default(this.providerURL, '') - await this.solanaProvider.connect() - return { - chainId, - account: this.solanaProvider.publicKey?.toBase58() ?? '', - } - } - - override async disconnect() { - this.solanaProvider = new Wallet.default(this.providerURL, '') - await this.solanaProvider.disconnect() - this.emitter.emit('disconnect', ProviderType.Sollet) - - // clean the internal wallet - this.wallet = null - } -} diff --git a/packages/web3-providers/src/Web3/Solana/providers/index.ts b/packages/web3-providers/src/Web3/Solana/providers/index.ts index d970c98db580..41af828b0bd1 100644 --- a/packages/web3-providers/src/Web3/Solana/providers/index.ts +++ b/packages/web3-providers/src/Web3/Solana/providers/index.ts @@ -2,7 +2,6 @@ import { ProviderType, type ChainId, type Transaction } from '@masknet/web3-shar import { SolanaPhantomProvider } from './Phantom.js' import { NoneProvider } from './None.js' import { SolanaSolflareProvider } from './SolflareProvider.js' -import { SolanaSolletProvider } from './Sollet.js' import { SolanaCoin98Provider } from './Coin98.js' import type { WalletAPI } from '../../../entry-types.js' @@ -21,7 +20,6 @@ export function createSolanaWalletProviders(): Record=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - /@babel/runtime@7.24.0: resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: false /@babel/template@7.22.5: resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} @@ -3791,7 +3775,7 @@ packages: /@blocto/flow-sdk@1.2.0: resolution: {integrity: sha512-SWz3Eae3v4QXHIQL6T0m/XfbkstU0dEzQv8bSTHI57Q6NfHMMDZ1Sj/SX4iJqHJ0sQ+1lmlF/3ORtEmAeUSGRA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@onflow/config': 1.1.0 '@onflow/rlp': 1.1.0 '@onflow/transport-http': 1.6.0 @@ -3807,69 +3791,6 @@ packages: - supports-color dev: false - /@bonfida/name-offers@0.0.1(@solana/buffer-layout@4.0.1): - resolution: {integrity: sha512-R0GJ/+R5OXfm4KfiKEjHx88QnQxw+kzDA8O4pHouev1voPJueOzzo5x+YLowMpk3/2maqEwkNPWiWIlSExjdug==} - dependencies: - '@bonfida/spl-name-service': 0.1.50(@solana/buffer-layout@4.0.1)(@solana/spl-token@0.1.8)(@solana/web3.js@1.75.0)(bn.js@5.2.1)(borsh@0.6.0) - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.75.0 - bn.js: 5.2.1 - borsh: 0.6.0 - bs58: 4.0.1 - transitivePeerDependencies: - - '@solana/buffer-layout' - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@bonfida/spl-name-service@0.1.50(@solana/buffer-layout@4.0.1)(@solana/spl-token@0.1.8)(@solana/web3.js@1.75.0)(bn.js@4.12.0)(borsh@0.7.0): - resolution: {integrity: sha512-StwKh4DPuLm/zSpf2Dk6pFr7gYrV7T+XTSafgoGI+2STXrzipPDtUtwXeF56HGKZ/IkUtZh4SRUd42q6U/MyrA==} - peerDependencies: - '@solana/buffer-layout': ^4.0.0 - '@solana/spl-token': 0.2.0 - '@solana/web3.js': ^1.37.1 - bn.js: ^5.1.3 - borsh: ^0.7.0 - dependencies: - '@bonfida/name-offers': 0.0.1(@solana/buffer-layout@4.0.1) - '@ethersproject/sha2': 5.7.0 - '@solana/buffer-layout': 4.0.1 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.75.0 - bn.js: 4.12.0 - borsh: 0.7.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@bonfida/spl-name-service@0.1.50(@solana/buffer-layout@4.0.1)(@solana/spl-token@0.1.8)(@solana/web3.js@1.75.0)(bn.js@5.2.1)(borsh@0.6.0): - resolution: {integrity: sha512-StwKh4DPuLm/zSpf2Dk6pFr7gYrV7T+XTSafgoGI+2STXrzipPDtUtwXeF56HGKZ/IkUtZh4SRUd42q6U/MyrA==} - peerDependencies: - '@solana/buffer-layout': ^4.0.0 - '@solana/spl-token': 0.2.0 - '@solana/web3.js': ^1.37.1 - bn.js: ^5.1.3 - borsh: ^0.7.0 - dependencies: - '@bonfida/name-offers': 0.0.1(@solana/buffer-layout@4.0.1) - '@ethersproject/sha2': 5.7.0 - '@solana/buffer-layout': 4.0.1 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.75.0 - bn.js: 5.2.1 - borsh: 0.6.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - /@ceramicnetwork/3id-did-resolver@1.4.16: resolution: {integrity: sha512-ES/vBp3TxBzKQGc++SymUEThUVjkmxqLKlsGfKfPR7iCt3xK0eDI7/qB1ZOOvDF0AXWRbfnem5INyioVZo2U/g==} dependencies: @@ -4057,7 +3978,7 @@ packages: /@changesets/apply-release-plan@6.1.4: resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/config': 2.3.1 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 2.0.0 @@ -4075,7 +3996,7 @@ packages: /@changesets/assemble-release-plan@5.2.4: resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/errors': 0.1.4 '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 @@ -4159,7 +4080,7 @@ packages: /@changesets/get-release-plan@3.0.17: resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/assemble-release-plan': 5.2.4 '@changesets/config': 2.3.1 '@changesets/pre': 1.0.14 @@ -4175,7 +4096,7 @@ packages: /@changesets/git@2.0.0: resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -4200,7 +4121,7 @@ packages: /@changesets/pre@1.0.14: resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -4210,7 +4131,7 @@ packages: /@changesets/read@0.5.9: resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/git': 2.0.0 '@changesets/logger': 0.0.5 '@changesets/parse': 0.3.16 @@ -4231,7 +4152,7 @@ packages: /@changesets/write@0.2.3: resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/types': 5.2.1 fs-extra: 7.0.1 human-id: 1.0.2 @@ -4975,7 +4896,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.5 - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -5040,7 +4961,7 @@ packages: react: optional: true dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.2 @@ -6915,7 +6836,7 @@ packages: /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@types/node': 20.12.7 find-up: 4.1.0 fs-extra: 8.1.0 @@ -6924,7 +6845,7 @@ packages: /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -7102,118 +7023,6 @@ packages: - supports-color dev: false - /@metaplex-foundation/mpl-auction@0.0.2: - resolution: {integrity: sha512-4UDDi8OiQr+D6KrCNTRrqf/iDD6vi5kzRtMRtuNpywTyhX9hnbr1Zkc6Ncncbh9GZhbhcn+/h5wHgzh+xA6TnQ==} - dependencies: - '@metaplex-foundation/mpl-core': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.78.4 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex-foundation/mpl-core@0.0.2: - resolution: {integrity: sha512-UUJ4BlYiWdDegAWmjsNQiNehwYU3QfSFWs3sv4VX0J6/ZrQ28zqosGhQ+I2ZCTEy216finJ82sZWNjuwSWCYyQ==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - dependencies: - '@solana/web3.js': 1.75.0 - bs58: 4.0.1 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex-foundation/mpl-metaplex@0.0.5: - resolution: {integrity: sha512-VRt3fiO/7/jcHwN+gWvTtpp+7wYhIcEDzMG1lOeV3yYyhz9fAT0E3LqEl2moifNTAopGCE4zYa84JA/OW+1YvA==} - dependencies: - '@metaplex-foundation/mpl-auction': 0.0.2 - '@metaplex-foundation/mpl-core': 0.0.2 - '@metaplex-foundation/mpl-token-metadata': 0.0.2 - '@metaplex-foundation/mpl-token-vault': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.78.4 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex-foundation/mpl-token-metadata@0.0.2: - resolution: {integrity: sha512-yKJPhFlX8MkNbSCi1iwHn4xKmguLK/xFhYa+RuYdL2seuT4CKXHj2CnR2AkcdQj46Za4/nR3jZcRFKq7QlnvBw==} - dependencies: - '@metaplex-foundation/mpl-core': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.78.4 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex-foundation/mpl-token-metadata@1.1.0: - resolution: {integrity: sha512-4tF+hO5H6eYJ49H72nvuID2nrD54X4yCxqKhbWLxqAI7v5vHSCH2QFVUnqbj3+P4ydxrNyof9MQm3qlzY8KU3g==} - dependencies: - '@metaplex-foundation/mpl-core': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.75.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex-foundation/mpl-token-vault@0.0.2: - resolution: {integrity: sha512-JiVcow8OzUGW0KTs/E1QrAdmYGqE9EGKE6cc2gxNNBYqDeVdjYlgEa64IiGvNF9rvbI2g2Z3jw0mYuA9LD9S/A==} - dependencies: - '@metaplex-foundation/mpl-core': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.78.4 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@metaplex/js@4.11.7(@metaplex-foundation/mpl-auction@0.0.2)(@metaplex-foundation/mpl-core@0.0.2)(@metaplex-foundation/mpl-metaplex@0.0.5)(@metaplex-foundation/mpl-token-metadata@1.1.0)(@metaplex-foundation/mpl-token-vault@0.0.2)(@solana/spl-token@0.1.8)(@solana/web3.js@1.75.0): - resolution: {integrity: sha512-/8X04VEHMfWF84H2DZwLY3yg0xq75vgt/VtLuChTm8iUHkj99Whnq0NLTe0OqfhiEV0qFvT5dbLFh7x2CZqrEQ==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - peerDependencies: - '@metaplex-foundation/mpl-auction': ^0.0.2 - '@metaplex-foundation/mpl-core': ^0.0.2 - '@metaplex-foundation/mpl-metaplex': ^0.0.5 - '@metaplex-foundation/mpl-token-metadata': ^0.0.2 - '@metaplex-foundation/mpl-token-vault': ^0.0.2 - '@solana/spl-token': ^0.1.8 - '@solana/web3.js': ^1.30.2 - dependencies: - '@metaplex-foundation/mpl-auction': 0.0.2 - '@metaplex-foundation/mpl-core': 0.0.2 - '@metaplex-foundation/mpl-metaplex': 0.0.5 - '@metaplex-foundation/mpl-token-metadata': 1.1.0 - '@metaplex-foundation/mpl-token-vault': 0.0.2 - '@solana/spl-token': 0.1.8 - '@solana/web3.js': 1.75.0 - '@types/bs58': 4.0.1 - axios: 0.25.0 - bn.js: 5.2.1 - borsh: 0.4.0 - bs58: 4.0.1 - buffer: 6.0.3 - crypto-hash: 1.3.0 - form-data: 4.0.0 - transitivePeerDependencies: - - debug - dev: false - /@motionone/animation@10.17.0: resolution: {integrity: sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg==} dependencies: @@ -7872,10 +7681,6 @@ packages: '@noble/hashes': 1.3.2 dev: false - /@noble/ed25519@1.7.3: - resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==} - dev: false - /@noble/hashes@1.2.0: resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} dev: false @@ -7890,6 +7695,11 @@ packages: engines: {node: '>= 16'} dev: false + /@noble/hashes@1.4.0: + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + dev: false + /@noble/secp256k1@1.7.1: resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} dev: false @@ -7936,7 +7746,7 @@ packages: /@onflow/config@1.1.0: resolution: {integrity: sha512-Knj+HgDJ7MBKzvWMOCE7ot0HDjVWE73Un+ncbQRZLu3kbnU8mxJYLGG7oRO//Lw6PFPdHxBK0iGG3kz4OtgqKg==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@onflow/util-actor': 1.2.0 eslint: 8.57.0 eslint-plugin-jsdoc: 40.3.0(eslint@8.57.0) @@ -7951,14 +7761,14 @@ packages: /@onflow/rlp@1.1.0: resolution: {integrity: sha512-JJj4dZpKaWmCOXnado+D3RU5xYAXui9kHOmCVfdLReYHIsYBgxwHCDO/hDiQjrbmfLpiybbJKTaIldu+xkCU3Q==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 buffer: 6.0.3 dev: false /@onflow/transport-http@1.6.0: resolution: {integrity: sha512-LgCbPod/JSLjPujs+84FHht2sj3iWszpBwwvN63QVKiVORpJ8Vl72jR1543LPBJQtAeboOd90YUwN3Qu6Kw1zA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@onflow/util-address': 1.1.0 '@onflow/util-invariant': 1.1.0 '@onflow/util-logger': 1.2.0 @@ -7973,32 +7783,32 @@ packages: /@onflow/types@1.1.0: resolution: {integrity: sha512-UUd2ZAFqdd8BHW//uTg+YRlh3EIH9Na+UYJ9qXpt6y87qjW0Lf4Zkhn6H5IqvhIJsjK17QYnXLFpoXItD/+ToQ==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /@onflow/util-actor@1.2.0: resolution: {integrity: sha512-voyHXE3qYNC+P75vzv55pGDDwpxxWKWH0aToR/3g9Bq8nKGcGoszxaMJkVy+fKX1akfdT0yKr/GxXRkjCS9SBg==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 queue-microtask: 1.2.3 dev: false /@onflow/util-address@1.1.0: resolution: {integrity: sha512-HhJOIfNaYAoeYuTNUe85jt4fqnJhFmSbYqyufiJBrplwd3eKmNsan0NYvVYO4U1ecu3KyuY4D9R/NtjGpHjMlA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /@onflow/util-invariant@1.1.0: resolution: {integrity: sha512-5uxweKl5tqeqB1QLzs3dWWLtpNo7H4PgcmgnRIcC2oItAkELcnVCybDdlfYjKB4n/dlg3rIym8cJQE2tBeOpZQ==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /@onflow/util-logger@1.2.0: resolution: {integrity: sha512-E/pKOvg6NnXEQ8ibdeA67/TurzwoXP7dKvqWYJxO0DrrhZLBMJbZygkBw3QBKpvlso+hXkVP4HeoltKijfknzA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 '@onflow/config': 1.1.0 transitivePeerDependencies: - supports-color @@ -8007,13 +7817,13 @@ packages: /@onflow/util-template@1.1.0: resolution: {integrity: sha512-gGqJJH5OTPPRmHk6YxMw4t5Ecy7iHnj00ZlCE7lnlOnpGUNdHrNNfoeE//hO8TBGNNVpOkEQERWiLDph+/oIxg==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /@onflow/util-uid@1.1.0: resolution: {integrity: sha512-HmWfKCOXoz1/TIu7fRx6iR7Nfhq6q5Tmn4YWc868SIfFeWv/inpvVJyrKX0nORf87csSnRSqdDzrJGBowgcXVA==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /@open-draft/until@1.0.3: @@ -8256,13 +8066,13 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@project-serum/sol-wallet-adapter@0.2.6(patch_hash=dl5wqnnaxjm7biesxtvdpr7gpe)(@solana/web3.js@1.75.0): + /@project-serum/sol-wallet-adapter@0.2.6(patch_hash=dl5wqnnaxjm7biesxtvdpr7gpe)(@solana/web3.js@1.91.2): resolution: {integrity: sha512-cpIb13aWPW8y4KzkZAPDgw+Kb+DXjCC6rZoH74MGm3I/6e/zKyGnfAuW5olb2zxonFqsYgnv7ev8MQnvSgJ3/g==} engines: {node: '>=10'} peerDependencies: '@solana/web3.js': ^1.5.0 dependencies: - '@solana/web3.js': 1.75.0 + '@solana/web3.js': 1.91.2 bs58: 4.0.1 eventemitter3: 4.0.7 dev: false @@ -8783,7 +8593,7 @@ packages: engines: {node: '>= 10'} dependencies: '@babel/runtime': 7.22.6 - '@solana/web3.js': 1.75.0 + '@solana/web3.js': 1.91.2 bn.js: 5.2.1 buffer: 6.0.3 buffer-layout: 1.2.2 @@ -8791,42 +8601,15 @@ packages: transitivePeerDependencies: - bufferutil - encoding - - supports-color - utf-8-validate dev: false - /@solana/web3.js@1.75.0: - resolution: {integrity: sha512-rHQgdo1EWfb+nPUpHe4O7i8qJPELHKNR5PAZRK+a7XxiykqOfbaAlPt5boDWAGPnYbSv0ziWZv5mq9DlFaQCxg==} - dependencies: - '@babel/runtime': 7.21.0 - '@noble/ed25519': 1.7.3 - '@noble/hashes': 1.2.0 - '@noble/secp256k1': 1.7.1 - '@solana/buffer-layout': 4.0.1 - agentkeepalive: 4.3.0 - bigint-buffer: 1.1.5 - bn.js: 5.2.1 - borsh: 0.7.0 - bs58: 4.0.1 - buffer: 6.0.3 - fast-stable-stringify: 1.0.0 - jayson: 3.6.5 - node-fetch: 2.6.9 - rpc-websockets: 7.5.1 - superstruct: 0.14.2 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - - /@solana/web3.js@1.78.4: - resolution: {integrity: sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==} + /@solana/web3.js@1.91.2: + resolution: {integrity: sha512-WXPl5VXtfNKWM2RkGj7mvX6dKcZURDKe1lWBFAt/RqDBI9Rjr9hr7Y+U+yz2+TyViMmoinfJVlkS4gk2FPDG/g==} dependencies: '@babel/runtime': 7.24.0 '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.4.0 '@solana/buffer-layout': 4.0.1 agentkeepalive: 4.5.0 bigint-buffer: 1.1.5 @@ -9818,10 +9601,6 @@ packages: '@types/lodash': 4.14.197 dev: true - /@types/lodash@4.14.191: - resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} - dev: false - /@types/lodash@4.14.197: resolution: {integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==} dev: true @@ -11669,17 +11448,6 @@ packages: - supports-color dev: false - /agentkeepalive@4.3.0: - resolution: {integrity: sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==} - engines: {node: '>= 8.0.0'} - dependencies: - debug: 4.3.4 - depd: 2.0.0 - humanize-ms: 1.2.1 - transitivePeerDependencies: - - supports-color - dev: false - /agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} @@ -12205,14 +11973,6 @@ packages: - debug dev: false - /axios@0.25.0: - resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} - dependencies: - follow-redirects: 1.15.2 - transitivePeerDependencies: - - debug - dev: false - /babel-jest@28.1.3(@babel/core@7.22.11): resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -12261,7 +12021,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 cosmiconfig: 7.1.0 resolve: 1.22.8 dev: false @@ -12591,14 +12351,6 @@ packages: text-encoding-utf-8: 1.0.2 dev: false - /borsh@0.6.0: - resolution: {integrity: sha512-sl5k89ViqsThXQpYa9XDtz1sBl3l1lI313cFUY1HKr+wvMILnb+58xpkqTNrYbelh99dY7K8usxoCusQmqix9Q==} - dependencies: - bn.js: 5.2.1 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - dev: false - /borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} dependencies: @@ -12820,7 +12572,7 @@ packages: resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} engines: {node: '>=6.14.2'} dependencies: - node-gyp-build: 4.3.0 + node-gyp-build: 4.6.1 /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} @@ -13949,11 +13701,6 @@ packages: randomfill: 1.0.4 dev: false - /crypto-hash@1.3.0: - resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==} - engines: {node: '>=8'} - dev: false - /crypto-js@3.3.0: resolution: {integrity: sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==} dev: false @@ -19011,31 +18758,6 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: false - /jayson@3.6.5: - resolution: {integrity: sha512-wmOjX+eQcnCDyPF4KORomaIj9wj3h0B5VEbeD0+2VHfTfErB+h1zpR7oBkgCZp36AFjp3+a4CLz6U72BYpFHAw==} - engines: {node: '>=8'} - hasBin: true - dependencies: - '@types/connect': 3.4.35 - '@types/express-serve-static-core': 4.17.31 - '@types/lodash': 4.14.191 - '@types/node': 20.12.7 - '@types/ws': 7.4.7 - JSONStream: 1.3.5 - commander: 2.20.3 - delay: 5.0.0 - es6-promisify: 5.0.0 - eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.9) - json-stringify-safe: 5.0.1 - lodash: 4.17.21 - uuid: 3.4.0 - ws: 7.5.9 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /jayson@4.1.0: resolution: {integrity: sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==} engines: {node: '>=8'} @@ -21537,6 +21259,7 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 + dev: true /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -21565,11 +21288,11 @@ packages: /node-gyp-build@4.3.0: resolution: {integrity: sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==} hasBin: true + dev: false /node-gyp-build@4.6.1: resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} hasBin: true - dev: false /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -24005,18 +23728,6 @@ packages: nanoid: 3.3.6 dev: false - /rpc-websockets@7.5.1: - resolution: {integrity: sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==} - dependencies: - '@babel/runtime': 7.23.2 - eventemitter3: 4.0.7 - uuid: 8.3.2 - ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) - optionalDependencies: - bufferutil: 4.0.7 - utf-8-validate: 5.0.10 - dev: false - /rpc-websockets@7.6.0: resolution: {integrity: sha512-Jgcs8q6t8Go98dEulww1x7RysgTkzpCMelVxZW4hvuyFtOGpeUz9prpr2KjUa/usqxgFCd9Tu3+yhHEP9GVmiQ==} dependencies: @@ -24052,7 +23763,7 @@ packages: /rtl-css-js@1.15.0: resolution: {integrity: sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew==} dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.24.0 dev: false /run-async@2.4.1: @@ -26446,7 +26157,7 @@ packages: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} dependencies: - node-gyp-build: 4.3.0 + node-gyp-build: 4.6.1 /utf8@3.0.0: resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} @@ -27144,7 +26855,7 @@ packages: '@ethereumjs/util': 8.1.0 eth-lib: 0.2.8 scrypt-js: 3.0.1 - uuid: 9.0.0 + uuid: 9.0.1 web3-core: 1.10.2(patch_hash=7c6knffilb7dxuf3r2r5ttgbem) web3-core-helpers: 1.10.2 web3-core-method: 1.10.2 @@ -28153,4 +27864,5 @@ packages: dev: true time: + /@solana/web3.js@1.91.2: '2024-03-26T19:54:47.543Z' /@types/web@0.0.143: '2024-04-15T04:20:47.209Z' From 9a814648e6f229c66207c69648c98da0a27d1dc8 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 16 Jul 2024 01:21:02 +0800 Subject: [PATCH 28/71] refactor: remove domain api --- .../src/Web3/Solana/apis/DomainAPI.ts | 48 ------------------- .../src/Web3/Solana/state/IdentityService.ts | 15 ------ 2 files changed, 63 deletions(-) delete mode 100644 packages/web3-providers/src/Web3/Solana/apis/DomainAPI.ts diff --git a/packages/web3-providers/src/Web3/Solana/apis/DomainAPI.ts b/packages/web3-providers/src/Web3/Solana/apis/DomainAPI.ts deleted file mode 100644 index 37d2c72d4e5d..000000000000 --- a/packages/web3-providers/src/Web3/Solana/apis/DomainAPI.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { first } from 'lodash-es' -import * as SolanaWeb3 from /* webpackDefer: true */ '@solana/web3.js' -import { - performReverseLookup, - getHashedName, - getNameAccountKey, - NameRegistryState, - getAllDomains, -} from '@bonfida/spl-name-service' -import { NameServiceID } from '@masknet/shared-base' -import { ChainId, createClient } from '@masknet/web3-shared-solana' -import type { NameServiceAPI } from '../../../entry-types.js' - -class SolanaDomainAPI implements NameServiceAPI.Provider { - private client = createClient(ChainId.Mainnet) - - private SOL_TLD_AUTHORITY = new SolanaWeb3.PublicKey('58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx') - - id = NameServiceID.SOL - - private async getKey(name: string) { - const hashedName = await getHashedName(name) - const domainKey = await getNameAccountKey(hashedName, undefined, this.SOL_TLD_AUTHORITY) - return { domainKey, hashedName } - } - - async lookup(name: string): Promise { - try { - const { domainKey } = await this.getKey(name.replace('.sol', '')) - const registry = await NameRegistryState.retrieve(this.client, domainKey) - const owner = registry.registry.owner.toBase58() - return owner - } catch { - return '' - } - } - async reverse(address: string): Promise { - const domainKey = new SolanaWeb3.PublicKey(address) - const keys = await getAllDomains(this.client, domainKey) - // resolve the first domain - const key = first(keys) - if (!key) return - - const domain = await performReverseLookup(this.client, key) - return `${domain}.sol` - } -} -export const SolanaDomain = new SolanaDomainAPI() diff --git a/packages/web3-providers/src/Web3/Solana/state/IdentityService.ts b/packages/web3-providers/src/Web3/Solana/state/IdentityService.ts index 63c75c9379ee..3ee6957b0bc0 100644 --- a/packages/web3-providers/src/Web3/Solana/state/IdentityService.ts +++ b/packages/web3-providers/src/Web3/Solana/state/IdentityService.ts @@ -2,7 +2,6 @@ import { compact } from 'lodash-es' import { NetworkPluginID, type SocialIdentity, type SocialAddress, SocialAddressType } from '@masknet/shared-base' import { type ChainId, isValidAddress } from '@masknet/web3-shared-solana' import { IdentityServiceState } from '../../Base/state/IdentityService.js' -import { SolanaDomain } from '../apis/DomainAPI.js' const SOL_RE = /\S{1,256}\.sol\b/i @@ -18,17 +17,11 @@ function getSolanaDomain(nickname: string, bio: string) { return matched } -function getSolanaDomainAddress(domain: string) { - if (!domain) return - return SolanaDomain.lookup(domain) -} - export class SolanaIdentityService extends IdentityServiceState { protected override async getFromRemote(identity: SocialIdentity) { const { bio = '', nickname = '' } = identity const address = getSolanaAddress(bio) const domain = getSolanaDomain(nickname, bio) - const domainAddress = domain ? await getSolanaDomainAddress(domain) : undefined return compact>([ address ? @@ -39,14 +32,6 @@ export class SolanaIdentityService extends IdentityServiceState { address, } : undefined, - domainAddress ? - { - pluginID: NetworkPluginID.PLUGIN_SOLANA, - type: SocialAddressType.SOL, - label: domain ?? domainAddress, - address: domainAddress, - } - : undefined, ]) } } From a08bf54626f07635c77dd7527aab115ddad74d28 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 17 Jul 2024 11:26:49 +0800 Subject: [PATCH 29/71] chore: engine --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 2fa28df62d36..6c31f2b37e72 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,10 @@ { "name": "mask-network", "type": "module", - "packageManager": "pnpm@8.7.6", + "packageManager": "pnpm@8.15.8", "engines": { "node": ">=20.11.1", - "pnpm": ">=8.6.0", - "yarn": ">=999.0.0", - "npm": ">=999.0.0" + "pnpm": ">=8.15.8" }, "version": "2.26.2", "private": true, From 8d25af7f8b5da18660d3ac7589cd14ff7369b7cf Mon Sep 17 00:00:00 2001 From: LeifXu <167842553+LeifXu@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:53:49 +0800 Subject: [PATCH 30/71] fix: adjust text (#11700) --- packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx | 1 - packages/plugins/Calendar/src/locales/zh-CN.json | 2 +- pnpm-lock.yaml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx b/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx index 7e8d8d769d32..c9036d2435bb 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx @@ -95,7 +95,6 @@ export function Footer({ provider, disableSetting }: FooterProps) { {t.title()}
- {t.powered_by()} {providerMap[provider]} {disableSetting ? null : ( Date: Tue, 23 Jul 2024 09:14:45 +0800 Subject: [PATCH 31/71] chore: lock file --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a700dc503ea5..c513cc844ca2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27865,4 +27865,4 @@ packages: time: /@solana/web3.js@1.91.2: '2024-03-26T19:54:47.543Z' - /@types/web@0.0.143: '2024-04-15T04:20:47.209Z' \ No newline at end of file + /@types/web@0.0.143: '2024-04-15T04:20:47.209Z' From 0144ce4a88aa52ba93bd3a8f3f833bc2762effe8 Mon Sep 17 00:00:00 2001 From: LeifXu Date: Tue, 23 Jul 2024 13:57:02 +0800 Subject: [PATCH 32/71] fix: remove calendar provider icon --- .../src/SiteAdaptor/CalendarContent.tsx | 5 +- .../src/SiteAdaptor/components/Footer.tsx | 59 +------------------ 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx index 246adc826e7e..fb158173f1fd 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx @@ -40,10 +40,9 @@ const useStyles = makeStyles()((theme) => ({ interface Props { target?: string - disableSetting?: boolean } -export function CalendarContent({ target, disableSetting }: Props) { +export function CalendarContent({ target }: Props) { const t = useCalendarTrans() const { classes } = useStyles() const [pathname, setPathname] = useState(location.pathname) @@ -116,7 +115,7 @@ export function CalendarContent({ target, disableSetting }: Props) { dateString={dateString} /> -
+
) diff --git a/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx b/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx index c9036d2435bb..ffc62bc40f14 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/components/Footer.tsx @@ -1,9 +1,6 @@ import { Icons } from '@masknet/icons' -import { ApplicationSettingTabs, useOpenApplicationSettings } from '@masknet/shared' -import { PluginID } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' -import { IconButton, Typography } from '@mui/material' -import { type ReactNode } from 'react' +import { Typography } from '@mui/material' import { useCalendarTrans } from '../../locales/i18n_generated.js' const useStyles = makeStyles()((theme) => ({ @@ -23,20 +20,6 @@ const useStyles = makeStyles()((theme) => ({ padding: '12px', width: '100%', }, - poweredByWrap: { - display: 'flex', - gap: '4px', - alignItems: 'center', - }, - poweredBy: { - display: 'flex', - color: theme.palette.maskColor.second, - fontSize: '14px', - fontWeight: 700, - lineHeight: '18px', - alignItems: 'center', - whiteSpace: 'nowrap', - }, calender: { display: 'flex', gap: '8px', @@ -58,35 +41,9 @@ const useStyles = makeStyles()((theme) => ({ }, })) -export interface FooterProps { - provider: string - disableSetting?: boolean -} - -export function Footer({ provider, disableSetting }: FooterProps) { +export function Footer() { const { classes } = useStyles() const t = useCalendarTrans() - const providerMap: Record = { - news: ( - <> - CoinCarp - - - ), - event: ( - <> - LINK3 - - - ), - nfts: ( - <> - NFTGO - - - ), - } - const openApplicationBoardDialog = useOpenApplicationSettings() return (
@@ -94,18 +51,6 @@ export function Footer({ provider, disableSetting }: FooterProps) { {t.title()}
-
- {providerMap[provider]} - {disableSetting ? null : ( - - openApplicationBoardDialog(ApplicationSettingTabs.pluginSwitch, PluginID.Calendar) - }> - - - )} -
) From c42a267bbfb125129554927efd22f1519b840615 Mon Sep 17 00:00:00 2001 From: dudu0506 Date: Tue, 23 Jul 2024 17:52:22 +0800 Subject: [PATCH 33/71] feat: traditional chinese translation for calendar (#11702) https://mask.atlassian.net/browse/FW-1860 --- packages/plugins/Calendar/src/locales/zh-TW.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/plugins/Calendar/src/locales/zh-TW.json b/packages/plugins/Calendar/src/locales/zh-TW.json index 0967ef424bce..f778921cc0e9 100644 --- a/packages/plugins/Calendar/src/locales/zh-TW.json +++ b/packages/plugins/Calendar/src/locales/zh-TW.json @@ -1 +1,15 @@ -{} +{ + "title": "日曆", + "description": "與 Twitter 高度關聯的 Web3 新聞和事件。提供 Token、NFT、AMA 和管理活動。", + "empty_status": "最近兩週沒有內容。", + "total": "總額", + "price": "價格", + "date": "日期", + "loading": "加載中", + "powered_by": "技術支援", + "news": "新聞", + "event": "活動", + "nfts": "NFTs", + "content_end": "內容已全部加載", + "expired": "已過期" +} From 32e0f6f95809964217c3c8c8e420bd2bdffb9849 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Wed, 24 Jul 2024 09:41:26 +0800 Subject: [PATCH 34/71] fix: lazy load calendar data --- .../plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx | 9 ++++++--- .../Calendar/src/SiteAdaptor/components/DatePicker.tsx | 6 +++--- packages/plugins/Calendar/src/hooks/useEventList.tsx | 9 ++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx index fb158173f1fd..a75aa2766aeb 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx @@ -50,9 +50,12 @@ export function CalendarContent({ target }: Props) { const [currentTab, onChange, tabs] = useTabs('news', 'event', 'nfts') const [selectedDate, setSelectedDate] = useState(new Date()) const [open, setOpen] = useState(false) - const { data: eventList = EMPTY_OBJECT, isPending: eventLoading } = useEventList(selectedDate) - const { data: newsList = EMPTY_OBJECT, isPending: newsLoading } = useNewsList(selectedDate) - const { data: nftList = EMPTY_OBJECT, isPending: nftLoading } = useNFTList(selectedDate) + const { data: eventList = EMPTY_OBJECT, isPending: eventLoading } = useEventList( + selectedDate, + currentTab === 'event', + ) + const { data: newsList = EMPTY_OBJECT, isPending: newsLoading } = useNewsList(selectedDate, currentTab === 'news') + const { data: nftList = EMPTY_OBJECT, isPending: nftLoading } = useNFTList(selectedDate, currentTab === 'nfts') const list = useMemo(() => { switch (currentTab) { case 'news': diff --git a/packages/plugins/Calendar/src/SiteAdaptor/components/DatePicker.tsx b/packages/plugins/Calendar/src/SiteAdaptor/components/DatePicker.tsx index de5ad07328af..9a84b6a491d3 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/components/DatePicker.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/components/DatePicker.tsx @@ -90,9 +90,9 @@ export function DatePicker({ selectedDate, setSelectedDate, open, setOpen, curre const startingDayOfWeek = monthStart.getDay() const daysInMonth = endOfMonth(currentDate).getDate() const daysInPrevMonth = endOfMonth(addMonths(currentDate, -1)).getDate() - const { data: eventList } = useEventList(monthStart) - const { data: newsList } = useNewsList(monthStart) - const { data: nftList } = useNFTList(monthStart) + const { data: eventList } = useEventList(monthStart, currentTab === 'event') + const { data: newsList } = useNewsList(monthStart, currentTab === 'news') + const { data: nftList } = useNFTList(monthStart, currentTab === 'nfts') const list = useMemo(() => { switch (currentTab) { case 'news': diff --git a/packages/plugins/Calendar/src/hooks/useEventList.tsx b/packages/plugins/Calendar/src/hooks/useEventList.tsx index 1f83cf3f0bc1..5445c3c7fba6 100644 --- a/packages/plugins/Calendar/src/hooks/useEventList.tsx +++ b/packages/plugins/Calendar/src/hooks/useEventList.tsx @@ -5,10 +5,11 @@ import { EMPTY_OBJECT } from '@masknet/shared-base' import type { UseQueryResult } from '@tanstack/react-query' import { addDays } from 'date-fns/esm' -export function useNewsList(date: Date): UseQueryResult { +export function useNewsList(date: Date, enabled = true): UseQueryResult { const startTime = startOfMonth(date).getTime() / 1000 const endTime = Math.floor(addDays(date, 45).getTime() / 1000) return useQuery({ + enabled, queryKey: ['newsList', startTime, endTime], queryFn: async () => Calendar.getNewsList(startTime, endTime), select(data) { @@ -24,10 +25,11 @@ export function useNewsList(date: Date): UseQueryResult { }) } -export function useEventList(date: Date) { +export function useEventList(date: Date, enabled = true) { const startTime = startOfMonth(date).getTime() / 1000 const endTime = Math.floor(addDays(date, 45).getTime() / 1000) return useQuery({ + enabled, queryKey: ['eventList', startTime, endTime], queryFn: async () => Calendar.getEventList(startTime, endTime), select(data) { @@ -43,10 +45,11 @@ export function useEventList(date: Date) { }) } -export function useNFTList(date: Date) { +export function useNFTList(date: Date, enabled = true) { const startTime = startOfMonth(date).getTime() / 1000 const endTime = Math.floor(endOfMonth(date).getTime() / 1000) return useQuery({ + enabled, queryKey: ['nftList', startTime, endTime], queryFn: async () => Calendar.getNFTList(startTime, endTime), select(data) { From 6648572e0314b2c5c81fef3289fe879a86fe7447 Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Fri, 26 Jul 2024 10:45:53 +0800 Subject: [PATCH 35/71] refactor: slim maskbook for firefly (#11694) * refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fixup! refactor: slim maskbook for firefly * fix: mask wallet dependency * fix: remove unused files * chore: remove unused files * fix: remove snapshot * fixup! fix: remove snapshot --- .i18n-codegen.json | 422 - eslint.config.js | 26 - knip.ts | 22 +- package.json | 8 - packages/backup-format/README.md | 11 - packages/backup-format/package.json | 23 - packages/backup-format/src/BackupErrors.ts | 4 - packages/backup-format/src/container/index.ts | 38 - packages/backup-format/src/index.ts | 9 - packages/backup-format/src/normalize/index.ts | 43 - packages/backup-format/src/normalize/type.ts | 102 - .../backup-format/src/utils/backupPreview.ts | 48 - .../backup-format/src/utils/hex2buffer.ts | 40 - packages/backup-format/src/version-0/index.ts | 75 - packages/backup-format/src/version-1/index.ts | 111 - packages/backup-format/src/version-2/index.ts | 393 - packages/backup-format/src/version-3/index.ts | 52 - packages/backup-format/tests/encryption.ts | 31 - packages/backup-format/tsconfig.json | 10 - packages/backup-format/tsconfig.tests.json | 9 - packages/icons/icon-generated-as-jsx.js | 297 - packages/icons/icon-generated-as-url.js | 57 - packages/icons/plugins/Approval.svg | 5 - packages/icons/plugins/ArtBlocks.png | Bin 43263 -> 0 bytes packages/icons/plugins/CrossBridge.png | Bin 3580 -> 0 bytes packages/icons/plugins/CyberConnect.dark.svg | 3 - packages/icons/plugins/CyberConnect.light.svg | 3 - packages/icons/plugins/FileService.svg | 18 - packages/icons/plugins/FindTruman.png | Bin 4340 -> 0 bytes packages/icons/plugins/FriendTech.svg | 5 - packages/icons/plugins/Gitcoin.dark.svg | 7 - packages/icons/plugins/Gitcoin.light.svg | 7 - packages/icons/plugins/GoodGhosting.dark.svg | 3 - packages/icons/plugins/GoodGhosting.light.svg | 3 - packages/icons/plugins/Markets.png | Bin 6335 -> 0 bytes packages/icons/plugins/PoolTogether.png | Bin 41928 -> 0 bytes packages/icons/plugins/Savings.svg | 35 - packages/icons/plugins/Snapshot.svg | 3 - packages/icons/plugins/TipCoin.svg | 8 - packages/icons/plugins/Transak.png | Bin 2565 -> 0 bytes packages/icons/plugins/Unstoppable.svg | 5 - packages/icons/rss3/AchievementBurn.svg | 7 - packages/icons/rss3/AchievementReceive.svg | 7 - packages/icons/rss3/ApprovalApprove.svg | 4 - packages/icons/rss3/CollectibleApprove.svg | 7 - packages/icons/rss3/CollectibleBurn.svg | 7 - packages/icons/rss3/CollectibleIn.svg | 7 - packages/icons/rss3/CollectibleMint.svg | 8 - packages/icons/rss3/CollectibleOut.svg | 7 - packages/icons/rss3/DonationDonate.svg | 4 - packages/icons/rss3/DonationLaunch.svg | 4 - packages/icons/rss3/Follow.svg | 3 - packages/icons/rss3/GovernancePropose.svg | 7 - packages/icons/rss3/GovernanceVote.svg | 4 - packages/icons/rss3/NoteBurn.svg | 7 - packages/icons/rss3/NoteCreate.svg | 7 - packages/icons/rss3/NoteEdit.svg | 7 - packages/icons/rss3/NoteLink.svg | 10 - packages/icons/rss3/NoteMint.svg | 8 - packages/icons/rss3/ProfileBurn.svg | 8 - packages/icons/rss3/ProfileCreate.svg | 8 - packages/icons/rss3/ProfileLink.svg | 10 - packages/icons/rss3/ProfileProxy.svg | 7 - packages/icons/rss3/ProfileUpdate.svg | 7 - packages/icons/rss3/RSS3Link.svg | 3 - packages/icons/rss3/TokenBridge.svg | 8 - packages/icons/rss3/TokenBurn.svg | 8 - packages/icons/rss3/TokenIn.svg | 8 - packages/icons/rss3/TokenLiquidity.svg | 8 - packages/icons/rss3/TokenMint.svg | 8 - packages/icons/rss3/TokenOut.svg | 8 - packages/icons/rss3/TokenStake.svg | 8 - packages/icons/rss3/TokenSwap.svg | 8 - packages/icons/rss3/TokenUnstake.svg | 8 - packages/icons/rss3/Unfollow.svg | 3 - packages/icons/rss3/UnknownBurn.svg | 4 - packages/icons/rss3/UnknownCancel.svg | 4 - packages/icons/rss3/UnknownIn.svg | 4 - packages/icons/rss3/UnknownOut.svg | 4 - packages/mask-sdk/README.md | 15 - packages/mask-sdk/gen-message.mjs | 71 - packages/mask-sdk/global.d.ts | 8 - packages/mask-sdk/main/bridge.ts | 24 - packages/mask-sdk/main/index.ts | 30 - packages/mask-sdk/main/tsconfig.json | 9 - packages/mask-sdk/main/wallet.ts | 61 - packages/mask-sdk/package.json | 29 - packages/mask-sdk/public-api/index.ts | 36 - packages/mask-sdk/public-api/mask-login.ts | 1 - packages/mask-sdk/public-api/mask-persona.ts | 1 - packages/mask-sdk/public-api/mask-wallet.ts | 299 - packages/mask-sdk/public-api/tsconfig.json | 11 - packages/mask-sdk/rollup.config.js | 18 - packages/mask-sdk/server/index.ts | 33 - packages/mask-sdk/server/tsconfig.json | 10 - packages/mask-sdk/shared/channel.ts | 20 - packages/mask-sdk/shared/error-generated.ts | 118 - packages/mask-sdk/shared/error.ts | 24 - packages/mask-sdk/shared/index.ts | 19 - packages/mask-sdk/shared/messages.txt | 27 - packages/mask-sdk/shared/serializer.ts | 69 - packages/mask-sdk/shared/tsconfig.json | 10 - packages/mask-sdk/shared/types.ts | 18 - packages/mask/.webpack/clean-hmr.ts | 5 - packages/mask/.webpack/config.ts | 412 - packages/mask/.webpack/flags.ts | 107 - packages/mask/.webpack/git-info.ts | 26 - .../loaders/fix-regenerator-runtime.js | 10 - .../mask/.webpack/manifest/manifest-mv3.json | 35 - packages/mask/.webpack/manifest/manifest.json | 20 - .../mask/.webpack/package-overrides/null.mjs | 0 .../mask/.webpack/plugins/ProfilingPlugin.ts | 59 - .../.webpack/plugins/TrustedTypesPlugin.ts | 30 - packages/mask/.webpack/plugins/manifest.ts | 118 - packages/mask/.webpack/popups.html | 99 - packages/mask/.webpack/swap.html | 99 - packages/mask/.webpack/template.html | 22 - packages/mask/.webpack/tsconfig.json | 8 - packages/mask/.webpack/utils.ts | 28 - packages/mask/.webpack/webpack.config.js | 2 - packages/mask/.webpack/webpack.config.ts | 5 - .../database/avatar-cache/avatar.ts | 95 - .../database/avatar-cache/cleanup.ts | 11 - .../background/database/avatar-cache/db.ts | 148 - .../database/persona/consistency.ts | 169 - .../mask/background/database/persona/db.ts | 2 - .../background/database/persona/helper.ts | 223 - .../mask/background/database/persona/type.ts | 157 - .../mask/background/database/persona/web.ts | 773 - .../background/database/plugin-db/base.ts | 59 - .../background/database/plugin-db/index.ts | 2 - .../plugin-db/wrap-plugin-database.ts | 123 - .../mask/background/database/post/dbType.ts | 123 - .../mask/background/database/post/helper.ts | 27 - .../mask/background/database/post/index.ts | 2 - .../mask/background/database/post/type.ts | 47 - packages/mask/background/database/post/web.ts | 286 - .../mask/background/database/utils/openDB.ts | 186 - packages/mask/background/env.d.ts | 3 - .../background/initialization/async-setup.ts | 7 - .../mask/background/initialization/entry.ts | 5 - .../mask/background/initialization/fetch.ts | 2 - .../background/initialization/kv-storage.ts | 8 - .../background/initialization/mv2-entry.ts | 1 - .../background/initialization/mv3-entry.ts | 2 - .../initialization/post-async-setup.ts | 6 - .../mask/background/initialization/setup.ts | 9 - .../initialization/storage-setup.ts | 7 - .../mask/background/network/queryPostKey.ts | 352 - .../background/services/__utils__/convert.ts | 70 - .../mask/background/services/backup/create.ts | 31 - .../mask/background/services/backup/index.ts | 3 - .../services/backup/internal_create.ts | 165 - .../services/backup/internal_restore.ts | 202 - .../services/backup/internal_wallet_backup.ts | 136 - .../backup/internal_wallet_restore.ts | 97 - .../background/services/backup/persona.ts | 12 - .../background/services/backup/restore.ts | 39 - .../services/crypto/appendEncryption.ts | 51 - .../background/services/crypto/comment.ts | 43 - .../background/services/crypto/decryption.ts | 256 - .../background/services/crypto/encryption.ts | 148 - .../mask/background/services/crypto/index.ts | 11 - .../background/services/crypto/recipients.ts | 33 - .../services/crypto/steganography.ts | 18 - .../services/helper/i18n-cache-query-list.ts | 41 - .../services/helper/i18n-cache-query.ts | 67 - .../mask/background/services/helper/index.ts | 18 - .../services/helper/popup-opener.ts | 117 - .../services/helper/request-permission.ts | 38 - .../background/services/helper/sandboxed.ts | 13 - .../services/helper/short-link-resolver.ts | 20 - .../mask/background/services/helper/tabs.ts | 8 - .../services/helper/telemetry-id.ts | 28 - .../services/identity/avatar/query.ts | 1 - .../background/services/identity/index.ts | 16 - .../services/identity/persona/avatar.ts | 29 - .../services/identity/persona/create.ts | 58 - .../services/identity/persona/query.ts | 118 - .../services/identity/persona/sign.ts | 58 - .../services/identity/persona/update.ts | 102 - .../services/identity/persona/utils.ts | 90 - .../services/identity/profile/query.ts | 38 - .../services/identity/profile/update.ts | 154 - .../services/identity/relation/create.ts | 15 - .../services/identity/relation/query.ts | 17 - .../services/identity/relation/update.ts | 16 - .../background/services/settings/index.ts | 2 - .../services/settings/kv-storage.ts | 17 - .../settings/old-settings-accessor.ts | 84 - packages/mask/background/services/setup.ts | 96 - .../services/site-adaptors/connect.ts | 135 - .../services/site-adaptors/index.ts | 9 - .../background/services/site-adaptors/sdk.ts | 56 - packages/mask/background/services/types.ts | 28 - .../services/wallet/database/Plugin.db.ts | 10 - .../services/wallet/database/Wallet.db.ts | 38 - .../services/wallet/database/types.ts | 34 - .../services/wallet/services/connect.ts | 247 - .../services/wallet/services/helpers.ts | 15 - .../services/wallet/services/index.ts | 7 - .../services/wallet/services/legacyWallet.ts | 92 - .../wallet/services/maskwallet/index.ts | 85 - .../services/wallet/services/rpc.ts | 49 - .../services/wallet/services/sdk.ts | 48 - .../services/wallet/services/select.ts | 36 - .../services/wallet/services/send.ts | 112 - .../wallet/services/wallet/database/index.ts | 3 - .../wallet/services/wallet/database/locker.ts | 26 - .../wallet/services/wallet/database/secret.ts | 153 - .../wallet/services/wallet/database/wallet.ts | 123 - .../services/wallet/services/wallet/index.ts | 508 - .../services/wallet/services/wallet/locker.ts | 53 - .../wallet/services/wallet/password.ts | 124 - .../services/wallet/services/wallet/type.ts | 19 - .../Cancellable/CleanProfileAndAvatar.ts | 38 - .../tasks/Cancellable/FetchRemoteFlags.ts | 7 - .../InjectContentScripts_declarative.ts | 67 - .../InjectContentScripts_imperative.ts | 43 - .../tasks/Cancellable/SettingsListener.ts | 13 - .../tasks/Cancellable/StartPluginHost.ts | 24 - .../Cancellable/StartSandboxedPluginHost.ts | 84 - .../tasks/Cancellable/WalletAutoLock.ts | 7 - .../tasks/NotCancellable/OnInstall.ts | 42 - .../tasks/NotCancellable/PendingTasks.ts | 30 - .../tasks/NotCancellable/PrintBuildFlags.ts | 2 - packages/mask/background/tasks/setup.hmr.ts | 10 - packages/mask/background/tasks/setup.ts | 5 - packages/mask/background/tsconfig.json | 24 - .../background/utils/deprecated-storage.ts | 96 - .../mask/background/utils/injectScript.ts | 75 - .../CompositionDialog/Composition.tsx | 158 - .../CompositionDialog/CompositionUI.tsx | 356 - .../EncryptionMethodSelector.tsx | 68 - .../EncryptionTargetSelector.tsx | 154 - .../CompositionDialog/PopoverListItem.tsx | 56 - .../CompositionDialog/PopoverListTrigger.tsx | 91 - .../CompositionDialog/SteganographyPayload.ts | 22 - .../useCompositionClipboardRequest.tsx | 34 - .../CompositionDialog/useRecipientsList.ts | 29 - .../useSelectedRecipientsList.ts | 9 - .../components/CompositionDialog/useSubmit.ts | 114 - .../components/DataSource/useActivatedUI.ts | 124 - .../usePersonaPerSiteConnectStatus.ts | 35 - .../DataSource/usePluginHostPermission.ts | 43 - .../DataSource/useSearchedKeyword.ts | 20 - .../components/GuideStep/index.tsx | 232 - .../GuideStep/useSetupGuideStatus.ts | 15 - .../AdditionalPostContent.tsx | 84 - .../AutoPasteFailedDialog.tsx | 152 - .../components/InjectedComponents/Avatar.tsx | 38 - .../InjectedComponents/CommentBox.tsx | 59 - .../DecryptedPost/DecryptPostAwaiting.tsx | 34 - .../DecryptedPost/DecryptPostFailed.tsx | 26 - .../DecryptedPost/DecryptedPost.tsx | 234 - .../DecryptedPost/DecryptedPostSuccess.tsx | 177 - .../DecryptedPost/RecipientsToolTip.tsx | 74 - .../DecryptedPost/authorDifferentMessage.tsx | 18 - .../InjectedComponents/DecryptedPost/types.ts | 22 - .../DecryptedPostMetadataRender.tsx | 28 - .../DisabledPluginSuggestion.tsx | 179 - .../InjectedComponents/PageInspector.tsx | 67 - .../InjectedComponents/PermissionBoundary.tsx | 95 - .../InjectedComponents/PostActions.tsx | 17 - .../InjectedComponents/PostComments.tsx | 54 - .../InjectedComponents/PostDialogHint.tsx | 79 - .../InjectedComponents/PostInspector.tsx | 51 - .../InjectedComponents/PostReplacer.tsx | 117 - .../ProfileCard/AvatarDecoration.tsx | 30 - .../ProfileCard/ProfileBar.tsx | 284 - .../ProfileCard/ProfileCardTitle.tsx | 155 - .../InjectedComponents/ProfileCard/index.tsx | 260 - .../InjectedComponents/ProfileCover.tsx | 33 - .../InjectedComponents/ProfileTab.tsx | 66 - .../InjectedComponents/ProfileTabContent.tsx | 523 - .../SearchResultInspector.tsx | 147 - .../InjectedComponents/SelectPeopleDialog.tsx | 156 - .../SetupGuide/AccountConnectStatus.tsx | 138 - .../SetupGuide/CheckConnection.tsx | 16 - .../SetupGuide/PinExtension.tsx | 113 - .../SetupGuide/SetupGuideContext.tsx | 112 - .../SetupGuide/VerifyNextID.tsx | 344 - .../SetupGuide/WizardDialog.tsx | 121 - .../SetupGuide/hooks/useConnectPersona.ts | 39 - .../SetupGuide/hooks/useConnectedVerified.ts | 11 - .../SetupGuide/hooks/useCurrentUserId.ts | 13 - .../SetupGuide/hooks/useNotifyConnected.ts | 19 - .../InjectedComponents/SetupGuide/index.tsx | 101 - .../InjectedComponents/ToolboxUnstyled.tsx | 203 - .../components/Welcomes/Banner.tsx | 102 - .../components/shared/DraggableDiv.tsx | 46 - .../SelectProfileUI/SelectProfileUI.tsx | 204 - .../shared/SelectProfileUI/index.tsx | 1 - .../shared/SelectRecipients/ProfileInList.tsx | 173 - .../SelectRecipients/SelectRecipients.tsx | 67 - .../SelectRecipientsDialog.tsx | 298 - .../shared/SelectRecipients/useContacts.ts | 27 - .../useTwitterIdByWalletSearch.tsx | 38 - .../assets/stepAssets/dividerActive.png | Bin 342 -> 0 bytes .../assets/stepAssets/dividerDisable.png | Bin 71 -> 0 bytes .../shared/assets/stepAssets/dividerDone.png | Bin 74 -> 0 bytes .../shared/assets/stepAssets/step1Active.png | Bin 778 -> 0 bytes .../shared/assets/stepAssets/step2Active.png | Bin 837 -> 0 bytes .../shared/assets/stepAssets/step2Disable.png | Bin 501 -> 0 bytes .../shared/assets/stepAssets/stepSuccess.png | Bin 606 -> 0 bytes .../shared/openApplicationBoardDialog.tsx | 39 - .../useMaskSiteAdaptorMixedTheme.ts | 23 - packages/mask/content-script/env.d.ts | 3 - packages/mask/content-script/index.ts | 18 - .../resources/extensionPinned.png | Bin 4366 -> 0 bytes .../resources/image-payload/index.ts | 7 - .../image-payload/normal/payload-2021.png | Bin 176781 -> 0 bytes .../image-payload/normal/payload-2022.png | Bin 210938 -> 0 bytes .../image-payload/normal/payload-2023.png | Bin 174480 -> 0 bytes .../resources/maskFilledIcon.png | Bin 5456 -> 0 bytes .../resources/maskFilledIconDark.png | Bin 6971 -> 0 bytes .../resources/tool-icon/airdrop.png | Bin 1312 -> 0 bytes .../resources/tool-icon/claim.png | Bin 578 -> 0 bytes .../resources/tool-icon/encryptedmsg.png | Bin 476 -> 0 bytes .../resources/tool-icon/markets.png | Bin 583 -> 0 bytes .../resources/tool-icon/redpacket.png | Bin 962 -> 0 bytes .../resources/tool-icon/swap.png | Bin 915 -> 0 bytes .../resources/tool-icon/token.png | Bin 1696 -> 0 bytes .../automation/AttachImageToComposition.ts | 28 - .../site-adaptor-infra/defaults/index.ts | 8 - .../defaults/inject/CommentBox.tsx | 64 - .../defaults/inject/Comments.tsx | 58 - .../defaults/inject/PageInspector.tsx | 13 - .../defaults/inject/PostActions.tsx | 39 - .../defaults/inject/PostInspector.tsx | 38 - .../defaults/inject/PostReplacer.tsx | 37 - .../defaults/inject/StartSetupGuide.tsx | 9 - .../defaults/state/InitProfiles.ts | 21 - .../site-adaptor-infra/define.ts | 23 - .../site-adaptor-infra/index.ts | 3 - .../site-adaptor-infra/sandboxed-plugin.ts | 98 - .../content-script/site-adaptor-infra/ui.ts | 276 - .../site-adaptor-infra/utils.ts | 25 - .../utils/create-post-context.ts | 191 - .../content-script/site-adaptors/README.md | 18 - .../facebook.com/automation/openComposeBox.ts | 80 - .../automation/pasteTextToComposition.ts | 65 - .../automation/pasteToCommentBoxFacebook.ts | 20 - .../site-adaptors/facebook.com/base.ts | 15 - .../collecting/getSearchedKeyword.ts | 11 - .../facebook.com/collecting/identity.ts | 103 - .../facebook.com/collecting/posts.tsx | 186 - .../facebook.com/collecting/theme.ts | 38 - .../facebook.com/customization/custom.ts | 49 - .../customization/render-fragments.tsx | 21 - .../site-adaptors/facebook.com/index.ts | 14 - .../facebook.com/injection/Avatar/index.tsx | 47 - .../facebook.com/injection/Banner.tsx | 26 - .../facebook.com/injection/Composition.tsx | 75 - .../injection/NFT/NFTAvatarEditProfile.tsx | 86 - .../injection/NFT/NFTAvatarInFacebook.tsx | 82 - .../injection/NFT/NFTAvatarInTimeline.tsx | 111 - .../injection/NFT/ProfileNFTAvatar.tsx | 114 - .../injection/NFT/useSaveAvatarInFacebook.ts | 51 - .../facebook.com/injection/PostInspector.tsx | 80 - .../facebook.com/injection/PostReplacer.tsx | 21 - .../facebook.com/injection/ProfileContent.tsx | 74 - .../facebook.com/injection/ProfileCover.tsx | 15 - .../facebook.com/injection/ProfileTab.tsx | 164 - .../injection/SearchResultInspector.tsx | 11 - .../facebook.com/injection/Toolbar.tsx | 47 - .../facebook.com/injection/ToolbarUI.tsx | 65 - .../site-adaptors/facebook.com/shared.ts | 38 - .../site-adaptors/facebook.com/ui-provider.ts | 228 - .../facebook.com/utils/avatar.ts | 16 - .../utils/getProfileIdentifier.ts | 92 - .../facebook.com/utils/parse-username.ts | 41 - .../facebook.com/utils/resolveFacebookLink.ts | 3 - .../facebook.com/utils/selector.ts | 151 - .../site-adaptors/facebook.com/utils/user.ts | 76 - .../content-script/site-adaptors/index.ts | 5 - .../site-adaptors/instagram.com/base.ts | 13 - .../collecting/identity-provider.ts | 55 - .../instagram.com/collecting/identity.ts | 59 - .../instagram.com/collecting/posts.ts | 74 - .../instagram.com/collecting/theme.ts | 42 - .../instagram.com/customization/custom.ts | 26 - .../site-adaptors/instagram.com/index.ts | 14 - .../instagram.com/injection/Avatar/index.tsx | 54 - .../injection/NFT/NFTAvatarEditProfile.tsx | 65 - .../injection/NFT/NFTAvatarInInstagram.tsx | 111 - .../injection/NFT/NFTAvatarInTimeline.tsx | 91 - .../injection/NFT/NFTAvatarSettingDialog.tsx | 98 - .../injection/NFT/ProfileNFTAvatar.tsx | 80 - .../instagram.com/injection/ProfileTab.tsx | 209 - .../injection/ProfileTabContent.tsx | 65 - .../instagram.com/injection/post-inspector.ts | 19 - .../site-adaptors/instagram.com/shared.ts | 18 - .../instagram.com/ui-provider.ts | 73 - .../instagram.com/utils/avatar.ts | 15 - .../instagram.com/utils/selector.ts | 82 - .../site-adaptors/instagram.com/utils/user.ts | 49 - .../automation/AttachImageToComposition.ts | 32 - .../minds.com/automation/gotoNewsFeedPage.ts | 5 - .../minds.com/automation/gotoProfilePage.ts | 10 - .../minds.com/automation/openComposeBox.ts | 36 - .../automation/pasteTextToComposition.ts | 67 - .../automation/pasteToCommentBoxMinds.ts | 19 - .../site-adaptors/minds.com/base.ts | 17 - .../collecting/getSearchedKeyword.ts | 8 - .../minds.com/collecting/identity.ts | 53 - .../minds.com/collecting/post.ts | 143 - .../minds.com/collecting/theme.ts | 40 - .../minds.com/customization/custom.ts | 48 - .../customization/render-fragments.tsx | 44 - .../site-adaptors/minds.com/index.ts | 14 - .../minds.com/injection/Avatar/index.tsx | 52 - .../minds.com/injection/Banner.tsx | 51 - .../minds.com/injection/CommentBox.tsx | 41 - .../minds.com/injection/PostDialog.tsx | 19 - .../minds.com/injection/PostDialogHint.tsx | 68 - .../minds.com/injection/PostInspector.tsx | 6 - .../minds.com/injection/PostReplacer.tsx | 26 - .../minds.com/injection/ProfileCover.tsx | 15 - .../injection/SearchResultInspector.tsx | 11 - .../minds.com/injection/ToolboxHint.tsx | 13 - .../minds.com/injection/ToolboxHint_UI.tsx | 56 - .../minds.com/injection/inject.tsx | 7 - .../site-adaptors/minds.com/shared.ts | 40 - .../site-adaptors/minds.com/ui-provider.ts | 207 - .../site-adaptors/minds.com/utils/fetch.ts | 89 - .../site-adaptors/minds.com/utils/postBox.ts | 20 - .../site-adaptors/minds.com/utils/selector.ts | 96 - .../site-adaptors/minds.com/utils/user.ts | 11 - .../site-adaptors/mirror.xyz/base.ts | 13 - .../mirror.xyz/collecting/identity.ts | 90 - .../mirror.xyz/collecting/posts.ts | 131 - .../mirror.xyz/collecting/theme.ts | 31 - .../mirror.xyz/collecting/utils.ts | 73 - .../mirror.xyz/customization/custom.ts | 26 - .../mirror.xyz/customization/ui-overwrite.ts | 84 - .../site-adaptors/mirror.xyz/index.ts | 14 - .../injection/PostActions/index.tsx | 75 - .../injection/Tips/MenuAuthorTipButton.tsx | 27 - .../injection/Tips/PostVerification.tsx | 29 - .../mirror.xyz/injection/Tips/ProfilePage.tsx | 29 - .../injection/Tips/TipsButtonWrapper.tsx | 81 - .../injection/Tips/adjustArticleInfoBar.ts | 17 - .../mirror.xyz/injection/Tips/index.tsx | 11 - .../site-adaptors/mirror.xyz/shared.ts | 24 - .../mirror.xyz/tests/collection-utils.ts | 100 - .../site-adaptors/mirror.xyz/ui-provider.ts | 51 - .../mirror.xyz/utils/selectors.ts | 30 - .../site-adaptors/mirror.xyz/utils/user.ts | 28 - .../automation/gotoNewsFeedPage.ts | 4 - .../twitter.com/automation/gotoProfilePage.ts | 10 - .../twitter.com/automation/openComposeBox.ts | 14 - .../automation/pasteImageToComposition.ts | 31 - .../automation/pasteTextToComposition.ts | 65 - .../twitter.com/automation/publishPost.ts | 38 - .../site-adaptors/twitter.com/base.ts | 14 - .../collecting/getSearchedKeyword.ts | 27 - .../twitter.com/collecting/identity.ts | 187 - .../twitter.com/collecting/post.ts | 235 - .../twitter.com/collecting/theme.ts | 82 - .../site-adaptors/twitter.com/constant.ts | 30 - .../twitter.com/customization/custom.ts | 70 - .../twitter.com/customization/i18n.ts | 13 - .../customization/render-fragments.tsx | 28 - .../customization/twitter-color-schema.json | 226 - .../site-adaptors/twitter.com/index.ts | 14 - .../twitter.com/injection/Avatar/index.tsx | 76 - .../twitter.com/injection/Banner.tsx | 23 - .../twitter.com/injection/Calendar.tsx | 40 - .../twitter.com/injection/Farcaster/index.tsx | 13 - .../injectFarcasterOnConversation.tsx | 96 - .../Farcaster/injectFarcasterOnPost.tsx | 87 - .../Farcaster/injectFarcasterOnProfile.tsx | 58 - .../Farcaster/injectFarcasterOnSpaceDock.tsx | 95 - .../Farcaster/injectFarcasterOnUserCell.tsx | 89 - .../twitter.com/injection/Lens/index.tsx | 13 - .../Lens/injectLensOnConversation.tsx | 92 - .../injection/Lens/injectLensOnPost.tsx | 81 - .../injection/Lens/injectLensOnProfile.tsx | 58 - .../injection/Lens/injectLensOnSpaceDock.tsx | 91 - .../injection/Lens/injectLensOnUserCell.tsx | 85 - .../twitter.com/injection/MaskIcon.tsx | 85 - .../twitter.com/injection/NFT/Avatar.tsx | 72 - .../injection/NFT/MiniAvatarBorder.tsx | 12 - .../injection/NFT/NFTAvatarEditProfile.tsx | 92 - .../NFT/NFTAvatarEditProfileDialog.tsx | 99 - .../injection/NFT/NFTAvatarInTwitter.tsx | 109 - .../injection/NFT/TweetNFTAvatar.tsx | 58 - .../twitter.com/injection/NFT/index.tsx | 14 - .../injection/NFT/useInjectedCSS.ts | 54 - .../injection/NFT/useSaveAvatarInTwitter.tsx | 32 - .../injection/NFT/useUpdatedAvatar.ts | 38 - .../injection/NameWidget/index.tsx | 9 - .../NameWidget/injectNameWidgetOnPost.tsx | 78 - .../NameWidget/injectNameWidgetOnProfile.tsx | 59 - .../NameWidget/injectNameWidgetOnSidebar.tsx | 54 - .../injection/PostActions/index.tsx | 9 - .../twitter.com/injection/PostDialog.tsx | 23 - .../twitter.com/injection/PostDialogHint.tsx | 111 - .../twitter.com/injection/PostInspector.tsx | 55 - .../twitter.com/injection/PostReplacer.tsx | 37 - .../injection/ProfileCard/constants.ts | 2 - .../injection/ProfileCard/index.tsx | 102 - .../ProfileCard/useControlProfileCard.ts | 92 - .../twitter.com/injection/ProfileCover.tsx | 17 - .../twitter.com/injection/ProfileTab.tsx | 414 - .../injection/ProfileTabContent.tsx | 98 - .../injection/SearchResultInspector.tsx | 13 - .../twitter.com/injection/SwitchLogo.tsx | 17 - .../injection/Tips/FollowTipsButton.tsx | 99 - .../injection/Tips/PostTipsButton.tsx | 108 - .../injection/Tips/ProfileTipsButton.tsx | 66 - .../twitter.com/injection/Tips/hooks.ts | 10 - .../twitter.com/injection/Tips/index.tsx | 9 - .../twitter.com/injection/ToolboxHint.tsx | 62 - .../twitter.com/injection/ToolboxHint_UI.tsx | 110 - .../twitter.com/injection/inject.tsx | 7 - .../twitter.com/locales/en-US.json | 3 - .../twitter.com/locales/index.ts | 6 - .../twitter.com/locales/ja-JP.json | 1 - .../twitter.com/locales/ko-KR.json | 3 - .../twitter.com/locales/languages.ts | 33 - .../twitter.com/locales/qya-AA.json | 3 - .../twitter.com/locales/zh-CN.json | 3 - .../twitter.com/locales/zh-TW.json | 1 - .../site-adaptors/twitter.com/shared.ts | 58 - .../site-adaptors/twitter.com/ui-provider.ts | 249 - .../twitter.com/utils/AvatarType.ts | 3 - .../site-adaptors/twitter.com/utils/avatar.ts | 29 - .../site-adaptors/twitter.com/utils/fetch.ts | 171 - .../twitter.com/utils/postBox.ts | 21 - .../twitter.com/utils/selector.ts | 312 - .../site-adaptors/twitter.com/utils/url.ts | 23 - .../site-adaptors/twitter.com/utils/user.ts | 34 - .../content-script/site-adaptors/utils.ts | 16 - packages/mask/content-script/tsconfig.json | 38 - .../content-script/utils/collectNodeText.ts | 31 - .../utils/collectTwitterEmoji.ts | 6 - .../mask/content-script/utils/downloadUrl.ts | 15 - .../content-script/utils/hasPayloadLike.ts | 4 - packages/mask/content-script/utils/index.ts | 8 - .../utils/pasteImageToActiveElements.ts | 10 - .../mask/content-script/utils/regexMatch.ts | 16 - .../utils/selectElementContents.ts | 13 - .../mask/content-script/utils/shadow-root.ts | 5 - .../shadow-root/ShadowRootAttachPointRoot.tsx | 31 - .../utils/shadow-root/renderInShadowRoot.tsx | 54 - .../mask/content-script/utils/startWatch.ts | 102 - .../utils/untilElementAvailable.ts | 12 - packages/mask/dashboard/Dashboard.tsx | 78 - .../dashboard/assets/Welcome.splinecode.png | Bin 486904 -> 0 bytes .../assets/images/AboutDialogBackground.png | Bin 11032 -> 0 bytes .../dashboard/assets/images/MaskWallet.png | Bin 5329 -> 0 bytes .../dashboard/assets/images/MaskWatermark.png | Bin 1178 -> 0 bytes .../assets/images/PrintBackground.png | Bin 53159 -> 0 bytes .../mask/dashboard/assets/images/Trend.png | Bin 1146 -> 0 bytes packages/mask/dashboard/assets/index.ts | 10 - .../dashboard/components/ActionCard/index.tsx | 65 - .../components/BackupPreview/index.tsx | 267 - .../dashboard/components/FooterLine/About.tsx | 136 - .../components/FooterLine/Version.tsx | 22 - .../dashboard/components/FooterLine/index.tsx | 119 - .../components/FooterLine/links.json | 10 - .../dashboard/components/HeaderLine/index.tsx | 13 - .../Mnemonic/DesktopMnemonicConfirm.tsx | 85 - .../components/Mnemonic/MnemonicReveal.tsx | 57 - .../dashboard/components/Mnemonic/index.tsx | 2 - .../components/OnboardingWriter/index.tsx | 97 - .../components/PasswordField/index.tsx | 43 - .../components/PrimaryButton/index.tsx | 38 - .../RegisterFrame/ButtonContainer.tsx | 25 - .../RegisterFrame/ColumnContentHeader.tsx | 69 - .../RegisterFrame/ColumnContentLayout.tsx | 58 - .../components/RegisterFrame/ColumnLayout.tsx | 51 - .../components/Restore/AccountStatusBar.tsx | 33 - .../components/Restore/BackupInfoCard.tsx | 43 - .../ConfirmSynchronizePasswordDialog.tsx | 39 - .../RestoreFromCloud/ConfirmBackupInfo.tsx | 90 - .../Restore/RestoreFromCloud/EmailField.tsx | 124 - .../Restore/RestoreFromCloud/InputForm.tsx | 107 - .../Restore/RestoreFromCloud/PhoneField.tsx | 124 - .../RestoreFromCloud/RestoreProvider.tsx | 17 - .../Restore/RestoreFromCloud/index.tsx | 127 - .../RestoreFromCloud/restoreReducer.ts | 157 - .../Restore/RestoreFromMnemonic.tsx | 58 - .../Restore/RestoreFromPrivateKey.tsx | 97 - .../Restore/RestorePersonaFromLocal.tsx | 194 - .../Restore/RestoreWalletFromLocal.tsx | 115 - .../components/SecondaryButton/index.tsx | 39 - .../dashboard/components/SetupFrame/index.tsx | 93 - .../contexts/CloudBackupFormContext.tsx | 75 - .../dashboard/contexts/RecoveryContext.tsx | 49 - packages/mask/dashboard/contexts/index.ts | 1 - packages/mask/dashboard/env.d.ts | 3 - .../dashboard/hooks/useBackupFormState.ts | 64 - .../dashboard/hooks/useCreatePersonaV2.ts | 21 - .../dashboard/hooks/useMnemonicWordsPuzzle.ts | 90 - .../mask/dashboard/hooks/useTermsAgreed.ts | 21 - .../mask/dashboard/initialization/i18n.ts | 8 - .../mask/dashboard/initialization/index.ts | 2 - .../mask/dashboard/initialization/render.tsx | 15 - packages/mask/dashboard/locales/en-US.json | 519 - packages/mask/dashboard/locales/index.ts | 6 - packages/mask/dashboard/locales/ja-JP.json | 519 - packages/mask/dashboard/locales/ko-KR.json | 517 - packages/mask/dashboard/locales/languages.ts | 33 - packages/mask/dashboard/locales/qya-AA.json | 519 - packages/mask/dashboard/locales/zh-CN.json | 519 - packages/mask/dashboard/locales/zh-TW.json | 346 - .../BackupPreviewDialog.tsx | 301 - .../modals/BackupPreviewModal/index.tsx | 50 - .../dashboard/modals/ConfirmModal/index.tsx | 108 - .../MergeBackupModal/MergeBackupDialog.tsx | 264 - .../modals/MergeBackupModal/index.tsx | 57 - packages/mask/dashboard/modals/index.tsx | 17 - packages/mask/dashboard/modals/modals.ts | 8 - .../AddDeriveWallet/index.tsx | 251 - .../CreateMnemonic/ComponentToPrint.tsx | 127 - .../CreateMaskWallet/CreateMnemonic/index.tsx | 461 - .../CreateWalletForm/index.tsx | 169 - .../CreateMaskWallet/Onboarding/index.tsx | 138 - .../pages/CreateMaskWallet/Recovery/index.tsx | 276 - .../pages/CreateMaskWallet/context.ts | 54 - .../pages/CreateMaskWallet/index.tsx | 28 - .../dashboard/pages/PrivacyPolicy/en.html | 872 - .../dashboard/pages/PrivacyPolicy/index.tsx | 28 - .../dashboard/pages/PrivacyPolicy/zh.html | 737 - .../SetupPersona/CloudBackup/EmailForm.tsx | 107 - .../SetupPersona/CloudBackup/PhoneForm.tsx | 106 - .../pages/SetupPersona/CloudBackup/index.tsx | 180 - .../SetupPersona/CloudBackupPreview/index.tsx | 218 - .../pages/SetupPersona/LocalBackup/index.tsx | 151 - .../Mnemonic/ComponentToPrint.tsx | 121 - .../pages/SetupPersona/Mnemonic/Words.tsx | 55 - .../pages/SetupPersona/Mnemonic/index.tsx | 216 - .../pages/SetupPersona/Onboarding/index.tsx | 197 - .../pages/SetupPersona/Recovery/index.tsx | 210 - .../pages/SetupPersona/SignUp/index.tsx | 131 - .../pages/SetupPersona/Welcome/Article.tsx | 111 - .../pages/SetupPersona/Welcome/index.tsx | 134 - .../dashboard/pages/SetupPersona/index.tsx | 29 - .../mask/dashboard/pages/SignUp/index.tsx | 10 - .../mask/dashboard/pages/SignUp/routePath.ts | 6 - .../mask/dashboard/pages/SignUp/routes.tsx | 28 - .../pages/SignUp/steps/ConnectSocialMedia.tsx | 74 - .../pages/SignUp/steps/MnemonicRevealForm.tsx | 159 - .../pages/SignUp/steps/PersonaCreate.tsx | 34 - .../pages/SignUp/steps/PersonaNameUI.tsx | 82 - .../pages/SignUp/steps/PersonaRecovery.tsx | 91 - .../pages/SignUp/steps/PreviewDialog.tsx | 182 - .../dashboard/pages/SignUp/steps/index.ts | 4 - packages/mask/dashboard/pages/TermsGuard.tsx | 33 - packages/mask/dashboard/pages/routes.tsx | 28 - packages/mask/dashboard/tsconfig.json | 21 - packages/mask/dashboard/utils/api.ts | 108 - packages/mask/dashboard/utils/regexp.ts | 5 - packages/mask/dashboard/utils/type.ts | 18 - .../mask/devtools/content-script/index.ts | 25 - packages/mask/devtools/env.d.ts | 1 - packages/mask/devtools/panels/index.tsx | 31 - packages/mask/devtools/panels/react.tsx | 345 - packages/mask/devtools/panels/utils.ts | 37 - packages/mask/devtools/shared.ts | 32 - packages/mask/devtools/tsconfig.json | 9 - packages/mask/entry-sdk/README.md | 100 - packages/mask/entry-sdk/bridge/eth.ts | 264 - .../mask/entry-sdk/bridge/eth/validator.ts | 304 - packages/mask/entry-sdk/bridge/index.ts | 11 - packages/mask/entry-sdk/hmr-bridge.ts | 14 - packages/mask/entry-sdk/hmr-sdk.ts | 14 - packages/mask/entry-sdk/index.ts | 12 - packages/mask/entry-sdk/tsconfig.json | 17 - packages/mask/package.json | 177 - packages/mask/popups/Popup.tsx | 201 - .../components/ActionModal/ActionModal.tsx | 99 - .../ActionModal/ActionModalContext.tsx | 60 - .../popups/components/ActionModal/index.tsx | 2 - .../components/AddContactInputPanel/index.tsx | 157 - .../components/BottomController/index.tsx | 23 - .../popups/components/BottomDrawer/index.tsx | 74 - .../ConnectSocialAccounts/index.tsx | 71 - .../components/ConnectedWallet/index.tsx | 222 - .../components/GasSettingMenu/index.tsx | 191 - .../popups/components/LoadingMask/index.tsx | 32 - .../components/LoadingPlaceholder/index.tsx | 28 - .../components/MnemonicDisplay/index.tsx | 118 - .../NFTAvatarPicker/CollectionList.tsx | 94 - .../components/NFTAvatarPicker/index.tsx | 133 - .../popups/components/Navigator/index.tsx | 84 - .../popups/components/NormalHeader/index.tsx | 88 - .../popups/components/PasswordField/index.tsx | 36 - .../popups/components/PersonaAvatar/index.tsx | 24 - .../components/PersonaPublicKey/index.tsx | 55 - .../components/PopupLayout/LoadMaskSDK.tsx | 66 - .../popups/components/PopupLayout/index.tsx | 96 - .../components/PrivateKeyDisplay/index.tsx | 167 - .../components/SelectProvider/index.tsx | 121 - .../components/SignRequestInfo/index.tsx | 314 - .../components/SocialAccounts/index.tsx | 112 - .../popups/components/StyledInput/index.tsx | 22 - .../popups/components/StyledRadio/index.tsx | 38 - .../components/TokenPicker/TokenItem.tsx | 190 - .../popups/components/TokenPicker/index.tsx | 97 - .../components/TransactionPreview/index.tsx | 249 - .../components/UnlockERC20Token/index.tsx | 284 - .../components/UnlockERC721Token/index.tsx | 240 - .../popups/components/WalletBalance/index.tsx | 44 - .../popups/components/WalletItem/index.tsx | 142 - .../components/WalletSettingList/index.tsx | 172 - packages/mask/popups/components/index.ts | 5 - packages/mask/popups/constants.ts | 14 - packages/mask/popups/hooks/index.ts | 12 - .../mask/popups/hooks/useConnectedOrigins.ts | 13 - .../mask/popups/hooks/useContactsContext.ts | 87 - .../mask/popups/hooks/useFriendProfiles.ts | 55 - packages/mask/popups/hooks/useFriends.ts | 104 - .../popups/hooks/useFriendsFromSearch.tsx | 79 - .../mask/popups/hooks/useGasOptionsMenu.tsx | 152 - packages/mask/popups/hooks/useGasRatio.ts | 24 - packages/mask/popups/hooks/useHasPassword.ts | 19 - packages/mask/popups/hooks/usePopupContext.ts | 26 - packages/mask/popups/hooks/usePopupTheme.ts | 17 - packages/mask/popups/hooks/useSearchValue.ts | 18 - .../popups/hooks/useSupportSocialNetworks.ts | 14 - .../mask/popups/hooks/useSupportedSites.ts | 20 - packages/mask/popups/hooks/useTitle.ts | 35 - packages/mask/popups/hooks/useTokenParams.ts | 29 - .../mask/popups/hooks/useVerifiedWallets.ts | 12 - packages/mask/popups/hooks/useWalletGroup.ts | 35 - packages/mask/popups/initialization/index.ts | 4 - .../mask/popups/initialization/render.tsx | 33 - .../popups/modals/AddContactModal/index.tsx | 174 - .../ChangeBackupPasswordModal/index.tsx | 193 - .../ChangePaymentPasswordModal/index.tsx | 203 - .../modals/ChooseCurrencyModal/index.tsx | 86 - .../modals/ChooseNetworkModal/index.tsx | 157 - .../mask/popups/modals/ChooseToken/index.tsx | 46 - .../mask/popups/modals/ConfirmModal/index.tsx | 62 - .../popups/modals/ConnectProvider/index.tsx | 188 - .../ConnectSocialAccountModal/index.tsx | 38 - .../modals/DeleteContactModal/index.tsx | 122 - .../popups/modals/EditContactModal/index.tsx | 192 - .../GasSettingModal/GasSettingDialog.tsx | 442 - .../popups/modals/GasSettingModal/index.tsx | 60 - .../modals/PersonaRenameModal/index.tsx | 81 - .../modals/PersonaSettingModal/index.tsx | 122 - .../modals/SelectAppearanceModal/index.tsx | 82 - .../modals/SelectLanguageModal/index.tsx | 61 - .../modals/SelectProviderModal/index.tsx | 29 - .../modals/SetBackupPasswordModal/index.tsx | 108 - .../modals/ShowPrivateKeyModal/index.tsx | 114 - .../modals/SupportedSitesModal/index.tsx | 115 - .../modals/SwitchPersonaModal/PersonaItem.tsx | 71 - .../modals/SwitchPersonaModal/index.tsx | 98 - .../VerifyBackupPasswordModal/index.tsx | 55 - .../WalletAutoLockSettingModal/index.tsx | 160 - .../popups/modals/WalletGroupModal/index.tsx | 113 - .../popups/modals/WalletRemoveModal/index.tsx | 144 - .../popups/modals/WalletRenameModal/index.tsx | 117 - packages/mask/popups/modals/index.tsx | 35 - packages/mask/popups/modals/modal-controls.ts | 24 - packages/mask/popups/modals/modals.ts | 16 - .../pages/Friends/AccountRender/index.tsx | 82 - .../Friends/ContactCard/Account/index.tsx | 52 - .../ContactCard/ConnectedAccounts/index.tsx | 90 - .../ContactCard/SocialAccount/index.tsx | 64 - .../pages/Friends/ContactCard/index.tsx | 232 - .../popups/pages/Friends/Contacts/index.tsx | 62 - .../pages/Friends/Detail/Account/index.tsx | 63 - .../Friends/Detail/ConnectAccounts/index.tsx | 52 - .../Friends/Detail/SocialAccount/index.tsx | 57 - .../mask/popups/pages/Friends/Detail/UI.tsx | 154 - .../popups/pages/Friends/Detail/index.tsx | 80 - .../mask/popups/pages/Friends/Home/UI.tsx | 74 - .../mask/popups/pages/Friends/Home/index.tsx | 79 - .../popups/pages/Friends/Search/index.tsx | 87 - .../popups/pages/Friends/SearchList/index.tsx | 65 - packages/mask/popups/pages/Friends/common.tsx | 68 - packages/mask/popups/pages/Friends/index.tsx | 37 - .../pages/Personas/AccountDetail/UI.tsx | 108 - .../pages/Personas/AccountDetail/index.tsx | 244 - .../pages/Personas/ConnectWallet/index.tsx | 320 - .../pages/Personas/ExportPrivateKey/index.tsx | 79 - .../mask/popups/pages/Personas/Home/UI.tsx | 306 - .../mask/popups/pages/Personas/Home/index.tsx | 81 - .../popups/pages/Personas/Logout/index.tsx | 328 - .../Personas/PersonaAvatarSetting/index.tsx | 296 - .../Personas/PersonaSignRequest/index.tsx | 126 - .../pages/Personas/WalletConnect/index.tsx | 123 - .../components/AccountAvatar/index.tsx | 72 - .../Personas/components/PersonaHeader/UI.tsx | 91 - .../components/PersonaHeader/index.tsx | 36 - packages/mask/popups/pages/Personas/index.tsx | 64 - packages/mask/popups/pages/Personas/type.ts | 5 - .../RequestPermission/RequestPermission.tsx | 54 - .../popups/pages/RequestPermission/index.tsx | 49 - packages/mask/popups/pages/Settings/index.tsx | 398 - .../popups/pages/Wallet/AddToken/index.tsx | 204 - .../popups/pages/Wallet/ChangeOwner/index.tsx | 469 - .../pages/Wallet/CollectibleDetail/index.tsx | 372 - .../pages/Wallet/ConnectedSites/index.tsx | 49 - .../popups/pages/Wallet/ContactList/index.tsx | 359 - .../pages/Wallet/CreateWallet/Derive.tsx | 165 - .../pages/Wallet/CreateWallet/context.ts | 8 - .../pages/Wallet/CreateWallet/index.tsx | 133 - .../popups/pages/Wallet/EditNetwork/index.tsx | 323 - .../Wallet/EditNetwork/network-schema.ts | 96 - .../pages/Wallet/EditNetwork/useWarnings.ts | 32 - .../pages/Wallet/ExportPrivateKey/index.tsx | 183 - .../Wallet/GasSetting/GasSetting1559.tsx | 475 - .../Wallet/GasSetting/Prior1559GasSetting.tsx | 330 - .../popups/pages/Wallet/GasSetting/index.tsx | 49 - .../Wallet/Interaction/InteractionContext.ts | 16 - .../popups/pages/Wallet/Interaction/index.tsx | 654 - .../pages/Wallet/NetworkManagement/index.tsx | 106 - .../pages/Wallet/NoWalletGuard/index.tsx | 13 - .../popups/pages/Wallet/Receive/index.tsx | 177 - .../popups/pages/Wallet/ResetWallet/index.tsx | 134 - .../pages/Wallet/SelectWallet/index.tsx | 219 - .../pages/Wallet/SetPaymentPassword/index.tsx | 355 - .../pages/Wallet/SwitchWallet/index.tsx | 179 - .../Wallet/TokenDetail/TrendingChart.tsx | 46 - .../popups/pages/Wallet/TokenDetail/index.tsx | 270 - .../Wallet/TokenDetail/useCoinGeckoCoinId.ts | 40 - .../TokenDetail/useCoinTrendingStats.ts | 22 - .../pages/Wallet/TokenDetail/useTokenPrice.ts | 8 - .../pages/Wallet/TokenDetail/useTrending.ts | 21 - .../pages/Wallet/TransactionDetail/index.tsx | 380 - .../pages/Wallet/TransactionDetail/types.ts | 4 - .../TransactionDetail/useTransactionLogs.ts | 49 - .../Wallet/Transfer/FungibleTokenSection.tsx | 335 - .../Transfer/NonFungibleTokenSection.tsx | 171 - .../popups/pages/Wallet/Transfer/index.tsx | 120 - .../Wallet/Transfer/useDefaultGasConfig.ts | 26 - .../mask/popups/pages/Wallet/Unlock/index.tsx | 130 - .../popups/pages/Wallet/WalletGuard/index.tsx | 62 - .../Wallet/WalletGuard/useMessageGuard.ts | 13 - .../WalletGuard/usePaymentPasswordGuard.ts | 10 - .../pages/Wallet/WalletSettings/AutoLock.tsx | 44 - .../Wallet/WalletSettings/ChangeCurrency.tsx | 37 - .../Wallet/WalletSettings/ChangeNetwork.tsx | 29 - .../Wallet/WalletSettings/ChangeOwner.tsx | 51 - .../WalletSettings/ChangePaymentPassword.tsx | 28 - .../WalletSettings/ConnectedOrigins.tsx | 28 - .../pages/Wallet/WalletSettings/Contacts.tsx | 32 - .../Wallet/WalletSettings/HidingScamTx.tsx | 32 - .../pages/Wallet/WalletSettings/Rename.tsx | 34 - .../Wallet/WalletSettings/ShowPrivateKey.tsx | 26 - .../pages/Wallet/WalletSettings/index.tsx | 148 - .../pages/Wallet/WalletSettings/useStyles.ts | 93 - .../Wallet/components/ActionGroup/index.tsx | 123 - .../components/ActivityList/ActivityItem.tsx | 432 - .../Wallet/components/ActivityList/index.tsx | 92 - .../ActivityList/useTransactions.ts | 67 - .../Wallet/components/AssetsList/MoreBar.tsx | 25 - .../Wallet/components/AssetsList/index.tsx | 253 - .../components/DisconnectModal/index.tsx | 159 - .../components/ImportCreateWallet/index.tsx | 104 - .../Wallet/components/OriginCard/index.tsx | 75 - .../pages/Wallet/components/StartUp/index.tsx | 70 - .../WalletAssets/WalletCollections.tsx | 128 - .../Wallet/components/WalletAssets/index.tsx | 177 - .../Wallet/components/WalletHeader/UI.tsx | 238 - .../WalletHeader/WalletAssetsValue.tsx | 27 - .../WalletHeader/WalletSetupHeaderUI.tsx | 51 - .../Wallet/components/WalletHeader/index.tsx | 74 - .../popups/pages/Wallet/components/index.ts | 1 - .../mask/popups/pages/Wallet/hooks/index.ts | 4 - .../popups/pages/Wallet/hooks/useAsset.ts | 16 - .../pages/Wallet/hooks/useAssetExpand.ts | 21 - .../popups/pages/Wallet/hooks/useConnected.ts | 18 - .../pages/Wallet/hooks/usePasswordForm.ts | 46 - .../Wallet/hooks/useUnConfirmedRequest.ts | 39 - .../pages/Wallet/hooks/useWalletAssets.ts | 7 - .../Wallet/hooks/useWalletAutoLockTime.ts | 16 - .../pages/Wallet/hooks/useWalletLockStatus.ts | 24 - packages/mask/popups/pages/Wallet/index.tsx | 83 - packages/mask/popups/pages/Wallet/type.ts | 56 - packages/mask/popups/pages/Wallet/utils.ts | 79 - packages/mask/popups/tsconfig.json | 13 - packages/mask/public/assets/128x128.png | Bin 1347 -> 0 bytes packages/mask/public/assets/16x16.png | Bin 291 -> 0 bytes packages/mask/public/assets/256x256.png | Bin 2476 -> 0 bytes packages/mask/public/assets/48x48.png | Bin 737 -> 0 bytes packages/mask/public/empty.html | 13 - packages/mask/public/js/lockdown.js | 25 - packages/mask/public/js/module-loader.js | 40 - packages/mask/public/js/patches.js | 99 - packages/mask/public/js/perf-measure.js | 464 - packages/mask/public/js/sentry-patch.js | 13 - packages/mask/public/js/trusted-types.js | 10 - packages/mask/public/jsconfig.json | 7 - packages/mask/public/manifest-v3.entry.js | 16 - .../public/sandboxed-modules/mv3-preload.js | 2 - packages/mask/public/worker.js | 16 - .../TypedMessageRender/Components/Text.tsx | 43 - .../shared-ui/TypedMessageRender/context.tsx | 47 - .../shared-ui/TypedMessageRender/registry.ts | 2 - .../TypedMessageRender/transformer.ts | 103 - packages/mask/shared-ui/components/Avatar.tsx | 23 - packages/mask/shared-ui/env.d.ts | 3 - packages/mask/shared-ui/hooks/index.ts | 7 - .../mask/shared-ui/hooks/useAppearance.ts | 14 - .../mask/shared-ui/hooks/useCurrentPersona.ts | 20 - packages/mask/shared-ui/hooks/useLanguage.ts | 15 - .../mask/shared-ui/hooks/usePersonasFromDB.ts | 15 - .../hooks/useSupportedSocialNetworkSites.ts | 14 - .../mask/shared-ui/hooks/useThemeLanguage.ts | 26 - .../mask/shared-ui/hooks/useUserContext.ts | 43 - packages/mask/shared-ui/index.ts | 5 - packages/mask/shared-ui/initUIContext.ts | 52 - .../shared-ui/initialization/async-setup.ts | 4 - .../mask/shared-ui/initialization/debugger.ts | 13 - .../mask/shared-ui/initialization/fetch.ts | 67 - .../mask/shared-ui/initialization/index.ts | 5 - .../mask/shared-ui/initialization/locales.ts | 11 - .../initialization/post-async-setup.ts | 5 - .../shared-ui/initialization/react-query.ts | 5 - .../mask/shared-ui/initialization/storage.ts | 27 - .../initialization/telemetry-update.ts | 3 - .../shared-ui/initialization/telemetry.ts | 13 - .../shared-ui/initialization/walletSetup.ts | 55 - packages/mask/shared-ui/locales/en-US.json | 1198 - packages/mask/shared-ui/locales/index.ts | 6 - packages/mask/shared-ui/locales/ja-JP.json | 1178 - packages/mask/shared-ui/locales/ko-KR.json | 1160 - packages/mask/shared-ui/locales/languages.ts | 33 - packages/mask/shared-ui/locales/qya-AA.json | 1178 - packages/mask/shared-ui/locales/zh-CN.json | 1175 - packages/mask/shared-ui/locales/zh-TW.json | 218 - .../mask/shared-ui/locales_legacy/index.ts | 9 - packages/mask/shared-ui/service.ts | 68 - packages/mask/shared-ui/tsconfig.json | 17 - .../shared-ui/utils/createNormalReactRoot.tsx | 30 - packages/mask/shared-ui/utils/permissions.ts | 21 - .../mask/shared-ui/utils/persistOptions.ts | 71 - packages/mask/shared/definitions/event.ts | 20 - packages/mask/shared/definitions/routes.ts | 10 - packages/mask/shared/definitions/wallet.ts | 16 - packages/mask/shared/env.d.ts | 2 - .../shared/helpers/attachNextIDToProfile.ts | 23 - packages/mask/shared/helpers/download.ts | 18 - .../mask/shared/helpers/formatTokenBalance.ts | 13 - packages/mask/shared/helpers/index.ts | 3 - packages/mask/shared/helpers/remoteFlagIO.ts | 14 - packages/mask/shared/index.ts | 1 - .../mask/shared/legacy-settings/listener.ts | 22 - packages/mask/shared/plugin-infra/host.ts | 52 - .../mask/shared/plugin-infra/register.d.ts | 2 - packages/mask/shared/plugin-infra/register.js | 32 - .../mask/shared/sandboxed-plugin/host-api.ts | 60 - .../mask/shared/site-adaptors/definitions.ts | 41 - .../implementations/facebook.com.ts | 13 - .../implementations/instagram.com.ts | 13 - .../implementations/minds.com.ts | 12 - .../implementations/mirror.xyz.ts | 12 - .../implementations/twitter.com.ts | 12 - packages/mask/shared/site-adaptors/types.d.ts | 20 - packages/mask/shared/tsconfig.json | 15 - packages/mask/swap/Swap.tsx | 33 - .../mask/swap/components/AccountManager.tsx | 141 - .../mask/swap/components/SwapBackground.tsx | 60 - packages/mask/swap/initialization/index.ts | 4 - packages/mask/swap/initialization/render.tsx | 8 - packages/mask/swap/pages/Swap/index.tsx | 278 - packages/mask/swap/tsconfig.json | 13 - packages/mask/swap/webgl/TickerManager.ts | 117 - packages/mask/swap/webgl/circleFrag.frag.ts | 1 - packages/mask/swap/webgl/circleVert.vert.ts | 1 - packages/mask/swap/webgl/shader2d.ts | 173 - packages/mask/tsconfig.json | 12 - packages/mask/utils-pure/README.md | 3 - packages/mask/utils-pure/crypto/index.ts | 17 - packages/mask/utils-pure/env.d.ts | 1 - packages/mask/utils-pure/hmr.ts | 14 - packages/mask/utils-pure/index.ts | 2 - packages/mask/utils-pure/tsconfig.json | 10 - packages/mask/web-workers/env.d.ts | 1 - packages/mask/web-workers/prepare.ts | 2 - packages/mask/web-workers/tsconfig.json | 11 - packages/mask/web-workers/wallet.ts | 36 - packages/plugins/Approval/package.json | 31 - .../src/SiteAdaptor/ApprovalDialog.tsx | 133 - .../src/SiteAdaptor/ApprovalNFTContent.tsx | 310 - .../src/SiteAdaptor/ApprovalTokenContent.tsx | 289 - .../Approval/src/SiteAdaptor/index.tsx | 59 - packages/plugins/Approval/src/base.ts | 43 - packages/plugins/Approval/src/constants.ts | 6 - .../plugins/Approval/src/locales/en-US.json | 14 - .../plugins/Approval/src/locales/index.ts | 6 - .../plugins/Approval/src/locales/ja-JP.json | 1 - .../plugins/Approval/src/locales/ko-KR.json | 14 - .../plugins/Approval/src/locales/languages.ts | 34 - .../plugins/Approval/src/locales/qya-AA.json | 14 - .../plugins/Approval/src/locales/zh-CN.json | 14 - .../plugins/Approval/src/locales/zh-TW.json | 1 - packages/plugins/Approval/src/register.ts | 11 - packages/plugins/Approval/tsconfig.json | 20 - packages/plugins/ArtBlocks/package.json | 37 - packages/plugins/ArtBlocks/src/README.md | 22 - .../ArtBlocks/src/SiteAdaptor/ActionBar.tsx | 47 - .../ArtBlocks/src/SiteAdaptor/Collectible.tsx | 141 - .../src/SiteAdaptor/CollectionView.tsx | 159 - .../ArtBlocks/src/SiteAdaptor/DetailsView.tsx | 142 - .../src/SiteAdaptor/PurchaseDialog.tsx | 189 - .../ArtBlocks/src/SiteAdaptor/index.tsx | 48 - packages/plugins/ArtBlocks/src/apis/index.ts | 40 - packages/plugins/ArtBlocks/src/base.tsx | 27 - packages/plugins/ArtBlocks/src/constants.ts | 19 - packages/plugins/ArtBlocks/src/env.d.ts | 1 - .../src/hooks/useArtBlocksContract.ts | 11 - .../plugins/ArtBlocks/src/hooks/useProject.ts | 11 - .../src/hooks/usePurchaseCallback.ts | 26 - .../plugins/ArtBlocks/src/icon/artblocks.png | Bin 2841 -> 0 bytes .../plugins/ArtBlocks/src/locales/en-US.json | 31 - .../plugins/ArtBlocks/src/locales/index.ts | 6 - .../plugins/ArtBlocks/src/locales/ja-JP.json | 1 - .../plugins/ArtBlocks/src/locales/ko-KR.json | 31 - .../ArtBlocks/src/locales/languages.ts | 34 - .../plugins/ArtBlocks/src/locales/qya-AA.json | 31 - .../plugins/ArtBlocks/src/locales/zh-CN.json | 31 - .../plugins/ArtBlocks/src/locales/zh-TW.json | 1 - packages/plugins/ArtBlocks/src/pipes/index.ts | 51 - packages/plugins/ArtBlocks/src/register.ts | 11 - packages/plugins/ArtBlocks/src/tests/check.ts | 40 - packages/plugins/ArtBlocks/src/types.ts | 24 - packages/plugins/ArtBlocks/src/utils.ts | 36 - packages/plugins/ArtBlocks/tsconfig.json | 22 - packages/plugins/Avatar/package.json | 42 - .../Avatar/src/Application/NFTAvatar.tsx | 58 - .../src/Application/NFTAvatarDialog.tsx | 24 - .../Avatar/src/Application/NFTInfo.tsx | 51 - .../Avatar/src/Application/NFTListDialog.tsx | 258 - .../Avatar/src/Application/PersonaItem.tsx | 102 - .../Avatar/src/Application/PersonaPage.tsx | 121 - .../Avatar/src/Application/RouterDialog.tsx | 69 - .../plugins/Avatar/src/Application/Routes.tsx | 26 - .../src/Application/UploadAvatarDialog.tsx | 157 - .../Avatar/src/SiteAdaptor/NFTAvatar.tsx | 238 - .../src/SiteAdaptor/NFTAvatarButton.tsx | 55 - .../Avatar/src/SiteAdaptor/NFTAvatarRing.tsx | 126 - .../Avatar/src/SiteAdaptor/NFTBadge.tsx | 69 - .../src/SiteAdaptor/NFTBadgeTimeline.tsx | 31 - .../Avatar/src/SiteAdaptor/NFTImage.tsx | 99 - .../Avatar/src/SiteAdaptor/RainbowBox.tsx | 70 - .../plugins/Avatar/src/SiteAdaptor/index.tsx | 85 - packages/plugins/Avatar/src/base.ts | 23 - packages/plugins/Avatar/src/constants.ts | 13 - .../Avatar/src/contexts/AvatarManagement.tsx | 132 - packages/plugins/Avatar/src/env.d.ts | 1 - packages/plugins/Avatar/src/hooks/useSave.ts | 57 - .../Avatar/src/hooks/useSaveAddress.ts | 27 - .../plugins/Avatar/src/hooks/useSaveAvatar.ts | 24 - .../plugins/Avatar/src/hooks/useSaveKV.ts | 23 - .../Avatar/src/hooks/useSaveStringStorage.ts | 19 - .../Avatar/src/hooks/useSaveToNextID.ts | 32 - packages/plugins/Avatar/src/index.ts | 13 - .../plugins/Avatar/src/locales/en-US.json | 56 - packages/plugins/Avatar/src/locales/index.ts | 6 - .../plugins/Avatar/src/locales/ja-JP.json | 56 - .../plugins/Avatar/src/locales/ko-KR.json | 56 - .../plugins/Avatar/src/locales/languages.ts | 34 - .../plugins/Avatar/src/locales/qya-AA.json | 56 - .../plugins/Avatar/src/locales/zh-CN.json | 56 - .../plugins/Avatar/src/locales/zh-TW.json | 1 - packages/plugins/Avatar/src/register.ts | 11 - packages/plugins/Avatar/src/types.ts | 25 - packages/plugins/Avatar/src/utils/index.ts | 74 - packages/plugins/Avatar/tsconfig.json | 15 - packages/plugins/Claim/README.md | 7 - packages/plugins/Claim/package.json | 36 - .../AirDropActivities/AirDropActivityItem.tsx | 223 - .../components/AirDropActivities/index.tsx | 83 - .../components/ClaimDialog/index.tsx | 39 - .../components/ClaimEntry/index.tsx | 31 - .../components/ClaimSuccessDialog/index.tsx | 109 - .../plugins/Claim/src/SiteAdaptor/index.tsx | 66 - .../Claim/src/assets/ARB-background.png | Bin 376992 -> 0 bytes .../plugins/Claim/src/assets/lock-dark.png | Bin 4631 -> 0 bytes packages/plugins/Claim/src/assets/lock.png | Bin 5128 -> 0 bytes packages/plugins/Claim/src/base.ts | 41 - packages/plugins/Claim/src/constants.ts | 5 - packages/plugins/Claim/src/env.d.ts | 1 - .../Claim/src/hooks/useAirDropActivity.ts | 46 - .../Claim/src/hooks/useClaimAirdrop.tsx | 105 - packages/plugins/Claim/src/locales/en-US.json | 29 - packages/plugins/Claim/src/locales/index.ts | 6 - packages/plugins/Claim/src/locales/ja-JP.json | 1 - packages/plugins/Claim/src/locales/ko-KR.json | 29 - .../plugins/Claim/src/locales/languages.ts | 34 - .../plugins/Claim/src/locales/qya-AA.json | 29 - packages/plugins/Claim/src/locales/zh-CN.json | 29 - packages/plugins/Claim/src/locales/zh-TW.json | 1 - packages/plugins/Claim/src/message.ts | 14 - packages/plugins/Claim/src/register.ts | 11 - packages/plugins/Claim/src/types.ts | 5 - packages/plugins/Claim/tsconfig.json | 10 - packages/plugins/Collectible/package.json | 40 - .../src/SiteAdaptor/Card/Collectible.tsx | 277 - .../src/SiteAdaptor/Card/CollectibleCard.tsx | 35 - .../src/SiteAdaptor/Card/CollectiblePaper.tsx | 16 - .../src/SiteAdaptor/Card/tabs/AboutTab.tsx | 65 - .../SiteAdaptor/Card/tabs/ActivitiesTab.tsx | 10 - .../src/SiteAdaptor/Card/tabs/DetailsTab.tsx | 48 - .../src/SiteAdaptor/Card/tabs/OffersTab.tsx | 12 - .../src/SiteAdaptor/CardDialog/CardDialog.tsx | 68 - .../CardDialog/CardDialogContent.tsx | 188 - .../SiteAdaptor/CardDialog/tabs/AboutTab.tsx | 58 - .../CardDialog/tabs/ActivitiesTab.tsx | 5 - .../SiteAdaptor/CardDialog/tabs/OffersTab.tsx | 7 - .../src/SiteAdaptor/Context/index.tsx | 70 - .../src/SiteAdaptor/DialogInspector.tsx | 69 - .../src/SiteAdaptor/PostInspector.tsx | 35 - .../src/SiteAdaptor/Shared/ActivitiesList.tsx | 64 - .../src/SiteAdaptor/Shared/ActivityCard.tsx | 177 - .../SiteAdaptor/Shared/DescriptionCard.tsx | 65 - .../src/SiteAdaptor/Shared/DetailsCard.tsx | 131 - .../src/SiteAdaptor/Shared/FigureCard.tsx | 138 - .../src/SiteAdaptor/Shared/LinkingAvatar.tsx | 25 - .../src/SiteAdaptor/Shared/OfferCard.tsx | 131 - .../src/SiteAdaptor/Shared/OffersList.tsx | 92 - .../src/SiteAdaptor/Shared/PriceCard.tsx | 144 - .../src/SiteAdaptor/Shared/PropertiesCard.tsx | 115 - .../src/SiteAdaptor/Shared/Rank.tsx | 29 - .../SiteAdaptor/hooks/getNFTXAssetAddress.ts | 35 - .../Collectible/src/SiteAdaptor/index.tsx | 196 - packages/plugins/Collectible/src/base.ts | 29 - packages/plugins/Collectible/src/constants.ts | 6 - packages/plugins/Collectible/src/env.d.ts | 1 - .../plugins/Collectible/src/helpers/index.ts | 1 - .../plugins/Collectible/src/helpers/url.ts | 252 - .../Collectible/src/locales/en-US.json | 115 - .../plugins/Collectible/src/locales/index.ts | 6 - .../Collectible/src/locales/ja-JP.json | 1 - .../Collectible/src/locales/ko-KR.json | 115 - .../Collectible/src/locales/languages.ts | 34 - .../Collectible/src/locales/qya-AA.json | 115 - .../Collectible/src/locales/zh-CN.json | 115 - .../Collectible/src/locales/zh-TW.json | 1 - packages/plugins/Collectible/src/register.ts | 11 - packages/plugins/Collectible/src/schema.json | 5 - packages/plugins/Collectible/src/types.ts | 17 - packages/plugins/Collectible/tests/helpers.ts | 152 - packages/plugins/Collectible/tsconfig.json | 17 - packages/plugins/CrossChainBridge/README.md | 5 - .../plugins/CrossChainBridge/package.json | 26 - .../SiteAdaptor/CrossChainBridgeDialog.tsx | 31 - .../src/SiteAdaptor/MaskIcon.tsx | 50 - .../assets/arbitrum-one-bridge.png | Bin 1845 -> 0 bytes .../src/SiteAdaptor/assets/boba-bridge.png | Bin 3054 -> 0 bytes .../src/SiteAdaptor/assets/cbridge.png | Bin 3949 -> 0 bytes .../src/SiteAdaptor/assets/cross-chain.png | Bin 3527 -> 0 bytes .../src/SiteAdaptor/assets/polygon-bridge.png | Bin 1020 -> 0 bytes .../src/SiteAdaptor/assets/rainbow-bridge.png | Bin 1355 -> 0 bytes .../SiteAdaptor/components/BridgeStack.tsx | 70 - .../src/SiteAdaptor/index.tsx | 49 - packages/plugins/CrossChainBridge/src/base.ts | 21 - .../CrossChainBridge/src/constants.tsx | 52 - .../plugins/CrossChainBridge/src/env.d.ts | 1 - .../plugins/CrossChainBridge/src/index.ts | 1 - .../CrossChainBridge/src/locales/en-US.json | 5 - .../CrossChainBridge/src/locales/index.ts | 6 - .../CrossChainBridge/src/locales/ja-JP.json | 5 - .../CrossChainBridge/src/locales/ko-KR.json | 5 - .../CrossChainBridge/src/locales/languages.ts | 34 - .../CrossChainBridge/src/locales/qya-AA.json | 5 - .../CrossChainBridge/src/locales/zh-CN.json | 5 - .../CrossChainBridge/src/locales/zh-TW.json | 1 - .../plugins/CrossChainBridge/src/register.ts | 11 - .../plugins/CrossChainBridge/tsconfig.json | 10 - packages/plugins/CyberConnect/package.json | 33 - .../src/SiteAdaptor/ConnectButton.tsx | 115 - .../src/SiteAdaptor/FollowTab.tsx | 68 - .../src/SiteAdaptor/FollowersPage.tsx | 44 - .../CyberConnect/src/SiteAdaptor/Profile.tsx | 207 - .../CyberConnect/src/SiteAdaptor/index.tsx | 70 - .../CyberConnect/src/Worker/apis/index.ts | 139 - .../plugins/CyberConnect/src/Worker/index.ts | 10 - .../CyberConnect/src/assets/Context.png | Bin 888 -> 0 bytes .../CyberConnect/src/assets/Foundation.png | Bin 704 -> 0 bytes .../CyberConnect/src/assets/Opensea.png | Bin 1975 -> 0 bytes .../CyberConnect/src/assets/Rarible.png | Bin 861 -> 0 bytes .../CyberConnect/src/assets/logo-white.svg | 1 - packages/plugins/CyberConnect/src/base.tsx | 19 - .../plugins/CyberConnect/src/constants.ts | 8 - packages/plugins/CyberConnect/src/env.d.ts | 1 - .../CyberConnect/src/hooks/useFollowers.ts | 15 - .../CyberConnect/src/locales/en-US.json | 12 - .../plugins/CyberConnect/src/locales/index.ts | 6 - .../CyberConnect/src/locales/ja-JP.json | 12 - .../CyberConnect/src/locales/ko-KR.json | 12 - .../CyberConnect/src/locales/languages.ts | 34 - .../CyberConnect/src/locales/qya-AA.json | 12 - .../CyberConnect/src/locales/zh-CN.json | 12 - .../CyberConnect/src/locales/zh-TW.json | 1 - packages/plugins/CyberConnect/src/messages.ts | 5 - packages/plugins/CyberConnect/src/register.ts | 15 - packages/plugins/CyberConnect/tsconfig.json | 20 - packages/plugins/Debugger/package.json | 36 - .../components/AvatarDecorator.tsx | 33 - .../components/ConnectionContent.tsx | 583 - .../components/ConnectionDialog.tsx | 19 - .../SiteAdaptor/components/ConsoleContent.tsx | 133 - .../SiteAdaptor/components/ConsoleDialog.tsx | 16 - .../src/SiteAdaptor/components/HubContent.tsx | 183 - .../src/SiteAdaptor/components/HubDialog.tsx | 20 - .../components/SearchResultInspector.tsx | 11 - .../src/SiteAdaptor/components/TabContent.tsx | 133 - .../SiteAdaptor/components/WidgetContent.tsx | 29 - .../SiteAdaptor/components/WidgetDialog.tsx | 20 - .../Debugger/src/SiteAdaptor/index.tsx | 181 - .../plugins/Debugger/src/assets/cover.png | Bin 198955 -> 0 bytes packages/plugins/Debugger/src/base.ts | 22 - packages/plugins/Debugger/src/constants.ts | 5 - packages/plugins/Debugger/src/env.d.ts | 1 - .../plugins/Debugger/src/locales/en-US.json | 1 - .../plugins/Debugger/src/locales/index.ts | 6 - .../plugins/Debugger/src/locales/ja-JP.json | 1 - .../plugins/Debugger/src/locales/ko-KR.json | 1 - .../plugins/Debugger/src/locales/languages.ts | 34 - .../plugins/Debugger/src/locales/qya-AA.json | 1 - .../plugins/Debugger/src/locales/zh-CN.json | 1 - .../plugins/Debugger/src/locales/zh-TW.json | 1 - packages/plugins/Debugger/src/messages.ts | 30 - packages/plugins/Debugger/src/register.ts | 11 - packages/plugins/Debugger/tsconfig.json | 19 - packages/plugins/FileService/package.json | 43 - packages/plugins/FileService/src/README.md | 34 - .../src/SiteAdaptor/FileServiceInjection.tsx | 34 - .../src/SiteAdaptor/FileViewer.tsx | 54 - .../src/SiteAdaptor/MainDialog.tsx | 75 - .../FileService/src/SiteAdaptor/Routes.tsx | 18 - .../SiteAdaptor/components/FileBrowser.tsx | 284 - .../src/SiteAdaptor/components/FileChip.tsx | 60 - .../src/SiteAdaptor/components/FileList.tsx | 228 - .../components/Files/DisplayingFile.tsx | 80 - .../components/Files/ManageableFile.tsx | 150 - .../components/Files/SelectableFile.tsx | 66 - .../components/Files/UploadingFile.tsx | 49 - .../src/SiteAdaptor/components/Files/index.ts | 4 - .../SiteAdaptor/components/RouterDialog.tsx | 33 - .../src/SiteAdaptor/components/Terms.tsx | 119 - .../src/SiteAdaptor/components/UploadFile.tsx | 171 - .../src/SiteAdaptor/components/index.tsx | 5 - .../contexts/FileManagement/index.tsx | 183 - .../src/SiteAdaptor/contexts/index.tsx | 1 - .../FileService/src/SiteAdaptor/emitter.ts | 14 - .../FileService/src/SiteAdaptor/index.tsx | 147 - .../modals/ConfirmModal/ConfirmDialog.tsx | 87 - .../SiteAdaptor/modals/ConfirmModal/index.tsx | 37 - .../modals/RenameModal/RenameDialog.tsx | 112 - .../SiteAdaptor/modals/RenameModal/index.tsx | 37 - .../src/SiteAdaptor/modals/index.tsx | 14 - .../src/SiteAdaptor/modals/modals.ts | 6 - .../FileService/src/SiteAdaptor/rpc.ts | 10 - .../FileService/src/SiteAdaptor/storage.ts | 21 - .../FileService/src/Worker/arweave-token.json | 4 - .../plugins/FileService/src/Worker/arweave.ts | 80 - .../FileService/src/Worker/database.ts | 53 - .../plugins/FileService/src/Worker/index.ts | 31 - .../plugins/FileService/src/Worker/ipfs.ts | 72 - .../FileService/src/Worker/remote-signing.ts | 28 - .../plugins/FileService/src/Worker/service.ts | 24 - packages/plugins/FileService/src/base.ts | 26 - packages/plugins/FileService/src/constants.ts | 23 - packages/plugins/FileService/src/env.d.ts | 1 - packages/plugins/FileService/src/helpers.ts | 93 - .../FileService/src/locales/en-US.json | 53 - .../plugins/FileService/src/locales/index.ts | 6 - .../FileService/src/locales/ja-JP.json | 46 - .../FileService/src/locales/ko-KR.json | 52 - .../FileService/src/locales/languages.ts | 34 - .../FileService/src/locales/qya-AA.json | 53 - .../FileService/src/locales/zh-CN.json | 53 - .../FileService/src/locales/zh-TW.json | 17 - packages/plugins/FileService/src/register.ts | 18 - .../plugins/FileService/src/schema-v1.json | 41 - .../plugins/FileService/src/schema-v2.json | 46 - .../plugins/FileService/src/schema-v3.json | 50 - packages/plugins/FileService/src/types.ts | 63 - packages/plugins/FileService/tsconfig.json | 18 - packages/plugins/FriendTech/package.json | 35 - .../src/SiteAdaptor/ActionsContent.tsx | 9 - .../src/SiteAdaptor/FriendTechDialog.tsx | 62 - .../src/SiteAdaptor/FriendTechInjection.tsx | 21 - .../src/SiteAdaptor/FriendTechNameWidget.tsx | 70 - .../FriendTech/src/SiteAdaptor/Main.tsx | 31 - .../FriendTech/src/SiteAdaptor/Order.tsx | 212 - .../FriendTech/src/SiteAdaptor/Routes.tsx | 19 - .../FriendTech/src/SiteAdaptor/UserDetail.tsx | 109 - .../SiteAdaptor/components/HistoryList.tsx | 152 - .../SiteAdaptor/components/HoldingCard.tsx | 187 - .../SiteAdaptor/components/HoldingList.tsx | 104 - .../src/SiteAdaptor/components/KeysTab.tsx | 32 - .../SiteAdaptor/components/RouterDialog.tsx | 72 - .../SiteAdaptor/components/UserProfile.tsx | 214 - .../FriendTech/src/SiteAdaptor/emitter.ts | 7 - .../SiteAdaptor/hooks/useEstimateSellGas.ts | 21 - .../src/SiteAdaptor/hooks/useOwnKeys.ts | 20 - .../src/SiteAdaptor/hooks/useUser.ts | 10 - .../src/SiteAdaptor/hooks/useUserInfo.ts | 11 - .../FriendTech/src/SiteAdaptor/index.tsx | 38 - packages/plugins/FriendTech/src/base.ts | 24 - packages/plugins/FriendTech/src/constants.ts | 19 - packages/plugins/FriendTech/src/env.d.ts | 1 - .../plugins/FriendTech/src/locales/en-US.json | 41 - .../plugins/FriendTech/src/locales/index.ts | 6 - .../plugins/FriendTech/src/locales/ja-JP.json | 1 - .../plugins/FriendTech/src/locales/ko-KR.json | 41 - .../FriendTech/src/locales/languages.ts | 34 - .../FriendTech/src/locales/qya-AA.json | 41 - .../plugins/FriendTech/src/locales/zh-CN.json | 1 - .../plugins/FriendTech/src/locales/zh-TW.json | 1 - packages/plugins/FriendTech/src/register.ts | 11 - packages/plugins/FriendTech/tsconfig.json | 10 - packages/plugins/Gitcoin/package.json | 38 - .../Gitcoin/src/SiteAdaptor/PreviewCard.tsx | 289 - .../SiteAdaptor/gitcoin-grant-detail-style.ts | 455 - .../hooks/useBulkCheckoutWallet.ts | 10 - .../SiteAdaptor/hooks/useDonateCallback.ts | 91 - .../Gitcoin/src/SiteAdaptor/hooks/useGrant.ts | 6 - .../plugins/Gitcoin/src/SiteAdaptor/index.tsx | 77 - .../modals/DonateModal/DonateDialog.tsx | 269 - .../modals/DonateModal/GiveBackSelect.tsx | 23 - .../SiteAdaptor/modals/DonateModal/index.tsx | 23 - .../modals/ResultModal/ResultDialog.tsx | 97 - .../SiteAdaptor/modals/ResultModal/index.tsx | 35 - .../Gitcoin/src/SiteAdaptor/modals/index.tsx | 14 - .../Gitcoin/src/SiteAdaptor/modals/modals.tsx | 6 - packages/plugins/Gitcoin/src/apis/index.ts | 52 - packages/plugins/Gitcoin/src/base.ts | 24 - packages/plugins/Gitcoin/src/constants.ts | 28 - packages/plugins/Gitcoin/src/env.d.ts | 2 - .../plugins/Gitcoin/src/locales/en-US.json | 34 - packages/plugins/Gitcoin/src/locales/index.ts | 6 - .../plugins/Gitcoin/src/locales/ja-JP.json | 30 - .../plugins/Gitcoin/src/locales/ko-KR.json | 34 - .../plugins/Gitcoin/src/locales/languages.ts | 31 - .../plugins/Gitcoin/src/locales/qya-AA.json | 34 - .../plugins/Gitcoin/src/locales/zh-CN.json | 34 - .../plugins/Gitcoin/src/locales/zh-TW.json | 10 - packages/plugins/Gitcoin/src/register.ts | 11 - packages/plugins/Gitcoin/src/utils.ts | 7 - packages/plugins/Gitcoin/tsconfig.json | 15 - packages/plugins/GoPlusSecurity/package.json | 46 - .../src/SiteAdaptor/CheckSecurityDialog.tsx | 112 - .../src/SiteAdaptor/GoPlusGlobalInjection.tsx | 61 - .../components/CheckSecurityConfirmDialog.tsx | 69 - .../components/DefaultPlaceholder.tsx | 17 - .../src/SiteAdaptor/components/Footer.tsx | 33 - .../src/SiteAdaptor/components/NotFound.tsx | 23 - .../src/SiteAdaptor/components/RiskCard.tsx | 75 - .../components/RiskWarningDialog.tsx | 139 - .../src/SiteAdaptor/components/SearchBox.tsx | 144 - .../SiteAdaptor/components/SecurityPanel.tsx | 215 - .../src/SiteAdaptor/components/TokenPanel.tsx | 135 - .../src/SiteAdaptor/constants.tsx | 40 - .../SiteAdaptor/hooks/useSupportedChains.ts | 44 - .../src/SiteAdaptor/icons/Logo.tsx | 30 - .../GoPlusSecurity/src/SiteAdaptor/index.tsx | 52 - .../src/UI/TokenSecurityBoundary.tsx | 52 - .../plugins/GoPlusSecurity/src/UI/index.ts | 1 - .../src/assets/chain-cronos.png | Bin 2064 -> 0 bytes .../GoPlusSecurity/src/assets/chain-heco.png | Bin 6593 -> 0 bytes .../GoPlusSecurity/src/assets/chain-okex.png | Bin 1722 -> 0 bytes .../src/assets/security-icon.png | Bin 1482 -> 0 bytes packages/plugins/GoPlusSecurity/src/base.ts | 21 - .../plugins/GoPlusSecurity/src/constants.ts | 6 - packages/plugins/GoPlusSecurity/src/env.d.ts | 1 - packages/plugins/GoPlusSecurity/src/index.ts | 1 - .../GoPlusSecurity/src/locales/en-US.json | 108 - .../GoPlusSecurity/src/locales/index.ts | 6 - .../GoPlusSecurity/src/locales/ja-JP.json | 108 - .../GoPlusSecurity/src/locales/ko-KR.json | 108 - .../GoPlusSecurity/src/locales/languages.ts | 34 - .../GoPlusSecurity/src/locales/qya-AA.json | 108 - .../GoPlusSecurity/src/locales/zh-CN.json | 108 - .../GoPlusSecurity/src/locales/zh-TW.json | 1 - .../plugins/GoPlusSecurity/src/messages.ts | 34 - .../plugins/GoPlusSecurity/src/register.ts | 11 - .../GoPlusSecurity/src/utils/helper.ts | 6 - packages/plugins/GoPlusSecurity/tsconfig.json | 14 - packages/plugins/Handle/package.json | 29 - .../Handle/src/SiteAdaptor/PluginHeader.tsx | 66 - .../src/SiteAdaptor/SearchResultInspector.tsx | 120 - .../Handle/src/SiteAdaptor/context.tsx | 46 - .../plugins/Handle/src/SiteAdaptor/index.tsx | 41 - packages/plugins/Handle/src/base.ts | 30 - packages/plugins/Handle/src/constants.ts | 36 - packages/plugins/Handle/src/index.ts | 1 - .../plugins/Handle/src/locales/en-US.json | 9 - packages/plugins/Handle/src/locales/index.ts | 6 - .../plugins/Handle/src/locales/ja-JP.json | 1 - .../plugins/Handle/src/locales/ko-KR.json | 9 - .../plugins/Handle/src/locales/languages.ts | 34 - .../plugins/Handle/src/locales/qya-AA.json | 9 - .../plugins/Handle/src/locales/zh-CN.json | 9 - .../plugins/Handle/src/locales/zh-TW.json | 1 - packages/plugins/Handle/src/register.ts | 11 - packages/plugins/Handle/tsconfig.json | 22 - packages/plugins/MaskBox/package.json | 43 - .../SiteAdaptor/components/ArticlesTab.tsx | 64 - .../components/CollectibleCard.tsx | 111 - .../src/SiteAdaptor/components/DetailsTab.tsx | 81 - .../src/SiteAdaptor/components/DrawDialog.tsx | 288 - .../components/DrawResultDialog.tsx | 70 - .../SiteAdaptor/components/PreviewCard.tsx | 406 - .../src/SiteAdaptor/components/TokenCard.tsx | 42 - .../plugins/MaskBox/src/SiteAdaptor/index.tsx | 83 - packages/plugins/MaskBox/src/Worker/index.ts | 10 - packages/plugins/MaskBox/src/apis/index.ts | 2 - .../plugins/MaskBox/src/apis/merkleProof.ts | 20 - packages/plugins/MaskBox/src/apis/storage.ts | 7 - .../MaskBox/src/assets/FallbackImage.svg | 1 - .../plugins/MaskBox/src/assets/bridge.png | Bin 2326 -> 0 bytes .../plugins/MaskBox/src/assets/mask_box.png | Bin 1828 -> 0 bytes packages/plugins/MaskBox/src/base.ts | 25 - packages/plugins/MaskBox/src/constants.ts | 5 - .../MaskBox/src/helpers/formatCountdown.ts | 6 - .../plugins/MaskBox/src/hooks/useContext.ts | 369 - .../MaskBox/src/hooks/useMaskBoxContract.ts | 12 - .../hooks/useMaskBoxCreationSuccessEvent.ts | 43 - .../MaskBox/src/hooks/useMaskBoxInfo.ts | 11 - .../MaskBox/src/hooks/useMaskBoxMetadata.ts | 10 - .../src/hooks/useMaskBoxPurchasedTokens.ts | 11 - .../hooks/useMaskBoxQualificationContract.ts | 9 - .../MaskBox/src/hooks/useMaskBoxStatus.ts | 10 - .../src/hooks/useMaskBoxTokensForSale.ts | 10 - .../MaskBox/src/hooks/useMerkleProof.ts | 17 - .../src/hooks/useOpenBoxTransaction.ts | 44 - .../MaskBox/src/hooks/useQualification.ts | 16 - .../plugins/MaskBox/src/locales/en-US.json | 17 - .../plugins/MaskBox/src/locales/es-ES.json | 1 - .../plugins/MaskBox/src/locales/fa-IR.json | 1 - .../plugins/MaskBox/src/locales/fr-FR.json | 1 - packages/plugins/MaskBox/src/locales/index.ts | 6 - .../plugins/MaskBox/src/locales/it-IT.json | 1 - .../plugins/MaskBox/src/locales/ja-JP.json | 1 - .../plugins/MaskBox/src/locales/ko-KR.json | 17 - .../plugins/MaskBox/src/locales/languages.ts | 68 - .../plugins/MaskBox/src/locales/qya-AA.json | 17 - .../plugins/MaskBox/src/locales/ru-RU.json | 1 - .../plugins/MaskBox/src/locales/zh-CN.json | 17 - .../plugins/MaskBox/src/locales/zh-TW.json | 1 - packages/plugins/MaskBox/src/messages.ts | 4 - packages/plugins/MaskBox/src/register.ts | 15 - packages/plugins/MaskBox/src/type.ts | 74 - packages/plugins/MaskBox/tsconfig.json | 15 - packages/plugins/NextID/package.json | 32 - .../src/SiteAdaptor/VerificationPayload.tsx | 46 - .../plugins/NextID/src/SiteAdaptor/index.tsx | 84 - packages/plugins/NextID/src/base.ts | 42 - .../NextID/src/components/Actions/index.tsx | 93 - .../NextID/src/components/BindDialog.tsx | 97 - .../NextID/src/components/BindPanelUI.tsx | 186 - .../NextID/src/components/NextIdPage.tsx | 104 - packages/plugins/NextID/src/constants.ts | 7 - packages/plugins/NextID/src/env.d.ts | 1 - .../NextID/src/hooks/useBindPayload.ts | 11 - .../NextID/src/hooks/usePersonaSign.ts | 14 - .../plugins/NextID/src/hooks/useWalletSign.ts | 30 - .../plugins/NextID/src/locales/en-US.json | 66 - packages/plugins/NextID/src/locales/index.ts | 6 - .../plugins/NextID/src/locales/ja-JP.json | 66 - .../plugins/NextID/src/locales/ko-KR.json | 65 - .../plugins/NextID/src/locales/languages.ts | 34 - .../plugins/NextID/src/locales/qya-AA.json | 66 - .../plugins/NextID/src/locales/zh-CN.json | 65 - .../plugins/NextID/src/locales/zh-TW.json | 21 - packages/plugins/NextID/src/register.ts | 11 - packages/plugins/NextID/tsconfig.json | 14 - packages/plugins/Pets/package.json | 31 - .../plugins/Pets/src/SiteAdaptor/Animate.tsx | 51 - .../plugins/Pets/src/SiteAdaptor/Drag.tsx | 115 - .../Pets/src/SiteAdaptor/ImageLoader.tsx | 23 - .../Pets/src/SiteAdaptor/ModelView.tsx | 17 - .../Pets/src/SiteAdaptor/NormalNFT.tsx | 98 - .../Pets/src/SiteAdaptor/PetDialog.tsx | 54 - .../Pets/src/SiteAdaptor/PetSetDialog.tsx | 397 - .../Pets/src/SiteAdaptor/PetShareDialog.tsx | 57 - .../src/SiteAdaptor/PetsGlobalInjection.tsx | 21 - .../Pets/src/SiteAdaptor/PreviewBox.tsx | 166 - .../plugins/Pets/src/SiteAdaptor/index.tsx | 52 - packages/plugins/Pets/src/assets/close.png | Bin 1898 -> 0 bytes .../plugins/Pets/src/assets/defaultIcon.png | Bin 2529 -> 0 bytes packages/plugins/Pets/src/assets/glb3D.png | Bin 2213 -> 0 bytes packages/plugins/Pets/src/assets/info.png | Bin 1534 -> 0 bytes packages/plugins/Pets/src/assets/punk2d.png | Bin 70339 -> 0 bytes packages/plugins/Pets/src/base.tsx | 29 - packages/plugins/Pets/src/constants.ts | 26 - packages/plugins/Pets/src/hooks/index.ts | 3 - packages/plugins/Pets/src/hooks/useEssay.ts | 46 - packages/plugins/Pets/src/hooks/useNfts.ts | 58 - packages/plugins/Pets/src/hooks/useUser.ts | 44 - packages/plugins/Pets/src/locales/en-US.json | 26 - packages/plugins/Pets/src/locales/index.ts | 6 - packages/plugins/Pets/src/locales/ja-JP.json | 1 - packages/plugins/Pets/src/locales/ko-KR.json | 26 - .../plugins/Pets/src/locales/languages.ts | 34 - packages/plugins/Pets/src/locales/qya-AA.json | 26 - packages/plugins/Pets/src/locales/zh-CN.json | 26 - packages/plugins/Pets/src/locales/zh-TW.json | 1 - packages/plugins/Pets/src/messages.ts | 13 - packages/plugins/Pets/src/register.ts | 11 - packages/plugins/Pets/src/settings.ts | 4 - packages/plugins/Pets/src/types.ts | 62 - packages/plugins/Pets/tsconfig.json | 14 - packages/plugins/ProfileCard/package.json | 28 - .../SiteAdaptor/AvatarBadge/AvatarBadge.tsx | 36 - .../CollectionProjectAvatarBadge.tsx | 63 - .../AvatarBadge/ProfileAvatarBadge.tsx | 72 - .../ProfileCard/src/SiteAdaptor/index.tsx | 61 - packages/plugins/ProfileCard/src/base.ts | 24 - packages/plugins/ProfileCard/src/constants.ts | 3 - .../ProfileCard/src/locales/en-US.json | 4 - .../plugins/ProfileCard/src/locales/index.ts | 6 - .../ProfileCard/src/locales/ja-JP.json | 1 - .../ProfileCard/src/locales/ko-KR.json | 4 - .../ProfileCard/src/locales/languages.ts | 34 - .../ProfileCard/src/locales/qya-AA.json | 4 - .../ProfileCard/src/locales/zh-CN.json | 4 - .../ProfileCard/src/locales/zh-TW.json | 1 - packages/plugins/ProfileCard/src/register.ts | 13 - packages/plugins/ProfileCard/tsconfig.json | 15 - packages/plugins/RSS3/package.json | 37 - .../RSS3/src/SiteAdaptor/FeedsPage.tsx | 86 - .../FeedCard/CollectibleApprovalCard.tsx | 82 - .../components/FeedCard/CollectibleCard.tsx | 335 - .../components/FeedCard/CommentCard.tsx | 158 - .../components/FeedCard/DonationCard.tsx | 169 - .../components/FeedCard/LensAvatar.tsx | 80 - .../components/FeedCard/LiquidityCard.tsx | 146 - .../components/FeedCard/NoteCard.tsx | 270 - .../components/FeedCard/ProfileCard.tsx | 137 - .../components/FeedCard/ProfileLinkCard.tsx | 154 - .../components/FeedCard/ProfileProxy.tsx | 112 - .../components/FeedCard/ProposeCard.tsx | 74 - .../components/FeedCard/StakingCard.tsx | 105 - .../components/FeedCard/TokenApprovalCard.tsx | 109 - .../components/FeedCard/TokenBridgeCard.tsx | 121 - .../FeedCard/TokenOperationCard.tsx | 151 - .../components/FeedCard/TokenSwapCard.tsx | 124 - .../components/FeedCard/UnknownCard.tsx | 55 - .../components/FeedCard/VoteCard.tsx | 116 - .../components/FeedCard/common.tsx | 77 - .../SiteAdaptor/components/FeedCard/index.tsx | 57 - .../components/FeedCard/useMarkdownStyles.ts | 18 - .../src/SiteAdaptor/components/Slider.tsx | 97 - .../RSS3/src/SiteAdaptor/components/base.tsx | 128 - .../RSS3/src/SiteAdaptor/components/index.ts | 1 - .../RSS3/src/SiteAdaptor/components/share.ts | 212 - .../SiteAdaptor/contexts/FeedOwnerContext.tsx | 13 - .../RSS3/src/SiteAdaptor/contexts/index.ts | 1 - .../RSS3/src/SiteAdaptor/hooks/index.ts | 3 - .../src/SiteAdaptor/hooks/useAddressLabel.ts | 13 - .../RSS3/src/SiteAdaptor/hooks/useFeeds.ts | 18 - .../src/SiteAdaptor/hooks/usePublicationId.ts | 12 - .../plugins/RSS3/src/SiteAdaptor/index.tsx | 138 - .../modals/DetailsModal/DetailDialog.tsx | 123 - .../SiteAdaptor/modals/DetailsModal/index.tsx | 41 - .../RSS3/src/SiteAdaptor/modals/index.tsx | 12 - .../RSS3/src/SiteAdaptor/modals/modals.tsx | 4 - packages/plugins/RSS3/src/base.ts | 22 - packages/plugins/RSS3/src/constants.ts | 6 - packages/plugins/RSS3/src/env.d.ts | 1 - packages/plugins/RSS3/src/locales/en-US.json | 64 - packages/plugins/RSS3/src/locales/index.ts | 6 - packages/plugins/RSS3/src/locales/ja-JP.json | 64 - packages/plugins/RSS3/src/locales/ko-KR.json | 64 - .../plugins/RSS3/src/locales/languages.ts | 31 - packages/plugins/RSS3/src/locales/qya-AA.json | 64 - packages/plugins/RSS3/src/locales/zh-CN.json | 64 - packages/plugins/RSS3/src/locales/zh-TW.json | 1 - packages/plugins/RSS3/src/register.ts | 11 - packages/plugins/RSS3/tsconfig.json | 21 - .../SiteAdaptor/RedPacketConfirmDialog.tsx | 8 +- .../src/SiteAdaptor/RedPacketERC20Form.tsx | 55 +- .../src/SiteAdaptor/RedPacketERC721Form.tsx | 6 - .../SiteAdaptor/RedpacketNftConfirmDialog.tsx | 24 +- .../hooks/useNftRedPacketHistory.ts | 17 +- packages/plugins/Savings/package.json | 34 - .../Savings/src/SiteAdaptor/IconURL.tsx | 6 - .../Savings/src/SiteAdaptor/SavingsDialog.tsx | 183 - .../Savings/src/SiteAdaptor/SavingsForm.tsx | 328 - .../SiteAdaptor/SavingsTable/SavingsRow.tsx | 134 - .../src/SiteAdaptor/SavingsTable/index.tsx | 128 - .../Savings/src/SiteAdaptor/assets/aave.png | Bin 41931 -> 0 bytes .../Savings/src/SiteAdaptor/assets/lido.png | Bin 13047 -> 0 bytes .../Savings/src/SiteAdaptor/hooks/index.ts | 27 - .../plugins/Savings/src/SiteAdaptor/index.tsx | 51 - packages/plugins/Savings/src/base.ts | 42 - packages/plugins/Savings/src/constants.ts | 12 - .../plugins/Savings/src/locales/en-US.json | 25 - packages/plugins/Savings/src/locales/index.ts | 6 - .../plugins/Savings/src/locales/ja-JP.json | 1 - .../plugins/Savings/src/locales/ko-KR.json | 25 - .../plugins/Savings/src/locales/languages.ts | 31 - .../plugins/Savings/src/locales/qya-AA.json | 25 - .../plugins/Savings/src/locales/zh-CN.json | 25 - .../plugins/Savings/src/locales/zh-TW.json | 1 - .../Savings/src/protocols/AAVEProtocol.ts | 237 - .../Savings/src/protocols/LDOProtocol.ts | 118 - packages/plugins/Savings/src/register.ts | 11 - packages/plugins/Savings/src/types.ts | 34 - packages/plugins/Savings/tsconfig.json | 15 - packages/plugins/ScamSniffer/package.json | 30 - .../ScamSniffer/src/SiteAdaptor/ScamAlert.tsx | 134 - .../ScamSniffer/src/SiteAdaptor/index.tsx | 116 - .../plugins/ScamSniffer/src/Worker/index.ts | 10 - .../plugins/ScamSniffer/src/Worker/rpc.ts | 40 - packages/plugins/ScamSniffer/src/base.ts | 23 - packages/plugins/ScamSniffer/src/constants.ts | 7 - packages/plugins/ScamSniffer/src/env.d.ts | 1 - .../ScamSniffer/src/locales/en-US.json | 6 - .../plugins/ScamSniffer/src/locales/index.ts | 6 - .../ScamSniffer/src/locales/ja-JP.json | 1 - .../ScamSniffer/src/locales/ko-KR.json | 6 - .../ScamSniffer/src/locales/languages.ts | 34 - .../ScamSniffer/src/locales/qya-AA.json | 6 - .../ScamSniffer/src/locales/zh-CN.json | 6 - .../ScamSniffer/src/locales/zh-TW.json | 1 - packages/plugins/ScamSniffer/src/messages.ts | 6 - packages/plugins/ScamSniffer/src/register.ts | 15 - packages/plugins/ScamSniffer/tsconfig.json | 10 - packages/plugins/ScamWarning/README.md | 1 - packages/plugins/ScamWarning/package.json | 27 - .../SiteAdaptor/components/PreviewCard.tsx | 59 - .../ScamWarning/src/SiteAdaptor/index.tsx | 47 - packages/plugins/ScamWarning/src/base.ts | 22 - packages/plugins/ScamWarning/src/constants.ts | 5 - packages/plugins/ScamWarning/src/env.d.ts | 1 - .../ScamWarning/src/locales/en-US.json | 6 - .../plugins/ScamWarning/src/locales/index.ts | 6 - .../ScamWarning/src/locales/ja-JP.json | 1 - .../ScamWarning/src/locales/ko-KR.json | 6 - .../ScamWarning/src/locales/languages.ts | 34 - .../ScamWarning/src/locales/qya-AA.json | 6 - .../ScamWarning/src/locales/zh-CN.json | 6 - .../ScamWarning/src/locales/zh-TW.json | 1 - packages/plugins/ScamWarning/src/register.ts | 11 - packages/plugins/ScamWarning/tsconfig.json | 16 - packages/plugins/SmartPay/README.md | 7 - packages/plugins/SmartPay/package.json | 35 - .../components/AccountsManagePopover.tsx | 141 - .../components/AddSmartPayPopover.tsx | 172 - .../components/CreateSuccessDialog.tsx | 73 - .../src/SiteAdaptor/components/Deploy.tsx | 354 - .../components/InEligibilityTips.tsx | 74 - .../SiteAdaptor/components/ManagePopover.tsx | 183 - .../SiteAdaptor/components/ReceiveDialog.tsx | 92 - .../SiteAdaptor/components/RouterDialog.tsx | 128 - .../SiteAdaptor/components/SmartPayBanner.tsx | 38 - .../components/SmartPayContent.tsx | 521 - .../components/SmartPayDescriptionDialog.tsx | 77 - .../SiteAdaptor/components/SmartPayDialog.tsx | 17 - .../SiteAdaptor/components/SmartPayEntry.tsx | 90 - .../SmartPay/src/SiteAdaptor/index.tsx | 50 - .../plugins/SmartPay/src/assets/banner.png | Bin 29886 -> 0 bytes packages/plugins/SmartPay/src/base.ts | 22 - packages/plugins/SmartPay/src/constants.ts | 11 - packages/plugins/SmartPay/src/env.d.ts | 1 - .../plugins/SmartPay/src/hooks/useDeploy.tsx | 178 - .../plugins/SmartPay/src/hooks/useManagers.ts | 14 - .../src/hooks/useQueryQualifications.ts | 83 - .../SmartPay/src/hooks/useSmartPayContext.ts | 20 - .../plugins/SmartPay/src/locales/en-US.json | 67 - .../plugins/SmartPay/src/locales/index.ts | 6 - .../plugins/SmartPay/src/locales/ja-JP.json | 1 - .../plugins/SmartPay/src/locales/ko-KR.json | 67 - .../plugins/SmartPay/src/locales/languages.ts | 34 - .../plugins/SmartPay/src/locales/qya-AA.json | 67 - .../plugins/SmartPay/src/locales/zh-CN.json | 67 - .../plugins/SmartPay/src/locales/zh-TW.json | 1 - packages/plugins/SmartPay/src/message.ts | 21 - packages/plugins/SmartPay/src/register.ts | 11 - packages/plugins/SmartPay/src/type.ts | 13 - packages/plugins/SmartPay/tsconfig.json | 14 - packages/plugins/Snapshot/package.json | 38 - .../src/SiteAdaptor/InformationCard.tsx | 143 - .../Snapshot/src/SiteAdaptor/LoadingCard.tsx | 44 - .../src/SiteAdaptor/LoadingFailCard.tsx | 79 - .../src/SiteAdaptor/PluginDescriptor.tsx | 54 - .../src/SiteAdaptor/PostInspector.tsx | 27 - .../Snapshot/src/SiteAdaptor/ProfileCard.tsx | 30 - .../src/SiteAdaptor/ProfileProposalList.tsx | 281 - .../src/SiteAdaptor/ProfileSpaceHeader.tsx | 132 - .../Snapshot/src/SiteAdaptor/ProfileView.tsx | 146 - .../Snapshot/src/SiteAdaptor/ProgressTab.tsx | 14 - .../Snapshot/src/SiteAdaptor/ProposalTab.tsx | 17 - .../Snapshot/src/SiteAdaptor/ReadmeCard.tsx | 41 - .../Snapshot/src/SiteAdaptor/ResultCard.tsx | 223 - .../Snapshot/src/SiteAdaptor/Snapshot.tsx | 204 - .../Snapshot/src/SiteAdaptor/SnapshotCard.tsx | 75 - .../Snapshot/src/SiteAdaptor/SnapshotTab.tsx | 34 - .../Snapshot/src/SiteAdaptor/SpaceMenu.tsx | 136 - .../src/SiteAdaptor/VoteConfirmDialog.tsx | 110 - .../Snapshot/src/SiteAdaptor/VotesCard.tsx | 207 - .../Snapshot/src/SiteAdaptor/VotingCard.tsx | 194 - .../Snapshot/src/SiteAdaptor/helpers.ts | 18 - .../hooks/useCurrentAccountFollowSpaceList.ts | 13 - .../hooks/useCurrentAccountVote.ts | 13 - .../src/SiteAdaptor/hooks/usePower.ts | 17 - .../src/SiteAdaptor/hooks/useProposal.ts | 16 - .../src/SiteAdaptor/hooks/useProposalList.ts | 11 - .../src/SiteAdaptor/hooks/useResults.ts | 37 - .../src/SiteAdaptor/hooks/useSpace.ts | 10 - .../src/SiteAdaptor/hooks/useVotes.ts | 47 - .../Snapshot/src/SiteAdaptor/index.tsx | 101 - packages/plugins/Snapshot/src/Worker/apis.ts | 183 - packages/plugins/Snapshot/src/Worker/index.ts | 10 - packages/plugins/Snapshot/src/base.ts | 26 - packages/plugins/Snapshot/src/constants.ts | 8 - packages/plugins/Snapshot/src/context.ts | 5 - packages/plugins/Snapshot/src/index.ts | 1 - packages/plugins/Snapshot/src/json2csv.d.ts | 6 - .../plugins/Snapshot/src/locales/en-US.json | 39 - .../plugins/Snapshot/src/locales/index.ts | 6 - .../plugins/Snapshot/src/locales/ja-JP.json | 1 - .../plugins/Snapshot/src/locales/ko-KR.json | 39 - .../plugins/Snapshot/src/locales/languages.ts | 34 - .../plugins/Snapshot/src/locales/qya-AA.json | 39 - .../plugins/Snapshot/src/locales/zh-CN.json | 39 - .../plugins/Snapshot/src/locales/zh-TW.json | 1 - packages/plugins/Snapshot/src/messages.ts | 5 - packages/plugins/Snapshot/src/register.ts | 15 - packages/plugins/Snapshot/src/types.ts | 128 - packages/plugins/Snapshot/src/utils.ts | 43 - packages/plugins/Snapshot/tsconfig.json | 14 - packages/plugins/SwitchLogo/README.md | 7 - packages/plugins/SwitchLogo/package.json | 27 - .../src/SiteAdaptor/SwitchLogoButton.tsx | 100 - .../src/SiteAdaptor/SwitchLogoDialog.tsx | 179 - .../SwitchLogo/src/SiteAdaptor/index.tsx | 33 - packages/plugins/SwitchLogo/src/base.ts | 22 - packages/plugins/SwitchLogo/src/constants.ts | 5 - packages/plugins/SwitchLogo/src/env.d.ts | 1 - packages/plugins/SwitchLogo/src/index.ts | 1 - .../plugins/SwitchLogo/src/locales/en-US.json | 20 - .../plugins/SwitchLogo/src/locales/index.ts | 6 - .../plugins/SwitchLogo/src/locales/ja-JP.json | 1 - .../plugins/SwitchLogo/src/locales/ko-KR.json | 20 - .../SwitchLogo/src/locales/languages.ts | 34 - .../SwitchLogo/src/locales/qya-AA.json | 20 - .../plugins/SwitchLogo/src/locales/zh-CN.json | 20 - .../plugins/SwitchLogo/src/locales/zh-TW.json | 1 - packages/plugins/SwitchLogo/src/register.ts | 11 - packages/plugins/SwitchLogo/tsconfig.json | 9 - packages/plugins/Tips/package.json | 37 - .../components/TipsRealmContent/index.tsx | 135 - .../plugins/Tips/src/SiteAdaptor/index.tsx | 65 - packages/plugins/Tips/src/base.ts | 47 - .../Tips/src/components/NFTSection/index.tsx | 215 - .../src/components/NetworkSection/index.tsx | 32 - .../RecipientSection/RecipientSelect.tsx | 193 - .../src/components/RecipientSection/index.tsx | 52 - .../plugins/Tips/src/components/TipDialog.tsx | 216 - .../Tips/src/components/TipsButton/index.tsx | 131 - .../components/TipsButton/useTipsAccounts.ts | 24 - .../TokenSection/GasSettingsBar.tsx | 54 - .../src/components/TokenSection/index.tsx | 58 - .../components/TokenSection/useGasLimit.ts | 34 - packages/plugins/Tips/src/components/index.ts | 2 - .../src/contexts/TargetRuntimeContext.tsx | 52 - .../Tips/src/contexts/Tip/TipContext.ts | 72 - .../Tips/src/contexts/Tip/TipTaskProvider.tsx | 210 - .../plugins/Tips/src/contexts/Tip/index.ts | 4 - .../plugins/Tips/src/contexts/Tip/type.ts | 1 - .../Tips/src/contexts/Tip/useNftTip.ts | 37 - .../src/contexts/Tip/useRecipientValidate.ts | 34 - .../contexts/Tip/useTipAccountsCompletion.ts | 34 - .../Tips/src/contexts/Tip/useTipValidate.ts | 49 - .../Tips/src/contexts/Tip/useTokenTip.ts | 28 - .../Tips/src/contexts/TipTaskManager.tsx | 61 - packages/plugins/Tips/src/contexts/index.ts | 2 - .../Tips/src/hooks/useProfilePublicKey.ts | 17 - packages/plugins/Tips/src/index.ts | 1 - packages/plugins/Tips/src/locales/en-US.json | 59 - packages/plugins/Tips/src/locales/index.ts | 6 - packages/plugins/Tips/src/locales/ja-JP.json | 58 - packages/plugins/Tips/src/locales/ko-KR.json | 58 - .../plugins/Tips/src/locales/languages.ts | 34 - packages/plugins/Tips/src/locales/qya-AA.json | 58 - packages/plugins/Tips/src/locales/zh-CN.json | 58 - packages/plugins/Tips/src/locales/zh-TW.json | 1 - packages/plugins/Tips/src/messages.ts | 11 - packages/plugins/Tips/src/register.ts | 11 - packages/plugins/Tips/src/storage/index.ts | 68 - packages/plugins/Tips/src/types/index.ts | 2 - packages/plugins/Tips/src/types/tip.ts | 8 - packages/plugins/Tips/src/types/validation.ts | 1 - packages/plugins/Tips/tsconfig.json | 14 - packages/plugins/Trader/package.json | 38 - packages/plugins/Trader/src/README.md | 32 - .../Trader/src/SiteAdaptor/cashTag.tsx | 26 - .../plugins/Trader/src/SiteAdaptor/index.tsx | 111 - .../src/SiteAdaptor/trader/ExchangeDialog.tsx | 173 - .../SiteAdaptor/trader/ExchangeInjection.tsx | 30 - .../src/SiteAdaptor/trending/CoinIcon.tsx | 11 - .../SiteAdaptor/trending/CoinMarketPanel.tsx | 21 - .../SiteAdaptor/trending/CoinMarketTable.tsx | 187 - .../trending/FailedTrendingView.tsx | 35 - .../trending/NonFungibleTickersTable.tsx | 308 - .../SiteAdaptor/trending/PluginDescriptor.tsx | 56 - .../src/SiteAdaptor/trending/PriceChart.tsx | 98 - .../trending/PriceChartDaysControl.tsx | 56 - .../src/SiteAdaptor/trending/TagInspector.tsx | 103 - .../src/SiteAdaptor/trending/TickersTable.tsx | 183 - .../src/SiteAdaptor/trending/TrendingCard.tsx | 29 - .../SiteAdaptor/trending/TrendingPopper.tsx | 94 - .../src/SiteAdaptor/trending/TrendingView.tsx | 421 - .../SiteAdaptor/trending/TrendingViewDeck.tsx | 464 - .../trending/TrendingViewDescriptor.tsx | 91 - .../trending/TrendingViewSkeleton.tsx | 63 - .../src/SiteAdaptor/trending/context.tsx | 37 - packages/plugins/Trader/src/Worker/index.ts | 10 - packages/plugins/Trader/src/apis/index.ts | 121 - packages/plugins/Trader/src/assets/swap.png | Bin 1153 -> 0 bytes packages/plugins/Trader/src/base.ts | 23 - packages/plugins/Trader/src/config.ts | 25 - .../plugins/Trader/src/constants/index.ts | 1 - .../plugins/Trader/src/constants/trending.ts | 3 - packages/plugins/Trader/src/helpers/index.ts | 1 - .../Trader/src/helpers/resolveDaysName.ts | 8 - packages/plugins/Trader/src/index.ts | 3 - .../plugins/Trader/src/locales/en-US.json | 141 - packages/plugins/Trader/src/locales/index.ts | 6 - .../plugins/Trader/src/locales/ja-JP.json | 1 - .../plugins/Trader/src/locales/ko-KR.json | 139 - .../plugins/Trader/src/locales/languages.ts | 34 - .../plugins/Trader/src/locales/qya-AA.json | 140 - .../plugins/Trader/src/locales/zh-CN.json | 139 - .../plugins/Trader/src/locales/zh-TW.json | 1 - packages/plugins/Trader/src/messages.ts | 40 - packages/plugins/Trader/src/register.ts | 15 - .../Trader/src/trending/usePriceStats.ts | 34 - .../Trader/src/trending/useTrending.ts | 150 - packages/plugins/Trader/src/types/index.ts | 2 - packages/plugins/Trader/src/types/trader.ts | 22 - packages/plugins/Trader/src/types/trending.ts | 13 - packages/plugins/Trader/tsconfig.json | 17 - packages/plugins/Transak/package.json | 30 - .../src/SiteAdaptor/BuyTokenDialog.tsx | 79 - .../SiteAdaptor/BuyTokenGlobalInjection.tsx | 16 - .../plugins/Transak/src/SiteAdaptor/index.tsx | 52 - .../plugins/Transak/src/assets/fiat_ramp.png | Bin 798 -> 0 bytes packages/plugins/Transak/src/base.ts | 21 - packages/plugins/Transak/src/constants.ts | 8 - packages/plugins/Transak/src/env.d.ts | 1 - .../src/hooks/useTransakAllowanceCoin.ts | 21 - .../Transak/src/hooks/useTransakURL.ts | 42 - packages/plugins/Transak/src/index.ts | 3 - .../plugins/Transak/src/locales/en-US.json | 4 - packages/plugins/Transak/src/locales/index.ts | 6 - .../plugins/Transak/src/locales/ja-JP.json | 1 - .../plugins/Transak/src/locales/ko-KR.json | 4 - .../plugins/Transak/src/locales/languages.ts | 34 - .../plugins/Transak/src/locales/qya-AA.json | 4 - .../plugins/Transak/src/locales/zh-CN.json | 4 - .../plugins/Transak/src/locales/zh-TW.json | 1 - packages/plugins/Transak/src/messages.ts | 19 - packages/plugins/Transak/src/register.ts | 11 - packages/plugins/Transak/src/types.ts | 20 - packages/plugins/Transak/tsconfig.json | 14 - packages/plugins/VCent/package.json | 30 - packages/plugins/VCent/src/README.md | 32 - .../VCent/src/SiteAdaptor/TweetDialog.tsx | 94 - .../plugins/VCent/src/SiteAdaptor/index.tsx | 49 - packages/plugins/VCent/src/Worker/index.ts | 10 - packages/plugins/VCent/src/apis/index.ts | 27 - packages/plugins/VCent/src/base.ts | 21 - packages/plugins/VCent/src/constants.ts | 12 - packages/plugins/VCent/src/env.d.ts | 1 - packages/plugins/VCent/src/locales/en-US.json | 6 - packages/plugins/VCent/src/locales/index.ts | 6 - packages/plugins/VCent/src/locales/ja-JP.json | 1 - packages/plugins/VCent/src/locales/ko-KR.json | 6 - .../plugins/VCent/src/locales/languages.ts | 34 - .../plugins/VCent/src/locales/qya-AA.json | 6 - packages/plugins/VCent/src/locales/zh-CN.json | 6 - packages/plugins/VCent/src/locales/zh-TW.json | 1 - packages/plugins/VCent/src/messages.ts | 9 - packages/plugins/VCent/src/register.ts | 16 - packages/plugins/VCent/tsconfig.json | 16 - packages/plugins/Web3Profile/package.json | 39 - .../Web3ProfileGlobalInjection.tsx | 46 - .../src/SiteAdaptor/assets/Lens.png | Bin 1371 -> 0 bytes .../src/SiteAdaptor/assets/TwitterXRound.svg | 4 - .../components/Farcaster/FarcasterBadge.tsx | 84 - .../components/Farcaster/FarcasterList.tsx | 121 - .../components/Farcaster/FarcasterPopup.tsx | 62 - .../components/Lens/FollowLensDialog.tsx | 453 - .../components/Lens/HandlerDescription.tsx | 125 - .../SiteAdaptor/components/Lens/LensBadge.tsx | 84 - .../SiteAdaptor/components/Lens/LensList.tsx | 212 - .../SiteAdaptor/components/Lens/LensPopup.tsx | 82 - .../SiteAdaptor/components/ProfileCard.tsx | 255 - .../SiteAdaptor/components/ProfilePopup.tsx | 220 - .../components/Web3ProfileDialog.tsx | 201 - .../Web3Profile/src/SiteAdaptor/context.ts | 11 - .../Web3Profile/src/SiteAdaptor/emitter.ts | 43 - .../hooks/ConfettiExplosion/Confetto.ts | 47 - .../hooks/ConfettiExplosion/Sequin.ts | 34 - .../hooks/ConfettiExplosion/index.tsx | 135 - .../hooks/ConfettiExplosion/utils.ts | 33 - .../Farcaster/useControlFarcasterPopup.ts | 62 - .../hooks/Lens/useControlLensPopup.ts | 62 - .../src/SiteAdaptor/hooks/Lens/useFollow.ts | 185 - .../hooks/Lens/useQueryAuthenticate.ts | 62 - .../src/SiteAdaptor/hooks/Lens/useUnfollow.ts | 162 - .../hooks/Lens/useUpdateFollowingStatus.ts | 26 - .../src/SiteAdaptor/hooks/index.ts | 1 - .../src/SiteAdaptor/hooks/usePersona.ts | 15 - .../Web3Profile/src/SiteAdaptor/index.tsx | 126 - .../Web3Profile/src/SiteAdaptor/types.ts | 16 - packages/plugins/Web3Profile/src/base.ts | 21 - packages/plugins/Web3Profile/src/constants.ts | 5 - packages/plugins/Web3Profile/src/env.d.ts | 1 - .../Web3Profile/src/locales/en-US.json | 95 - .../plugins/Web3Profile/src/locales/index.ts | 6 - .../Web3Profile/src/locales/ja-JP.json | 1 - .../Web3Profile/src/locales/ko-KR.json | 95 - .../Web3Profile/src/locales/languages.ts | 34 - .../Web3Profile/src/locales/qya-AA.json | 95 - .../Web3Profile/src/locales/zh-CN.json | 92 - .../Web3Profile/src/locales/zh-TW.json | 1 - packages/plugins/Web3Profile/src/register.ts | 11 - packages/plugins/Web3Profile/src/utils.ts | 13 - packages/plugins/Web3Profile/tsconfig.json | 15 - packages/plugins/template/README.md | 7 - packages/plugins/template/package.json | 22 - .../template/src/SiteAdaptor/index.tsx | 8 - packages/plugins/template/src/base.ts | 22 - packages/plugins/template/src/constants.ts | 5 - packages/plugins/template/src/env.d.ts | 1 - .../plugins/template/src/locales/en-US.json | 4 - .../plugins/template/src/locales/index.ts | 6 - .../plugins/template/src/locales/ja-JP.json | 4 - .../plugins/template/src/locales/ko-KR.json | 4 - .../plugins/template/src/locales/languages.ts | 36 - .../plugins/template/src/locales/qya-AA.json | 4 - .../plugins/template/src/locales/zh-CN.json | 4 - .../plugins/template/src/locales/zh-TW.json | 1 - packages/plugins/template/src/register.ts | 11 - packages/plugins/template/tsconfig.json | 10 - packages/plugins/tsconfig.json | 35 +- packages/scripts/src/bin/build.ts | 4 - packages/scripts/src/extension/ci.ts | 50 - packages/scripts/src/extension/dotenv.ts | 45 - packages/scripts/src/extension/flags.ts | 6 - packages/scripts/src/extension/gulp-zip.d.ts | 13 - packages/scripts/src/extension/index.ts | 2 - packages/scripts/src/extension/normal.ts | 70 - packages/scripts/src/extension/web-ext.d.ts | 29 - packages/scripts/src/index.ts | 1 - packages/shared-base/package.json | 1 - packages/shared-base/src/Histories/Popups.ts | 14 - packages/shared-base/src/Histories/index.ts | 1 - packages/shared-base/src/KVStorage/index.ts | 10 - .../src/Messages/CrossIsolationEvents.ts | 6 - packages/shared-base/src/Messages/Events.ts | 19 - packages/shared-base/src/index.ts | 1 - packages/shared-base/src/serializer/index.ts | 17 - packages/shared-base/src/types/PluginID.ts | 23 - packages/shared-base/tsconfig.json | 1 - .../AssetsManagement/CollectionHeader.tsx | 5 +- .../UI/components/CountryCodePicker/index.tsx | 145 - .../src/UI/components/FileFrame/index.tsx | 92 - .../src/UI/components/NetworkTab/index.tsx | 20 +- .../src/UI/components/PersonaGuard/index.tsx | 62 - .../UI/components/PhoneNumberField/index.tsx | 60 - .../src/UI/components/PluginGuide/index.tsx | 220 - .../SelectGasSettingsToolbar/index.tsx | 59 +- .../UI/components/SocialAccountList/utils.tsx | 2 +- .../src/UI/components/SocialIcon/index.tsx | 22 - .../SourceProviderSwitcher/index.tsx | 55 - .../UI/components/SourceSwitcher/index.tsx | 63 - .../UI/components/TokenAmountPanel/index.tsx | 217 - .../UI/components/TokenSecurity/Common.tsx | 35 - .../src/UI/components/TokenSecurity/index.tsx | 105 - .../UI/components/UploadDropArea/index.tsx | 131 - .../WalletConnectedBoundary/index.tsx | 7 +- .../WalletSettingsCardUI.tsx | 75 - .../components/WalletSettingsCard/index.tsx | 52 - .../PluginVerifiedWalletStatusBar.tsx | 9 +- .../WalletStatusBar/PluginWalletStatusBar.tsx | 6 +- .../WalletStatusBar/hooks/useWalletName.ts | 16 +- .../UI/components/WalletStatusBox/index.tsx | 5 - packages/shared/src/UI/components/index.ts | 20 +- .../contexts/components/ApproveMaskDialog.tsx | 4 +- .../ApplicationSettingPluginSwitch.tsx | 17 +- .../SelectProviderModal/SelectProvider.tsx | 12 +- packages/shared/src/constants.tsx | 41 - packages/shared/src/hooks/index.ts | 6 - .../shared/src/hooks/useAvailableBalance.ts | 19 +- .../src/hooks/useCollectionByTwitterHandle.ts | 15 - packages/shared/src/hooks/useDimension.ts | 18 - .../shared/src/hooks/useGasCurrencyMenu.tsx | 43 +- .../shared/src/hooks/useLineChart/index.ts | 247 - .../hooks/useLineChart/tests/utils.test.ts | 17 - .../shared/src/hooks/useLineChart/utils.ts | 16 - .../src/hooks/useOpenApplicationSettings.ts | 15 - packages/shared/src/hooks/useParamTab.ts | 21 - .../shared/src/hooks/usePriceLineChart.ts | 35 - packages/shared/src/hooks/useTokenSecurity.ts | 14 - .../shared/src/utils/identifierSelector.ts | 4 - packages/shared/src/utils/index.ts | 2 - .../shared/src/utils/resolveValueToSearch.ts | 7 - packages/web3-constants/evm/gopluslabs.json | 29 - packages/web3-constants/evm/smart-pay.json | 64 - packages/web3-contracts/abis/Airdrop.json | 209 - packages/web3-contracts/abis/AirdropV2.json | 132 - packages/web3-contracts/abis/CryptoPunks.json | 21 - packages/web3-contracts/abis/FriendTech.json | 170 - packages/web3-hooks/base/src/index.ts | 3 - packages/web3-hooks/base/src/useContext.tsx | 23 +- .../src/useSnapshotSpacesByTwitterHandle.ts | 14 - packages/web3-hooks/base/src/useWallet.ts | 18 - packages/web3-hooks/base/src/useWallets.ts | 44 - packages/web3-providers/src/Airdrop/index.ts | 41 - .../src/FriendTech/constants.ts | 1 - .../web3-providers/src/FriendTech/index.ts | 68 - .../src/GoPlusLabs/constants.ts | 3 - .../web3-providers/src/GoPlusLabs/index.ts | 253 - .../web3-providers/src/GoPlusLabs/rules.ts | 310 - .../web3-providers/src/GoPlusLabs/types.ts | 151 - .../src/SmartPay/apis/AbstractAccountAPI.ts | 146 - .../src/SmartPay/apis/BundlerAPI.ts | 87 - .../src/SmartPay/apis/FunderAPI.ts | 72 - .../src/SmartPay/apis/OwnerAPI.ts | 186 - .../web3-providers/src/SmartPay/constants.ts | 5 - packages/web3-providers/src/SmartPay/index.ts | 3 - .../src/SmartPay/libs/ContractWallet.ts | 82 - .../src/SmartPay/libs/Create2Factory.ts | 32 - .../src/SmartPay/libs/DepositPaymaster.ts | 31 - .../src/SmartPay/libs/UserTransaction.ts | 370 - .../src/Snapshot/apis/provider.ts | 151 - .../src/Snapshot/apis/search.ts | 38 - packages/web3-providers/src/Snapshot/index.ts | 2 - .../src/Web3/EVM/apis/ComposerAPI.ts | 2 - .../Web3/EVM/apis/ConnectionReadonlyAPI.ts | 7 - .../src/Web3/EVM/apis/ContractReadonlyAPI.ts | 18 - .../src/Web3/EVM/apis/HubFungibleAPI.ts | 3 - .../src/Web3/EVM/apis/HubNonFungibleAPI.ts | 4 - .../src/Web3/EVM/interceptors/MaskWallet.ts | 104 - .../src/Web3/EVM/interceptors/Popups.ts | 182 - .../src/Web3/EVM/middleware/Interceptor.ts | 18 +- .../Web3/EVM/middleware/RecentTransaction.ts | 84 - .../Web3/EVM/providers/BaseContractWallet.ts | 19 - .../src/Web3/EVM/providers/MaskWallet.ts | 234 - .../src/Web3/EVM/providers/index.ts | 5 +- .../src/Web3/EVM/state/Message.ts | 18 +- .../Web3/EVM/state/TransactionFormatter.ts | 4 - .../EVM/state/TransactionFormatter/abi.ts | 8 - .../descriptors/Airdrop.ts | 39 - .../descriptors/MaskBox.ts | 54 - packages/web3-providers/src/entry-types.ts | 4 - packages/web3-providers/src/entry.ts | 23 - .../helpers/getAllMaskDappContractInfo.tsx | 13 +- packages/web3-providers/src/types/Security.ts | 111 - .../evm/src/constants/constants.ts | 47 - .../web3-shared/evm/src/helpers/address.ts | 6 - .../web3-shared/evm/src/libs/PayloadEditor.ts | 20 +- packages/xcode/.gitignore | 90 - .../Mask Network Extension/Info.plist | 13 - .../Mask_Network_Extension.entitlements | 10 - .../SafariWebExtensionHandler.swift | 16 - .../Mask Network.xcodeproj/project.pbxproj | 633 - .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../Mask Network/AppDelegate.swift | 19 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/128x128.png | Bin 1347 -> 0 bytes .../AppIcon.appiconset/16x16.png | Bin 291 -> 0 bytes .../AppIcon.appiconset/256x256 1.png | Bin 2476 -> 0 bytes .../AppIcon.appiconset/256x256.png | Bin 2476 -> 0 bytes .../AppIcon.appiconset/Contents.json | 62 - .../Assets.xcassets/Contents.json | 6 - .../LargeIcon.imageset/Contents.json | 20 - .../Mask Network/Base.lproj/Main.html | 15 - .../Mask Network/Base.lproj/Main.storyboard | 124 - .../Mask Network/Mask Network.entitlements | 12 - .../Mask Network/Mask_Network.entitlements | 10 - .../Mask Network/Resources/Script.js | 3 - .../Mask Network/Resources/Style.css | 22 - .../Mask Network/ViewController.swift | 41 - packages/xcode/package.json | 6 - patches/@splinetool__runtime@0.9.342.patch | 10930 ------- pnpm-lock.yaml | 27025 ++++++---------- tsconfig.json | 36 - 2089 files changed, 9218 insertions(+), 163220 deletions(-) delete mode 100644 packages/backup-format/README.md delete mode 100644 packages/backup-format/package.json delete mode 100644 packages/backup-format/src/BackupErrors.ts delete mode 100644 packages/backup-format/src/container/index.ts delete mode 100644 packages/backup-format/src/index.ts delete mode 100644 packages/backup-format/src/normalize/index.ts delete mode 100644 packages/backup-format/src/normalize/type.ts delete mode 100644 packages/backup-format/src/utils/backupPreview.ts delete mode 100644 packages/backup-format/src/utils/hex2buffer.ts delete mode 100644 packages/backup-format/src/version-0/index.ts delete mode 100644 packages/backup-format/src/version-1/index.ts delete mode 100644 packages/backup-format/src/version-2/index.ts delete mode 100644 packages/backup-format/src/version-3/index.ts delete mode 100644 packages/backup-format/tests/encryption.ts delete mode 100644 packages/backup-format/tsconfig.json delete mode 100644 packages/backup-format/tsconfig.tests.json delete mode 100644 packages/icons/plugins/Approval.svg delete mode 100644 packages/icons/plugins/ArtBlocks.png delete mode 100644 packages/icons/plugins/CrossBridge.png delete mode 100644 packages/icons/plugins/CyberConnect.dark.svg delete mode 100644 packages/icons/plugins/CyberConnect.light.svg delete mode 100644 packages/icons/plugins/FileService.svg delete mode 100644 packages/icons/plugins/FindTruman.png delete mode 100644 packages/icons/plugins/FriendTech.svg delete mode 100644 packages/icons/plugins/Gitcoin.dark.svg delete mode 100644 packages/icons/plugins/Gitcoin.light.svg delete mode 100644 packages/icons/plugins/GoodGhosting.dark.svg delete mode 100644 packages/icons/plugins/GoodGhosting.light.svg delete mode 100644 packages/icons/plugins/Markets.png delete mode 100644 packages/icons/plugins/PoolTogether.png delete mode 100644 packages/icons/plugins/Savings.svg delete mode 100644 packages/icons/plugins/Snapshot.svg delete mode 100644 packages/icons/plugins/TipCoin.svg delete mode 100644 packages/icons/plugins/Transak.png delete mode 100644 packages/icons/plugins/Unstoppable.svg delete mode 100644 packages/icons/rss3/AchievementBurn.svg delete mode 100644 packages/icons/rss3/AchievementReceive.svg delete mode 100644 packages/icons/rss3/ApprovalApprove.svg delete mode 100644 packages/icons/rss3/CollectibleApprove.svg delete mode 100644 packages/icons/rss3/CollectibleBurn.svg delete mode 100644 packages/icons/rss3/CollectibleIn.svg delete mode 100644 packages/icons/rss3/CollectibleMint.svg delete mode 100644 packages/icons/rss3/CollectibleOut.svg delete mode 100644 packages/icons/rss3/DonationDonate.svg delete mode 100644 packages/icons/rss3/DonationLaunch.svg delete mode 100644 packages/icons/rss3/Follow.svg delete mode 100644 packages/icons/rss3/GovernancePropose.svg delete mode 100644 packages/icons/rss3/GovernanceVote.svg delete mode 100644 packages/icons/rss3/NoteBurn.svg delete mode 100644 packages/icons/rss3/NoteCreate.svg delete mode 100644 packages/icons/rss3/NoteEdit.svg delete mode 100644 packages/icons/rss3/NoteLink.svg delete mode 100644 packages/icons/rss3/NoteMint.svg delete mode 100644 packages/icons/rss3/ProfileBurn.svg delete mode 100644 packages/icons/rss3/ProfileCreate.svg delete mode 100644 packages/icons/rss3/ProfileLink.svg delete mode 100644 packages/icons/rss3/ProfileProxy.svg delete mode 100644 packages/icons/rss3/ProfileUpdate.svg delete mode 100644 packages/icons/rss3/RSS3Link.svg delete mode 100644 packages/icons/rss3/TokenBridge.svg delete mode 100644 packages/icons/rss3/TokenBurn.svg delete mode 100644 packages/icons/rss3/TokenIn.svg delete mode 100644 packages/icons/rss3/TokenLiquidity.svg delete mode 100644 packages/icons/rss3/TokenMint.svg delete mode 100644 packages/icons/rss3/TokenOut.svg delete mode 100644 packages/icons/rss3/TokenStake.svg delete mode 100644 packages/icons/rss3/TokenSwap.svg delete mode 100644 packages/icons/rss3/TokenUnstake.svg delete mode 100644 packages/icons/rss3/Unfollow.svg delete mode 100644 packages/icons/rss3/UnknownBurn.svg delete mode 100644 packages/icons/rss3/UnknownCancel.svg delete mode 100644 packages/icons/rss3/UnknownIn.svg delete mode 100644 packages/icons/rss3/UnknownOut.svg delete mode 100644 packages/mask-sdk/README.md delete mode 100644 packages/mask-sdk/gen-message.mjs delete mode 100644 packages/mask-sdk/global.d.ts delete mode 100644 packages/mask-sdk/main/bridge.ts delete mode 100644 packages/mask-sdk/main/index.ts delete mode 100644 packages/mask-sdk/main/tsconfig.json delete mode 100644 packages/mask-sdk/main/wallet.ts delete mode 100644 packages/mask-sdk/package.json delete mode 100644 packages/mask-sdk/public-api/index.ts delete mode 100644 packages/mask-sdk/public-api/mask-login.ts delete mode 100644 packages/mask-sdk/public-api/mask-persona.ts delete mode 100644 packages/mask-sdk/public-api/mask-wallet.ts delete mode 100644 packages/mask-sdk/public-api/tsconfig.json delete mode 100644 packages/mask-sdk/rollup.config.js delete mode 100644 packages/mask-sdk/server/index.ts delete mode 100644 packages/mask-sdk/server/tsconfig.json delete mode 100644 packages/mask-sdk/shared/channel.ts delete mode 100644 packages/mask-sdk/shared/error-generated.ts delete mode 100644 packages/mask-sdk/shared/error.ts delete mode 100644 packages/mask-sdk/shared/index.ts delete mode 100644 packages/mask-sdk/shared/messages.txt delete mode 100644 packages/mask-sdk/shared/serializer.ts delete mode 100644 packages/mask-sdk/shared/tsconfig.json delete mode 100644 packages/mask-sdk/shared/types.ts delete mode 100644 packages/mask/.webpack/clean-hmr.ts delete mode 100644 packages/mask/.webpack/config.ts delete mode 100644 packages/mask/.webpack/flags.ts delete mode 100644 packages/mask/.webpack/git-info.ts delete mode 100644 packages/mask/.webpack/loaders/fix-regenerator-runtime.js delete mode 100644 packages/mask/.webpack/manifest/manifest-mv3.json delete mode 100644 packages/mask/.webpack/manifest/manifest.json delete mode 100644 packages/mask/.webpack/package-overrides/null.mjs delete mode 100644 packages/mask/.webpack/plugins/ProfilingPlugin.ts delete mode 100644 packages/mask/.webpack/plugins/TrustedTypesPlugin.ts delete mode 100644 packages/mask/.webpack/plugins/manifest.ts delete mode 100644 packages/mask/.webpack/popups.html delete mode 100644 packages/mask/.webpack/swap.html delete mode 100644 packages/mask/.webpack/template.html delete mode 100644 packages/mask/.webpack/tsconfig.json delete mode 100644 packages/mask/.webpack/utils.ts delete mode 100644 packages/mask/.webpack/webpack.config.js delete mode 100644 packages/mask/.webpack/webpack.config.ts delete mode 100644 packages/mask/background/database/avatar-cache/avatar.ts delete mode 100644 packages/mask/background/database/avatar-cache/cleanup.ts delete mode 100644 packages/mask/background/database/avatar-cache/db.ts delete mode 100644 packages/mask/background/database/persona/consistency.ts delete mode 100644 packages/mask/background/database/persona/db.ts delete mode 100644 packages/mask/background/database/persona/helper.ts delete mode 100644 packages/mask/background/database/persona/type.ts delete mode 100644 packages/mask/background/database/persona/web.ts delete mode 100644 packages/mask/background/database/plugin-db/base.ts delete mode 100644 packages/mask/background/database/plugin-db/index.ts delete mode 100644 packages/mask/background/database/plugin-db/wrap-plugin-database.ts delete mode 100644 packages/mask/background/database/post/dbType.ts delete mode 100644 packages/mask/background/database/post/helper.ts delete mode 100644 packages/mask/background/database/post/index.ts delete mode 100644 packages/mask/background/database/post/type.ts delete mode 100644 packages/mask/background/database/post/web.ts delete mode 100644 packages/mask/background/database/utils/openDB.ts delete mode 100644 packages/mask/background/env.d.ts delete mode 100644 packages/mask/background/initialization/async-setup.ts delete mode 100644 packages/mask/background/initialization/entry.ts delete mode 100644 packages/mask/background/initialization/fetch.ts delete mode 100644 packages/mask/background/initialization/kv-storage.ts delete mode 100644 packages/mask/background/initialization/mv2-entry.ts delete mode 100644 packages/mask/background/initialization/mv3-entry.ts delete mode 100644 packages/mask/background/initialization/post-async-setup.ts delete mode 100644 packages/mask/background/initialization/setup.ts delete mode 100644 packages/mask/background/initialization/storage-setup.ts delete mode 100644 packages/mask/background/network/queryPostKey.ts delete mode 100644 packages/mask/background/services/__utils__/convert.ts delete mode 100644 packages/mask/background/services/backup/create.ts delete mode 100644 packages/mask/background/services/backup/index.ts delete mode 100644 packages/mask/background/services/backup/internal_create.ts delete mode 100644 packages/mask/background/services/backup/internal_restore.ts delete mode 100644 packages/mask/background/services/backup/internal_wallet_backup.ts delete mode 100644 packages/mask/background/services/backup/internal_wallet_restore.ts delete mode 100644 packages/mask/background/services/backup/persona.ts delete mode 100644 packages/mask/background/services/backup/restore.ts delete mode 100644 packages/mask/background/services/crypto/appendEncryption.ts delete mode 100644 packages/mask/background/services/crypto/comment.ts delete mode 100644 packages/mask/background/services/crypto/decryption.ts delete mode 100644 packages/mask/background/services/crypto/encryption.ts delete mode 100644 packages/mask/background/services/crypto/index.ts delete mode 100644 packages/mask/background/services/crypto/recipients.ts delete mode 100644 packages/mask/background/services/crypto/steganography.ts delete mode 100644 packages/mask/background/services/helper/i18n-cache-query-list.ts delete mode 100644 packages/mask/background/services/helper/i18n-cache-query.ts delete mode 100644 packages/mask/background/services/helper/index.ts delete mode 100644 packages/mask/background/services/helper/popup-opener.ts delete mode 100644 packages/mask/background/services/helper/request-permission.ts delete mode 100644 packages/mask/background/services/helper/sandboxed.ts delete mode 100644 packages/mask/background/services/helper/short-link-resolver.ts delete mode 100644 packages/mask/background/services/helper/tabs.ts delete mode 100644 packages/mask/background/services/helper/telemetry-id.ts delete mode 100644 packages/mask/background/services/identity/avatar/query.ts delete mode 100644 packages/mask/background/services/identity/index.ts delete mode 100644 packages/mask/background/services/identity/persona/avatar.ts delete mode 100644 packages/mask/background/services/identity/persona/create.ts delete mode 100644 packages/mask/background/services/identity/persona/query.ts delete mode 100644 packages/mask/background/services/identity/persona/sign.ts delete mode 100644 packages/mask/background/services/identity/persona/update.ts delete mode 100644 packages/mask/background/services/identity/persona/utils.ts delete mode 100644 packages/mask/background/services/identity/profile/query.ts delete mode 100644 packages/mask/background/services/identity/profile/update.ts delete mode 100644 packages/mask/background/services/identity/relation/create.ts delete mode 100644 packages/mask/background/services/identity/relation/query.ts delete mode 100644 packages/mask/background/services/identity/relation/update.ts delete mode 100644 packages/mask/background/services/settings/index.ts delete mode 100644 packages/mask/background/services/settings/kv-storage.ts delete mode 100644 packages/mask/background/services/settings/old-settings-accessor.ts delete mode 100644 packages/mask/background/services/setup.ts delete mode 100644 packages/mask/background/services/site-adaptors/connect.ts delete mode 100644 packages/mask/background/services/site-adaptors/index.ts delete mode 100644 packages/mask/background/services/site-adaptors/sdk.ts delete mode 100644 packages/mask/background/services/types.ts delete mode 100644 packages/mask/background/services/wallet/database/Plugin.db.ts delete mode 100644 packages/mask/background/services/wallet/database/Wallet.db.ts delete mode 100644 packages/mask/background/services/wallet/database/types.ts delete mode 100644 packages/mask/background/services/wallet/services/connect.ts delete mode 100644 packages/mask/background/services/wallet/services/helpers.ts delete mode 100644 packages/mask/background/services/wallet/services/index.ts delete mode 100644 packages/mask/background/services/wallet/services/legacyWallet.ts delete mode 100644 packages/mask/background/services/wallet/services/maskwallet/index.ts delete mode 100644 packages/mask/background/services/wallet/services/rpc.ts delete mode 100644 packages/mask/background/services/wallet/services/sdk.ts delete mode 100644 packages/mask/background/services/wallet/services/select.ts delete mode 100644 packages/mask/background/services/wallet/services/send.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/database/index.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/database/locker.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/database/secret.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/database/wallet.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/index.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/locker.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/password.ts delete mode 100644 packages/mask/background/services/wallet/services/wallet/type.ts delete mode 100644 packages/mask/background/tasks/Cancellable/CleanProfileAndAvatar.ts delete mode 100644 packages/mask/background/tasks/Cancellable/FetchRemoteFlags.ts delete mode 100644 packages/mask/background/tasks/Cancellable/InjectContentScripts_declarative.ts delete mode 100644 packages/mask/background/tasks/Cancellable/InjectContentScripts_imperative.ts delete mode 100644 packages/mask/background/tasks/Cancellable/SettingsListener.ts delete mode 100644 packages/mask/background/tasks/Cancellable/StartPluginHost.ts delete mode 100644 packages/mask/background/tasks/Cancellable/StartSandboxedPluginHost.ts delete mode 100644 packages/mask/background/tasks/Cancellable/WalletAutoLock.ts delete mode 100644 packages/mask/background/tasks/NotCancellable/OnInstall.ts delete mode 100644 packages/mask/background/tasks/NotCancellable/PendingTasks.ts delete mode 100644 packages/mask/background/tasks/NotCancellable/PrintBuildFlags.ts delete mode 100644 packages/mask/background/tasks/setup.hmr.ts delete mode 100644 packages/mask/background/tasks/setup.ts delete mode 100644 packages/mask/background/tsconfig.json delete mode 100644 packages/mask/background/utils/deprecated-storage.ts delete mode 100644 packages/mask/background/utils/injectScript.ts delete mode 100644 packages/mask/content-script/components/CompositionDialog/Composition.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/CompositionUI.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/EncryptionMethodSelector.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/EncryptionTargetSelector.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/PopoverListItem.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/PopoverListTrigger.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/SteganographyPayload.ts delete mode 100644 packages/mask/content-script/components/CompositionDialog/useCompositionClipboardRequest.tsx delete mode 100644 packages/mask/content-script/components/CompositionDialog/useRecipientsList.ts delete mode 100644 packages/mask/content-script/components/CompositionDialog/useSelectedRecipientsList.ts delete mode 100644 packages/mask/content-script/components/CompositionDialog/useSubmit.ts delete mode 100644 packages/mask/content-script/components/DataSource/useActivatedUI.ts delete mode 100644 packages/mask/content-script/components/DataSource/usePersonaPerSiteConnectStatus.ts delete mode 100644 packages/mask/content-script/components/DataSource/usePluginHostPermission.ts delete mode 100644 packages/mask/content-script/components/DataSource/useSearchedKeyword.ts delete mode 100644 packages/mask/content-script/components/GuideStep/index.tsx delete mode 100644 packages/mask/content-script/components/GuideStep/useSetupGuideStatus.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/AdditionalPostContent.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/AutoPasteFailedDialog.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/Avatar.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/CommentBox.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/RecipientsToolTip.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/authorDifferentMessage.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPost/types.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PageInspector.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PermissionBoundary.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PostActions.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PostComments.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PostDialogHint.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PostInspector.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/PostReplacer.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileBar.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileCardTitle.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileCard/index.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileCover.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileTab.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ProfileTabContent.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SearchResultInspector.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SelectPeopleDialog.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/AccountConnectStatus.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/CheckConnection.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/PinExtension.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/SetupGuideContext.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/VerifyNextID.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/WizardDialog.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectPersona.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectedVerified.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useCurrentUserId.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useNotifyConnected.ts delete mode 100644 packages/mask/content-script/components/InjectedComponents/SetupGuide/index.tsx delete mode 100644 packages/mask/content-script/components/InjectedComponents/ToolboxUnstyled.tsx delete mode 100644 packages/mask/content-script/components/Welcomes/Banner.tsx delete mode 100644 packages/mask/content-script/components/shared/DraggableDiv.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectProfileUI/SelectProfileUI.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectProfileUI/index.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectRecipients/ProfileInList.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectRecipients/SelectRecipients.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectRecipients/SelectRecipientsDialog.tsx delete mode 100644 packages/mask/content-script/components/shared/SelectRecipients/useContacts.ts delete mode 100644 packages/mask/content-script/components/shared/SelectRecipients/useTwitterIdByWalletSearch.tsx delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/dividerActive.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/dividerDisable.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/dividerDone.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/step1Active.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/step2Active.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/step2Disable.png delete mode 100644 packages/mask/content-script/components/shared/assets/stepAssets/stepSuccess.png delete mode 100644 packages/mask/content-script/components/shared/openApplicationBoardDialog.tsx delete mode 100644 packages/mask/content-script/components/useMaskSiteAdaptorMixedTheme.ts delete mode 100644 packages/mask/content-script/env.d.ts delete mode 100644 packages/mask/content-script/index.ts delete mode 100644 packages/mask/content-script/resources/extensionPinned.png delete mode 100644 packages/mask/content-script/resources/image-payload/index.ts delete mode 100644 packages/mask/content-script/resources/image-payload/normal/payload-2021.png delete mode 100644 packages/mask/content-script/resources/image-payload/normal/payload-2022.png delete mode 100644 packages/mask/content-script/resources/image-payload/normal/payload-2023.png delete mode 100644 packages/mask/content-script/resources/maskFilledIcon.png delete mode 100644 packages/mask/content-script/resources/maskFilledIconDark.png delete mode 100644 packages/mask/content-script/resources/tool-icon/airdrop.png delete mode 100644 packages/mask/content-script/resources/tool-icon/claim.png delete mode 100644 packages/mask/content-script/resources/tool-icon/encryptedmsg.png delete mode 100644 packages/mask/content-script/resources/tool-icon/markets.png delete mode 100644 packages/mask/content-script/resources/tool-icon/redpacket.png delete mode 100644 packages/mask/content-script/resources/tool-icon/swap.png delete mode 100644 packages/mask/content-script/resources/tool-icon/token.png delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/automation/AttachImageToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/index.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/CommentBox.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/Comments.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/PageInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/PostActions.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/PostReplacer.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/inject/StartSetupGuide.tsx delete mode 100644 packages/mask/content-script/site-adaptor-infra/defaults/state/InitProfiles.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/define.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/index.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/sandboxed-plugin.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/ui.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/utils.ts delete mode 100644 packages/mask/content-script/site-adaptor-infra/utils/create-post-context.ts delete mode 100644 packages/mask/content-script/site-adaptors/README.md delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/automation/openComposeBox.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/automation/pasteTextToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/automation/pasteToCommentBoxFacebook.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/base.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/collecting/getSearchedKeyword.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/collecting/identity.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/collecting/posts.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/collecting/theme.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/customization/custom.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/customization/render-fragments.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/Avatar/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/Banner.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/Composition.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInFacebook.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInTimeline.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/ProfileNFTAvatar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/useSaveAvatarInFacebook.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/PostInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/PostReplacer.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileContent.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileCover.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileTab.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/SearchResultInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/Toolbar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/injection/ToolbarUI.tsx delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/shared.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/ui-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/avatar.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/getProfileIdentifier.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/parse-username.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/resolveFacebookLink.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/selector.ts delete mode 100644 packages/mask/content-script/site-adaptors/facebook.com/utils/user.ts delete mode 100644 packages/mask/content-script/site-adaptors/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/base.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/collecting/identity-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/collecting/identity.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/collecting/posts.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/collecting/theme.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/customization/custom.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/Avatar/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarEditProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInInstagram.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInTimeline.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/ProfileNFTAvatar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTab.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTabContent.tsx delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/injection/post-inspector.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/shared.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/ui-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/utils/avatar.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/utils/selector.ts delete mode 100644 packages/mask/content-script/site-adaptors/instagram.com/utils/user.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/AttachImageToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/gotoNewsFeedPage.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/gotoProfilePage.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/openComposeBox.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/pasteTextToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/automation/pasteToCommentBoxMinds.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/base.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/collecting/getSearchedKeyword.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/collecting/identity.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/collecting/post.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/collecting/theme.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/customization/custom.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/customization/render-fragments.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/Avatar/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/Banner.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/CommentBox.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/PostDialog.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/PostDialogHint.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/PostInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/PostReplacer.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/ProfileCover.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/SearchResultInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint_UI.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/injection/inject.tsx delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/shared.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/ui-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/utils/fetch.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/utils/postBox.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/utils/selector.ts delete mode 100644 packages/mask/content-script/site-adaptors/minds.com/utils/user.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/base.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/collecting/identity.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/collecting/posts.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/collecting/theme.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/collecting/utils.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/customization/custom.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/customization/ui-overwrite.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/PostActions/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/MenuAuthorTipButton.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/PostVerification.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/ProfilePage.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/shared.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/tests/collection-utils.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/ui-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/utils/selectors.ts delete mode 100644 packages/mask/content-script/site-adaptors/mirror.xyz/utils/user.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/gotoNewsFeedPage.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/gotoProfilePage.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/openComposeBox.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/pasteImageToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/pasteTextToComposition.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/automation/publishPost.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/base.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/collecting/getSearchedKeyword.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/collecting/identity.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/collecting/theme.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/constant.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/customization/custom.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/customization/i18n.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/customization/twitter-color-schema.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Avatar/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Banner.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Calendar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/MaskIcon.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/Avatar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/MiniAvatarBorder.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/TweetNFTAvatar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useInjectedCSS.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useSaveAvatarInTwitter.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useUpdatedAvatar.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnPost.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnProfile.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnSidebar.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/PostActions/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialog.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialogHint.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/PostReplacer.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/constants.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/useControlProfileCard.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCover.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTab.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTabContent.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/SearchResultInspector.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/SwitchLogo.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/hooks.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/index.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint_UI.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/injection/inject.tsx delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/en-US.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/index.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/ja-JP.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/ko-KR.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/languages.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/qya-AA.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/zh-CN.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/locales/zh-TW.json delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/shared.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/AvatarType.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/avatar.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/postBox.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/url.ts delete mode 100644 packages/mask/content-script/site-adaptors/twitter.com/utils/user.ts delete mode 100644 packages/mask/content-script/site-adaptors/utils.ts delete mode 100644 packages/mask/content-script/tsconfig.json delete mode 100644 packages/mask/content-script/utils/collectNodeText.ts delete mode 100644 packages/mask/content-script/utils/collectTwitterEmoji.ts delete mode 100644 packages/mask/content-script/utils/downloadUrl.ts delete mode 100644 packages/mask/content-script/utils/hasPayloadLike.ts delete mode 100644 packages/mask/content-script/utils/index.ts delete mode 100644 packages/mask/content-script/utils/pasteImageToActiveElements.ts delete mode 100644 packages/mask/content-script/utils/regexMatch.ts delete mode 100644 packages/mask/content-script/utils/selectElementContents.ts delete mode 100644 packages/mask/content-script/utils/shadow-root.ts delete mode 100644 packages/mask/content-script/utils/shadow-root/ShadowRootAttachPointRoot.tsx delete mode 100644 packages/mask/content-script/utils/shadow-root/renderInShadowRoot.tsx delete mode 100644 packages/mask/content-script/utils/startWatch.ts delete mode 100644 packages/mask/content-script/utils/untilElementAvailable.ts delete mode 100644 packages/mask/dashboard/Dashboard.tsx delete mode 100644 packages/mask/dashboard/assets/Welcome.splinecode.png delete mode 100644 packages/mask/dashboard/assets/images/AboutDialogBackground.png delete mode 100644 packages/mask/dashboard/assets/images/MaskWallet.png delete mode 100644 packages/mask/dashboard/assets/images/MaskWatermark.png delete mode 100644 packages/mask/dashboard/assets/images/PrintBackground.png delete mode 100644 packages/mask/dashboard/assets/images/Trend.png delete mode 100644 packages/mask/dashboard/assets/index.ts delete mode 100644 packages/mask/dashboard/components/ActionCard/index.tsx delete mode 100644 packages/mask/dashboard/components/BackupPreview/index.tsx delete mode 100644 packages/mask/dashboard/components/FooterLine/About.tsx delete mode 100644 packages/mask/dashboard/components/FooterLine/Version.tsx delete mode 100644 packages/mask/dashboard/components/FooterLine/index.tsx delete mode 100644 packages/mask/dashboard/components/FooterLine/links.json delete mode 100644 packages/mask/dashboard/components/HeaderLine/index.tsx delete mode 100644 packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx delete mode 100644 packages/mask/dashboard/components/Mnemonic/MnemonicReveal.tsx delete mode 100644 packages/mask/dashboard/components/Mnemonic/index.tsx delete mode 100644 packages/mask/dashboard/components/OnboardingWriter/index.tsx delete mode 100644 packages/mask/dashboard/components/PasswordField/index.tsx delete mode 100644 packages/mask/dashboard/components/PrimaryButton/index.tsx delete mode 100644 packages/mask/dashboard/components/RegisterFrame/ButtonContainer.tsx delete mode 100644 packages/mask/dashboard/components/RegisterFrame/ColumnContentHeader.tsx delete mode 100644 packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx delete mode 100644 packages/mask/dashboard/components/RegisterFrame/ColumnLayout.tsx delete mode 100644 packages/mask/dashboard/components/Restore/AccountStatusBar.tsx delete mode 100644 packages/mask/dashboard/components/Restore/BackupInfoCard.tsx delete mode 100644 packages/mask/dashboard/components/Restore/ConfirmSynchronizePasswordDialog.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/InputForm.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/PhoneField.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/RestoreProvider.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromMnemonic.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestorePersonaFromLocal.tsx delete mode 100644 packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx delete mode 100644 packages/mask/dashboard/components/SecondaryButton/index.tsx delete mode 100644 packages/mask/dashboard/components/SetupFrame/index.tsx delete mode 100644 packages/mask/dashboard/contexts/CloudBackupFormContext.tsx delete mode 100644 packages/mask/dashboard/contexts/RecoveryContext.tsx delete mode 100644 packages/mask/dashboard/contexts/index.ts delete mode 100644 packages/mask/dashboard/env.d.ts delete mode 100644 packages/mask/dashboard/hooks/useBackupFormState.ts delete mode 100644 packages/mask/dashboard/hooks/useCreatePersonaV2.ts delete mode 100644 packages/mask/dashboard/hooks/useMnemonicWordsPuzzle.ts delete mode 100644 packages/mask/dashboard/hooks/useTermsAgreed.ts delete mode 100644 packages/mask/dashboard/initialization/i18n.ts delete mode 100644 packages/mask/dashboard/initialization/index.ts delete mode 100644 packages/mask/dashboard/initialization/render.tsx delete mode 100644 packages/mask/dashboard/locales/en-US.json delete mode 100644 packages/mask/dashboard/locales/index.ts delete mode 100644 packages/mask/dashboard/locales/ja-JP.json delete mode 100644 packages/mask/dashboard/locales/ko-KR.json delete mode 100644 packages/mask/dashboard/locales/languages.ts delete mode 100644 packages/mask/dashboard/locales/qya-AA.json delete mode 100644 packages/mask/dashboard/locales/zh-CN.json delete mode 100644 packages/mask/dashboard/locales/zh-TW.json delete mode 100644 packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx delete mode 100644 packages/mask/dashboard/modals/BackupPreviewModal/index.tsx delete mode 100644 packages/mask/dashboard/modals/ConfirmModal/index.tsx delete mode 100644 packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx delete mode 100644 packages/mask/dashboard/modals/MergeBackupModal/index.tsx delete mode 100644 packages/mask/dashboard/modals/index.tsx delete mode 100644 packages/mask/dashboard/modals/modals.ts delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/AddDeriveWallet/index.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/ComponentToPrint.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/Onboarding/index.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/context.ts delete mode 100644 packages/mask/dashboard/pages/CreateMaskWallet/index.tsx delete mode 100644 packages/mask/dashboard/pages/PrivacyPolicy/en.html delete mode 100644 packages/mask/dashboard/pages/PrivacyPolicy/index.tsx delete mode 100644 packages/mask/dashboard/pages/PrivacyPolicy/zh.html delete mode 100644 packages/mask/dashboard/pages/SetupPersona/CloudBackup/EmailForm.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/CloudBackup/PhoneForm.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/CloudBackup/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/CloudBackupPreview/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/LocalBackup/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Mnemonic/ComponentToPrint.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Mnemonic/Words.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Mnemonic/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Onboarding/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Recovery/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/SignUp/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Welcome/Article.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/Welcome/index.tsx delete mode 100644 packages/mask/dashboard/pages/SetupPersona/index.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/index.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/routePath.ts delete mode 100644 packages/mask/dashboard/pages/SignUp/routes.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/ConnectSocialMedia.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/MnemonicRevealForm.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/PersonaCreate.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/PersonaNameUI.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/PersonaRecovery.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/PreviewDialog.tsx delete mode 100644 packages/mask/dashboard/pages/SignUp/steps/index.ts delete mode 100644 packages/mask/dashboard/pages/TermsGuard.tsx delete mode 100644 packages/mask/dashboard/pages/routes.tsx delete mode 100644 packages/mask/dashboard/tsconfig.json delete mode 100644 packages/mask/dashboard/utils/api.ts delete mode 100644 packages/mask/dashboard/utils/regexp.ts delete mode 100644 packages/mask/dashboard/utils/type.ts delete mode 100644 packages/mask/devtools/content-script/index.ts delete mode 100644 packages/mask/devtools/env.d.ts delete mode 100644 packages/mask/devtools/panels/index.tsx delete mode 100644 packages/mask/devtools/panels/react.tsx delete mode 100644 packages/mask/devtools/panels/utils.ts delete mode 100644 packages/mask/devtools/shared.ts delete mode 100644 packages/mask/devtools/tsconfig.json delete mode 100644 packages/mask/entry-sdk/README.md delete mode 100644 packages/mask/entry-sdk/bridge/eth.ts delete mode 100644 packages/mask/entry-sdk/bridge/eth/validator.ts delete mode 100644 packages/mask/entry-sdk/bridge/index.ts delete mode 100644 packages/mask/entry-sdk/hmr-bridge.ts delete mode 100644 packages/mask/entry-sdk/hmr-sdk.ts delete mode 100644 packages/mask/entry-sdk/index.ts delete mode 100644 packages/mask/entry-sdk/tsconfig.json delete mode 100644 packages/mask/package.json delete mode 100644 packages/mask/popups/Popup.tsx delete mode 100644 packages/mask/popups/components/ActionModal/ActionModal.tsx delete mode 100644 packages/mask/popups/components/ActionModal/ActionModalContext.tsx delete mode 100644 packages/mask/popups/components/ActionModal/index.tsx delete mode 100644 packages/mask/popups/components/AddContactInputPanel/index.tsx delete mode 100644 packages/mask/popups/components/BottomController/index.tsx delete mode 100644 packages/mask/popups/components/BottomDrawer/index.tsx delete mode 100644 packages/mask/popups/components/ConnectSocialAccounts/index.tsx delete mode 100644 packages/mask/popups/components/ConnectedWallet/index.tsx delete mode 100644 packages/mask/popups/components/GasSettingMenu/index.tsx delete mode 100644 packages/mask/popups/components/LoadingMask/index.tsx delete mode 100644 packages/mask/popups/components/LoadingPlaceholder/index.tsx delete mode 100644 packages/mask/popups/components/MnemonicDisplay/index.tsx delete mode 100644 packages/mask/popups/components/NFTAvatarPicker/CollectionList.tsx delete mode 100644 packages/mask/popups/components/NFTAvatarPicker/index.tsx delete mode 100644 packages/mask/popups/components/Navigator/index.tsx delete mode 100644 packages/mask/popups/components/NormalHeader/index.tsx delete mode 100644 packages/mask/popups/components/PasswordField/index.tsx delete mode 100644 packages/mask/popups/components/PersonaAvatar/index.tsx delete mode 100644 packages/mask/popups/components/PersonaPublicKey/index.tsx delete mode 100644 packages/mask/popups/components/PopupLayout/LoadMaskSDK.tsx delete mode 100644 packages/mask/popups/components/PopupLayout/index.tsx delete mode 100644 packages/mask/popups/components/PrivateKeyDisplay/index.tsx delete mode 100644 packages/mask/popups/components/SelectProvider/index.tsx delete mode 100644 packages/mask/popups/components/SignRequestInfo/index.tsx delete mode 100644 packages/mask/popups/components/SocialAccounts/index.tsx delete mode 100644 packages/mask/popups/components/StyledInput/index.tsx delete mode 100644 packages/mask/popups/components/StyledRadio/index.tsx delete mode 100644 packages/mask/popups/components/TokenPicker/TokenItem.tsx delete mode 100644 packages/mask/popups/components/TokenPicker/index.tsx delete mode 100644 packages/mask/popups/components/TransactionPreview/index.tsx delete mode 100644 packages/mask/popups/components/UnlockERC20Token/index.tsx delete mode 100644 packages/mask/popups/components/UnlockERC721Token/index.tsx delete mode 100644 packages/mask/popups/components/WalletBalance/index.tsx delete mode 100644 packages/mask/popups/components/WalletItem/index.tsx delete mode 100644 packages/mask/popups/components/WalletSettingList/index.tsx delete mode 100644 packages/mask/popups/components/index.ts delete mode 100644 packages/mask/popups/constants.ts delete mode 100644 packages/mask/popups/hooks/index.ts delete mode 100644 packages/mask/popups/hooks/useConnectedOrigins.ts delete mode 100644 packages/mask/popups/hooks/useContactsContext.ts delete mode 100644 packages/mask/popups/hooks/useFriendProfiles.ts delete mode 100644 packages/mask/popups/hooks/useFriends.ts delete mode 100644 packages/mask/popups/hooks/useFriendsFromSearch.tsx delete mode 100644 packages/mask/popups/hooks/useGasOptionsMenu.tsx delete mode 100644 packages/mask/popups/hooks/useGasRatio.ts delete mode 100644 packages/mask/popups/hooks/useHasPassword.ts delete mode 100644 packages/mask/popups/hooks/usePopupContext.ts delete mode 100644 packages/mask/popups/hooks/usePopupTheme.ts delete mode 100644 packages/mask/popups/hooks/useSearchValue.ts delete mode 100644 packages/mask/popups/hooks/useSupportSocialNetworks.ts delete mode 100644 packages/mask/popups/hooks/useSupportedSites.ts delete mode 100644 packages/mask/popups/hooks/useTitle.ts delete mode 100644 packages/mask/popups/hooks/useTokenParams.ts delete mode 100644 packages/mask/popups/hooks/useVerifiedWallets.ts delete mode 100644 packages/mask/popups/hooks/useWalletGroup.ts delete mode 100644 packages/mask/popups/initialization/index.ts delete mode 100644 packages/mask/popups/initialization/render.tsx delete mode 100644 packages/mask/popups/modals/AddContactModal/index.tsx delete mode 100644 packages/mask/popups/modals/ChangeBackupPasswordModal/index.tsx delete mode 100644 packages/mask/popups/modals/ChangePaymentPasswordModal/index.tsx delete mode 100644 packages/mask/popups/modals/ChooseCurrencyModal/index.tsx delete mode 100644 packages/mask/popups/modals/ChooseNetworkModal/index.tsx delete mode 100644 packages/mask/popups/modals/ChooseToken/index.tsx delete mode 100644 packages/mask/popups/modals/ConfirmModal/index.tsx delete mode 100644 packages/mask/popups/modals/ConnectProvider/index.tsx delete mode 100644 packages/mask/popups/modals/ConnectSocialAccountModal/index.tsx delete mode 100644 packages/mask/popups/modals/DeleteContactModal/index.tsx delete mode 100644 packages/mask/popups/modals/EditContactModal/index.tsx delete mode 100644 packages/mask/popups/modals/GasSettingModal/GasSettingDialog.tsx delete mode 100644 packages/mask/popups/modals/GasSettingModal/index.tsx delete mode 100644 packages/mask/popups/modals/PersonaRenameModal/index.tsx delete mode 100644 packages/mask/popups/modals/PersonaSettingModal/index.tsx delete mode 100644 packages/mask/popups/modals/SelectAppearanceModal/index.tsx delete mode 100644 packages/mask/popups/modals/SelectLanguageModal/index.tsx delete mode 100644 packages/mask/popups/modals/SelectProviderModal/index.tsx delete mode 100644 packages/mask/popups/modals/SetBackupPasswordModal/index.tsx delete mode 100644 packages/mask/popups/modals/ShowPrivateKeyModal/index.tsx delete mode 100644 packages/mask/popups/modals/SupportedSitesModal/index.tsx delete mode 100644 packages/mask/popups/modals/SwitchPersonaModal/PersonaItem.tsx delete mode 100644 packages/mask/popups/modals/SwitchPersonaModal/index.tsx delete mode 100644 packages/mask/popups/modals/VerifyBackupPasswordModal/index.tsx delete mode 100644 packages/mask/popups/modals/WalletAutoLockSettingModal/index.tsx delete mode 100644 packages/mask/popups/modals/WalletGroupModal/index.tsx delete mode 100644 packages/mask/popups/modals/WalletRemoveModal/index.tsx delete mode 100644 packages/mask/popups/modals/WalletRenameModal/index.tsx delete mode 100644 packages/mask/popups/modals/index.tsx delete mode 100644 packages/mask/popups/modals/modal-controls.ts delete mode 100644 packages/mask/popups/modals/modals.ts delete mode 100644 packages/mask/popups/pages/Friends/AccountRender/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/ContactCard/Account/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/ContactCard/ConnectedAccounts/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/ContactCard/SocialAccount/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/ContactCard/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Contacts/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Detail/Account/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Detail/ConnectAccounts/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Detail/SocialAccount/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Detail/UI.tsx delete mode 100644 packages/mask/popups/pages/Friends/Detail/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Home/UI.tsx delete mode 100644 packages/mask/popups/pages/Friends/Home/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/Search/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/SearchList/index.tsx delete mode 100644 packages/mask/popups/pages/Friends/common.tsx delete mode 100644 packages/mask/popups/pages/Friends/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/AccountDetail/UI.tsx delete mode 100644 packages/mask/popups/pages/Personas/AccountDetail/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/ConnectWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/ExportPrivateKey/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/Home/UI.tsx delete mode 100644 packages/mask/popups/pages/Personas/Home/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/Logout/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/PersonaAvatarSetting/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/PersonaSignRequest/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/WalletConnect/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/components/AccountAvatar/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/components/PersonaHeader/UI.tsx delete mode 100644 packages/mask/popups/pages/Personas/components/PersonaHeader/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/index.tsx delete mode 100644 packages/mask/popups/pages/Personas/type.ts delete mode 100644 packages/mask/popups/pages/RequestPermission/RequestPermission.tsx delete mode 100644 packages/mask/popups/pages/RequestPermission/index.tsx delete mode 100644 packages/mask/popups/pages/Settings/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/AddToken/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/ChangeOwner/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/CollectibleDetail/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/ConnectedSites/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/ContactList/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/CreateWallet/Derive.tsx delete mode 100644 packages/mask/popups/pages/Wallet/CreateWallet/context.ts delete mode 100644 packages/mask/popups/pages/Wallet/CreateWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/EditNetwork/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/EditNetwork/network-schema.ts delete mode 100644 packages/mask/popups/pages/Wallet/EditNetwork/useWarnings.ts delete mode 100644 packages/mask/popups/pages/Wallet/ExportPrivateKey/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/GasSetting/GasSetting1559.tsx delete mode 100644 packages/mask/popups/pages/Wallet/GasSetting/Prior1559GasSetting.tsx delete mode 100644 packages/mask/popups/pages/Wallet/GasSetting/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/Interaction/InteractionContext.ts delete mode 100644 packages/mask/popups/pages/Wallet/Interaction/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/NetworkManagement/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/NoWalletGuard/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/Receive/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/ResetWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/SelectWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/SetPaymentPassword/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/SwitchWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/TrendingChart.tsx delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/useCoinGeckoCoinId.ts delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/useCoinTrendingStats.ts delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/useTokenPrice.ts delete mode 100644 packages/mask/popups/pages/Wallet/TokenDetail/useTrending.ts delete mode 100644 packages/mask/popups/pages/Wallet/TransactionDetail/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/TransactionDetail/types.ts delete mode 100644 packages/mask/popups/pages/Wallet/TransactionDetail/useTransactionLogs.ts delete mode 100644 packages/mask/popups/pages/Wallet/Transfer/FungibleTokenSection.tsx delete mode 100644 packages/mask/popups/pages/Wallet/Transfer/NonFungibleTokenSection.tsx delete mode 100644 packages/mask/popups/pages/Wallet/Transfer/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/Transfer/useDefaultGasConfig.ts delete mode 100644 packages/mask/popups/pages/Wallet/Unlock/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletGuard/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletGuard/useMessageGuard.ts delete mode 100644 packages/mask/popups/pages/Wallet/WalletGuard/usePaymentPasswordGuard.ts delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/AutoLock.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ChangeCurrency.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ChangeNetwork.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ChangeOwner.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ChangePaymentPassword.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ConnectedOrigins.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/Contacts.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/HidingScamTx.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/Rename.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/ShowPrivateKey.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/WalletSettings/useStyles.ts delete mode 100644 packages/mask/popups/pages/Wallet/components/ActionGroup/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/ActivityList/ActivityItem.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/ActivityList/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/ActivityList/useTransactions.ts delete mode 100644 packages/mask/popups/pages/Wallet/components/AssetsList/MoreBar.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/AssetsList/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/DisconnectModal/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/ImportCreateWallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/OriginCard/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/StartUp/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletAssets/WalletCollections.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletAssets/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletHeader/UI.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletHeader/WalletAssetsValue.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletHeader/WalletSetupHeaderUI.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/WalletHeader/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/components/index.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/index.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useAsset.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useAssetExpand.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useConnected.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/usePasswordForm.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useUnConfirmedRequest.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useWalletAssets.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useWalletAutoLockTime.ts delete mode 100644 packages/mask/popups/pages/Wallet/hooks/useWalletLockStatus.ts delete mode 100644 packages/mask/popups/pages/Wallet/index.tsx delete mode 100644 packages/mask/popups/pages/Wallet/type.ts delete mode 100644 packages/mask/popups/pages/Wallet/utils.ts delete mode 100644 packages/mask/popups/tsconfig.json delete mode 100644 packages/mask/public/assets/128x128.png delete mode 100644 packages/mask/public/assets/16x16.png delete mode 100644 packages/mask/public/assets/256x256.png delete mode 100644 packages/mask/public/assets/48x48.png delete mode 100644 packages/mask/public/empty.html delete mode 100644 packages/mask/public/js/lockdown.js delete mode 100644 packages/mask/public/js/module-loader.js delete mode 100644 packages/mask/public/js/patches.js delete mode 100644 packages/mask/public/js/perf-measure.js delete mode 100644 packages/mask/public/js/sentry-patch.js delete mode 100644 packages/mask/public/js/trusted-types.js delete mode 100644 packages/mask/public/jsconfig.json delete mode 100644 packages/mask/public/manifest-v3.entry.js delete mode 100644 packages/mask/public/sandboxed-modules/mv3-preload.js delete mode 100644 packages/mask/public/worker.js delete mode 100644 packages/mask/shared-ui/TypedMessageRender/Components/Text.tsx delete mode 100644 packages/mask/shared-ui/TypedMessageRender/context.tsx delete mode 100644 packages/mask/shared-ui/TypedMessageRender/registry.ts delete mode 100644 packages/mask/shared-ui/TypedMessageRender/transformer.ts delete mode 100644 packages/mask/shared-ui/components/Avatar.tsx delete mode 100644 packages/mask/shared-ui/env.d.ts delete mode 100644 packages/mask/shared-ui/hooks/index.ts delete mode 100644 packages/mask/shared-ui/hooks/useAppearance.ts delete mode 100644 packages/mask/shared-ui/hooks/useCurrentPersona.ts delete mode 100644 packages/mask/shared-ui/hooks/useLanguage.ts delete mode 100644 packages/mask/shared-ui/hooks/usePersonasFromDB.ts delete mode 100644 packages/mask/shared-ui/hooks/useSupportedSocialNetworkSites.ts delete mode 100644 packages/mask/shared-ui/hooks/useThemeLanguage.ts delete mode 100644 packages/mask/shared-ui/hooks/useUserContext.ts delete mode 100644 packages/mask/shared-ui/index.ts delete mode 100644 packages/mask/shared-ui/initUIContext.ts delete mode 100644 packages/mask/shared-ui/initialization/async-setup.ts delete mode 100644 packages/mask/shared-ui/initialization/debugger.ts delete mode 100644 packages/mask/shared-ui/initialization/fetch.ts delete mode 100644 packages/mask/shared-ui/initialization/index.ts delete mode 100644 packages/mask/shared-ui/initialization/locales.ts delete mode 100644 packages/mask/shared-ui/initialization/post-async-setup.ts delete mode 100644 packages/mask/shared-ui/initialization/react-query.ts delete mode 100644 packages/mask/shared-ui/initialization/storage.ts delete mode 100644 packages/mask/shared-ui/initialization/telemetry-update.ts delete mode 100644 packages/mask/shared-ui/initialization/telemetry.ts delete mode 100644 packages/mask/shared-ui/initialization/walletSetup.ts delete mode 100644 packages/mask/shared-ui/locales/en-US.json delete mode 100644 packages/mask/shared-ui/locales/index.ts delete mode 100644 packages/mask/shared-ui/locales/ja-JP.json delete mode 100644 packages/mask/shared-ui/locales/ko-KR.json delete mode 100644 packages/mask/shared-ui/locales/languages.ts delete mode 100644 packages/mask/shared-ui/locales/qya-AA.json delete mode 100644 packages/mask/shared-ui/locales/zh-CN.json delete mode 100644 packages/mask/shared-ui/locales/zh-TW.json delete mode 100644 packages/mask/shared-ui/locales_legacy/index.ts delete mode 100644 packages/mask/shared-ui/service.ts delete mode 100644 packages/mask/shared-ui/tsconfig.json delete mode 100644 packages/mask/shared-ui/utils/createNormalReactRoot.tsx delete mode 100644 packages/mask/shared-ui/utils/permissions.ts delete mode 100644 packages/mask/shared-ui/utils/persistOptions.ts delete mode 100644 packages/mask/shared/definitions/event.ts delete mode 100644 packages/mask/shared/definitions/routes.ts delete mode 100644 packages/mask/shared/definitions/wallet.ts delete mode 100644 packages/mask/shared/env.d.ts delete mode 100644 packages/mask/shared/helpers/attachNextIDToProfile.ts delete mode 100644 packages/mask/shared/helpers/download.ts delete mode 100644 packages/mask/shared/helpers/formatTokenBalance.ts delete mode 100644 packages/mask/shared/helpers/index.ts delete mode 100644 packages/mask/shared/helpers/remoteFlagIO.ts delete mode 100644 packages/mask/shared/index.ts delete mode 100644 packages/mask/shared/legacy-settings/listener.ts delete mode 100644 packages/mask/shared/plugin-infra/host.ts delete mode 100644 packages/mask/shared/plugin-infra/register.d.ts delete mode 100644 packages/mask/shared/plugin-infra/register.js delete mode 100644 packages/mask/shared/sandboxed-plugin/host-api.ts delete mode 100644 packages/mask/shared/site-adaptors/definitions.ts delete mode 100644 packages/mask/shared/site-adaptors/implementations/facebook.com.ts delete mode 100644 packages/mask/shared/site-adaptors/implementations/instagram.com.ts delete mode 100644 packages/mask/shared/site-adaptors/implementations/minds.com.ts delete mode 100644 packages/mask/shared/site-adaptors/implementations/mirror.xyz.ts delete mode 100644 packages/mask/shared/site-adaptors/implementations/twitter.com.ts delete mode 100644 packages/mask/shared/site-adaptors/types.d.ts delete mode 100644 packages/mask/shared/tsconfig.json delete mode 100644 packages/mask/swap/Swap.tsx delete mode 100644 packages/mask/swap/components/AccountManager.tsx delete mode 100644 packages/mask/swap/components/SwapBackground.tsx delete mode 100644 packages/mask/swap/initialization/index.ts delete mode 100644 packages/mask/swap/initialization/render.tsx delete mode 100644 packages/mask/swap/pages/Swap/index.tsx delete mode 100644 packages/mask/swap/tsconfig.json delete mode 100644 packages/mask/swap/webgl/TickerManager.ts delete mode 100644 packages/mask/swap/webgl/circleFrag.frag.ts delete mode 100644 packages/mask/swap/webgl/circleVert.vert.ts delete mode 100644 packages/mask/swap/webgl/shader2d.ts delete mode 100644 packages/mask/tsconfig.json delete mode 100644 packages/mask/utils-pure/README.md delete mode 100644 packages/mask/utils-pure/crypto/index.ts delete mode 100644 packages/mask/utils-pure/env.d.ts delete mode 100644 packages/mask/utils-pure/hmr.ts delete mode 100644 packages/mask/utils-pure/index.ts delete mode 100644 packages/mask/utils-pure/tsconfig.json delete mode 100644 packages/mask/web-workers/env.d.ts delete mode 100644 packages/mask/web-workers/prepare.ts delete mode 100644 packages/mask/web-workers/tsconfig.json delete mode 100644 packages/mask/web-workers/wallet.ts delete mode 100644 packages/plugins/Approval/package.json delete mode 100644 packages/plugins/Approval/src/SiteAdaptor/ApprovalDialog.tsx delete mode 100644 packages/plugins/Approval/src/SiteAdaptor/ApprovalNFTContent.tsx delete mode 100644 packages/plugins/Approval/src/SiteAdaptor/ApprovalTokenContent.tsx delete mode 100644 packages/plugins/Approval/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Approval/src/base.ts delete mode 100644 packages/plugins/Approval/src/constants.ts delete mode 100644 packages/plugins/Approval/src/locales/en-US.json delete mode 100644 packages/plugins/Approval/src/locales/index.ts delete mode 100644 packages/plugins/Approval/src/locales/ja-JP.json delete mode 100644 packages/plugins/Approval/src/locales/ko-KR.json delete mode 100644 packages/plugins/Approval/src/locales/languages.ts delete mode 100644 packages/plugins/Approval/src/locales/qya-AA.json delete mode 100644 packages/plugins/Approval/src/locales/zh-CN.json delete mode 100644 packages/plugins/Approval/src/locales/zh-TW.json delete mode 100644 packages/plugins/Approval/src/register.ts delete mode 100644 packages/plugins/Approval/tsconfig.json delete mode 100644 packages/plugins/ArtBlocks/package.json delete mode 100644 packages/plugins/ArtBlocks/src/README.md delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/ActionBar.tsx delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/Collectible.tsx delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/CollectionView.tsx delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/DetailsView.tsx delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/PurchaseDialog.tsx delete mode 100644 packages/plugins/ArtBlocks/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/ArtBlocks/src/apis/index.ts delete mode 100644 packages/plugins/ArtBlocks/src/base.tsx delete mode 100644 packages/plugins/ArtBlocks/src/constants.ts delete mode 100644 packages/plugins/ArtBlocks/src/env.d.ts delete mode 100644 packages/plugins/ArtBlocks/src/hooks/useArtBlocksContract.ts delete mode 100644 packages/plugins/ArtBlocks/src/hooks/useProject.ts delete mode 100644 packages/plugins/ArtBlocks/src/hooks/usePurchaseCallback.ts delete mode 100644 packages/plugins/ArtBlocks/src/icon/artblocks.png delete mode 100644 packages/plugins/ArtBlocks/src/locales/en-US.json delete mode 100644 packages/plugins/ArtBlocks/src/locales/index.ts delete mode 100644 packages/plugins/ArtBlocks/src/locales/ja-JP.json delete mode 100644 packages/plugins/ArtBlocks/src/locales/ko-KR.json delete mode 100644 packages/plugins/ArtBlocks/src/locales/languages.ts delete mode 100644 packages/plugins/ArtBlocks/src/locales/qya-AA.json delete mode 100644 packages/plugins/ArtBlocks/src/locales/zh-CN.json delete mode 100644 packages/plugins/ArtBlocks/src/locales/zh-TW.json delete mode 100644 packages/plugins/ArtBlocks/src/pipes/index.ts delete mode 100644 packages/plugins/ArtBlocks/src/register.ts delete mode 100644 packages/plugins/ArtBlocks/src/tests/check.ts delete mode 100644 packages/plugins/ArtBlocks/src/types.ts delete mode 100644 packages/plugins/ArtBlocks/src/utils.ts delete mode 100644 packages/plugins/ArtBlocks/tsconfig.json delete mode 100644 packages/plugins/Avatar/package.json delete mode 100644 packages/plugins/Avatar/src/Application/NFTAvatar.tsx delete mode 100644 packages/plugins/Avatar/src/Application/NFTAvatarDialog.tsx delete mode 100644 packages/plugins/Avatar/src/Application/NFTInfo.tsx delete mode 100644 packages/plugins/Avatar/src/Application/NFTListDialog.tsx delete mode 100644 packages/plugins/Avatar/src/Application/PersonaItem.tsx delete mode 100644 packages/plugins/Avatar/src/Application/PersonaPage.tsx delete mode 100644 packages/plugins/Avatar/src/Application/RouterDialog.tsx delete mode 100644 packages/plugins/Avatar/src/Application/Routes.tsx delete mode 100644 packages/plugins/Avatar/src/Application/UploadAvatarDialog.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTAvatar.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTAvatarButton.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTAvatarRing.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTBadge.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTBadgeTimeline.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/NFTImage.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/RainbowBox.tsx delete mode 100644 packages/plugins/Avatar/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Avatar/src/base.ts delete mode 100644 packages/plugins/Avatar/src/constants.ts delete mode 100644 packages/plugins/Avatar/src/contexts/AvatarManagement.tsx delete mode 100644 packages/plugins/Avatar/src/env.d.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSave.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSaveAddress.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSaveAvatar.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSaveKV.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSaveStringStorage.ts delete mode 100644 packages/plugins/Avatar/src/hooks/useSaveToNextID.ts delete mode 100644 packages/plugins/Avatar/src/index.ts delete mode 100644 packages/plugins/Avatar/src/locales/en-US.json delete mode 100644 packages/plugins/Avatar/src/locales/index.ts delete mode 100644 packages/plugins/Avatar/src/locales/ja-JP.json delete mode 100644 packages/plugins/Avatar/src/locales/ko-KR.json delete mode 100644 packages/plugins/Avatar/src/locales/languages.ts delete mode 100644 packages/plugins/Avatar/src/locales/qya-AA.json delete mode 100644 packages/plugins/Avatar/src/locales/zh-CN.json delete mode 100644 packages/plugins/Avatar/src/locales/zh-TW.json delete mode 100644 packages/plugins/Avatar/src/register.ts delete mode 100644 packages/plugins/Avatar/src/types.ts delete mode 100644 packages/plugins/Avatar/src/utils/index.ts delete mode 100644 packages/plugins/Avatar/tsconfig.json delete mode 100644 packages/plugins/Claim/README.md delete mode 100644 packages/plugins/Claim/package.json delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/components/AirDropActivities/AirDropActivityItem.tsx delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/components/AirDropActivities/index.tsx delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/components/ClaimDialog/index.tsx delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/components/ClaimEntry/index.tsx delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/components/ClaimSuccessDialog/index.tsx delete mode 100644 packages/plugins/Claim/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Claim/src/assets/ARB-background.png delete mode 100644 packages/plugins/Claim/src/assets/lock-dark.png delete mode 100644 packages/plugins/Claim/src/assets/lock.png delete mode 100644 packages/plugins/Claim/src/base.ts delete mode 100644 packages/plugins/Claim/src/constants.ts delete mode 100644 packages/plugins/Claim/src/env.d.ts delete mode 100644 packages/plugins/Claim/src/hooks/useAirDropActivity.ts delete mode 100644 packages/plugins/Claim/src/hooks/useClaimAirdrop.tsx delete mode 100644 packages/plugins/Claim/src/locales/en-US.json delete mode 100644 packages/plugins/Claim/src/locales/index.ts delete mode 100644 packages/plugins/Claim/src/locales/ja-JP.json delete mode 100644 packages/plugins/Claim/src/locales/ko-KR.json delete mode 100644 packages/plugins/Claim/src/locales/languages.ts delete mode 100644 packages/plugins/Claim/src/locales/qya-AA.json delete mode 100644 packages/plugins/Claim/src/locales/zh-CN.json delete mode 100644 packages/plugins/Claim/src/locales/zh-TW.json delete mode 100644 packages/plugins/Claim/src/message.ts delete mode 100644 packages/plugins/Claim/src/register.ts delete mode 100644 packages/plugins/Claim/src/types.ts delete mode 100644 packages/plugins/Claim/tsconfig.json delete mode 100644 packages/plugins/Collectible/package.json delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/Collectible.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/CollectibleCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/CollectiblePaper.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/tabs/AboutTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/tabs/ActivitiesTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/tabs/DetailsTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Card/tabs/OffersTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/CardDialog/CardDialog.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/CardDialog/CardDialogContent.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/CardDialog/tabs/AboutTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/CardDialog/tabs/ActivitiesTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/CardDialog/tabs/OffersTab.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Context/index.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/DialogInspector.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/PostInspector.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/ActivitiesList.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/ActivityCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/DescriptionCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/DetailsCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/FigureCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/LinkingAvatar.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/OfferCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/OffersList.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/PriceCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/PropertiesCard.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/Shared/Rank.tsx delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/hooks/getNFTXAssetAddress.ts delete mode 100644 packages/plugins/Collectible/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Collectible/src/base.ts delete mode 100644 packages/plugins/Collectible/src/constants.ts delete mode 100644 packages/plugins/Collectible/src/env.d.ts delete mode 100644 packages/plugins/Collectible/src/helpers/index.ts delete mode 100644 packages/plugins/Collectible/src/helpers/url.ts delete mode 100644 packages/plugins/Collectible/src/locales/en-US.json delete mode 100644 packages/plugins/Collectible/src/locales/index.ts delete mode 100644 packages/plugins/Collectible/src/locales/ja-JP.json delete mode 100644 packages/plugins/Collectible/src/locales/ko-KR.json delete mode 100644 packages/plugins/Collectible/src/locales/languages.ts delete mode 100644 packages/plugins/Collectible/src/locales/qya-AA.json delete mode 100644 packages/plugins/Collectible/src/locales/zh-CN.json delete mode 100644 packages/plugins/Collectible/src/locales/zh-TW.json delete mode 100644 packages/plugins/Collectible/src/register.ts delete mode 100644 packages/plugins/Collectible/src/schema.json delete mode 100644 packages/plugins/Collectible/src/types.ts delete mode 100644 packages/plugins/Collectible/tests/helpers.ts delete mode 100644 packages/plugins/Collectible/tsconfig.json delete mode 100644 packages/plugins/CrossChainBridge/README.md delete mode 100644 packages/plugins/CrossChainBridge/package.json delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/CrossChainBridgeDialog.tsx delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/MaskIcon.tsx delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/arbitrum-one-bridge.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/boba-bridge.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/cbridge.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/cross-chain.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/polygon-bridge.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/assets/rainbow-bridge.png delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/components/BridgeStack.tsx delete mode 100644 packages/plugins/CrossChainBridge/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/CrossChainBridge/src/base.ts delete mode 100644 packages/plugins/CrossChainBridge/src/constants.tsx delete mode 100644 packages/plugins/CrossChainBridge/src/env.d.ts delete mode 100644 packages/plugins/CrossChainBridge/src/index.ts delete mode 100644 packages/plugins/CrossChainBridge/src/locales/en-US.json delete mode 100644 packages/plugins/CrossChainBridge/src/locales/index.ts delete mode 100644 packages/plugins/CrossChainBridge/src/locales/ja-JP.json delete mode 100644 packages/plugins/CrossChainBridge/src/locales/ko-KR.json delete mode 100644 packages/plugins/CrossChainBridge/src/locales/languages.ts delete mode 100644 packages/plugins/CrossChainBridge/src/locales/qya-AA.json delete mode 100644 packages/plugins/CrossChainBridge/src/locales/zh-CN.json delete mode 100644 packages/plugins/CrossChainBridge/src/locales/zh-TW.json delete mode 100644 packages/plugins/CrossChainBridge/src/register.ts delete mode 100644 packages/plugins/CrossChainBridge/tsconfig.json delete mode 100644 packages/plugins/CyberConnect/package.json delete mode 100644 packages/plugins/CyberConnect/src/SiteAdaptor/ConnectButton.tsx delete mode 100644 packages/plugins/CyberConnect/src/SiteAdaptor/FollowTab.tsx delete mode 100644 packages/plugins/CyberConnect/src/SiteAdaptor/FollowersPage.tsx delete mode 100644 packages/plugins/CyberConnect/src/SiteAdaptor/Profile.tsx delete mode 100644 packages/plugins/CyberConnect/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/CyberConnect/src/Worker/apis/index.ts delete mode 100644 packages/plugins/CyberConnect/src/Worker/index.ts delete mode 100644 packages/plugins/CyberConnect/src/assets/Context.png delete mode 100644 packages/plugins/CyberConnect/src/assets/Foundation.png delete mode 100644 packages/plugins/CyberConnect/src/assets/Opensea.png delete mode 100644 packages/plugins/CyberConnect/src/assets/Rarible.png delete mode 100644 packages/plugins/CyberConnect/src/assets/logo-white.svg delete mode 100644 packages/plugins/CyberConnect/src/base.tsx delete mode 100644 packages/plugins/CyberConnect/src/constants.ts delete mode 100644 packages/plugins/CyberConnect/src/env.d.ts delete mode 100644 packages/plugins/CyberConnect/src/hooks/useFollowers.ts delete mode 100644 packages/plugins/CyberConnect/src/locales/en-US.json delete mode 100644 packages/plugins/CyberConnect/src/locales/index.ts delete mode 100644 packages/plugins/CyberConnect/src/locales/ja-JP.json delete mode 100644 packages/plugins/CyberConnect/src/locales/ko-KR.json delete mode 100644 packages/plugins/CyberConnect/src/locales/languages.ts delete mode 100644 packages/plugins/CyberConnect/src/locales/qya-AA.json delete mode 100644 packages/plugins/CyberConnect/src/locales/zh-CN.json delete mode 100644 packages/plugins/CyberConnect/src/locales/zh-TW.json delete mode 100644 packages/plugins/CyberConnect/src/messages.ts delete mode 100644 packages/plugins/CyberConnect/src/register.ts delete mode 100644 packages/plugins/CyberConnect/tsconfig.json delete mode 100644 packages/plugins/Debugger/package.json delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/AvatarDecorator.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/ConnectionContent.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/ConnectionDialog.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/ConsoleContent.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/ConsoleDialog.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/HubContent.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/HubDialog.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/SearchResultInspector.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/TabContent.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/WidgetContent.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/components/WidgetDialog.tsx delete mode 100644 packages/plugins/Debugger/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Debugger/src/assets/cover.png delete mode 100644 packages/plugins/Debugger/src/base.ts delete mode 100644 packages/plugins/Debugger/src/constants.ts delete mode 100644 packages/plugins/Debugger/src/env.d.ts delete mode 100644 packages/plugins/Debugger/src/locales/en-US.json delete mode 100644 packages/plugins/Debugger/src/locales/index.ts delete mode 100644 packages/plugins/Debugger/src/locales/ja-JP.json delete mode 100644 packages/plugins/Debugger/src/locales/ko-KR.json delete mode 100644 packages/plugins/Debugger/src/locales/languages.ts delete mode 100644 packages/plugins/Debugger/src/locales/qya-AA.json delete mode 100644 packages/plugins/Debugger/src/locales/zh-CN.json delete mode 100644 packages/plugins/Debugger/src/locales/zh-TW.json delete mode 100644 packages/plugins/Debugger/src/messages.ts delete mode 100644 packages/plugins/Debugger/src/register.ts delete mode 100644 packages/plugins/Debugger/tsconfig.json delete mode 100644 packages/plugins/FileService/package.json delete mode 100644 packages/plugins/FileService/src/README.md delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/FileServiceInjection.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/FileViewer.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/MainDialog.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/Routes.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/FileBrowser.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/FileChip.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/FileList.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Files/DisplayingFile.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Files/ManageableFile.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Files/SelectableFile.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Files/UploadingFile.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Files/index.ts delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/RouterDialog.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/Terms.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/UploadFile.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/components/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/contexts/FileManagement/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/contexts/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/emitter.ts delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/ConfirmModal/ConfirmDialog.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/ConfirmModal/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/RenameModal/RenameDialog.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/RenameModal/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/index.tsx delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/modals/modals.ts delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/rpc.ts delete mode 100644 packages/plugins/FileService/src/SiteAdaptor/storage.ts delete mode 100644 packages/plugins/FileService/src/Worker/arweave-token.json delete mode 100644 packages/plugins/FileService/src/Worker/arweave.ts delete mode 100644 packages/plugins/FileService/src/Worker/database.ts delete mode 100644 packages/plugins/FileService/src/Worker/index.ts delete mode 100644 packages/plugins/FileService/src/Worker/ipfs.ts delete mode 100644 packages/plugins/FileService/src/Worker/remote-signing.ts delete mode 100644 packages/plugins/FileService/src/Worker/service.ts delete mode 100644 packages/plugins/FileService/src/base.ts delete mode 100644 packages/plugins/FileService/src/constants.ts delete mode 100644 packages/plugins/FileService/src/env.d.ts delete mode 100644 packages/plugins/FileService/src/helpers.ts delete mode 100644 packages/plugins/FileService/src/locales/en-US.json delete mode 100644 packages/plugins/FileService/src/locales/index.ts delete mode 100644 packages/plugins/FileService/src/locales/ja-JP.json delete mode 100644 packages/plugins/FileService/src/locales/ko-KR.json delete mode 100644 packages/plugins/FileService/src/locales/languages.ts delete mode 100644 packages/plugins/FileService/src/locales/qya-AA.json delete mode 100644 packages/plugins/FileService/src/locales/zh-CN.json delete mode 100644 packages/plugins/FileService/src/locales/zh-TW.json delete mode 100644 packages/plugins/FileService/src/register.ts delete mode 100644 packages/plugins/FileService/src/schema-v1.json delete mode 100644 packages/plugins/FileService/src/schema-v2.json delete mode 100644 packages/plugins/FileService/src/schema-v3.json delete mode 100644 packages/plugins/FileService/src/types.ts delete mode 100644 packages/plugins/FileService/tsconfig.json delete mode 100644 packages/plugins/FriendTech/package.json delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/ActionsContent.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/FriendTechDialog.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/FriendTechInjection.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/FriendTechNameWidget.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/Main.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/Order.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/Routes.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/UserDetail.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/HistoryList.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/HoldingCard.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/HoldingList.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/KeysTab.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/RouterDialog.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/components/UserProfile.tsx delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/emitter.ts delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/hooks/useEstimateSellGas.ts delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/hooks/useOwnKeys.ts delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/hooks/useUser.ts delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/hooks/useUserInfo.ts delete mode 100644 packages/plugins/FriendTech/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/FriendTech/src/base.ts delete mode 100644 packages/plugins/FriendTech/src/constants.ts delete mode 100644 packages/plugins/FriendTech/src/env.d.ts delete mode 100644 packages/plugins/FriendTech/src/locales/en-US.json delete mode 100644 packages/plugins/FriendTech/src/locales/index.ts delete mode 100644 packages/plugins/FriendTech/src/locales/ja-JP.json delete mode 100644 packages/plugins/FriendTech/src/locales/ko-KR.json delete mode 100644 packages/plugins/FriendTech/src/locales/languages.ts delete mode 100644 packages/plugins/FriendTech/src/locales/qya-AA.json delete mode 100644 packages/plugins/FriendTech/src/locales/zh-CN.json delete mode 100644 packages/plugins/FriendTech/src/locales/zh-TW.json delete mode 100644 packages/plugins/FriendTech/src/register.ts delete mode 100644 packages/plugins/FriendTech/tsconfig.json delete mode 100644 packages/plugins/Gitcoin/package.json delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/PreviewCard.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/gitcoin-grant-detail-style.ts delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/hooks/useBulkCheckoutWallet.ts delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/hooks/useDonateCallback.ts delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/hooks/useGrant.ts delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/DonateDialog.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/GiveBackSelect.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/DonateModal/index.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/ResultModal/ResultDialog.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/ResultModal/index.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/index.tsx delete mode 100644 packages/plugins/Gitcoin/src/SiteAdaptor/modals/modals.tsx delete mode 100644 packages/plugins/Gitcoin/src/apis/index.ts delete mode 100644 packages/plugins/Gitcoin/src/base.ts delete mode 100644 packages/plugins/Gitcoin/src/constants.ts delete mode 100644 packages/plugins/Gitcoin/src/env.d.ts delete mode 100644 packages/plugins/Gitcoin/src/locales/en-US.json delete mode 100644 packages/plugins/Gitcoin/src/locales/index.ts delete mode 100644 packages/plugins/Gitcoin/src/locales/ja-JP.json delete mode 100644 packages/plugins/Gitcoin/src/locales/ko-KR.json delete mode 100644 packages/plugins/Gitcoin/src/locales/languages.ts delete mode 100644 packages/plugins/Gitcoin/src/locales/qya-AA.json delete mode 100644 packages/plugins/Gitcoin/src/locales/zh-CN.json delete mode 100644 packages/plugins/Gitcoin/src/locales/zh-TW.json delete mode 100644 packages/plugins/Gitcoin/src/register.ts delete mode 100644 packages/plugins/Gitcoin/src/utils.ts delete mode 100644 packages/plugins/Gitcoin/tsconfig.json delete mode 100644 packages/plugins/GoPlusSecurity/package.json delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/CheckSecurityDialog.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/GoPlusGlobalInjection.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/CheckSecurityConfirmDialog.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/DefaultPlaceholder.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/Footer.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/NotFound.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/RiskCard.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/RiskWarningDialog.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/SearchBox.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/SecurityPanel.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/components/TokenPanel.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/constants.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/hooks/useSupportedChains.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/icons/Logo.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/UI/TokenSecurityBoundary.tsx delete mode 100644 packages/plugins/GoPlusSecurity/src/UI/index.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/assets/chain-cronos.png delete mode 100644 packages/plugins/GoPlusSecurity/src/assets/chain-heco.png delete mode 100644 packages/plugins/GoPlusSecurity/src/assets/chain-okex.png delete mode 100644 packages/plugins/GoPlusSecurity/src/assets/security-icon.png delete mode 100644 packages/plugins/GoPlusSecurity/src/base.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/constants.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/env.d.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/index.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/en-US.json delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/index.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/ja-JP.json delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/ko-KR.json delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/languages.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/qya-AA.json delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/zh-CN.json delete mode 100644 packages/plugins/GoPlusSecurity/src/locales/zh-TW.json delete mode 100644 packages/plugins/GoPlusSecurity/src/messages.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/register.ts delete mode 100644 packages/plugins/GoPlusSecurity/src/utils/helper.ts delete mode 100644 packages/plugins/GoPlusSecurity/tsconfig.json delete mode 100644 packages/plugins/Handle/package.json delete mode 100644 packages/plugins/Handle/src/SiteAdaptor/PluginHeader.tsx delete mode 100644 packages/plugins/Handle/src/SiteAdaptor/SearchResultInspector.tsx delete mode 100644 packages/plugins/Handle/src/SiteAdaptor/context.tsx delete mode 100644 packages/plugins/Handle/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Handle/src/base.ts delete mode 100644 packages/plugins/Handle/src/constants.ts delete mode 100644 packages/plugins/Handle/src/index.ts delete mode 100644 packages/plugins/Handle/src/locales/en-US.json delete mode 100644 packages/plugins/Handle/src/locales/index.ts delete mode 100644 packages/plugins/Handle/src/locales/ja-JP.json delete mode 100644 packages/plugins/Handle/src/locales/ko-KR.json delete mode 100644 packages/plugins/Handle/src/locales/languages.ts delete mode 100644 packages/plugins/Handle/src/locales/qya-AA.json delete mode 100644 packages/plugins/Handle/src/locales/zh-CN.json delete mode 100644 packages/plugins/Handle/src/locales/zh-TW.json delete mode 100644 packages/plugins/Handle/src/register.ts delete mode 100644 packages/plugins/Handle/tsconfig.json delete mode 100644 packages/plugins/MaskBox/package.json delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/ArticlesTab.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/CollectibleCard.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/DetailsTab.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/DrawDialog.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/DrawResultDialog.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/PreviewCard.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/components/TokenCard.tsx delete mode 100644 packages/plugins/MaskBox/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/MaskBox/src/Worker/index.ts delete mode 100644 packages/plugins/MaskBox/src/apis/index.ts delete mode 100644 packages/plugins/MaskBox/src/apis/merkleProof.ts delete mode 100644 packages/plugins/MaskBox/src/apis/storage.ts delete mode 100644 packages/plugins/MaskBox/src/assets/FallbackImage.svg delete mode 100644 packages/plugins/MaskBox/src/assets/bridge.png delete mode 100644 packages/plugins/MaskBox/src/assets/mask_box.png delete mode 100644 packages/plugins/MaskBox/src/base.ts delete mode 100644 packages/plugins/MaskBox/src/constants.ts delete mode 100644 packages/plugins/MaskBox/src/helpers/formatCountdown.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useContext.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxContract.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxCreationSuccessEvent.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxInfo.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxMetadata.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxPurchasedTokens.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxQualificationContract.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxStatus.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMaskBoxTokensForSale.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useMerkleProof.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useOpenBoxTransaction.ts delete mode 100644 packages/plugins/MaskBox/src/hooks/useQualification.ts delete mode 100644 packages/plugins/MaskBox/src/locales/en-US.json delete mode 100644 packages/plugins/MaskBox/src/locales/es-ES.json delete mode 100644 packages/plugins/MaskBox/src/locales/fa-IR.json delete mode 100644 packages/plugins/MaskBox/src/locales/fr-FR.json delete mode 100644 packages/plugins/MaskBox/src/locales/index.ts delete mode 100644 packages/plugins/MaskBox/src/locales/it-IT.json delete mode 100644 packages/plugins/MaskBox/src/locales/ja-JP.json delete mode 100644 packages/plugins/MaskBox/src/locales/ko-KR.json delete mode 100644 packages/plugins/MaskBox/src/locales/languages.ts delete mode 100644 packages/plugins/MaskBox/src/locales/qya-AA.json delete mode 100644 packages/plugins/MaskBox/src/locales/ru-RU.json delete mode 100644 packages/plugins/MaskBox/src/locales/zh-CN.json delete mode 100644 packages/plugins/MaskBox/src/locales/zh-TW.json delete mode 100644 packages/plugins/MaskBox/src/messages.ts delete mode 100644 packages/plugins/MaskBox/src/register.ts delete mode 100644 packages/plugins/MaskBox/src/type.ts delete mode 100644 packages/plugins/MaskBox/tsconfig.json delete mode 100644 packages/plugins/NextID/package.json delete mode 100644 packages/plugins/NextID/src/SiteAdaptor/VerificationPayload.tsx delete mode 100644 packages/plugins/NextID/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/NextID/src/base.ts delete mode 100644 packages/plugins/NextID/src/components/Actions/index.tsx delete mode 100644 packages/plugins/NextID/src/components/BindDialog.tsx delete mode 100644 packages/plugins/NextID/src/components/BindPanelUI.tsx delete mode 100644 packages/plugins/NextID/src/components/NextIdPage.tsx delete mode 100644 packages/plugins/NextID/src/constants.ts delete mode 100644 packages/plugins/NextID/src/env.d.ts delete mode 100644 packages/plugins/NextID/src/hooks/useBindPayload.ts delete mode 100644 packages/plugins/NextID/src/hooks/usePersonaSign.ts delete mode 100644 packages/plugins/NextID/src/hooks/useWalletSign.ts delete mode 100644 packages/plugins/NextID/src/locales/en-US.json delete mode 100644 packages/plugins/NextID/src/locales/index.ts delete mode 100644 packages/plugins/NextID/src/locales/ja-JP.json delete mode 100644 packages/plugins/NextID/src/locales/ko-KR.json delete mode 100644 packages/plugins/NextID/src/locales/languages.ts delete mode 100644 packages/plugins/NextID/src/locales/qya-AA.json delete mode 100644 packages/plugins/NextID/src/locales/zh-CN.json delete mode 100644 packages/plugins/NextID/src/locales/zh-TW.json delete mode 100644 packages/plugins/NextID/src/register.ts delete mode 100644 packages/plugins/NextID/tsconfig.json delete mode 100644 packages/plugins/Pets/package.json delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/Animate.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/Drag.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/ImageLoader.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/ModelView.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/NormalNFT.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/PetDialog.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/PetSetDialog.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/PetShareDialog.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/PetsGlobalInjection.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/PreviewBox.tsx delete mode 100644 packages/plugins/Pets/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Pets/src/assets/close.png delete mode 100644 packages/plugins/Pets/src/assets/defaultIcon.png delete mode 100644 packages/plugins/Pets/src/assets/glb3D.png delete mode 100644 packages/plugins/Pets/src/assets/info.png delete mode 100644 packages/plugins/Pets/src/assets/punk2d.png delete mode 100644 packages/plugins/Pets/src/base.tsx delete mode 100644 packages/plugins/Pets/src/constants.ts delete mode 100644 packages/plugins/Pets/src/hooks/index.ts delete mode 100644 packages/plugins/Pets/src/hooks/useEssay.ts delete mode 100644 packages/plugins/Pets/src/hooks/useNfts.ts delete mode 100644 packages/plugins/Pets/src/hooks/useUser.ts delete mode 100644 packages/plugins/Pets/src/locales/en-US.json delete mode 100644 packages/plugins/Pets/src/locales/index.ts delete mode 100644 packages/plugins/Pets/src/locales/ja-JP.json delete mode 100644 packages/plugins/Pets/src/locales/ko-KR.json delete mode 100644 packages/plugins/Pets/src/locales/languages.ts delete mode 100644 packages/plugins/Pets/src/locales/qya-AA.json delete mode 100644 packages/plugins/Pets/src/locales/zh-CN.json delete mode 100644 packages/plugins/Pets/src/locales/zh-TW.json delete mode 100644 packages/plugins/Pets/src/messages.ts delete mode 100644 packages/plugins/Pets/src/register.ts delete mode 100644 packages/plugins/Pets/src/settings.ts delete mode 100644 packages/plugins/Pets/src/types.ts delete mode 100644 packages/plugins/Pets/tsconfig.json delete mode 100644 packages/plugins/ProfileCard/package.json delete mode 100644 packages/plugins/ProfileCard/src/SiteAdaptor/AvatarBadge/AvatarBadge.tsx delete mode 100644 packages/plugins/ProfileCard/src/SiteAdaptor/AvatarBadge/CollectionProjectAvatarBadge.tsx delete mode 100644 packages/plugins/ProfileCard/src/SiteAdaptor/AvatarBadge/ProfileAvatarBadge.tsx delete mode 100644 packages/plugins/ProfileCard/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/ProfileCard/src/base.ts delete mode 100644 packages/plugins/ProfileCard/src/constants.ts delete mode 100644 packages/plugins/ProfileCard/src/locales/en-US.json delete mode 100644 packages/plugins/ProfileCard/src/locales/index.ts delete mode 100644 packages/plugins/ProfileCard/src/locales/ja-JP.json delete mode 100644 packages/plugins/ProfileCard/src/locales/ko-KR.json delete mode 100644 packages/plugins/ProfileCard/src/locales/languages.ts delete mode 100644 packages/plugins/ProfileCard/src/locales/qya-AA.json delete mode 100644 packages/plugins/ProfileCard/src/locales/zh-CN.json delete mode 100644 packages/plugins/ProfileCard/src/locales/zh-TW.json delete mode 100644 packages/plugins/ProfileCard/src/register.ts delete mode 100644 packages/plugins/ProfileCard/tsconfig.json delete mode 100644 packages/plugins/RSS3/package.json delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/FeedsPage.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/CollectibleApprovalCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/CollectibleCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/CommentCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/DonationCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/LensAvatar.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/LiquidityCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/NoteCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/ProfileCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/ProfileLinkCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/ProfileProxy.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/ProposeCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/StakingCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/TokenApprovalCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/TokenBridgeCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/TokenOperationCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/TokenSwapCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/UnknownCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/VoteCard.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/common.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/index.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/FeedCard/useMarkdownStyles.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/Slider.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/base.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/index.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/components/share.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/contexts/FeedOwnerContext.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/contexts/index.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/hooks/index.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/hooks/useAddressLabel.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/hooks/useFeeds.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/hooks/usePublicationId.ts delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/modals/DetailsModal/DetailDialog.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/modals/DetailsModal/index.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/modals/index.tsx delete mode 100644 packages/plugins/RSS3/src/SiteAdaptor/modals/modals.tsx delete mode 100644 packages/plugins/RSS3/src/base.ts delete mode 100644 packages/plugins/RSS3/src/constants.ts delete mode 100644 packages/plugins/RSS3/src/env.d.ts delete mode 100644 packages/plugins/RSS3/src/locales/en-US.json delete mode 100644 packages/plugins/RSS3/src/locales/index.ts delete mode 100644 packages/plugins/RSS3/src/locales/ja-JP.json delete mode 100644 packages/plugins/RSS3/src/locales/ko-KR.json delete mode 100644 packages/plugins/RSS3/src/locales/languages.ts delete mode 100644 packages/plugins/RSS3/src/locales/qya-AA.json delete mode 100644 packages/plugins/RSS3/src/locales/zh-CN.json delete mode 100644 packages/plugins/RSS3/src/locales/zh-TW.json delete mode 100644 packages/plugins/RSS3/src/register.ts delete mode 100644 packages/plugins/RSS3/tsconfig.json delete mode 100644 packages/plugins/Savings/package.json delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/IconURL.tsx delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/SavingsDialog.tsx delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/SavingsForm.tsx delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/SavingsTable/SavingsRow.tsx delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/SavingsTable/index.tsx delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/assets/aave.png delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/assets/lido.png delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/hooks/index.ts delete mode 100644 packages/plugins/Savings/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Savings/src/base.ts delete mode 100644 packages/plugins/Savings/src/constants.ts delete mode 100644 packages/plugins/Savings/src/locales/en-US.json delete mode 100644 packages/plugins/Savings/src/locales/index.ts delete mode 100644 packages/plugins/Savings/src/locales/ja-JP.json delete mode 100644 packages/plugins/Savings/src/locales/ko-KR.json delete mode 100644 packages/plugins/Savings/src/locales/languages.ts delete mode 100644 packages/plugins/Savings/src/locales/qya-AA.json delete mode 100644 packages/plugins/Savings/src/locales/zh-CN.json delete mode 100644 packages/plugins/Savings/src/locales/zh-TW.json delete mode 100644 packages/plugins/Savings/src/protocols/AAVEProtocol.ts delete mode 100644 packages/plugins/Savings/src/protocols/LDOProtocol.ts delete mode 100644 packages/plugins/Savings/src/register.ts delete mode 100644 packages/plugins/Savings/src/types.ts delete mode 100644 packages/plugins/Savings/tsconfig.json delete mode 100644 packages/plugins/ScamSniffer/package.json delete mode 100644 packages/plugins/ScamSniffer/src/SiteAdaptor/ScamAlert.tsx delete mode 100644 packages/plugins/ScamSniffer/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/ScamSniffer/src/Worker/index.ts delete mode 100644 packages/plugins/ScamSniffer/src/Worker/rpc.ts delete mode 100644 packages/plugins/ScamSniffer/src/base.ts delete mode 100644 packages/plugins/ScamSniffer/src/constants.ts delete mode 100644 packages/plugins/ScamSniffer/src/env.d.ts delete mode 100644 packages/plugins/ScamSniffer/src/locales/en-US.json delete mode 100644 packages/plugins/ScamSniffer/src/locales/index.ts delete mode 100644 packages/plugins/ScamSniffer/src/locales/ja-JP.json delete mode 100644 packages/plugins/ScamSniffer/src/locales/ko-KR.json delete mode 100644 packages/plugins/ScamSniffer/src/locales/languages.ts delete mode 100644 packages/plugins/ScamSniffer/src/locales/qya-AA.json delete mode 100644 packages/plugins/ScamSniffer/src/locales/zh-CN.json delete mode 100644 packages/plugins/ScamSniffer/src/locales/zh-TW.json delete mode 100644 packages/plugins/ScamSniffer/src/messages.ts delete mode 100644 packages/plugins/ScamSniffer/src/register.ts delete mode 100644 packages/plugins/ScamSniffer/tsconfig.json delete mode 100644 packages/plugins/ScamWarning/README.md delete mode 100644 packages/plugins/ScamWarning/package.json delete mode 100644 packages/plugins/ScamWarning/src/SiteAdaptor/components/PreviewCard.tsx delete mode 100644 packages/plugins/ScamWarning/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/ScamWarning/src/base.ts delete mode 100644 packages/plugins/ScamWarning/src/constants.ts delete mode 100644 packages/plugins/ScamWarning/src/env.d.ts delete mode 100644 packages/plugins/ScamWarning/src/locales/en-US.json delete mode 100644 packages/plugins/ScamWarning/src/locales/index.ts delete mode 100644 packages/plugins/ScamWarning/src/locales/ja-JP.json delete mode 100644 packages/plugins/ScamWarning/src/locales/ko-KR.json delete mode 100644 packages/plugins/ScamWarning/src/locales/languages.ts delete mode 100644 packages/plugins/ScamWarning/src/locales/qya-AA.json delete mode 100644 packages/plugins/ScamWarning/src/locales/zh-CN.json delete mode 100644 packages/plugins/ScamWarning/src/locales/zh-TW.json delete mode 100644 packages/plugins/ScamWarning/src/register.ts delete mode 100644 packages/plugins/ScamWarning/tsconfig.json delete mode 100644 packages/plugins/SmartPay/README.md delete mode 100644 packages/plugins/SmartPay/package.json delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/AccountsManagePopover.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/AddSmartPayPopover.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/CreateSuccessDialog.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/Deploy.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/InEligibilityTips.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/ManagePopover.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/ReceiveDialog.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/RouterDialog.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/SmartPayBanner.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/SmartPayContent.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/SmartPayDescriptionDialog.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/SmartPayDialog.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/components/SmartPayEntry.tsx delete mode 100644 packages/plugins/SmartPay/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/SmartPay/src/assets/banner.png delete mode 100644 packages/plugins/SmartPay/src/base.ts delete mode 100644 packages/plugins/SmartPay/src/constants.ts delete mode 100644 packages/plugins/SmartPay/src/env.d.ts delete mode 100644 packages/plugins/SmartPay/src/hooks/useDeploy.tsx delete mode 100644 packages/plugins/SmartPay/src/hooks/useManagers.ts delete mode 100644 packages/plugins/SmartPay/src/hooks/useQueryQualifications.ts delete mode 100644 packages/plugins/SmartPay/src/hooks/useSmartPayContext.ts delete mode 100644 packages/plugins/SmartPay/src/locales/en-US.json delete mode 100644 packages/plugins/SmartPay/src/locales/index.ts delete mode 100644 packages/plugins/SmartPay/src/locales/ja-JP.json delete mode 100644 packages/plugins/SmartPay/src/locales/ko-KR.json delete mode 100644 packages/plugins/SmartPay/src/locales/languages.ts delete mode 100644 packages/plugins/SmartPay/src/locales/qya-AA.json delete mode 100644 packages/plugins/SmartPay/src/locales/zh-CN.json delete mode 100644 packages/plugins/SmartPay/src/locales/zh-TW.json delete mode 100644 packages/plugins/SmartPay/src/message.ts delete mode 100644 packages/plugins/SmartPay/src/register.ts delete mode 100644 packages/plugins/SmartPay/src/type.ts delete mode 100644 packages/plugins/SmartPay/tsconfig.json delete mode 100644 packages/plugins/Snapshot/package.json delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/InformationCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/LoadingCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/LoadingFailCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/PluginDescriptor.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/PostInspector.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProfileCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProfileProposalList.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProfileSpaceHeader.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProfileView.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProgressTab.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ProposalTab.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ReadmeCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/ResultCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/Snapshot.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/SnapshotCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/SnapshotTab.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/SpaceMenu.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/VoteConfirmDialog.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/VotesCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/VotingCard.tsx delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/helpers.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useCurrentAccountFollowSpaceList.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useCurrentAccountVote.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/usePower.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useProposal.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useProposalList.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useResults.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useSpace.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/hooks/useVotes.ts delete mode 100644 packages/plugins/Snapshot/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Snapshot/src/Worker/apis.ts delete mode 100644 packages/plugins/Snapshot/src/Worker/index.ts delete mode 100644 packages/plugins/Snapshot/src/base.ts delete mode 100644 packages/plugins/Snapshot/src/constants.ts delete mode 100644 packages/plugins/Snapshot/src/context.ts delete mode 100644 packages/plugins/Snapshot/src/index.ts delete mode 100644 packages/plugins/Snapshot/src/json2csv.d.ts delete mode 100644 packages/plugins/Snapshot/src/locales/en-US.json delete mode 100644 packages/plugins/Snapshot/src/locales/index.ts delete mode 100644 packages/plugins/Snapshot/src/locales/ja-JP.json delete mode 100644 packages/plugins/Snapshot/src/locales/ko-KR.json delete mode 100644 packages/plugins/Snapshot/src/locales/languages.ts delete mode 100644 packages/plugins/Snapshot/src/locales/qya-AA.json delete mode 100644 packages/plugins/Snapshot/src/locales/zh-CN.json delete mode 100644 packages/plugins/Snapshot/src/locales/zh-TW.json delete mode 100644 packages/plugins/Snapshot/src/messages.ts delete mode 100644 packages/plugins/Snapshot/src/register.ts delete mode 100644 packages/plugins/Snapshot/src/types.ts delete mode 100644 packages/plugins/Snapshot/src/utils.ts delete mode 100644 packages/plugins/Snapshot/tsconfig.json delete mode 100644 packages/plugins/SwitchLogo/README.md delete mode 100644 packages/plugins/SwitchLogo/package.json delete mode 100644 packages/plugins/SwitchLogo/src/SiteAdaptor/SwitchLogoButton.tsx delete mode 100644 packages/plugins/SwitchLogo/src/SiteAdaptor/SwitchLogoDialog.tsx delete mode 100644 packages/plugins/SwitchLogo/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/SwitchLogo/src/base.ts delete mode 100644 packages/plugins/SwitchLogo/src/constants.ts delete mode 100644 packages/plugins/SwitchLogo/src/env.d.ts delete mode 100644 packages/plugins/SwitchLogo/src/index.ts delete mode 100644 packages/plugins/SwitchLogo/src/locales/en-US.json delete mode 100644 packages/plugins/SwitchLogo/src/locales/index.ts delete mode 100644 packages/plugins/SwitchLogo/src/locales/ja-JP.json delete mode 100644 packages/plugins/SwitchLogo/src/locales/ko-KR.json delete mode 100644 packages/plugins/SwitchLogo/src/locales/languages.ts delete mode 100644 packages/plugins/SwitchLogo/src/locales/qya-AA.json delete mode 100644 packages/plugins/SwitchLogo/src/locales/zh-CN.json delete mode 100644 packages/plugins/SwitchLogo/src/locales/zh-TW.json delete mode 100644 packages/plugins/SwitchLogo/src/register.ts delete mode 100644 packages/plugins/SwitchLogo/tsconfig.json delete mode 100644 packages/plugins/Tips/package.json delete mode 100644 packages/plugins/Tips/src/SiteAdaptor/components/TipsRealmContent/index.tsx delete mode 100644 packages/plugins/Tips/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Tips/src/base.ts delete mode 100644 packages/plugins/Tips/src/components/NFTSection/index.tsx delete mode 100644 packages/plugins/Tips/src/components/NetworkSection/index.tsx delete mode 100644 packages/plugins/Tips/src/components/RecipientSection/RecipientSelect.tsx delete mode 100644 packages/plugins/Tips/src/components/RecipientSection/index.tsx delete mode 100644 packages/plugins/Tips/src/components/TipDialog.tsx delete mode 100644 packages/plugins/Tips/src/components/TipsButton/index.tsx delete mode 100644 packages/plugins/Tips/src/components/TipsButton/useTipsAccounts.ts delete mode 100644 packages/plugins/Tips/src/components/TokenSection/GasSettingsBar.tsx delete mode 100644 packages/plugins/Tips/src/components/TokenSection/index.tsx delete mode 100644 packages/plugins/Tips/src/components/TokenSection/useGasLimit.ts delete mode 100644 packages/plugins/Tips/src/components/index.ts delete mode 100644 packages/plugins/Tips/src/contexts/TargetRuntimeContext.tsx delete mode 100644 packages/plugins/Tips/src/contexts/Tip/TipContext.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/TipTaskProvider.tsx delete mode 100644 packages/plugins/Tips/src/contexts/Tip/index.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/type.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/useNftTip.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/useRecipientValidate.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/useTipAccountsCompletion.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/useTipValidate.ts delete mode 100644 packages/plugins/Tips/src/contexts/Tip/useTokenTip.ts delete mode 100644 packages/plugins/Tips/src/contexts/TipTaskManager.tsx delete mode 100644 packages/plugins/Tips/src/contexts/index.ts delete mode 100644 packages/plugins/Tips/src/hooks/useProfilePublicKey.ts delete mode 100644 packages/plugins/Tips/src/index.ts delete mode 100644 packages/plugins/Tips/src/locales/en-US.json delete mode 100644 packages/plugins/Tips/src/locales/index.ts delete mode 100644 packages/plugins/Tips/src/locales/ja-JP.json delete mode 100644 packages/plugins/Tips/src/locales/ko-KR.json delete mode 100644 packages/plugins/Tips/src/locales/languages.ts delete mode 100644 packages/plugins/Tips/src/locales/qya-AA.json delete mode 100644 packages/plugins/Tips/src/locales/zh-CN.json delete mode 100644 packages/plugins/Tips/src/locales/zh-TW.json delete mode 100644 packages/plugins/Tips/src/messages.ts delete mode 100644 packages/plugins/Tips/src/register.ts delete mode 100644 packages/plugins/Tips/src/storage/index.ts delete mode 100644 packages/plugins/Tips/src/types/index.ts delete mode 100644 packages/plugins/Tips/src/types/tip.ts delete mode 100644 packages/plugins/Tips/src/types/validation.ts delete mode 100644 packages/plugins/Tips/tsconfig.json delete mode 100644 packages/plugins/Trader/package.json delete mode 100644 packages/plugins/Trader/src/README.md delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/cashTag.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trader/ExchangeDialog.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trader/ExchangeInjection.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/CoinIcon.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/CoinMarketPanel.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/CoinMarketTable.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/FailedTrendingView.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/NonFungibleTickersTable.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/PluginDescriptor.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/PriceChart.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/PriceChartDaysControl.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TagInspector.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TickersTable.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingCard.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingPopper.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingView.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingViewDeck.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingViewDescriptor.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/TrendingViewSkeleton.tsx delete mode 100644 packages/plugins/Trader/src/SiteAdaptor/trending/context.tsx delete mode 100644 packages/plugins/Trader/src/Worker/index.ts delete mode 100644 packages/plugins/Trader/src/apis/index.ts delete mode 100644 packages/plugins/Trader/src/assets/swap.png delete mode 100644 packages/plugins/Trader/src/base.ts delete mode 100644 packages/plugins/Trader/src/config.ts delete mode 100644 packages/plugins/Trader/src/constants/index.ts delete mode 100644 packages/plugins/Trader/src/constants/trending.ts delete mode 100644 packages/plugins/Trader/src/helpers/index.ts delete mode 100644 packages/plugins/Trader/src/helpers/resolveDaysName.ts delete mode 100644 packages/plugins/Trader/src/index.ts delete mode 100644 packages/plugins/Trader/src/locales/en-US.json delete mode 100644 packages/plugins/Trader/src/locales/index.ts delete mode 100644 packages/plugins/Trader/src/locales/ja-JP.json delete mode 100644 packages/plugins/Trader/src/locales/ko-KR.json delete mode 100644 packages/plugins/Trader/src/locales/languages.ts delete mode 100644 packages/plugins/Trader/src/locales/qya-AA.json delete mode 100644 packages/plugins/Trader/src/locales/zh-CN.json delete mode 100644 packages/plugins/Trader/src/locales/zh-TW.json delete mode 100644 packages/plugins/Trader/src/messages.ts delete mode 100644 packages/plugins/Trader/src/register.ts delete mode 100644 packages/plugins/Trader/src/trending/usePriceStats.ts delete mode 100644 packages/plugins/Trader/src/trending/useTrending.ts delete mode 100644 packages/plugins/Trader/src/types/index.ts delete mode 100644 packages/plugins/Trader/src/types/trader.ts delete mode 100644 packages/plugins/Trader/src/types/trending.ts delete mode 100644 packages/plugins/Trader/tsconfig.json delete mode 100644 packages/plugins/Transak/package.json delete mode 100644 packages/plugins/Transak/src/SiteAdaptor/BuyTokenDialog.tsx delete mode 100644 packages/plugins/Transak/src/SiteAdaptor/BuyTokenGlobalInjection.tsx delete mode 100644 packages/plugins/Transak/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Transak/src/assets/fiat_ramp.png delete mode 100644 packages/plugins/Transak/src/base.ts delete mode 100644 packages/plugins/Transak/src/constants.ts delete mode 100644 packages/plugins/Transak/src/env.d.ts delete mode 100644 packages/plugins/Transak/src/hooks/useTransakAllowanceCoin.ts delete mode 100644 packages/plugins/Transak/src/hooks/useTransakURL.ts delete mode 100644 packages/plugins/Transak/src/index.ts delete mode 100644 packages/plugins/Transak/src/locales/en-US.json delete mode 100644 packages/plugins/Transak/src/locales/index.ts delete mode 100644 packages/plugins/Transak/src/locales/ja-JP.json delete mode 100644 packages/plugins/Transak/src/locales/ko-KR.json delete mode 100644 packages/plugins/Transak/src/locales/languages.ts delete mode 100644 packages/plugins/Transak/src/locales/qya-AA.json delete mode 100644 packages/plugins/Transak/src/locales/zh-CN.json delete mode 100644 packages/plugins/Transak/src/locales/zh-TW.json delete mode 100644 packages/plugins/Transak/src/messages.ts delete mode 100644 packages/plugins/Transak/src/register.ts delete mode 100644 packages/plugins/Transak/src/types.ts delete mode 100644 packages/plugins/Transak/tsconfig.json delete mode 100644 packages/plugins/VCent/package.json delete mode 100644 packages/plugins/VCent/src/README.md delete mode 100644 packages/plugins/VCent/src/SiteAdaptor/TweetDialog.tsx delete mode 100644 packages/plugins/VCent/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/VCent/src/Worker/index.ts delete mode 100644 packages/plugins/VCent/src/apis/index.ts delete mode 100644 packages/plugins/VCent/src/base.ts delete mode 100644 packages/plugins/VCent/src/constants.ts delete mode 100644 packages/plugins/VCent/src/env.d.ts delete mode 100644 packages/plugins/VCent/src/locales/en-US.json delete mode 100644 packages/plugins/VCent/src/locales/index.ts delete mode 100644 packages/plugins/VCent/src/locales/ja-JP.json delete mode 100644 packages/plugins/VCent/src/locales/ko-KR.json delete mode 100644 packages/plugins/VCent/src/locales/languages.ts delete mode 100644 packages/plugins/VCent/src/locales/qya-AA.json delete mode 100644 packages/plugins/VCent/src/locales/zh-CN.json delete mode 100644 packages/plugins/VCent/src/locales/zh-TW.json delete mode 100644 packages/plugins/VCent/src/messages.ts delete mode 100644 packages/plugins/VCent/src/register.ts delete mode 100644 packages/plugins/VCent/tsconfig.json delete mode 100644 packages/plugins/Web3Profile/package.json delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/Web3ProfileGlobalInjection.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/assets/Lens.png delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/assets/TwitterXRound.svg delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterBadge.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterList.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterPopup.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/FollowLensDialog.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/HandlerDescription.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensBadge.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensList.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensPopup.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfileCard.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfilePopup.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/components/Web3ProfileDialog.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/context.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/emitter.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/ConfettiExplosion/Confetto.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/ConfettiExplosion/Sequin.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/ConfettiExplosion/index.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/ConfettiExplosion/utils.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Farcaster/useControlFarcasterPopup.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Lens/useControlLensPopup.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Lens/useFollow.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Lens/useQueryAuthenticate.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Lens/useUnfollow.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/Lens/useUpdateFollowingStatus.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/index.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/hooks/usePersona.ts delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/Web3Profile/src/SiteAdaptor/types.ts delete mode 100644 packages/plugins/Web3Profile/src/base.ts delete mode 100644 packages/plugins/Web3Profile/src/constants.ts delete mode 100644 packages/plugins/Web3Profile/src/env.d.ts delete mode 100644 packages/plugins/Web3Profile/src/locales/en-US.json delete mode 100644 packages/plugins/Web3Profile/src/locales/index.ts delete mode 100644 packages/plugins/Web3Profile/src/locales/ja-JP.json delete mode 100644 packages/plugins/Web3Profile/src/locales/ko-KR.json delete mode 100644 packages/plugins/Web3Profile/src/locales/languages.ts delete mode 100644 packages/plugins/Web3Profile/src/locales/qya-AA.json delete mode 100644 packages/plugins/Web3Profile/src/locales/zh-CN.json delete mode 100644 packages/plugins/Web3Profile/src/locales/zh-TW.json delete mode 100644 packages/plugins/Web3Profile/src/register.ts delete mode 100644 packages/plugins/Web3Profile/src/utils.ts delete mode 100644 packages/plugins/Web3Profile/tsconfig.json delete mode 100644 packages/plugins/template/README.md delete mode 100644 packages/plugins/template/package.json delete mode 100644 packages/plugins/template/src/SiteAdaptor/index.tsx delete mode 100644 packages/plugins/template/src/base.ts delete mode 100644 packages/plugins/template/src/constants.ts delete mode 100644 packages/plugins/template/src/env.d.ts delete mode 100644 packages/plugins/template/src/locales/en-US.json delete mode 100644 packages/plugins/template/src/locales/index.ts delete mode 100644 packages/plugins/template/src/locales/ja-JP.json delete mode 100644 packages/plugins/template/src/locales/ko-KR.json delete mode 100644 packages/plugins/template/src/locales/languages.ts delete mode 100644 packages/plugins/template/src/locales/qya-AA.json delete mode 100644 packages/plugins/template/src/locales/zh-CN.json delete mode 100644 packages/plugins/template/src/locales/zh-TW.json delete mode 100644 packages/plugins/template/src/register.ts delete mode 100644 packages/plugins/template/tsconfig.json delete mode 100644 packages/scripts/src/extension/ci.ts delete mode 100644 packages/scripts/src/extension/dotenv.ts delete mode 100644 packages/scripts/src/extension/flags.ts delete mode 100644 packages/scripts/src/extension/gulp-zip.d.ts delete mode 100644 packages/scripts/src/extension/index.ts delete mode 100644 packages/scripts/src/extension/normal.ts delete mode 100644 packages/scripts/src/extension/web-ext.d.ts delete mode 100644 packages/shared-base/src/Histories/Popups.ts delete mode 100644 packages/shared-base/src/Histories/index.ts delete mode 100644 packages/shared/src/UI/components/CountryCodePicker/index.tsx delete mode 100644 packages/shared/src/UI/components/FileFrame/index.tsx delete mode 100644 packages/shared/src/UI/components/PersonaGuard/index.tsx delete mode 100644 packages/shared/src/UI/components/PhoneNumberField/index.tsx delete mode 100644 packages/shared/src/UI/components/PluginGuide/index.tsx delete mode 100644 packages/shared/src/UI/components/SocialIcon/index.tsx delete mode 100644 packages/shared/src/UI/components/SourceProviderSwitcher/index.tsx delete mode 100644 packages/shared/src/UI/components/SourceSwitcher/index.tsx delete mode 100644 packages/shared/src/UI/components/TokenAmountPanel/index.tsx delete mode 100644 packages/shared/src/UI/components/TokenSecurity/Common.tsx delete mode 100644 packages/shared/src/UI/components/TokenSecurity/index.tsx delete mode 100644 packages/shared/src/UI/components/UploadDropArea/index.tsx delete mode 100644 packages/shared/src/UI/components/WalletSettingsCard/WalletSettingsCardUI.tsx delete mode 100644 packages/shared/src/UI/components/WalletSettingsCard/index.tsx delete mode 100644 packages/shared/src/hooks/useCollectionByTwitterHandle.ts delete mode 100644 packages/shared/src/hooks/useDimension.ts delete mode 100644 packages/shared/src/hooks/useLineChart/index.ts delete mode 100644 packages/shared/src/hooks/useLineChart/tests/utils.test.ts delete mode 100644 packages/shared/src/hooks/useLineChart/utils.ts delete mode 100644 packages/shared/src/hooks/useOpenApplicationSettings.ts delete mode 100644 packages/shared/src/hooks/useParamTab.ts delete mode 100644 packages/shared/src/hooks/usePriceLineChart.ts delete mode 100644 packages/shared/src/hooks/useTokenSecurity.ts delete mode 100644 packages/shared/src/utils/identifierSelector.ts delete mode 100644 packages/shared/src/utils/resolveValueToSearch.ts delete mode 100644 packages/web3-constants/evm/gopluslabs.json delete mode 100644 packages/web3-constants/evm/smart-pay.json delete mode 100644 packages/web3-contracts/abis/Airdrop.json delete mode 100644 packages/web3-contracts/abis/AirdropV2.json delete mode 100644 packages/web3-contracts/abis/CryptoPunks.json delete mode 100644 packages/web3-contracts/abis/FriendTech.json delete mode 100644 packages/web3-hooks/base/src/useSnapshotSpacesByTwitterHandle.ts delete mode 100644 packages/web3-hooks/base/src/useWallet.ts delete mode 100644 packages/web3-hooks/base/src/useWallets.ts delete mode 100644 packages/web3-providers/src/Airdrop/index.ts delete mode 100644 packages/web3-providers/src/FriendTech/constants.ts delete mode 100644 packages/web3-providers/src/FriendTech/index.ts delete mode 100644 packages/web3-providers/src/GoPlusLabs/constants.ts delete mode 100644 packages/web3-providers/src/GoPlusLabs/index.ts delete mode 100644 packages/web3-providers/src/GoPlusLabs/rules.ts delete mode 100644 packages/web3-providers/src/GoPlusLabs/types.ts delete mode 100644 packages/web3-providers/src/SmartPay/apis/AbstractAccountAPI.ts delete mode 100644 packages/web3-providers/src/SmartPay/apis/BundlerAPI.ts delete mode 100644 packages/web3-providers/src/SmartPay/apis/FunderAPI.ts delete mode 100644 packages/web3-providers/src/SmartPay/apis/OwnerAPI.ts delete mode 100644 packages/web3-providers/src/SmartPay/constants.ts delete mode 100644 packages/web3-providers/src/SmartPay/index.ts delete mode 100644 packages/web3-providers/src/SmartPay/libs/ContractWallet.ts delete mode 100644 packages/web3-providers/src/SmartPay/libs/Create2Factory.ts delete mode 100644 packages/web3-providers/src/SmartPay/libs/DepositPaymaster.ts delete mode 100644 packages/web3-providers/src/SmartPay/libs/UserTransaction.ts delete mode 100644 packages/web3-providers/src/Snapshot/apis/provider.ts delete mode 100644 packages/web3-providers/src/Snapshot/apis/search.ts delete mode 100644 packages/web3-providers/src/Snapshot/index.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/interceptors/MaskWallet.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/interceptors/Popups.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/middleware/RecentTransaction.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/providers/MaskWallet.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/state/TransactionFormatter/descriptors/Airdrop.ts delete mode 100644 packages/web3-providers/src/Web3/EVM/state/TransactionFormatter/descriptors/MaskBox.ts delete mode 100644 packages/web3-providers/src/types/Security.ts delete mode 100644 packages/xcode/.gitignore delete mode 100644 packages/xcode/Mask Network/Mask Network Extension/Info.plist delete mode 100644 packages/xcode/Mask Network/Mask Network Extension/Mask_Network_Extension.entitlements delete mode 100644 packages/xcode/Mask Network/Mask Network Extension/SafariWebExtensionHandler.swift delete mode 100644 packages/xcode/Mask Network/Mask Network.xcodeproj/project.pbxproj delete mode 100644 packages/xcode/Mask Network/Mask Network.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/xcode/Mask Network/Mask Network.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/xcode/Mask Network/Mask Network/AppDelegate.swift delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AppIcon.appiconset/128x128.png delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AppIcon.appiconset/16x16.png delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AppIcon.appiconset/256x256 1.png delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AppIcon.appiconset/256x256.png delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/Contents.json delete mode 100644 packages/xcode/Mask Network/Mask Network/Assets.xcassets/LargeIcon.imageset/Contents.json delete mode 100644 packages/xcode/Mask Network/Mask Network/Base.lproj/Main.html delete mode 100644 packages/xcode/Mask Network/Mask Network/Base.lproj/Main.storyboard delete mode 100644 packages/xcode/Mask Network/Mask Network/Mask Network.entitlements delete mode 100644 packages/xcode/Mask Network/Mask Network/Mask_Network.entitlements delete mode 100644 packages/xcode/Mask Network/Mask Network/Resources/Script.js delete mode 100644 packages/xcode/Mask Network/Mask Network/Resources/Style.css delete mode 100644 packages/xcode/Mask Network/Mask Network/ViewController.swift delete mode 100644 packages/xcode/package.json delete mode 100644 patches/@splinetool__runtime@0.9.342.patch diff --git a/.i18n-codegen.json b/.i18n-codegen.json index a9395a1f305a..104718180bb7 100644 --- a/.i18n-codegen.json +++ b/.i18n-codegen.json @@ -2,32 +2,6 @@ "$schema": "./node_modules/@magic-works/i18n-codegen/schema.json", "version": 1, "list": [ - { - "input": "./packages/mask/shared-ui/locales/en-US.json", - "output": "./packages/mask/shared-ui/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useMaskSharedTrans", - "namespace": "mask", - "trans": "MaskSharedTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/mask/content-script/site-adaptors/twitter.com/locales/en-US.json", - "output": "./packages/mask/content-script/site-adaptors/twitter.com/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useDO_NOT_USE", - "namespace": "DO_NOT_USE", - "trans": "DO_NOT_USE_TRANSLATOR", - "emitTS": true, - "shouldUnescape": true - } - }, { "input": "./packages/shared/src/locales/en-US.json", "output": "./packages/shared/src/locales/i18n_generated", @@ -54,136 +28,6 @@ "shouldUnescape": true } }, - { - "input": "./packages/mask/dashboard/locales/en-US.json", - "output": "./packages/mask/dashboard/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useDashboardTrans", - "namespace": "dashboard", - "trans": "DashboardTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Debugger/src/locales/en-US.json", - "output": "./packages/plugins/Debugger/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useDebuggerTrans", - "namespace": "io.mask.debugger", - "trans": "DebuggerTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/FileService/src/locales/en-US.json", - "output": "./packages/plugins/FileService/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useFileServiceTrans", - "namespace": "com.maskbook.fileservice", - "trans": "FileServiceTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/ScamSniffer/src/locales/en-US.json", - "output": "./packages/plugins/ScamSniffer/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useScamSnifferTrans", - "namespace": "io.scamsniffer.mask-plugin", - "trans": "ScamSnifferTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/CyberConnect/src/locales/en-US.json", - "output": "./packages/plugins/CyberConnect/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useCyberConnectTrans", - "namespace": "me.cyberconnect.app", - "trans": "CyberConnectTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/RSS3/src/locales/en-US.json", - "output": "./packages/plugins/RSS3/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useRSS3Trans", - "namespace": "bio.rss3", - "trans": "RSS3Trans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/NextID/src/locales/en-US.json", - "output": "./packages/plugins/NextID/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useNextID_Trans", - "namespace": "com.mask.next_id", - "trans": "NextID_Trans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/template/src/locales/en-US.json", - "output": "./packages/plugins/template/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useTemplateTrans", - "namespace": "__template__", - "trans": "TemplateTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/GoPlusSecurity/src/locales/en-US.json", - "output": "./packages/plugins/GoPlusSecurity/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useGoPlusLabsTrans", - "namespace": "io.gopluslabs.security", - "trans": "GoPlusLabsTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/CrossChainBridge/src/locales/en-US.json", - "output": "./packages/plugins/CrossChainBridge/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useCrossChainBridgeTrans", - "namespace": "io.mask.cross-chain-bridge", - "trans": "CrossChainBridgeTrans", - "emitTS": true, - "shouldUnescape": true - } - }, { "input": "./packages/plugins/RedPacket/src/locales/en-US.json", "output": "./packages/plugins/RedPacket/src/locales/i18n_generated", @@ -197,260 +41,6 @@ "shouldUnescape": true } }, - { - "input": "./packages/plugins/Tips/src/locales/en-US.json", - "output": "./packages/plugins/Tips/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useTipsTrans", - "namespace": "com.maskbook.tip", - "trans": "TipsTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Avatar/src/locales/en-US.json", - "output": "./packages/plugins/Avatar/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useAvatarTrans", - "namespace": "com.maskbook.avatar", - "trans": "AvatarTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Trader/src/locales/en-US.json", - "output": "./packages/plugins/Trader/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useTraderTrans", - "namespace": "com.maskbook.trader", - "trans": "TraderTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Gitcoin/src/locales/en-US.json", - "output": "./packages/plugins/Gitcoin/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useGitcoinTrans", - "namespace": "co.gitcoin", - "trans": "GitcoinTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/MaskBox/src/locales/en-US.json", - "output": "./packages/plugins/MaskBox/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useMaskBoxTrans", - "namespace": "com.maskbook.box", - "trans": "MaskBoxTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Pets/src/locales/en-US.json", - "output": "./packages/plugins/Pets/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "usePetsTrans", - "namespace": "com.maskbook.pets", - "trans": "PetsTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Web3Profile/src/locales/en-US.json", - "output": "./packages/plugins/Web3Profile/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useWeb3ProfileTrans", - "namespace": "io.mask.web3-profile", - "trans": "Web3ProfileTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Handle/src/locales/en-US.json", - "output": "./packages/plugins/Handle/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useHandleTrans", - "namespace": "com.maskbook.handle", - "trans": "HandleTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Approval/src/locales/en-US.json", - "output": "./packages/plugins/Approval/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useApprovalTrans", - "namespace": "com.maskbook.approval", - "trans": "ApprovalTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/ScamWarning/src/locales/en-US.json", - "output": "./packages/plugins/ScamWarning/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useScamWarningTrans", - "namespace": "com.mask.scam-warning", - "trans": "ScamWarningTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/SmartPay/src/locales/en-US.json", - "output": "./packages/plugins/SmartPay/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSmartPayTrans", - "namespace": "com.mask.smart-pay", - "trans": "SmartPayTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/VCent/src/locales/en-US.json", - "output": "./packages/plugins/VCent/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useVCentTrans", - "namespace": "com.maskbook.tweet", - "trans": "VCentTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Transak/src/locales/en-US.json", - "output": "./packages/plugins/Transak/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useTransakTrans", - "namespace": "com.maskbook.transak", - "trans": "TransakTrans", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Collectible/src/locales/en-US.json", - "output": "./packages/plugins/Collectible/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useCollectibleTrans", - "namespace": "com.maskbook.collectibles", - "trans": "Collectible", - "emitTS": true, - "shouldUnescape": true - } - }, - { - "input": "./packages/plugins/Claim/src/locales/en-US.json", - "output": "./packages/plugins/Claim/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useClaimTrans", - "namespace": "com.mask.claim", - "trans": "ClaimTrans", - "emitTS": true - } - }, - { - "input": "./packages/plugins/ArtBlocks/src/locales/en-US.json", - "output": "./packages/plugins/ArtBlocks/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useArtBlocksTrans", - "namespace": "io.artblocks", - "trans": "ArtBlocksTrans", - "emitTS": true - } - }, - { - "input": "./packages/plugins/Savings/src/locales/en-US.json", - "output": "./packages/plugins/Savings/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSavingsTrans", - "namespace": "com.savings", - "trans": "SavingsTrans", - "emitTS": true - } - }, - { - "input": "./packages/plugins/Snapshot/src/locales/en-US.json", - "output": "./packages/plugins/Snapshot/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSnapshotTrans", - "namespace": "org.snapshot", - "trans": "SnapshotTrans", - "emitTS": true - } - }, - { - "input": "./packages/plugins/ProfileCard/src/locales/en-US.json", - "output": "./packages/plugins/ProfileCard/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useProfileCardTrans", - "namespace": "io.mask.web3-profile-card", - "trans": "ProfileCardTrans", - "emitTS": true - } - }, - { - "input": "./packages/plugins/SwitchLogo/src/locales/en-US.json", - "output": "./packages/plugins/SwitchLogo/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSwitchLogoTrans", - "namespace": "io.mask.switch-logo", - "trans": "SwitchLogoTrans", - "emitTS": true - } - }, { "input": "./packages/plugins/Calendar/src/locales/en-US.json", "output": "./packages/plugins/Calendar/src/locales/i18n_generated", @@ -462,18 +52,6 @@ "trans": "CalendarTrans", "emitTS": true } - }, - { - "input": "./packages/plugins/FriendTech/src/locales/en-US.json", - "output": "./packages/plugins/FriendTech/src/locales/i18n_generated", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "io.mask.friend-tech", - "trans": "Translate", - "emitTS": true - } } ] } diff --git a/eslint.config.js b/eslint.config.js index 199be62b58d6..e91c35289c84 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -465,24 +465,6 @@ const moduleSystemRules = { 'error', { zones: [ - { - target: './packages/mask/background/**', - from: './packages/mask/shared-ui/', - message: 'Background cannot import Ui specific code.', - }, - { - target: './packages/mask/!(background)/**', - from: './packages/mask/background/', - message: 'Use Services.* instead.', - }, - { - target: './packages/mask/', - from: [ - './packages/plugin-infra/src/dom/context.ts', - './packages/plugin-infra/src/site-adaptor/context.ts', - ], - message: 'Use Services.* instead.', - }, // ideally shared folder should also bans import plugin context // but that requires a lot of context passing. we leave it as a legacy escape path. { @@ -570,7 +552,6 @@ export default tseslint.config( '**/languages.ts', 'packages/contracts', 'packages/scripts', - 'packages/mask/.webpack', ], }, { @@ -596,13 +577,6 @@ export default tseslint.config( ...moduleSystemRules, }, }, - { - files: ['packages/mask/background/**/*.ts'], - plugins, - rules: { - 'no-restricted-globals': ['error', 'setTimeout', 'setInterval'], - }, - }, { files: ['packages/**/tests/**/*.ts'], rules: { diff --git a/knip.ts b/knip.ts index 91f03311b2fa..a1cc65d42648 100644 --- a/knip.ts +++ b/knip.ts @@ -11,22 +11,6 @@ const config: KnipConfig = { entry: ['*.js', '*.cjs'], ignoreDependencies: ['@typescript/lib-dom', 'ses', 'eslint-import-resolver-typescript', 'vite'], }, - 'packages/mask': { - ignore: ['public'], - entry: [ - '.webpack/webpack.config.ts', - 'background/initialization/mv2-entry.ts', - 'background/initialization/mv3-entry.ts', - 'dashboard/initialization/index.ts', - 'popups/initialization/index.ts', - 'swap/initialization/index.ts', - 'content-script/index.ts', - 'web-workers/wallet.ts', - 'devtools/content-script/index.ts', - 'devtools/panels/index.tsx', - ], - ignoreDependencies: ['webpack-cli'], - }, 'packages/web3-constants': { entry: ['constants.ts'], }, @@ -37,15 +21,11 @@ const config: KnipConfig = { ignore: ['main/debugger.ts'], entry: ['main/index.ts'], }, - 'packages/mask-sdk': { - ignore: ['public-api'], - entry: ['main/index.ts'], - }, 'packages/sentry': { ignoreDependencies: ['@sentry/browser'], }, }, - ignoreWorkspaces: ['packages/polyfills', 'packages/sandboxed-plugins', 'packages/xcode'], + ignoreWorkspaces: ['packages/polyfills', 'packages/sandboxed-plugins'], ignoreDependencies: ['buffer', 'https-browserify', 'punycode'], } diff --git a/package.json b/package.json index 6c31f2b37e72..6f7696f8b8e0 100644 --- a/package.json +++ b/package.json @@ -123,28 +123,20 @@ } }, "patchedDependencies": { - "@ceramicnetwork/rpc-transport@0.3.1": "patches/@ceramicnetwork__rpc-transport@0.3.1.patch", "micromark@3.1.0": "patches/micromark@3.1.0.patch", "micromark-util-symbol@1.0.1": "patches/micromark-util-symbol@1.0.1.patch", - "@types/react-avatar-editor@13.0.0": "patches/@types__react-avatar-editor@13.0.0.patch", "rss3-next@0.6.17": "patches/rss3-next@0.6.17.patch", "@project-serum/sol-wallet-adapter@0.2.6": "patches/@project-serum__sol-wallet-adapter@0.2.6.patch", - "@types/react-highlight-words@0.16.4": "patches/@types__react-highlight-words@0.16.4.patch", - "@cyberlab/cyberconnect@4.2.2": "patches/@cyberlab__cyberconnect@4.2.2.patch", "fortmatic@2.2.1": "patches/fortmatic@2.2.1.patch", "reflect-metadata@0.1.13": "patches/reflect-metadata@0.1.13.patch", "bloom-filters@3.0.0": "patches/bloom-filters@3.0.0.patch", "urlcat@3.1.0": "patches/urlcat@3.1.0.patch", "@chainsafe/as-sha256@0.3.1": "patches/@chainsafe__as-sha256@0.3.1.patch", "@protobufjs/inquire@1.1.0": "patches/@protobufjs__inquire@1.1.0.patch", - "@splinetool/runtime@0.9.342": "patches/@splinetool__runtime@0.9.342.patch", "web3-core@1.10.2": "patches/web3-core@1.10.2.patch", - "react-devtools-inline@4.28.5": "patches/react-devtools-inline@4.28.5.patch", "eslint-plugin-i@2.29.1": "patches/eslint-plugin-i@2.29.1.patch", "@mui/material@5.15.12": "patches/@mui__material@5.15.12.patch", - "@lifi/widget@2.10.1": "patches/@lifi__widget@2.10.1.patch", "@mui/base@5.0.0-beta.38": "patches/@mui__base@5.0.0-beta.38.patch", - "@lifi/wallet-management@2.6.0": "patches/@lifi__wallet-management@2.6.0.patch", "gulp@4.0.2": "patches/gulp@4.0.2.patch", "react-use@17.4.0": "patches/react-use@17.4.0.patch" } diff --git a/packages/backup-format/README.md b/packages/backup-format/README.md deleted file mode 100644 index cdae998a068b..000000000000 --- a/packages/backup-format/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Backup format - -## Backup file container - -Binary format: - -```plain -Magic header (MASK-BACKUP-V001): 16 bytes -Data: Arbitrary length -Checksum (SHA-256): 32 bytes -``` diff --git a/packages/backup-format/package.json b/packages/backup-format/package.json deleted file mode 100644 index 4384686afae0..000000000000 --- a/packages/backup-format/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@masknet/backup-format", - "private": true, - "sideEffects": false, - "type": "module", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "mask-src": "./src/index.ts", - "default": "./dist/index.js" - } - }, - "types": "./dist/index.d.ts", - "dependencies": { - "@masknet/shared-base": "workspace:^", - "@msgpack/msgpack": "^3.0.0-beta2", - "elliptic": "^6.5.4", - "pvtsutils": "^1.3.5" - }, - "devDependencies": { - "@types/elliptic": "^6.4.14" - } -} diff --git a/packages/backup-format/src/BackupErrors.ts b/packages/backup-format/src/BackupErrors.ts deleted file mode 100644 index 8891c3d3fcac..000000000000 --- a/packages/backup-format/src/BackupErrors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum BackupErrors { - UnknownFormat = '[@masknet/backup-format] Unknown format.', - WrongCheckSum = '[@masknet/backup-format] Bad checksum.', -} diff --git a/packages/backup-format/src/container/index.ts b/packages/backup-format/src/container/index.ts deleted file mode 100644 index 8e2b1d0baf6d..000000000000 --- a/packages/backup-format/src/container/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { unreachable, concatArrayBuffer } from '@masknet/kit' -import { BackupErrors } from '../BackupErrors.js' - -const MAGIC_HEADER_Version0 = new TextEncoder().encode('MASK-BACKUP-V000') -const CHECKSUM_LENGTH = 32 - -/** @internal */ -export enum SupportedVersions { - Version0 = 0, -} -function getMagicHeader(version: SupportedVersions) { - if (version === 0) return MAGIC_HEADER_Version0 - unreachable(version) -} - -/** @internal */ -export async function createContainer(version: SupportedVersions, data: ArrayBuffer) { - const checksum = await crypto.subtle.digest({ name: 'SHA-256' }, data) - return concatArrayBuffer(getMagicHeader(version), data, checksum) -} - -/** @internal */ -export async function parseEncryptedJSONContainer(version: SupportedVersions, _container: ArrayBuffer) { - const container = new Uint8Array(_container) - - for (const [index, value] of getMagicHeader(version).entries()) { - if (container[index] !== value) throw new TypeError(BackupErrors.UnknownFormat) - } - - const data = container.slice(MAGIC_HEADER_Version0.length, -CHECKSUM_LENGTH) - const sum = new Uint8Array(await crypto.subtle.digest({ name: 'SHA-256' }, data)) - - for (const [index, value] of container.slice(-CHECKSUM_LENGTH).entries()) { - if (sum[index] !== value) throw new TypeError(BackupErrors.WrongCheckSum) - } - - return data -} diff --git a/packages/backup-format/src/index.ts b/packages/backup-format/src/index.ts deleted file mode 100644 index 9e4909af9ca0..000000000000 --- a/packages/backup-format/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { encryptBackup, decryptBackup } from './version-3/index.js' -export { BackupErrors } from './BackupErrors.js' -export { - normalizeBackup, - type NormalizedBackup, - createEmptyNormalizedBackup, - generateBackupRAW, -} from './normalize/index.js' -export { getBackupSummary, type BackupSummary } from './utils/backupPreview.js' diff --git a/packages/backup-format/src/normalize/index.ts b/packages/backup-format/src/normalize/index.ts deleted file mode 100644 index b568d74d83d9..000000000000 --- a/packages/backup-format/src/normalize/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { None } from 'ts-results-es' -import { BackupErrors } from '../BackupErrors.js' -import { isBackupVersion0, normalizeBackupVersion0 } from '../version-0/index.js' -import { isBackupVersion1, normalizeBackupVersion1 } from '../version-1/index.js' -import { generateBackupVersion2, isBackupVersion2, normalizeBackupVersion2 } from '../version-2/index.js' -import type { NormalizedBackup } from './type.js' - -export * from './type.js' -async function __normalizeBackup(data: unknown): Promise { - if (isBackupVersion2(data)) return normalizeBackupVersion2(data) - if (isBackupVersion1(data)) return normalizeBackupVersion1(data) - if (isBackupVersion0(data)) return normalizeBackupVersion0(data) - throw new TypeError(BackupErrors.UnknownFormat) -} - -export async function normalizeBackup(data: unknown): Promise { - const normalized = await __normalizeBackup(data) - - // fix invalid URL - normalized.settings.grantedHostPermissions = normalized.settings.grantedHostPermissions.filter((url) => - /^(http|)/.test(url), - ) - return normalized -} - -/** It will return the internal format. DO NOT rely on the detail of it! */ -export function generateBackupRAW(data: NormalizedBackup.Data): unknown { - const result = generateBackupVersion2(data) - return result -} - -export function createEmptyNormalizedBackup(): NormalizedBackup.Data { - return { - meta: { version: 2, createdAt: None, maskVersion: None }, - personas: new Map(), - profiles: new Map(), - posts: new Map(), - relations: [], - settings: { grantedHostPermissions: [] }, - wallets: [], - plugins: {}, - } -} diff --git a/packages/backup-format/src/normalize/type.ts b/packages/backup-format/src/normalize/type.ts deleted file mode 100644 index c98a11f29efc..000000000000 --- a/packages/backup-format/src/normalize/type.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { - PersonaIdentifier, - EC_Private_JsonWebKey, - EC_Public_JsonWebKey, - AESJsonWebKey, - ProfileIdentifier, - RelationFavor, - PostIVIdentifier, - ECKeyIdentifier, -} from '@masknet/shared-base' -import type { Option } from 'ts-results-es' - -// All optional type in this file is marked by Option because we don't want to miss any field. -export namespace NormalizedBackup { - export interface Data { - /** Meta about this backup */ - meta: Meta - personas: Map - profiles: Map - relations: RelationBackup[] - posts: Map - wallets: WalletBackup[] - settings: SettingsBackup - plugins: Record - } - export interface Meta { - /** Backup file version */ - version: 0 | 1 | 2 - /** Backup created by which Mask version */ - maskVersion: Option - createdAt: Option - } - export interface PersonaBackup { - identifier: PersonaIdentifier - mnemonic: Option - publicKey: EC_Public_JsonWebKey - privateKey: Option - localKey: Option - linkedProfiles: Map - nickname: Option - createdAt: Option - updatedAt: Option - address: Option - } - export interface Mnemonic { - words: string - path: string - hasPassword: boolean - } - export interface ProfileBackup { - identifier: ProfileIdentifier - nickname: Option - localKey: Option - linkedPersona: Option - createdAt: Option - updatedAt: Option - } - export interface RelationBackup { - profile: ProfileIdentifier | ECKeyIdentifier - persona: PersonaIdentifier - favor: RelationFavor - } - export interface PostBackup { - identifier: PostIVIdentifier - postBy: Option - postCryptoKey: Option - recipients: Option - foundAt: Date - encryptBy: Option - url: Option - summary: Option - interestedMeta: ReadonlyMap - } - export interface PostReceiverPublic { - type: 'public' - } - export interface PostReceiverE2E { - type: 'e2e' - receivers: Map - } - export interface RecipientReason { - type: 'auto-share' | 'direct' | 'group' - // We don't care about this field anymore. Do not wrap it with Option - group?: unknown - at: Date - } - export interface WalletBackup { - address: string - name: string - mnemonicId: Option - derivationPath: Option - passphrase: Option - publicKey: Option - privateKey: Option - mnemonic: Option - createdAt: Date - updatedAt: Date - } - export interface SettingsBackup { - grantedHostPermissions: string[] - } -} diff --git a/packages/backup-format/src/utils/backupPreview.ts b/packages/backup-format/src/utils/backupPreview.ts deleted file mode 100644 index 4ddfc407e446..000000000000 --- a/packages/backup-format/src/utils/backupPreview.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { NormalizedBackup } from '@masknet/backup-format' -import { compact, flatten, sumBy } from 'lodash-es' - -export interface BackupSummary { - personas: string[] - accounts: number - posts: number - contacts: number - relations: number - files: number - wallets: string[] - createdAt: number - countOfWallets: number -} - -export function getBackupSummary(json: NormalizedBackup.Data): BackupSummary { - let files = 0 - - try { - files = Number((json.plugins['com.maskbook.fileservice'] as any)?.length || 0) - } catch {} - - const ownerPersonas = [...json.personas.values()].filter((persona) => !persona.privateKey.isNone()) - const ownerProfiles = flatten(ownerPersonas.map((persona) => [...persona.linkedProfiles.keys()])).map((item) => - item.toText(), - ) - - const personas = compact( - ownerPersonas - .sort((p) => (p.nickname.unwrapOr(false) ? -1 : 0)) - .map((p) => p.nickname.unwrapOr(p.identifier.rawPublicKey).trim()), - ) - const contacts = [...json.profiles.values()].filter((profile) => { - return !ownerProfiles.includes(profile.identifier.toText()) && profile.linkedPersona.isSome() - }) - return { - // Names or publicKeys */ - personas, - accounts: sumBy(ownerPersonas, (persona) => persona.linkedProfiles.size), - posts: json.posts.size, - contacts: contacts.length, - relations: json.relations.length, - files, - wallets: json.wallets.map((wallet) => wallet.address), - createdAt: Number(json.meta.createdAt.unwrapOr(undefined)), - countOfWallets: 0, - } -} diff --git a/packages/backup-format/src/utils/hex2buffer.ts b/packages/backup-format/src/utils/hex2buffer.ts deleted file mode 100644 index 691ee45e9e91..000000000000 --- a/packages/backup-format/src/utils/hex2buffer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { sum } from 'lodash-es' - -/** @internal */ -export function hex2buffer(hexString: string, padded?: boolean) { - if (hexString.length % 2) { - hexString = '0' + hexString - } - let res = new Uint8Array(hexString.length / 2) - for (let i = 0; i < hexString.length; i += 2) { - const c = hexString.slice(i, i + 2) - res[(i - 1) / 2] = Number.parseInt(c, 16) - } - // BN padding - if (padded) { - let len = res.length - len = - len > 32 ? - len > 48 ? - 66 - : 48 - : 32 - if (res.length < len) { - res = concat(new Uint8Array(len - res.length), res) - } - } - return res -} - -/** @internal */ -function concat(...buf: Array) { - const res = new Uint8Array(sum(buf.map((item) => item.length))) - let offset = 0 - buf.forEach((item) => { - for (let i = 0; i < item.length; i += 1) { - res[offset + i] = item[i] - } - offset += item.length - }) - return res -} diff --git a/packages/backup-format/src/version-0/index.ts b/packages/backup-format/src/version-0/index.ts deleted file mode 100644 index 413af1a0a832..000000000000 --- a/packages/backup-format/src/version-0/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - type AESJsonWebKey, - type EC_Private_JsonWebKey, - type EC_Public_JsonWebKey, - isAESJsonWebKey, - isEC_Private_JsonWebKey, - isEC_JsonWebKey, - ProfileIdentifier, - ECKeyIdentifier, -} from '@masknet/shared-base' -import { isObjectLike } from 'lodash-es' -import { None, Some } from 'ts-results-es' -import { createEmptyNormalizedBackup } from '../normalize/index.js' -import type { NormalizedBackup } from '../normalize/type.js' - -export function isBackupVersion0(obj: unknown): obj is BackupJSONFileVersion0 { - if (!isObjectLike(obj)) return false - try { - const data: BackupJSONFileVersion0 = obj as any - if (!data.local || !data.key?.key?.privateKey || !data.key.key.publicKey) return false - return true - } catch { - return false - } -} -export async function normalizeBackupVersion0(file: BackupJSONFileVersion0): Promise { - const backup = createEmptyNormalizedBackup() - backup.meta.version = 0 - backup.meta.maskVersion = Some('<=1.3.2') - - const { local } = file - const { username, key } = file.key - const { publicKey, privateKey } = key - - if (!isEC_JsonWebKey(publicKey)) return backup - - const persona: NormalizedBackup.PersonaBackup = { - identifier: (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap(), - publicKey, - linkedProfiles: new Map(), - localKey: isAESJsonWebKey(local) ? Some(local) : None, - privateKey: isEC_Private_JsonWebKey(privateKey) ? Some(privateKey) : None, - mnemonic: None, - nickname: None, - createdAt: None, - updatedAt: None, - address: None, - } - backup.personas.set(persona.identifier, persona) - - const identifier = ProfileIdentifier.of('facebook.com', username) - if (identifier.isSome()) { - const profile: NormalizedBackup.ProfileBackup = { - identifier: identifier.value, - linkedPersona: Some(persona.identifier), - createdAt: None, - updatedAt: None, - localKey: isAESJsonWebKey(local) ? Some(local) : None, - nickname: None, - } - backup.profiles.set(profile.identifier, profile) - persona.linkedProfiles.set(profile.identifier, void 0) - } - - return backup -} -interface BackupJSONFileVersion0 { - key: { - username: string - key: { publicKey: EC_Public_JsonWebKey; privateKey?: EC_Private_JsonWebKey } - algor: unknown - usages: string[] - } - local: AESJsonWebKey -} diff --git a/packages/backup-format/src/version-1/index.ts b/packages/backup-format/src/version-1/index.ts deleted file mode 100644 index 4475db020acf..000000000000 --- a/packages/backup-format/src/version-1/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - type AESJsonWebKey, - type EC_Private_JsonWebKey, - type EC_Public_JsonWebKey, - isEC_Private_JsonWebKey, - isEC_JsonWebKey, - isAESJsonWebKey, - ProfileIdentifier, - ECKeyIdentifier, -} from '@masknet/shared-base' -import { isObjectLike } from 'lodash-es' -import { None, Some } from 'ts-results-es' -import { createEmptyNormalizedBackup } from '../normalize/index.js' -import type { NormalizedBackup } from '../normalize/type.js' - -export function isBackupVersion1(obj: unknown): obj is BackupJSONFileVersion1 { - if (!isObjectLike(obj)) return false - try { - const data: BackupJSONFileVersion1 = obj as any - if (data.version !== 1) return false - if (!Array.isArray(data.whoami)) return false - if (!data.whoami) return false - return true - } catch { - return false - } -} -export async function normalizeBackupVersion1(file: BackupJSONFileVersion1): Promise { - const backup = createEmptyNormalizedBackup() - - backup.meta.version = 1 - if (!file.grantedHostPermissions) backup.meta.maskVersion = Some('<=1.5.2') - else if (!file.maskbookVersion) backup.meta.maskVersion = Some('<=1.6.0') - - if (file.grantedHostPermissions) { - backup.settings.grantedHostPermissions = file.grantedHostPermissions - } - - const { whoami, people } = file - for (const { network, publicKey, userId, nickname, localKey, privateKey } of [...whoami, ...(people || [])]) { - const identifier = ProfileIdentifier.of(network, userId).expect( - `backup should not contain invalid identifier parts ${network} and ${userId}`, - ) - const profile: NormalizedBackup.ProfileBackup = { - identifier, - nickname: nickname ? Some(nickname) : None, - createdAt: None, - updatedAt: None, - localKey: None, - linkedPersona: None, - } - - if (isEC_JsonWebKey(publicKey)) { - const personaID = (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap() - const persona: NormalizedBackup.PersonaBackup = backup.personas.get(personaID) || { - identifier: personaID, - nickname: None, - linkedProfiles: new Map(), - publicKey, - privateKey: None, - localKey: None, - mnemonic: None, - createdAt: None, - updatedAt: None, - address: None, - } - profile.linkedPersona = Some(personaID) - - if (isEC_Private_JsonWebKey(privateKey)) { - persona.privateKey = Some(privateKey) - } - backup.personas.set(personaID, persona) - persona.linkedProfiles.set(profile.identifier, void 0) - } - if (isAESJsonWebKey(localKey)) { - profile.localKey = Some(localKey) - if (profile.linkedPersona.isSome() && backup.personas.has(profile.linkedPersona.value)) { - backup.personas.get(profile.linkedPersona.value)!.localKey = Some(localKey) - } - } - } - - return backup -} - -interface BackupJSONFileVersion1 { - maskbookVersion?: string - version: 1 - whoami: Array<{ - network: string - userId: string - publicKey: EC_Public_JsonWebKey - privateKey: EC_Private_JsonWebKey - localKey: AESJsonWebKey - previousIdentifiers?: Array<{ network: string; userId: string }> - nickname?: string - }> - people?: Array<{ - network: string - userId: string - publicKey: EC_Public_JsonWebKey - previousIdentifiers?: Array<{ network: string; userId: string }> - nickname?: string - groups?: Array<{ network: string; groupID: string; virtualGroupOwner: string | null }> - - // Note: those props are not existed in the backup, just to make the code more readable - privateKey?: EC_Private_JsonWebKey - localKey?: AESJsonWebKey - }> - grantedHostPermissions?: string[] -} diff --git a/packages/backup-format/src/version-2/index.ts b/packages/backup-format/src/version-2/index.ts deleted file mode 100644 index d6fff3db8c03..000000000000 --- a/packages/backup-format/src/version-2/index.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { decodeArrayBuffer, encodeArrayBuffer, safeUnreachable } from '@masknet/kit' -import { - ECKeyIdentifier, - isAESJsonWebKey, - isEC_Private_JsonWebKey, - isEC_Public_JsonWebKey, - PostIVIdentifier, - ProfileIdentifier, - type RelationFavor, -} from '@masknet/shared-base' -import __ from 'elliptic' -import { Convert } from 'pvtsutils' -import { decode, encode } from '@msgpack/msgpack' -import { None, Some } from 'ts-results-es' -import { createEmptyNormalizedBackup } from '../normalize/index.js' -import type { NormalizedBackup } from '../normalize/type.js' -import { hex2buffer } from '../utils/hex2buffer.js' - -export function isBackupVersion2(item: unknown): item is BackupJSONFileVersion2 { - try { - const x = item as BackupJSONFileVersion2 - return x._meta_.version === 2 - } catch {} - return false -} - -export async function normalizeBackupVersion2(item: BackupJSONFileVersion2): Promise { - const backup = createEmptyNormalizedBackup() - - backup.meta.version = 2 - backup.meta.maskVersion = Some(item._meta_.maskbookVersion) - backup.meta.createdAt = Some(new Date(item._meta_.createdAt)) - backup.settings.grantedHostPermissions = item.grantedHostPermissions - - const { personas, posts, profiles, relations, wallets, plugin } = item - - for (const persona of personas) { - const { publicKey } = persona - if (!isEC_Public_JsonWebKey(publicKey)) continue - const identifier = (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap() - const normalizedPersona: NormalizedBackup.PersonaBackup = { - identifier, - linkedProfiles: new Map(), - publicKey, - privateKey: isEC_Private_JsonWebKey(persona.privateKey) ? Some(persona.privateKey) : None, - localKey: isAESJsonWebKey(persona.localKey) ? Some(persona.localKey) : None, - createdAt: Some(new Date(persona.createdAt)), - updatedAt: Some(new Date(persona.updatedAt)), - nickname: persona.nickname ? Some(persona.nickname) : None, - mnemonic: None, - address: persona.address ? Some(persona.address) : None, - } - for (const [profile] of persona.linkedProfiles) { - const id = ProfileIdentifier.from(profile) - if (id.isNone()) continue - normalizedPersona.linkedProfiles.set(id.value, null) - } - if (persona.mnemonic) { - const { words, parameter } = persona.mnemonic - normalizedPersona.mnemonic = Some({ words, hasPassword: parameter.withPassword, path: parameter.path }) - } - - backup.personas.set(identifier, normalizedPersona) - } - - for (const profile of profiles) { - const identifier = ProfileIdentifier.from(profile.identifier) - if (identifier.isNone()) continue - const normalizedProfile: NormalizedBackup.ProfileBackup = { - identifier: identifier.value, - createdAt: Some(new Date(profile.createdAt)), - updatedAt: Some(new Date(profile.updatedAt)), - nickname: profile.nickname ? Some(profile.nickname) : None, - linkedPersona: ECKeyIdentifier.from(profile.linkedPersona), - localKey: isAESJsonWebKey(profile.localKey) ? Some(profile.localKey) : None, - } - backup.profiles.set(identifier.value, normalizedProfile) - } - - for (const persona of backup.personas.values()) { - const toRemove: ProfileIdentifier[] = [] - for (const profile of persona.linkedProfiles.keys()) { - if (backup.profiles.get(profile)?.linkedPersona.unwrapOr(undefined) === persona.identifier) { - // do nothing - } else toRemove.push(profile) - } - for (const profile of toRemove) persona.linkedProfiles.delete(profile) - } - - for (const post of posts) { - const identifier = PostIVIdentifier.from(post.identifier) - const postBy = ProfileIdentifier.from(post.postBy) - const encryptBy = ECKeyIdentifier.from(post.encryptBy) - - if (identifier.isNone()) continue - const interestedMeta = new Map() - const normalizedPost: NormalizedBackup.PostBackup = { - identifier: identifier.value, - foundAt: new Date(post.foundAt), - postBy, - interestedMeta, - encryptBy, - summary: post.summary ? Some(post.summary) : None, - url: post.url ? Some(post.url) : None, - postCryptoKey: isAESJsonWebKey(post.postCryptoKey) ? Some(post.postCryptoKey) : None, - recipients: None, - } - - if (post.recipients) { - if (post.recipients === 'everyone') - normalizedPost.recipients = Some({ type: 'public' }) - else { - const map = new Map() - for (const [recipient, { reason }] of post.recipients) { - const id = ProfileIdentifier.from(recipient) - if (id.isNone()) continue - const reasons: NormalizedBackup.RecipientReason[] = [] - map.set(id.value, reasons) - for (const r of reason) { - // we ignore the original reason because we no longer support group / auto sharing - reasons.push({ type: 'direct', at: new Date(r.at) }) - } - } - normalizedPost.recipients = Some({ type: 'e2e', receivers: map }) - } - } - if (post.interestedMeta) normalizedPost.interestedMeta = MetaFromJson(post.interestedMeta) - - backup.posts.set(identifier.value, normalizedPost) - } - - for (const relation of relations || []) { - const { profile, persona, favor } = relation - const a = ProfileIdentifier.from(profile) - const b = ECKeyIdentifier.from(persona) - if (a.isSome() && b.isSome()) { - backup.relations.push({ - profile: a.value, - persona: b.value, - favor, - }) - } - } - - for (const wallet of wallets || []) { - if (wallet.privateKey?.d && !wallet.publicKey) { - // @ts-expect-error cjs-esm interop - const ec = new (__.ec || __.default.ec)('secp256k1') - const key = ec.keyFromPrivate(wallet.privateKey.d) - const hexPub = key.getPublic('hex').slice(2) - const hexX = hexPub.slice(0, hexPub.length / 2) - const hexY = hexPub.slice(hexPub.length / 2, hexPub.length) - wallet.privateKey.x = Convert.ToBase64Url(hex2buffer(hexX)) - wallet.privateKey.y = Convert.ToBase64Url(hex2buffer(hexY)) - } - const normalizedWallet: NormalizedBackup.WalletBackup = { - address: wallet.address, - name: wallet.name, - passphrase: wallet.passphrase ? Some(wallet.passphrase) : None, - mnemonicId: wallet.mnemonicId ? Some(wallet.mnemonicId) : None, - derivationPath: wallet.derivationPath ? Some(wallet.derivationPath) : None, - publicKey: isEC_Public_JsonWebKey(wallet.publicKey) ? Some(wallet.publicKey) : None, - privateKey: isEC_Private_JsonWebKey(wallet.privateKey) ? Some(wallet.privateKey) : None, - mnemonic: - wallet.mnemonic ? - Some({ - words: wallet.mnemonic.words, - hasPassword: wallet.mnemonic.parameter.withPassword, - path: wallet.mnemonic.parameter.path, - }) - : None, - createdAt: new Date(wallet.createdAt), - updatedAt: new Date(wallet.updatedAt), - } - backup.wallets.push(normalizedWallet) - } - - backup.plugins = plugin || {} - - return backup -} - -export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONFileVersion2 { - const now = new Date() - const result: BackupJSONFileVersion2 = { - _meta_: { - maskbookVersion: item.meta.maskVersion.unwrapOr('>=2.5.0'), - createdAt: Number(item.meta.createdAt.unwrapOr(now)), - type: 'maskbook-backup', - version: 2, - }, - grantedHostPermissions: item.settings.grantedHostPermissions, - plugin: item.plugins, - personas: [], - posts: [], - profiles: [], - relations: [], - wallets: [], - userGroups: [], - } - for (const [id, data] of item.personas) { - result.personas.push({ - identifier: id.toText(), - createdAt: Number(data.createdAt.unwrapOr(now)), - updatedAt: Number(data.updatedAt.unwrapOr(now)), - nickname: data.nickname.unwrapOr(undefined), - linkedProfiles: [...data.linkedProfiles.keys()].map((id) => [ - id.toText(), - { connectionConfirmState: 'confirmed' } as LinkedProfileDetails, - ]), - publicKey: data.publicKey, - privateKey: data.privateKey.unwrapOr(undefined), - mnemonic: data.mnemonic - .map((data) => ({ - words: data.words, - parameter: { path: data.path, withPassword: data.hasPassword }, - })) - .unwrapOr(undefined), - localKey: data.localKey.unwrapOr(undefined), - }) - } - - for (const [id, data] of item.profiles) { - result.profiles.push({ - identifier: id.toText(), - createdAt: Number(data.createdAt.unwrapOr(now)), - updatedAt: Number(data.updatedAt.unwrapOr(now)), - nickname: data.nickname.unwrapOr(undefined), - linkedPersona: data.linkedPersona.unwrapOr(undefined)?.toText(), - localKey: data.localKey.unwrapOr(undefined), - }) - } - - for (const [id, data] of item.posts) { - const item: BackupJSONFileVersion2['posts'][0] = { - identifier: id.toText(), - foundAt: Number(data.foundAt), - postBy: data.postBy.isSome() ? data.postBy.value.toText() : 'person:localhost/$unknown', - interestedMeta: MetaToJson(data.interestedMeta), - encryptBy: data.encryptBy.unwrapOr(undefined)?.toText(), - summary: data.summary.unwrapOr(undefined), - url: data.url.unwrapOr(undefined), - postCryptoKey: data.postCryptoKey.unwrapOr(undefined), - recipientGroups: [], - recipients: [], - } - result.posts.push(item) - if (data.recipients.isSome()) { - if (data.recipients.value.type === 'public') item.recipients = 'everyone' - else if (data.recipients.value.type === 'e2e') { - item.recipients = [] - for (const [recipient, reasons] of data.recipients.value.receivers) { - if (!reasons.length) continue - item.recipients.push([ - recipient.toText(), - { - reason: [ - { - at: Number(reasons[0].at), - type: 'direct', - }, - ], - }, - ]) - } - } else safeUnreachable(data.recipients.value) - } - } - - for (const data of item.relations) { - result.relations!.push({ - profile: data.profile.toText(), - persona: data.persona.toText(), - favor: data.favor, - }) - } - - for (const data of item.wallets) { - result.wallets!.push({ - address: data.address, - name: data.name, - passphrase: data.passphrase.unwrapOr(undefined), - publicKey: data.publicKey.unwrapOr(undefined), - privateKey: data.privateKey.unwrapOr(undefined), - mnemonic: data.mnemonic - .map((data) => ({ - words: data.words, - parameter: { path: data.path, withPassword: data.hasPassword }, - })) - .unwrapOr(undefined), - createdAt: Number(data.createdAt), - updatedAt: Number(data.updatedAt), - derivationPath: data.derivationPath.unwrapOr(undefined), - mnemonicId: data.mnemonicId.unwrapOr(undefined), - }) - } - return result -} - -function MetaFromJson(meta: string | undefined): Map { - if (!meta) return new Map() - const raw = decode(decodeArrayBuffer(meta)) - if (typeof raw !== 'object' || !raw) return new Map() - return new Map(Object.entries(raw)) -} -function MetaToJson(meta: ReadonlyMap) { - return encodeArrayBuffer(encode(Object.fromEntries(meta.entries()))) -} - -/** - * @see https://github.com/DimensionDev/Maskbook/issues/194 - */ -interface BackupJSONFileVersion2 { - _meta_: { - version: 2 - type: 'maskbook-backup' - maskbookVersion: string // e.g. "1.8.0" - createdAt: number // Unix timestamp - } - personas: Array<{ - // ? PersonaIdentifier can be infer from the publicKey - identifier: string // PersonaIdentifier.toText() - mnemonic?: { - words: string - parameter: { path: string; withPassword: boolean } - } - publicKey: JsonWebKey - privateKey?: JsonWebKey - localKey?: JsonWebKey - nickname?: string - linkedProfiles: Array<[/** ProfileIdentifier.toText() */ string, LinkedProfileDetails]> - createdAt: number // Unix timestamp - updatedAt: number // Unix timestamp - address?: string - }> - profiles: Array<{ - identifier: string // ProfileIdentifier.toText() - nickname?: string - localKey?: JsonWebKey - linkedPersona?: string // PersonaIdentifier.toText() - createdAt: number // Unix timestamp - updatedAt: number // Unix timestamp - }> - relations?: Array<{ - profile: string // ProfileIdentifier.toText() - persona: string // PersonaIdentifier.toText() - favor: RelationFavor - }> - /** @deprecated */ - userGroups: never[] - posts: Array<{ - postBy: string // ProfileIdentifier.toText() - identifier: string // PostIVIdentifier.toText() - postCryptoKey?: JsonWebKey - recipients: 'everyone' | Array<[/** ProfileIdentifier.toText() */ string, { reason: RecipientReasonJSON[] }]> - /** @deprecated */ - recipientGroups: never[] - foundAt: number // Unix timestamp - encryptBy?: string // PersonaIdentifier.toText() - url?: string - summary?: string - interestedMeta?: string // encoded by MessagePack - }> - wallets?: Array<{ - address: string - name: string - passphrase?: string - publicKey?: JsonWebKey - privateKey?: JsonWebKey - mnemonic?: { - words: string - parameter: { path: string; withPassword: boolean } - } - createdAt: number // Unix timestamp - updatedAt: number // Unix timestamp - mnemonicId?: string - derivationPath?: string - }> - grantedHostPermissions: string[] - plugin?: Record -} - -interface LinkedProfileDetails { - connectionConfirmState: 'confirmed' | 'pending' | 'denied' -} - -type RecipientReasonJSON = ( - | { type: 'auto-share' } - | { type: 'direct' } - | { type: 'group'; /** @deprecated */ group: unknown } -) & { - at: number -} diff --git a/packages/backup-format/src/version-3/index.ts b/packages/backup-format/src/version-3/index.ts deleted file mode 100644 index 77b5ecc7d5b2..000000000000 --- a/packages/backup-format/src/version-3/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { decode, encode } from '@msgpack/msgpack' -import { createContainer, parseEncryptedJSONContainer, SupportedVersions } from '../container/index.js' -import { BackupErrors } from '../BackupErrors.js' - -export async function encryptBackup(password: BufferSource, binaryBackup: BufferSource) { - const [pbkdf2IV, AESKey] = await createAESFromPassword(password) - const AESParam: AesGcmParams = { name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(16)) } - - const encrypted = new Uint8Array(await crypto.subtle.encrypt(AESParam, AESKey, binaryBackup)) - const container = encode([pbkdf2IV, AESParam.iv, encrypted]) - return createContainer(SupportedVersions.Version0, container) -} - -export async function decryptBackup(password: BufferSource, data: ArrayBuffer) { - const container = await parseEncryptedJSONContainer(SupportedVersions.Version0, data) - - const _ = decode(container) - if (!Array.isArray(_) || _.length !== 3) throw new TypeError(BackupErrors.UnknownFormat) - if (!_.every((x): x is Uint8Array => x instanceof Uint8Array)) throw new TypeError(BackupErrors.UnknownFormat) - const [pbkdf2IV, encryptIV, encrypted] = _ - - const aes = await getAESFromPassword(password, pbkdf2IV) - - const AESParam: AesGcmParams = { name: 'AES-GCM', iv: encryptIV } - const decryptedBackup = await crypto.subtle.decrypt(AESParam, aes, encrypted) - return decryptedBackup -} - -async function createAESFromPassword(password: BufferSource) { - const pbkdf = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey']) - const iv = crypto.getRandomValues(new Uint8Array(16)) - const aes = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: iv, iterations: 10000, hash: 'SHA-256' }, - pbkdf, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'], - ) - return [iv, aes] as const -} - -async function getAESFromPassword(password: BufferSource, iv: Uint8Array) { - const pbkdf = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey']) - const aes = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: iv, iterations: 10000, hash: 'SHA-256' }, - pbkdf, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'], - ) - return aes -} diff --git a/packages/backup-format/tests/encryption.ts b/packages/backup-format/tests/encryption.ts deleted file mode 100644 index 424bc9d077ed..000000000000 --- a/packages/backup-format/tests/encryption.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { test, expect, beforeAll } from 'vitest' -import { webcrypto } from 'crypto' -import { encryptBackup, decryptBackup } from '../src/index.js' - -beforeAll(() => { - Reflect.set(globalThis, 'crypto', webcrypto) -}) - -const rawData = new Uint8Array([4, 5, 6]) -const testData = new Uint8Array([ - 77, 65, 83, 75, 45, 66, 65, 67, 75, 85, 80, 45, 86, 48, 48, 48, 147, 196, 16, 246, 104, 235, 238, 199, 70, 129, 183, - 82, 183, 204, 172, 98, 189, 231, 224, 196, 16, 237, 35, 98, 148, 79, 117, 119, 53, 249, 154, 178, 4, 144, 24, 141, - 165, 196, 19, 2, 53, 83, 21, 28, 73, 245, 184, 178, 219, 72, 182, 96, 141, 138, 201, 114, 163, 61, 82, 63, 146, 102, - 206, 147, 218, 15, 110, 204, 205, 252, 41, 114, 194, 18, 156, 183, 171, 55, 23, 109, 55, 107, 181, 122, 241, 200, - 182, 24, 138, 144, -]) - -test('Old data can be still decrypted', async () => { - const password = Uint8Array.from('password'.split('').map((x) => x.charCodeAt(0))) - const decrypted = await decryptBackup(password, testData) - expect(new Uint8Array(decrypted)).toEqual(rawData) -}) - -test('decrypt(password, encrypt(password, data)) === data', async () => { - const password = Uint8Array.from('password'.split('').map((x) => x.charCodeAt(0))) - const data = new Uint8Array([4, 5, 6]) - - const result = await encryptBackup(password, data) - const decrypted = await decryptBackup(password, result) - expect(new Uint8Array(decrypted)).toEqual(new Uint8Array(decrypted)) -}) diff --git a/packages/backup-format/tsconfig.json b/packages/backup-format/tsconfig.json deleted file mode 100644 index 1b10e8f1de10..000000000000 --- a/packages/backup-format/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src/", - "outDir": "./dist/", - "tsBuildInfoFile": "./dist/.tsbuildinfo" - }, - "include": ["./src", "./src/**/*.json"], - "references": [{ "path": "../shared-base/tsconfig.json" }] -} diff --git a/packages/backup-format/tsconfig.tests.json b/packages/backup-format/tsconfig.tests.json deleted file mode 100644 index 5ddf66a9482e..000000000000 --- a/packages/backup-format/tsconfig.tests.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.leaf.json", - "compilerOptions": { - "rootDir": "./tests/", - "tsBuildInfoFile": "./dist/tests.tsbuildinfo" - }, - "include": ["./tests"], - "references": [{ "path": "./tsconfig.json" }] -} diff --git a/packages/icons/icon-generated-as-jsx.js b/packages/icons/icon-generated-as-jsx.js index 01ea2c11035f..175ceda42efe 100644 --- a/packages/icons/icon-generated-as-jsx.js +++ b/packages/icons/icon-generated-as-jsx.js @@ -69,14 +69,6 @@ export const CyberConnect = /*#__PURE__*/ __createIcon('CyberConnect', [ { u: () => new URL('./brands/CyberConnect.svg', import.meta.url).href, }, - { - c: ['dark'], - u: () => new URL('./plugins/CyberConnect.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./plugins/CyberConnect.light.svg', import.meta.url).href, - }, ]) export const Danger = /*#__PURE__*/ __createIcon('Danger', [ { @@ -3750,16 +3742,6 @@ export const MenuWalletsActive = /*#__PURE__*/ __createIcon('MenuWalletsActive', u: () => new URL('./menus/MenuWalletsActive.png', import.meta.url).href, }, ]) -export const Approval = /*#__PURE__*/ __createIcon('Approval', [ - { - u: () => new URL('./plugins/Approval.svg', import.meta.url).href, - }, -]) -export const ArtBlocks = /*#__PURE__*/ __createIcon('ArtBlocks', [ - { - u: () => new URL('./plugins/ArtBlocks.png', import.meta.url).href, - }, -]) export const Avatar = /*#__PURE__*/ __createIcon('Avatar', [ { j: () => @@ -3790,11 +3772,6 @@ export const Collectibles = /*#__PURE__*/ __createIcon('Collectibles', [ u: () => new URL('./plugins/Collectibles.svg', import.meta.url).href, }, ]) -export const CrossBridge = /*#__PURE__*/ __createIcon('CrossBridge', [ - { - u: () => new URL('./plugins/CrossBridge.png', import.meta.url).href, - }, -]) export const DecentralizedSearch = /*#__PURE__*/ __createIcon('DecentralizedSearch', [ { u: () => new URL('./plugins/DecentralizedSearch.svg', import.meta.url).href, @@ -3810,46 +3787,6 @@ export const ENSCover = /*#__PURE__*/ __createIcon('ENSCover', [ u: () => new URL('./plugins/ENSCover.svg', import.meta.url).href, }, ]) -export const FileService = /*#__PURE__*/ __createIcon('FileService', [ - { - u: () => new URL('./plugins/FileService.svg', import.meta.url).href, - }, -]) -export const FindTruman = /*#__PURE__*/ __createIcon('FindTruman', [ - { - u: () => new URL('./plugins/FindTruman.png', import.meta.url).href, - }, -]) -export const FriendTech = /*#__PURE__*/ __createIcon('FriendTech', [ - { - u: () => new URL('./plugins/FriendTech.svg', import.meta.url).href, - }, -]) -export const Gitcoin = /*#__PURE__*/ __createIcon('Gitcoin', [ - { - c: ['dark'], - u: () => new URL('./plugins/Gitcoin.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./plugins/Gitcoin.light.svg', import.meta.url).href, - }, -]) -export const GoodGhosting = /*#__PURE__*/ __createIcon('GoodGhosting', [ - { - c: ['dark'], - u: () => new URL('./plugins/GoodGhosting.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./plugins/GoodGhosting.light.svg', import.meta.url).href, - }, -]) -export const Markets = /*#__PURE__*/ __createIcon('Markets', [ - { - u: () => new URL('./plugins/Markets.png', import.meta.url).href, - }, -]) export const MarketsClaim = /*#__PURE__*/ __createIcon('MarketsClaim', [ { u: () => new URL('./plugins/MarketsClaim.svg', import.meta.url).href, @@ -3865,16 +3802,6 @@ export const NFTAvatar = /*#__PURE__*/ __createIcon('NFTAvatar', [ u: () => new URL('./plugins/NFTAvatar.svg', import.meta.url).href, }, ]) -export const PoolTogether = /*#__PURE__*/ __createIcon('PoolTogether', [ - { - u: () => new URL('./plugins/PoolTogether.png', import.meta.url).href, - }, -]) -export const Savings = /*#__PURE__*/ __createIcon('Savings', [ - { - u: () => new URL('./plugins/Savings.svg', import.meta.url).href, - }, -]) export const ScamSniffer = /*#__PURE__*/ __createIcon('ScamSniffer', [ { u: () => new URL('./plugins/ScamSniffer.svg', import.meta.url).href, @@ -3900,31 +3827,11 @@ export const Shared = /*#__PURE__*/ __createIcon('shared', [ u: () => new URL('./plugins/shared.svg', import.meta.url).href, }, ]) -export const Snapshot = /*#__PURE__*/ __createIcon('Snapshot', [ - { - u: () => new URL('./plugins/Snapshot.svg', import.meta.url).href, - }, -]) export const SpaceId = /*#__PURE__*/ __createIcon('SpaceId', [ { u: () => new URL('./plugins/SpaceId.svg', import.meta.url).href, }, ]) -export const TipCoin = /*#__PURE__*/ __createIcon('TipCoin', [ - { - u: () => new URL('./plugins/TipCoin.svg', import.meta.url).href, - }, -]) -export const Transak = /*#__PURE__*/ __createIcon('Transak', [ - { - u: () => new URL('./plugins/Transak.png', import.meta.url).href, - }, -]) -export const Unstoppable = /*#__PURE__*/ __createIcon('Unstoppable', [ - { - u: () => new URL('./plugins/Unstoppable.svg', import.meta.url).href, - }, -]) export const Valuables = /*#__PURE__*/ __createIcon('Valuables', [ { c: ['dark'], @@ -3945,210 +3852,6 @@ export const Web3ProfileCard = /*#__PURE__*/ __createIcon('Web3ProfileCard', [ u: () => new URL('./plugins/Web3ProfileCard.svg', import.meta.url).href, }, ]) -export const AchievementBurn = /*#__PURE__*/ __createIcon('AchievementBurn', [ - { - u: () => new URL('./rss3/AchievementBurn.svg', import.meta.url).href, - }, -]) -export const AchievementReceive = /*#__PURE__*/ __createIcon('AchievementReceive', [ - { - u: () => new URL('./rss3/AchievementReceive.svg', import.meta.url).href, - }, -]) -export const ApprovalApprove = /*#__PURE__*/ __createIcon('ApprovalApprove', [ - { - u: () => new URL('./rss3/ApprovalApprove.svg', import.meta.url).href, - }, -]) -export const CollectibleApprove = /*#__PURE__*/ __createIcon('CollectibleApprove', [ - { - u: () => new URL('./rss3/CollectibleApprove.svg', import.meta.url).href, - }, -]) -export const CollectibleBurn = /*#__PURE__*/ __createIcon('CollectibleBurn', [ - { - u: () => new URL('./rss3/CollectibleBurn.svg', import.meta.url).href, - }, -]) -export const CollectibleIn = /*#__PURE__*/ __createIcon('CollectibleIn', [ - { - u: () => new URL('./rss3/CollectibleIn.svg', import.meta.url).href, - }, -]) -export const CollectibleMint = /*#__PURE__*/ __createIcon('CollectibleMint', [ - { - u: () => new URL('./rss3/CollectibleMint.svg', import.meta.url).href, - }, -]) -export const CollectibleOut = /*#__PURE__*/ __createIcon('CollectibleOut', [ - { - u: () => new URL('./rss3/CollectibleOut.svg', import.meta.url).href, - }, -]) -export const DonationDonate = /*#__PURE__*/ __createIcon('DonationDonate', [ - { - u: () => new URL('./rss3/DonationDonate.svg', import.meta.url).href, - }, -]) -export const DonationLaunch = /*#__PURE__*/ __createIcon('DonationLaunch', [ - { - u: () => new URL('./rss3/DonationLaunch.svg', import.meta.url).href, - }, -]) -export const Follow = /*#__PURE__*/ __createIcon('Follow', [ - { - u: () => new URL('./rss3/Follow.svg', import.meta.url).href, - }, -]) -export const GovernancePropose = /*#__PURE__*/ __createIcon('GovernancePropose', [ - { - u: () => new URL('./rss3/GovernancePropose.svg', import.meta.url).href, - }, -]) -export const GovernanceVote = /*#__PURE__*/ __createIcon('GovernanceVote', [ - { - u: () => new URL('./rss3/GovernanceVote.svg', import.meta.url).href, - }, -]) -export const NoteBurn = /*#__PURE__*/ __createIcon('NoteBurn', [ - { - u: () => new URL('./rss3/NoteBurn.svg', import.meta.url).href, - }, -]) -export const NoteCreate = /*#__PURE__*/ __createIcon('NoteCreate', [ - { - u: () => new URL('./rss3/NoteCreate.svg', import.meta.url).href, - }, -]) -export const NoteEdit = /*#__PURE__*/ __createIcon('NoteEdit', [ - { - u: () => new URL('./rss3/NoteEdit.svg', import.meta.url).href, - }, -]) -export const NoteLink = /*#__PURE__*/ __createIcon('NoteLink', [ - { - u: () => new URL('./rss3/NoteLink.svg', import.meta.url).href, - }, -]) -export const NoteMint = /*#__PURE__*/ __createIcon('NoteMint', [ - { - u: () => new URL('./rss3/NoteMint.svg', import.meta.url).href, - }, -]) -export const ProfileBurn = /*#__PURE__*/ __createIcon('ProfileBurn', [ - { - u: () => new URL('./rss3/ProfileBurn.svg', import.meta.url).href, - }, -]) -export const ProfileCreate = /*#__PURE__*/ __createIcon('ProfileCreate', [ - { - u: () => new URL('./rss3/ProfileCreate.svg', import.meta.url).href, - }, -]) -export const ProfileLink = /*#__PURE__*/ __createIcon('ProfileLink', [ - { - u: () => new URL('./rss3/ProfileLink.svg', import.meta.url).href, - }, -]) -export const ProfileProxy = /*#__PURE__*/ __createIcon('ProfileProxy', [ - { - u: () => new URL('./rss3/ProfileProxy.svg', import.meta.url).href, - }, -]) -export const ProfileUpdate = /*#__PURE__*/ __createIcon('ProfileUpdate', [ - { - u: () => new URL('./rss3/ProfileUpdate.svg', import.meta.url).href, - }, -]) -export const RSS3Link = /*#__PURE__*/ __createIcon( - 'RSS3Link', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 9 2', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M.848 1.872a.83.83 0 0 1-.608-.256A.879.879 0 0 1 0 .992C0 .747.08.544.24.384A.83.83 0 0 1 .848.128a.83.83 0 0 1 .608.256.8.8 0 0 1 .256.608.85.85 0 0 1-.256.624.83.83 0 0 1-.608.256Zm3.391 0a.83.83 0 0 1-.608-.256.879.879 0 0 1-.24-.624c0-.245.08-.448.24-.608a.83.83 0 0 1 .608-.256.83.83 0 0 1 .608.256.8.8 0 0 1 .256.608.85.85 0 0 1-.256.624.83.83 0 0 1-.608.256Zm3.391 0a.83.83 0 0 1-.609-.256.879.879 0 0 1-.24-.624c0-.245.08-.448.24-.608A.83.83 0 0 1 7.63.128a.83.83 0 0 1 .608.256.8.8 0 0 1 .256.608.85.85 0 0 1-.256.624.83.83 0 0 1-.608.256Z', - }), - }), - s: true, - }, - ], - [9, 2], -) -export const TokenBridge = /*#__PURE__*/ __createIcon('TokenBridge', [ - { - u: () => new URL('./rss3/TokenBridge.svg', import.meta.url).href, - }, -]) -export const TokenBurn = /*#__PURE__*/ __createIcon('TokenBurn', [ - { - u: () => new URL('./rss3/TokenBurn.svg', import.meta.url).href, - }, -]) -export const TokenIn = /*#__PURE__*/ __createIcon('TokenIn', [ - { - u: () => new URL('./rss3/TokenIn.svg', import.meta.url).href, - }, -]) -export const TokenLiquidity = /*#__PURE__*/ __createIcon('TokenLiquidity', [ - { - u: () => new URL('./rss3/TokenLiquidity.svg', import.meta.url).href, - }, -]) -export const TokenMint = /*#__PURE__*/ __createIcon('TokenMint', [ - { - u: () => new URL('./rss3/TokenMint.svg', import.meta.url).href, - }, -]) -export const TokenOut = /*#__PURE__*/ __createIcon('TokenOut', [ - { - u: () => new URL('./rss3/TokenOut.svg', import.meta.url).href, - }, -]) -export const TokenStake = /*#__PURE__*/ __createIcon('TokenStake', [ - { - u: () => new URL('./rss3/TokenStake.svg', import.meta.url).href, - }, -]) -export const TokenSwap = /*#__PURE__*/ __createIcon('TokenSwap', [ - { - u: () => new URL('./rss3/TokenSwap.svg', import.meta.url).href, - }, -]) -export const TokenUnstake = /*#__PURE__*/ __createIcon('TokenUnstake', [ - { - u: () => new URL('./rss3/TokenUnstake.svg', import.meta.url).href, - }, -]) -export const Unfollow = /*#__PURE__*/ __createIcon('Unfollow', [ - { - u: () => new URL('./rss3/Unfollow.svg', import.meta.url).href, - }, -]) -export const UnknownBurn = /*#__PURE__*/ __createIcon('UnknownBurn', [ - { - u: () => new URL('./rss3/UnknownBurn.svg', import.meta.url).href, - }, -]) -export const UnknownCancel = /*#__PURE__*/ __createIcon('UnknownCancel', [ - { - u: () => new URL('./rss3/UnknownCancel.svg', import.meta.url).href, - }, -]) -export const UnknownIn = /*#__PURE__*/ __createIcon('UnknownIn', [ - { - u: () => new URL('./rss3/UnknownIn.svg', import.meta.url).href, - }, -]) -export const UnknownOut = /*#__PURE__*/ __createIcon('UnknownOut', [ - { - u: () => new URL('./rss3/UnknownOut.svg', import.meta.url).href, - }, -]) export const CN = /*#__PURE__*/ __createIcon('CN', [ { u: () => new URL('./settings/CN.svg', import.meta.url).href, diff --git a/packages/icons/icon-generated-as-url.js b/packages/icons/icon-generated-as-url.js index 565d37b7e0da..9d35fb2f51c7 100644 --- a/packages/icons/icon-generated-as-url.js +++ b/packages/icons/icon-generated-as-url.js @@ -346,83 +346,26 @@ export function menu_settings_url() { return new URL("./menus/MenuSettings.png", export function menu_settings_active_url() { return new URL("./menus/MenuSettingsActive.png", import.meta.url).href } export function menu_wallets_url() { return new URL("./menus/MenuWallets.png", import.meta.url).href } export function menu_wallets_active_url() { return new URL("./menus/MenuWalletsActive.png", import.meta.url).href } -export function approval_url() { return new URL("./plugins/Approval.svg", import.meta.url).href } -export function art_blocks_url() { return new URL("./plugins/ArtBlocks.png", import.meta.url).href } export function avatar_url() { return new URL("./plugins/Avatar.svg", import.meta.url).href } export function bit_url() { return new URL("./plugins/Bit.svg", import.meta.url).href } export function calendar_url() { return new URL("./plugins/Calendar.svg", import.meta.url).href } export function collectibles_url() { return new URL("./plugins/Collectibles.svg", import.meta.url).href } -export function cross_bridge_url() { return new URL("./plugins/CrossBridge.png", import.meta.url).href } -export function cyber_connect_dark_url() { return new URL("./plugins/CyberConnect.dark.svg", import.meta.url).href } -export function cyber_connect_light_url() { return new URL("./plugins/CyberConnect.light.svg", import.meta.url).href } export function decentralized_search_url() { return new URL("./plugins/DecentralizedSearch.svg", import.meta.url).href } export function ens_url() { return new URL("./plugins/ENS.png", import.meta.url).href } export function ens_cover_url() { return new URL("./plugins/ENSCover.svg", import.meta.url).href } -export function file_service_url() { return new URL("./plugins/FileService.svg", import.meta.url).href } -export function find_truman_url() { return new URL("./plugins/FindTruman.png", import.meta.url).href } -export function friend_tech_url() { return new URL("./plugins/FriendTech.svg", import.meta.url).href } -export function gitcoin_dark_url() { return new URL("./plugins/Gitcoin.dark.svg", import.meta.url).href } -export function gitcoin_light_url() { return new URL("./plugins/Gitcoin.light.svg", import.meta.url).href } -export function good_ghosting_dark_url() { return new URL("./plugins/GoodGhosting.dark.svg", import.meta.url).href } -export function good_ghosting_light_url() { return new URL("./plugins/GoodGhosting.light.svg", import.meta.url).href } -export function markets_url() { return new URL("./plugins/Markets.png", import.meta.url).href } export function markets_claim_url() { return new URL("./plugins/MarketsClaim.svg", import.meta.url).href } export function mask_box_url() { return new URL("./plugins/MaskBox.svg", import.meta.url).href } export function nft_avatar_url() { return new URL("./plugins/NFTAvatar.svg", import.meta.url).href } -export function pool_together_url() { return new URL("./plugins/PoolTogether.png", import.meta.url).href } -export function savings_url() { return new URL("./plugins/Savings.svg", import.meta.url).href } export function scam_sniffer_url() { return new URL("./plugins/ScamSniffer.svg", import.meta.url).href } export function security_checker_url() { return new URL("./plugins/SecurityChecker.svg", import.meta.url).href } export function setting_info_dark_url() { return new URL("./plugins/SettingInfo.dark.svg", import.meta.url).href } export function setting_info_light_url() { return new URL("./plugins/SettingInfo.light.svg", import.meta.url).href } export function shared_url() { return new URL("./plugins/shared.svg", import.meta.url).href } -export function snapshot_url() { return new URL("./plugins/Snapshot.svg", import.meta.url).href } export function space_id_url() { return new URL("./plugins/SpaceId.svg", import.meta.url).href } -export function tip_coin_url() { return new URL("./plugins/TipCoin.svg", import.meta.url).href } -export function transak_url() { return new URL("./plugins/Transak.png", import.meta.url).href } -export function unstoppable_url() { return new URL("./plugins/Unstoppable.svg", import.meta.url).href } export function valuables_dark_url() { return new URL("./plugins/Valuables.dark.svg", import.meta.url).href } export function valuables_light_url() { return new URL("./plugins/Valuables.light.svg", import.meta.url).href } export function web_3_profile_url() { return new URL("./plugins/Web3Profile.svg", import.meta.url).href } export function web_3_profile_card_url() { return new URL("./plugins/Web3ProfileCard.svg", import.meta.url).href } -export function achievement_burn_url() { return new URL("./rss3/AchievementBurn.svg", import.meta.url).href } -export function achievement_receive_url() { return new URL("./rss3/AchievementReceive.svg", import.meta.url).href } -export function approval_approve_url() { return new URL("./rss3/ApprovalApprove.svg", import.meta.url).href } -export function collectible_approve_url() { return new URL("./rss3/CollectibleApprove.svg", import.meta.url).href } -export function collectible_burn_url() { return new URL("./rss3/CollectibleBurn.svg", import.meta.url).href } -export function collectible_in_url() { return new URL("./rss3/CollectibleIn.svg", import.meta.url).href } -export function collectible_mint_url() { return new URL("./rss3/CollectibleMint.svg", import.meta.url).href } -export function collectible_out_url() { return new URL("./rss3/CollectibleOut.svg", import.meta.url).href } -export function donation_donate_url() { return new URL("./rss3/DonationDonate.svg", import.meta.url).href } -export function donation_launch_url() { return new URL("./rss3/DonationLaunch.svg", import.meta.url).href } -export function follow_url() { return new URL("./rss3/Follow.svg", import.meta.url).href } -export function governance_propose_url() { return new URL("./rss3/GovernancePropose.svg", import.meta.url).href } -export function governance_vote_url() { return new URL("./rss3/GovernanceVote.svg", import.meta.url).href } -export function note_burn_url() { return new URL("./rss3/NoteBurn.svg", import.meta.url).href } -export function note_create_url() { return new URL("./rss3/NoteCreate.svg", import.meta.url).href } -export function note_edit_url() { return new URL("./rss3/NoteEdit.svg", import.meta.url).href } -export function note_link_url() { return new URL("./rss3/NoteLink.svg", import.meta.url).href } -export function note_mint_url() { return new URL("./rss3/NoteMint.svg", import.meta.url).href } -export function profile_burn_url() { return new URL("./rss3/ProfileBurn.svg", import.meta.url).href } -export function profile_create_url() { return new URL("./rss3/ProfileCreate.svg", import.meta.url).href } -export function profile_link_url() { return new URL("./rss3/ProfileLink.svg", import.meta.url).href } -export function profile_proxy_url() { return new URL("./rss3/ProfileProxy.svg", import.meta.url).href } -export function profile_update_url() { return new URL("./rss3/ProfileUpdate.svg", import.meta.url).href } -export function rss_3_link_url() { return new URL("./rss3/RSS3Link.svg", import.meta.url).href } -export function token_bridge_url() { return new URL("./rss3/TokenBridge.svg", import.meta.url).href } -export function token_burn_url() { return new URL("./rss3/TokenBurn.svg", import.meta.url).href } -export function token_in_url() { return new URL("./rss3/TokenIn.svg", import.meta.url).href } -export function token_liquidity_url() { return new URL("./rss3/TokenLiquidity.svg", import.meta.url).href } -export function token_mint_url() { return new URL("./rss3/TokenMint.svg", import.meta.url).href } -export function token_out_url() { return new URL("./rss3/TokenOut.svg", import.meta.url).href } -export function token_stake_url() { return new URL("./rss3/TokenStake.svg", import.meta.url).href } -export function token_swap_url() { return new URL("./rss3/TokenSwap.svg", import.meta.url).href } -export function token_unstake_url() { return new URL("./rss3/TokenUnstake.svg", import.meta.url).href } -export function unfollow_url() { return new URL("./rss3/Unfollow.svg", import.meta.url).href } -export function unknown_burn_url() { return new URL("./rss3/UnknownBurn.svg", import.meta.url).href } -export function unknown_cancel_url() { return new URL("./rss3/UnknownCancel.svg", import.meta.url).href } -export function unknown_in_url() { return new URL("./rss3/UnknownIn.svg", import.meta.url).href } -export function unknown_out_url() { return new URL("./rss3/UnknownOut.svg", import.meta.url).href } export function cn_url() { return new URL("./settings/CN.svg", import.meta.url).href } export function email_url() { return new URL("./settings/Email.svg", import.meta.url).href } export function jp_url() { return new URL("./settings/JP.svg", import.meta.url).href } diff --git a/packages/icons/plugins/Approval.svg b/packages/icons/plugins/Approval.svg deleted file mode 100644 index f9945e1d055c..000000000000 --- a/packages/icons/plugins/Approval.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/plugins/ArtBlocks.png b/packages/icons/plugins/ArtBlocks.png deleted file mode 100644 index bd3eb544a8ccea25706bb550db1f3e464a6cff32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43263 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 B6yyK^ diff --git a/packages/icons/plugins/CrossBridge.png b/packages/icons/plugins/CrossBridge.png deleted file mode 100644 index 5834a1348926a8e57b402caeac7ef57136459b80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3580 zcmVzuB1COEWVwGqWG_ z8#XmFGZaY5{y5~})`XFe>pFDki3ms2&=3}rwMv`p{ z$zU)7K*BZv`G4XOTL>_i2#`d56$8RH5`X|?u%LQ2;P6~B&@*Ysj{?6&JJ26LeZ7^* zecinH24)%mLX)5Ny+CxB`0HH=r^z^Hv>Wf=kbaZ3lyou7*V5U zTz~_~7>Vx%zLz=BpEVGKd0NtY+ZOXsqz87U6X@|t

C{`Yi?|0pxR#z@{H^Jyy-I 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 - - diff --git a/packages/icons/plugins/CyberConnect.light.svg b/packages/icons/plugins/CyberConnect.light.svg deleted file mode 100644 index 845f56e08b1d..000000000000 --- a/packages/icons/plugins/CyberConnect.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/FileService.svg b/packages/icons/plugins/FileService.svg deleted file mode 100644 index 125ae5edfec2..000000000000 --- a/packages/icons/plugins/FileService.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/plugins/FindTruman.png b/packages/icons/plugins/FindTruman.png deleted file mode 100644 index 9abb4c30f594da35ab505bcbbc0a474407510111..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4340 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 WQMn1{G&|})^)U-O^D - - - - diff --git a/packages/icons/plugins/Gitcoin.dark.svg b/packages/icons/plugins/Gitcoin.dark.svg deleted file mode 100644 index dfb770fd3275..000000000000 --- a/packages/icons/plugins/Gitcoin.dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/plugins/Gitcoin.light.svg b/packages/icons/plugins/Gitcoin.light.svg deleted file mode 100644 index 277c227cae19..000000000000 --- a/packages/icons/plugins/Gitcoin.light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/plugins/GoodGhosting.dark.svg b/packages/icons/plugins/GoodGhosting.dark.svg deleted file mode 100644 index 04184c97fcd1..000000000000 --- a/packages/icons/plugins/GoodGhosting.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/GoodGhosting.light.svg b/packages/icons/plugins/GoodGhosting.light.svg deleted file mode 100644 index f27ad6a221f5..000000000000 --- a/packages/icons/plugins/GoodGhosting.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/Markets.png b/packages/icons/plugins/Markets.png deleted file mode 100644 index d7f87ec351062576e6b1e76d09764bf75270dcb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6335 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 B`EURL diff --git a/packages/icons/plugins/PoolTogether.png b/packages/icons/plugins/PoolTogether.png deleted file mode 100644 index 3b23abf50b6068d10222d6f18ec2bec483527d42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41928 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 %>o1@F0`T)D$GL>VhmAT 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

4o_O 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/plugins/Snapshot.svg b/packages/icons/plugins/Snapshot.svg deleted file mode 100644 index 27cc48a039c7..000000000000 --- a/packages/icons/plugins/Snapshot.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/TipCoin.svg b/packages/icons/plugins/TipCoin.svg deleted file mode 100644 index edaf07e6c04b..000000000000 --- a/packages/icons/plugins/TipCoin.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/plugins/Transak.png b/packages/icons/plugins/Transak.png deleted file mode 100644 index b35227258284285d8824b28e550e20dea94f9175..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2565 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 bQAhs)IJLr+jZ9B`00000NkvXXu0mjfk=4b( diff --git a/packages/icons/plugins/Unstoppable.svg b/packages/icons/plugins/Unstoppable.svg deleted file mode 100644 index 9dd93bb3ed62..000000000000 --- a/packages/icons/plugins/Unstoppable.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/rss3/AchievementBurn.svg b/packages/icons/rss3/AchievementBurn.svg deleted file mode 100644 index e3d5fb63d0ee..000000000000 --- a/packages/icons/rss3/AchievementBurn.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/AchievementReceive.svg b/packages/icons/rss3/AchievementReceive.svg deleted file mode 100644 index 54e845f0f5e5..000000000000 --- a/packages/icons/rss3/AchievementReceive.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/ApprovalApprove.svg b/packages/icons/rss3/ApprovalApprove.svg deleted file mode 100644 index 5eac1996eec4..000000000000 --- a/packages/icons/rss3/ApprovalApprove.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/CollectibleApprove.svg b/packages/icons/rss3/CollectibleApprove.svg deleted file mode 100644 index 0eaaacd0ad83..000000000000 --- a/packages/icons/rss3/CollectibleApprove.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/CollectibleBurn.svg b/packages/icons/rss3/CollectibleBurn.svg deleted file mode 100644 index cd7d4e687354..000000000000 --- a/packages/icons/rss3/CollectibleBurn.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/CollectibleIn.svg b/packages/icons/rss3/CollectibleIn.svg deleted file mode 100644 index 9cdb00ae3ddc..000000000000 --- a/packages/icons/rss3/CollectibleIn.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/CollectibleMint.svg b/packages/icons/rss3/CollectibleMint.svg deleted file mode 100644 index 843fd5442499..000000000000 --- a/packages/icons/rss3/CollectibleMint.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/CollectibleOut.svg b/packages/icons/rss3/CollectibleOut.svg deleted file mode 100644 index 2e70bf1a6306..000000000000 --- a/packages/icons/rss3/CollectibleOut.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/DonationDonate.svg b/packages/icons/rss3/DonationDonate.svg deleted file mode 100644 index 9c0a08759b7a..000000000000 --- a/packages/icons/rss3/DonationDonate.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/DonationLaunch.svg b/packages/icons/rss3/DonationLaunch.svg deleted file mode 100644 index a0cd6751e415..000000000000 --- a/packages/icons/rss3/DonationLaunch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/Follow.svg b/packages/icons/rss3/Follow.svg deleted file mode 100644 index 030e7a2b2bd8..000000000000 --- a/packages/icons/rss3/Follow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/rss3/GovernancePropose.svg b/packages/icons/rss3/GovernancePropose.svg deleted file mode 100644 index c5ebe840c292..000000000000 --- a/packages/icons/rss3/GovernancePropose.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/GovernanceVote.svg b/packages/icons/rss3/GovernanceVote.svg deleted file mode 100644 index e08476af5ca1..000000000000 --- a/packages/icons/rss3/GovernanceVote.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/NoteBurn.svg b/packages/icons/rss3/NoteBurn.svg deleted file mode 100644 index f0da0d0695aa..000000000000 --- a/packages/icons/rss3/NoteBurn.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/NoteCreate.svg b/packages/icons/rss3/NoteCreate.svg deleted file mode 100644 index 4d8e55b8aa10..000000000000 --- a/packages/icons/rss3/NoteCreate.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/NoteEdit.svg b/packages/icons/rss3/NoteEdit.svg deleted file mode 100644 index fcb523d9656a..000000000000 --- a/packages/icons/rss3/NoteEdit.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/NoteLink.svg b/packages/icons/rss3/NoteLink.svg deleted file mode 100644 index ea11f167ef01..000000000000 --- a/packages/icons/rss3/NoteLink.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/rss3/NoteMint.svg b/packages/icons/rss3/NoteMint.svg deleted file mode 100644 index 3253ba7ee577..000000000000 --- a/packages/icons/rss3/NoteMint.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/ProfileBurn.svg b/packages/icons/rss3/ProfileBurn.svg deleted file mode 100644 index 71719c0c60dd..000000000000 --- a/packages/icons/rss3/ProfileBurn.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/ProfileCreate.svg b/packages/icons/rss3/ProfileCreate.svg deleted file mode 100644 index 0d485240dc6d..000000000000 --- a/packages/icons/rss3/ProfileCreate.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/ProfileLink.svg b/packages/icons/rss3/ProfileLink.svg deleted file mode 100644 index 60748317b9e5..000000000000 --- a/packages/icons/rss3/ProfileLink.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/rss3/ProfileProxy.svg b/packages/icons/rss3/ProfileProxy.svg deleted file mode 100644 index 75e751b46c70..000000000000 --- a/packages/icons/rss3/ProfileProxy.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/ProfileUpdate.svg b/packages/icons/rss3/ProfileUpdate.svg deleted file mode 100644 index e20af83470c4..000000000000 --- a/packages/icons/rss3/ProfileUpdate.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/rss3/RSS3Link.svg b/packages/icons/rss3/RSS3Link.svg deleted file mode 100644 index a29f2aec2053..000000000000 --- a/packages/icons/rss3/RSS3Link.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/rss3/TokenBridge.svg b/packages/icons/rss3/TokenBridge.svg deleted file mode 100644 index 2af0f9d68aba..000000000000 --- a/packages/icons/rss3/TokenBridge.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenBurn.svg b/packages/icons/rss3/TokenBurn.svg deleted file mode 100644 index 62be203bd4fa..000000000000 --- a/packages/icons/rss3/TokenBurn.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenIn.svg b/packages/icons/rss3/TokenIn.svg deleted file mode 100644 index cbb54e672e45..000000000000 --- a/packages/icons/rss3/TokenIn.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenLiquidity.svg b/packages/icons/rss3/TokenLiquidity.svg deleted file mode 100644 index e31cae0abde1..000000000000 --- a/packages/icons/rss3/TokenLiquidity.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenMint.svg b/packages/icons/rss3/TokenMint.svg deleted file mode 100644 index 4933abcd0049..000000000000 --- a/packages/icons/rss3/TokenMint.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenOut.svg b/packages/icons/rss3/TokenOut.svg deleted file mode 100644 index d8601411dcd3..000000000000 --- a/packages/icons/rss3/TokenOut.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenStake.svg b/packages/icons/rss3/TokenStake.svg deleted file mode 100644 index 9d49b6b9e066..000000000000 --- a/packages/icons/rss3/TokenStake.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenSwap.svg b/packages/icons/rss3/TokenSwap.svg deleted file mode 100644 index 18a86b228c30..000000000000 --- a/packages/icons/rss3/TokenSwap.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/TokenUnstake.svg b/packages/icons/rss3/TokenUnstake.svg deleted file mode 100644 index ce6ae6b3c3ed..000000000000 --- a/packages/icons/rss3/TokenUnstake.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/rss3/Unfollow.svg b/packages/icons/rss3/Unfollow.svg deleted file mode 100644 index b4431c410bde..000000000000 --- a/packages/icons/rss3/Unfollow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/rss3/UnknownBurn.svg b/packages/icons/rss3/UnknownBurn.svg deleted file mode 100644 index fe52b9937a6f..000000000000 --- a/packages/icons/rss3/UnknownBurn.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/UnknownCancel.svg b/packages/icons/rss3/UnknownCancel.svg deleted file mode 100644 index 95867401f319..000000000000 --- a/packages/icons/rss3/UnknownCancel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/UnknownIn.svg b/packages/icons/rss3/UnknownIn.svg deleted file mode 100644 index 912d828c6937..000000000000 --- a/packages/icons/rss3/UnknownIn.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/rss3/UnknownOut.svg b/packages/icons/rss3/UnknownOut.svg deleted file mode 100644 index 17b7873b1132..000000000000 --- a/packages/icons/rss3/UnknownOut.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/mask-sdk/README.md b/packages/mask-sdk/README.md deleted file mode 100644 index 412616956137..000000000000 --- a/packages/mask-sdk/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Mask SDK - -This SDK provides the ability of accessing Mask Network API to normal web sites. - -## public-api - -This part is the public API for developers - -## main - -This part is the implementation of the public API - -## server - -This part is the server that communicate with the "main" project to provide the functionality. diff --git a/packages/mask-sdk/gen-message.mjs b/packages/mask-sdk/gen-message.mjs deleted file mode 100644 index 6973a35d249f..000000000000 --- a/packages/mask-sdk/gen-message.mjs +++ /dev/null @@ -1,71 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises' -import { snakeCase } from 'lodash-es' - -const message = await readFile(new URL('./shared/messages.txt', import.meta.url), 'utf-8') - -let templateFile = `// This file is generated by ./messages.txt with ../gen-message.mjs. DO NOT EDIT this file. -import { MaskEthereumProviderRpcError, type MaskEthereumProviderRpcErrorOptions } from './error.js' -// prettier-ignore -export function fromMessage(message: string, options?: MaskEthereumProviderRpcErrorOptions): MaskEthereumProviderRpcError | undefined -// prettier-ignore -export function fromMessage(message: ErrorMessages, options?: MaskEthereumProviderRpcErrorOptions): MaskEthereumProviderRpcError -// prettier-ignore -export function fromMessage(message: string | ErrorMessages, options: MaskEthereumProviderRpcErrorOptions = {}): MaskEthereumProviderRpcError | undefined { - // prettier-ignore - return message in codeMap ? new MaskEthereumProviderRpcError((codeMap as any)[message], message, options) : undefined -} -` - -let err = `// prettier-ignore -export const err = { -` -let messages = `// prettier-ignore -export enum ErrorMessages { -` -let codeMap = `// prettier-ignore -const codeMap = { -` - -const format = /(?-?\d+)\s+(?\w+_\w+)?\s+(?.+)/g -const interpolation = /\$\{(?\w+)\}/g -let lastTopic = undefined -for (const line of message.split('\n').sort()) { - if (line === '' || line.startsWith('#')) continue - format.lastIndex = 0 - const match = format.exec(line) - if (!match) console.log(line) - const { code, method, message } = match?.groups - if (lastTopic !== method) { - if (lastTopic) err += ` },\n` - if (method) err += ` ${method}: {\n` - lastTopic = method - } - const interpolations = [] - for (const i of String(message).matchAll(interpolation)) { - interpolations.push(i.groups.interpolation) - } - - const arg0 = - interpolations.length ? - `{ ${interpolations.join(', ')} }: Record<${interpolations.map(JSON.stringify).join(' | ')}, string>,` - : '' - const messageArg = arg0 ? `\`${message}\`` : JSON.stringify(message) - let indent = method ? ` ` : ` ` - err += `${indent}${snakeCase(message)}(${arg0}options: MaskEthereumProviderRpcErrorOptions = {}) {\n` - err += `${indent} return new MaskEthereumProviderRpcError(${code}, ${messageArg}, options)\n` - err += `${indent}},\n` - - if (interpolations.length === 0) { - const k = snakeCase(method ? `${method}_${message}` : message) - const v = JSON.stringify(message) - messages += ` ${k} = ${v},\n` - codeMap += ` ${v}: ${code},\n` - } -} -err += `}\n` -messages += `}\n` -codeMap += `}\n` - -templateFile += messages + codeMap + err - -await writeFile(new URL('./shared/error-generated.ts', import.meta.url), templateFile) diff --git a/packages/mask-sdk/global.d.ts b/packages/mask-sdk/global.d.ts deleted file mode 100644 index 6a4d4bd176f1..000000000000 --- a/packages/mask-sdk/global.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const Mask: undefined | typeof import('./dist/public-api/index.ts').Mask -interface Window { - Mask?: typeof Mask -} -interface WindowEventMap { - 'eip6963:requestProvider': Event - 'eip6963:announceProvider': CustomEvent -} diff --git a/packages/mask-sdk/main/bridge.ts b/packages/mask-sdk/main/bridge.ts deleted file mode 100644 index 54a2dc738d03..000000000000 --- a/packages/mask-sdk/main/bridge.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - createMaskSDKChannel, - type BridgeAPI, - type UserScriptAPI, - encoder, - type InitInformation, -} from '../shared/index.js' -import { AsyncCall } from 'async-call-rpc/base.min' -import { ethereum } from './wallet.js' - -const self: UserScriptAPI = { - request_init: null!, - async eth_message(message) { - ethereum.dispatchEvent(new CustomEvent('message', { detail: message })) - }, -} -export const readyPromise = new Promise((resolve) => { - self.request_init = async (init) => resolve(init) -}) -export const contentScript: BridgeAPI = AsyncCall(self, { - channel: createMaskSDKChannel('user'), - encoder, - log: false, -}) diff --git a/packages/mask-sdk/main/index.ts b/packages/mask-sdk/main/index.ts deleted file mode 100644 index 59ea0eb13a56..000000000000 --- a/packages/mask-sdk/main/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Mask } from '../public-api/index.js' -import { contentScript, readyPromise } from './bridge.js' -import { ethereum } from './wallet.js' - -if (location.protocol.includes('-extension')) throw new TypeError('Mask SDK: this is not expected to run in extension.') -document.currentScript?.remove() - -const MaskSDK: { - [key in keyof typeof Mask as undefined extends (typeof Mask)[key] ? never : key]: (typeof Mask)[key] -} & { reload?(): Promise } = { - sdkVersion: 0, - ethereum, -} -Object.assign(globalThis, { Mask: MaskSDK }) - -readyPromise.then((init) => { - if (init.debuggerMode) { - if (location.href === 'https://metamask.github.io/test-dapp/' || location.href === 'http://localhost:9011/') { - Object.assign(window, { ethereum }) - Object.assign(ethereum, { isMetaMask: true }) - } - MaskSDK.reload = () => contentScript.reload() - } - - ethereum.request({ method: 'eth_chainId', params: [] }).then((chainId) => { - ethereum.dispatchEvent(new CustomEvent('connect', { detail: { chainId } })) - }) -}) - -undefined diff --git a/packages/mask-sdk/main/tsconfig.json b/packages/mask-sdk/main/tsconfig.json deleted file mode 100644 index 97c461451180..000000000000 --- a/packages/mask-sdk/main/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.leaf.json", - "compilerOptions": { - "rootDir": "./", - "tsBuildInfoFile": "../dist/main.tsbuildinfo" - }, - "include": ["./"], - "references": [{ "path": "../public-api/tsconfig.json" }, { "path": "../shared/tsconfig.json" }] -} diff --git a/packages/mask-sdk/main/wallet.ts b/packages/mask-sdk/main/wallet.ts deleted file mode 100644 index 871294fdc235..000000000000 --- a/packages/mask-sdk/main/wallet.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { contentScript } from './bridge.js' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import image from '../../icons/brands/MaskBlue.svg' -import type { Ethereum } from '../public-api/mask-wallet.js' - -class EthereumEventEmitter extends EventTarget implements Ethereum.MaskEthereumEventEmitter { - #mapping = new WeakMap() - #getMappedFunction(listener: unknown) { - if (typeof listener !== 'function') return undefined - if (!this.#mapping.has(listener)) { - const mapped = (event: CustomEvent) => { - listener(event.detail) - } - this.#mapping.set(listener, mapped) - return mapped - } - return this.#mapping.get(listener) - } - on(eventName: string | symbol, listener: (...args: any[]) => void): this { - if (typeof eventName === 'symbol') return this - const f = this.#getMappedFunction(listener) - if (!f) return this - super.addEventListener(eventName, f) - return this - } - removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this { - if (typeof eventName === 'symbol') return this - super.removeEventListener(eventName, this.#getMappedFunction(listener)) - return this - } -} -class MaskProvider extends EthereumEventEmitter implements Ethereum.ProviderObject { - async request(param: any): Promise { - const stack = new Error().stack?.replace(/^Error\n/, '') - const result = await contentScript.eth_request(param) - if (result.e) { - result.e.stack = `MaskEthereumProviderRpcError: ${result.e.message}\n${stack}` - throw result.e - } - return result.d - } -} -export const ethereum = new MaskProvider() - -const detail: Ethereum.EIP6963ProviderDetail = { - info: { - // MetaMask generate a random UUID each connect - uuid: crypto.randomUUID(), - name: 'Mask Wallet', - rdns: 'io.mask', - icon: String(image), - }, - provider: ethereum, -} -Object.freeze(detail) -Object.freeze(detail.info) -const event = () => new CustomEvent('eip6963:announceProvider', { detail }) - -window.dispatchEvent(event()) -window.addEventListener('eip6963:requestProvider', () => window.dispatchEvent(event())) diff --git a/packages/mask-sdk/package.json b/packages/mask-sdk/package.json deleted file mode 100644 index ae6b428db912..000000000000 --- a/packages/mask-sdk/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@masknet/sdk", - "version": "0.0.0", - "private": true, - "sideEffects": [ - "./server/index.ts" - ], - "type": "module", - "exports": { - ".": { - "types": "./dist/server/index.d.ts", - "mask-src": "./server/index.ts", - "default": "./dist/server/index.js" - } - }, - "types": "./dist/server/index.d.ts", - "scripts": { - "build": "rollup -c", - "start": "rollup -c -w", - "gen-msg": "node ./gen-message.mjs" - }, - "devDependencies": { - "@rollup/plugin-image": "^3.0.2", - "@rollup/plugin-node-resolve": "^15.2.1", - "async-call-rpc": "^6.4.0", - "rollup": "^3.28.1", - "rollup-plugin-swc3": "^0.10.1" - } -} diff --git a/packages/mask-sdk/public-api/index.ts b/packages/mask-sdk/public-api/index.ts deleted file mode 100644 index b163dd4aa171..000000000000 --- a/packages/mask-sdk/public-api/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Persona } from './mask-persona.js' -import type { Ethereum } from './mask-wallet.js' -/** - * @packageDocumentation - * Mask SDK that provides the ability of interacting with Mask Network. - */ - -export declare namespace Mask { - /** - * Current Mask SDK version. - * @public - */ - export const sdkVersion: number - - /** - * Mask Login is following the same API as WebAuthn. - * - * @public - * @remarks Since API=0 - */ - export const credentials: undefined | CredentialsContainer - /** - * Provide the ability to interact with Persona. - * - * @remarks Since API=0 - * @public - */ - export const persona: undefined | Persona - /** - * @see https://eips.ethereum.org/EIPS/eip-1193 - * A EIP-1193 compatible Ethereum provider. - * @public - * @remarks Since API=0 - */ - export const ethereum: Ethereum.ProviderObject -} diff --git a/packages/mask-sdk/public-api/mask-login.ts b/packages/mask-sdk/public-api/mask-login.ts deleted file mode 100644 index d0588e3655d1..000000000000 --- a/packages/mask-sdk/public-api/mask-login.ts +++ /dev/null @@ -1 +0,0 @@ -export declare namespace Mask {} diff --git a/packages/mask-sdk/public-api/mask-persona.ts b/packages/mask-sdk/public-api/mask-persona.ts deleted file mode 100644 index 1c82b995e112..000000000000 --- a/packages/mask-sdk/public-api/mask-persona.ts +++ /dev/null @@ -1 +0,0 @@ -export interface Persona {} diff --git a/packages/mask-sdk/public-api/mask-wallet.ts b/packages/mask-sdk/public-api/mask-wallet.ts deleted file mode 100644 index d63d7da9a633..000000000000 --- a/packages/mask-sdk/public-api/mask-wallet.ts +++ /dev/null @@ -1,299 +0,0 @@ -// Defined in EIP-1193 -export declare namespace Ethereum { - export interface ProviderObject extends EIP1193Provider, EthereumEventEmitter {} - export interface EIP1193Provider { - /** - * The `request` method is intended as a transport- and protocol-agnostic wrapper function for Remote Procedure Calls (RPCs). - * @remarks Since API=0 - */ - request(args: { - readonly method: key - readonly params: Readonly> - }): Promise> - } - export interface RequestArguments { - readonly method: string - readonly params?: readonly unknown[] | object - } - export interface ProviderRpcError extends Error { - code: number - data?: unknown - } - /** - * @see https://nodejs.org/api/events.html - */ - export interface EventEmitter { - /** - * @remarks Since API=0 - */ - on(eventName: string | symbol, listener: (...args: any[]) => void): this - /** - * @remarks Since API=0 - */ - removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this - } - export interface EthereumEventEmitter extends EventEmitter { - on(eventName: 'message', listener: (message: ProviderMessage | EthSubscription) => void): this - // on(eventName: 'connect', listener: (message: ProviderConnectInfo) => void): this - // on(eventName: 'disconnect', listener: (error: ProviderRpcError) => void): this - // on(eventName: 'chainChanged', listener: (chainId: string) => void): this - // on(eventName: 'accountsChanged', listener: (accounts: string[]) => void): this - } - export interface ProviderMessage { - readonly type: string - readonly data: unknown - } - export interface EthSubscription extends ProviderMessage { - readonly type: 'eth_subscription' - readonly data: { - readonly subscription: string - readonly result: unknown - } - } - export interface ProviderConnectInfo { - readonly chainId: string - } -} - -// Defined in EIP-6963 -export declare namespace Ethereum { - export interface EIP6963ProviderInfo { - uuid: string - name: string - icon: string - rdns: string - } - export interface EIP6963ProviderDetail { - info: EIP6963ProviderInfo - provider: EIP1193Provider - } -} - -// Implemented RPCs - -export declare namespace Ethereum.RPC { - export interface Block { - hash: string - parentHash: string - sha3Uncles: string - miner: string - stateRoot: string - transactionsRoot: string - receiptsRoot: string - logsBloom: string - difficulty?: string - number: string - gasLimit: string - gasUsed: string - timestamp: string - extraData: string - mixHash: string - nonce: string - totalDifficulty?: string - baseFeePerGas?: string - withdrawalsRoot?: string - size: string - transactions: string[] | Transaction[] - withdrawals: Array<{ index: string; validatorIndex: string; address: string; amount: string }> - uncles: string[] - } - export interface Signed1559Transaction { - blockHash: string - blockNumber: string - from: string - hash: string - transactionIndex: string - type: string - nonce: string - to?: string | null - gas: string - value: string - input: string - maxPriorityFeePerGas: string - maxFeePerGas: string - gasPrice: string - accessList: EIP2930AccessListEntry[] - chainId: string - yParity: string - v?: string - r: string - s: string - } - export interface EIP2930AccessListEntry { - address: string - storageKeys: string[] - } - export interface Signed2930Transaction { - blockHash: string - blockNumber: string - from: string - hash: string - transactionIndex: string - type: string - nonce: string - to?: string | null - gas: string - value: string - input: string - gasPrice: string - accessList: EIP2930AccessListEntry[] - chainId: string - yParity: string - v?: string - r: string - s: string - } - export interface SignedLegacyTransaction { - blockHash: string - blockNumber: string - from: string - hash: string - transactionIndex: string - type: string - nonce: string - to?: string | null - gas: string - value: string - input: string - gasPrice: string - chainId?: string - v: string - r: string - s: string - } - export type Transaction = Signed1559Transaction | Signed2930Transaction | SignedLegacyTransaction - export interface Receipt { - type?: string - transactionHash: string - transactionIndex: string - blockHash: string - blockNumber: string - from: string - to?: unknown - cumulativeGasUsed: string - gasUsed: string - contractAddress?: unknown - logs: Log[] - logsBloom: string - root?: string - status?: string - effectiveGasPrice: string - } - export interface Log { - removed?: boolean - logIndex?: string - transactionIndex?: string - transactionHash: string - blockHash?: string - blockNumber?: string - address?: string - data?: string - topics?: string[] - } - export interface Filter { - fromBlock?: string - toBlock?: string - address?: string | string[] - topics?: string[] - blockhash?: string - } - export interface EIP2255Caveat { - type: string - value: unknown - } - export interface EIP2255Permission { - invoker: string - parentCapability: string - caveats: EIP2255Caveat[] - } - export interface EIP2255RequestedPermission { - parentCapability: string - date?: number - } - export interface EIP2255PermissionRequest { - [methodName: string]: { - [caveatName: string]: any - } - } - interface ImplementedMethods { - net_version: () => string - eth_accounts: () => string[] - eth_blockNumber: () => string - eth_call: (transaction: Transaction, block?: string) => string - eth_chainId: () => string - eth_estimateGas: (transaction: Transaction, block?: string) => string - eth_feeHistory: (args_0: string | number, args_1: string, args_2: number[]) => any - eth_gasPrice: () => string - eth_getBalance: (address: string, block?: string | null | undefined) => string - eth_getBlockByHash: (hash: string, hydrated_transactions?: boolean | null | undefined) => Block | null - eth_getBlockByNumber: (block: string, hydrated_transactions?: boolean | null | undefined) => Block | null - eth_getBlockReceipts: (args_0: string) => any - eth_getBlockTransactionCountByHash: (args_0: string) => string | null | undefined - eth_getBlockTransactionCountByNumber: (args_0: string) => string | null | undefined - eth_getCode: (address: string, block?: string | null | undefined) => string - eth_getLogs: (filter: Filter) => string[] | Log[] - eth_getProof: (args_0: string, args_1: string[], args_2: string) => any - eth_getStorageAt: (args_0: string, args_1: string, args_2: string | null | undefined) => string - eth_getTransactionByBlockHashAndIndex: (args_0: string, args_1: string) => any - eth_getTransactionByBlockNumberAndIndex: (args_0: string, args_1: string) => any - eth_getTransactionByHash: (hash: string) => Transaction | null - eth_getTransactionCount: (address: string, block?: string | null | undefined) => string - eth_getTransactionReceipt: (transaction_hash: string) => Receipt | null - eth_getUncleCountByBlockHash: (args_0: string) => string | null | undefined - eth_getUncleCountByBlockNumber: (args_0: string) => string | null | undefined - eth_syncing: () => any - - personal_sign: (args_0: string, args_1: string) => string - eth_sendTransaction: (transaction: any) => any - eth_sendRawTransaction: (args_0: string) => string - eth_subscribe: ( - args_0: 'newHeads' | 'logs' | 'newPendingTransactions' | 'syncing', - args_1: - | { - topics: string[] - address?: string | string[] | null | undefined - } - | null - | undefined, - ) => string - eth_unsubscribe: (args_0: string) => boolean - - eth_getFilterChanges: (id: string) => string[] | Log[] - eth_getFilterLogs: (args_0: string) => any - eth_newBlockFilter: () => string - eth_newFilter: (args_0: any) => string - eth_uninstallFilter: (args_0: string) => boolean - - eth_requestAccounts: () => string[] - - wallet_getPermissions: () => EIP2255Permission[] - wallet_requestPermissions(request: EIP2255PermissionRequest): EIP2255RequestedPermission[] - } -} - -// Mask specific part -export declare namespace Ethereum { - export interface ProviderObject extends EIP1193Provider, ExperimentalProvider, EthereumEventEmitter {} - - /** Extra APIs that only can be used with Mask Network is defined here. */ - export interface ExperimentalProvider {} - export interface EthereumEventMap { - message: CustomEvent - // connect: CustomEvent - // disconnect: CustomEvent - // chainChanged: CustomEvent - // accountsChanged: CustomEvent - } - export interface MaskEthereumEventEmitter extends EthereumEventEmitter, EventTarget { - addEventListener( - type: K, - callback: EventListenerOrEventListenerObject | null | ((ev: EthereumEventMap[K]) => any), - options?: boolean | AddEventListenerOptions, - ): void - removeEventListener( - type: K, - listener: EventListenerOrEventListenerObject | null | ((ev: EthereumEventMap[K]) => any), - options?: boolean | EventListenerOptions, - ): void - } -} diff --git a/packages/mask-sdk/public-api/tsconfig.json b/packages/mask-sdk/public-api/tsconfig.json deleted file mode 100644 index c3ac265c9769..000000000000 --- a/packages/mask-sdk/public-api/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "rootDir": "./", - "outDir": "../dist/public-api/", - "tsBuildInfoFile": "../dist/public-api.tsbuildinfo", - "declarationMap": false - }, - "include": ["./"], - "references": [] -} diff --git a/packages/mask-sdk/rollup.config.js b/packages/mask-sdk/rollup.config.js deleted file mode 100644 index 5ae43c29229c..000000000000 --- a/packages/mask-sdk/rollup.config.js +++ /dev/null @@ -1,18 +0,0 @@ -import node from '@rollup/plugin-node-resolve' -import image from '@rollup/plugin-image' -import swc from 'rollup-plugin-swc3' - -export default { - input: 'main/index.ts', - output: { - file: 'dist/mask-sdk.js', - format: 'iife', - }, - plugins: [ - node(), - swc({ - tsconfig: '../../tsconfig.json', - }), - image(), - ], -} diff --git a/packages/mask-sdk/server/index.ts b/packages/mask-sdk/server/index.ts deleted file mode 100644 index 6e8925cc3694..000000000000 --- a/packages/mask-sdk/server/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { MaskEthereumProviderRpcError } from '../shared/error.js' -import { type BridgeAPI, type UserScriptAPI, createMaskSDKChannel, encoder } from '../shared/index.js' -import { AsyncCall, type AsyncVersionOf } from 'async-call-rpc/full' - -export * from '../shared/types.js' -export { - MaskEthereumProviderRpcError, - type BridgeAPI, - type UserScriptAPI, - type InitInformation, - ErrorCode, - ErrorMessages, - fromMessage, - err, - type MaskEthereumProviderRpcErrorOptions, -} from '../shared/index.js' - -export function createMaskSDKServer(api: BridgeAPI, signal?: AbortSignal): AsyncVersionOf { - return AsyncCall(api, { - signal, - encoder, - channel: createMaskSDKChannel('content'), - log: false, - thenable: false, - mapError(error) { - return { - code: (error as MaskEthereumProviderRpcError).code, - message: (error as MaskEthereumProviderRpcError).message, - data: error, - } - }, - }) -} diff --git a/packages/mask-sdk/server/tsconfig.json b/packages/mask-sdk/server/tsconfig.json deleted file mode 100644 index e4f6d02670c6..000000000000 --- a/packages/mask-sdk/server/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "rootDir": "./", - "outDir": "../dist/server/", - "tsBuildInfoFile": "../dist/server.tsbuildinfo" - }, - "include": ["./"], - "references": [{ "path": "../public-api/tsconfig.json" }, { "path": "../shared/tsconfig.json" }] -} diff --git a/packages/mask-sdk/shared/channel.ts b/packages/mask-sdk/shared/channel.ts deleted file mode 100644 index 5b78823ceae9..000000000000 --- a/packages/mask-sdk/shared/channel.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { EventBasedChannel } from 'async-call-rpc' - -const EVENT_UserScript = '@masknet/sdk-raw/us' -const EVENT_ContentScript = '@masknet/sdk-raw/cs' -export function createMaskSDKChannel(side: 'user' | 'content'): EventBasedChannel { - const thisSide = side === 'content' ? EVENT_ContentScript : EVENT_UserScript - const otherSide = side === 'content' ? EVENT_UserScript : EVENT_ContentScript - return { - on(callback) { - const f = (e: Event) => { - if (e instanceof CustomEvent) callback(e.detail) - } - globalThis.addEventListener(thisSide, f) - return () => document.removeEventListener(thisSide, f) - }, - send(data) { - globalThis.dispatchEvent(new CustomEvent(otherSide, { detail: data })) - }, - } -} diff --git a/packages/mask-sdk/shared/error-generated.ts b/packages/mask-sdk/shared/error-generated.ts deleted file mode 100644 index a763673c1059..000000000000 --- a/packages/mask-sdk/shared/error-generated.ts +++ /dev/null @@ -1,118 +0,0 @@ -// This file is generated by ./messages.txt with ../gen-message.mjs. DO NOT EDIT this file. -import { MaskEthereumProviderRpcError, type MaskEthereumProviderRpcErrorOptions } from './error.js' -// prettier-ignore -export function fromMessage(message: string, options?: MaskEthereumProviderRpcErrorOptions): MaskEthereumProviderRpcError | undefined -// prettier-ignore -export function fromMessage(message: ErrorMessages, options?: MaskEthereumProviderRpcErrorOptions): MaskEthereumProviderRpcError -// prettier-ignore -export function fromMessage(message: string | ErrorMessages, options: MaskEthereumProviderRpcErrorOptions = {}): MaskEthereumProviderRpcError | undefined { - // prettier-ignore - return message in codeMap ? new MaskEthereumProviderRpcError((codeMap as any)[message], message, options) : undefined -} -// prettier-ignore -export enum ErrorMessages { - invalid_input = "Invalid input", - resource_not_found = "Resource not found", - resource_unavailable = "Resource unavailable", - transaction_rejected = "Transaction rejected", - method_not_supported = "Method not supported", - limit_exceeded = "Limit exceeded", - json_rpc_version_not_supported = "JSON-RPC version not supported", - invalid_request = "Invalid request", - the_method_eth_subscribe_is_only_available_on_the_mainnet = "The method \"eth_subscribe\" is only available on the mainnet.", - invalid_params = "Invalid params", - wallet_request_permissions_a_permission_request_must_contain_at_least_1_permission = "A permission request must contain at least 1 permission.", - internal_error = "Internal error", - parse_error = "Parse error", - user_rejected_the_request = "User rejected the request", - the_requested_account_and_or_method_has_not_been_authorized_by_the_user = "The requested account and/or method has not been authorized by the user", - the_requested_method_is_not_supported_by_this_ethereum_provider = "The requested method is not supported by this Ethereum provider", - the_provider_is_disconnected_from_all_chains = "The provider is disconnected from all chains", - the_provider_is_disconnected_from_the_specified_chain = "The provider is disconnected from the specified chain", -} -// prettier-ignore -const codeMap = { - "Invalid input": -32000, - "Resource not found": -32001, - "Resource unavailable": -32002, - "Transaction rejected": -32003, - "Method not supported": -32004, - "Limit exceeded": -32005, - "JSON-RPC version not supported": -32006, - "Invalid request": -32600, - "The method \"eth_subscribe\" is only available on the mainnet.": -32601, - "Invalid params": -32602, - "A permission request must contain at least 1 permission.": -32602, - "Internal error": -32603, - "Parse error": -32700, - "User rejected the request": 4001, - "The requested account and/or method has not been authorized by the user": 4100, - "The requested method is not supported by this Ethereum provider": 4200, - "The provider is disconnected from all chains": 4900, - "The provider is disconnected from the specified chain": 4901, -} -// prettier-ignore -export const err = { - invalid_input(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32000, "Invalid input", options) - }, - resource_not_found(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32001, "Resource not found", options) - }, - resource_unavailable(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32002, "Resource unavailable", options) - }, - transaction_rejected(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32003, "Transaction rejected", options) - }, - method_not_supported(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32004, "Method not supported", options) - }, - limit_exceeded(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32005, "Limit exceeded", options) - }, - json_rpc_version_not_supported(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32006, "JSON-RPC version not supported", options) - }, - invalid_request(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32600, "Invalid request", options) - }, - the_method_method_does_not_exist_is_not_available({ method }: Record<"method", string>,options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32601, `The method "${method}" does not exist / is not available.`, options) - }, - the_method_eth_subscribe_is_only_available_on_the_mainnet(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32601, "The method \"eth_subscribe\" is only available on the mainnet.", options) - }, - invalid_params(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32602, "Invalid params", options) - }, - wallet_requestPermissions: { - a_permission_request_must_contain_at_least_1_permission(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32602, "A permission request must contain at least 1 permission.", options) - }, - permission_request_contains_unsupported_permission_permission({ permission }: Record<"permission", string>,options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32602, `Permission request contains unsupported permission ${permission}.`, options) - }, - }, - internal_error(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32603, "Internal error", options) - }, - parse_error(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(-32700, "Parse error", options) - }, - user_rejected_the_request(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(4001, "User rejected the request", options) - }, - the_requested_account_and_or_method_has_not_been_authorized_by_the_user(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(4100, "The requested account and/or method has not been authorized by the user", options) - }, - the_requested_method_is_not_supported_by_this_ethereum_provider(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(4200, "The requested method is not supported by this Ethereum provider", options) - }, - the_provider_is_disconnected_from_all_chains(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(4900, "The provider is disconnected from all chains", options) - }, - the_provider_is_disconnected_from_the_specified_chain(options: MaskEthereumProviderRpcErrorOptions = {}) { - return new MaskEthereumProviderRpcError(4901, "The provider is disconnected from the specified chain", options) - }, -} diff --git a/packages/mask-sdk/shared/error.ts b/packages/mask-sdk/shared/error.ts deleted file mode 100644 index ec366e335c1e..000000000000 --- a/packages/mask-sdk/shared/error.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Ethereum } from '../public-api/mask-wallet.js' -export interface MaskEthereumProviderRpcErrorOptions extends ErrorOptions { - data?: unknown -} -export class MaskEthereumProviderRpcError extends Error implements Ethereum.ProviderRpcError { - constructor(code: number, message: string, options: MaskEthereumProviderRpcErrorOptions = {}) { - const { cause = undefined, data } = options - super(message, { cause }) - this.code = code - this.data = data - delete this.stack - } - code: number - data?: unknown -} -export enum ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - UserRejectedTheRequest = 4001, - RequestedAccountHasNotBeenAuthorized = 4100, -} diff --git a/packages/mask-sdk/shared/index.ts b/packages/mask-sdk/shared/index.ts deleted file mode 100644 index c8a39b9147f2..000000000000 --- a/packages/mask-sdk/shared/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/// -import type { MaskEthereumProviderRpcError } from './error.js' -export interface BridgeAPI { - eth_request(request: unknown): Promise<{ e?: MaskEthereumProviderRpcError | null; d?: unknown }> - reload(): Promise -} -export interface UserScriptAPI { - // When User script loaded, content script is not loaded. We must _be_ called to make sure CS has loaded. - request_init(init: InitInformation): Promise - eth_message(message: unknown): Promise -} -export interface InitInformation { - debuggerMode: boolean -} -export { encoder } from './serializer.js' -export { createMaskSDKChannel } from './channel.js' -export * from './types.js' -export * from './error.js' -export * from './error-generated.js' diff --git a/packages/mask-sdk/shared/messages.txt b/packages/mask-sdk/shared/messages.txt deleted file mode 100644 index a3a7af22671d..000000000000 --- a/packages/mask-sdk/shared/messages.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Format: number method_name message -# or : number message -# Interpolation: ${word} - --32700 Parse error --32600 Invalid request --32601 The method "${method}" does not exist / is not available. --32602 Invalid params --32602 wallet_requestPermissions A permission request must contain at least 1 permission. --32602 wallet_requestPermissions Permission request contains unsupported permission ${permission}. --32603 Internal error --32000 Invalid input --32001 Resource not found --32002 Resource unavailable --32003 Transaction rejected --32004 Method not supported --32005 Limit exceeded --32006 JSON-RPC version not supported - -4001 User rejected the request -4100 The requested account and/or method has not been authorized by the user -4200 The requested method is not supported by this Ethereum provider -4900 The provider is disconnected from all chains -4901 The provider is disconnected from the specified chain - -# Our special behavior --32601 The method "eth_subscribe" is only available on the mainnet. diff --git a/packages/mask-sdk/shared/serializer.ts b/packages/mask-sdk/shared/serializer.ts deleted file mode 100644 index 64a74be536f6..000000000000 --- a/packages/mask-sdk/shared/serializer.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { JSONEncoder } from 'async-call-rpc/base.min' -import type { IsomorphicEncoder } from 'async-call-rpc/base.min' -import { MaskEthereumProviderRpcError } from './error.js' - -const replacer = (key: string, value: unknown): unknown => { - if (value === undefined) return { $type: 'undefined' } - if (value instanceof ArrayBuffer) return ArrayBufferEncode(value) - if (value instanceof Uint8Array) return U8ArrayEncode(value) - if (value instanceof Map) return MapEncode(value) - if (value instanceof MaskEthereumProviderRpcError) return MaskEthereumProviderRpcErrorEncode(value) - return value -} -const reviver = (key: string, value: any): unknown => { - if (typeof value === 'object' && value !== null && '$type' in value) { - if (value.$type === 'undefined') return undefined - return ( - ArrayBufferDecode(value) || - U8ArrayDecode(value) || - MapDecode(value) || - MaskEthereumProviderRpcErrorDecode(value) - ) - } - return value -} -export const encoder: IsomorphicEncoder = JSONEncoder({ - replacer, - reviver, -}) - -const [ArrayBufferEncode, ArrayBufferDecode] = createClassSerializer( - ArrayBuffer, - (e) => [...new Uint8Array(e)], - (e) => new Uint8Array(e).buffer, -) -const [U8ArrayEncode, U8ArrayDecode] = createClassSerializer( - Uint8Array, - (e) => [...e], - (e) => new Uint8Array(e), -) -const [MapEncode, MapDecode] = createClassSerializer( - Map, - (e) => [...e.entries()].map((value) => [replacer('', value[0]), replacer('', value[1])]), - (e) => new Map(e.map(([k, v]) => [reviver('', k), reviver('', v)])), -) -const [MaskEthereumProviderRpcErrorEncode, MaskEthereumProviderRpcErrorDecode] = createClassSerializer( - MaskEthereumProviderRpcError, - (e) => [replacer('', e.cause), Number(e.code), replacer('', e.data), String(e.message)], - ([cause, code, data, message]) => { - return new MaskEthereumProviderRpcError(Number(code), String(message), { - cause: reviver('', cause), - data: reviver('', data), - }) - }, -) -function createClassSerializer( - clz: { new (...args: any): T; name: string }, - encode: (a: T) => Q, - decode: (a: Q) => T, -) { - return [ - (v: T) => { - return { $type: clz.name, value: encode(v) } - }, - (v: { $type: string; value: Q }) => { - if (v.$type === clz.name) return decode(v.value) - return undefined - }, - ] as const -} diff --git a/packages/mask-sdk/shared/tsconfig.json b/packages/mask-sdk/shared/tsconfig.json deleted file mode 100644 index e2cb55c0cb23..000000000000 --- a/packages/mask-sdk/shared/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "rootDir": "./", - "outDir": "../dist/shared/", - "tsBuildInfoFile": "../dist/shared.tsbuildinfo" - }, - "include": ["./"], - "references": [{ "path": "../public-api/tsconfig.json" }] -} diff --git a/packages/mask-sdk/shared/types.ts b/packages/mask-sdk/shared/types.ts deleted file mode 100644 index e5052d379637..000000000000 --- a/packages/mask-sdk/shared/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface EIP2255Caveat { - type: string - value: unknown -} -export interface EIP2255Permission { - invoker: string - parentCapability: string - caveats: EIP2255Caveat[] -} -export interface EIP2255RequestedPermission { - parentCapability: string - date?: number -} -export interface EIP2255PermissionRequest { - [methodName: string]: { - [caveatName: string]: unknown - } -} diff --git a/packages/mask/.webpack/clean-hmr.ts b/packages/mask/.webpack/clean-hmr.ts deleted file mode 100644 index c0affef721f2..000000000000 --- a/packages/mask/.webpack/clean-hmr.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { rimraf } from 'rimraf' - -rimraf('hot*', { - glob: { cwd: new URL('../../../dist', import.meta.url) }, -}) diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts deleted file mode 100644 index c2411661ffcf..000000000000 --- a/packages/mask/.webpack/config.ts +++ /dev/null @@ -1,412 +0,0 @@ -/* spell-checker: disable */ -import webpack from 'webpack' -import type { Configuration as DevServerConfiguration } from 'webpack-dev-server' -const { ProvidePlugin, DefinePlugin, EnvironmentPlugin } = webpack - -import { emitJSONFile } from '@nice-labs/emit-file-webpack-plugin' -import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin' -import CopyPlugin from 'copy-webpack-plugin' -import DevtoolsIgnorePlugin from 'devtools-ignore-webpack-plugin' -import HTMLPlugin from 'html-webpack-plugin' -import TerserPlugin from 'terser-webpack-plugin' -import WebExtensionPlugin from 'webpack-target-webextension' -import { getGitInfo } from './git-info.js' -import { emitManifestFile } from './plugins/manifest.js' - -import { readFile, readdir } from 'node:fs/promises' -import { createRequire } from 'node:module' -import { join } from 'node:path' - -import { computeCacheKey, computedBuildFlags, normalizeBuildFlags, type BuildFlags } from './flags.js' -import { ProfilingPlugin } from './plugins/ProfilingPlugin.js' -import { joinEntryItem, normalizeEntryDescription, type EntryDescription } from './utils.js' - -import './clean-hmr.js' -import { TrustedTypesPlugin } from './plugins/TrustedTypesPlugin.js' - -const require = createRequire(import.meta.url) -const patchesDir = join(import.meta.dirname, '../../../patches') -const templateContent = readFile(join(import.meta.dirname, './template.html'), 'utf8') -const popupTemplateContent = readFile(join(import.meta.dirname, './popups.html'), 'utf8') - -export async function createConfiguration(_inputFlags: BuildFlags): Promise { - const VERSION = JSON.parse(await readFile(new URL('../../../package.json', import.meta.url), 'utf-8')).version - const flags = normalizeBuildFlags(_inputFlags) - const computedFlags = computedBuildFlags(flags) - const cacheKey = computeCacheKey(flags, computedFlags) - - const productionLike = flags.mode === 'production' || flags.profiling - - const nonWebpackJSFiles = join(flags.outputPath, './js') - const polyfillFolder = join(nonWebpackJSFiles, './polyfill') - - const pnpmPatches = readdir(patchesDir).then((files) => files.map((x) => join(patchesDir, x))) - const baseConfig = { - name: 'mask', - // to set a correct base path for source map - context: join(import.meta.dirname, '../../../'), - mode: flags.mode, - devtool: computedFlags.sourceMapKind, - target: ['web', 'es2022'], - entry: {}, - node: { - global: true, - __dirname: false, - __filename: false, - }, - experiments: { - futureDefaults: true, - syncImportAssertion: true, - deferImport: { asyncModule: 'error' }, - }, - cache: { - type: 'filesystem', - buildDependencies: { - config: [import.meta.filename], - patches: await pnpmPatches, - }, - version: cacheKey, - }, - resolve: { - extensionAlias: { - '.js': ['.js', '.tsx', '.ts'], - '.mjs': ['.mjs', '.mts'], - }, - extensions: ['.js', '.ts', '.tsx'], - alias: (() => { - const alias: Record = { - // conflict with SES - 'error-polyfill': require.resolve('./package-overrides/null.mjs'), - } - if (computedFlags.reactProductionProfiling) alias['react-dom$'] = require.resolve('react-dom/profiling') - if (flags.devtools) { - // Note: when devtools is enabled, we will install react-refresh/runtime manually to keep the correct react global hook installation order. - // https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/680 - alias[require.resolve('@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js')] = - require.resolve('./package-overrides/null.mjs') - } - return alias - })(), - // Polyfill those Node built-ins - fallback: { - http: require.resolve('stream-http'), - https: require.resolve('https-browserify'), - stream: require.resolve('stream-browserify'), - crypto: require.resolve('crypto-browserify'), - zlib: require.resolve('zlib-browserify'), - 'text-encoding': require.resolve('@sinonjs/text-encoding'), - }, - conditionNames: ['mask-src', '...'], - }, - module: { - parser: { - javascript: { - // Treat as missing export as error - strictExportPresence: true, - }, - }, - rules: [ - // Source map for libraries - computedFlags.sourceMapKind ? - { test: /\.js$/, enforce: 'pre', use: [require.resolve('source-map-loader')] } - : null, - // Patch old regenerator-runtime - { - test: /\..?js$/, - loader: require.resolve('./loaders/fix-regenerator-runtime.js'), - }, - // TypeScript - { - test: /\.tsx?$/, - parser: { worker: ['OnDemandWorker', '...'] }, - // Compile all ts files in the workspace - include: join(import.meta.dirname, '../../'), - loader: require.resolve('swc-loader'), - options: { - sourceMaps: !!computedFlags.sourceMapKind, - // https://swc.rs/docs/configuring-swc/ - jsc: { - preserveAllComments: true, - parser: { - syntax: 'typescript', - dynamicImport: true, - tsx: true, - }, - target: 'es2022', - externalHelpers: true, - transform: { - react: { - runtime: 'automatic', - development: !productionLike, - refresh: flags.reactRefresh && { - refreshReg: '$RefreshReg$', - refreshSig: '$RefreshSig$', - emitFullSignatures: true, - }, - }, - }, - experimental: { - keepImportAssertions: true, - }, - }, - }, - }, - { - test: /\.svg$/, - include: /node_modules[\\/]@lifi[\\/]wallet-management/, // Only effective for @lifi/wallet-management - loader: require.resolve('file-loader'), - }, - // compress svg files - flags.mode === 'production' ? - { - test: /\.svg$/, - loader: require.resolve('svgo-loader'), - // overrides - options: { - js2svg: { - pretty: false, - }, - }, - exclude: /node_modules/, - dependency: 'url', - type: 'asset/resource', - } - : null, - ], - }, - plugins: [ - new WebExtensionPlugin({ background: { pageEntry: 'background', serviceWorkerEntry: 'backgroundWorker' } }), - flags.sourceMapHideFrameworks !== false && - new DevtoolsIgnorePlugin({ - shouldIgnorePath: (path) => { - if (path.includes('masknet') || path.includes('dimensiondev')) return false - return path.includes('/node_modules/') || path.includes('/webpack/') - }, - }), - new ProvidePlugin({ - // Polyfill for Node global "Buffer" variable - Buffer: [require.resolve('buffer'), 'Buffer'], - 'process.nextTick': require.resolve('next-tick'), - }), - new EnvironmentPlugin({ - NODE_ENV: productionLike ? 'production' : flags.mode, - NODE_DEBUG: false, - WEB3_CONSTANTS_RPC: process.env.WEB3_CONSTANTS_RPC || '{}', - MASK_SENTRY_DSN: process.env.MASK_SENTRY_DSN || '', - MASK_SENTRY: process.env.MASK_SENTRY || 'disabled', - MASK_MIXPANEL: process.env.MASK_MIXPANEL || 'disabled', - NEXT_PUBLIC_FIREFLY_API_URL: process.env.NEXT_PUBLIC_FIREFLY_API_URL || '', - }), - new DefinePlugin({ - 'process.browser': 'true', - 'process.version': JSON.stringify('v20.0.0'), - // MetaMaskInpageProvider => extension-port-stream => readable-stream depends on stdin and stdout - 'process.stdout': '/* stdout */ null', - 'process.stderr': '/* stdin */ null', - }), - flags.reactRefresh && new ReactRefreshWebpackPlugin({ overlay: false, esModule: true }), - flags.profiling && new ProfilingPlugin(), - new TrustedTypesPlugin(), - ...emitManifestFile(flags, computedFlags), - new CopyPlugin({ - patterns: [ - { from: join(import.meta.dirname, '../public/'), to: flags.outputPath }, - { - from: join(import.meta.dirname, '../../injected-script/dist/injected-script.js'), - to: nonWebpackJSFiles, - }, - { from: join(import.meta.dirname, '../../gun-utils/gun.js'), to: nonWebpackJSFiles }, - { from: join(import.meta.dirname, '../../mask-sdk/dist/mask-sdk.js'), to: nonWebpackJSFiles }, - { - context: join(import.meta.dirname, '../../polyfills/dist/'), - from: '*.js', - to: polyfillFolder, - }, - { from: require.resolve('webextension-polyfill/dist/browser-polyfill.js'), to: polyfillFolder }, - { - from: - productionLike ? - require.resolve('../../../node_modules/ses/dist/lockdown.umd.min.js') - : require.resolve('../../../node_modules/ses/dist/lockdown.umd.js'), - to: join(polyfillFolder, 'lockdown.js'), - }, - { - from: join(import.meta.dirname, '../../sentry/dist/sentry.js'), - to: nonWebpackJSFiles, - }, - ], - }), - ...(() => { - const { BRANCH_NAME, BUILD_DATE, COMMIT_DATE, COMMIT_HASH, DIRTY } = getGitInfo() - const json = { - BRANCH_NAME, - BUILD_DATE, - channel: flags.channel, - COMMIT_DATE, - COMMIT_HASH, - DIRTY, - VERSION, - REACT_DEVTOOLS_EDITOR_URL: flags.devtools ? flags.devtoolsEditorURI : undefined, - } - return [ - emitJSONFile({ content: json, name: 'build-info.json' }), - emitJSONFile({ content: { ...json, channel: 'beta' }, name: 'build-info-beta.json' }), - ] - })(), - ], - // Focus on performance optimization. Not for download size/cache stability optimization. - optimization: { - // we don't need deterministic, and we also don't have chunk request at init we don't need "size" - chunkIds: productionLike ? 'total-size' : 'named', - concatenateModules: productionLike, - flagIncludedChunks: productionLike, - mangleExports: false, - minimize: productionLike, - minimizer: [ - new TerserPlugin({ - minify: TerserPlugin.swcMinify, - // https://swc.rs/docs/config-js-minify - terserOptions: { - compress: { - drop_debugger: false, - ecma: 2020, - keep_classnames: true, - keep_fnames: true, - keep_infinity: true, - passes: 3, - pure_getters: false, - sequences: false, - }, - mangle: false, - }, - }), - ], - moduleIds: flags.channel === 'stable' && flags.mode === 'production' ? 'deterministic' : 'named', - nodeEnv: false, // provided in EnvironmentPlugin - realContentHash: false, - removeAvailableModules: productionLike, - // cannot use single, mv3 requires a different runtime. - // cannot use false, in some cases there are more than 1 runtime in the single page and cause bug. - runtimeChunk: { - name: (entry: { name: string }) => { - return entry.name === 'backgroundWorker' ? false : 'runtime' - }, - }, - splitChunks: - productionLike ? undefined : ( - { - maxInitialRequests: Infinity, - chunks: 'all', - cacheGroups: { - // split each npm package into a chunk to give better debug experience. - defaultVendors: { - test: /[/\\]node_modules[/\\]/, - name(module: any) { - const path = (module.context as string) - .replace(/\\/g, '/') - .match(/node_modules\/\.pnpm\/(.+)/)![1] - .split('/') - // [@org+pkgname@version, node_modules, @org, pkgname, ...inner path] - if (path[0].startsWith('@')) return `npm-ns.${path[2].replace('@', '')}.${path[3]}` - // [pkgname@version, node_modules, pkgname, ...inner path] - return `npm.${path[2]}` - }, - }, - }, - } - ), - }, - output: { - environment: { - module: false, - dynamicImport: true, - }, - path: flags.outputPath, - filename: 'entry/[name].js', - chunkFilename: productionLike ? 'bundled/[id].js' : 'bundled/chunk-[name].js', - assetModuleFilename: 'assets/[hash][ext][query]', - webassemblyModuleFilename: 'assets/[hash].wasm', - hotUpdateMainFilename: 'hot/[runtime].[fullhash].json', - hotUpdateChunkFilename: 'hot/[id].[fullhash].js', - devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]', - globalObject: 'globalThis', - publicPath: '/', - clean: flags.mode === 'production', - trustedTypes: - String(computedFlags.sourceMapKind).includes('eval') ? - { - policyName: 'webpack', - } - : undefined, - }, - ignoreWarnings: [ - /Failed to parse source map/, - /Critical dependency: the request of a dependency is an expression/, - ], - devServer: { - hot: flags.hmr ? 'only' : false, - liveReload: false, - client: flags.hmr ? undefined : false, - } as DevServerConfiguration, - stats: flags.mode === 'production' ? 'errors-only' : undefined, - } satisfies webpack.Configuration - - const entries = (baseConfig.entry = { - dashboard: withReactDevTools(join(import.meta.dirname, '../dashboard/initialization/index.ts')), - popups: withReactDevTools(join(import.meta.dirname, '../popups/initialization/index.ts')), - swap: withReactDevTools(join(import.meta.dirname, '../swap/initialization/index.ts')), - contentScript: withReactDevTools(join(import.meta.dirname, '../content-script/index.ts')), - background: normalizeEntryDescription(join(import.meta.dirname, '../background/initialization/mv2-entry.ts')), - backgroundWorker: normalizeEntryDescription( - join(import.meta.dirname, '../background/initialization/mv3-entry.ts'), - ), - devtools: undefined as EntryDescription | undefined, - }) satisfies Record - delete entries.devtools - - baseConfig.plugins.push( - await addHTMLEntry({ chunks: ['dashboard'], filename: 'dashboard.html', perf: flags.profiling }), - await addHTMLEntry({ chunks: ['popups'], filename: 'popups.html', perf: flags.profiling }), - await addHTMLEntry({ chunks: ['swap'], filename: 'swap.html', perf: flags.profiling }), - await addHTMLEntry({ - chunks: ['contentScript'], - filename: 'generated__content__script.html', - perf: flags.profiling, - }), - await addHTMLEntry({ chunks: ['background'], filename: 'background.html', gun: true, perf: flags.profiling }), - ) - if (flags.devtools) { - entries.devtools = normalizeEntryDescription(join(import.meta.dirname, '../devtools/panels/index.tsx')) - baseConfig.plugins.push( - await addHTMLEntry({ chunks: ['devtools'], filename: 'devtools-background.html', perf: flags.profiling }), - ) - } - return baseConfig - - function withReactDevTools(entry: string | string[] | EntryDescription): EntryDescription { - if (!flags.devtools) return normalizeEntryDescription(entry) - entry = normalizeEntryDescription(entry) - entry.import = joinEntryItem(join(import.meta.dirname, '../devtools/content-script/index.ts'), entry.import) - return entry - } -} - -async function addHTMLEntry({ - gun, - perf, - ...options -}: HTMLPlugin.Options & { - gun?: boolean - perf: boolean -}) { - let template = await (options.filename === 'popups.html' && !perf ? popupTemplateContent : templateContent) - if (gun) template = template.replace(``, '') - if (perf) template = template.replace(``, '') - return new HTMLPlugin({ - templateContent: template, - inject: 'body', - scriptLoading: 'defer', - minify: false, - ...options, - }) -} diff --git a/packages/mask/.webpack/flags.ts b/packages/mask/.webpack/flags.ts deleted file mode 100644 index 07b76a2a45d7..000000000000 --- a/packages/mask/.webpack/flags.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { Configuration } from 'webpack' -import { join, isAbsolute } from 'node:path' - -export enum ManifestFile { - ChromiumMV2 = 'chromium-mv2', - ChromiumBetaMV2 = 'chromium-beta-mv2', - ChromiumMV3 = 'chromium-mv3', - FirefoxMV2 = 'firefox-mv2', - FirefoxMV3 = 'firefox-mv3', - SafariMV3 = 'safari-mv3', -} -export interface BuildFlags { - /** If this field is set, manifest.json will copy the content of manifest-*.json */ - manifestFile?: ManifestFile - mode: 'development' | 'production' - /** @default 'stable' */ - channel?: 'stable' | 'beta' | 'insider' - /** @default false */ - profiling?: boolean - /** @default true in development */ - hmr?: boolean - /** @default true in development and hmr is true */ - reactRefresh?: boolean - outputPath?: string - /** @default true */ - devtools?: boolean - /** @default "vscode://file/{path}:{line}" */ - devtoolsEditorURI?: string - /** @default true */ - sourceMapPreference?: boolean | string - /** @default true */ - sourceMapHideFrameworks?: boolean | undefined -} -export type NormalizedFlags = Required -export function normalizeBuildFlags(flags: BuildFlags): NormalizedFlags { - const { - mode, - profiling = false, - channel = 'stable', - devtoolsEditorURI = 'vscode://file/{path}:{line}', - sourceMapHideFrameworks = true, - manifestFile = ManifestFile.ChromiumMV2, - } = flags - let { - hmr = mode === 'development', - reactRefresh = hmr, - devtools = mode === 'development' || channel !== 'stable', - sourceMapPreference = mode === 'development', - outputPath = join(import.meta.dirname, '../../../', mode === 'development' ? 'dist' : 'build'), - } = flags - if (!isAbsolute(outputPath)) outputPath = join(import.meta.dirname, '../../../', outputPath) - - if (mode === 'production' || profiling) hmr = false - if (!hmr) reactRefresh = false - - return { - mode, - channel, - outputPath, - // Runtime - manifestFile, - // DX - hmr, - reactRefresh, - sourceMapPreference, - sourceMapHideFrameworks, - devtools, - devtoolsEditorURI, - // CI / profiling - profiling, - } -} - -export interface ComputedFlags { - sourceMapKind: Configuration['devtool'] - reactProductionProfiling: boolean -} - -export function computedBuildFlags( - flags: Pick, 'mode' | 'sourceMapPreference' | 'profiling' | 'manifestFile'>, -): ComputedFlags { - let sourceMapKind: Configuration['devtool'] = false - if (flags.mode === 'production') sourceMapKind = 'source-map' - - if (flags.sourceMapPreference) { - if (flags.manifestFile.includes('3')) sourceMapKind = 'inline-cheap-source-map' - else sourceMapKind = 'eval-cheap-source-map' - - if (flags.mode === 'production') sourceMapKind = 'source-map' - if (typeof flags.sourceMapPreference === 'string') sourceMapKind = flags.sourceMapPreference - if (process.env.CI) sourceMapKind = false - } - - const reactProductionProfiling = flags.profiling - return { sourceMapKind, reactProductionProfiling } -} - -export function computeCacheKey(flags: Required, computedFlags: ComputedFlags) { - return [ - '1', - 'node' + process.version, - flags.mode, - computedFlags.reactProductionProfiling, // it will affect module resolution of react-dom - flags.devtools, // it will affect module resolution of react-refresh-webpack-plugin/client/ReactRefreshEntry.js - flags.reactRefresh, // it will affect all TSX files - ].join('-') -} diff --git a/packages/mask/.webpack/git-info.ts b/packages/mask/.webpack/git-info.ts deleted file mode 100644 index df0778947bbb..000000000000 --- a/packages/mask/.webpack/git-info.ts +++ /dev/null @@ -1,26 +0,0 @@ -import git from '@nice-labs/git-rev' - -interface GitInfoReport { - BUILD_DATE?: string | undefined - COMMIT_HASH?: string | undefined - COMMIT_DATE?: string | undefined - BRANCH_NAME?: string | undefined - DIRTY?: boolean | undefined -} - -/** Get git info */ -export function getGitInfo(): GitInfoReport { - const report: GitInfoReport = {} - try { - if (!git.default.isRepository()) return report - const DIRTY = git.default.isDirty() - report.BUILD_DATE = new Date().toISOString() - report.COMMIT_HASH = git.default.commitHash() - report.COMMIT_DATE = git.default.commitDate().toISOString() - report.BRANCH_NAME = git.default.branchName() - report.DIRTY = DIRTY - } catch { - // ignore - } - return report -} diff --git a/packages/mask/.webpack/loaders/fix-regenerator-runtime.js b/packages/mask/.webpack/loaders/fix-regenerator-runtime.js deleted file mode 100644 index 53c64acab9bf..000000000000 --- a/packages/mask/.webpack/loaders/fix-regenerator-runtime.js +++ /dev/null @@ -1,10 +0,0 @@ -// import type { LoaderContext } from 'webpack' - -const reg = /Gp\[iteratorSymbol\]\s*=\s*function\s*\(\)\s*{\n\s*return this;\n\s*};/ -export default function (/* this: LoaderContext<{}>,*/ source /* : string */) { - if (reg.test(source)) { - // this.emitWarning(new Error(' contains old version of regenerator runtime.')) - return source.replace(reg, 'define(Gp, iteratorSymbol, function () { return this });') - } - return source -} diff --git a/packages/mask/.webpack/manifest/manifest-mv3.json b/packages/mask/.webpack/manifest/manifest-mv3.json deleted file mode 100644 index de02f7d7e4d6..000000000000 --- a/packages/mask/.webpack/manifest/manifest-mv3.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Mask Network", - "version": "0", - "manifest_version": 3, - "permissions": ["storage", "webNavigation", "activeTab", "scripting"], - "optional_permissions": ["notifications", "clipboardRead"], - "optional_host_permissions": [""], - "background": { "service_worker": "/manifest-v3.entry.js" }, - "options_ui": { "page": "dashboard.html", "open_in_tab": true }, - "icons": { - "16": "assets/16x16.png", - "48": "assets/48x48.png", - "128": "assets/128x128.png", - "256": "assets/256x256.png" - }, - "action": { "default_popup": "popups.html" }, - "homepage_url": "https://mask.io", - "description": "The portal to the new & open Internet. Send encrypted message and decentralized Apps right on top of social networks.", - "web_accessible_resources": [ - { - "resources": ["js/*", "bundled/*", "entry/*", "*.svg", "*.png", "*.css", "build-info.json"], - "matches": [""], - "use_dynamic_url": true - }, - { - "resources": ["hot/*"], - "matches": [""], - "use_dynamic_url": false - } - ], - "content_security_policy": { - "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; trusted-types default dompurify mask;" - }, - "minimum_chrome_version": "102" -} diff --git a/packages/mask/.webpack/manifest/manifest.json b/packages/mask/.webpack/manifest/manifest.json deleted file mode 100644 index 512143e80759..000000000000 --- a/packages/mask/.webpack/manifest/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Mask Network", - "version": "0", - "manifest_version": 2, - "permissions": ["storage", "webNavigation", "activeTab"], - "optional_permissions": ["", "notifications", "clipboardRead"], - "background": { "page": "background.html" }, - "options_ui": { "page": "dashboard.html", "open_in_tab": true }, - "icons": { - "16": "assets/16x16.png", - "48": "assets/48x48.png", - "128": "assets/128x128.png", - "256": "assets/256x256.png" - }, - "browser_action": { "default_popup": "popups.html" }, - "homepage_url": "https://mask.io", - "description": "The portal to the new & open Internet. Send encrypted message and decentralized Apps right on top of social networks.", - "web_accessible_resources": ["js/*", "bundled/*", "entry/*", "*.svg", "*.png", "*.jpg", "*.css", "build-info.json"], - "content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'; trusted-types default dompurify mask;" -} diff --git a/packages/mask/.webpack/package-overrides/null.mjs b/packages/mask/.webpack/package-overrides/null.mjs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/mask/.webpack/plugins/ProfilingPlugin.ts b/packages/mask/.webpack/plugins/ProfilingPlugin.ts deleted file mode 100644 index b5897e9e861a..000000000000 --- a/packages/mask/.webpack/plugins/ProfilingPlugin.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Compiler } from 'webpack' - -const connect = 'if (cachedModule !== undefined) {' -const enter = '// Create a new module (and put it into the cache)' -const leaving = 'globalThis.measure?.leave?.(moduleId);' -const leave1 = 'if(threw) delete __webpack_module_cache__[moduleId];' -const leave3 = '// Return the exports of the module' - -type data = Array<[from: string | number, asyncTarget: string[], deferTarget: string[]]> -export class ProfilingPlugin { - apply(compiler: Compiler) { - const { javascript, Template, RuntimeModule } = compiler.webpack - class HintRuntimeModule extends RuntimeModule { - constructor(private modules: data) { - super('profiling-hint') - } - override generate() { - return `globalThis.measure?.set_compile_info?.(${JSON.stringify(this.modules)});` - } - } - compiler.hooks.compilation.tap('ProfilingPlugin', (compilation) => { - compilation.hooks.afterOptimizeModuleIds.tap('ProfilingPlugin', (modules) => { - const data: data = [] - for (const module of modules) { - const asyncTarget = new Set() - const deferTarget = new Set() - for (const dep of [...module.dependencies, ...module.blocks.flatMap((x) => x.dependencies)]) { - if (dep.defer) - deferTarget.add( - String(compilation.chunkGraph.getModuleId(compilation.moduleGraph.getModule(dep)!)), - ) - else if (dep.type.startsWith('import()')) - asyncTarget.add( - String(compilation.chunkGraph.getModuleId(compilation.moduleGraph.getModule(dep)!)), - ) - } - if (asyncTarget.size || deferTarget.size) - data.push([compilation.chunkGraph.getModuleId(module), [...asyncTarget], [...deferTarget]]) - } - compilation.hooks.additionalChunkRuntimeRequirements.tap('ProfilingPlugin', (chunk) => { - if (data.length) - compilation.addRuntimeModule(chunk, new HintRuntimeModule(data), compilation.chunkGraph) - }) - }) - javascript.JavascriptModulesPlugin.getCompilationHooks(compilation).renderRequire.tap( - 'ProfilingPlugin', - (source, context) => { - return source - .replace( - connect, - Template.asString([connect, Template.indent('globalThis.measure?.connect?.(moduleId);')]), - ) - .replace(enter, Template.asString([enter, 'globalThis.measure?.enter?.(moduleId);'])) - .replace(leave3, Template.asString([leave3, leaving])) - }, - ) - }) - } -} diff --git a/packages/mask/.webpack/plugins/TrustedTypesPlugin.ts b/packages/mask/.webpack/plugins/TrustedTypesPlugin.ts deleted file mode 100644 index 21d76397a03e..000000000000 --- a/packages/mask/.webpack/plugins/TrustedTypesPlugin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import webpack, { type Compiler } from 'webpack' - -const { RuntimeModule, Template } = webpack -class TrustedTypesRuntimeModule extends RuntimeModule { - constructor() { - super('trustedTypes', RuntimeModule.STAGE_TRIGGER) - } - override generate(): string { - const { compilation } = this - if (!compilation) - return Template.asString('/* TrustedTypesRuntimeModule skipped because compilation is undefined. */') - return Template.asString([ - 'if (typeof trustedTypes !== "undefined" && location.protocol.includes("extension") && !trustedTypes.defaultPolicy) {', - Template.indent([`trustedTypes.createPolicy('default', { createScriptURL: (string) => string });`]), - '}', - ]) - } -} -export class TrustedTypesPlugin { - apply(compiler: Compiler) { - compiler.hooks.compilation.tap('TrustedTypes', (compilation) => { - compilation.hooks.afterChunks.tap('TrustedTypes', (chunks) => { - for (const c of chunks) { - if (!c.hasEntryModule()) continue - compilation.addRuntimeModule(c, new TrustedTypesRuntimeModule(), compilation.chunkGraph) - } - }) - }) - } -} diff --git a/packages/mask/.webpack/plugins/manifest.ts b/packages/mask/.webpack/plugins/manifest.ts deleted file mode 100644 index 8fb71395894b..000000000000 --- a/packages/mask/.webpack/plugins/manifest.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* spell-checker: disable */ -import emitFile from '@nice-labs/emit-file-webpack-plugin' -import type { ComputedFlags, NormalizedFlags } from '../flags.js' -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' -import type { Manifest } from 'webextension-polyfill' -import { parseJSONc } from '../utils.js' -import { join } from 'node:path' - -const cloneDeep = (x: T): T => JSON.parse(JSON.stringify(x)) - -export function emitManifestFile(flags: NormalizedFlags, computedFlags: ComputedFlags) { - const manifest = prepareAllManifest(flags, computedFlags) - const plugins = [] - for (const [fileName, fileContent] of manifest) { - plugins.push( - emitFile.emitJSONFile({ - name: `manifest-${fileName}.json`, - content: fileContent, - }), - ) - } - if (flags.mode === 'development') { - if (!existsSync(flags.outputPath)) mkdirSync(flags.outputPath, { recursive: true }) - // Note: this is to optimize the DX of the following case: - // change manifest.json within all the targets we support. - // if we use emitFile plugin here, manifest.json will be overwritten by the plugin once _any_ new file is emitted, - // which is annoying when debugging in this case. - writeFileSync( - join(flags.outputPath, 'manifest.json'), - JSON.stringify(manifest.get(flags.manifestFile)!, undefined, 4), - ) - } else { - plugins.push( - emitFile.emitJSONFile({ - name: `manifest.json`, - content: manifest.get(flags.manifestFile)!, - }), - ) - } - return plugins -} - -type ManifestV2 = Manifest.WebExtensionManifest & { manifest_version: 2; key?: string } -type ManifestV3 = Manifest.WebExtensionManifest & { manifest_version: 3; key?: string } -type ModifyAcceptFlags = Pick -type ManifestPresets = - | [flags: ModifyAcceptFlags, base: ManifestV2, modify?: (manifest: ManifestV2) => void] - | [flags: ModifyAcceptFlags, base: ManifestV3, modify?: (manifest: ManifestV3) => void] -function prepareAllManifest(flags: NormalizedFlags, computedFlags: ComputedFlags) { - const mv2Base: ManifestV2 = parseJSONc(readFileSync(new URL('../manifest/manifest.json', import.meta.url), 'utf-8')) - const mv3Base: ManifestV3 = parseJSONc( - readFileSync(new URL('../manifest/manifest-mv3.json', import.meta.url), 'utf-8'), - ) - - const manifestFlags: Record = { - 'chromium-beta-mv2': [{ ...flags, channel: 'beta' }, mv2Base], - 'chromium-mv2': [flags, mv2Base], - 'chromium-mv3': [flags, mv3Base], - 'firefox-mv2': [flags, mv2Base, (manifest: ManifestV2) => manifest.permissions!.push('tabs')], - 'firefox-mv3': [ - flags, - mv3Base, - (manifest: ManifestV3) => { - manifest.host_permissions = (manifest as any).optional_host_permissions - manifest.background = { page: 'background.html' } - }, - ], - 'safari-mv3': [flags, mv3Base], - } - const manifest = new Map() - for (const fileName in manifestFlags) { - if (!Object.hasOwn(manifestFlags, fileName)) continue - const [flags, base, modify]: ManifestPresets = (manifestFlags as any)[fileName] - const fileContent = cloneDeep(base) - editManifest(fileContent, cloneDeep(flags), cloneDeep(computedFlags)) - modify?.(fileContent as any) - manifest.set(fileName as any, fileContent) - } - return manifest -} - -function editManifest(manifest: ManifestV2 | ManifestV3, flags: ModifyAcceptFlags, computedFlags: ComputedFlags) { - if (flags.mode === 'development') manifest.name += ' (dev)' - else if (flags.channel === 'beta') manifest.name += ' (beta)' - else if (flags.channel === 'insider') manifest.name += ' (insider)' - - fixTheExtensionID(manifest) - if (flags.devtools) manifest.devtools_page = 'devtools-background.html' - - const topPackageJSON = JSON.parse(readFileSync(new URL('../../../../package.json', import.meta.url), 'utf-8')) - manifest.version = topPackageJSON.version - - if (manifest.manifest_version === 2) { - if (String(computedFlags.sourceMapKind).includes('eval')) { - manifest.content_security_policy = `script-src 'self' 'unsafe-eval'; object-src 'self'; require-trusted-types-for 'script'; trusted-types default dompurify webpack mask` - } - - if (flags.hmr) { - ;(manifest.web_accessible_resources as string[]).push('*.json', '*.js') - } - } else { - // https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/ - } -} - -// cspell: disable-next-line -// ID: jkoeaghipilijlahjplgbfiocjhldnap -// Note: with this key you cannot upload it to the extension store -function fixTheExtensionID(manifest: ManifestV2 | ManifestV3) { - manifest.key = - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoz51rhO1w+wD' + - '0EKZJEFJaSMkIcIj0qRadfi0tqcl5nbpuJAsafvLe3MaTbW9LhbixTg9' + - 'PHybO3tlYUFJrZgUuZlEvt2T6SKIu6Rs9e9B3/brNQG3+hCHudbZkq2W' + - 'G2IzO44dglrs24bRp/pV5oIif0bLuwrzvYsPQ6hgSp+5gc4pg0LEJPLF' + - 'p01fbORDknWt8suJmEMz7S0O5+u13+34NvxYzUNeLJF9gYrd4zzrAFYI' + - 'TDEYcqr0OMZvVrKz7IkJasER1uJyoGj4gFJeXNGE8y4Sqb150wBju70l' + - 'KNKlNevWDRJKasG9CjagAD2+BAfqNyltn7KwK7jAyL1w6d6mOwIDAQAB' -} diff --git a/packages/mask/.webpack/popups.html b/packages/mask/.webpack/popups.html deleted file mode 100644 index 9dd1265dc9af..000000000000 --- a/packages/mask/.webpack/popups.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - Mask Network - - - - - - - - - - - - - - - - - - -

-
- - - - - - -

Loading

-
-
- - diff --git a/packages/mask/.webpack/swap.html b/packages/mask/.webpack/swap.html deleted file mode 100644 index 9dd1265dc9af..000000000000 --- a/packages/mask/.webpack/swap.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - Mask Network - - - - - - - - - - - - - - - - - - -
-
- - - - - - -

Loading

-
-
- - diff --git a/packages/mask/.webpack/template.html b/packages/mask/.webpack/template.html deleted file mode 100644 index a3121fc32217..000000000000 --- a/packages/mask/.webpack/template.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - Mask Network - - - - - - - - - - - - - - - - - - diff --git a/packages/mask/.webpack/tsconfig.json b/packages/mask/.webpack/tsconfig.json deleted file mode 100644 index 96475e1d2b61..000000000000 --- a/packages/mask/.webpack/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "incremental": false, - "composite": false - }, - "include": ["./**/*.ts"] -} diff --git a/packages/mask/.webpack/utils.ts b/packages/mask/.webpack/utils.ts deleted file mode 100644 index 3e5356c4784e..000000000000 --- a/packages/mask/.webpack/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Configuration } from 'webpack' - -export function normalizeEntryDescription(entry: string | string[] | EntryDescription): EntryDescription { - if (typeof entry === 'string') return { import: entry } - if (Array.isArray(entry)) return { import: entry } - return entry -} -export function joinEntryItem(x: string | string[], y: string | string[]): string[] { - if (typeof x === 'string') return [x].concat(y) - return x.concat(y) -} - -export type EntryDescription = Exclude< - Exclude, string | string[] | Function>[string], - string | string[] -> -export function parseJSONc(data: string) { - data = data - .split('\n') - .filter((line) => !line.trim().startsWith('//')) - .join('\n') - - try { - return JSON.parse(data) - } catch (err) { - throw new TypeError('Only // comments are supported.', { cause: err }) - } -} diff --git a/packages/mask/.webpack/webpack.config.js b/packages/mask/.webpack/webpack.config.js deleted file mode 100644 index 8e8155aa1b4a..000000000000 --- a/packages/mask/.webpack/webpack.config.js +++ /dev/null @@ -1,2 +0,0 @@ -// webpack-cli does not know swc-node -export { default } from './webpack.config.ts' diff --git a/packages/mask/.webpack/webpack.config.ts b/packages/mask/.webpack/webpack.config.ts deleted file mode 100644 index 4e833ce93773..000000000000 --- a/packages/mask/.webpack/webpack.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createConfiguration } from './config.js' -export default async function (cli_env: any, argv: any) { - const flags = JSON.parse(Buffer.from(cli_env.flags, 'hex').toString('utf-8')) - return createConfiguration(flags) -} diff --git a/packages/mask/background/database/avatar-cache/avatar.ts b/packages/mask/background/database/avatar-cache/avatar.ts deleted file mode 100644 index 1198848478a5..000000000000 --- a/packages/mask/background/database/avatar-cache/avatar.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - queryAvatarDB, - isAvatarOutdatedDB, - storeAvatarDB, - type IdentifierWithAvatar, - createAvatarDBAccess, - queryAvatarMetaDataDB, -} from './db.js' -import { blobToDataURL, memoizePromise } from '@masknet/kit' -import { createTransaction } from '../utils/openDB.js' -import { memoize } from 'lodash-es' -import type { PersonaIdentifier } from '@masknet/shared-base' - -const impl = memoizePromise( - memoize, - async function (identifiers: readonly IdentifierWithAvatar[]): Promise> { - const promises: Array> = [] - - const map = new Map() - const t = createTransaction(await createAvatarDBAccess(), 'readonly')('avatars') - for (const id of identifiers) { - // Must not await here. Because we insert non-idb async operation (blobToDataURL). - promises.push( - queryAvatarDB(t, id) - .then((avatar) => { - if (!avatar) return - return typeof avatar === 'string' ? avatar : ( - blobToDataURL(new Blob([avatar], { type: 'image/png' })) - ) - }) - .then((url) => url && map.set(id, url)), - ) - } - - await Promise.allSettled(promises) - return map - }, - (id: IdentifierWithAvatar[]) => id.flatMap((x) => x.toText()).join(';'), -) - -const queryAvatarLastUpdateTimeImpl = memoizePromise( - memoize, - async (identifier: IdentifierWithAvatar) => { - const t = createTransaction(await createAvatarDBAccess(), 'readonly')('metadata') - const metadata = await queryAvatarMetaDataDB(t, identifier) - return metadata?.lastUpdateTime - }, - (x) => x, -) - -export const queryAvatarsDataURL: ( - identifiers: readonly IdentifierWithAvatar[], -) => Promise> = impl - -export const queryAvatarLastUpdateTime: (identifier: PersonaIdentifier) => Promise = - queryAvatarLastUpdateTimeImpl - -/** - * Store an avatar with a url for an identifier. - * @param identifier - This avatar belongs to. - * @param avatar - Avatar to store. If it is a string, will try to fetch it. - */ - -export async function storeAvatar(identifier: IdentifierWithAvatar, avatar: ArrayBuffer | string): Promise { - try { - if (typeof avatar === 'string') { - if (avatar.startsWith('https') === false) return - const isOutdated = await isAvatarOutdatedDB( - createTransaction(await createAvatarDBAccess(), 'readonly')('metadata'), - identifier, - 'lastUpdateTime', - ) - if (isOutdated) { - // ! must fetch before create the transaction - const buffer = await fetch(avatar).then( - (r) => r.arrayBuffer(), - () => avatar, - ) - { - const t = createTransaction(await createAvatarDBAccess(), 'readwrite')('avatars', 'metadata') - await storeAvatarDB(t, identifier, buffer) - } - } - // else do nothing - } else { - const t = createTransaction(await createAvatarDBAccess(), 'readwrite')('avatars', 'metadata') - await storeAvatarDB(t, identifier, avatar) - } - } catch (error) { - console.error('[AvatarDB] Store avatar failed', error) - } finally { - queryAvatarLastUpdateTimeImpl.cache.clear() - impl.cache.clear() - } -} diff --git a/packages/mask/background/database/avatar-cache/cleanup.ts b/packages/mask/background/database/avatar-cache/cleanup.ts deleted file mode 100644 index f0dec276cbc2..000000000000 --- a/packages/mask/background/database/avatar-cache/cleanup.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createTransaction } from '../utils/openDB.js' -import { createAvatarDBAccess, deleteAvatarsDB, type IdentifierWithAvatar, queryAvatarOutdatedDB } from './db.js' - -export async function cleanAvatarDB(anotherList: Set) { - const t = createTransaction(await createAvatarDBAccess(), 'readwrite')('avatars', 'metadata') - const outdated = await queryAvatarOutdatedDB(t, 'lastAccessTime') - for (const each of outdated) { - anotherList.add(each) - } - await deleteAvatarsDB(t, Array.from(anotherList.keys())) -} diff --git a/packages/mask/background/database/avatar-cache/db.ts b/packages/mask/background/database/avatar-cache/db.ts deleted file mode 100644 index 018da0c63246..000000000000 --- a/packages/mask/background/database/avatar-cache/db.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { openDB, type DBSchema } from 'idb/with-async-ittr' -import { ECKeyIdentifier, Identifier, type PersonaIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { createDBAccess, createTransaction, type IDBPSafeTransaction } from '../utils/openDB.js' - -const pendingUpdate = new Map>() -// This setTimeout is ok because it is only 10 seconds in mv3 and ok if data is lost. -// eslint-disable-next-line no-restricted-globals -let pendingUpdateTimer: ReturnType | null - -// #region Schema -export type IdentifierWithAvatar = ProfileIdentifier | PersonaIdentifier -type AvatarRecord = ArrayBuffer | string -interface AvatarMetadataRecord { - identifier: string - lastUpdateTime: Date - lastAccessTime: Date -} -interface AvatarDBSchema extends DBSchema { - /** Use out-of-line keys */ - avatars: { - value: AvatarRecord - key: string - } - /** Key is value.identifier */ - metadata: { - value: AvatarMetadataRecord - key: string - } -} -// #endregion -export const createAvatarDBAccess = createDBAccess(() => { - return openDB('maskbook-avatar-cache', 1, { - upgrade(db, oldVersion, newVersion, transaction) { - // Out line keys - db.createObjectStore('avatars') - db.createObjectStore('metadata', { keyPath: 'identifier' }) - }, - }) -}) -/** - * Store avatar into database - */ -export async function storeAvatarDB( - t: IDBPSafeTransaction, - id: IdentifierWithAvatar, - avatar: ArrayBuffer | string, -): Promise { - const meta: AvatarMetadataRecord = { - identifier: id.toText(), - lastUpdateTime: new Date(), - lastAccessTime: new Date(), - } - await t.objectStore('avatars').put(avatar, id.toText()) - await t.objectStore('metadata').put(meta) -} -/** - * Read avatar out - */ -export async function queryAvatarDB( - t: IDBPSafeTransaction, - id: IdentifierWithAvatar, -): Promise { - const result = await t.objectStore('avatars').get(id.toText()) - if (result) scheduleAvatarMetaUpdate(id, { lastAccessTime: new Date() }) - return result || null -} - -export async function queryAvatarMetaDataDB( - t: IDBPSafeTransaction, - id: IdentifierWithAvatar, -) { - return t.objectStore('metadata').get(id.toText()) -} -function scheduleAvatarMetaUpdate(id: IdentifierWithAvatar, meta: Partial) { - pendingUpdate.set(id, meta) - - if (pendingUpdateTimer) return - const timeout = browser.runtime.getManifest().version === '2' ? 60 * 1000 : 10 * 1000 - // eslint-disable-next-line no-restricted-globals - pendingUpdateTimer = setTimeout(async () => { - try { - const t = createTransaction(await createAvatarDBAccess(), 'readwrite')('metadata') - for (const [id, meta] of pendingUpdate) { - const old = await t.objectStore('metadata').get(id.toText()) - await t.objectStore('metadata').put({ ...old, ...meta } as AvatarMetadataRecord) - } - } finally { - pendingUpdateTimer = null - pendingUpdate.clear() - } - }, timeout) -} - -/** - * Find avatar lastUpdateTime or lastAccessTime out-of-date - * @param attribute - Which attribute want to query - * @param deadline - Select all identifiers before a date - * defaults to 14 days for lastAccessTime - * defaults to 7 days for lastUpdateTime - * @internal - */ -export async function queryAvatarOutdatedDB( - t: IDBPSafeTransaction, - attribute: 'lastUpdateTime' | 'lastAccessTime', - deadline: Date = new Date(Date.now() - 1000 * 60 * 60 * 24 * (attribute === 'lastAccessTime' ? 14 : 7)), -) { - const outdated: IdentifierWithAvatar[] = [] - for await (const { value } of t.objectStore('metadata')) { - if (deadline > value[attribute]) { - const id = Identifier.from(value.identifier) - if (id.isNone()) continue - if (id.value instanceof ProfileIdentifier || id.value instanceof ECKeyIdentifier) outdated.push(id.value) - } - } - return outdated -} -/** - * Query if the avatar is outdated - * @param attribute - Which attribute want to query - * @param deadline - Select all identifiers before a date - * defaults to 30 days for lastAccessTime - * defaults to 7 days for lastUpdateTime - * @internal - */ -export async function isAvatarOutdatedDB( - t: IDBPSafeTransaction, - identifier: IdentifierWithAvatar, - attribute: 'lastUpdateTime' | 'lastAccessTime', - deadline: Date = new Date(Date.now() - 1000 * 60 * 60 * 24 * (attribute === 'lastAccessTime' ? 30 : 7)), -): Promise { - const meta = await t.objectStore('metadata').get(identifier.toText()) - if (!meta) return true - if (deadline > meta[attribute]) return true - return false -} -/** - * Batch delete avatars - * @internal - */ -export async function deleteAvatarsDB( - t: IDBPSafeTransaction, - ids: IdentifierWithAvatar[], -): Promise { - for (const id of ids) { - t.objectStore('avatars').delete(id.toText()) - t.objectStore('metadata').delete(id.toText()) - } -} diff --git a/packages/mask/background/database/persona/consistency.ts b/packages/mask/background/database/persona/consistency.ts deleted file mode 100644 index 8a4b05b637f5..000000000000 --- a/packages/mask/background/database/persona/consistency.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { unreachable } from '@masknet/kit' -import { ProfileIdentifier, type PersonaIdentifier, ECKeyIdentifier } from '@masknet/shared-base' -import type { FullPersonaDBTransaction } from './type.js' - -type ReadwriteFullPersonaDBTransaction = FullPersonaDBTransaction<'readwrite'> - -/** @internal */ -export async function assertPersonaDBConsistency( - behavior: 'fix' | 'throw', - ...[checkRange, t]: Parameters -): Promise { - const diag: Diagnosis[] = [] - for await (const w of checkFullPersonaDBConsistency(checkRange, t)) { - diag.push(w) - } - if (diag.length) { - const warn = 'PersonaDB is in the inconsistency state' - console.warn(warn) - console.info(await t.objectStore('personas').getAll()) - console.info(await t.objectStore('profiles').getAll()) - console.error(...diag) - if (behavior === 'throw') { - t.abort() - throw new Error(warn) - } else if (t.mode === 'readwrite') { - console.warn('Try to fix the inconsistent db') - for (const each of diag) await fixDBInconsistency(each, t).catch(() => {}) - } - } - return diag -} -async function fixDBInconsistency(diagnosis: Diagnosis, t: ReadwriteFullPersonaDBTransaction) { - const personas = t.objectStore('personas') - const profiles = t.objectStore('profiles') - switch (diagnosis.type) { - case Type.Invalid_Persona: - return personas.delete(diagnosis.invalidPersonaKey) - case Type.Invalid_Profile: - return profiles.delete(diagnosis.invalidProfileKey) - case Type.One_Way_Link_In_Persona: - case Type.Invalid_Persona_LinkedProfiles: { - const rec = await personas.get(diagnosis.persona.toText()) - const profileWantToUnlink = - diagnosis.type === Type.One_Way_Link_In_Persona ? - diagnosis.designatedProfile.toText() - : diagnosis.invalidProfile - rec!.linkedProfiles.delete(profileWantToUnlink) - return personas.put(rec!) - } - case Type.One_Way_Link_In_Profile: - case Type.Invalid_Profile_LinkedPersona: { - const rec = await profiles.get(diagnosis.profile.toText()) - delete rec!.linkedPersona - return profiles.put(rec!) - } - default: - return unreachable(diagnosis) - } -} - -async function* checkFullPersonaDBConsistency( - checkRange: 'full check' | Map, - t: ReadwriteFullPersonaDBTransaction, -): AsyncGenerator { - for await (const persona of t.objectStore('personas')) { - const personaID = ECKeyIdentifier.from(persona.key) - if (personaID.isNone()) { - yield { type: Type.Invalid_Persona, invalidPersonaKey: persona.key, _record: persona.value } - continue - } - if (checkRange === 'full check' || checkRange.has(personaID.value)) { - yield* checkPersonaLink(personaID.value, t) - } - } - - for await (const profile of t.objectStore('profiles')) { - const profileID = ProfileIdentifier.from(profile.key) - if (profileID.isNone()) { - yield { type: Type.Invalid_Profile, invalidProfileKey: profile.key, _record: profile.value } - } else if (checkRange === 'full check' || checkRange.has(profileID.value)) { - yield* checkProfileLink(profileID.value, t) - } - } -} -async function* checkPersonaLink( - personaID: PersonaIdentifier, - t: ReadwriteFullPersonaDBTransaction, -): AsyncGenerator { - const rec = await t.objectStore('personas').get(personaID.toText()) - const linkedProfiles = rec?.linkedProfiles - if (!linkedProfiles) return - for (const each of linkedProfiles) { - const profileID = ProfileIdentifier.from(each[0]) - if (profileID.isNone()) { - yield { type: Type.Invalid_Persona_LinkedProfiles, invalidProfile: each[0], persona: personaID } - continue - } - const profile = await t.objectStore('profiles').get(profileID.value.toText()) - if (!profile?.linkedPersona) { - yield { - type: Type.One_Way_Link_In_Persona, - persona: personaID, - designatedProfile: profileID.value, - profileActuallyLinkedPersona: profile?.linkedPersona, - } - } - } -} -async function* checkProfileLink( - profile: ProfileIdentifier, - t: ReadwriteFullPersonaDBTransaction, -): AsyncGenerator { - const rec = await t.objectStore('profiles').get(profile.toText()) - const invalidLinkedPersona = rec?.linkedPersona - if (!invalidLinkedPersona) return - if (invalidLinkedPersona.type !== 'ec_key') { - yield { type: Type.Invalid_Profile_LinkedPersona, invalidLinkedPersona, profile } - return - } - const designatedPersona = new ECKeyIdentifier( - invalidLinkedPersona.curve, - invalidLinkedPersona.compressedPoint || invalidLinkedPersona.encodedCompressedKey!, - ) - const persona = await t.objectStore('personas').get(designatedPersona.toText()) - if (!persona?.linkedProfiles.has(profile.toText())) { - yield { type: Type.One_Way_Link_In_Profile, profile, designatedPersona } - } -} - -const enum Type { - Invalid_Persona = 'invalid identifier in persona', - Invalid_Persona_LinkedProfiles = 'invalid identifier in persona.linkedProfiles', - Invalid_Profile = 'invalid identifier in profile', - Invalid_Profile_LinkedPersona = 'invalid identifier in profile.linkedPersona', - One_Way_Link_In_Persona = 'a persona linked to a profile meanwhile the profile is not linked to the persona', - One_Way_Link_In_Profile = 'a profile linked to a persona meanwhile it is not appeared in the persona.linkedProfiles', -} -type Diagnosis = - | { - type: Type.Invalid_Persona - invalidPersonaKey: string - _record: unknown - } - | { - type: Type.Invalid_Persona_LinkedProfiles - persona: PersonaIdentifier - invalidProfile: string - } - | { - type: Type.Invalid_Profile - invalidProfileKey: string - _record: unknown - } - | { - type: Type.Invalid_Profile_LinkedPersona - profile: ProfileIdentifier - invalidLinkedPersona: unknown - } - | { - type: Type.One_Way_Link_In_Persona - persona: PersonaIdentifier - designatedProfile: ProfileIdentifier - profileActuallyLinkedPersona?: unknown - } - | { - type: Type.One_Way_Link_In_Profile - profile: ProfileIdentifier - designatedPersona: PersonaIdentifier - } diff --git a/packages/mask/background/database/persona/db.ts b/packages/mask/background/database/persona/db.ts deleted file mode 100644 index 8a5efb2f427a..000000000000 --- a/packages/mask/background/database/persona/db.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './type.js' -export * from './web.js' diff --git a/packages/mask/background/database/persona/helper.ts b/packages/mask/background/database/persona/helper.ts deleted file mode 100644 index 9da72db248fd..000000000000 --- a/packages/mask/background/database/persona/helper.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { safeUnreachable } from '@masknet/kit' -import { - type AESCryptoKey, - type AESJsonWebKey, - ECKeyIdentifier, - type EC_Private_JsonWebKey, - type EC_Public_CryptoKey, - type EC_Public_JsonWebKey, - type PersonaIdentifier, - ProfileIdentifier, -} from '@masknet/shared-base' -import { - attachProfileDB, - consistentPersonaDBWriteAccess, - createOrUpdatePersonaDB, - createPersonaDB, - createPersonaDBReadonlyAccess, - type FullPersonaDBTransaction, - type LinkedProfileDetails, - type PersonaRecord, - queryPersonaByProfileDB, - queryPersonasWithPrivateKey, - queryProfileDB, -} from './db.js' -import { noop } from 'lodash-es' - -// #region Local key helpers -/** - * If has local key of a profile in the database. - * @param id Profile Identifier - */ -export async function hasLocalKeyOf(id: ProfileIdentifier) { - let has = false - await createPersonaDBReadonlyAccess(async (tx) => { - const result = await getLocalKeyOf(id, tx) - has = !!result - }) - return has -} - -/** - * Try to decrypt data using local key. - * - * @param authorHint Author of the local key - * @param data Data to be decrypted - * @param iv IV - */ -export async function decryptByLocalKey( - authorHint: ProfileIdentifier | null, - data: Uint8Array, - iv: Uint8Array, -): Promise { - const candidateKeys: AESJsonWebKey[] = [] - - if (authorHint) { - await createPersonaDBReadonlyAccess(async (tx) => { - const key = await getLocalKeyOf(authorHint, tx) - key && candidateKeys.push(key) - }) - // TODO: We may push every local key we owned to the candidate list so we can also decrypt when authorHint is null, but that might be a performance pitfall when localKey field is not indexed. - } - - let check = noop - return Promise.any( - candidateKeys.map(async (key): Promise => { - const k = await crypto.subtle.importKey('jwk', key, { name: 'AES-GCM', length: 256 }, false, ['decrypt']) - check() - - const result = await crypto.subtle.decrypt({ iv, name: 'AES-GCM' }, k, data) - check = abort - return result - }), - ) -} - -export async function encryptByLocalKey(who: ProfileIdentifier, content: Uint8Array, iv: Uint8Array) { - let key: AESCryptoKey | undefined - await createPersonaDBReadonlyAccess(async (tx) => { - const jwk = await getLocalKeyOf(who, tx) - if (!jwk) return - const k = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM', length: 256 }, false, ['encrypt']) - key = k as AESCryptoKey - }) - if (!key) throw new Error('No local key found') - const result = await crypto.subtle.encrypt({ iv, name: 'AES-GCM' }, key, content) - return result as Uint8Array -} - -async function getLocalKeyOf(id: ProfileIdentifier, tx: FullPersonaDBTransaction<'readonly'>) { - const profile = await queryProfileDB(id, tx) - if (!profile) return - if (profile.localKey) return profile.localKey - if (!profile.linkedPersona) return - - const persona = await queryPersonaByProfileDB(id, tx) - return persona?.localKey -} -// #endregion - -// #region ECDH -export async function deriveAESByECDH(pub: EC_Public_CryptoKey, of?: ProfileIdentifier | PersonaIdentifier) { - // Note: the correct type should be EcKeyAlgorithm but it is missing in worker dts - const curve = (pub.algorithm as EcKeyImportParams).namedCurve || '' - const sameCurvePrivateKeys = new Map() - - await createPersonaDBReadonlyAccess(async (tx) => { - const personas = await queryPersonasWithPrivateKey(tx) - for (const persona of personas) { - if (!persona.privateKey) continue - if (persona.privateKey.crv !== curve) continue - if (of) { - if (of instanceof ProfileIdentifier) { - if (!persona.linkedProfiles.has(of)) continue - } else if (of instanceof ECKeyIdentifier) { - if (persona.identifier !== of) continue - } else safeUnreachable(of) - } - sameCurvePrivateKeys.set(persona.identifier, persona.privateKey) - } - }) - - const deriveResult = new Map() - const result = await Promise.allSettled( - [...sameCurvePrivateKeys].map(async ([id, key]) => { - const privateKey = await crypto.subtle.importKey( - 'jwk', - key, - { name: 'ECDH', namedCurve: key.crv! }, - false, - ['deriveKey'], - ) - const derived = await crypto.subtle.deriveKey( - { name: 'ECDH', public: pub }, - privateKey, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'], - ) - deriveResult.set(id, derived as AESCryptoKey) - }), - ) - const failed = result.filter((x) => x.status === 'rejected') - if (failed.length) { - console.warn('Failed to ECDH', ...failed.map((x) => x.reason)) - } - return deriveResult -} -// #endregion - -// #region normal functions -export async function createPersonaByJsonWebKey(options: { - publicKey: EC_Public_JsonWebKey - privateKey: EC_Private_JsonWebKey - localKey?: AESJsonWebKey - nickname?: string - mnemonic?: PersonaRecord['mnemonic'] - uninitialized?: boolean -}): Promise { - const identifier = (await ECKeyIdentifier.fromJsonWebKey(options.publicKey)).unwrap() - const record: PersonaRecord = { - createdAt: new Date(), - updatedAt: new Date(), - identifier, - linkedProfiles: new Map(), - publicKey: options.publicKey, - privateKey: options.privateKey, - nickname: options.nickname, - mnemonic: options.mnemonic, - localKey: options.localKey, - hasLogout: false, - uninitialized: options.uninitialized, - } - await consistentPersonaDBWriteAccess((t) => createPersonaDB(record, t)) - return identifier -} - -export async function createProfileWithPersona( - profileID: ProfileIdentifier, - data: LinkedProfileDetails, - keys: { - nickname?: string - publicKey: EC_Public_JsonWebKey - privateKey?: EC_Private_JsonWebKey - localKey?: AESJsonWebKey - mnemonic?: PersonaRecord['mnemonic'] - }, -): Promise { - const ec_id = (await ECKeyIdentifier.fromJsonWebKey(keys.publicKey)).unwrap() - const rec: PersonaRecord = { - createdAt: new Date(), - updatedAt: new Date(), - identifier: ec_id, - linkedProfiles: new Map(), - nickname: keys.nickname, - publicKey: keys.publicKey, - privateKey: keys.privateKey, - localKey: keys.localKey, - mnemonic: keys.mnemonic, - hasLogout: false, - } - await consistentPersonaDBWriteAccess(async (t) => { - await createOrUpdatePersonaDB(rec, { explicitUndefinedField: 'ignore', linkedProfiles: 'merge' }, t) - await attachProfileDB(profileID, ec_id, data, t) - }) -} -// #endregion - -export async function queryPublicKey(author: ProfileIdentifier | null) { - if (!author) return null - const persona = await queryPersonaByProfileDB(author) - if (!persona) return null - return (await crypto.subtle.importKey( - 'jwk', - persona.publicKey, - { name: 'ECDH', namedCurve: persona.publicKey.crv! }, - true, - ['deriveKey'], - )) as EC_Public_CryptoKey -} - -function abort() { - throw new Error('Cancelled') -} diff --git a/packages/mask/background/database/persona/type.ts b/packages/mask/background/database/persona/type.ts deleted file mode 100644 index 4dc7e2d6fa2f..000000000000 --- a/packages/mask/background/database/persona/type.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { IDBPSafeTransaction } from '../utils/openDB.js' -import type { DBSchema } from 'idb/with-async-ittr' -import type { - PersonaIdentifier, - AESJsonWebKey, - EC_Private_JsonWebKey, - EC_Public_JsonWebKey, - ProfileIdentifier, - RelationFavor, -} from '@masknet/shared-base' - -/** @internal */ -export type FullPersonaDBTransaction = IDBPSafeTransaction< - PersonaDB, - ['personas', 'profiles', 'relations'], - Mode -> - -/** @internal */ -export type ProfileTransaction = IDBPSafeTransaction< - PersonaDB, - ['profiles'], - Mode -> - -/** @internal */ -export type PersonasTransaction = IDBPSafeTransaction< - PersonaDB, - ['personas'], - Mode -> - -/** @internal */ -export type RelationTransaction = IDBPSafeTransaction< - PersonaDB, - ['relations'], - Mode -> - -// #region Type -/** @internal */ -export type PersonaRecordDB = Omit & { - identifier: string - linkedProfiles: Map - /** - * This field is used as index of the db. - */ - hasPrivateKey: 'no' | 'yes' -} - -/** @internal */ -export type ProfileRecordDB = Omit & { - identifier: string - network: string - linkedPersona?: PersonaIdentifierStoredInDB -} -/** @internal */ -type PersonaIdentifierStoredInDB = { - compressedPoint?: string - encodedCompressedKey?: string - type: 'ec_key' - curve: 'secp256k1' -} - -/** @internal */ -export type RelationRecordDB = Omit & { - network?: string - profile: string - linked: string -} - -/** @internal */ -export interface PersonaDB extends DBSchema { - /** Use inline keys */ - personas: { - value: PersonaRecordDB - key: string - indexes: { - hasPrivateKey: string - } - } - /** Use inline keys */ - profiles: { - value: ProfileRecordDB - key: string - indexes: { - // Use `network` field as index - network: string - } - } - /** Use inline keys **/ - relations: { - key: IDBValidKey[] - value: RelationRecordDB - indexes: { - 'linked, profile, favor': [string, string, number] - 'favor, profile, linked': [number, string, string] - } - } -} - -export interface RelationRecord { - profile: ProfileIdentifier | PersonaIdentifier - linked: PersonaIdentifier - network?: string - favor: RelationFavor -} - -/** @internal */ -export interface ProfileRecord { - identifier: ProfileIdentifier - nickname?: string - localKey?: AESJsonWebKey - linkedPersona?: PersonaIdentifier - createdAt: Date - updatedAt: Date -} - -export interface PersonaRecord { - identifier: PersonaIdentifier - /** The evm address of persona */ - address?: string - /** - * If this key is generated by the mnemonic word, this field should be set. - */ - mnemonic?: { - words: string - parameter: { - path: string - withPassword: boolean - } - } - publicKey: EC_Public_JsonWebKey - publicHexKey?: string - privateKey?: EC_Private_JsonWebKey - localKey?: AESJsonWebKey - nickname?: string - linkedProfiles: Map - createdAt: Date - updatedAt: Date - hasLogout?: boolean - /** - * create a dummy persona which should hide to the user until - * connected at least one website - */ - uninitialized?: boolean -} - -/** @internal */ -export interface LinkedProfileDetails { - connectionConfirmState: 'confirmed' | 'pending' -} - -/** @internal */ -export type PersonaRecordWithPrivateKey = PersonaRecord & Required> - -// #endregion diff --git a/packages/mask/background/database/persona/web.ts b/packages/mask/background/database/persona/web.ts deleted file mode 100644 index 939d7a08df8b..000000000000 --- a/packages/mask/background/database/persona/web.ts +++ /dev/null @@ -1,773 +0,0 @@ -import { isEmpty } from 'lodash-es' -import { openDB } from 'idb/with-async-ittr' -import { bufferToHex, privateToPublic, publicToAddress } from '@ethereumjs/util' -import { - type AESJsonWebKey, - convertIdentifierMapToRawMap, - convertRawMapToIdentifierMap, - ECKeyIdentifier, - type PersonaIdentifier, - ProfileIdentifier, - RelationFavor, - fromBase64URL, - MaskMessages, -} from '@masknet/shared-base' -import { CryptoKeyToJsonWebKey } from '../../../utils-pure/index.js' -import { createDBAccessWithAsyncUpgrade, createTransaction } from '../utils/openDB.js' -import { assertPersonaDBConsistency } from './consistency.js' -import type { - FullPersonaDBTransaction, - ProfileTransaction, - PersonasTransaction, - RelationTransaction, - PersonaRecordWithPrivateKey, - PersonaDB, - PersonaRecordDB, - ProfileRecord, - ProfileRecordDB, - LinkedProfileDetails, - RelationRecord, - RelationRecordDB, - PersonaRecord, -} from './type.js' - -/** - * Database structure: - * - * # ObjectStore `persona`: - * @description Store Personas. - * @type {PersonaRecordDB} - * @keys inline, {@link PersonaRecordDB.identifier} - * - * # ObjectStore `profiles`: - * @description Store profiles. - * @type {ProfileRecord} - * A persona links to 0 or more profiles. - * Each profile links to 0 or 1 persona. - * @keys inline, {@link ProfileRecord.identifier} - * - * # ObjectStore `relations`: - * @description Store relations. - * @type {RelationRecord} - * Save the relationship between persona and profile. - * @keys inline {@link RelationRecord.linked @link RelationRecord.profile} - */ - -const db = createDBAccessWithAsyncUpgrade( - 1, - 4, - (currentOpenVersion, knowledge) => { - return openDB('maskbook-persona', currentOpenVersion, { - upgrade(db, oldVersion, newVersion, transaction) { - function v0_v1() { - db.createObjectStore('personas', { keyPath: 'identifier' }) - db.createObjectStore('profiles', { keyPath: 'identifier' }) - transaction.objectStore('profiles').createIndex('network', 'network', { unique: false }) - transaction.objectStore('personas').createIndex('hasPrivateKey', 'hasPrivateKey', { unique: false }) - } - async function v1_v2() { - const persona = transaction.objectStore('personas') - const profile = transaction.objectStore('profiles') - await update(persona) - await update(profile) - async function update(q: typeof persona | typeof profile) { - for await (const rec of persona) { - if (!rec.value.localKey) continue - const jwk = knowledge?.data.get(rec.value.identifier) - if (!jwk) { - // !!! This should not happen - // !!! Remove it will implicitly drop user's localKey - delete rec.value.localKey - // !!! Keep it will leave a bug, broken data in the DB - // continue - // !!! DON'T throw cause it will break the database upgrade - } - rec.value.localKey = jwk - await rec.update(rec.value) - } - } - } - async function v2_v3() { - try { - db.createObjectStore('relations', { keyPath: ['linked', 'profile'] }) - transaction - .objectStore('relations') - .createIndex('linked, profile, favor', ['linked', 'profile', 'favor'], { unique: true }) - } catch {} - } - async function v3_v4() { - try { - transaction.objectStore('relations').deleteIndex('linked, profile, favor') - transaction - .objectStore('relations') - .createIndex('favor, profile, linked', ['favor', 'profile', 'linked'], { unique: true }) - const relation = transaction.objectStore('relations') - - await update(relation) - async function update(q: typeof relation) { - for await (const rec of relation) { - rec.value.favor = - rec.value.favor === RelationFavor.DEPRECATED ? - RelationFavor.UNCOLLECTED - : RelationFavor.COLLECTED - - await rec.update(rec.value) - } - } - } catch {} - } - if (oldVersion < 1) v0_v1() - if (oldVersion < 2) v1_v2() - if (oldVersion < 3) v2_v3() - if (oldVersion < 4) v3_v4() - }, - }) - }, - async (db) => { - if (db.version === 1) { - const map: V1To2 = { version: 2, data: new Map() } - const t = createTransaction(db, 'readonly')('personas', 'profiles') - const a = await t.objectStore('personas').getAll() - const b = await t.objectStore('profiles').getAll() - for (const rec of [...a, ...b]) { - if (!rec.localKey) continue - map.data.set(rec.identifier, (await CryptoKeyToJsonWebKey(rec.localKey as any)) as any) - } - return map - } - return undefined - }, - 'maskbook-persona', -) -type V1To2 = { - version: 2 - data: Map -} -type Knowledge = V1To2 - -/** @internal */ -export async function createRelationsTransaction() { - const database = await db() - return createTransaction(database, 'readwrite')('relations') -} - -/** @internal */ -export async function createPersonaDBReadonlyAccess( - action: (t: FullPersonaDBTransaction<'readonly'>) => Promise, -) { - const database = await db() - const transaction = createTransaction(database, 'readonly')('personas', 'profiles', 'relations') - await action(transaction) -} - -/** @internal */ -export async function consistentPersonaDBWriteAccess( - action: (t: FullPersonaDBTransaction<'readwrite'>) => Promise, - tryToAutoFix = true, -) { - // TODO: collect all changes on this transaction then only perform consistency check on those records. - const database = await db() - let t = createTransaction(database, 'readwrite')('profiles', 'personas', 'relations') - let finished = false - const finish = () => (finished = true) - t.addEventListener('abort', finish) - t.addEventListener('complete', finish) - t.addEventListener('error', finish) - - // Pause those events when patching write access - const resumePersona = MaskMessages.events.ownPersonaChanged.pause!() - const resumeRelation = MaskMessages.events.relationsChanged.pause!() - try { - await action(t) - } finally { - if (finished) { - console.warn('The transaction ends too early! There MUST be a bug in the program!') - console.trace() - // start a new transaction to check consistency - t = createTransaction(database, 'readwrite')('profiles', 'personas', 'relations') - } - try { - await assertPersonaDBConsistency(tryToAutoFix ? 'fix' : 'throw', 'full check', t) - resumePersona((data) => (data.length ? [undefined] : [])) - resumeRelation((data) => [data.flat()]) - } finally { - // If the consistency check throws, we drop all pending events - resumePersona(() => []) - resumeRelation(() => []) - } - } -} - -// #region Plain methods -/** @internal */ -export async function createPersonaDB(record: PersonaRecord, t: PersonasTransaction<'readwrite'>): Promise { - await t.objectStore('personas').add(personaRecordToDB(record)) - record.privateKey && MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} - -/** @internal */ -export async function queryPersonaByProfileDB( - query: ProfileIdentifier, - t?: FullPersonaDBTransaction<'readonly'>, -): Promise { - t = t || createTransaction(await db(), 'readonly')('personas', 'profiles', 'relations') - const x = await t.objectStore('profiles').get(query.toText()) - if (!x?.linkedPersona) return null - return queryPersonaDB( - new ECKeyIdentifier( - x.linkedPersona.curve, - x.linkedPersona.compressedPoint || x.linkedPersona.encodedCompressedKey!, - ), - t, - ) -} - -/** @internal */ -export async function queryPersonaDB( - query: PersonaIdentifier, - t?: PersonasTransaction<'readonly'>, - isIncludeLogout?: boolean, -): Promise { - t = t || createTransaction(await db(), 'readonly')('personas') - const x = await t.objectStore('personas').get(query.toText()) - if (x && (isIncludeLogout || !x.hasLogout)) return personaRecordOutDB(x) - return null -} - -export async function queryPersonasDB( - query?: { - identifiers?: PersonaIdentifier[] - hasPrivateKey?: boolean - nameContains?: string - initialized?: boolean - }, - t?: PersonasTransaction<'readonly'>, - isIncludeLogout?: boolean, -): Promise { - t = t || createTransaction(await db(), 'readonly')('personas') - const records: PersonaRecord[] = [] - for await (const each of t.objectStore('personas')) { - const out = personaRecordOutDB(each.value) - if ( - (query?.hasPrivateKey && !out.privateKey) || - (query?.nameContains && out.nickname !== query.nameContains) || - (query?.identifiers && !query.identifiers.some((x) => x === out.identifier)) || - (query?.initialized && out.uninitialized) - ) - continue - - if (isIncludeLogout || !out.hasLogout) records.push(out) - } - return records -} - -/** @internal */ -export async function queryPersonasWithPrivateKey( - t?: FullPersonaDBTransaction<'readonly'>, -): Promise { - t = t || createTransaction(await db(), 'readonly')('personas', 'profiles', 'relations') - const records: PersonaRecord[] = [] - records.push( - ...(await t.objectStore('personas').index('hasPrivateKey').getAll(IDBKeyRange.only('yes'))).map( - personaRecordOutDB, - ), - ) - return records as PersonaRecordWithPrivateKey[] -} - -/** - * Update an existing Persona record. - * @param nextRecord The partial record to be merged - * @param howToMerge How to merge linkedProfiles and `field: undefined` - * @param t transaction - * @internal - */ -export async function updatePersonaDB( - // Do a copy here. We need to delete keys from it. - { ...nextRecord }: Readonly & Pick>, - howToMerge: { - linkedProfiles: 'replace' | 'merge' - explicitUndefinedField: 'ignore' | 'delete field' - }, - t: PersonasTransaction<'readwrite'>, -): Promise { - const _old = await t.objectStore('personas').get(nextRecord.identifier.toText()) - if (!_old) throw new TypeError('Update a non-exist data') - const old = personaRecordOutDB(_old) - let nextLinkedProfiles = old.linkedProfiles - if (nextRecord.linkedProfiles) { - if (howToMerge.linkedProfiles === 'merge') - nextLinkedProfiles = new Map([...nextLinkedProfiles, ...nextRecord.linkedProfiles]) - else nextLinkedProfiles = nextRecord.linkedProfiles - } - if (howToMerge.explicitUndefinedField === 'ignore') { - const keys = Object.keys(nextRecord) as Array - for (const key of keys) { - if (nextRecord[key] === undefined) { - delete nextRecord[key] - } - } - } - const next: PersonaRecordDB = personaRecordToDB({ - ...old, - ...nextRecord, - linkedProfiles: nextLinkedProfiles, - updatedAt: nextRecord.updatedAt ?? new Date(), - }) - await t.objectStore('personas').put(next) - ;(next.privateKey || old.privateKey) && MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} - -/** @internal */ -export async function createOrUpdatePersonaDB( - record: Partial & Pick, - howToMerge: Parameters[1], - t: PersonasTransaction<'readwrite'>, -) { - const personaInDB = await t.objectStore('personas').get(record.identifier.toText()) - if (personaInDB) return updatePersonaDB(record, howToMerge, t) - else - return createPersonaDB( - { - ...record, - address: - record.privateKey?.d ? - bufferToHex(publicToAddress(privateToPublic(Buffer.from(fromBase64URL(record.privateKey.d))))) - : undefined, - createdAt: record.createdAt ?? new Date(), - updatedAt: record.updatedAt ?? new Date(), - linkedProfiles: record.linkedProfiles ?? new Map(), - }, - t, - ) -} - -/** @internal */ -export async function deletePersonaDB( - id: PersonaIdentifier, - confirm: 'delete even with private' | "don't delete if have private key", - t: PersonasTransaction<'readwrite'>, -): Promise { - const r = await t.objectStore('personas').get(id.toText()) - if (!r) return - if (confirm !== 'delete even with private' && r.privateKey) - throw new TypeError('Cannot delete a persona with a private key') - await t.objectStore('personas').delete(id.toText()) - r.privateKey && MaskMessages.events.ownPersonaChanged.sendToAll() -} -/** - * Delete a Persona - * @returns a boolean. true: the record no longer exists; false: the record is kept. - * @internal - */ -export async function safeDeletePersonaDB( - id: PersonaIdentifier, - t?: FullPersonaDBTransaction<'readwrite'>, -): Promise { - t = t || createTransaction(await db(), 'readwrite')('personas', 'profiles', 'relations') - const r = await queryPersonaDB(id, t) - if (!r) return true - if (r.linkedProfiles.size !== 0) return false - if (r.privateKey) return false - await deletePersonaDB(id, "don't delete if have private key", t) - return true -} - -/** @internal */ -export async function createProfileDB(record: ProfileRecord, t: ProfileTransaction<'readwrite'>): Promise { - await t.objectStore('profiles').add(profileToDB(record)) -} - -/** @internal */ -export async function queryProfileDB( - id: ProfileIdentifier, - t?: ProfileTransaction<'readonly'>, -): Promise { - t = t || createTransaction(await db(), 'readonly')('profiles') - const result = await t.objectStore('profiles').get(id.toText()) - if (result) return profileOutDB(result) - return null -} - -/** @internal */ -export async function queryProfilesDB( - query: { - network?: string - identifiers?: ProfileIdentifier[] - hasLinkedPersona?: boolean - }, - t?: ProfileTransaction<'readonly'>, -): Promise { - t = t || createTransaction(await db(), 'readonly')('profiles') - const result: ProfileRecord[] = [] - - if (isEmpty(query)) { - const results = await t.objectStore('profiles').getAll() - results.forEach((each) => { - const out = profileOutDB(each) - result.push(out) - }) - } - - if (query.network) { - const results = await t.objectStore('profiles').index('network').getAll(IDBKeyRange.only(query.network)) - - results.forEach((each) => { - const out = profileOutDB(each) - if (query.hasLinkedPersona && !out.linkedPersona) return - result.push(out) - }) - } else if (query.identifiers?.length) { - for await (const each of t.objectStore('profiles').iterate()) { - const out = profileOutDB(each.value) - if (query.hasLinkedPersona && !out.linkedPersona) continue - if (query.identifiers.some((x) => out.identifier === x)) result.push(out) - } - } else { - for await (const each of t.objectStore('profiles').iterate()) { - const out = profileOutDB(each.value) - if (query.hasLinkedPersona && !out.linkedPersona) continue - result.push(out) - } - } - - return result -} - -async function updateProfileDB( - updating: Partial & Pick, - t: FullPersonaDBTransaction<'readwrite'>, -): Promise { - const old = await t.objectStore('profiles').get(updating.identifier.toText()) - if (!old) throw new Error('Updating a non exists record') - const oldLinkedPersona = - old.linkedPersona ? - new ECKeyIdentifier( - old.linkedPersona.curve, - old.linkedPersona.compressedPoint || old.linkedPersona.encodedCompressedKey!, - ) - : undefined - - if (oldLinkedPersona && updating.linkedPersona && oldLinkedPersona !== updating.linkedPersona) { - const oldIdentifier = ProfileIdentifier.from(old.identifier).expect( - `old data in the profile database should be a valid ProfileIdentifier, but found ${old.identifier}`, - ) - const oldLinkedPersona = await queryPersonaByProfileDB(oldIdentifier, t) - - if (oldLinkedPersona) { - oldLinkedPersona.linkedProfiles.delete(oldIdentifier) - await updatePersonaDB( - oldLinkedPersona, - { - linkedProfiles: 'replace', - explicitUndefinedField: 'ignore', - }, - t, - ) - } - } - - if (updating.linkedPersona && oldLinkedPersona !== updating.linkedPersona) { - const linkedPersona = await queryPersonaDB(updating.linkedPersona, t) - if (linkedPersona) { - linkedPersona.linkedProfiles.set(updating.identifier, { connectionConfirmState: 'confirmed' }) - await updatePersonaDB( - linkedPersona, - { - linkedProfiles: 'replace', - explicitUndefinedField: 'ignore', - }, - t, - ) - } - } - - const nextRecord: ProfileRecordDB = profileToDB({ - ...profileOutDB(old), - ...updating, - }) - await t.objectStore('profiles').put(nextRecord) -} - -/** @internal */ -export async function createOrUpdateProfileDB(rec: ProfileRecord, t: FullPersonaDBTransaction<'readwrite'>) { - if (await queryProfileDB(rec.identifier, t)) return updateProfileDB(rec, t) - else return createProfileDB(rec, t) -} - -/** @internal */ -export async function detachProfileDB( - identifier: ProfileIdentifier, - t?: FullPersonaDBTransaction<'readwrite'>, -): Promise { - t = t || createTransaction(await db(), 'readwrite')('personas', 'profiles', 'relations') - const profile = await queryProfileDB(identifier, t) - if (!profile?.linkedPersona) return - - const linkedPersona = profile.linkedPersona - const persona = await queryPersonaDB(linkedPersona, t) - persona?.linkedProfiles.delete(identifier) - - if (persona) { - await updatePersonaDB(persona, { linkedProfiles: 'replace', explicitUndefinedField: 'delete field' }, t) - if (persona.privateKey) MaskMessages.events.ownPersonaChanged.sendToAll(undefined) - } - profile.linkedPersona = undefined - await updateProfileDB(profile, t) -} - -/** @internal */ -export async function attachProfileDB( - identifier: ProfileIdentifier, - attachTo: PersonaIdentifier, - data: LinkedProfileDetails, - t?: FullPersonaDBTransaction<'readwrite'>, -): Promise { - t = t || createTransaction(await db(), 'readwrite')('personas', 'profiles', 'relations') - const profile = - (await queryProfileDB(identifier, t)) || - (await createProfileDB({ identifier, createdAt: new Date(), updatedAt: new Date() }, t)) || - (await queryProfileDB(identifier, t)) - const persona = await queryPersonaDB(attachTo, t) - if (!persona || !profile) return - - if (profile.linkedPersona !== undefined && profile.linkedPersona !== attachTo) { - await detachProfileDB(identifier, t) - } - - profile.linkedPersona = attachTo - persona.linkedProfiles.set(identifier, data) - - await updatePersonaDB(persona, { linkedProfiles: 'merge', explicitUndefinedField: 'ignore' }, t) - await updateProfileDB(profile, t) - - if (persona.privateKey) MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} - -/** @internal */ -export async function deleteProfileDB(id: ProfileIdentifier, t: ProfileTransaction<'readwrite'>): Promise { - await t.objectStore('profiles').delete(id.toText()) -} - -/** @internal */ -export async function createRelationDB( - record: Omit, - t: RelationTransaction<'readwrite'>, - silent = false, -): Promise { - await t.objectStore('relations').add(relationRecordToDB(record)) - if (!silent) - MaskMessages.events.relationsChanged.sendToAll([{ of: record.profile, reason: 'update', favor: record.favor }]) -} - -/** @internal */ -export async function queryRelations(query: (record: RelationRecord) => boolean, t?: RelationTransaction<'readonly'>) { - t = t || createTransaction(await db(), 'readonly')('relations') - const records: RelationRecord[] = [] - - for await (const each of t.objectStore('relations')) { - const out = relationRecordOutDB(each.value) - if (query(out)) records.push(out) - } - - return records -} - -/** @internal */ -export async function queryRelationsPagedDB( - linked: PersonaIdentifier, - options: { - network: string - after?: RelationRecord - pageOffset?: number - }, - count: number, -) { - const t = createTransaction(await db(), 'readonly')('relations') - let firstRecord = true - - const data: RelationRecord[] = [] - - for await (const cursor of t.objectStore('relations').index('favor, profile, linked').iterate()) { - if (cursor.value.linked !== linked.toText()) continue - if (options.network !== 'all' && cursor.value.network !== options.network) continue - - if (firstRecord && options.after && options.after.profile.toText() !== cursor.value.profile) { - cursor.continue([options.after.favor, options.after.profile.toText(), options.after.linked.toText()]) - firstRecord = false - continue - } - - firstRecord = false - - // after this record - if ( - options.after?.linked.toText() === cursor.value.linked && - options.after.profile.toText() === cursor.value.profile - ) - continue - - if (count <= 0) break - const outData = relationRecordOutDB(cursor.value) - count -= 1 - data.push(outData) - } - return data -} - -/** @internal */ -export async function updateRelationDB( - updating: Omit, - t: RelationTransaction<'readwrite'>, - silent = false, -): Promise { - const old = await t - .objectStore('relations') - .get(IDBKeyRange.only([updating.linked.toText(), updating.profile.toText()])) - - if (!old) throw new Error('Updating a non exists record') - - const nextRecord: RelationRecordDB = relationRecordToDB({ - ...relationRecordOutDB(old), - ...updating, - }) - - await t.objectStore('relations').put(nextRecord) - if (!silent) { - MaskMessages.events.relationsChanged.sendToAll([ - { of: updating.profile, favor: updating.favor, reason: 'update' }, - ]) - } -} - -/** @internal */ -export async function deletePersonaRelationDB( - persona: PersonaIdentifier, - linkedPersona: PersonaIdentifier, - t: RelationTransaction<'readwrite'>, - silent = false, -): Promise { - const old = await t.objectStore('relations').get(IDBKeyRange.only([linkedPersona.toText(), persona.toText()])) - if (!old) return - await t.objectStore('relations').delete(IDBKeyRange.only([linkedPersona.toText(), persona.toText()])) - if (!silent) MaskMessages.events.relationsChanged.sendToAll([{ of: persona, reason: 'delete', favor: old.favor }]) -} - -/** @internal */ -export async function createOrUpdateRelationDB( - record: Omit, - t: RelationTransaction<'readwrite'>, - silent = false, -) { - const old = await t - .objectStore('relations') - .get(IDBKeyRange.only([record.linked.toText(), record.profile.toText()])) - - if (old) { - await updateRelationDB(record, t, silent) - } else { - await createRelationDB(record, t, silent) - } -} - -// #endregion - -// #region out db & to db -function profileToDB(x: ProfileRecord): ProfileRecordDB { - return { - ...x, - identifier: x.identifier.toText(), - network: x.identifier.network, - linkedPersona: - x.linkedPersona ? - { curve: x.linkedPersona.curve, type: 'ec_key', compressedPoint: x.linkedPersona.rawPublicKey } - : undefined, - } -} -function profileOutDB({ network, ...x }: ProfileRecordDB): ProfileRecord { - if (x.linkedPersona) { - if (x.linkedPersona.type !== 'ec_key') throw new Error('Unknown type of linkedPersona') - } - return { - ...x, - identifier: ProfileIdentifier.from(x.identifier).expect( - `data stored in the profile database should be a valid ProfileIdentifier, but found ${x.identifier}`, - ), - linkedPersona: - x.linkedPersona ? - new ECKeyIdentifier( - x.linkedPersona.curve, - x.linkedPersona.compressedPoint || x.linkedPersona.encodedCompressedKey!, - ) - : undefined, - } -} -function personaRecordToDB(x: PersonaRecord): PersonaRecordDB { - return { - ...x, - identifier: x.identifier.toText(), - hasPrivateKey: x.privateKey ? 'yes' : 'no', - linkedProfiles: convertIdentifierMapToRawMap(x.linkedProfiles), - } -} -function personaRecordOutDB(x: PersonaRecordDB): PersonaRecord { - Reflect.deleteProperty(x, 'hasPrivateKey' as keyof typeof x) - const identifier = ECKeyIdentifier.from(x.identifier).expect( - `data stored in the profile database should be a valid ECKeyIdentifier, but found ${x.identifier}`, - ) - - const obj: PersonaRecord = { - ...x, - address: - x.privateKey?.d ? - bufferToHex(publicToAddress(privateToPublic(Buffer.from(fromBase64URL(x.privateKey.d))))) - : undefined, - identifier, - publicHexKey: identifier.publicKeyAsHex, - linkedProfiles: convertRawMapToIdentifierMap(x.linkedProfiles, ProfileIdentifier), - } - return obj -} - -function relationRecordToDB(x: Omit): RelationRecordDB { - if (x.profile instanceof ProfileIdentifier) { - return { - ...x, - network: x.profile.network, - profile: x.profile.toText(), - linked: x.linked.toText(), - } - } else { - return { - ...x, - profile: x.profile.toText(), - linked: x.linked.toText(), - } - } -} - -function relationRecordOutDB(x: RelationRecordDB): RelationRecord { - if (x.profile.startsWith('person:')) { - return { - ...x, - profile: ProfileIdentifier.from(x.profile).expect( - `data stored in the profile database should be a valid ProfileIdentifier, but found ${x.profile}`, - ), - linked: ECKeyIdentifier.from(x.linked).expect( - `data stored in the profile database should be a valid ECKeyIdentifier, but found ${x.linked}`, - ), - } - } else { - return { - ...x, - profile: ECKeyIdentifier.from(x.profile).expect( - `data stored in the profile database should be a valid ECKeyIdentifier, but found ${x.profile}`, - ), - linked: ECKeyIdentifier.from(x.linked).expect( - `data stored in the profile database should be a valid ECKeyIdentifier, but found ${x.linked}`, - ), - } - } -} - -// #endregion diff --git a/packages/mask/background/database/plugin-db/base.ts b/packages/mask/background/database/plugin-db/base.ts deleted file mode 100644 index fccba6d611e0..000000000000 --- a/packages/mask/background/database/plugin-db/base.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { openDB, type DBSchema } from 'idb/with-async-ittr' -import { createDBAccess } from '../utils/openDB.js' - -type InStore = { - plugin_id: string - value: unknown -} - -/** @internal */ -export interface PluginDatabase extends DBSchema { - PluginStore: { - value: InStore - indexes: { - type: [string, string] - } - key: string - } -} - -const db = createDBAccess(() => { - return openDB('maskbook-plugin-data', 2, { - async upgrade(db, oldVersion, newVersion, transaction) { - if (oldVersion < 1) db.createObjectStore('PluginStore') - if (oldVersion < 2) { - const data = await transaction.objectStore('PluginStore').getAll() - db.deleteObjectStore('PluginStore') - const os = db.createObjectStore('PluginStore', { keyPath: ['plugin_id', 'value.type', 'value.id'] }) - - // a compound index by "rec.plugin_id" + "rec.value.type" - os.createIndex('type', ['plugin_id', 'value.type']) - for (const each of data) { - if (!each.plugin_id) continue - if (!pluginDataHasValidKeyPath(each.value)) continue - Reflect.deleteProperty(each, 'type') - Reflect.deleteProperty(each, 'record_id') - await os.add(each) - } - } - }, - }) -}) -// cause key path error in "add" will cause transaction fail, we need to check them first -/** @internal */ -export function pluginDataHasValidKeyPath(value: unknown): value is InStore { - try { - if (typeof value !== 'object' || value === null) return false - const id = Reflect.get(value, 'id') - const type = Reflect.get(value, 'type') - if (typeof id !== 'string' && typeof id !== 'number') return false - if (typeof type !== 'string' && typeof type !== 'number') return false - return true - } catch { - return false - } -} -export const createPluginDBAccess = db -export function toStore(plugin_id: string, value: unknown): InStore { - return { plugin_id, value } -} diff --git a/packages/mask/background/database/plugin-db/index.ts b/packages/mask/background/database/plugin-db/index.ts deleted file mode 100644 index 8a4e9beb2116..000000000000 --- a/packages/mask/background/database/plugin-db/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './base.js' -export * from './wrap-plugin-database.js' diff --git a/packages/mask/background/database/plugin-db/wrap-plugin-database.ts b/packages/mask/background/database/plugin-db/wrap-plugin-database.ts deleted file mode 100644 index 63089803f238..000000000000 --- a/packages/mask/background/database/plugin-db/wrap-plugin-database.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { IDBPTransaction } from 'idb/with-async-ittr' -import type { Plugin, IndexableTaggedUnion } from '@masknet/plugin-infra' -import { createPluginDBAccess, type PluginDatabase, pluginDataHasValidKeyPath, toStore } from './base.js' - -/** - * Avoid calling it directly. - * - * You should get the instance from WorkerContext when the plugin is initialized. - * - * ```ts - * let storage: Plugin.Worker.Storage | null = null - * const worker: Plugin.Worker.Definition = { - * ...base, - * init(signal, context) { - * storage = context.getDatabaseStorage() - * // get it here, instance of calling this function directly. - * }, - * } - * ``` - */ -export function createPluginDatabase( - plugin_id: string, - signal?: AbortSignal, -): Plugin.Worker.DatabaseStorage { - let livingTransaction: IDBPTransaction | undefined = undefined - let ended = false - signal?.addEventListener('abort', () => { - // give some extra time after the plugin shutdown to store data. - // this setTimeout is ok because it only last 1.5 seconds. - // eslint-disable-next-line no-restricted-globals - setTimeout(() => (ended = true), 1500) - }) - function key(data: IndexableTaggedUnion) { - return IDBKeyRange.only([plugin_id, data.type, data.id]) - } - function ensureAlive() { - if (ended) throw new Error(`[@masknet/plugin-infra] Storage instance for ${plugin_id} has expired.`) - } - return { - async get(type, id) { - const t = await c('r') - const data = await t.store.get(key({ type, id })) - if (!data) return undefined - return data.value as any - }, - async has(type, id) { - const t = await c('r') - const count = await t.store.count(key({ type, id })) - return count > 0 - }, - async add(data) { - const t = await c('rw') - if (!pluginDataHasValidKeyPath(data)) throw new TypeError("Data doesn't have a valid key path") - if (await t.store.get(key(data))) await t.store.put(toStore(plugin_id, data)) - else await t.store.add(toStore(plugin_id, data)) - t.commit() - }, - async remove(type, id) { - const t = await c('rw') - await t.store.delete(key({ type, id })) - t.commit() - }, - async *iterate(type) { - const db = await c('r') - const cursor = await db - .objectStore('PluginStore') - .index('type') - .openCursor(IDBKeyRange.only([plugin_id, type])) - if (!cursor) return - for await (const each of cursor) { - const roCursor: Plugin.Worker.StorageReadonlyCursor = { - value: each.value.value as any, - } - yield roCursor - } - }, - async *iterate_mutate(type) { - const cursor = await ( - await c('rw') - ) - .objectStore('PluginStore') - .index('type') - .openCursor(IDBKeyRange.only([plugin_id, type])) - if (!cursor) return - for await (const each of cursor) { - const rwCursor: Plugin.Worker.StorageMutableCursor = { - value: each.value.value as any, - delete: () => each.delete(), - update: async (data) => { - await each.update(toStore(plugin_id, data)) - }, - } - yield rwCursor - } - }, - } - async function c(usage: 'r' | 'rw'): Promise> { - ensureAlive() - if (usage === 'rw' && (livingTransaction as any)?.mode === 'readonly') invalidateTransaction() - try { - await livingTransaction?.store.openCursor() - } catch { - invalidateTransaction() - } - if (livingTransaction === undefined) { - const db = await createPluginDBAccess() - const tx = db.transaction('PluginStore', usage === 'r' ? 'readonly' : 'readwrite') as any - livingTransaction = tx - // Oops, workaround for https://bugs.webkit.org/show_bug.cgi?id=216769 or https://github.com/jakearchibald/idb/issues/201 - try { - await tx.store.openCursor() - } catch { - livingTransaction = db.transaction('PluginStore', usage === 'r' ? 'readonly' : 'readwrite') as any - return livingTransaction as any - } - return tx - } - return livingTransaction - } - function invalidateTransaction() { - livingTransaction = undefined - } -} diff --git a/packages/mask/background/database/post/dbType.ts b/packages/mask/background/database/post/dbType.ts deleted file mode 100644 index 890481c1d85a..000000000000 --- a/packages/mask/background/database/post/dbType.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { AESJsonWebKey } from '@masknet/shared-base' - -// This file records the history version of the database. NEVER change old type. -/** @internal */ -export declare namespace PostDB_HistoryTypes { - export interface Version2PostRecord { - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - recipients?: Array<{ userId: string; network: string }> - foundAt: Date - postCryptoKey?: CryptoKey - } - - // #region Version 3 - export interface Version3PostRecord { - // Inherited from Version2PostRecord - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - foundAt: Date - postCryptoKey?: CryptoKey - // Type of "recipients" has changed. - recipients: Record - } - export type Version3_RecipientReason = ( - | { type: 'auto-share' } - | { type: 'direct' } - | { type: 'group'; /** @deprecated */ group: unknown } - ) & { at: Date } - export type Version3RecipientDetail = { - /** Why they're able to receive this message? */ - reason: Version3_RecipientReason[] - } - // #endregion - - export interface Version4PostRecord { - // Inherited from Version3PostRecord - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - foundAt: Date - postCryptoKey?: CryptoKey - // Type of "recipients" has changed from Record to Map. - recipients: Map - } - - export interface Version5PostRecord { - // Inherited from Version4PostRecord - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - foundAt: Date - // postCryptoKey is changed from native CryptoKey object to AESJsonWebKey. - postCryptoKey?: AESJsonWebKey - // recipients is changed to allow share public (represented by true). - recipients: true | Map - // New properties - encryptBy?: { - compressedPoint?: string - encodedCompressedKey?: string - type: 'ec_key' - curve: 'secp256k1' - } - url?: string - summary?: string - interestedMeta?: ReadonlyMap - } - - export interface LatestPostDBRecord { - // Inherited from Version5PostRecord - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - foundAt: Date - postCryptoKey?: AESJsonWebKey - recipients: true | Map - url?: string - summary?: string - interestedMeta?: ReadonlyMap - - // encryptBy is changed from an object to a string. - encryptBy?: string - } -} - -// When you want to make a breaking change on the following type, copy it to the history type above. -// The following type should marked as @internal, so they're not going to exposed in the public API. - -/** @internal */ -export type LatestRecipientReasonDB = ( - | { type: 'auto-share' } - | { type: 'direct' } - | { type: 'group'; /** @deprecated */ group: unknown } -) & { - /** - * When we send the key to them by this reason? - * If the unix timestamp of this Date is 0, - * should display it as "unknown" or "before Nov 2019" - */ - at: Date -} -/** @internal */ -export interface LatestRecipientDetailDB { - /** Why they're able to receive this message? */ - reason: LatestRecipientReasonDB[] -} -/** @internal */ -export interface LatestPostDBRecord { - // Inherited from Version5PostRecord - postBy: { userId: string; network: string } | undefined - identifier: string - recipientGroups?: unknown - foundAt: Date - postCryptoKey?: AESJsonWebKey - recipients: true | Map - url?: string - summary?: string - interestedMeta?: ReadonlyMap - - // encryptBy is changed from an object to a string. - encryptBy?: string -} diff --git a/packages/mask/background/database/post/helper.ts b/packages/mask/background/database/post/helper.ts deleted file mode 100644 index 88c119d78343..000000000000 --- a/packages/mask/background/database/post/helper.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AESCryptoKey, PostIVIdentifier } from '@masknet/shared-base' -import { CryptoKeyToJsonWebKey } from '../../../utils-pure/index.js' -import { withPostDBTransaction, queryPostDB, createPostDB, updatePostDB, type PostRecord } from './index.js' - -export async function savePostKeyToDB( - id: PostIVIdentifier, - key: AESCryptoKey, - extraInfo: Omit, -): Promise { - const jwk = await CryptoKeyToJsonWebKey(key) - await withPostDBTransaction(async (t) => { - const post = await queryPostDB(id, t) - if (!post) { - await createPostDB( - { - identifier: id, - postCryptoKey: jwk, - foundAt: new Date(), - ...extraInfo, - }, - t, - ) - } else { - await updatePostDB({ ...post, postCryptoKey: jwk }, 'override', t) - } - }) -} diff --git a/packages/mask/background/database/post/index.ts b/packages/mask/background/database/post/index.ts deleted file mode 100644 index 8a5efb2f427a..000000000000 --- a/packages/mask/background/database/post/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './type.js' -export * from './web.js' diff --git a/packages/mask/background/database/post/type.ts b/packages/mask/background/database/post/type.ts deleted file mode 100644 index 74977a10e7e6..000000000000 --- a/packages/mask/background/database/post/type.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { AESJsonWebKey, PersonaIdentifier, PostIVIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import type { DBSchema } from 'idb/with-async-ittr' -import type { IDBPSafeTransaction } from '../utils/openDB.js' -import type { LatestPostDBRecord } from './dbType.js' - -/** - * @internal - * This type represented the deserialized data type of LatestPostDBRecord. - * - * This type should not be exposed in the API too. They should use PostInformation exported from @masknet/shared-base package. - */ -export interface PostRecord { - postBy: ProfileIdentifier | undefined - identifier: PostIVIdentifier - postCryptoKey?: AESJsonWebKey - /** Receivers */ - recipients: 'everyone' | Map - /** @deprecated */ - recipientGroups?: unknown - /** - * When does Mask find this post. - * For your own post, it is when Mask created this post. - * For others post, it is when you see it first time. - */ - foundAt: Date - encryptBy?: PersonaIdentifier - /** The URL of this post */ - url?: string - /** Summary of this post (maybe front 20 chars). */ - summary?: string - /** Interested metadata contained in this post. */ - interestedMeta?: ReadonlyMap -} - -export interface PostDB extends DBSchema { - /** Use inline keys */ - post: { - value: LatestPostDBRecord - key: string - indexes: { - 'persona, date': [string, Date] - } - } -} - -export type PostReadOnlyTransaction = IDBPSafeTransaction -export type PostReadWriteTransaction = IDBPSafeTransaction diff --git a/packages/mask/background/database/post/web.ts b/packages/mask/background/database/post/web.ts deleted file mode 100644 index b75282c1e08a..000000000000 --- a/packages/mask/background/database/post/web.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { - type AESCryptoKey, - type AESJsonWebKey, - ECKeyIdentifier, - PostIdentifier, - PostIVIdentifier, - ProfileIdentifier, -} from '@masknet/shared-base' -import { openDB } from 'idb/with-async-ittr' -import { CryptoKeyToJsonWebKey } from '../../../utils-pure/index.js' -import { createDBAccessWithAsyncUpgrade, createTransaction } from '../utils/openDB.js' -import type { PostRecord, PostDB, PostReadOnlyTransaction, PostReadWriteTransaction } from './type.js' -import type { PostDB_HistoryTypes, LatestPostDBRecord, LatestRecipientDetailDB } from './dbType.js' - -type UpgradeKnowledge = - | { - version: 4 - data: Map - } - | undefined -const db = createDBAccessWithAsyncUpgrade( - 4, - 7, - (currentTryOpen, knowledge) => - openDB('maskbook-post-v2', currentTryOpen, { - async upgrade(db, oldVersion, _newVersion, transaction): Promise { - if (oldVersion < 1) { - // inline keys - return void db.createObjectStore('post', { keyPath: 'identifier' }) - } - /** - * In the version 1 we use PostIdentifier to store post that identified by post iv - * After upgrade to version 2, we use PostIVIdentifier to store it. - * So we transform all old data into new data. - */ - if (oldVersion <= 1) { - const store = transaction.objectStore('post') - const old = await store.getAll() - await store.clear() - for (const each of old) { - const id = PostIdentifier.from(each.identifier) - if (id.isSome()) { - const { postId, identifier } = id.value - each.identifier = new PostIVIdentifier(identifier.network, postId).toText() - await store.add(each) - } - } - } - - /** - * In the version 2 we use `recipients?: ProfileIdentifier[]` - * After upgrade to version 3, we use `recipients: Record` - */ - if (oldVersion <= 2) { - const store = transaction.objectStore('post') - for await (const cursor of store) { - const v2record: PostDB_HistoryTypes.Version2PostRecord = cursor.value as any - const oldType = v2record.recipients - ?.map((x) => ProfileIdentifier.of(x.network, x.userId).unwrapOr(null!)) - .filter(Boolean) - const newType: PostDB_HistoryTypes.Version3PostRecord['recipients'] = {} - if (oldType !== undefined) - for (const each of oldType) { - newType[each.toText()] = { reason: [{ type: 'direct', at: new Date(0) }] } - } - const next: PostDB_HistoryTypes.Version3PostRecord = { - ...v2record, - recipients: newType, - postBy: undefined, - foundAt: new Date(0), - recipientGroups: [], - } - await cursor.update(next satisfies PostDB_HistoryTypes.Version3PostRecord as any) - } - } - - /** - * In the version 3 we use `recipients?: Record` - * After upgrade to version 4, we use `recipients: Map` - */ - if (oldVersion <= 3) { - const store = transaction.objectStore('post') - for await (const cursor of store) { - const v3Record: PostDB_HistoryTypes.Version3PostRecord = cursor.value as any - const newType: PostDB_HistoryTypes.Version4PostRecord['recipients'] = new Map() - for (const [key, value] of Object.entries(v3Record.recipients)) { - newType.set(key, value) - } - const v4Record: PostDB_HistoryTypes.Version4PostRecord = { - ...v3Record, - recipients: newType, - } - await cursor.update(v4Record as any) - } - } - /** - * In version 4 we use CryptoKey, in version 5 we use JsonWebKey - */ - if (oldVersion <= 4) { - const store = transaction.objectStore('post') - for await (const cursor of store) { - const v4Record: PostDB_HistoryTypes.Version4PostRecord = cursor.value as any - const data = knowledge?.data - if (!data) { - await cursor.delete() - continue - } - if (!v4Record.postCryptoKey) continue - const v5Record: PostDB_HistoryTypes.Version5PostRecord = { - ...v4Record, - postCryptoKey: data.get(v4Record.identifier), - } - if (!v5Record.postCryptoKey) delete v5Record.postCryptoKey - await cursor.update(v5Record as any) - } - } - - // version 6 ships a wrong db migration. - // therefore need to upgrade again to fix it. - if (oldVersion <= 6) { - const store = transaction.objectStore('post') - for await (const cursor of store) { - const v5Record: PostDB_HistoryTypes.Version5PostRecord = cursor.value as any - const by = v5Record.encryptBy - // This is the correct data type - if (typeof by === 'string') continue - if (!by) continue - cursor.value.encryptBy = new ECKeyIdentifier( - by.curve, - by.compressedPoint || by.encodedCompressedKey!, - ).toText() - cursor.update(cursor.value) - } - if (!store.indexNames.contains('persona, date')) - store.createIndex('persona, date', ['encryptBy', 'foundAt'], { unique: false }) - } - }, - }), - async (db): Promise => { - if (db.version === 4) { - const map = new Map() - const knowledge: UpgradeKnowledge = { version: 4, data: map } - const records = await createTransaction(db, 'readonly')('post').objectStore('post').getAll() - for (const r of records) { - const x = r.postCryptoKey - if (!x) continue - try { - const key = await CryptoKeyToJsonWebKey(x as any as AESCryptoKey) - map.set(r.identifier, key) - } catch { - continue - } - } - return knowledge - } - return undefined - }, - 'maskbook-post-v2', -) - -const PostDBAccess = db - -/** @internal */ -export async function withPostDBTransaction(task: (t: PostReadWriteTransaction) => Promise) { - const t = createTransaction(await PostDBAccess(), 'readwrite')('post') - await task(t) -} - -/** @internal */ -export async function createPostDB(record: PostRecord, t?: PostReadWriteTransaction): Promise { - t ||= createTransaction(await db(), 'readwrite')('post') - const toSave = postToDB(record) - await t.objectStore('post').add(toSave) -} - -/** @internal */ -export async function updatePostDB( - updateRecord: Partial & Pick, - mode: 'append' | 'override', - t?: PostReadWriteTransaction, -): Promise { - t ||= createTransaction(await db(), 'readwrite')('post') - const emptyRecord: PostRecord = { - identifier: updateRecord.identifier, - recipients: new Map(), - postBy: undefined, - foundAt: new Date(), - } - const currentRecord = (await queryPostDB(updateRecord.identifier, t)) || emptyRecord - const nextRecord: PostRecord = { ...currentRecord, ...updateRecord } - const nextRecipients: LatestPostDBRecord['recipients'] = - mode === 'override' ? postToDB(nextRecord).recipients : postToDB(currentRecord).recipients - if (mode === 'append') { - if (updateRecord.recipients) { - if (typeof updateRecord.recipients === 'object' && typeof nextRecipients === 'object') { - for (const [id, date] of updateRecord.recipients) { - nextRecipients.set(id.toText(), { reason: [{ at: date, type: 'direct' }] }) - } - } else { - nextRecord.recipients = 'everyone' - } - } - } - const nextRecordInDBType = postToDB(nextRecord) - await t.objectStore('post').put(nextRecordInDBType) -} - -/** @internal */ -export async function queryPostDB(record: PostIVIdentifier, t?: PostReadOnlyTransaction): Promise { - t ||= createTransaction(await db(), 'readonly')('post') - const result = await t.objectStore('post').get(record.toText()) - if (result) return postOutDB(result) - return null -} - -/** @internal */ -export async function queryPostsDB( - query: string | ((data: PostRecord, id: PostIVIdentifier) => boolean), - t?: PostReadOnlyTransaction, -): Promise { - t ||= createTransaction(await db(), 'readonly')('post') - const selected: PostRecord[] = [] - for await (const { value } of t.objectStore('post')) { - const idResult = PostIVIdentifier.from(value.identifier) - if (idResult.isNone()) { - console.warn('Invalid identifier', value.identifier) - continue - } - const id = idResult.value - if (typeof query === 'string') { - if (id.network === query) selected.push(postOutDB(value)) - } else { - const v = postOutDB(value) - if (query(v, id)) selected.push(v) - } - } - return selected -} - -function postOutDB(db: LatestPostDBRecord): PostRecord { - const { identifier, foundAt, postBy, postCryptoKey, encryptBy, interestedMeta, summary, url } = db - let recipients: PostRecord['recipients'] - if (db.recipients === true) { - recipients = 'everyone' - } else { - recipients = new Map() - for (const [id, { reason }] of db.recipients) { - const identifier = ProfileIdentifier.from(id) - if (identifier.isNone()) continue - const detail = reason[0] - if (!detail) continue - recipients.set(identifier.value, detail.at) - } - } - return { - identifier: PostIVIdentifier.from(identifier).expect( - `data stored in the post database should be a valid PostIVIdentifier, but found ${identifier}`, - ), - postBy: ProfileIdentifier.of(postBy?.network, postBy?.userId).unwrapOr(undefined), - recipients, - foundAt, - postCryptoKey, - encryptBy: ECKeyIdentifier.from(encryptBy).unwrapOr(undefined), - interestedMeta, - summary, - url, - } -} -function postToDB(out: PostRecord): LatestPostDBRecord { - let recipients: LatestPostDBRecord['recipients'] - if (out.recipients === 'everyone') { - recipients = true - } else { - const map = new Map() - for (const [id, detail] of out.recipients) { - map.set(id.toText(), { reason: [{ at: detail, type: 'direct' }] }) - } - recipients = map - } - return { - ...out, - identifier: out.identifier.toText(), - encryptBy: out.encryptBy?.toText(), - recipients, - } -} diff --git a/packages/mask/background/database/utils/openDB.ts b/packages/mask/background/database/utils/openDB.ts deleted file mode 100644 index 90859a70b41b..000000000000 --- a/packages/mask/background/database/utils/openDB.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { - IDBPDatabase, - DBSchema, - StoreNames, - IDBPTransaction, - IDBPObjectStore, - TypedDOMStringList, - IDBPCursorWithValueIteratorValue, - StoreKey, - IndexNames, - IDBPIndex, - IDBPCursorWithValue, - IDBPCursor, -} from 'idb/with-async-ittr' -import { assertEnvironment, Environment } from '@dimensiondev/holoflows-kit' - -export function createDBAccess(opener: () => Promise>) { - let db: IDBPDatabase | undefined = undefined - function clean() { - if (db) { - db.close() - db.addEventListener('close', () => (db = undefined), { once: true }) - } - db = undefined - } - return async () => { - assertEnvironment(Environment.ManifestBackground) - if (db) { - try { - // try if the db still open - const t = db.transaction([db.objectStoreNames[0]], 'readonly', {}) - t.commit() - return db - } catch { - clean() - } - } - db = await opener() - db.addEventListener('close', clean, { once: true }) - db.addEventListener('error', clean, { once: true }) - return db - } -} -export function createDBAccessWithAsyncUpgrade( - firstVersionThatRequiresAsyncUpgrade: number, - latestVersion: number, - opener: (currentTryOpenVersion: number, knowledge?: AsyncUpgradePreparedData) => Promise>, - asyncUpgradePrepare: (db: IDBPDatabase) => Promise, - dbName: string, -) { - let db: IDBPDatabase | undefined = undefined - - let pendingOpen: Promise> | undefined - async function open(): Promise> { - assertEnvironment(Environment.ManifestBackground) - if (db?.version === latestVersion) return db - let currentVersion = firstVersionThatRequiresAsyncUpgrade - let lastVersionData: AsyncUpgradePreparedData | undefined = undefined - while (currentVersion < latestVersion) { - try { - db = await opener(currentVersion, lastVersionData) - // if the open success, the stored version is small or eq than currentTryOpenVersion - // let's call the prepare function to do all the async jobs - lastVersionData = await asyncUpgradePrepare(db) - } catch (error) { - if (currentVersion >= latestVersion) throw error - // if the stored database version is bigger than the currentTryOpenVersion - // It will fail and we just move to next version - } - currentVersion += 1 - db?.close() - db = undefined - } - db = await opener(currentVersion, lastVersionData) - db.addEventListener('close', (e) => (db = undefined), { once: true }) - if (!db) throw new Error('Invalid state') - return db - } - return async () => { - if (indexedDB.databases) { - const oldDBs = await indexedDB.databases() - const hasNoOldVersion = !oldDBs.some((db) => db.name === dbName) - const hasSameLatestVersion = oldDBs.some((db) => db.name === dbName && db.version === latestVersion) - if (hasNoOldVersion || hasSameLatestVersion) { - return opener(latestVersion) - } - } - - // Share a Promise to prevent async upgrade for multiple times - if (pendingOpen) return pendingOpen - const promise = (pendingOpen = open()) - promise.catch(() => (pendingOpen = undefined)) - return promise - } -} -interface IDBPSafeObjectStore< - DBTypes extends DBSchema, - TxStores extends Array> = Array>, - StoreName extends StoreNames = StoreNames, - Writable extends boolean = boolean, -> extends Pick< - IDBPObjectStore, - 'get' | 'getAll' | 'getAllKeys' | 'getKey' | 'count' | 'autoIncrement' | 'indexNames' | 'keyPath' | 'name' - > { - add: Writable extends true ? IDBPObjectStore['add'] : unknown - clear: Writable extends true ? IDBPObjectStore['clear'] : unknown - delete: Writable extends true ? IDBPObjectStore['delete'] : unknown - put: Writable extends true ? IDBPObjectStore['put'] : unknown - - index>( - name: IndexName, - ): IDBPIndex - openCursor( - query?: StoreKey | IDBKeyRange | null, - direction?: IDBCursorDirection, - ): Promise | null> - - openKeyCursor( - query?: StoreKey | IDBKeyRange | null, - direction?: IDBCursorDirection, - ): Promise | null> - - [Symbol.asyncIterator](): AsyncIterableIterator< - IDBPCursorWithValueIteratorValue< - DBTypes, - TxStores, - StoreName, - unknown, - Writable extends true ? 'readwrite' : 'readonly' - > - > - iterate( - query?: StoreKey | IDBKeyRange | null, - direction?: IDBCursorDirection, - ): AsyncIterableIterator< - IDBPCursorWithValueIteratorValue< - DBTypes, - TxStores, - StoreName, - unknown, - Writable extends true ? 'readwrite' : 'readonly' - > - > -} -export type IDBPSafeTransaction< - DBTypes extends DBSchema, - TxStores extends Array>, - Mode extends IDBTransactionMode = 'readonly', -> = Omit, 'mode' | 'objectStoreNames' | 'objectStore' | 'store'> & { - readonly objectStoreNames: TypedDOMStringList & string> - readonly mode: IDBTransactionMode - readonly __writable__?: Mode extends 'readwrite' ? true : boolean - readonly __stores__?: Record< - TxStores extends ReadonlyArray ? - ValueOfUsedStoreName extends string | number | symbol ? - ValueOfUsedStoreName - : never - : never, - never - > - objectStore( - name: StoreName, - ): IDBPSafeObjectStore -} - -export function createTransaction( - db: IDBPDatabase, - mode: Mode, -) { - // It must be a high order function to infer the type of UsedStoreName correctly. - return > = []>(...storeNames: UsedStoreName) => { - return db.transaction(storeNames, mode) as IDBPSafeTransaction - } -} diff --git a/packages/mask/background/env.d.ts b/packages/mask/background/env.d.ts deleted file mode 100644 index 9f694a23efec..000000000000 --- a/packages/mask/background/env.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// -/// -/// diff --git a/packages/mask/background/initialization/async-setup.ts b/packages/mask/background/initialization/async-setup.ts deleted file mode 100644 index 27e3b9bc8b6e..000000000000 --- a/packages/mask/background/initialization/async-setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { polyfill } from '@masknet/secp256k1-webcrypto' -import { setTelemetryID } from '../services/helper/telemetry-id.js' -import { setupBuildInfo } from '@masknet/flags/build-info' - -polyfill() -setTelemetryID(false) -setupBuildInfo() diff --git a/packages/mask/background/initialization/entry.ts b/packages/mask/background/initialization/entry.ts deleted file mode 100644 index 1524029243b8..000000000000 --- a/packages/mask/background/initialization/entry.ts +++ /dev/null @@ -1,5 +0,0 @@ -// The following file MUST be sync, otherwise it will miss the init event. -import /* webpackSync: true */ '../tasks/NotCancellable/OnInstall.js' - -import './async-setup.js' -import './post-async-setup.js' diff --git a/packages/mask/background/initialization/fetch.ts b/packages/mask/background/initialization/fetch.ts deleted file mode 100644 index 53fb6581bb39..000000000000 --- a/packages/mask/background/initialization/fetch.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { fetchGlobal } from '@masknet/web3-providers/helpers' -globalThis.fetch = fetchGlobal diff --git a/packages/mask/background/initialization/kv-storage.ts b/packages/mask/background/initialization/kv-storage.ts deleted file mode 100644 index 6ea48ccbdfbc..000000000000 --- a/packages/mask/background/initialization/kv-storage.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createIndexedDB_KVStorageBackend, createInMemoryKVStorageBackend, MaskMessages } from '@masknet/shared-base' - -export const indexedDB_KVStorageBackend = createIndexedDB_KVStorageBackend('mask-kv', (k, v) => - MaskMessages.events.__kv_backend_persistent__.sendByBroadcast([k, v]), -) -export const inMemory_KVStorageBackend = createInMemoryKVStorageBackend((k, v) => - MaskMessages.events.__kv_backend_in_memory__.sendByBroadcast([k, v]), -) diff --git a/packages/mask/background/initialization/mv2-entry.ts b/packages/mask/background/initialization/mv2-entry.ts deleted file mode 100644 index bff21fb25d62..000000000000 --- a/packages/mask/background/initialization/mv2-entry.ts +++ /dev/null @@ -1 +0,0 @@ -import './entry.js' diff --git a/packages/mask/background/initialization/mv3-entry.ts b/packages/mask/background/initialization/mv3-entry.ts deleted file mode 100644 index 916bc12f6f29..000000000000 --- a/packages/mask/background/initialization/mv3-entry.ts +++ /dev/null @@ -1,2 +0,0 @@ -import './entry.js' -Error.stackTraceLimit = Number.POSITIVE_INFINITY diff --git a/packages/mask/background/initialization/post-async-setup.ts b/packages/mask/background/initialization/post-async-setup.ts deleted file mode 100644 index 5e837999fd37..000000000000 --- a/packages/mask/background/initialization/post-async-setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -import './storage-setup.js' -import './fetch.js' -import { startServices } from '../services/setup.js' -import '../tasks/setup.js' // Setup Tasks - -startServices() diff --git a/packages/mask/background/initialization/setup.ts b/packages/mask/background/initialization/setup.ts deleted file mode 100644 index 38a38ad75455..000000000000 --- a/packages/mask/background/initialization/setup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import '../tasks/NotCancellable/OnInstall.js' - -import './async-setup.js' -import './storage-setup.js' -import './fetch.js' - -import '../tasks/setup.js' -import { startServices } from '../services/setup.js' -startServices() diff --git a/packages/mask/background/initialization/storage-setup.ts b/packages/mask/background/initialization/storage-setup.ts deleted file mode 100644 index 2b43dbaf0140..000000000000 --- a/packages/mask/background/initialization/storage-setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { setupLegacySettingsAtBackground, setupMaskKVStorageBackend } from '@masknet/shared-base' -import { inMemory_KVStorageBackend, indexedDB_KVStorageBackend } from './kv-storage.js' - -import { __deprecated__getStorage, __deprecated__setStorage } from '../utils/deprecated-storage.js' - -setupMaskKVStorageBackend(indexedDB_KVStorageBackend, inMemory_KVStorageBackend) -setupLegacySettingsAtBackground(__deprecated__getStorage, __deprecated__setStorage) diff --git a/packages/mask/background/network/queryPostKey.ts b/packages/mask/background/network/queryPostKey.ts deleted file mode 100644 index ef3ee97cefe0..000000000000 --- a/packages/mask/background/network/queryPostKey.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { decodeArrayBuffer, encodeArrayBuffer, isNonNull, unreachable } from '@masknet/kit' -import { - type DecryptStaticECDH_PostKey, - type DecryptEphemeralECDH_PostKey, - type EncryptionResultE2EMap, - EncryptPayloadNetwork, - EC_KeyCurve, - importEC_Key, - getEcKeyCurve, -} from '@masknet/encryption' -import type { EC_Public_CryptoKey, EC_Public_JsonWebKey } from '@masknet/shared-base' -import { CryptoKeyToJsonWebKey } from '../../utils-pure/index.js' -import * as gun_utils from /* webpackDefer: true */ '@masknet/gun-utils' -import { EventIterator } from 'event-iterator' -import { isObject, noop, uniq } from 'lodash-es' - -// !!! Change how this file access Gun will break the compatibility of v40 payload decryption. -export async function GUN_queryPostKey_version40( - iv: Uint8Array, - whoAmI: string, -): Promise { - // PATH ON GUN: maskbook > posts > iv > userID - const result = await gun_utils.getGunData('maskbook', 'posts', encodeArrayBuffer(iv), whoAmI) - if (!isValidData(result)) return null - return { - encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), - postKeyIV: new Uint8Array(decodeArrayBuffer(result.salt)), - } - - type DataOnGun = { - encryptedKey: string - salt: string - } - function isValidData(x: typeof result): x is DataOnGun { - if (typeof x !== 'object') return false - if (!x) return false - - const { encryptedKey, salt: encryptedKeyIV } = x - if (typeof encryptedKey !== 'string' || typeof encryptedKeyIV !== 'string') return false - return true - } -} - -namespace Version38Or39 { - export async function* GUN_queryPostKey_version39Or38( - version: -38 | -39, - iv: Uint8Array, - minePublicKey: EC_Public_CryptoKey, - network: EncryptPayloadNetwork, - abortSignal: AbortSignal, - ): AsyncGenerator { - const minePublicKeyJWK = await CryptoKeyToJsonWebKey(minePublicKey) - const { keyHash, postHash } = await calculatePostKeyPartition(version, network, iv, minePublicKeyJWK) - - /* cspell:disable-next-line */ - // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] - console.log( - `[@masknet/encryption] Reading key partition [${postHash[0]}][${keyHash}] and [${postHash[1]}][${keyHash}]`, - ) - const internalNodeNames = uniq( - ( - await Promise.all([ - // - gun_utils.getGunData(postHash[0], keyHash), - gun_utils.getGunData(postHash[1], keyHash), - ]) - ) - .filter(isNonNull) - .filter(isObject) - .map(Object.keys) - .flat() - .filter((x) => x !== '_'), - ) - // ? In this step we get all keys in this category (gun2[postHash][keyHash]) - const resultPromise = internalNodeNames.map((key) => gun_utils.getGunData(key)) - - const iter = new EventIterator((queue) => { - // immediate results - for (const result of resultPromise) result.then(emit, noop) - // future results - Promise.all([ - main(gun_utils.subscribeGunMapData([postHash[1]], isValidData, abortSignal)), - main(gun_utils.subscribeGunMapData([postHash[0]], isValidData, abortSignal)), - ]).then(() => queue.stop()) - - async function main(keyProvider: AsyncGenerator) { - for await (const data of keyProvider) Promise.resolve(data).then(emit, noop) - } - function emit(result: unknown) { - if (abortSignal.aborted) return - if (!isValidData(result)) return - queue.push({ - encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), - postKeyIV: new Uint8Array(decodeArrayBuffer(result.salt)), - }) - } - }) - yield* iter - } - - /** - * Publish post keys on the gun - * @param version current payload - * @param postIV Post iv - * @param receiversKeys Keys needs to publish - */ - export async function publishPostAESKey_version39Or38( - version: -39 | -38, - postIV: Uint8Array, - network: EncryptPayloadNetwork, - receiversKeys: EncryptionResultE2EMap, - ) { - const [postHash] = await hashIV(network, postIV) - if (version === -39) throw new Error('unreachable') - for (const result of receiversKeys.values()) { - try { - if (result.status === 'rejected') continue - const { encryptedPostKey, target, ivToBePublished } = result.value - if (!ivToBePublished) throw new Error('Missing salt') - const jwk = await CryptoKeyToJsonWebKey(target.key) - const keyHash = await hashKey38(jwk) - const post: DataOnGun = { - encryptedKey: encodeArrayBuffer(encryptedPostKey), - salt: encodeArrayBuffer(ivToBePublished), - } - console.log(`gun[${postHash}][${keyHash}].push(`, post, ')') - gun_utils.pushToGunDataArray([postHash, keyHash], post) - } catch (error) { - console.error('[@masknet/encryption] An error occurs when sending E2E keys', error) - } - } - } - - type DataOnGun = { - encryptedKey: string - salt: string - } - - function isValidData(data: unknown): data is DataOnGun { - if (!data) return false - if (typeof data !== 'object') return false - const { encryptedKey, salt } = data as DataOnGun - if (typeof encryptedKey !== 'string') return false - if (typeof salt !== 'string') return false - return true - } - - async function calculatePostKeyPartition( - version: -38 | -39, - network: EncryptPayloadNetwork, - iv: Uint8Array, - key: EC_Public_JsonWebKey, - ) { - const postHash = await hashIV(network, iv) - // In version > -39, we will use stable hash to prevent unstable result for key hashing - - const keyHash = version === -39 ? await hashKey39(key) : await hashKey38(key) - return { postHash, keyHash } - } - - async function hashIV(network: EncryptPayloadNetwork, iv: Uint8Array): Promise<[string, string]> { - const hashPair = '9283464d-ee4e-4e8d-a7f3-cf392a88133f' - const N = 2 - - const hash = (await GUN_SEA_work(encodeArrayBuffer(iv), hashPair)).slice(0, N) - const networkHint = getNetworkHint(network) - return [`${networkHint}${hash}`, `${networkHint}-${hash}`] - } - - function getNetworkHint(x: EncryptPayloadNetwork) { - if (x === EncryptPayloadNetwork.Facebook) return '' - if (x === EncryptPayloadNetwork.Twitter) return 'twitter-' - if (x === EncryptPayloadNetwork.Minds) return 'minds-' - if (x === EncryptPayloadNetwork.Instagram) return 'instagram-' - if (x === EncryptPayloadNetwork.Unknown) - throw new TypeError('[@masknet/encryption] Current network is not correctly configured.') - unreachable(x) - } - - // The difference between V38 and V39 is: V39 is not stable (JSON.stringify) - // it's an implementation bug but for backward compatibility, it cannot be changed. - // Therefore we upgraded the version and use a stable hash. - async function hashKey39(key: EC_Public_JsonWebKey) { - const hashPair = '10198a2f-205f-45a6-9987-3488c80113d0' - const N = 2 - - const jwk = JSON.stringify(key) - const hash = await GUN_SEA_work(jwk, hashPair) - return hash.slice(0, N) - } - - async function hashKey38(jwk: EC_Public_JsonWebKey) { - const hashPair = '10198a2f-205f-45a6-9987-3488c80113d0' - const N = 2 - - const hash = await GUN_SEA_work(jwk.x! + jwk.y!, hashPair) - return hash.slice(0, N) - } -} - -// This is a self contained Gun.SEA.work implementation that only contains code path we used. -async function GUN_SEA_work(data: Uint8Array | string, salt: Uint8Array | string) { - if (typeof data === 'string') data = new TextEncoder().encode(data) - if (typeof salt === 'string') salt = new TextEncoder().encode(salt) - const key = await crypto.subtle.importKey('raw', data, { name: 'PBKDF2' }, false, ['deriveBits']) - const params: Pbkdf2Params = { name: 'PBKDF2', iterations: 100000, salt, hash: { name: 'SHA-256' } } - const derived = await crypto.subtle.deriveBits(params, key, 512) - return btoa(String.fromCharCode(...new Uint8Array(derived))) -} - -namespace Version37 { - export async function* GUN_queryPostKey_version37( - iv: Uint8Array, - minePublicKey: EC_Public_CryptoKey, - network: EncryptPayloadNetwork, - abortSignal: AbortSignal, - ): AsyncGenerator { - const minePublicKeyJWK = await CryptoKeyToJsonWebKey(minePublicKey) - const { keyHash, postHash, networkHint } = await calculatePostKeyPartition(network, iv, minePublicKeyJWK) - - /* cspell:disable-next-line */ - // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] - const keyPartition = `${networkHint}-${postHash}-${keyHash}` - console.log(`[@masknet/encryption] Reading key partition [${keyPartition}]`) - const internalNodeNames = await gun_utils.getGunData(keyPartition).then((x) => { - if (!x) return [] - if (typeof x !== 'object') return [] - return Object.keys(x) - }) - // ? In this step we get all keys in this category (gun2[keyPartition]) - const resultPromise = internalNodeNames.map((key) => gun_utils.getGunData(key)) - - const iter = new EventIterator((queue) => { - // immediate results - for (const result of resultPromise) result.then(emit, noop) - - // future results - main(gun_utils.subscribeGunMapData([keyPartition], isValidData, abortSignal)) - - async function main(keyProvider: AsyncGenerator) { - for await (const data of keyProvider) Promise.resolve(data).then(emit, noop) - queue.stop() - } - async function emit(result: unknown) { - if (abortSignal.aborted) return - if (!isValidData(result)) return - - const data: DecryptEphemeralECDH_PostKey = { - encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.e)), - } - if (result.k && result.c) { - data.ephemeralPublicKey = ( - await importEC_Key(new Uint8Array(decodeArrayBuffer(result.k)), result.c) - ).unwrap() - } - queue.push(data) - } - }) - yield* iter - } - - /** - * Publish post keys on the gun - * @param postIV Post iv - * @param receiversKeys Keys needs to publish - */ - export async function publishPostAESKey_version37( - postIV: Uint8Array, - network: EncryptPayloadNetwork, - receiversKeys: EncryptionResultE2EMap, - ) { - const networkPartition = getNetworkPartition(network) - const postHash = await hashIV(postIV) - for (const result of receiversKeys.values()) { - try { - if (result.status === 'rejected') continue - const { encryptedPostKey, target, ephemeralPublicKey } = result.value - const jwk = await CryptoKeyToJsonWebKey(target.key) - const keyPartition = `${networkPartition}-${postHash}-${await hashKey(jwk)}` - const post: DataOnGun = { - e: encodeArrayBuffer(encryptedPostKey), - } - if (ephemeralPublicKey) { - post.c = getEcKeyCurve(ephemeralPublicKey) - post.k = encodeArrayBuffer(new Uint8Array(await crypto.subtle.exportKey('raw', ephemeralPublicKey))) - } - console.log(`[@masknet/encryption] gun[${keyPartition}].push(`, post, ')') - gun_utils.pushToGunDataArray([keyPartition], post) - } catch (error) { - console.error('[@masknet/encryption] An error occurs when sending E2E keys', error) - } - } - } - - // we need to make it short, but looks like gun does not support storing Uint8Array? - type DataOnGun = { - /** encrypted key */ - e: string - /** ephemeral public key chain */ - c?: EC_KeyCurve - /** ephemeral public key */ - k?: string - } - - function isValidData(data: unknown): data is DataOnGun { - if (!data) return false - if (typeof data !== 'object') return false - const { e, c, k } = data as DataOnGun - if (typeof e !== 'string') return false - if (![EC_KeyCurve.secp256k1, EC_KeyCurve.secp256p1, undefined].includes(c)) return false - if (typeof k !== 'string' && k !== undefined) return false - return true - } - - async function calculatePostKeyPartition( - network: EncryptPayloadNetwork, - iv: Uint8Array, - key: EC_Public_JsonWebKey, - ) { - const postHash = await hashIV(iv) - const keyHash = await hashKey(key) - return { postHash, keyHash, networkHint: getNetworkPartition(network) } - } - - async function hashIV(iv: Uint8Array): Promise { - const hashPair = '9283464d-ee4e-4e8d-a7f3-cf392a88133f' - const N = 2 - - return (await GUN_SEA_work(encodeArrayBuffer(iv), hashPair)).slice(0, N) - } - - async function hashKey(jwk: EC_Public_JsonWebKey) { - const hashPair = 'ace7ab0c-5507-4bdd-9d43-e4249a48e720' - const N = 2 - - const hash = await GUN_SEA_work(jwk.x! + jwk.y!, hashPair) - return hash.slice(0, N) - } - - function getNetworkPartition(x: EncryptPayloadNetwork) { - if (x === EncryptPayloadNetwork.Facebook) return '37-fb' - if (x === EncryptPayloadNetwork.Twitter) return '37-tw' - if (x === EncryptPayloadNetwork.Minds) return '37-minds' - if (x === EncryptPayloadNetwork.Instagram) return '37-ins' - if (x === EncryptPayloadNetwork.Unknown) - throw new TypeError('[@masknet/encryption] Current network is not correctly configured.') - unreachable(x) - } -} - -export const { GUN_queryPostKey_version39Or38, publishPostAESKey_version39Or38 } = Version38Or39 -export const { GUN_queryPostKey_version37, publishPostAESKey_version37 } = Version37 diff --git a/packages/mask/background/services/__utils__/convert.ts b/packages/mask/background/services/__utils__/convert.ts deleted file mode 100644 index 851da6667488..000000000000 --- a/packages/mask/background/services/__utils__/convert.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { PersonaInformation, ProfileInformation } from '@masknet/shared-base' -import { noop } from 'lodash-es' -import { queryAvatarsDataURL } from '../../database/avatar-cache/avatar.js' -import { - type FullPersonaDBTransaction, - type PersonaRecord, - type ProfileRecord, - queryProfilesDB, -} from '../../database/persona/db.js' - -/** @internal */ -export function toProfileInformation(profiles: ProfileRecord[]) { - return { - mustNotAwaitThisWithInATransaction: (async () => { - const result: ProfileInformation[] = [] - for (const profile of profiles) { - result.push({ - identifier: profile.identifier, - nickname: profile.nickname, - linkedPersona: profile.linkedPersona, - createAt: profile.createdAt, - }) - } - - const avatars = await queryAvatarsDataURL(result.map((x) => x.identifier)) - result.forEach((x) => avatars.has(x.identifier) && (x.avatar = avatars.get(x.identifier))) - return result - })(), - } -} - -/** @internal */ -export function toPersonaInformation(personas: PersonaRecord[], t: FullPersonaDBTransaction<'readonly'>) { - const personaInfo: PersonaInformation[] = [] - const dbQueryPass2: Array> = [] - const dbQuery: Array> = personas.map(async (persona) => { - const map: ProfileInformation[] = [] - personaInfo.push({ - nickname: persona.nickname, - identifier: persona.identifier, - address: persona.address, - linkedProfiles: map, - }) - - if (persona.linkedProfiles.size) { - const profiles = await queryProfilesDB({ identifiers: [...persona.linkedProfiles.keys()] }, t) - // we must not await toProfileInformation cause it is tx of another db. - dbQueryPass2.push( - toProfileInformation(profiles).mustNotAwaitThisWithInATransaction.then((x) => void map.push(...x)), - ) - } - }) - dbQueryPass2.push( - queryAvatarsDataURL(personas.map((x) => x.identifier)) - .then((avatars) => { - for (const [id, avatar] of avatars) { - const info = personaInfo.find((x) => x.identifier === id) - if (info) info.avatar = avatar - } - }) - .catch(noop), - ) - - return { - // we have to split two arrays for them and await them one by one, otherwise it will be race condition - mustNotAwaitThisWithInATransaction: Promise.all(dbQuery) - .then(() => Promise.all(dbQueryPass2)) - .then(() => personaInfo), - } -} diff --git a/packages/mask/background/services/backup/create.ts b/packages/mask/background/services/backup/create.ts deleted file mode 100644 index de7931f66e61..000000000000 --- a/packages/mask/background/services/backup/create.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { type BackupSummary, generateBackupRAW, getBackupSummary } from '@masknet/backup-format' -import { createNewBackup } from './internal_create.js' -import { env } from '@masknet/flags' - -export async function generateBackupPreviewInfo(): Promise { - // can we avoid create a full backup? - const backup = await createNewBackup({ allProfile: true, maskVersion: env.VERSION }) - return getBackupSummary(backup) -} - -export interface BackupOptions { - excludeWallet?: boolean - /** Includes persona, relations, posts and profiles. */ - excludeBase?: boolean -} -export async function createBackupFile(options: BackupOptions): Promise<{ - file: unknown - personaNickNames: string[] -}> { - const { excludeBase, excludeWallet } = options - const backup = await createNewBackup({ - noPersonas: excludeBase, - noPosts: excludeBase, - noProfiles: excludeBase, - noWallets: excludeWallet, - maskVersion: env.VERSION, - }) - const file = generateBackupRAW(backup) - const personaNickNames = [...backup.personas.values()].map((p) => p.nickname.unwrapOr('')).filter(Boolean) - return { file, personaNickNames } -} diff --git a/packages/mask/background/services/backup/index.ts b/packages/mask/background/services/backup/index.ts deleted file mode 100644 index f76aa6aed5b6..000000000000 --- a/packages/mask/background/services/backup/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { generateBackupPreviewInfo, createBackupFile, type BackupOptions } from './create.js' -export { generateBackupSummary, restoreBackup } from './restore.js' -export { backupPersonaPrivateKey } from './persona.js' diff --git a/packages/mask/background/services/backup/internal_create.ts b/packages/mask/background/services/backup/internal_create.ts deleted file mode 100644 index 61b7535ae184..000000000000 --- a/packages/mask/background/services/backup/internal_create.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { None, Some } from 'ts-results-es' -import { timeout } from '@masknet/kit' -import type { PersonaIdentifier } from '@masknet/shared-base' -import { activatedPluginsWorker, type Plugin } from '@masknet/plugin-infra/background-worker' -import { createEmptyNormalizedBackup, type NormalizedBackup } from '@masknet/backup-format' -import { queryPersonasDB, queryProfilesDB, queryRelations } from '../../database/persona/db.js' -import { queryPostsDB } from '../../database/post/index.js' -import { internal_wallet_backup } from './internal_wallet_backup.js' - -/** @internal */ -interface InternalBackupOptions { - hasPrivateKeyOnly?: boolean - noPosts?: boolean - noWallets?: boolean - noPersonas?: boolean - noProfiles?: boolean - onlyForPersona?: PersonaIdentifier - allProfile?: boolean - maskVersion?: string -} -/** - * @internal - * DO NOT expose this function as a service. - */ -// TODO: use a single readonly transaction in this operation. -export async function createNewBackup(options: InternalBackupOptions): Promise { - const { noPersonas, noPosts, noProfiles, noWallets, onlyForPersona, allProfile } = options - const file = createEmptyNormalizedBackup() - const { meta, personas, posts, profiles, relations, settings } = file - - meta.version = 2 - meta.maskVersion = Some(options.maskVersion || '>=2.21.0') - meta.createdAt = Some(new Date()) - - settings.grantedHostPermissions = (await browser.permissions.getAll()).origins || [] - - await Promise.allSettled([ - noPersonas || backupPersonas(onlyForPersona ? [onlyForPersona] : undefined), - noProfiles || backupProfiles(onlyForPersona, allProfile), - (noPersonas && noProfiles) || backupAllRelations(), - noPosts || backupPosts(), - noWallets || internal_wallet_backup().then((w) => (file.wallets = w)), - backupPlugins().then((p) => (file.plugins = p)), - ]) - - return file - - async function backupPersonas(of?: PersonaIdentifier[]) { - const data = await queryPersonasDB( - { - initialized: true, - hasPrivateKey: options.hasPrivateKeyOnly, - identifiers: of, - }, - undefined, - true, - ) - for (const persona of data) { - personas.set(persona.identifier, { - identifier: persona.identifier, - nickname: persona.nickname ? Some(persona.nickname) : None, - publicKey: persona.publicKey, - privateKey: persona.privateKey ? Some(persona.privateKey) : None, - localKey: persona.localKey ? Some(persona.localKey) : None, - createdAt: persona.createdAt ? Some(persona.createdAt) : None, - updatedAt: persona.updatedAt ? Some(persona.updatedAt) : None, - mnemonic: - persona.mnemonic ? - Some({ - hasPassword: persona.mnemonic.parameter.withPassword, - path: persona.mnemonic.parameter.path, - words: persona.mnemonic.words, - }) - : None, - linkedProfiles: persona.linkedProfiles, - address: persona.address ? Some(persona.address) : None, - }) - } - } - - async function backupPosts() { - const data = await queryPostsDB(() => true) - for (const post of data) { - let recipients: NormalizedBackup.PostReceiverE2E | NormalizedBackup.PostReceiverPublic = { - type: 'public', - } - if (post.recipients !== 'everyone') { - recipients = { - type: 'e2e', - receivers: new Map(), - } - for (const [recipient, date] of post.recipients) { - recipients.receivers.set(recipient, [{ at: date, type: 'direct' }]) - } - } - posts.set(post.identifier, { - identifier: post.identifier, - foundAt: post.foundAt, - interestedMeta: post.interestedMeta || new Map(), - postBy: post.postBy ? Some(post.postBy) : None, - encryptBy: post.encryptBy ? Some(post.encryptBy) : None, - postCryptoKey: post.postCryptoKey ? Some(post.postCryptoKey) : None, - summary: post.summary ? Some(post.summary) : None, - url: post.url ? Some(post.url) : None, - recipients: Some(recipients), - }) - } - } - - async function backupProfiles(of?: PersonaIdentifier, all = false) { - const data = await queryProfilesDB({ - hasLinkedPersona: !all, - }) - for (const profile of data) { - if (of) { - if (!profile.linkedPersona) continue - if (profile.linkedPersona !== of) continue - } - profiles.set(profile.identifier, { - identifier: profile.identifier, - nickname: profile.nickname ? Some(profile.nickname) : None, - localKey: profile.localKey ? Some(profile.localKey) : None, - createdAt: profile.createdAt ? Some(profile.createdAt) : None, - updatedAt: profile.updatedAt ? Some(profile.updatedAt) : None, - linkedPersona: profile.linkedPersona ? Some(profile.linkedPersona) : None, - }) - } - } - - async function backupAllRelations() { - const data = await queryRelations(() => true) - for (const relation of data) { - relations.push({ - favor: relation.favor, - persona: relation.linked, - profile: relation.profile, - }) - } - } - - async function backupPlugins() { - const plugins = Object.create(null) as Record - const allPlugins = [...activatedPluginsWorker] - - async function backup(plugin: Plugin.Worker.Definition): Promise { - const backupCreator = plugin.backup?.onBackup - if (!backupCreator) return - - async function backupPlugin() { - const result = await timeout(backupCreator!(), 60 * 1000, 'Timeout to backup creator.') - if (result.isNone()) return - // We limit the plugin contributed backups must be simple objects. - // We may allow plugin to store binary if we're moving to binary backup format like MessagePack. - plugins[plugin.ID] = result.map(JSON.stringify).map(JSON.parse).value - } - if (process.env.NODE_ENV === 'development') return backupPlugin() - return backupPlugin().catch((error) => - console.error(`[@masknet/plugin-infra] Plugin ${plugin.ID} failed to backup`, error), - ) - } - - await Promise.all(allPlugins.map(backup)) - return plugins - } -} diff --git a/packages/mask/background/services/backup/internal_restore.ts b/packages/mask/background/services/backup/internal_restore.ts deleted file mode 100644 index aec91db18a44..000000000000 --- a/packages/mask/background/services/backup/internal_restore.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { delay } from '@masknet/kit' -import type { NormalizedBackup } from '@masknet/backup-format' -import { activatedPluginsWorker, registeredPlugins } from '@masknet/plugin-infra/background-worker' -import { type PluginID, type ProfileIdentifier, RelationFavor, MaskMessages } from '@masknet/shared-base' -import { - consistentPersonaDBWriteAccess, - createOrUpdatePersonaDB, - createOrUpdateProfileDB, - createOrUpdateRelationDB, - type LinkedProfileDetails, -} from '../../database/persona/db.js' -import { - withPostDBTransaction, - createPostDB, - type PostRecord, - queryPostDB, - updatePostDB, -} from '../../database/post/index.js' -import type { LatestRecipientDetailDB, LatestRecipientReasonDB } from '../../database/post/dbType.js' -import { internal_wallet_restore } from './internal_wallet_restore.js' - -export async function restoreNormalizedBackup(backup: NormalizedBackup.Data) { - const { plugins, posts, wallets } = backup - - await restorePersonas(backup) - await restorePosts(posts.values()) - if (wallets.length) { - await internal_wallet_restore(wallets) - } - await restorePlugins(plugins) - - // Note: it looks like the restore will not immediately available to the dashboard, maybe due to - // serialization cost or indexedDB transaction apply cost? - // Here we add a delay as a workaround. It increases linearly as the scale of personas and profiles. - await delay(backup.personas.size + backup.profiles.size) - - if (backup.personas.size || backup.profiles.size) MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} - -async function restorePersonas(backup: NormalizedBackup.Data) { - const { personas, profiles, relations } = backup - - await consistentPersonaDBWriteAccess(async (t) => { - const promises: Array> = [] - for (const [id, persona] of personas) { - for (const [id] of persona.linkedProfiles) { - const state: LinkedProfileDetails = { connectionConfirmState: 'confirmed' } - persona.linkedProfiles.set(id, state) - } - - promises.push( - createOrUpdatePersonaDB( - { - identifier: id, - publicKey: persona.publicKey, - privateKey: persona.privateKey.unwrapOr(undefined), - createdAt: persona.createdAt.unwrapOr(undefined), - updatedAt: persona.updatedAt.unwrapOr(undefined), - localKey: persona.localKey.unwrapOr(undefined), - nickname: persona.nickname.unwrapOr(undefined), - mnemonic: persona.mnemonic - .map((mnemonic) => ({ - words: mnemonic.words, - parameter: { path: mnemonic.path, withPassword: mnemonic.hasPassword }, - })) - .unwrapOr(undefined), - linkedProfiles: persona.linkedProfiles as any, - // "login" again because this is the restore process. - // We need to explicitly set this flag because the backup may already in the database (but marked as "logout"). - hasLogout: false, - }, - { - explicitUndefinedField: 'ignore', - linkedProfiles: 'merge', - }, - t, - ), - ) - } - - for (const [id, profile] of profiles) { - promises.push( - createOrUpdateProfileDB( - { - identifier: id, - nickname: profile.nickname.unwrapOr(undefined), - localKey: profile.localKey.unwrapOr(undefined), - linkedPersona: profile.linkedPersona.unwrapOr(undefined), - createdAt: profile.createdAt.unwrapOr(new Date()), - updatedAt: profile.updatedAt.unwrapOr(new Date()), - }, - t, - ), - ) - } - - for (const relation of relations) { - promises.push( - createOrUpdateRelationDB( - { - profile: relation.profile, - linked: relation.persona, - favor: relation.favor, - }, - t, - ), - ) - } - - if (!relations.length) { - for (const persona of personas.values()) { - if (persona.privateKey.isNone()) continue - - for (const profile of profiles.values()) { - promises.push( - createOrUpdateRelationDB( - { - favor: RelationFavor.UNCOLLECTED, - linked: persona.identifier, - profile: profile.identifier, - }, - t, - ), - ) - } - } - } - await Promise.all(promises) - }) -} - -function restorePosts(backup: Iterable) { - return withPostDBTransaction(async (t) => { - const promises: Array> = [] - for (const post of backup) { - const rec: PostRecord = { - identifier: post.identifier, - foundAt: post.foundAt, - postBy: post.postBy.unwrapOr(undefined), - recipients: 'everyone', - } - if (post.encryptBy.isSome()) rec.encryptBy = post.encryptBy.value - if (post.postCryptoKey.isSome()) rec.postCryptoKey = post.postCryptoKey.value - if (post.summary.isSome()) rec.summary = post.summary.value - if (post.url.isSome()) rec.url = post.url.value - if (post.interestedMeta.size) rec.interestedMeta = post.interestedMeta - if (post.recipients.isSome()) { - const { value } = post.recipients - if (value.type === 'public') rec.recipients = 'everyone' - else { - const map = new Map() - for (const [id, detail] of value.receivers) { - map.set(id, { - reason: detail.map((x): LatestRecipientReasonDB => ({ at: x.at, type: 'direct' })), - }) - } - } - } - - // TODO: have a createOrUpdatePostDB - promises.push( - queryPostDB(post.identifier, t).then((result) => - result ? updatePostDB(rec, 'override', t) : createPostDB(rec, t), - ), - ) - } - await Promise.all(promises) - }) -} -async function restorePlugins(backup: NormalizedBackup.Data['plugins']) { - const plugins = [...activatedPluginsWorker] - const works = new Set>() - for (const [pluginID, item] of Object.entries(backup)) { - const plugin = plugins.find((x) => x.ID === pluginID) - // should we warn user here? - if (!plugin) { - if ([...registeredPlugins.getCurrentValue().map((x) => x[0])].includes(pluginID as PluginID)) - console.warn(`[@masknet/plugin-infra] Found a backup of a not enabled plugin ${plugin}`, item) - else console.warn(`[@masknet/plugin-infra] Found an unknown plugin backup of ${plugin}`, item) - continue - } - - const onRestore = plugin.backup?.onRestore - if (!onRestore) { - console.warn( - `[@masknet/plugin-infra] Found a backup of plugin ${plugin.ID} but it did not register a onRestore callback.`, - item, - ) - continue - } - works.add( - (async (): Promise => { - const result = await onRestore(item) - if (result.isErr()) { - const msg = `[@masknet/plugin-infra] Plugin ${plugin.ID} failed to restore its backup.` - throw new Error(msg, { cause: result.error }) - } - })(), - ) - } - await Promise.allSettled(works) -} diff --git a/packages/mask/background/services/backup/internal_wallet_backup.ts b/packages/mask/background/services/backup/internal_wallet_backup.ts deleted file mode 100644 index e713f625c5ce..000000000000 --- a/packages/mask/background/services/backup/internal_wallet_backup.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Some, None } from 'ts-results-es' -import { ec as EC } from 'elliptic' -import * as wallet_ts from /* webpackDefer: true */ 'wallet.ts' -import { isNonNull } from '@masknet/kit' -import { isSameAddress } from '@masknet/web3-shared-base' -import type { NormalizedBackup } from '@masknet/backup-format' -import { - toBase64URL, - type EC_Public_JsonWebKey, - type EC_Private_JsonWebKey, - type LegacyWalletRecord, -} from '@masknet/shared-base' -import type { WalletRecord } from '../../../shared/definitions/wallet.js' -import { exportMnemonicWords, exportPrivateKey, getLegacyWallets, getWallets } from '../wallet/services/index.js' - -export async function internal_wallet_backup() { - const wallet = await Promise.all([backupAllWallets(), backupAllLegacyWallets()]) - return wallet.flat() -} - -async function backupAllWallets(): Promise { - const wallets = await getWallets() - const allSettled = await Promise.allSettled( - wallets.map(async (wallet) => { - return { - ...wallet, - mnemonic: !wallet.configurable ? await exportMnemonicWords(wallet.address) : undefined, - privateKey: !wallet.configurable ? undefined : await exportPrivateKey(wallet.address), - } - }), - ) - const wallets_ = allSettled.map((x) => (x.status === 'fulfilled' ? WalletRecordToJSONFormat(x.value) : null)) - if (wallets_.some((x) => !x)) throw new Error('Failed to backup wallets.') - return wallets_.filter(isNonNull) -} - -async function backupAllLegacyWallets(): Promise { - const x = await getLegacyWallets() - return x.map(LegacyWalletRecordToJSONFormat) -} -function WalletRecordToJSONFormat( - wallet: Omit & { - mnemonic?: string - privateKey?: string - }, -): NormalizedBackup.WalletBackup { - const backup: NormalizedBackup.WalletBackup = { - name: wallet.name ?? '', - address: wallet.address, - createdAt: wallet.createdAt, - updatedAt: wallet.updatedAt, - derivationPath: None, - mnemonicId: None, - mnemonic: None, - passphrase: None, - publicKey: None, - privateKey: None, - } - if (wallet.mnemonic && wallet.derivationPath) { - backup.mnemonic = Some({ - words: wallet.mnemonic, - path: wallet.derivationPath, - hasPassword: false, - }) - } - - if (wallet.privateKey) backup.privateKey = Some(keyToJWK(wallet.privateKey, 'private')) - - if (wallet.mnemonicId) backup.mnemonicId = Some(wallet.mnemonicId) - - if (wallet.derivationPath) backup.derivationPath = Some(wallet.derivationPath) - return backup -} - -function LegacyWalletRecordToJSONFormat(wallet: LegacyWalletRecord): NormalizedBackup.WalletBackup { - const backup: NormalizedBackup.WalletBackup = { - name: wallet.name ?? '', - address: wallet.address, - createdAt: wallet.createdAt, - updatedAt: wallet.updatedAt, - derivationPath: None, - mnemonicId: None, - mnemonic: None, - passphrase: None, - privateKey: None, - publicKey: None, - } - - // generate keys for managed wallet - try { - const wallet_ = wallet - backup.passphrase = Some(wallet_.passphrase) - if (wallet_.mnemonic.length) - backup.mnemonic = Some({ - words: wallet_.mnemonic.join(' '), - path: "m/44'/60'/0'/0/0", - hasPassword: false, - }) - if (wallet_._public_key_ && isSameAddress(keyToAddr(wallet_._public_key_, 'public'), wallet.address)) - backup.publicKey = Some(keyToJWK(wallet_._public_key_, 'public')) - if (wallet_._private_key_ && isSameAddress(keyToAddr(wallet_._private_key_, 'private'), wallet.address)) - backup.privateKey = Some(keyToJWK(wallet_._private_key_, 'private')) - } catch (error) { - console.error(error) - } - return backup -} - -function keyToJWK(key: string, type: 'public'): EC_Public_JsonWebKey -function keyToJWK(key: string, type: 'private'): EC_Private_JsonWebKey -function keyToJWK(key: string, type: 'public' | 'private'): JsonWebKey { - const ec = new EC('secp256k1') - const key_ = key.replace(/^0x/, '') - const keyPair = type === 'public' ? ec.keyFromPublic(key_) : ec.keyFromPrivate(key_) - const pubKey = keyPair.getPublic() - const privKey = keyPair.getPrivate() - return { - crv: 'K-256', - ext: true, - x: base64(pubKey.getX().toArray()), - y: base64(pubKey.getY().toArray()), - key_ops: ['deriveKey', 'deriveBits'], - kty: 'EC', - d: type === 'private' ? base64(privKey.toArray()) : undefined, - } -} - -function base64(nums: number[]) { - return toBase64URL(new Uint8Array(nums).buffer) -} -function keyToAddr(key: string, type: 'public' | 'private'): string { - const ec = new EC('secp256k1') - const key_ = key.replace(/^0x/, '') - const keyPair = type === 'public' ? ec.keyFromPublic(key_) : ec.keyFromPrivate(key_) - return wallet_ts.EthereumAddress.from(Buffer.from(keyPair.getPublic(false, 'array'))).address -} diff --git a/packages/mask/background/services/backup/internal_wallet_restore.ts b/packages/mask/background/services/backup/internal_wallet_restore.ts deleted file mode 100644 index 2c2cf658eb1c..000000000000 --- a/packages/mask/background/services/backup/internal_wallet_restore.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { NormalizedBackup } from '@masknet/backup-format' -import { concatArrayBuffer } from '@masknet/kit' -import { fromBase64URL, isK256Point, isK256PrivateKey, type EC_JsonWebKey } from '@masknet/shared-base' -import { ChainbaseDomain } from '@masknet/web3-providers' -import { HD_PATH_WITHOUT_INDEX_ETHEREUM, currySameAddress, generateNewWalletName } from '@masknet/web3-shared-base' -import { ec as EC } from 'elliptic' -import { - getDerivableAccounts, - createMnemonicId, - getWallets, - recoverWalletFromMnemonicWords, - recoverWalletFromPrivateKey, -} from '../wallet/services/index.js' -import { ChainId, formatEthereumAddress } from '@masknet/web3-shared-evm' - -export async function internal_wallet_restore(backup: NormalizedBackup.WalletBackup[]) { - const mnemonicWalletMap = new Map< - string, - { - mnemonicId: string - derivationPath: string - } - >() - if (backup.some((x) => !!x.mnemonic.isSome())) { - const mnemonicWallets = backup.filter((x) => !!x.mnemonic.isSome()) - for (const wallet of mnemonicWallets) { - if (wallet.mnemonic.isSome()) { - const accounts = await getDerivableAccounts(wallet.mnemonic.value.words, 0, 10) - const mnemonicId = await createMnemonicId(wallet.mnemonic.value.words) - if (!mnemonicId) continue - - accounts.forEach((x) => { - mnemonicWalletMap.set(formatEthereumAddress(x.address), { - mnemonicId, - derivationPath: x.derivationPath, - }) - }) - } - } - } - - for (const wallet of backup) { - try { - const wallets = await getWallets() - const matchedDefaultNameFormat = wallet.name.match(/Wallet (\d+)/) - const digitIndex = matchedDefaultNameFormat?.[1] - let name = wallet.name - if (!name) { - const ens = await ChainbaseDomain.reverse(ChainId.Mainnet, wallet.address) - if (ens) name = ens - } - if (!name) { - name = generateNewWalletName( - wallets, - undefined, - digitIndex && !Number.isNaN(digitIndex) ? Number(digitIndex) : undefined, - ) - } - if (wallet.privateKey.isSome()) { - const info = mnemonicWalletMap.get(wallet.address) - await recoverWalletFromPrivateKey( - name, - await JWKToKey(wallet.privateKey.value, 'private'), - wallet.mnemonicId.unwrapOr(undefined) ?? info?.mnemonicId, - wallet.derivationPath.unwrapOr(undefined) ?? info?.derivationPath, - ) - } else if (wallet.mnemonic.isSome()) { - // fix a backup bug of pre-v2.2.2 versions - const accounts = await getDerivableAccounts(wallet.mnemonic.value.words, 1, 5) - const index = accounts.findIndex(currySameAddress(wallet.address)) - await recoverWalletFromMnemonicWords( - name, - wallet.mnemonic.value.words, - index > -1 ? `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/${index}` : wallet.mnemonic.value.path, - ) - } - } catch (error) { - console.error(error) - continue - } - } -} - -async function JWKToKey(jwk: EC_JsonWebKey, type: 'public' | 'private'): Promise { - const ec = new EC('secp256k1') - if (type === 'public' && jwk.x && jwk.y) { - const xb = fromBase64URL(jwk.x) - const yb = fromBase64URL(jwk.y) - const point = new Uint8Array(concatArrayBuffer(new Uint8Array([4]), xb, yb)) - if (await isK256Point(point)) return `0x${ec.keyFromPublic(point).getPublic(false, 'hex')}` - } - if (type === 'private' && jwk.d) { - const db = fromBase64URL(jwk.d) - if (await isK256PrivateKey(db)) return `0x${ec.keyFromPrivate(db).getPrivate('hex')}` - } - throw new Error('invalid private key') -} diff --git a/packages/mask/background/services/backup/persona.ts b/packages/mask/background/services/backup/persona.ts deleted file mode 100644 index 108e3567293f..000000000000 --- a/packages/mask/background/services/backup/persona.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { encode } from '@msgpack/msgpack' -import { encodeArrayBuffer } from '@masknet/kit' -import type { PersonaIdentifier } from '@masknet/shared-base' -import { queryPersonaDB } from '../../database/persona/db.js' - -export async function backupPersonaPrivateKey(identifier: PersonaIdentifier): Promise { - const profile = await queryPersonaDB(identifier) - if (!profile?.privateKey) return undefined - - const encodePrivateKey = encode(profile.privateKey) - return encodeArrayBuffer(encodePrivateKey) -} diff --git a/packages/mask/background/services/backup/restore.ts b/packages/mask/background/services/backup/restore.ts deleted file mode 100644 index d278e88f46a4..000000000000 --- a/packages/mask/background/services/backup/restore.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { getBackupSummary, normalizeBackup } from '@masknet/backup-format' -import { restoreNormalizedBackup } from './internal_restore.js' -import { Result } from 'ts-results-es' -import { SmartPayBundler, SmartPayOwner } from '@masknet/web3-providers' -import { compact, sum } from 'lodash-es' -import { bufferToHex, privateToPublic, publicToAddress } from '@ethereumjs/util' -import { fromBase64URL } from '@masknet/shared-base' - -export async function generateBackupSummary(raw: string) { - return Result.wrapAsync(async () => { - const backupObj: unknown = JSON.parse(raw) - const backup = await normalizeBackup(backupObj) - - const personas = [...backup.personas.values()].map((x) => { - if (!x.address.isNone()) return x.address.unwrap() - if (x.privateKey.isNone()) return - const privateKey = x.privateKey.unwrap() - if (!privateKey.d) return - const address = bufferToHex(publicToAddress(privateToPublic(Buffer.from(fromBase64URL(privateKey.d))))) - - return address - }) - - const wallets = backup.wallets.map((x) => x.address) - - const chainId = await SmartPayBundler.getSupportedChainId() - const accounts = await SmartPayOwner.getAccountsByOwners(chainId, [...compact(personas), ...wallets]) - return { - ...getBackupSummary(backup), - countOfWallets: sum([accounts.filter((x) => x.deployed).length, wallets.length]), - } - }) -} - -export async function restoreBackup(raw: string) { - const backupObj: unknown = JSON.parse(raw) - const backup = await normalizeBackup(backupObj) - await restoreNormalizedBackup(backup) -} diff --git a/packages/mask/background/services/crypto/appendEncryption.ts b/packages/mask/background/services/crypto/appendEncryption.ts deleted file mode 100644 index 8105ec0ea06b..000000000000 --- a/packages/mask/background/services/crypto/appendEncryption.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { appendEncryptionTarget, type EncryptPayloadNetwork, type SupportedPayloadVersions } from '@masknet/encryption' -import type { PostIVIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { deriveAESByECDH } from '../../database/persona/helper.js' -import { updatePostDB, queryPostDB } from '../../database/post/index.js' -import { publishPostAESKey_version37, publishPostAESKey_version39Or38 } from '../../network/queryPostKey.js' -import { getPostKeyCache } from './decryption.js' -import { prepareEncryptTarget, type EncryptTargetE2EFromProfileIdentifier } from './encryption.js' - -export async function appendShareTarget( - version: SupportedPayloadVersions, - post: PostIVIdentifier, - target: EncryptTargetE2EFromProfileIdentifier['target'], - whoAmI: ProfileIdentifier, - network: EncryptPayloadNetwork, -): Promise { - if (version === -39 || version === -40) throw new TypeError('invalid version') - const key = await getPostKeyCache(post) - const postRec = await queryPostDB(post) - const postBy = postRec?.encryptBy || postRec?.postBy || whoAmI - - const [keyMap, { target: convertedTarget }] = await prepareEncryptTarget({ type: 'E2E', target }) - - if (!key) throw new Error('No post key found') - const e2e = await appendEncryptionTarget( - { - target: convertedTarget, - iv: post.toIV(), - postAESKey: key, - version, - }, - { - async deriveAESKey(pub) { - const result = Array.from((await deriveAESByECDH(pub, postBy)).values()) - if (result.length === 0) throw new Error('No key found') - return result[0] - }, - }, - ) - - if (version === -38) { - publishPostAESKey_version39Or38(-38, post.toIV(), network, e2e) - } else { - publishPostAESKey_version37(post.toIV(), network, e2e) - } - - { - const recipients = new Map() - for (const [, value] of keyMap) recipients.set(value, new Date()) - updatePostDB({ identifier: post, recipients }, 'append') - } -} diff --git a/packages/mask/background/services/crypto/comment.ts b/packages/mask/background/services/crypto/comment.ts deleted file mode 100644 index 59b79b11f9a1..000000000000 --- a/packages/mask/background/services/crypto/comment.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { encodeArrayBuffer, decodeText, decodeArrayBuffer, encodeText } from '@masknet/kit' -import type { AESCryptoKey } from '@masknet/shared-base' - -// * Payload format: 🎶2/4|encrypted_comment:|| -export async function encryptComment(postIV: Uint8Array, postContent: string, comment: string): Promise { - const key = await getCommentKey(postIV, postContent) - - const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: postIV }, key, encodeText(comment)) - return `\u{1F3B6}2/4|${encodeArrayBuffer(encrypted)}:||` -} -export async function decryptComment( - postIV: Uint8Array, - postContent: string, - encryptComment: string, -): Promise { - const payload = extractCommentPayload(encryptComment) - if (!payload) return null - - const key = await getCommentKey(postIV, postContent) - const result = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: postIV }, key, decodeArrayBuffer(payload)) - return decodeText(result) -} - -async function getCommentKey(postIV: Uint8Array, postContent: string) { - const pbkdf = await crypto.subtle.importKey('raw', encodeText(postContent), 'PBKDF2', false, [ - 'deriveBits', - 'deriveKey', - ]) - return (await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: postIV, iterations: 100000, hash: 'SHA-256' }, - pbkdf, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'], - )) as AESCryptoKey -} - -function extractCommentPayload(text: string) { - const [_, toEnd] = text.split('\u{1F3B6}2/4|') - const [content, _2] = (toEnd || '').split(':||') - if (content.length) return content - return -} diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts deleted file mode 100644 index 4c836b5a3d55..000000000000 --- a/packages/mask/background/services/crypto/decryption.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { encodeArrayBuffer } from '@masknet/kit' -import { - decrypt, - parsePayload, - DecryptProgressKind, - EC_KeyCurve, - type DecryptProgress, - type EncryptPayloadNetwork, - encryptPayloadNetworkToDomain, - type EC_Key, - decodeByNetwork, - steganographyDecodeImage, - DecryptErrorReasons, - type DecryptReportedInfo, -} from '@masknet/encryption' -import { - type AESCryptoKey, - type EC_JsonWebKey, - type EC_Public_JsonWebKey, - PostIVIdentifier, - type ProfileIdentifier, - ECKeyIdentifier, -} from '@masknet/shared-base' -import type { TypedMessage } from '@masknet/typed-message' -import { noop } from 'lodash-es' -import { queryProfileDB, queryPersonaDB } from '../../database/persona/db.js' -import { - createProfileWithPersona, - decryptByLocalKey, - deriveAESByECDH, - hasLocalKeyOf, - queryPublicKey, -} from '../../database/persona/helper.js' -import { queryPostDB } from '../../database/post/index.js' -import { savePostKeyToDB } from '../../database/post/helper.js' -import { - GUN_queryPostKey_version37, - GUN_queryPostKey_version39Or38, - GUN_queryPostKey_version40, -} from '../../network/queryPostKey.js' - -export interface DecryptionContext { - encryptPayloadNetwork: EncryptPayloadNetwork - currentProfile: ProfileIdentifier | null - authorHint: ProfileIdentifier | null - postURL: string | undefined -} -export type EncodedPayload = - | { - type: 'text' - text: string - } - | { - type: 'image' - image: Blob - } - | { - type: 'image-url' - image: string - } -async function downloadImage(url: string): Promise { - const x = await fetch(url) - return await x.arrayBuffer() -} - -/** - * - * @param encoded If the encoded content is a text, it should only contain 1 payload. Extra payload will be ignored. - * @param context - */ -export async function* decryptWithDecoding( - encoded: EncodedPayload, - context: DecryptionContext, -): AsyncGenerator { - let decoded: string | Uint8Array - if (encoded.type === 'text') { - decoded = decodeByNetwork(context.encryptPayloadNetwork, encoded.text)[0] - } else { - if (!context.authorHint) { - return yield { type: DecryptProgressKind.Error, error: new Error(DecryptErrorReasons.UnrecognizedAuthor) } - } - const result = await steganographyDecodeImage(encoded.image, { - password: context.authorHint.toText(), - downloadImage, - }) - if (typeof result === 'string') { - decoded = decodeByNetwork(context.encryptPayloadNetwork, result)[0] - } else if (result === null) { - return yield { type: DecryptProgressKind.Error, error: new Error(DecryptErrorReasons.NoPayloadFound) } - } else { - decoded = result - } - } - - if (!decoded) return yield { type: DecryptProgressKind.Error, error: new Error(DecryptErrorReasons.NoPayloadFound) } - yield* decryption(decoded, context) -} - -const inMemoryCache = new Map() -async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { - const parse = await parsePayload(payload) - if (parse.isErr()) return null - - const { encryptPayloadNetwork, postURL, currentProfile, authorHint } = context - - // #region Identify the PostIdentifier - const iv = parse.value.encryption.unwrapOr(null)?.iv.unwrapOr(null) - { - if (!iv) return null - // iv is required to identify the post and it also used in comment encryption. - const info: DecryptReportedInfo = { - type: DecryptProgressKind.Info, - iv, - version: parse.value.version, - } - if (parse.value.encryption.isOk()) { - const val = parse.value.encryption.value - info.publicShared = val.type === 'public' - if (val.type === 'E2E') info.isAuthorOfPost = val.ownersAESKeyEncrypted.isOk() - } - yield info - } - const id = new PostIVIdentifier( - encryptPayloadNetworkToDomain(encryptPayloadNetwork), - encodeArrayBuffer(new Uint8Array(iv)), - ) - // #endregion - - if (inMemoryCache.has(id)) { - const p: DecryptProgress = { type: DecryptProgressKind.Success, content: inMemoryCache.get(id)! } - return void (yield p) - } - - // #region store author public key into the database - try { - const id = parse.unwrap().author.unwrap().unwrap() - if (!hasStoredAuthorPublicKey.has(id)) { - storeAuthorPublicKey(id, authorHint, parse.unwrap().authorPublicKey.unwrap().unwrap()).catch(noop) - hasStoredAuthorPublicKey.add(id) - } - } catch {} - // #endregion - - const progress = decrypt( - { - message: parse.value, - onDecrypted(message) { - inMemoryCache.set(id, message) - }, - }, - { - getPostKeyCache: getPostKeyCache.bind(null, id), - setPostKeyCache: (key) => { - return savePostKeyToDB(id, key, { - // public post will not call this function. - // and recipients only will be set when posting/appending recipients. - recipients: new Map(), - postBy: authorHint || parse.safeUnwrap().author.unwrapOr(undefined)?.unwrapOr(undefined), - url: postURL, - }) - }, - hasLocalKeyOf, - decryptByLocalKey, - async deriveAESKey(pub) { - return Array.from((await deriveAESByECDH(pub)).values()) - }, - queryAuthorPublicKey(author, signal) { - // TODO: This should try to fetch publicKey from NextID - // but it is not urgent because all new posts has their publicKey embedded - return queryPublicKey(author || authorHint) - }, - async *queryPostKey_version37(iv, signal) { - const author = await queryPublicKey(context.currentProfile) - if (!author) throw new Error(DecryptErrorReasons.CurrentProfileDoesNotConnectedToPersona) - yield* GUN_queryPostKey_version37( - iv, - author, - context.encryptPayloadNetwork, - signal || new AbortController().signal, - ) - }, - async *queryPostKey_version38(iv, signal) { - const author = await queryPublicKey(context.currentProfile) - if (!author) throw new Error(DecryptErrorReasons.CurrentProfileDoesNotConnectedToPersona) - yield* GUN_queryPostKey_version39Or38( - -38, - iv, - author, - context.encryptPayloadNetwork, - signal || new AbortController().signal, - ) - }, - async *queryPostKey_version39(iv, signal) { - const author = await queryPublicKey(context.currentProfile) - if (!author) throw new Error(DecryptErrorReasons.CurrentProfileDoesNotConnectedToPersona) - yield* GUN_queryPostKey_version39Or38( - -39, - iv, - author, - context.encryptPayloadNetwork, - signal || new AbortController().signal, - ) - }, - async queryPostKey_version40(iv) { - if (!currentProfile) return null - return GUN_queryPostKey_version40(iv, currentProfile.userId) - }, - }, - ) - - yield* progress - return null -} - -/** @internal */ -export async function getPostKeyCache(id: PostIVIdentifier) { - const post = await queryPostDB(id) - if (!post?.postCryptoKey) return null - const k = await crypto.subtle.importKey('jwk', post.postCryptoKey, { name: 'AES-GCM', length: 256 }, true, [ - 'decrypt', - ]) - return k as AESCryptoKey -} - -const hasStoredAuthorPublicKey = new Set() -async function storeAuthorPublicKey( - payloadAuthor: ProfileIdentifier, - postAuthor: ProfileIdentifier | null, - pub: EC_Key, -) { - if (payloadAuthor !== postAuthor) { - // ! Author detected is not equal to AuthorHint. - // ! Skip store the public key because it might be a security problem. - return - } - if (pub.algr !== EC_KeyCurve.secp256k1) { - throw new Error('TODO: support other curves') - } - - // if privateKey, we should possibly not recreate it - const profile = await queryProfileDB(payloadAuthor) - const persona = profile?.linkedPersona ? await queryPersonaDB(profile.linkedPersona) : undefined - if (persona?.privateKey) return - - const key = (await crypto.subtle.exportKey('jwk', pub.key)) as EC_JsonWebKey - const otherPersona = await queryPersonaDB((await ECKeyIdentifier.fromJsonWebKey(key)).unwrap()) - if (otherPersona?.privateKey) return - - return createProfileWithPersona( - payloadAuthor, - { connectionConfirmState: 'confirmed' }, - { - publicKey: (await crypto.subtle.exportKey('jwk', pub.key)) as EC_Public_JsonWebKey, - }, - ) -} diff --git a/packages/mask/background/services/crypto/encryption.ts b/packages/mask/background/services/crypto/encryption.ts deleted file mode 100644 index cc6bdab00948..000000000000 --- a/packages/mask/background/services/crypto/encryption.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { EC_Public_CryptoKey, PersonaIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { isTypedMessageText, type SerializableTypedMessages, type TypedMessageText } from '@masknet/typed-message' -import { - type EC_Key, - EC_KeyCurve, - encrypt, - type EncryptionResultE2EMap, - type EncryptTargetE2E, - type EncryptTargetPublic, - type EncryptPayloadNetwork, - encryptPayloadNetworkToDomain, -} from '@masknet/encryption' -import { encryptByLocalKey, deriveAESByECDH, queryPublicKey } from '../../database/persona/helper.js' -import { savePostKeyToDB } from '../../database/post/helper.js' -import { noop } from 'lodash-es' -import { queryProfileDB } from '../../database/persona/db.js' -import { publishPostAESKey_version39Or38, publishPostAESKey_version37 } from '../../network/queryPostKey.js' -import { None, Some } from 'ts-results-es' - -export interface EncryptTargetE2EFromProfileIdentifier { - type: 'E2E' - target: ReadonlyArray<{ profile: ProfileIdentifier; persona?: PersonaIdentifier }> -} -export async function encryptTo( - version: -37 | -38, - content: SerializableTypedMessages, - target: EncryptTargetPublic | EncryptTargetE2EFromProfileIdentifier, - whoAmI: ProfileIdentifier | undefined, - network: EncryptPayloadNetwork, -): Promise { - const [keyMap, convertedTarget] = await prepareEncryptTarget(target) - - const authorPublicKey = whoAmI ? await queryPublicKey(whoAmI).catch(noop) : undefined - const { identifier, output, postKey, e2e } = await encrypt( - { - network: whoAmI?.network || encryptPayloadNetworkToDomain(network), - author: whoAmI ? Some(whoAmI) : None, - authorPublicKey: - authorPublicKey ? - Some({ algr: EC_KeyCurve.secp256k1, key: authorPublicKey } satisfies EC_Key) - : None, - message: content, - target: convertedTarget, - version, - }, - { - async deriveAESKey(pub) { - const result = Array.from((await deriveAESByECDH(pub, whoAmI)).values()) - if (result.length === 0) throw new Error('No key found') - return result[0] - }, - encryptByLocalKey: async (content, iv) => { - if (!whoAmI) throw new Error('No Profile found') - return encryptByLocalKey(whoAmI, content, iv) - }, - }, - ) - ;(async () => { - const profile = whoAmI ? await queryProfileDB(whoAmI).catch(noop) : null - const usingPersona = profile?.linkedPersona - return savePostKeyToDB(identifier, postKey, { - postBy: whoAmI, - recipients: target.type === 'public' ? 'everyone' : e2eMapToRecipientDetails(keyMap!, e2e!), - encryptBy: usingPersona, - ...collectInterestedMeta(content), - }) - })().catch((error) => console.error('[@masknet/encryption] Failed to save post key to DB', error)) - - if (target.type === 'E2E') { - if (version === -37) { - publishPostAESKey_version37(identifier.toIV(), network, e2e!) - } else { - publishPostAESKey_version39Or38(-38, identifier.toIV(), network, e2e!) - } - } - return output -} - -function e2eMapToRecipientDetails( - keyMap: Map, - input: EncryptionResultE2EMap, -): Map { - const result = new Map() - for (const [key] of input) { - const identifier = keyMap.get(key.key) - if (!identifier) continue - result.set(identifier, new Date()) - } - return result -} - -/** @internal */ -export function prepareEncryptTarget( - target: EncryptTargetE2EFromProfileIdentifier, -): Promise, EncryptTargetE2E]> -export function prepareEncryptTarget(target: EncryptTargetPublic): Promise -export function prepareEncryptTarget( - target: EncryptTargetPublic | EncryptTargetE2EFromProfileIdentifier, -): Promise< - readonly [key_map: Map | null, EncryptTargetPublic | EncryptTargetE2E] -> -export async function prepareEncryptTarget( - target: EncryptTargetPublic | EncryptTargetE2EFromProfileIdentifier, -): Promise< - readonly [key_map: Map | null, EncryptTargetPublic | EncryptTargetE2E] -> { - if (target.type === 'public') return [null, target] as const - const key_map = new Map() - const map: Array> = [] - - await Promise.allSettled( - target.target.map(async (id) => { - const key = (await id.persona?.toCryptoKey('derive')) || (await queryPublicKey(id.profile)) - if (!key) { - console.error('No publicKey found for profile', id.profile.toText()) - return - } - map.push({ algr: EC_KeyCurve.secp256k1, key }) - key_map.set(key, id.profile) - }), - ) - - return [key_map, { type: 'E2E', target: map } satisfies EncryptTargetE2E] as const -} - -function collectInterestedMeta(content: SerializableTypedMessages) { - if (isTypedMessageText(content)) return { summary: getSummary(content), meta: content.meta } - return {} -} - -const SUMMARY_MAX_LENGTH = 40 -function getSummary(content: TypedMessageText) { - let result = '' - const sliceLength = content.content.length > SUMMARY_MAX_LENGTH ? SUMMARY_MAX_LENGTH + 1 : SUMMARY_MAX_LENGTH - - // UTF-8 aware summary - if (Intl.Segmenter) { - // it seems like using "en" can also split the word correctly. - const seg = new Intl.Segmenter('en') - for (const word of seg.segment(content.content)) { - if (result.length >= sliceLength) break - result += word.segment - } - } else { - result = content.content.slice(0, sliceLength) - } - return result -} diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts deleted file mode 100644 index d8c4c11ef9e5..000000000000 --- a/packages/mask/background/services/crypto/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Encrypt & decrypt (decryptWithDecoding is a generator, not included.) -export { encryptTo } from './encryption.js' -export { appendShareTarget } from './appendEncryption.js' - -// Comments -export { encryptComment, decryptComment } from './comment.js' - -// Steganography -export { steganographyEncodeImage } from './steganography.js' - -export { getRecipients, hasRecipientAvailable, getIncompleteRecipientsOfPost } from './recipients.js' diff --git a/packages/mask/background/services/crypto/recipients.ts b/packages/mask/background/services/crypto/recipients.ts deleted file mode 100644 index 9e202f556e3e..000000000000 --- a/packages/mask/background/services/crypto/recipients.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { PostIVIdentifier, ProfileIdentifier, ProfileInformation } from '@masknet/shared-base' -import { queryProfilesDB } from '../../database/persona/db.js' -import { queryPostDB } from '../../database/post/index.js' -import { toProfileInformation } from '../__utils__/convert.js' - -export async function hasRecipientAvailable(whoAmI: ProfileIdentifier): Promise { - const profiles = await queryProfilesDB({ hasLinkedPersona: true, network: whoAmI.network }) - - if (profiles.length === 0) return false - if (profiles.length > 1) return true - return profiles[0].identifier !== whoAmI -} - -export async function getRecipients(whoAmI: ProfileIdentifier): Promise { - const profiles = (await queryProfilesDB({ network: whoAmI.network, hasLinkedPersona: true })).filter( - (x) => x.identifier !== whoAmI, - ) - return toProfileInformation(profiles).mustNotAwaitThisWithInATransaction -} - -export async function getIncompleteRecipientsOfPost(id: PostIVIdentifier): Promise { - const nameInDB = (await queryPostDB(id))?.recipients - if (nameInDB === 'everyone') return [] - if (!nameInDB?.size) return [] - - const profiles = ( - await queryProfilesDB({ - identifiers: [...nameInDB.keys()], - hasLinkedPersona: true, - }) - ).filter((x) => x.linkedPersona) - return toProfileInformation(profiles).mustNotAwaitThisWithInATransaction -} diff --git a/packages/mask/background/services/crypto/steganography.ts b/packages/mask/background/services/crypto/steganography.ts deleted file mode 100644 index f5359ec0cd76..000000000000 --- a/packages/mask/background/services/crypto/steganography.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { memoize } from 'lodash-es' -import { memoizePromise } from '@masknet/kit' -import { steganographyEncodeImage as __steganographyEncodeImage, type EncodeImageOptions } from '@masknet/encryption' - -async function fetchImage(url: string) { - const res = await fetch(url) - if (!res.ok) throw new Error('Fetch failed.') - return res.arrayBuffer() -} - -const steganographyDownloadImage = memoizePromise(memoize, fetchImage, (x) => x) - -export function steganographyEncodeImage( - buf: ArrayBuffer, - options: Omit, -): Promise { - return __steganographyEncodeImage(buf, { ...options, downloadImage: steganographyDownloadImage }) -} diff --git a/packages/mask/background/services/helper/i18n-cache-query-list.ts b/packages/mask/background/services/helper/i18n-cache-query-list.ts deleted file mode 100644 index 23d001aa8169..000000000000 --- a/packages/mask/background/services/helper/i18n-cache-query-list.ts +++ /dev/null @@ -1,41 +0,0 @@ -// This file is auto generated. DO NOT EDIT -// Run `npx gulp sync-languages` to regenerate. -export default { - 'mask/shared-ui/locales/%locale%.json': 'mask', - 'mask/content-script/site-adaptors/twitter.com/locales/%locale%.json': 'DO_NOT_USE', - 'shared/src/locales/%locale%.json': 'shared', - 'shared-base-ui/src/locales/%locale%.json': 'shareBase', - 'mask/dashboard/locales/%locale%.json': 'dashboard', - 'plugins/Debugger/src/locales/%locale%.json': 'io.mask.debugger', - 'plugins/FileService/src/locales/%locale%.json': 'com.maskbook.fileservice', - 'plugins/ScamSniffer/src/locales/%locale%.json': 'io.scamsniffer.mask-plugin', - 'plugins/CyberConnect/src/locales/%locale%.json': 'me.cyberconnect.app', - 'plugins/RSS3/src/locales/%locale%.json': 'bio.rss3', - 'plugins/NextID/src/locales/%locale%.json': 'com.mask.next_id', - 'plugins/template/src/locales/%locale%.json': '__template__', - 'plugins/GoPlusSecurity/src/locales/%locale%.json': 'io.gopluslabs.security', - 'plugins/CrossChainBridge/src/locales/%locale%.json': 'io.mask.cross-chain-bridge', - 'plugins/RedPacket/src/locales/%locale%.json': 'com.maskbook.red_packet', - 'plugins/Tips/src/locales/%locale%.json': 'com.maskbook.tip', - 'plugins/Avatar/src/locales/%locale%.json': 'com.maskbook.avatar', - 'plugins/Trader/src/locales/%locale%.json': 'com.maskbook.trader', - 'plugins/Gitcoin/src/locales/%locale%.json': 'co.gitcoin', - 'plugins/MaskBox/src/locales/%locale%.json': 'com.maskbook.box', - 'plugins/Pets/src/locales/%locale%.json': 'com.maskbook.pets', - 'plugins/Web3Profile/src/locales/%locale%.json': 'io.mask.web3-profile', - 'plugins/Handle/src/locales/%locale%.json': 'com.maskbook.handle', - 'plugins/Approval/src/locales/%locale%.json': 'com.maskbook.approval', - 'plugins/ScamWarning/src/locales/%locale%.json': 'com.mask.scam-warning', - 'plugins/SmartPay/src/locales/%locale%.json': 'com.mask.smart-pay', - 'plugins/VCent/src/locales/%locale%.json': 'com.maskbook.tweet', - 'plugins/Transak/src/locales/%locale%.json': 'com.maskbook.transak', - 'plugins/Collectible/src/locales/%locale%.json': 'com.maskbook.collectibles', - 'plugins/Claim/src/locales/%locale%.json': 'com.mask.claim', - 'plugins/ArtBlocks/src/locales/%locale%.json': 'io.artblocks', - 'plugins/Savings/src/locales/%locale%.json': 'com.savings', - 'plugins/Snapshot/src/locales/%locale%.json': 'org.snapshot', - 'plugins/ProfileCard/src/locales/%locale%.json': 'io.mask.web3-profile-card', - 'plugins/SwitchLogo/src/locales/%locale%.json': 'io.mask.switch-logo', - 'plugins/Calendar/src/locales/%locale%.json': 'io.mask.calendar', - 'plugins/FriendTech/src/locales/%locale%.json': 'io.mask.friend-tech', -} diff --git a/packages/mask/background/services/helper/i18n-cache-query.ts b/packages/mask/background/services/helper/i18n-cache-query.ts deleted file mode 100644 index 3114a3c3a61a..000000000000 --- a/packages/mask/background/services/helper/i18n-cache-query.ts +++ /dev/null @@ -1,67 +0,0 @@ -import list from './i18n-cache-query-list.js' - -export type Bundle = [namespace: string, lang: string, json: Record] -export async function queryRemoteI18NBundle(lang: string): Promise { - // skip fetching in development. if you need to debug this, please comment this code. - if (process.env.NODE_ENV === 'development') return [] - - const updateLang = getCurrentLanguage(lang) - if (!updateLang) return [] - - const responses = updateLang === 'en-US' ? fetchEnglishBundle() : fetchTranslatedBundle(lang) - const results = await Promise.allSettled(responses) - return results - .filter((x) => x.status === 'fulfilled') - .map((x) => x.value!) - .filter(Boolean) -} - -const I18N_LOCALES_HOST = 'https://maskbook.pages.dev/' - -function fetchTranslatedBundle(lang: string) { - return Object.entries(list).map(async ([url, namespace]): Promise => { - try { - const path = url.replace('%locale%', lang) - const response = await fetch(I18N_LOCALES_HOST + path, fetchOption) - const json = await response.json() - if (!isValidTranslation(json)) return null - return [namespace, lang, json] - } catch { - return null - } - }) -} -function fetchEnglishBundle() { - return Object.entries(list).map(async ([url, namespace]): Promise => { - try { - const path = url.replace('%locale%', 'en-US') - const response = await fetch(I18N_LOCALES_HOST + path, fetchOption) - const json = await response.json() - if (!isValidTranslation(json)) return null - return [namespace, 'en-US', json] - } catch { - return null - } - }) -} -function isValidTranslation(obj: unknown): obj is Record { - if (typeof obj !== 'object' || obj === null) return false - for (const key in obj) { - if (typeof (obj as any)[key] !== 'string') return false - } - return true -} - -const fetchOption = { - credentials: 'omit', - referrerPolicy: 'no-referrer', -} as const - -function getCurrentLanguage(lang: string) { - if (['zh-CN', 'zh-TW'].includes(lang)) return lang - if (lang.startsWith('en')) return 'en-US' - if (lang.startsWith('zh')) return 'zh-TW' - if (lang.startsWith('ja')) return 'ja-JP' - if (lang.startsWith('ko')) return 'ko-KR' - return null -} diff --git a/packages/mask/background/services/helper/index.ts b/packages/mask/background/services/helper/index.ts deleted file mode 100644 index 787a82bce187..000000000000 --- a/packages/mask/background/services/helper/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export { fetchBlob, fetchJSON, fetchText, fetchGlobal } from '@masknet/web3-providers/helpers' -export { resolveTCOLink } from './short-link-resolver.js' -export { - openPopupWindow, - removePopupWindow, - openDashboard, - queryCurrentActiveTab, - queryCurrentPopupWindowId, -} from './popup-opener.js' -export { - queryExtensionPermission, - hasHostPermission, - requestExtensionPermissionFromContentScript, -} from './request-permission.js' -export { queryRemoteI18NBundle, type Bundle } from './i18n-cache-query.js' -export { getTelemetryID, setTelemetryID } from './telemetry-id.js' -export { fetchSandboxedPluginManifest } from './sandboxed.js' -export { getActiveTab } from './tabs.js' diff --git a/packages/mask/background/services/helper/popup-opener.ts b/packages/mask/background/services/helper/popup-opener.ts deleted file mode 100644 index 33b982be1204..000000000000 --- a/packages/mask/background/services/helper/popup-opener.ts +++ /dev/null @@ -1,117 +0,0 @@ -import urlcat, { type ParamMap } from 'urlcat' -import { type DashboardRoutes, PopupRoutes, MaskMessages, type PopupRoutesParamsMap } from '@masknet/shared-base' -import { isLocked } from '../wallet/services/index.js' - -let currentPopupWindowId = 0 - -async function openWindow(url: string): Promise { - const windows = await browser.windows.getAll() - const popup = windows.find((window) => window.type === 'popup' && window.id === currentPopupWindowId) - if (popup) { - await browser.windows.update(popup.id!, { focused: true }) - } else { - let left: number - let top: number - - try { - const lastFocused = await browser.windows.getLastFocused() - // Position window in top right corner of lastFocused window. - top = lastFocused.top ?? 0 - left = (lastFocused.left ?? 0) + (lastFocused.width ?? 0) - 400 - } catch { - // The following properties are more than likely 0, due to being - // opened from the background chrome process for the extension that - // has no physical dimensions - - // Note: DOM is only available in MV2 or MV3 page mode. - const { screenX, outerWidth, screenY } = globalThis as any - if (typeof screenX === 'number' && typeof screenY === 'number' && typeof outerWidth === 'number') { - top = Math.max(screenY, 0) - left = Math.max(screenX + (outerWidth - 400), 0) - } else { - top = 100 - left = 100 - } - } - - const { id } = await browser.windows.create({ - url: browser.runtime.getURL(url), - width: 400, - height: 628, - type: 'popup', - state: 'normal', - left, - top, - }) - - // update currentPopupWindowId and clean event - if (id) { - currentPopupWindowId = id - browser.windows.onRemoved.addListener(function listener(windowID: number) { - if (windowID !== id) return - currentPopupWindowId = 0 - browser.windows.onRemoved.removeListener(listener) - }) - } - } -} -async function openOrUpdatePopupWindow(route: PopupRoutes, params: ParamMap) { - if (!currentPopupWindowId) return openWindow(urlcat('popups.html#', route, params)) - - await browser.windows.update(currentPopupWindowId, { focused: true }) - MaskMessages.events.popupRouteUpdated.sendToAll( - urlcat(route, { - close_after_unlock: true, - ...params, - }), - ) -} - -const noWalletUnlockNeeded: PopupRoutes[] = [ - PopupRoutes.PersonaSignRequest, - PopupRoutes.Personas, - PopupRoutes.WalletUnlock, -] - -export interface OpenPopupWindowOptions { - bypassWalletLock?: boolean -} -export async function openPopupWindow( - route: T, - params: T extends keyof PopupRoutesParamsMap ? PopupRoutesParamsMap[T] : undefined, - options?: OpenPopupWindowOptions, -): Promise { - if (noWalletUnlockNeeded.includes(route) || options?.bypassWalletLock || !(await isLocked())) { - return openOrUpdatePopupWindow(route, { - close_after_unlock: true, - ...params, - }) - } else { - return openOrUpdatePopupWindow(PopupRoutes.Wallet, { - close_after_unlock: true, - from: urlcat(route, params as ParamMap), - } satisfies PopupRoutesParamsMap[PopupRoutes.Wallet]) - } -} - -export async function queryCurrentPopupWindowId() { - return currentPopupWindowId -} - -export async function removePopupWindow(): Promise { - if (!currentPopupWindowId) return - browser.windows.remove(currentPopupWindowId) - currentPopupWindowId = 0 -} - -export async function openDashboard(route?: DashboardRoutes, search?: string) { - await browser.tabs.create({ - active: true, - url: browser.runtime.getURL(`/dashboard.html#${route}${search ? `?${search}` : ''}`), - }) -} - -export async function queryCurrentActiveTab() { - const [activeTab] = await browser.tabs.query({ active: true, currentWindow: true }) - return activeTab -} diff --git a/packages/mask/background/services/helper/request-permission.ts b/packages/mask/background/services/helper/request-permission.ts deleted file mode 100644 index 07e241aafa6f..000000000000 --- a/packages/mask/background/services/helper/request-permission.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Permissions } from 'webextension-polyfill' -import { waitUntil } from '@dimensiondev/holoflows-kit' -import { MaskMessages } from '@masknet/shared-base' -import { getPermissionRequestURL } from '../../../shared/definitions/routes.js' - -export async function requestExtensionPermissionFromContentScript( - permission: Permissions.Permissions, -): Promise { - if (await browser.permissions.contains(permission)) return true - const popup = await browser.windows.create({ - height: 600, - width: 400, - type: 'popup', - url: getPermissionRequestURL(permission), - }) - const promise = new Promise((resolve) => { - browser.windows.onRemoved.addListener(function listener(windowID: number) { - if (windowID !== popup.id) return - browser.permissions.contains(permission).then(sendNotification).then(resolve) - browser.windows.onRemoved.removeListener(listener) - }) - }) - waitUntil(promise) - return promise -} - -function sendNotification(result: boolean) { - if (result) MaskMessages.events.hostPermissionChanged.sendToAll() - return result -} - -export function hasHostPermission(origins: readonly string[]) { - return browser.permissions.contains({ origins: [...origins] }) -} - -export function queryExtensionPermission(permission: Permissions.AnyPermissions): Promise { - return browser.permissions.contains(permission) -} diff --git a/packages/mask/background/services/helper/sandboxed.ts b/packages/mask/background/services/helper/sandboxed.ts deleted file mode 100644 index e0ea171a7913..000000000000 --- a/packages/mask/background/services/helper/sandboxed.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fetchText } from './index.js' - -export async function fetchSandboxedPluginManifest(addr: string): Promise { - const text = await fetchText(addr + 'mask-manifest.json') - - // TODO: verify manifest - return JSON.parse( - text - .split('\n') - .filter((x) => !x.match(/^ +\/\//)) - .join('\n'), - ) -} diff --git a/packages/mask/background/services/helper/short-link-resolver.ts b/packages/mask/background/services/helper/short-link-resolver.ts deleted file mode 100644 index 730bb68b643d..000000000000 --- a/packages/mask/background/services/helper/short-link-resolver.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { memoize } from 'lodash-es' -import { memoizePromise } from '@masknet/kit' -import { fetchText } from '@masknet/web3-providers/helpers' - -const cache = new Map() - -async function resolver(u: string): Promise { - if (!u.startsWith('https://t.co/')) return null - if (cache.has(u)) return cache.get(u)! - const text = await fetchText(u, { - redirect: 'error', - credentials: 'omit', - referrerPolicy: 'no-referrer', - }) - const url = text.match(/URL=(.+).><\/noscript/)?.[1] - if (url) cache.set(u, url) - return url ?? null -} -/** Resolve a https://t.co/ link to it's real address. */ -export const resolveTCOLink = memoizePromise(memoize, resolver, (x) => x) diff --git a/packages/mask/background/services/helper/tabs.ts b/packages/mask/background/services/helper/tabs.ts deleted file mode 100644 index 3026e311a9f5..000000000000 --- a/packages/mask/background/services/helper/tabs.ts +++ /dev/null @@ -1,8 +0,0 @@ -export async function getActiveTab(): Promise { - const [tab] = await browser.tabs.query({ currentWindow: true, active: true, windowType: 'normal' }) - if (!tab) return undefined - return { - id: tab.id, - url: tab.url, - } -} diff --git a/packages/mask/background/services/helper/telemetry-id.ts b/packages/mask/background/services/helper/telemetry-id.ts deleted file mode 100644 index b7c3d84a57d9..000000000000 --- a/packages/mask/background/services/helper/telemetry-id.ts +++ /dev/null @@ -1,28 +0,0 @@ -// DO NOT CHANGE! import from folder instead of package directly -// because we need as less as possible files to be imported. - -// All imports must be deferred. This file loads in the very early stage. - -import * as base /* webpackDefer: true */ from '@masknet/shared-base' -import { TelemetryID } from '../../../../shared-base/src/Telemetry/index.js' - -import.meta.webpackHot?.accept() - -export async function getTelemetryID(): Promise { - const { telemetry_id } = await browser.storage.local.get('telemetry_id') - return telemetry_id || setTelemetryID() -} - -export async function setTelemetryID(sendNotification = true): Promise { - const id = Array.from(crypto.getRandomValues(new Uint8Array(40)), (i) => (i % 16).toString(16)) - .join('') - .slice(0, 40) - try { - await browser.storage.local.set({ telemetry_id: id }) - } catch {} - - if (sendNotification) base.MaskMessages.events.telemetryIDReset.sendToAll(id) - - TelemetryID.value = id - return id -} diff --git a/packages/mask/background/services/identity/avatar/query.ts b/packages/mask/background/services/identity/avatar/query.ts deleted file mode 100644 index 3ad15cb99cd1..000000000000 --- a/packages/mask/background/services/identity/avatar/query.ts +++ /dev/null @@ -1 +0,0 @@ -export { queryAvatarsDataURL } from '../../../database/avatar-cache/avatar.js' diff --git a/packages/mask/background/services/identity/index.ts b/packages/mask/background/services/identity/index.ts deleted file mode 100644 index e6080f1d2077..000000000000 --- a/packages/mask/background/services/identity/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from './persona/create.js' -export * from './persona/query.js' -export * from './persona/update.js' -export * from './persona/sign.js' -export * from './persona/avatar.js' - -export * from './profile/query.js' -export * from './profile/update.js' - -export * from './relation/create.js' -export * from './relation/query.js' -export * from './relation/update.js' - -export * from './avatar/query.js' - -export { validateMnemonic } from './persona/utils.js' diff --git a/packages/mask/background/services/identity/persona/avatar.ts b/packages/mask/background/services/identity/persona/avatar.ts deleted file mode 100644 index 43cb8321b0a5..000000000000 --- a/packages/mask/background/services/identity/persona/avatar.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MaskMessages, type PersonaIdentifier, type ProfileIdentifier } from '@masknet/shared-base' -import { queryAvatarLastUpdateTime, queryAvatarsDataURL, storeAvatar } from '../../../database/avatar-cache/avatar.js' - -export async function getPersonaAvatar(identifiers: undefined | PersonaIdentifier): Promise -export async function getPersonaAvatar( - identifiers: readonly PersonaIdentifier[], -): Promise> -export async function getPersonaAvatar( - identifiers: undefined | PersonaIdentifier | readonly PersonaIdentifier[], -): Promise> { - if (!identifiers) return undefined - // Array.isArray cannot guard for readonly array. - // eslint-disable-next-line @masknet/type-no-instanceof-wrapper - const map = await queryAvatarsDataURL(identifiers instanceof Array ? identifiers : [identifiers]) - // eslint-disable-next-line @masknet/type-no-instanceof-wrapper - if (identifiers instanceof Array) return map - return map.get(identifiers) -} - -export async function getPersonaAvatarLastUpdateTime(identifier?: PersonaIdentifier | null) { - if (!identifier) return undefined - return queryAvatarLastUpdateTime(identifier) -} - -export async function updatePersonaAvatar(identifier: PersonaIdentifier | null | undefined, avatar: Blob) { - if (!identifier) return - await storeAvatar(identifier, await avatar.arrayBuffer()) - MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} diff --git a/packages/mask/background/services/identity/persona/create.ts b/packages/mask/background/services/identity/persona/create.ts deleted file mode 100644 index e9b27bd30c17..000000000000 --- a/packages/mask/background/services/identity/persona/create.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as bip39 from 'bip39' -import { decodeArrayBuffer, encodeArrayBuffer } from '@masknet/kit' -import { - type EC_Public_JsonWebKey, - type PersonaIdentifier, - isEC_Private_JsonWebKey, - ECKeyIdentifier, -} from '@masknet/shared-base' -import { createPersonaByJsonWebKey } from '../../../database/persona/helper.js' -import { decode, encode } from '@msgpack/msgpack' -import { omit } from 'lodash-es' -import { queryPersonasDB } from '../../../database/persona/db.js' -import { deriveLocalKeyFromECDHKey, recover_ECDH_256k1_KeyPair_ByMnemonicWord } from './utils.js' - -export async function createPersonaByPrivateKey( - privateKeyString: string, - nickname: string, -): Promise { - const privateKey = decode(decodeArrayBuffer(privateKeyString)) - if (!isEC_Private_JsonWebKey(privateKey)) throw new TypeError('Invalid private key') - - return createPersonaByJsonWebKey({ privateKey, publicKey: omit(privateKey, 'd') as EC_Public_JsonWebKey, nickname }) -} - -export async function createPersonaByMnemonicV2( - mnemonicWord: string, - nickname: string | undefined, - password: string, -): Promise { - const personas = await queryPersonasDB({ nameContains: nickname }) - if (personas.length > 0) throw new Error('Nickname already exists') - - const verify = bip39.validateMnemonic(mnemonicWord) - if (!verify) throw new Error('Verify error') - - const { key, mnemonicRecord: mnemonic } = await recover_ECDH_256k1_KeyPair_ByMnemonicWord(mnemonicWord, password) - const { privateKey, publicKey } = key - const localKey = await deriveLocalKeyFromECDHKey(publicKey, mnemonic.words) - return createPersonaByJsonWebKey({ - privateKey, - publicKey, - localKey, - mnemonic, - nickname, - uninitialized: false, - }) -} - -export async function queryPersonaKeyByMnemonicV2(mnemonicWords: string) { - const { key } = await recover_ECDH_256k1_KeyPair_ByMnemonicWord(mnemonicWords, '') - const identifier = (await ECKeyIdentifier.fromJsonWebKey(key.publicKey)).unwrap() - const encodePrivateKey = encode(key.privateKey) - const privateKey = encodeArrayBuffer(encodePrivateKey) - return { - publicKey: identifier.toText(), - privateKey, - } -} diff --git a/packages/mask/background/services/identity/persona/query.ts b/packages/mask/background/services/identity/persona/query.ts deleted file mode 100644 index f7368cafe352..000000000000 --- a/packages/mask/background/services/identity/persona/query.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { first, omit, orderBy } from 'lodash-es' -import { - ECKeyIdentifier, - type EC_Public_JsonWebKey, - fromBase64URL, - isEC_Private_JsonWebKey, - type NextIDPlatform, - type PersonaIdentifier, - type PersonaInformation, - type ProfileIdentifier, - type SocialIdentity, -} from '@masknet/shared-base' -import type { IdentityResolved } from '@masknet/plugin-infra' -import { NextIDProof } from '@masknet/web3-providers' -import { - createPersonaDBReadonlyAccess, - queryPersonaDB, - queryPersonasDB, - queryProfileDB, -} from '../../../database/persona/db.js' -import { toPersonaInformation } from '../../__utils__/convert.js' -import * as bip39 from 'bip39' -import { recover_ECDH_256k1_KeyPair_ByMnemonicWord } from './utils.js' -import { bufferToHex, privateToPublic, publicToAddress } from '@ethereumjs/util' -import { decode } from '@msgpack/msgpack' -import { decodeArrayBuffer } from '@masknet/kit' - -export async function queryOwnedPersonaInformation(initializedOnly: boolean): Promise { - let result: Promise - await createPersonaDBReadonlyAccess(async (t) => { - let personas = await queryPersonasDB({ hasPrivateKey: true }, t) - if (initializedOnly) personas = personas.filter((x) => !x.uninitialized) - result = toPersonaInformation(personas, t).mustNotAwaitThisWithInATransaction - }) - return result! -} - -export async function queryLastPersonaCreated(): Promise { - const all = await queryPersonasDB({ hasPrivateKey: true }) - return first(orderBy(all, (x) => x.createdAt, 'desc'))?.identifier -} - -export async function queryPersonaByProfile(id: ProfileIdentifier): Promise { - let result: Promise | undefined - await createPersonaDBReadonlyAccess(async (t) => { - const profile = await queryProfileDB(id, t) - if (!profile?.linkedPersona) return - const persona = await queryPersonaDB(profile.linkedPersona, t) - if (!persona) return - result = toPersonaInformation([persona], t).mustNotAwaitThisWithInATransaction.then((x) => first(x)!) - }) - return result -} - -export async function queryPersona(id: PersonaIdentifier): Promise { - let result: Promise | undefined - await createPersonaDBReadonlyAccess(async (t) => { - const persona = await queryPersonaDB(id, t) - if (!persona) return - result = toPersonaInformation([persona], t).mustNotAwaitThisWithInATransaction.then((x) => first(x)!) - }) - return result -} - -export async function queryPersonaEOAByMnemonic(mnemonicWord: string, password: string) { - const verify = bip39.validateMnemonic(mnemonicWord) - if (!verify) throw new Error('Verify error') - - const { key } = await recover_ECDH_256k1_KeyPair_ByMnemonicWord(mnemonicWord, password) - const { privateKey, publicKey } = key - - if (!privateKey.d) return - return { - address: bufferToHex(publicToAddress(privateToPublic(Buffer.from(fromBase64URL(privateKey.d))))), - identifier: (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap(), - publicKey, - } -} - -export async function queryPersonaEOAByPrivateKey(privateKeyString: string) { - const privateKey = decode(decodeArrayBuffer(privateKeyString)) - - if (!isEC_Private_JsonWebKey(privateKey) || !privateKey.d) throw new TypeError('Invalid private key') - const publicKey = omit(privateKey, 'd') as EC_Public_JsonWebKey - return { - address: bufferToHex(publicToAddress(privateToPublic(Buffer.from(fromBase64URL(privateKey.d))))), - identifier: (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap(), - publicKey, - } -} - -export async function queryPersonasFromNextID(platform: NextIDPlatform, identityResolved: IdentityResolved) { - if (!identityResolved.identifier) return - return NextIDProof.queryAllExistedBindingsByPlatform(platform, identityResolved.identifier.userId) -} - -export async function querySocialIdentity( - platform: NextIDPlatform, - identity: IdentityResolved | undefined, -): Promise { - if (!identity?.identifier) return - const persona = await queryPersonaByProfile(identity.identifier) - if (!persona) return identity - - const bindings = await queryPersonasFromNextID(platform, identity) - if (!bindings) return identity - - const personaBindings = - bindings?.filter((x) => x.persona === persona?.identifier.publicKeyAsHex.toLowerCase()) ?? [] - return { - ...identity, - publicKey: persona?.identifier.publicKeyAsHex, - hasBinding: personaBindings.length > 0, - binding: first(personaBindings), - } -} - -export { queryPersonaDB } from '../../../database/persona/db.js' diff --git a/packages/mask/background/services/identity/persona/sign.ts b/packages/mask/background/services/identity/persona/sign.ts deleted file mode 100644 index 89b8736bf733..000000000000 --- a/packages/mask/background/services/identity/persona/sign.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { timeout } from '@masknet/kit' -import { Signer } from '@masknet/web3-providers' -import { - type PersonaIdentifier, - fromBase64URL, - PopupRoutes, - type ECKeyIdentifier, - type SignType, - MaskMessages, -} from '@masknet/shared-base' -import { queryPersonasWithPrivateKey } from '../../../database/persona/web.js' -import { openPopupWindow } from '../../helper/popup-opener.js' - -/** - * Generate a signature w or w/o confirmation from user - */ -export async function signWithPersona( - type: SignType, - message: unknown, - identifier?: ECKeyIdentifier, - source?: string, - silent = false, -): Promise { - const getIdentifier = async () => { - if (!identifier || !silent) { - const requestID = uuid() - await openPopupWindow(PopupRoutes.PersonaSignRequest, { - message: JSON.stringify(message), - requestID, - identifier: identifier?.toText(), - source, - }) - - return timeout( - new Promise((resolve, reject) => { - MaskMessages.events.personaSignRequest.on((approval) => { - if (approval.requestID !== requestID) return - if (!approval.selectedPersona) - reject(new Error('The user refused to sign message with persona.')) - resolve(approval.selectedPersona!) - }) - }), - 60 * 1000, - 'Timeout of signing with persona.', - ) - } - return identifier - } - - const identifier_ = await getIdentifier() - - // find the persona with the signer's identifier - const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier_) - if (!persona?.privateKey.d) throw new Error('Persona not found') - - return Signer.sign(type, Buffer.from(fromBase64URL(persona.privateKey.d)), message) -} diff --git a/packages/mask/background/services/identity/persona/update.ts b/packages/mask/background/services/identity/persona/update.ts deleted file mode 100644 index 0b34aef4f1eb..000000000000 --- a/packages/mask/background/services/identity/persona/update.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { decodeArrayBuffer } from '@masknet/kit' -import { ECKeyIdentifier, isEC_Private_JsonWebKey, MaskMessages, type PersonaIdentifier } from '@masknet/shared-base' -import { decode } from '@msgpack/msgpack' -import { - consistentPersonaDBWriteAccess, - queryPersonaDB, - detachProfileDB, - deletePersonaDB, - safeDeletePersonaDB, - updatePersonaDB, - queryPersonasDB, -} from '../../../database/persona/db.js' -import { recover_ECDH_256k1_KeyPair_ByMnemonicWord, validateMnemonic } from './utils.js' - -export async function deletePersona(id: PersonaIdentifier, confirm: 'delete even with private' | 'safe delete') { - return consistentPersonaDBWriteAccess(async (t) => { - const d = await queryPersonaDB(id, t) - if (!d) return - for (const e of d.linkedProfiles) { - await detachProfileDB(e[0], t) - } - if (confirm === 'delete even with private') await deletePersonaDB(id, 'delete even with private', t) - else if (confirm === 'safe delete') await safeDeletePersonaDB(id, t) - }) -} - -async function loginPersona(identifier: PersonaIdentifier) { - return consistentPersonaDBWriteAccess((t) => - updatePersonaDB( - { identifier, hasLogout: false }, - { linkedProfiles: 'merge', explicitUndefinedField: 'ignore' }, - t, - ), - ) -} - -export async function logoutPersona(identifier: PersonaIdentifier) { - await consistentPersonaDBWriteAccess((t) => - updatePersonaDB( - { identifier, hasLogout: true }, - { linkedProfiles: 'merge', explicitUndefinedField: 'ignore' }, - t, - ), - ) - - MaskMessages.events.personasChanged.sendToAll() -} - -export async function setupPersona(id: PersonaIdentifier) { - return consistentPersonaDBWriteAccess(async (t) => { - const d = await queryPersonaDB(id, t) - if (!d) throw new Error('cannot find persona') - if (!d.privateKey) throw new Error('Cannot setup a persona without a private key') - if (d.linkedProfiles.size === 0) throw new Error('persona should link at least one profile') - if (d.uninitialized) { - await updatePersonaDB( - { identifier: id, uninitialized: false }, - { linkedProfiles: 'merge', explicitUndefinedField: 'ignore' }, - t, - ) - } - }) -} - -export async function loginExistPersonaByPrivateKey(privateKeyString: string): Promise { - const privateKey = decode(decodeArrayBuffer(privateKeyString)) - if (!isEC_Private_JsonWebKey(privateKey)) throw new TypeError('Invalid private key') - const identifier = (await ECKeyIdentifier.fromJsonWebKey(privateKey)).unwrap() - - const persona = await queryPersonaDB(identifier, undefined, true) - if (persona) { - await loginPersona(persona.identifier) - - return identifier - } - - return null -} - -export async function renamePersona(identifier: PersonaIdentifier, nickname: string): Promise { - const personas = await queryPersonasDB({ nameContains: nickname }) - if (personas.length > 0) throw new Error('Nickname already exists') - - return consistentPersonaDBWriteAccess((t) => - updatePersonaDB({ identifier, nickname }, { linkedProfiles: 'merge', explicitUndefinedField: 'ignore' }, t), - ) -} - -export async function queryPersonaByMnemonic(mnemonic: string, password: ''): Promise { - const verified = await validateMnemonic(mnemonic) - if (!verified) throw new Error('Verify error') - - const { key } = await recover_ECDH_256k1_KeyPair_ByMnemonicWord(mnemonic, password) - const identifier = (await ECKeyIdentifier.fromJsonWebKey(key.privateKey)).unwrap() - const persona = await queryPersonaDB(identifier, undefined, true) - if (persona) { - await loginPersona(persona.identifier) - return persona.identifier - } - - return null -} diff --git a/packages/mask/background/services/identity/persona/utils.ts b/packages/mask/background/services/identity/persona/utils.ts deleted file mode 100644 index 06bd29c857ce..000000000000 --- a/packages/mask/background/services/identity/persona/utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as bip39 from 'bip39' -import * as wallet from /* webpackDefer: true */ 'wallet.ts' -import { encodeArrayBuffer, encodeText } from '@masknet/kit' -import { - type EC_Private_JsonWebKey, - type EC_Public_JsonWebKey, - type JsonWebKeyPair, - toBase64URL, - decompressK256Key, - type AESCryptoKey, - type AESJsonWebKey, - isEC_Private_JsonWebKey, -} from '@masknet/shared-base' -import { CryptoKeyToJsonWebKey } from '../../../../utils-pure/index.js' -import type { PersonaRecord } from '../../../database/persona/db.js' - -/** - * Local key (AES key) is used to encrypt message to myself. - * This key should never be published. - */ - -export async function deriveLocalKeyFromECDHKey( - pub: EC_Public_JsonWebKey, - mnemonicWord: string, -): Promise { - // ? Derive method: publicKey as "password" and password for the mnemonicWord as hash - const pbkdf2 = await crypto.subtle.importKey('raw', encodeText(pub.x! + pub.y!), 'PBKDF2', false, [ - 'deriveBits', - 'deriveKey', - ]) - const aes = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: encodeText(mnemonicWord), iterations: 100000, hash: 'SHA-256' }, - pbkdf2, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'], - ) - return CryptoKeyToJsonWebKey(aes as AESCryptoKey) -} - -// Private key at m/44'/coinType'/account'/change/addressIndex -// coinType = ether -const path = "m/44'/60'/0'/0/0" - -type MnemonicGenerationInformation = { - key: JsonWebKeyPair - password: string - mnemonicRecord: NonNullable -} - -export async function recover_ECDH_256k1_KeyPair_ByMnemonicWord( - mnemonicWord: string, - password: string, -): Promise { - const verify = bip39.validateMnemonic(mnemonicWord) - if (!verify) { - console.warn('Verify error') - } - const seed = await bip39.mnemonicToSeed(mnemonicWord, password) - const masterKey = wallet.HDKey.parseMasterSeed(seed) - const derivedKey = masterKey.derive(path) - const key = await split_ec_k256_key_pair_into_pub_priv(await HDKeyToJwk(derivedKey)) - return { - key, - password, - mnemonicRecord: { - parameter: { path, withPassword: password.length > 0 }, - words: mnemonicWord, - }, - } -} - -export async function validateMnemonic(mnemonic: string, wordList?: string[] | undefined): Promise { - return bip39.validateMnemonic(mnemonic, wordList) -} - -async function HDKeyToJwk(hdk: wallet.HDKey): Promise { - const jwk = await decompressK256Key(encodeArrayBuffer(hdk.publicKey)) - jwk.d = hdk.privateKey ? toBase64URL(hdk.privateKey) : undefined - return jwk -} - -async function split_ec_k256_key_pair_into_pub_priv( - key: Readonly, -): Promise> { - if (!isEC_Private_JsonWebKey(key)) throw new TypeError('Not a EC private key') - const { d, ...pub } = key - // @ts-expect-error Do a force transform - return { privateKey: { ...key }, publicKey: pub } -} diff --git a/packages/mask/background/services/identity/profile/query.ts b/packages/mask/background/services/identity/profile/query.ts deleted file mode 100644 index 74bc2e680c22..000000000000 --- a/packages/mask/background/services/identity/profile/query.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { ProfileIdentifier, ProfileInformation } from '@masknet/shared-base' -import { - createPersonaDBReadonlyAccess, - type ProfileRecord, - queryPersonasDB, - queryProfilesDB, - queryProfileDB, -} from '../../../database/persona/db.js' -import { hasLocalKeyOf } from '../../../database/persona/helper.js' -import { toProfileInformation } from '../../__utils__/convert.js' - -export async function queryProfilesInformation(identifiers: ProfileIdentifier[]): Promise { - const profiles = await queryProfilesDB({ identifiers }) - return toProfileInformation(profiles).mustNotAwaitThisWithInATransaction -} - -export async function queryProfileInformation(identifier: ProfileIdentifier): Promise { - const profile = await queryProfileDB(identifier) - return toProfileInformation(profile ? [profile] : []).mustNotAwaitThisWithInATransaction -} - -/** @deprecated */ -export async function hasLocalKey(identifier: ProfileIdentifier) { - return hasLocalKeyOf(identifier) -} - -export async function queryOwnedProfilesInformation(network?: string): Promise { - let profiles: ProfileRecord[] - await createPersonaDBReadonlyAccess(async (t) => { - const personas = (await queryPersonasDB({ hasPrivateKey: true }, t)).sort((a, b) => - a.updatedAt > b.updatedAt ? 1 : -1, - ) - const ids = Array.from(new Set(personas.flatMap((x) => [...x.linkedProfiles.keys()]))) - profiles = await queryProfilesDB({ identifiers: ids, network }, t) - }) - return toProfileInformation(profiles!.filter((x) => x.identifier.network === network)) - .mustNotAwaitThisWithInATransaction -} diff --git a/packages/mask/background/services/identity/profile/update.ts b/packages/mask/background/services/identity/profile/update.ts deleted file mode 100644 index c6c1a2416e80..000000000000 --- a/packages/mask/background/services/identity/profile/update.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - decompressK256Key, - type ECKeyIdentifier, - NextIDAction, - type PersonaIdentifier, - ProfileIdentifier, - type ProfileInformationFromNextID, - RelationFavor, - MaskMessages, -} from '@masknet/shared-base' -import { NextIDProof } from '@masknet/web3-providers' -import { storeAvatar } from '../../../database/avatar-cache/avatar.js' -import { - attachProfileDB, - consistentPersonaDBWriteAccess, - createOrUpdateProfileDB, - createProfileDB, - deleteProfileDB, - detachProfileDB, - type LinkedProfileDetails, - type PersonaRecord, - type ProfileRecord, - queryProfileDB, - queryProfilesDB, -} from '../../../database/persona/db.js' -import { createOrUpdatePersonaDB, createOrUpdateRelationDB } from '../../../database/persona/web.js' - -interface UpdateProfileInfo { - nickname?: string | null - avatarURL?: ArrayBuffer | string | null -} -export async function updateProfileInfo(identifier: ProfileIdentifier, data: UpdateProfileInfo): Promise { - if (data.nickname) { - const rec: ProfileRecord = { - identifier, - nickname: data.nickname, - createdAt: new Date(), - updatedAt: new Date(), - } - await consistentPersonaDBWriteAccess((t) => createOrUpdateProfileDB(rec, t)) - } - if (data.avatarURL) await storeAvatar(identifier, data.avatarURL) -} - -export async function detachProfileWithNextID( - uuid: string, - personaPublicKey: string, - platform: string, - identity: string, - createdAt: string, - options?: { - signature?: string - }, -): Promise { - await NextIDProof.bindProof(uuid, personaPublicKey, NextIDAction.Delete, platform, identity, createdAt, { - signature: options?.signature, - }) - MaskMessages.events.ownProofChanged.sendToAll(undefined) -} -const err = 'resolveUnknownLegacyIdentity should not be called on localhost' -/** - * In older version of Mask, identity is marked as `ProfileIdentifier(network, '$unknown')` or `ProfileIdentifier(network, '$self')`. After upgrading to the newer version of Mask, Mask will try to find the current user in that network and call this function to replace old identifier into a "resolved" identity. - * @param identifier The resolved identity - */ -export async function resolveUnknownLegacyIdentity(identifier: ProfileIdentifier): Promise { - const unknown = ProfileIdentifier.of(identifier.network, '$unknown').expect(err) - const self = ProfileIdentifier.of(identifier.network, '$self').expect(err) - - const records = await queryProfilesDB({ identifiers: [unknown, self] }) - if (!records.length) return - const finalRecord: ProfileRecord = Object.assign({}, ...records, { identifier }) - try { - await consistentPersonaDBWriteAccess(async (t) => { - await createProfileDB(finalRecord, t) - await deleteProfileDB(unknown, t).catch(() => {}) - await deleteProfileDB(self, t).catch(() => {}) - }) - } catch { - // the profile already exists - } -} - -/** - * Remove an identity. - */ -export async function attachProfile( - source: ProfileIdentifier, - target: ProfileIdentifier | PersonaIdentifier, - data: LinkedProfileDetails, -): Promise { - if (target instanceof ProfileIdentifier) { - const profile = await queryProfileDB(target) - if (!profile?.linkedPersona) throw new Error('target not found') - target = profile.linkedPersona - } - return attachProfileDB(source, target, data) -} -export function detachProfile(identifier: ProfileIdentifier): Promise { - return detachProfileDB(identifier) -} - -/** - * Set NextID profile to profileDB - * */ - -export async function attachNextIDPersonaToProfile(item: ProfileInformationFromNextID, whoAmI: ECKeyIdentifier) { - if (!item.linkedPersona) throw new Error('LinkedPersona Not Found') - const now = new Date() - const personaRecord: PersonaRecord = { - createdAt: now, - updatedAt: now, - identifier: item.linkedPersona, - linkedProfiles: new Map(), - publicKey: await decompressK256Key(item.linkedPersona.rawPublicKey), - publicHexKey: item.linkedPersona.publicKeyAsHex, - nickname: item.nickname, - hasLogout: false, - uninitialized: false, - } - - const profileRecord: ProfileRecord = { - identifier: item.identifier, - nickname: item.nickname, - linkedPersona: item.linkedPersona, - createdAt: now, - updatedAt: now, - } - try { - await consistentPersonaDBWriteAccess(async (t) => { - await createOrUpdatePersonaDB( - personaRecord, - { explicitUndefinedField: 'ignore', linkedProfiles: 'merge' }, - t, - ) - await createOrUpdateProfileDB(profileRecord, t) - await attachProfileDB( - profileRecord.identifier, - item.linkedPersona!, - { connectionConfirmState: 'confirmed' }, - t, - ) - await createOrUpdateRelationDB( - { - profile: profileRecord.identifier, - linked: whoAmI, - favor: RelationFavor.UNCOLLECTED, - }, - t, - ) - }) - } catch { - // already exist - } -} diff --git a/packages/mask/background/services/identity/relation/create.ts b/packages/mask/background/services/identity/relation/create.ts deleted file mode 100644 index 90ceaba5135e..000000000000 --- a/packages/mask/background/services/identity/relation/create.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { RelationFavor } from '@masknet/public-api' -import type { ProfileIdentifier, PersonaIdentifier } from '@masknet/shared-base' -import { createRelationsTransaction, createRelationDB } from '../../../database/persona/db.js' - -export async function createNewRelation( - profile: ProfileIdentifier | PersonaIdentifier, - linked: PersonaIdentifier, - favor = RelationFavor.UNCOLLECTED, -): Promise { - const t = await createRelationsTransaction() - const relationInDB = await t.objectStore('relations').get([linked.toText(), profile.toText()]) - if (relationInDB) return - - await createRelationDB({ profile, linked, favor }, t) -} diff --git a/packages/mask/background/services/identity/relation/query.ts b/packages/mask/background/services/identity/relation/query.ts deleted file mode 100644 index 9720269247ff..000000000000 --- a/packages/mask/background/services/identity/relation/query.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { PersonaIdentifier } from '@masknet/shared-base' -import { queryRelationsPagedDB, type RelationRecord } from '../../../database/persona/db.js' - -interface QueryRelationPagedOptions { - network: string - after?: RelationRecord - pageOffset?: number -} - -export async function queryRelationPaged( - currentPersona: PersonaIdentifier | undefined | null, - options: QueryRelationPagedOptions, - count: number, -): Promise { - if (!currentPersona) return [] - return queryRelationsPagedDB(currentPersona, options, count) -} diff --git a/packages/mask/background/services/identity/relation/update.ts b/packages/mask/background/services/identity/relation/update.ts deleted file mode 100644 index da9eb42bcc0b..000000000000 --- a/packages/mask/background/services/identity/relation/update.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { PersonaIdentifier, ProfileIdentifier, RelationFavor } from '@masknet/shared-base' -import { createRelationsTransaction, deletePersonaRelationDB, updateRelationDB } from '../../../database/persona/db.js' - -export async function updateRelation( - profile: ProfileIdentifier | PersonaIdentifier, - linked: PersonaIdentifier, - favor: RelationFavor, -) { - const t = await createRelationsTransaction() - await updateRelationDB({ profile, linked, favor }, t) -} - -export async function deletePersonaRelation(persona: PersonaIdentifier, linked: PersonaIdentifier) { - const t = await createRelationsTransaction() - await deletePersonaRelationDB(persona, linked, t) -} diff --git a/packages/mask/background/services/settings/index.ts b/packages/mask/background/services/settings/index.ts deleted file mode 100644 index f8b89ab875e1..000000000000 --- a/packages/mask/background/services/settings/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './old-settings-accessor.js' -export { __kv_storage_read__, __kv_storage_write__ } from './kv-storage.js' diff --git a/packages/mask/background/services/settings/kv-storage.ts b/packages/mask/background/services/settings/kv-storage.ts deleted file mode 100644 index 742e4692057d..000000000000 --- a/packages/mask/background/services/settings/kv-storage.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { indexedDB_KVStorageBackend, inMemory_KVStorageBackend } from '../../initialization/kv-storage.js' - -export async function __kv_storage_write__(kind: 'indexedDB' | 'memory', key: string, value: unknown) { - if (kind === 'memory') { - return inMemory_KVStorageBackend.setValue(key, value) - } else { - return indexedDB_KVStorageBackend.setValue(key, value) - } -} - -export async function __kv_storage_read__(kind: 'indexedDB' | 'memory', key: string) { - if (kind === 'memory') { - return inMemory_KVStorageBackend.getValue(key) - } else { - return indexedDB_KVStorageBackend.getValue(key) - } -} diff --git a/packages/mask/background/services/settings/old-settings-accessor.ts b/packages/mask/background/services/settings/old-settings-accessor.ts deleted file mode 100644 index fb5d009a0c87..000000000000 --- a/packages/mask/background/services/settings/old-settings-accessor.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { forIn } from 'lodash-es' -import { telemetrySettings } from '@masknet/web3-telemetry' -import { - currentPersonaIdentifier, - getCurrentPluginMinimalMode, - languageSettings, - MaskMessages, - setCurrentPluginMinimalMode, - type PersonaIdentifier, - type ValueRefWithReady, - appearanceSettings, - BooleanPreference, - InjectSwitchSettings, - EnhanceableSite, -} from '@masknet/shared-base' -import { queryPersonasDB } from '../../database/persona/web.js' -import { getPluginDefine } from '@masknet/plugin-infra' -import { unreachable } from '@masknet/kit' - -function create(settings: ValueRefWithReady) { - async function get() { - await settings.readyPromise - return settings.value - } - async function set(val: T) { - await settings.readyPromise - settings.value = val - } - return [get, set] as const -} -export const [isTelemetryEnabled, setTelemetryEnabled] = create(telemetrySettings) -export const [getTheme, setTheme] = create(appearanceSettings) -export const [getLanguage, setLanguage] = create(languageSettings) - -export async function getCurrentPersonaIdentifier(): Promise { - await currentPersonaIdentifier.readyPromise - const personas = (await queryPersonasDB({ hasPrivateKey: true })) - .sort((a, b) => (a.createdAt > b.createdAt ? 1 : 0)) - .map((x) => x.identifier) - const newVal = currentPersonaIdentifier.value || personas[0] - if (!newVal) return - if (personas.find((x) => x === newVal)) return newVal - if (personas[0]) currentPersonaIdentifier.value = personas[0] - return personas[0] -} -export async function setCurrentPersonaIdentifier(x?: PersonaIdentifier) { - await currentPersonaIdentifier.readyPromise - currentPersonaIdentifier.value = x - MaskMessages.events.ownPersonaChanged.sendToAll(undefined) -} -export async function getPluginMinimalModeEnabled(id: string): Promise { - return getCurrentPluginMinimalMode(id) -} -/** - * Return a resolved result of getPluginMinimalModeEnabled. - * If getPluginMinimalModeEnabled(id) returns BooleanPreference.Default, - * this function will resolve it to true or false based on the plugin default. - */ -export async function getPluginMinimalModeEnabledResolved(id: string): Promise { - const result = getCurrentPluginMinimalMode(id) - if (result === BooleanPreference.True) return true - if (result === BooleanPreference.False) return false - if (result === BooleanPreference.Default) return !!getPluginDefine(id)?.inMinimalModeByDefault - unreachable(result) -} -export async function setPluginMinimalModeEnabled(id: string, enabled: boolean) { - setCurrentPluginMinimalMode(id, enabled ? BooleanPreference.True : BooleanPreference.False) - - MaskMessages.events.pluginMinimalModeChanged.sendToAll([id, enabled]) -} - -export async function getAllInjectSwitchSettings() { - const result = {} as Record - forIn(EnhanceableSite, (value) => { - result[value] = InjectSwitchSettings[value].value - }) - return result -} - -export async function setInjectSwitchSetting(network: string, value: boolean) { - InjectSwitchSettings[network].value = value -} - -export { __deprecated__getStorage as getLegacySettingsInitialValue } from '../../utils/deprecated-storage.js' diff --git a/packages/mask/background/services/setup.ts b/packages/mask/background/services/setup.ts deleted file mode 100644 index 3621f250baa8..000000000000 --- a/packages/mask/background/services/setup.ts +++ /dev/null @@ -1,96 +0,0 @@ -// Notice, this module itself is not HMR ready. -// If you change this file to add a new service, you need to reload. -// This file should not rely on any other in-project files unless it is HMR ready. -/// - -import { AsyncCall, AsyncGeneratorCall } from 'async-call-rpc/full' -import { assertEnvironment, Environment, MessageTarget, WebExtensionMessage } from '@dimensiondev/holoflows-kit' -import { getOrUpdateLocalImplementationHMR, encoder, setDebugObject } from '@masknet/shared-base' -import type { GeneratorServices, Services } from './types.js' - -import { decryptWithDecoding } from './crypto/decryption.js' -assertEnvironment(Environment.ManifestBackground) - -const debugMode = process.env.NODE_ENV === 'development' -const message = new WebExtensionMessage>({ domain: '$' }) -const hmr = new EventTarget() - -const DebugService = Object.create(null) -export function startServices() { - setup('Crypto', () => import(/* webpackMode: 'eager' */ './crypto/index.js')) - setup('Identity', () => import(/* webpackMode: 'eager' */ './identity/index.js')) - setup('Backup', () => import(/* webpackMode: 'eager' */ './backup/index.js')) - setup('Helper', () => import(/* webpackMode: 'eager' */ './helper/index.js')) - setup('SiteAdaptor', () => import(/* webpackMode: 'eager' */ './site-adaptors/index.js')) - setup('Settings', () => import(/* webpackMode: 'eager' */ './settings/index.js'), false) - setup('Wallet', () => import(/* webpackMode: 'eager' */ './wallet/services/index.js')) - if (import.meta.webpackHot) { - import.meta.webpackHot.accept(['./crypto'], () => hmr.dispatchEvent(new Event('Crypto'))) - import.meta.webpackHot.accept(['./identity'], () => hmr.dispatchEvent(new Event('Identity'))) - import.meta.webpackHot.accept(['./backup'], () => hmr.dispatchEvent(new Event('Backup'))) - import.meta.webpackHot.accept(['./helper'], () => hmr.dispatchEvent(new Event('Helper'))) - import.meta.webpackHot.accept(['./settings'], () => hmr.dispatchEvent(new Event('Settings'))) - import.meta.webpackHot.accept(['./site-adaptors'], () => hmr.dispatchEvent(new Event('SiteAdaptor'))) - import.meta.webpackHot.accept(['./wallet/services'], () => hmr.dispatchEvent(new Event('Wallet'))) - } - setDebugObject('Service', DebugService) - - const GeneratorService: GeneratorServices = { - decrypt: decryptWithDecoding, - } - import.meta.webpackHot?.accept(['./crypto/decryption'], async () => { - GeneratorService.decrypt = ( - await import(/* webpackMode: 'eager' */ './crypto/decryption.js') - ).decryptWithDecoding - }) - const channel = message.events.GeneratorServices.bind(MessageTarget.Broadcast) - setDebugObject('GeneratorService', GeneratorService) - - AsyncGeneratorCall(GeneratorService, { - key: 'GeneratorService', - encoder, - channel: { - on: (c) => channel.on((d) => c(d)), - send: (d) => channel.send(d), - }, - log: { - beCalled: false, - remoteError: false, - type: 'pretty', - requestReplay: false, - }, - preferLocalImplementation: true, - thenable: false, - }) -} - -function setup(key: K, implementation: () => Promise, hasLog = true) { - const channel = message.events[key].bind(MessageTarget.Broadcast) - - async function load() { - const val = await getOrUpdateLocalImplementationHMR(implementation, channel) - DebugService[key] = val - return val - } - if (import.meta.webpackHot) hmr.addEventListener(key, load) - - // setup server - AsyncCall(load(), { - key, - encoder, - channel: { - on: (c) => channel.on((d) => c(d)), - send: (d) => channel.send(d), - }, - log: - hasLog ? - { - beCalled: true, - remoteError: false, - type: 'pretty', - requestReplay: debugMode, - } - : false, - thenable: false, - }) -} diff --git a/packages/mask/background/services/site-adaptors/connect.ts b/packages/mask/background/services/site-adaptors/connect.ts deleted file mode 100644 index f283040a2264..000000000000 --- a/packages/mask/background/services/site-adaptors/connect.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { compact, first, sortBy } from 'lodash-es' -import stringify from 'json-stable-stringify' -import { delay } from '@masknet/kit' -import { - type PersonaIdentifier, - type ProfileIdentifier, - currentSetupGuideStatus, - SetupGuideStep, -} from '@masknet/shared-base' -import { definedSiteAdaptors } from '../../../shared/site-adaptors/definitions.js' -import type { SiteAdaptor } from '../../../shared/site-adaptors/types.js' -import type { Tabs } from 'webextension-polyfill' - -async function hasPermission(origin: string): Promise { - return browser.permissions.contains({ - origins: [origin], - }) -} - -interface SitesQueryOptions { - isSocialNetwork?: boolean -} - -export async function getSupportedSites(options: SitesQueryOptions = {}): Promise< - Array<{ - networkIdentifier: string - }> -> { - return sortBy( - [...definedSiteAdaptors.values()].filter((x) => - options.isSocialNetwork === undefined ? true : x.isSocialNetwork === options.isSocialNetwork, - ), - (x) => x.sortIndex, - ).map((x) => ({ networkIdentifier: x.networkIdentifier })) -} - -export async function getSupportedOrigins(options: SitesQueryOptions = {}): Promise< - Array<{ - networkIdentifier: string - origins: string[] - }> -> { - return sortBy([...definedSiteAdaptors.values()], (x) => x.sortIndex) - .filter((x) => (options.isSocialNetwork === undefined ? true : x.isSocialNetwork === options.isSocialNetwork)) - .map((x) => ({ networkIdentifier: x.networkIdentifier, origins: [...x.declarativePermissions.origins] })) -} -export async function getOriginsWithoutPermission(options: SitesQueryOptions = {}): Promise< - Array<{ - networkIdentifier: string - origins: string[] - }> -> { - const groups = await getSupportedOrigins(options) - const promises = groups.map(async ({ origins, networkIdentifier }) => { - const unGrantedOrigins = compact( - await Promise.all(origins.map((origin) => hasPermission(origin).then((yes) => (yes ? null : origin)))), - ) - if (!unGrantedOrigins.length) return null - return { - networkIdentifier, - origins: compact(unGrantedOrigins), - } - }) - return compact(await Promise.all(promises)) -} - -export async function getAllOrigins() { - const groups = await getSupportedOrigins() - const promises = groups.map(async ({ origins, networkIdentifier }) => { - const originsWithNoPermission = compact( - await Promise.all(origins.map((origin) => hasPermission(origin).then((yes) => (yes ? null : origin)))), - ) - return { - networkIdentifier, - hasPermission: !originsWithNoPermission.length, - } - }) - - return Promise.all(promises) -} - -export async function getSitesWithoutPermission(): Promise { - const groups = [...definedSiteAdaptors.values()] - const promises = groups.map(async (x) => { - const origins = x.declarativePermissions.origins - const unGrantedOrigins = compact( - await Promise.all(origins.map((origin) => hasPermission(origin).then((yes) => (yes ? null : origin)))), - ) - if (!unGrantedOrigins.length) return null - return x - }) - return compact(await Promise.all(promises)) -} - -/** - * It's caller's responsibility to call browser.permissions.request to get the permissions needed. - * @param identifier Persona - * @param network Network to connect - * @param profile Profile - * @param openInNewTab Open in new tab - */ -export async function connectSite( - identifier: PersonaIdentifier, - network: string, - profile?: ProfileIdentifier, - openInNewTab = true, -) { - const site = definedSiteAdaptors.get(network) - if (!site) return - - const url = site.homepage - if (!url) return - - let targetTab: Tabs.Tab | undefined - if (openInNewTab) { - targetTab = await browser.tabs.create({ active: true, url: site.homepage }) - } else { - const openedTabs = await browser.tabs.query({ url: `${url}/*` }) - targetTab = openedTabs.find((x: { active: boolean }) => x.active) ?? first(openedTabs) - - if (!targetTab?.id || !targetTab.windowId) { - await browser.tabs.create({ active: true, url }) - } - } - await delay(100) - if (!targetTab?.windowId) return - await browser.tabs.update(targetTab.id, { active: true }) - await browser.windows.update(targetTab.windowId, { focused: true }) - currentSetupGuideStatus[network].value = stringify({ - status: SetupGuideStep.VerifyOnNextID, - persona: identifier.toText(), - username: profile?.userId, - tabId: targetTab.id, - }) -} diff --git a/packages/mask/background/services/site-adaptors/index.ts b/packages/mask/background/services/site-adaptors/index.ts deleted file mode 100644 index f9d80e90663c..000000000000 --- a/packages/mask/background/services/site-adaptors/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { - getSupportedSites, - getSupportedOrigins, - getOriginsWithoutPermission, - getSitesWithoutPermission, - getAllOrigins, - connectSite, -} from './connect.js' -export { attachMaskSDKToCurrentActivePage, shouldSuggestConnectInPopup } from './sdk.js' diff --git a/packages/mask/background/services/site-adaptors/sdk.ts b/packages/mask/background/services/site-adaptors/sdk.ts deleted file mode 100644 index 417fb3a4742a..000000000000 --- a/packages/mask/background/services/site-adaptors/sdk.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { maskSDK_URL, injectUserScriptMV2, evaluateContentScript } from '../../utils/injectScript.js' - -export async function attachMaskSDKToCurrentActivePage(): Promise { - if (browser.scripting) { - const [{ id }] = await browser.tabs.query({ active: true }) - if (!id) return false - await Promise.all([attachMaskSDK3(id), evaluateContentScript(id)]) - } else if (browser.tabs) { - await Promise.all([attachMaskSDK2(), evaluateContentScript(undefined)]) - } - return true -} - -async function attachMaskSDK2() { - await browser.tabs.executeScript(undefined, { - code: await injectUserScriptMV2(maskSDK_URL), - }) -} -async function attachMaskSDK3(id: number) { - const [{ error }] = await browser.scripting.executeScript({ - target: { tabId: id }, - files: [maskSDK_URL], - // @ts-expect-error Chrome API - world: 'MAIN', - }) - if (error) throw error -} -export async function developmentMaskSDKReload(): Promise { - if (process.env.NODE_ENV !== 'development') return - - if (browser.scripting) { - const [{ id }] = await browser.tabs.query({ active: true }) - if (!id) return - await attachMaskSDK3(id) - } else if (browser.tabs) { - await attachMaskSDK2() - } -} - -export async function shouldSuggestConnectInPopup(url?: string): Promise { - if (!url) { - const tabs = await browser.tabs.query({ active: true }) - if (!tabs.length) return false - url = tabs[0].url - } - if (!url) return false - return canInject(url) && !(await browser.permissions.contains({ origins: [new URL(url).origin + '/*'] })) -} - -function canInject(url: string) { - if (url.startsWith('http://localhost:')) return true - if (url.startsWith('http://localhost/')) return true - if (url.startsWith('http://127.0.0.1')) return true - if (url.startsWith('https://')) return true - return false -} diff --git a/packages/mask/background/services/types.ts b/packages/mask/background/services/types.ts deleted file mode 100644 index d1f284b76e9e..000000000000 --- a/packages/mask/background/services/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type * as Crypto from './crypto/index.js' -import type { decryptWithDecoding } from './crypto/decryption.js' -import type * as Helper from './helper/index.js' -import type * as Backup from './backup/index.js' -import type * as Identity from './identity/index.js' -import type * as Settings from './settings/index.js' -import type * as SiteAdaptor from './site-adaptors/index.js' -import type * as Wallet from './wallet/services/index.js' - -export type CryptoService = typeof Crypto -export type IdentityService = typeof Identity -export type BackupService = typeof Backup -export type HelperService = typeof Helper -export type SettingsService = typeof Settings -export type SiteAdaptorService = typeof SiteAdaptor -export type WalletService = typeof Wallet -export interface Services { - Crypto: CryptoService - Identity: IdentityService - Backup: BackupService - Helper: HelperService - Settings: SettingsService - SiteAdaptor: SiteAdaptorService - Wallet: WalletService -} -export type GeneratorServices = { - decrypt: typeof decryptWithDecoding -} diff --git a/packages/mask/background/services/wallet/database/Plugin.db.ts b/packages/mask/background/services/wallet/database/Plugin.db.ts deleted file mode 100644 index a4f6622ebf81..000000000000 --- a/packages/mask/background/services/wallet/database/Plugin.db.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { LockerRecord } from '../services/wallet/database/locker.js' -import type { SecretRecord, WalletRecord } from '../services/wallet/type.js' -import { createPluginDatabase } from '../../../database/plugin-db/wrap-plugin-database.js' -import { PluginID } from '@masknet/shared-base' -import type { WalletGrantedPermission, InternalWalletConnectRecord } from './types.js' - -// Note: Wallet was a plugin in the past, but now it's a core service in Mask. -export const walletDatabase = createPluginDatabase< - WalletRecord | SecretRecord | LockerRecord | WalletGrantedPermission | InternalWalletConnectRecord ->(PluginID.Wallet) diff --git a/packages/mask/background/services/wallet/database/Wallet.db.ts b/packages/mask/background/services/wallet/database/Wallet.db.ts deleted file mode 100644 index ad56fc1111d1..000000000000 --- a/packages/mask/background/services/wallet/database/Wallet.db.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { type DBSchema, openDB } from 'idb/with-async-ittr' -import { createDBAccess } from '../../../database/utils/openDB.js' -import type { LegacyWalletRecordInDatabase, UnconfirmedRequestChunkRecordInDatabase } from './types.js' - -function path(x: T) { - return x -} - -export const createWalletDBAccess = createDBAccess(() => { - return openDB('maskbook-plugin-wallet', 9, { - async upgrade(db, oldVersion, newVersion, tx) { - function v0_v1() { - db.createObjectStore('Wallet', { keyPath: path('address') }) - } - function v8_v9() { - const pluginStore = 'PluginStore' - db.objectStoreNames.contains(pluginStore as any) && db.deleteObjectStore(pluginStore as any) - db.createObjectStore('UnconfirmedRequestChunk', { - keyPath: path('record_id'), - }) - } - - if (oldVersion < 1) v0_v1() - if (oldVersion < 9) v8_v9() - }, - }) -}) - -interface WalletDB extends DBSchema { - Wallet: { - value: LegacyWalletRecordInDatabase - key: string - } - UnconfirmedRequestChunk: { - value: UnconfirmedRequestChunkRecordInDatabase - key: string - } -} diff --git a/packages/mask/background/services/wallet/database/types.ts b/packages/mask/background/services/wallet/database/types.ts deleted file mode 100644 index 582f3d977764..000000000000 --- a/packages/mask/background/services/wallet/database/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { JsonRpcPayload } from 'web3-core-helpers' -import type { EIP2255Permission } from '@masknet/sdk' -import type { LegacyWalletRecord } from '@masknet/shared-base' - -export interface RequestPayload extends JsonRpcPayload { - owner?: string - identifier?: string - paymentToken?: string - allowMaskAsGas?: boolean -} -interface UnconfirmedRequestChunkRecord { - /** A chunk of unconfirmed rpc requests */ - requests: RequestPayload[] - createdAt: Date - updatedAt: Date -} - -export interface LegacyWalletRecordInDatabase extends LegacyWalletRecord {} - -export interface UnconfirmedRequestChunkRecordInDatabase extends UnconfirmedRequestChunkRecord { - record_id: string -} - -export interface WalletGrantedPermission { - type: 'granted_permission' - id: string - origins: ReadonlyMap> -} - -export interface InternalWalletConnectRecord { - type: 'internal_connected' - id: string - origins: ReadonlySet -} diff --git a/packages/mask/background/services/wallet/services/connect.ts b/packages/mask/background/services/wallet/services/connect.ts deleted file mode 100644 index 3e320ed78f96..000000000000 --- a/packages/mask/background/services/wallet/services/connect.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { - type EIP2255PermissionRequest, - type EIP2255Permission, - type EIP2255RequestedPermission, - type MaskEthereumProviderRpcError, - err, -} from '@masknet/sdk' -import { walletDatabase } from '../database/Plugin.db.js' -import { produce, enableMapSet } from 'immer' -import { ChainId } from '@masknet/web3-shared-evm' -import { openPopupWindow } from '../../helper/popup-opener.js' -import { PopupRoutes } from '@masknet/shared-base' -import { defer, type DeferTuple } from '@masknet/kit' -import type { WalletGrantedPermission } from '../database/types.js' -import { omit } from 'lodash-es' -import { Err, Ok, type Result } from 'ts-results-es' - -// https://eips.ethereum.org/EIPS/eip-2255 -export async function sdk_EIP2255_wallet_getPermissions(origin: string): Promise { - const wallets = await getAllConnectedWallets(origin, 'sdk') - if (!wallets.size) return [] - return EIP2255PermissionsOfWallets(origin, wallets) -} -const requests = new Map< - string, - { - origin: string - request: EIP2255PermissionRequest - promise: DeferTuple> - } ->() -export async function sdk_EIP2255_wallet_requestPermissions( - origin: string, - request: EIP2255PermissionRequest, -): Promise> { - assertOrigin(origin) - for (const method in request) { - if (method !== 'eth_accounts') { - throw err.wallet_requestPermissions.permission_request_contains_unsupported_permission_permission({ - permission: method, - }) - } - } - const id = Math.random().toString(36).slice(2) - requests.set(id, { - origin, - request, - promise: defer(), - }) - - await openPopupWindow(PopupRoutes.SelectWallet, { - chainId: ChainId.Mainnet, - external_request: id, - }) - return requests.get(id)!.promise[0] -} -export async function sdk_getEIP2255PermissionRequestDetail(id: string) { - return omit(requests.get(id), 'promise') -} -export async function sdk_grantEIP2255Permission(id: string, grantedWalletAddress: Iterable) { - if (!requests.has(id)) throw new Error('Invalid request id') - const { origin, promise } = requests.get(id)! - enableMapSet() - for (const wallet of grantedWalletAddress) { - const data = await walletDatabase.get('granted_permission', wallet) - const newData = produce( - data || { - type: 'granted_permission', - id: wallet, - origins: new Map(), - }, - (draft) => { - if (!draft.origins.has(origin)) draft.origins.set(origin, new Set()) - const permissions = draft.origins.get(origin)! - if (Array.from(permissions).some((data) => hasEthAccountsPermission(origin, data))) return - permissions.add({ - invoker: origin, - parentCapability: 'eth_accounts', - caveats: [], - }) - }, - ) - if (data !== newData) await walletDatabase.add(newData) - } - promise[1](Ok(EIP2255PermissionsOfWallets(origin, grantedWalletAddress))) -} - -export async function sdk_denyEIP2255Permission(id: string) { - if (!requests.has(id)) throw new Error('Invalid request id') - const { promise } = requests.get(id)! - enableMapSet() - promise[1](Err(err.user_rejected_the_request())) -} - -export async function disconnectWalletFromOrigin(wallet: string, origin: string, type: 'any' | 'sdk' | 'internal') { - assertOrigin(origin) - if (type === 'any' || type === 'sdk') { - const origins = new Map((await walletDatabase.get('granted_permission', wallet))?.origins) - if (origins.has(origin)) { - origins.delete(origin) - if (origins.size) await walletDatabase.add({ type: 'granted_permission', id: wallet, origins }) - else await walletDatabase.remove('granted_permission', wallet) - } - } - if (type === 'any' || type === 'internal') { - const internalOrigins = new Set((await walletDatabase.get('internal_connected', wallet))?.origins) - if (internalOrigins.has(origin)) { - internalOrigins.delete(origin) - if (internalOrigins.size) - await walletDatabase.add({ type: 'internal_connected', id: wallet, origins: internalOrigins }) - else await walletDatabase.remove('internal_connected', wallet) - } - } -} -export async function disconnectAllWalletsFromOrigin(origin: string, type: 'any' | 'sdk' | 'internal') { - assertOrigin(origin) - enableMapSet() - if (type === 'any' || type === 'sdk') { - for await (const cursor of walletDatabase.iterate_mutate('granted_permission')) { - if (!cursor.value.origins.has(origin)) continue - if (cursor.value.origins.size === 1) await cursor.delete() - else { - await cursor.update( - produce(cursor.value, (draft) => { - draft.origins.delete(origin) - }), - ) - } - } - } - if (type === 'any' || type === 'internal') { - for await (const cursor of walletDatabase.iterate_mutate('internal_connected')) { - if (!cursor.value.origins.has(origin)) continue - if (cursor.value.origins.size === 1) await cursor.delete() - else { - await cursor.update( - produce(cursor.value, (draft) => { - draft.origins.delete(origin) - }), - ) - } - } - } -} -export async function disconnectAllOriginsConnectedFromWallet(wallet: string, type: 'any' | 'sdk' | 'internal') { - if (type === 'any' || type === 'sdk') await walletDatabase.remove('granted_permission', wallet) - if (type === 'any' || type === 'internal') await walletDatabase.remove('internal_connected', wallet) -} - -export async function internalWalletConnect(wallet: string, origin: string) { - assertOrigin(origin) - enableMapSet() - const origins = (await walletDatabase.get('internal_connected', wallet))?.origins - - if (!origins) { - walletDatabase.add({ - type: 'internal_connected', - id: wallet, - origins: new Set([origin]), - }) - } else if (!origins.has(origin)) { - for await (const cursor of walletDatabase.iterate_mutate('internal_connected')) { - if (cursor.value.id !== wallet) continue - await cursor.update( - produce(cursor.value, (draft) => { - draft.origins.add(origin) - }), - ) - } - } -} - -function hasEthAccountsPermission(origin: string, permission: EIP2255Permission) { - return permission.parentCapability === 'eth_accounts' && permission.invoker === origin -} -function EIP2255PermissionsOfWallets(origin: string, wallets: Iterable): EIP2255Permission[] { - return [ - { - parentCapability: 'eth_accounts', - invoker: origin, - caveats: [ - { - type: 'restrictReturnedAccounts', - value: [...wallets], - }, - ], - }, - ] -} -export async function getAllConnectedWallets( - origin: string, - type: 'any' | 'sdk' | 'internal', -): Promise> { - assertOrigin(origin) - const wallets = new Set() - if (type === 'any' || type === 'sdk') { - out: for await (const cursor of walletDatabase.iterate('granted_permission')) { - const thisOrigin = cursor.value.origins.get(origin) - if (!thisOrigin) continue - for (const permission of thisOrigin) { - if (hasEthAccountsPermission(origin, permission)) { - wallets.add(cursor.value.id) - continue out - } - } - } - } - - if (type === 'any' || type === 'internal') { - for await (const cursor of walletDatabase.iterate('internal_connected')) { - if (!cursor.value.origins.has(origin)) continue - wallets.add(cursor.value.id) - } - } - return wallets -} -export async function getAllConnectedOrigins( - wallet: string, - type: 'any' | 'sdk' | 'internal', -): Promise> { - const connectedOrigins = new Set() - if (type === 'any' || type === 'sdk') { - const origins = (await walletDatabase.get('granted_permission', wallet))?.origins || [] - out: for (const permissions of origins.values()) { - for (const permission of permissions) { - if (hasEthAccountsPermission(permission.invoker, permission)) { - connectedOrigins.add(permission.invoker) - continue out - } - } - } - } - if (type === 'any' || type === 'internal') { - const origins = (await walletDatabase.get('internal_connected', wallet))?.origins || [] - for (const origin of origins) { - connectedOrigins.add(origin) - } - } - return connectedOrigins -} - -function assertOrigin(origin: string) { - if (!URL.canParse(origin) || new URL(origin).origin !== origin) - throw new TypeError( - 'origin is not a valid origin. See https://developer.mozilla.org/en-US/docs/Glossary/Origin', - ) -} diff --git a/packages/mask/background/services/wallet/services/helpers.ts b/packages/mask/background/services/wallet/services/helpers.ts deleted file mode 100644 index c2fbbbc0ad2e..000000000000 --- a/packages/mask/background/services/wallet/services/helpers.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { LegacyWalletRecord } from '@masknet/shared-base' -import { formatEthereumAddress } from '@masknet/web3-shared-evm' -import type { LegacyWalletRecordInDatabase } from '../database/types.js' - -export function LegacyWalletRecordOutDB(x: LegacyWalletRecordInDatabase) { - const record = x as LegacyWalletRecord - record.address = formatEthereumAddress(record.address) - record.erc20_token_whitelist ??= new Set() - record.erc20_token_blacklist ??= new Set() - record.erc721_token_whitelist ??= new Set() - record.erc721_token_blacklist ??= new Set() - record.erc1155_token_whitelist ??= new Set() - record.erc1155_token_blacklist ??= new Set() - return record -} diff --git a/packages/mask/background/services/wallet/services/index.ts b/packages/mask/background/services/wallet/services/index.ts deleted file mode 100644 index df4cb961c1d0..000000000000 --- a/packages/mask/background/services/wallet/services/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './connect.js' -export * from './select.js' -export * from './wallet/index.js' -export * from './legacyWallet.js' -export * from './rpc.js' -export * from './send.js' -export * from './sdk.js' diff --git a/packages/mask/background/services/wallet/services/legacyWallet.ts b/packages/mask/background/services/wallet/services/legacyWallet.ts deleted file mode 100644 index 025246537111..000000000000 --- a/packages/mask/background/services/wallet/services/legacyWallet.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as bip39 from 'bip39' -import * as wallet_ts from /* webpackDefer: true */ 'wallet.ts' -import { BigNumber } from 'bignumber.js' -import { ec as EC } from 'elliptic' -import { fromHex, toHex, type LegacyWalletRecord } from '@masknet/shared-base' -import { HD_PATH_WITHOUT_INDEX_ETHEREUM } from '@masknet/web3-shared-base' -import { createTransaction } from '../../../database/utils/openDB.js' -import { createWalletDBAccess } from '../database/Wallet.db.js' -import { LegacyWalletRecordOutDB } from './helpers.js' - -function sortWallet(a: LegacyWalletRecord, b: LegacyWalletRecord) { - if (a.updatedAt > b.updatedAt) return -1 - if (a.updatedAt < b.updatedAt) return 1 - if (a.createdAt > b.createdAt) return -1 - if (a.createdAt < b.createdAt) return 1 - return 0 -} - -export async function getLegacyWallets() { - const wallets = await getAllWalletRecords() - return wallets.filter((x) => x._private_key_ || x.mnemonic.length) -} - -async function getAllWalletRecords() { - const t = createTransaction(await createWalletDBAccess(), 'readonly')('Wallet') - const records = await t.objectStore('Wallet').getAll() - const wallets = ( - await Promise.all( - records.map(async (record) => { - const walletRecord = LegacyWalletRecordOutDB(record) - return { - ...walletRecord, - _private_key_: await makePrivateKey(walletRecord), - } - }), - ) - ).sort(sortWallet) - return wallets -} - -async function makePrivateKey(record: LegacyWalletRecord) { - // not a managed wallet - if (!record._private_key_ && !record.mnemonic.length) return '' - const { privateKey } = - record._private_key_ ? - await recoverWalletFromPrivateKey(record._private_key_) - : await recoverWalletFromMnemonicWords(record.mnemonic, record.passphrase, record.path) - return `0x${toHex(privateKey)}` -} - -async function recoverWalletFromMnemonicWords( - mnemonic: string[], - passphrase: string, - path = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, -) { - const seed = await bip39.mnemonicToSeed(mnemonic.join(' '), passphrase) - const masterKey = wallet_ts.HDKey.parseMasterSeed(seed) - const extendedPrivateKey = masterKey.derive(path).extendedPrivateKey! - const childKey = wallet_ts.HDKey.parseExtendedKey(extendedPrivateKey) - const wallet = childKey.derive('') - const walletPublicKey = wallet.publicKey - const walletPrivateKey = wallet.privateKey! - return { - address: wallet_ts.EthereumAddress.from(walletPublicKey).address, - privateKey: walletPrivateKey, - privateKeyValid: true, - privateKeyInHex: `0x${toHex(walletPrivateKey)}`, - path, - mnemonic, - passphrase, - } -} - -async function recoverWalletFromPrivateKey(privateKey: string) { - const ec = new EC('secp256k1') - const privateKey_ = privateKey.replace(/^0x/, '').trim() // strip 0x - const key = ec.keyFromPrivate(privateKey_) - return { - address: wallet_ts.EthereumAddress.from(key.getPublic(false, 'array') as any).address, - privateKey: fromHex(privateKey_), - privateKeyValid: privateKeyVerify(privateKey_), - privateKeyInHex: privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`, - mnemonic: [], - } -} - -function privateKeyVerify(key: string) { - if (!/[\da-f]{64}/i.test(key)) return false - const k = new BigNumber(key, 16) - const n = new BigNumber('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16) - return !k.isZero() && k.isLessThan(n) -} diff --git a/packages/mask/background/services/wallet/services/maskwallet/index.ts b/packages/mask/background/services/wallet/services/maskwallet/index.ts deleted file mode 100644 index e5ba9440c9b4..000000000000 --- a/packages/mask/background/services/wallet/services/maskwallet/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { MaskBaseAPI } from '@masknet/web3-providers/types' -import { type api } from '@dimensiondev/mask-wallet-core/proto' -import { OnDemandWorker } from '@masknet/shared-base' - -type Request = InstanceType -type Response = InstanceType - -const worker = new OnDemandWorker(new URL('../../../../../web-workers/wallet.ts', import.meta.url), { - name: 'MaskWallet', -}) - -enum ErrorCode { - KdfParamsInvalid = '-3001', - PasswordIncorrect = '-3002', - InvalidKeyIvLength = '-3003', - InvalidCiphertext = '-3004', - InvalidPrivateKey = '-3005', - InvalidPublicKey = '-3006', - InvalidMnemonic = '-3007', - InvalidSeed = '-3008', - InvalidDerivationPath = '-3009', - InvalidKeyStoreJSON = '-3010', - NotSupportedPublicKeyType = '-3011', - NotSupportedCurve = '-3012', - NotSupportedCipher = '-3013', -} - -const ErrorMessage = { - [ErrorCode.KdfParamsInvalid]: 'Invalid kdf parameters.', - [ErrorCode.PasswordIncorrect]: 'Incorrect payment password.', - [ErrorCode.InvalidKeyIvLength]: 'Invalid key IV length.', - [ErrorCode.InvalidCiphertext]: 'Invalid cipher text.', - [ErrorCode.InvalidPrivateKey]: 'Invalid private key.', - [ErrorCode.InvalidPublicKey]: 'Invalid public key.', - [ErrorCode.InvalidMnemonic]: 'Invalid mnemonic words.', - [ErrorCode.InvalidSeed]: 'Invalid seed.', - [ErrorCode.InvalidDerivationPath]: 'Invalid derivation path.', - [ErrorCode.InvalidKeyStoreJSON]: 'Invalid keystore JSON.', - [ErrorCode.NotSupportedPublicKeyType]: 'Not supported public key type.', - [ErrorCode.NotSupportedCurve]: 'Not supported curve.', - [ErrorCode.NotSupportedCipher]: 'Not supported cipher.', -} - -function send(input: I, output: O) { - // https://bugs.chromium.org/p/chromium/issues/detail?id=1219164 - if (typeof Worker !== 'function') { - return async (value: Request[I]): Promise => { - const { request } = await import('@dimensiondev/mask-wallet-core/bundle') - const { api } = await import('@dimensiondev/mask-wallet-core/proto') - - const payload = api.MWRequest.encode({ [input]: value }).finish() - const wasmResult = request(payload) - return api.MWResponse.decode(wasmResult)[output] - } - } - return (value: Request[I]) => { - return new Promise((resolve, reject) => { - const req: MaskBaseAPI.Input = { id: Math.random(), data: { [input]: value } } - worker.postMessage(req) - worker.addEventListener('message', function f(message) { - if (message.data.id !== req.id) return - - worker.removeEventListener('message', f) - const data: MaskBaseAPI.Output = message.data - if (data.response.error) - return reject( - new Error(ErrorMessage[data.response.error.errorCode as ErrorCode] || 'Unknown Error'), - ) - resolve(data.response[output]) - }) - }) - } -} -export const importPrivateKey = send('param_import_private_key', 'resp_import_private_key') -export const importMnemonic = send('param_import_mnemonic', 'resp_import_mnemonic') -export const importJSON = send('param_import_json', 'resp_import_json') -export const createAccountOfCoinAtPath = send( - 'param_create_account_of_coin_at_path', - 'resp_create_account_of_coin_at_path', -) -export const exportPrivateKey = send('param_export_private_key', 'resp_export_private_key') -export const exportPrivateKeyOfPath = send('param_export_private_key_of_path', 'resp_export_private_key') -export const exportMnemonic = send('param_export_mnemonic', 'resp_export_mnemonic') -export const exportKeyStoreJSONOfAddress = send('param_export_key_store_json_of_address', 'resp_export_key_store_json') -export const exportKeyStoreJSONOfPath = send('param_export_key_store_json_of_path', 'resp_export_key_store_json') diff --git a/packages/mask/background/services/wallet/services/rpc.ts b/packages/mask/background/services/wallet/services/rpc.ts deleted file mode 100644 index 838e48aa46ae..000000000000 --- a/packages/mask/background/services/wallet/services/rpc.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { first } from 'lodash-es' -import type { JsonRpcPayload } from 'web3-core-helpers' -import { createWalletDBAccess } from '../database/Wallet.db.js' -import { createTransaction } from '../../../database/utils/openDB.js' -import type { RequestPayload } from '../database/types.js' -import { CrossIsolationMessages } from '@masknet/shared-base' - -const MAX_UNCONFIRMED_REQUESTS_SIZE = 1 -const MAIN_RECORD_ID = '0' - -function requestSorter(a: JsonRpcPayload, z: JsonRpcPayload) { - return ((a.id as number) ?? 0) - ((z.id as number) ?? 0) -} - -async function getUnconfirmedRequests() { - const t = createTransaction(await createWalletDBAccess(), 'readonly')('UnconfirmedRequestChunk') - const chunk = await t.objectStore('UnconfirmedRequestChunk').get(MAIN_RECORD_ID) - if (!chunk) return [] - return chunk.requests.slice(0, MAX_UNCONFIRMED_REQUESTS_SIZE).sort(requestSorter) -} - -export async function topUnconfirmedRequest() { - return first(await getUnconfirmedRequests()) -} - -export async function updateUnconfirmedRequest(payload: RequestPayload) { - const now = new Date() - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('UnconfirmedRequestChunk') - - const chunk_ = await t.objectStore('UnconfirmedRequestChunk').get(MAIN_RECORD_ID) - - if (!chunk_?.requests.length) throw new Error('No request to update.') - - const requests = - chunk_.requests.map((item) => { - if (item.id !== payload.id) return item - return payload - }) ?? [] - - const chunk = { - ...chunk_, - updatedAt: now, - requests, - } - - await t.objectStore('UnconfirmedRequestChunk').put(chunk) - CrossIsolationMessages.events.requestsUpdated.sendToAll({ hasRequest: true }) - return payload -} diff --git a/packages/mask/background/services/wallet/services/sdk.ts b/packages/mask/background/services/wallet/services/sdk.ts deleted file mode 100644 index e1107e642018..000000000000 --- a/packages/mask/background/services/wallet/services/sdk.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { PersistentStorages, type StorageObject } from '@masknet/shared-base' -import { ChainId } from '@masknet/web3-shared-evm' -import { isLocked, sdk_EIP2255_wallet_getPermissions } from './index.js' -import { isSameAddress } from '@masknet/web3-shared-base' - -let storage: StorageObject<{ - chainId: ChainId - account: string -}> -async function initStorage() { - // from packages/web3-providers/src/Web3/EVM/providers/BaseHosted.ts - storage ??= PersistentStorages.Web3.createSubScope('com.mask.evm_Maskbook_hosted', { - chainId: ChainId.Aurora_Testnet, - account: '', - }).storage - await storage.chainId.initializedPromise - await storage.account.initializedPromise - return storage -} - -export async function sdk_eth_accounts(origin: string): Promise { - if (await isLocked()) return [] - const wallets = await sdk_getGrantedWallets(origin) - const currentAccount = (await initStorage()).account.value - return wallets.sort((a, b) => - isSameAddress(a, currentAccount) ? -1 - : isSameAddress(b, currentAccount) ? 1 - : 0, - ) -} -export async function sdk_eth_chainId(): Promise { - return (await initStorage()).chainId.value -} - -export async function sdk_getGrantedWallets(origin: string) { - const wallets: string[] = [] - for (const permission of await sdk_EIP2255_wallet_getPermissions(origin)) { - if (permission.parentCapability !== 'eth_accounts') continue - for (const caveat of permission.caveats) { - if (caveat.type !== 'restrictReturnedAccounts') continue - if (!Array.isArray(caveat.value)) continue - for (const item of caveat.value) { - if (typeof item === 'string') wallets.push(item) - } - } - } - return wallets -} diff --git a/packages/mask/background/services/wallet/services/select.ts b/packages/mask/background/services/wallet/services/select.ts deleted file mode 100644 index ad987ca8e4bc..000000000000 --- a/packages/mask/background/services/wallet/services/select.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { defer, type DeferTuple } from '@masknet/kit' -import { PopupRoutes, type ECKeyIdentifier } from '@masknet/shared-base' -import { type ChainId } from '@masknet/web3-shared-evm' -import { openPopupWindow } from '../../helper/popup-opener.js' - -let deferred: DeferTuple | undefined - -interface MaskAccount { - address: string - owner?: string - identifier?: ECKeyIdentifier -} -/** - * @param chainId Chain ID - */ -export async function selectMaskAccount( - chainId: ChainId, - defaultAddress?: string, - source?: string, -): Promise { - await openPopupWindow(PopupRoutes.SelectWallet, { - chainId, - address: defaultAddress, - source, - }) - deferred = defer() - return deferred![0] -} - -export async function resolveMaskAccount(result: MaskAccount[] | PromiseSettledResult) { - if (Array.isArray(result)) deferred?.[1](result) - else if (result.status === 'fulfilled') deferred?.[1](result.value) - else deferred?.[2](result.reason) - - deferred = undefined -} diff --git a/packages/mask/background/services/wallet/services/send.ts b/packages/mask/background/services/wallet/services/send.ts deleted file mode 100644 index 42126e9d4310..000000000000 --- a/packages/mask/background/services/wallet/services/send.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { JsonRpcPayload } from 'web3-core-helpers' -import { ECKeyIdentifier, type SignType } from '@masknet/shared-base' -import { EVMRequestReadonly, SmartPayAccount, EVMWeb3Readonly } from '@masknet/web3-providers' -import { - ChainId, - createJsonRpcResponse, - ErrorEditor, - EthereumMethodType, - isValidAddress, - PayloadEditor, - type TransactionOptions, - Signer, -} from '@masknet/web3-shared-evm' -import { signWithWallet } from './wallet/index.js' -import { signWithPersona } from '../../identity/persona/sign.js' - -/** - * The entrance of all RPC requests to MaskWallet. - */ -export async function send(payload: JsonRpcPayload, options?: TransactionOptions) { - const { owner, paymentToken, providerURL } = options ?? {} - const { - pid = 0, - from, - chainId = options?.chainId ?? ChainId.Mainnet, - signableMessage, - signableConfig, - } = PayloadEditor.fromPayload(payload, options) - const identifier = ECKeyIdentifier.from(options?.identifier).unwrapOr(undefined) - const signer = - identifier ? - new Signer(identifier, (type: SignType, message: T, identifier?: ECKeyIdentifier) => - signWithPersona(type, message, identifier, undefined, true), - ) - : new Signer(owner || from!, signWithWallet) - - switch (payload.method) { - case EthereumMethodType.ETH_SEND_TRANSACTION: - case EthereumMethodType.MASK_REPLACE_TRANSACTION: - if (!signableConfig) throw new Error('No transaction to be sent.') - - try { - if (owner && paymentToken) { - return createJsonRpcResponse( - pid, - await SmartPayAccount.sendTransaction(chainId, owner, signableConfig, signer, { - paymentToken, - }), - ) - } else { - return createJsonRpcResponse( - pid, - await EVMWeb3Readonly.sendSignedTransaction(await signer.signTransaction(signableConfig), { - chainId, - providerURL, - }), - ) - } - } catch (error) { - throw ErrorEditor.from(error, null, 'Failed to send transaction.').error - } - case EthereumMethodType.ETH_SIGN: - case EthereumMethodType.PERSONAL_SIGN: - try { - if (!signableMessage) throw new Error('No message to be signed.') - return createJsonRpcResponse(pid, await signer.signMessage(signableMessage)) - } catch (error) { - throw ErrorEditor.from(error, null, 'Failed to sign message.').error - } - case EthereumMethodType.ETH_SIGN_TYPED_DATA: - try { - if (!signableMessage) throw new Error('No typed data to be signed.') - return createJsonRpcResponse(pid, await signer.signTypedData(signableMessage)) - } catch (error) { - throw ErrorEditor.from(error, null, 'Failed to sign typed data.').error - } - case EthereumMethodType.ETH_SIGN_TRANSACTION: - try { - if (!signableConfig) throw new Error('No transaction to be signed.') - return createJsonRpcResponse(pid, await signer.signTransaction(signableConfig)) - } catch (error) { - throw ErrorEditor.from(error, null, 'Failed to sign transaction.').error - } - case EthereumMethodType.MASK_DEPLOY: - try { - const [owner] = payload.params as [string] - if (!isValidAddress(owner)) throw new Error('Invalid sender address.') - return createJsonRpcResponse(pid, await SmartPayAccount.deploy(chainId, owner, signer)) - } catch (error) { - throw ErrorEditor.from(error, null, 'Failed to deploy.').error - } - case EthereumMethodType.ETH_DECRYPT: - case EthereumMethodType.ETH_GET_ENCRYPTION_PUBLIC_KEY: - throw new Error('Method not implemented.') - default: - try { - const result = await EVMRequestReadonly.request( - { - method: payload.method as EthereumMethodType, - params: payload.params ?? [], - }, - { - chainId, - providerURL, - }, - ) - return createJsonRpcResponse(pid, result) - } catch (error) { - throw error instanceof Error ? error : new Error('Failed to send request.') - } - } -} diff --git a/packages/mask/background/services/wallet/services/wallet/database/index.ts b/packages/mask/background/services/wallet/services/wallet/database/index.ts deleted file mode 100644 index b97933a36d25..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/database/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './wallet.js' -export * from './locker.js' -export * from './secret.js' diff --git a/packages/mask/background/services/wallet/services/wallet/database/locker.ts b/packages/mask/background/services/wallet/services/wallet/database/locker.ts deleted file mode 100644 index d949af6ef47b..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/database/locker.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CrossIsolationMessages } from '@masknet/shared-base' -import { walletDatabase } from '../../../database/Plugin.db.js' - -const DEFAULT_LOCK_DURATION = 1000 * 60 * 60 * 24 // One day - -const ID = 'locker' - -export interface LockerRecord { - type: 'locker' - id: string - duration: number // ms -} - -async function getAutoLockerRecord() { - return walletDatabase.get('locker', ID) -} - -export async function getAutoLockerDuration() { - const record = await getAutoLockerRecord() - return record?.duration ?? DEFAULT_LOCK_DURATION -} - -export async function setAutoLockerTime(duration: number) { - await walletDatabase.add({ type: 'locker', id: ID, duration }) - CrossIsolationMessages.events.walletLockTimeUpdated.sendToAll() -} diff --git a/packages/mask/background/services/wallet/services/wallet/database/secret.ts b/packages/mask/background/services/wallet/services/wallet/database/secret.ts deleted file mode 100644 index 1e7d824ab185..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/database/secret.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { decodeText, encodeText } from '@masknet/kit' -import { getDefaultWalletPassword } from '@masknet/shared-base' -import { walletDatabase } from '../../../database/Plugin.db.js' - -const SECRET_ID = '0' - -function derivePBKDF2(password: string) { - return crypto.subtle.importKey('raw', encodeText(password).buffer, 'PBKDF2', false, ['deriveBits', 'deriveKey']) -} -function deriveAES(key: CryptoKey, iv: ArrayBuffer) { - return crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt: iv, - iterations: 100000, - hash: 'SHA-256', - }, - key, - { name: 'AES-KW', length: 256 }, - false, - ['wrapKey', 'unwrapKey'], - ) -} -function createAES() { - return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']) -} -function encrypt(message: ArrayBuffer, key: CryptoKey, iv: ArrayBuffer) { - return crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, message) -} -function decrypt(message: ArrayBuffer, key: CryptoKey, iv: ArrayBuffer) { - return crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, message) -} -function wrapKey(key: CryptoKey, wrapKey: CryptoKey) { - return crypto.subtle.wrapKey('raw', key, wrapKey, 'AES-KW') -} -function unwrapKey(key: ArrayBuffer, wrapKey: CryptoKey) { - return crypto.subtle.unwrapKey('raw', key, wrapKey, 'AES-KW', 'AES-GCM', false, ['encrypt', 'decrypt']) -} -function getIV() { - return crypto.getRandomValues(new Uint8Array(16)).buffer -} -async function deriveKey(iv: ArrayBuffer, password: string) { - return deriveAES(await derivePBKDF2(password), iv) -} - -async function getSecret() { - return walletDatabase.get('secret', SECRET_ID) -} - -/** - * Return true means a user password (could be the default one) has been set. - * @returns - */ -export async function hasSecret() { - return !!(await getSecret()) -} - -/** - * Return true means the user has set a password (could not be the default one). - * @returns - */ -export async function hasSafeSecret() { - const secret = await getSecret() - return !!secret && (typeof secret.isUnsafe === 'undefined' || secret.isUnsafe === false) -} - -/** - * Erase the preexisting master secret by force, and create a new one with the given user password. - * @param password - */ -export async function resetSecret(password: string) { - await walletDatabase.remove('secret', SECRET_ID) - const iv = getIV() - const key = await deriveKey(iv, password) - const primaryKey = await createAES() - const primaryKeyWrapped = await wrapKey(primaryKey, key) - const message = uuid() // the primary key never change - await walletDatabase.add({ - id: SECRET_ID, - type: 'secret', - iv, - key: primaryKeyWrapped, - encrypted: await encrypt(encodeText(message), primaryKey, iv), - isUnsafe: password === getDefaultWalletPassword(), - }) -} - -/** - * Create a master secret which will be encrypted by the given user password. - * @param password - */ -export async function encryptSecret(password: string) { - const secret = await getSecret() - if (secret) throw new Error('A secret has already been set.') - - const iv = getIV() - const key = await deriveKey(iv, password) - const primaryKey = await createAES() - const primaryKeyWrapped = await wrapKey(primaryKey, key) - const message = uuid() // the master secret never change - await walletDatabase.add({ - id: SECRET_ID, - type: 'secret', - iv, - key: primaryKeyWrapped, - encrypted: await encrypt(encodeText(message), primaryKey, iv), - isUnsafe: password === getDefaultWalletPassword(), - }) -} -/** - * Update the user password which is used for encrypting the master secret. - * @param oldPassword - * @param newPassword - */ -export async function updateSecret(oldPassword: string, newPassword: string) { - const secret = await getSecret() - if (!secret) throw new Error('No secret has set before.') - - if (newPassword === getDefaultWalletPassword()) throw new Error('Invalid password.') - - const iv = getIV() - const message = await decryptSecret(oldPassword) - const key = await deriveKey(iv, newPassword) - const primaryKey = await createAES() - const primaryKeyWrapped = await wrapKey(primaryKey, key) - await walletDatabase.add({ - id: SECRET_ID, - type: 'secret', - iv, - key: primaryKeyWrapped, - encrypted: await encrypt(encodeText(message), primaryKey, iv), - isUnsafe: false, - }) -} - -/** - * Decrypt the master secret. - * @param password - * @returns - */ -export async function decryptSecret(password: string) { - const secret = await getSecret() - if (!secret) throw new Error('No secret has set before.') - - try { - const key = await deriveKey(secret.iv, password) - const primaryKey = await unwrapKey(secret.key, key) - return decodeText(await decrypt(secret.encrypted, primaryKey, secret.iv)) - } catch { - return '' - } -} diff --git a/packages/mask/background/services/wallet/services/wallet/database/wallet.ts b/packages/mask/background/services/wallet/services/wallet/database/wallet.ts deleted file mode 100644 index 0992dc9c1f52..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/database/wallet.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { omit } from 'lodash-es' -import { api } from '@dimensiondev/mask-wallet-core/proto' -import { CrossIsolationMessages, type ImportSource, asyncIteratorToArray } from '@masknet/shared-base' -import { formatEthereumAddress, isValidAddress } from '@masknet/web3-shared-evm' -import { walletDatabase } from '../../../database/Plugin.db.js' -import type { WalletRecord } from '../type.js' - -function WalletRecordOutDB(record: WalletRecord) { - return { - ...omit(record, 'type'), - configurable: record.storedKeyInfo?.type ? record.storedKeyInfo.type !== api.StoredKeyType.Mnemonic : true, - hasStoredKeyInfo: !!record.storedKeyInfo, - hasDerivationPath: !!record.derivationPath, - } -} - -export async function getWallet(address: string) { - if (!address) return null - if (!isValidAddress(address)) throw new Error('Not a valid address.') - const wallet = (await walletDatabase.get('wallet', formatEthereumAddress(address))) ?? null - return wallet ? WalletRecordOutDB(wallet) : null -} - -export async function getWalletRequired(address: string) { - const wallet = await getWallet(address) - if (!wallet) throw new Error('The wallet does not exist.') - return wallet -} - -export async function hasWallet(address: string) { - return walletDatabase.has('wallet', formatEthereumAddress(address)) -} - -export async function hasStoredKeyInfo(storedKeyInfo?: api.IStoredKeyInfo) { - const wallets = await getWallets() - if (!storedKeyInfo) return false - return wallets.filter((x) => x.storedKeyInfo?.hash).some((x) => x.storedKeyInfo?.hash === storedKeyInfo.hash) -} - -async function getWalletRecords() { - return (await asyncIteratorToArray(walletDatabase.iterate('wallet'))).map((x) => x.value) -} - -export async function getWallets() { - const wallets = await getWalletRecords() - - return wallets - .sort((a, z) => { - if (a.updatedAt > z.updatedAt) return -1 - if (a.updatedAt < z.updatedAt) return 1 - if (a.createdAt > z.createdAt) return -1 - if (a.createdAt < z.createdAt) return 1 - return 0 - }) - .map(WalletRecordOutDB) -} - -export async function addWallet( - source: ImportSource, - address: string, - updates?: { - name?: string - derivationPath?: string - storedKeyInfo?: api.IStoredKeyInfo - mnemonicId?: string - }, -) { - const wallet = await getWallet(address) - if (wallet?.storedKeyInfo?.data) throw new Error('The wallet already exists.') - - const now = new Date() - const address_ = formatEthereumAddress(address) - await walletDatabase.add({ - id: address_, - type: 'wallet', - source, - address: address_, - name: updates?.name?.trim() ?? `Account ${(await getWallets()).length + 1}`, - derivationPath: updates?.derivationPath, - storedKeyInfo: updates?.storedKeyInfo, - mnemonicId: updates?.mnemonicId, - createdAt: now, - updatedAt: now, - }) - CrossIsolationMessages.events.walletsUpdated.sendToAll(undefined) - return address_ -} - -export async function updateWallet( - address: string, - updates: Partial<{ - name: string - derivationPath?: string - latestDerivationPath?: string - mnemonicId?: string - }>, -) { - const wallet = await getWallet(address) - if (!wallet) throw new Error('The wallet does not exist') - - await walletDatabase.add({ - type: 'wallet', - ...wallet, - name: updates.name ?? wallet.name, - derivationPath: updates.derivationPath ?? wallet.derivationPath, - latestDerivationPath: updates.latestDerivationPath ?? wallet.latestDerivationPath, - mnemonicId: updates.mnemonicId ?? wallet.mnemonicId, - updatedAt: new Date(), - }) - CrossIsolationMessages.events.walletsUpdated.sendToAll(undefined) -} - -export async function deleteWallet(address: string) { - await walletDatabase.remove('wallet', address) - CrossIsolationMessages.events.walletsUpdated.sendToAll(undefined) -} - -export async function resetAllWallets() { - for await (const x of walletDatabase.iterate_mutate('wallet')) { - await x.delete() - } - CrossIsolationMessages.events.walletsUpdated.sendToAll(undefined) -} diff --git a/packages/mask/background/services/wallet/services/wallet/index.ts b/packages/mask/background/services/wallet/services/wallet/index.ts deleted file mode 100644 index e4013a870351..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/index.ts +++ /dev/null @@ -1,508 +0,0 @@ -import * as bip39 from 'bip39' -import { first, last, omit } from 'lodash-es' -import * as web3_utils from /* webpackDefer: true */ 'web3-utils' -import { toBuffer } from '@ethereumjs/util' -import { api } from '@dimensiondev/mask-wallet-core/proto' -import { Signer } from '@masknet/web3-providers' -import { ImportSource, type SignType, type Wallet } from '@masknet/shared-base' -import { HD_PATH_WITHOUT_INDEX_ETHEREUM } from '@masknet/web3-shared-base' -import * as Mask from '../maskwallet/index.js' -import * as database from './database/index.js' -import * as password from './password.js' - -const MAX_DERIVE_COUNT = 99 - -function bumpDerivationPath(path = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`) { - const splitted = path.split('/') - const index = Number.parseInt(last(splitted) ?? '', 10) - if (Number.isNaN(index) || index < 0 || splitted.length !== 6) throw new Error('Invalid derivation path.') - return [...splitted.slice(0, -1), index + 1].join('/') -} - -function sanitizeWallet(wallet: Wallet): Wallet { - return omit(wallet, ['storedKeyInfo']) -} - -// db -export { hasWallet, addWallet, updateWallet } from './database/wallet.js' - -// password -export { - setPassword, - hasPassword, - hasPasswordWithDefaultOne, - verifyPassword, - changePassword, - resetPassword, - setDefaultPassword, - validatePassword, - clearPassword, -} from './password.js' - -export { getAutoLockerDuration, setAutoLockerTime } from './database/locker.js' - -// locker -export { isLocked, lockWallet, unlockWallet, setAutoLockTimer, requestUnlockWallet } from './locker.js' - -export async function getWallet(address: string) { - const wallet = await database.getWallet(address) - if (!wallet?.hasStoredKeyInfo) return null - return sanitizeWallet(wallet) -} - -export async function getWallets(): Promise { - const wallets = await database.getWallets() - return wallets.filter((x) => x.hasStoredKeyInfo).map(sanitizeWallet) -} - -export async function createMnemonicWords() { - return bip39.generateMnemonic().split(' ') -} - -export async function createMnemonicId(mnemonic: string) { - const id = web3_utils.sha3(mnemonic) - if (!id) throw new Error('Failed to create mnemonic id.') - return id -} - -export async function getPrimaryWalletByMnemonicId(mnemonicId?: string) { - if (!mnemonicId) return - const wallets = await database.getWallets() - - return ( - wallets.find((x) => x.mnemonicId === mnemonicId && x.storedKeyInfo?.type === api.StoredKeyType.Mnemonic) ?? null - ) -} - -export async function getWalletPrimary(mnemonicId?: string) { - const wallets = await database.getWallets() - const { Mnemonic } = api.StoredKeyType - const list = wallets - .filter((x) => x.storedKeyInfo?.type === Mnemonic && (mnemonicId ? mnemonicId === x.mnemonicId : true)) - .sort((a, z) => a.createdAt.getTime() - z.createdAt.getTime()) - return first(list) ?? null -} - -export async function getDerivableAccounts(mnemonic: string, page: number, pageSize = 10) { - const oneTimePassword = 'MASK' - const imported = await Mask.importMnemonic({ - mnemonic, - password: oneTimePassword, - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - - const accounts: Array<{ - index: number - address: string - derivationPath: string - }> = [] - - for (let i = pageSize * page; i < pageSize * (page + 1); i += 1) { - const derivationPath = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/${i}` - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - password: oneTimePassword, - derivationPath, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error(`Failed to create account at path: ${derivationPath}.`) - accounts.push({ - index: i, - address: created.account.address, - derivationPath, - }) - } - return accounts -} - -export async function deriveWallet(name: string, defaultMnemonicId?: string) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - - // derive wallet base on the primary wallet - const primaryWallet = await getWalletPrimary(defaultMnemonicId) - if (!primaryWallet?.storedKeyInfo) throw new Error('Cannot find the primary wallet.') - - let derivedTimes = 0 - let latestDerivationPath = primaryWallet.latestDerivationPath ?? primaryWallet.derivationPath - if (!latestDerivationPath) throw new Error('Failed to derive wallet without derivation path.') - - let mnemonicId = defaultMnemonicId - if (!mnemonicId) { - const mnemonic = await exportMnemonicWords(primaryWallet.address, masterPassword) - mnemonicId = await createMnemonicId(mnemonic) - } - - // eslint-disable-next-line no-constant-condition - while (true) { - derivedTimes += 1 - - // protect from endless looping - if (derivedTimes >= MAX_DERIVE_COUNT) { - await database.updateWallet(primaryWallet.address, { - latestDerivationPath, - }) - throw new Error('Exceed the max derivation times.') - } - - // bump index - latestDerivationPath = bumpDerivationPath(latestDerivationPath) - - // derive a new wallet - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name, - password: masterPassword, - derivationPath: latestDerivationPath, - StoredKeyData: primaryWallet.storedKeyInfo.data, - }) - if (!created?.account?.address) throw new Error(`Failed to create account at path: ${latestDerivationPath}.`) - - // check its existence in DB - if (await database.hasWallet(created.account.address)) { - const localWallet = await database.getWallet(created.account.address) - if (localWallet?.mnemonicId) - await database.updateWallet(localWallet.address, { - mnemonicId, - }) - continue - } - - // update the primary wallet - await database.updateWallet(primaryWallet.address, { - latestDerivationPath, - }) - - // found a valid candidate, get the private key of it - const exported = await Mask.exportPrivateKeyOfPath({ - coin: api.Coin.Ethereum, - password: masterPassword, - derivationPath: latestDerivationPath, - StoredKeyData: primaryWallet.storedKeyInfo.data, - }) - if (!exported?.privateKey) throw new Error(`Failed to export private key at path: ${latestDerivationPath}`) - - // import the candidate by the private key - return createWalletFromPrivateKey(name, exported.privateKey, mnemonicId, latestDerivationPath) - } -} - -export async function generateNextDerivationAddress() { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - - // derive wallet base on the primary wallet - const primaryWallet = await getWalletPrimary() - if (!primaryWallet?.storedKeyInfo) throw new Error('Cannot find the primary wallet.') - - let derivedTimes = 0 - let latestDerivationPath = primaryWallet.latestDerivationPath ?? primaryWallet.derivationPath - if (!latestDerivationPath) throw new Error('Failed to derive wallet without derivation path.') - - // eslint-disable-next-line no-constant-condition - while (true) { - derivedTimes += 1 - - // protect from endless looping - if (derivedTimes >= MAX_DERIVE_COUNT) throw new Error('Exceed the max derivation times.') - - // bump index - latestDerivationPath = bumpDerivationPath(latestDerivationPath) - - // derive a new wallet - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name: '', - password: masterPassword, - derivationPath: latestDerivationPath, - StoredKeyData: primaryWallet.storedKeyInfo.data, - }) - if (!created?.account?.address) throw new Error(`Failed to create account at path: ${latestDerivationPath}.`) - - // check its existence in DB - if (await database.hasWallet(created.account.address)) continue - - // found a valid candidate, get the private key of it - const exported = await Mask.exportPrivateKeyOfPath({ - coin: api.Coin.Ethereum, - password: masterPassword, - derivationPath: latestDerivationPath, - StoredKeyData: primaryWallet.storedKeyInfo.data, - }) - if (!exported?.privateKey) throw new Error(`Failed to export private key at path: ${latestDerivationPath}`) - - return generateAddressFromPrivateKey(exported.privateKey) - } -} - -export async function renameWallet(address: string, name: string) { - const name_ = name.trim() - if (name_.length <= 0) throw new Error('Invalid wallet name.') - await database.updateWallet(address, { - name: name_, - }) -} - -export async function removeWallet(address: string, unverifiedPassword: string) { - await password.verifyPasswordRequired(unverifiedPassword) - const wallet = await database.getWalletRequired(address) - await database.deleteWallet(wallet.address) -} - -export async function resetAllWallets() { - await database.resetAllWallets() -} - -export async function signWithWallet(type: SignType, message: T, address: string) { - return Signer.sign(type, toBuffer(`0x${await exportPrivateKey(address)}`), message) -} - -export async function exportMnemonicWords(address: string, unverifiedPassword?: string) { - if (unverifiedPassword) await password.verifyPasswordRequired(unverifiedPassword) - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const wallet = await database.getWalletRequired(address) - if (wallet.storedKeyInfo?.type !== api.StoredKeyType.Mnemonic) - throw new Error(`Cannot export mnemonic words of ${address}.`) - const exported = await Mask.exportMnemonic({ - password: masterPassword, - StoredKeyData: wallet.storedKeyInfo.data, - }) - if (!exported?.mnemonic) throw new Error(`Failed to export mnemonic words of ${address}.`) - return exported.mnemonic -} - -export async function exportPrivateKey(address: string, unverifiedPassword?: string) { - if (unverifiedPassword) await password.verifyPasswordRequired(unverifiedPassword) - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const wallet = await database.getWalletRequired(address) - if (!wallet.storedKeyInfo) throw new Error(`Cannot export private key of ${address}.`) - const exported = - wallet.derivationPath && !wallet.configurable ? - await Mask.exportPrivateKeyOfPath({ - coin: api.Coin.Ethereum, - derivationPath: wallet.derivationPath ?? `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, - password: masterPassword, - StoredKeyData: wallet.storedKeyInfo.data, - }) - : await Mask.exportPrivateKey({ - coin: api.Coin.Ethereum, - password: masterPassword, - StoredKeyData: wallet.storedKeyInfo.data, - }) - - if (!exported?.privateKey) throw new Error(`Failed to export private key of ${address}.`) - return exported.privateKey -} - -export async function exportKeyStoreJSON(address: string, unverifiedPassword?: string) { - if (unverifiedPassword) await password.verifyPasswordRequired(unverifiedPassword) - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const wallet = await database.getWalletRequired(address) - if (!wallet.storedKeyInfo) throw new Error(`Cannot export private key of ${address}.`) - - const exported = - wallet.derivationPath && !wallet.configurable ? - await Mask.exportKeyStoreJSONOfPath({ - coin: api.Coin.Ethereum, - derivationPath: wallet.derivationPath ?? `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, - password: masterPassword, - newPassword: unverifiedPassword, - StoredKeyData: wallet.storedKeyInfo.data, - }) - : await Mask.exportKeyStoreJSONOfAddress({ - coin: api.Coin.Ethereum, - password: masterPassword, - newPassword: unverifiedPassword, - StoredKeyData: wallet.storedKeyInfo.data, - }) - if (!exported?.json) throw new Error(`Failed to export keystore JSON of ${address}.`) - return exported.json -} - -async function addWalletFromMnemonicWords( - source: ImportSource, - name: string, - mnemonic: string, - derivationPath: string, -) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const imported = await Mask.importMnemonic({ - mnemonic, - password: masterPassword, - }) - - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - - const mnemonicId = await createMnemonicId(mnemonic) - if (await database.hasStoredKeyInfo(imported.StoredKey)) { - const exported = await Mask.exportPrivateKeyOfPath({ - coin: api.Coin.Ethereum, - derivationPath, - password: masterPassword, - StoredKeyData: imported.StoredKey.data, - }) - if (!exported?.privateKey) throw new Error(`Failed to export private key at path: ${derivationPath}`) - - return addWalletFromPrivateKey(source, name, exported.privateKey, mnemonicId, derivationPath) - } else { - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name, - password: masterPassword, - derivationPath, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error('Failed to create the wallet.') - return database.addWallet(source, created.account.address, { - name, - derivationPath, - storedKeyInfo: imported.StoredKey, - mnemonicId, - }) - } -} - -async function addWalletFromPrivateKey( - source: ImportSource, - name: string, - privateKey: string, - mnemonicId?: string, - derivationPath?: string, -) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const imported = await Mask.importPrivateKey({ - coin: api.Coin.Ethereum, - name, - password: masterPassword, - privateKey: privateKey.replace(/^0x/, '').trim(), - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name, - password: masterPassword, - derivationPath: null, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error('Failed to create the wallet.') - return database.addWallet(source, created.account.address, { - name, - storedKeyInfo: imported.StoredKey, - mnemonicId, - derivationPath, - }) -} - -export function createWalletFromMnemonicWords( - name: string, - mnemonic: string, - derivationPath = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, -) { - return addWalletFromMnemonicWords(ImportSource.LocalGenerated, name, mnemonic, derivationPath) -} - -function createWalletFromPrivateKey(name: string, privateKey: string, mnemonicId?: string, derivationPath?: string) { - return addWalletFromPrivateKey(ImportSource.LocalGenerated, name, privateKey, mnemonicId, derivationPath) -} - -export function recoverWalletFromMnemonicWords( - name: string, - mnemonic: string, - derivationPath = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, -) { - return addWalletFromMnemonicWords(ImportSource.UserProvided, name, mnemonic, derivationPath) -} - -export function recoverWalletFromPrivateKey( - name: string, - privateKey: string, - mnemonicId?: string, - derivationPath?: string, -) { - return addWalletFromPrivateKey(ImportSource.UserProvided, name, privateKey, mnemonicId, derivationPath) -} - -export async function recoverWalletFromKeyStoreJSON(name: string, json: string, jsonPassword: string) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const imported = await Mask.importJSON({ - coin: api.Coin.Ethereum, - json, - keyStoreJsonPassword: jsonPassword, - name, - password: masterPassword, - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - derivationPath: null, - name, - password: masterPassword, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error('Failed to create the wallet.') - - return database.addWallet(ImportSource.UserProvided, created.account.address, { - name, - storedKeyInfo: imported.StoredKey, - }) -} - -export async function generateAddressFromPrivateKey(privateKey: string) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const imported = await Mask.importPrivateKey({ - coin: api.Coin.Ethereum, - name: '', - password: masterPassword, - privateKey: privateKey.replace(/^0x/, '').trim(), - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name: '', - password: masterPassword, - derivationPath: null, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error('Failed to create the wallet.') - return created.account.address -} - -export async function generateAddressFromKeyStoreJSON(json: string, jsonPassword: string) { - const masterPassword = await password.INTERNAL_getMasterPasswordRequired() - const imported = await Mask.importJSON({ - coin: api.Coin.Ethereum, - json, - keyStoreJsonPassword: jsonPassword, - name: '', - password: masterPassword, - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - derivationPath: null, - name: '', - password: masterPassword, - StoredKeyData: imported.StoredKey.data, - }) - if (!created?.account?.address) throw new Error('Failed to create the wallet.') - return created.account.address -} - -export async function generateAddressFromMnemonicWords( - name: string, - mnemonic: string, - derivationPath = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, -) { - const oneTimePassword = 'MASK' - const imported = await Mask.importMnemonic({ - mnemonic, - password: oneTimePassword, - }) - if (!imported?.StoredKey) throw new Error('Failed to import the wallet.') - const created = await Mask.createAccountOfCoinAtPath({ - coin: api.Coin.Ethereum, - name, - password: oneTimePassword, - derivationPath, - StoredKeyData: imported.StoredKey.data, - }) - return created?.account?.address ?? undefined -} diff --git a/packages/mask/background/services/wallet/services/wallet/locker.ts b/packages/mask/background/services/wallet/services/wallet/locker.ts deleted file mode 100644 index 460c4eff097a..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/locker.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { CrossIsolationMessages, PopupRoutes } from '@masknet/shared-base' -import { getAutoLockerDuration } from './database/locker.js' -import * as password from './password.js' -import { openPopupWindow } from '../../../helper/popup-opener.js' - -export async function isLocked() { - return (await password.hasPassword()) && !(await password.hasVerifiedPassword()) -} - -export async function lockWallet() { - password.clearPassword() - CrossIsolationMessages.events.walletLockStatusUpdated.sendToAll(true) -} - -export async function unlockWallet(unverifiedPassword: string) { - if (!isLocked()) return true - try { - await password.verifyPasswordRequired(unverifiedPassword) - CrossIsolationMessages.events.walletLockStatusUpdated.sendToAll(false) - await setAutoLockTimer() - return true - } catch { - CrossIsolationMessages.events.walletLockStatusUpdated.sendToAll(true) - return false - } -} - -export async function requestUnlockWallet(): Promise { - if (!(await isLocked())) return - await openPopupWindow(PopupRoutes.WalletUnlock, {}) - return new Promise((resolve) => { - CrossIsolationMessages.events.walletLockStatusUpdated.on((locked) => { - if (!locked) resolve() - }) - }) -} - -// This setTimeout is ok because if the background worker is killed, -// it's the same effect as lockWallet is called. -// eslint-disable-next-line no-restricted-globals -let autoLockTimer: ReturnType | undefined - -export async function setAutoLockTimer(initialTimeout = 0) { - if (typeof initialTimeout !== 'number' || Number.isNaN(initialTimeout)) initialTimeout = 0 - const autoLockDuration = (await getAutoLockerDuration()) - initialTimeout - - clearTimeout(autoLockTimer) - - if (autoLockDuration <= 0) return - - // eslint-disable-next-line no-restricted-globals - autoLockTimer = setTimeout(lockWallet, autoLockDuration) -} diff --git a/packages/mask/background/services/wallet/services/wallet/password.ts b/packages/mask/background/services/wallet/services/wallet/password.ts deleted file mode 100644 index b2a5ae8f50b1..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/password.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { validate } from 'uuid' -import { getDefaultWalletPassword } from '@masknet/shared-base' -import * as database from './database/index.js' -import { setAutoLockTimer } from './locker.js' - -const key = '' -const atKey = 'at' -const inMemoryPassword = { - ' value': '', - get value() { - return this[' value'] - }, - set value(value) { - this[' value'] = value - if (!value) browser.storage.session?.clear() - else browser.storage.session?.set({ [key]: value, [atKey]: Date.now() }) - }, -} -browser.storage.session?.get([key, atKey]).then(async (result) => { - if (Date.now() - result[atKey] > (await database.getAutoLockerDuration())) { - browser.storage.session.clear() - return - } - setAutoLockTimer(result[atKey]) - if (!result[key]) return - inMemoryPassword[' value'] = result[key] -}) - -/** Decrypt the master password and return it. If it fails to decrypt, then return an empty string. */ -async function INTERNAL_getMasterPassword() { - const hasSafeSecret = await database.hasSafeSecret() - if (!hasSafeSecret) return database.decryptSecret(getDefaultWalletPassword()) - return inMemoryPassword.value ? database.decryptSecret(inMemoryPassword.value) : '' -} - -/** Decrypt the master password and return it. If it fails to decrypt, then throw an error. */ -export async function INTERNAL_getMasterPasswordRequired() { - const password_ = await INTERNAL_getMasterPassword() - if (!password_) throw new Error('No password set yet or expired.') - return password_ -} - -function INTERNAL_setPassword(newPassword: string) { - validatePasswordRequired(newPassword) - inMemoryPassword.value = newPassword -} - -/** Force erase the preexisting password and set a new one. */ -export async function resetPassword(newPassword: string) { - validatePasswordRequired(newPassword) - await database.resetSecret(newPassword) - INTERNAL_setPassword(newPassword) -} - -/** Set a password when no one has set it before. */ -export async function setPassword(newPassword: string) { - validatePasswordRequired(newPassword) - await database.encryptSecret(newPassword) - INTERNAL_setPassword(newPassword) -} - -/** Set the default password if no secret set before. */ -export async function setDefaultPassword() { - const hasSecret = await database.hasSecret() - if (hasSecret) return - const password = getDefaultWalletPassword() - await database.encryptSecret(password) - INTERNAL_setPassword(password) -} - -/** Clear the verified password in memory forces the user to re-enter the password. */ -export async function clearPassword() { - inMemoryPassword.value = '' -} - -/** Has set a password (could not be the default one). */ -export async function hasPassword() { - return database.hasSafeSecret() -} - -/** Has set a password (could be the default one). */ -export async function hasPasswordWithDefaultOne() { - return database.hasSecret() -} - -/** Has a verified password in memory. */ -export async function hasVerifiedPassword() { - return validatePassword(inMemoryPassword.value) -} - -/** Verify the given password. if successful, keep it in memory. */ -export async function verifyPassword(unverifiedPassword: string) { - if (inMemoryPassword.value === unverifiedPassword) return true - const valid = validate(await database.decryptSecret(unverifiedPassword)) - if (!valid) return false - INTERNAL_setPassword(unverifiedPassword) - return true -} - -/** Verify the given password. if successful, keep it in memory; otherwise, throw an error. */ -export async function verifyPasswordRequired(unverifiedPassword: string, message?: string) { - if (!(await verifyPassword(unverifiedPassword))) throw new Error(message ?? 'Wrong password') - return true -} - -export async function changePassword(oldPassword: string, newPassword: string, message?: string) { - validatePasswordRequired(newPassword) - await verifyPasswordRequired(oldPassword, message ?? 'Incorrect payment password.') - if (oldPassword === newPassword) throw new Error('Failed to set the same password as the old one.') - await database.updateSecret(oldPassword, newPassword) - INTERNAL_setPassword(newPassword) -} - -export async function validatePassword(unverifiedPassword: string) { - if (!unverifiedPassword) return false - if (unverifiedPassword.length < 6) return false - if (unverifiedPassword.length > 20) return false - return true -} - -async function validatePasswordRequired(unverifiedPassword: string) { - if (!validatePassword(unverifiedPassword)) throw new Error('The password is not satisfied the requirement.') - return true -} diff --git a/packages/mask/background/services/wallet/services/wallet/type.ts b/packages/mask/background/services/wallet/services/wallet/type.ts deleted file mode 100644 index 3d21e755b68b..000000000000 --- a/packages/mask/background/services/wallet/services/wallet/type.ts +++ /dev/null @@ -1,19 +0,0 @@ -export type { WalletRecord } from '../../../../../shared/definitions/wallet.js' - -export interface SecretRecord { - id: string - type: 'secret' - iv: ArrayBuffer - key: ArrayBuffer - /** - * The encrypted master secret. - */ - encrypted: ArrayBuffer - /** - * Indicate whether the default user password is used. - * - * true: the unsafe default user password is used. - * false: the default user password is not used or has been modified by the user. - */ - isUnsafe?: boolean -} diff --git a/packages/mask/background/tasks/Cancellable/CleanProfileAndAvatar.ts b/packages/mask/background/tasks/Cancellable/CleanProfileAndAvatar.ts deleted file mode 100644 index 65b38db681cd..000000000000 --- a/packages/mask/background/tasks/Cancellable/CleanProfileAndAvatar.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { consistentPersonaDBWriteAccess } from '../../database/persona/db.js' -import { ProfileIdentifier, lastDatabaseCleanupTime } from '@masknet/shared-base' -import { cleanAvatarDB } from '../../database/avatar-cache/cleanup.js' -import { hmr } from '../../../utils-pure/index.js' - -const { signal } = hmr(import.meta.webpackHot) -cleanProfileWithNoLinkedPersona(signal) - -async function cleanRelationDB(anotherList: Set) { - await consistentPersonaDBWriteAccess(async (t) => { - for await (const x of t.objectStore('relations')) { - const profileIdentifier = ProfileIdentifier.from(x.value.profile) - if (profileIdentifier.isSome()) { - if (anotherList.has(profileIdentifier.value)) x.delete() - } - } - }) -} - -async function cleanProfileWithNoLinkedPersona(signal: AbortSignal) { - if (lastDatabaseCleanupTime.value < Date.now() - 1000 * 60 * 60 * 24 * 3 /** 3 day */) return - lastDatabaseCleanupTime.value = Date.now() - - const cleanedList = new Set() - const expired = new Date(Date.now() - 1000 * 60 * 60 * 24 * 14 /** days */) - await consistentPersonaDBWriteAccess(async (t) => { - if (signal.aborted) throw new Error('Abort') - for await (const x of t.objectStore('profiles')) { - if (x.value.linkedPersona) continue - if (expired < x.value.updatedAt) continue - const id = ProfileIdentifier.from(x.value.identifier) - if (id.isSome()) cleanedList.add(id.value) - await x.delete() - } - }, false) - await cleanAvatarDB(cleanedList) - await cleanRelationDB(cleanedList) -} diff --git a/packages/mask/background/tasks/Cancellable/FetchRemoteFlags.ts b/packages/mask/background/tasks/Cancellable/FetchRemoteFlags.ts deleted file mode 100644 index 238914ecd617..000000000000 --- a/packages/mask/background/tasks/Cancellable/FetchRemoteFlags.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { startFetchRemoteFlag } from '@masknet/flags' -import { extensionRemoteFlagIO } from '../../../shared/helpers/remoteFlagIO.js' -import { hmr } from '../../../utils-pure/index.js' - -const { signal } = hmr(import.meta.webpackHot) - -startFetchRemoteFlag(extensionRemoteFlagIO, signal) diff --git a/packages/mask/background/tasks/Cancellable/InjectContentScripts_declarative.ts b/packages/mask/background/tasks/Cancellable/InjectContentScripts_declarative.ts deleted file mode 100644 index f9d9dfb90e7e..000000000000 --- a/packages/mask/background/tasks/Cancellable/InjectContentScripts_declarative.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { noop } from 'lodash-es' -import { hmr } from '../../../utils-pure/index.js' -import type { Scripting } from 'webextension-polyfill' -import { injectedScriptURL, fetchInjectContentScriptList, maskSDK_URL } from '../../utils/injectScript.js' -import { Sniffings } from '@masknet/shared-base' -import { definedSiteAdaptors } from '../../../shared/site-adaptors/definitions.js' - -const { signal } = hmr(import.meta.webpackHot) -if (typeof browser.scripting?.registerContentScripts === 'function') { - ;(async () => { - await unregisterExistingScripts() - await browser.scripting.registerContentScripts([ - ...prepareMainWorldScript('sdk', [''], maskSDK_URL), - ...prepareMainWorldScript( - 'script', - Array.from(definedSiteAdaptors.values(), (x) => x.declarativePermissions.origins).flat(), - injectedScriptURL, - ), - ...(await prepareContentScript([''])), - ]) - })() - signal.addEventListener('abort', unregisterExistingScripts) -} - -async function unregisterExistingScripts() { - await browser.scripting.unregisterContentScripts().catch(noop) -} - -function prepareMainWorldScript(name: string, matches: string[], url: string): Scripting.RegisteredContentScript[] { - if (Sniffings.is_firefox) return [] - const result: Scripting.RegisteredContentScript = { - id: 'injected_' + name, - allFrames: true, - js: [url], - persistAcrossSessions: false, - // @ts-expect-error Chrome API - world: 'MAIN', - runAt: 'document_start', - matches, - } - return [result] -} - -async function prepareContentScript(matches: string[]): Promise { - const xrayScript: Scripting.RegisteredContentScript = { - id: 'xray', - allFrames: true, - js: [injectedScriptURL], - persistAcrossSessions: false, - // @ts-expect-error Chrome API - world: 'ISOLATED', - runAt: 'document_start', - matches, - } - const content: Scripting.RegisteredContentScript = { - id: 'content', - allFrames: true, - js: await fetchInjectContentScriptList(), - persistAcrossSessions: false, - // @ts-expect-error Chrome API - world: 'ISOLATED', - runAt: 'document_idle', - matches, - } - if (globalThis.navigator?.userAgent.includes('Firefox')) return [xrayScript, content] - return [content] -} diff --git a/packages/mask/background/tasks/Cancellable/InjectContentScripts_imperative.ts b/packages/mask/background/tasks/Cancellable/InjectContentScripts_imperative.ts deleted file mode 100644 index ed321b442cd9..000000000000 --- a/packages/mask/background/tasks/Cancellable/InjectContentScripts_imperative.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { hmr } from '../../../utils-pure/hmr.js' -import type { ExtensionTypes, WebNavigation } from 'webextension-polyfill' -import { - evaluateContentScript, - ignoreInjectError, - injectUserScriptMV2, - injectedScriptURL, - maskSDK_URL, -} from '../../utils/injectScript.js' -import { Sniffings } from '@masknet/shared-base' -import { matchesAnySiteAdaptor } from '../../../shared/site-adaptors/definitions.js' - -const { signal } = hmr(import.meta.webpackHot) -if (typeof browser.scripting?.registerContentScripts === 'undefined') InjectContentScript(signal) - -async function onCommittedListener(arg: WebNavigation.OnCommittedDetailsType): Promise { - if (!arg.url.startsWith('http')) return - const contains = await browser.permissions.contains({ origins: [arg.url] }) - if (!contains) return - - const detail: ExtensionTypes.InjectDetails = { runAt: 'document_start', frameId: arg.frameId } - const err = ignoreInjectError(arg) - - if (matchesAnySiteAdaptor(arg.url)) { - // don't add await here. we don't want this to block the content script - if (Sniffings.is_firefox) { - browser.tabs.executeScript(arg.tabId, { ...detail, file: injectedScriptURL }).catch(err) - } else { - injectUserScriptMV2(injectedScriptURL) - .then(async (code) => browser.tabs.executeScript(arg.tabId, { ...detail, code })) - .catch(err) - } - } - injectUserScriptMV2(maskSDK_URL) - .then(async (code) => browser.tabs.executeScript(arg.tabId, { ...detail, code })) - .catch(err) - - evaluateContentScript(arg.tabId, arg.frameId).catch(err) -} -async function InjectContentScript(signal: AbortSignal) { - browser.webNavigation.onCommitted.addListener(onCommittedListener) - signal.addEventListener('abort', () => browser.webNavigation.onCommitted.removeListener(onCommittedListener)) -} diff --git a/packages/mask/background/tasks/Cancellable/SettingsListener.ts b/packages/mask/background/tasks/Cancellable/SettingsListener.ts deleted file mode 100644 index 42ad5c9166c5..000000000000 --- a/packages/mask/background/tasks/Cancellable/SettingsListener.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MaskMessages, type MaskSettingsEvents } from '@masknet/shared-base' -import { hmr } from '../../../utils-pure/index.js' -import { ToBeListened } from '../../../shared/legacy-settings/listener.js' - -const { signal } = hmr(import.meta.webpackHot) -const listeners = ToBeListened() -const keys = Object.keys(listeners) as Array -for (const key of keys) { - signal.addEventListener( - 'abort', - listeners[key].addListener((data) => MaskMessages.events[key].sendToAll(data as never)), - ) -} diff --git a/packages/mask/background/tasks/Cancellable/StartPluginHost.ts b/packages/mask/background/tasks/Cancellable/StartPluginHost.ts deleted file mode 100644 index d0f3f1cbefae..000000000000 --- a/packages/mask/background/tasks/Cancellable/StartPluginHost.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { startPluginWorker, type Plugin } from '@masknet/plugin-infra/background-worker' -import { createPluginDatabase } from '../../database/plugin-db/index.js' -import { createPluginHost, createSharedContext } from '../../../shared/plugin-infra/host.js' -import { hmr } from '../../../utils-pure/index.js' -import { getPluginMinimalModeEnabled } from '../../services/settings/old-settings-accessor.js' -import { hasHostPermission } from '../../services/helper/request-permission.js' - -const { signal } = hmr(import.meta.webpackHot) -startPluginWorker(createPluginHost(signal, createWorkerContext, getPluginMinimalModeEnabled, hasHostPermission)) - -function createWorkerContext( - pluginID: string, - def: Plugin.Worker.Definition, - signal: AbortSignal, -): Plugin.__Host.WorkerContext { - let storage: Plugin.Worker.DatabaseStorage = undefined! - - return { - ...createSharedContext(pluginID, signal), - getDatabaseStorage() { - return storage || (storage = createPluginDatabase(pluginID, signal)) - }, - } -} diff --git a/packages/mask/background/tasks/Cancellable/StartSandboxedPluginHost.ts b/packages/mask/background/tasks/Cancellable/StartSandboxedPluginHost.ts deleted file mode 100644 index e9a4cb2f09ae..000000000000 --- a/packages/mask/background/tasks/Cancellable/StartSandboxedPluginHost.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { None, Result, Some } from 'ts-results-es' -import { Flags } from '@masknet/flags' -import type { PluginID } from '@masknet/shared-base' -import { type Plugin, registerPlugin } from '@masknet/plugin-infra' -import { type BackgroundInstance, BackgroundPluginHost } from '@masknet/sandboxed-plugin-runtime/background' -import { hmr } from '../../../utils-pure/index.js' -import { createPluginDatabase } from '../../database/plugin-db/index.js' -import { createHostAPIs } from '../../../shared/sandboxed-plugin/host-api.js' - -const { signal } = hmr(import.meta.webpackHot) -let hot: - | Map< - string, - ( - hot: Promise<{ - default: Plugin.Worker.Definition - }>, - ) => void - > - | undefined -if (process.env.NODE_ENV === 'development') { - const sym = Symbol.for('sandboxed plugin bridge hot map') - hot = (globalThis as any)[sym] ??= new Map() -} - -if (Flags.sandboxedPluginRuntime) { - const host = new BackgroundPluginHost( - { - ...createHostAPIs(true), - createTaggedStorage: createPluginDatabase, - }, - process.env.NODE_ENV === 'development', - signal, - ) - host.__builtInPluginInfraBridgeCallback__ = __builtInPluginInfraBridgeCallback__ - host.onPluginListUpdate() -} -function __builtInPluginInfraBridgeCallback__(this: BackgroundPluginHost, id: string) { - let instance: BackgroundInstance | undefined - - const base: Plugin.Shared.Definition = { - enableRequirement: { - supports: { type: 'opt-out', sites: {} }, - target: 'beta', - }, - ID: id as PluginID, - // TODO: read i18n files - // TODO: read the name from the manifest - name: { fallback: '__generated__bridge__plugin__' + id }, - experimentalMark: true, - } - const def: Plugin.DeferredDefinition = { - ...base, - Worker: { - hotModuleReload: (reload) => hot?.set(id, reload), - async load() { - return { default: worker } - }, - }, - } - const worker: Plugin.Worker.Definition = { - ...base, - init: async (signal, context) => { - const [i] = await this.startPlugin_bridged(id, signal) - instance = i - }, - backup: { - async onBackup() { - if (!instance?.backupHandler) return None - const data = await instance.backupHandler.onBackup() - if (data === None) return None - if (!(data instanceof Some)) throw new TypeError('Backup handler must return Some(data) or None') - return data as Some - }, - onRestore(data) { - return Result.wrapAsync(async () => { - await instance?.backupHandler?.onRestore(data) - }) - }, - }, - } - if (hot?.has(id)) hot.get(id)!(def.Worker!.load()) - else registerPlugin(def) -} diff --git a/packages/mask/background/tasks/Cancellable/WalletAutoLock.ts b/packages/mask/background/tasks/Cancellable/WalletAutoLock.ts deleted file mode 100644 index dc4090fb0cf0..000000000000 --- a/packages/mask/background/tasks/Cancellable/WalletAutoLock.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrossIsolationMessages } from '@masknet/shared-base' -import { hmr } from '../../../utils-pure/index.js' -import { setAutoLockTimer } from '../../services/wallet/services/index.js' - -const { signal } = hmr(import.meta.webpackHot) -// Reset timer -CrossIsolationMessages.events.walletLockStatusUpdated.on(() => setAutoLockTimer(), { signal }) diff --git a/packages/mask/background/tasks/NotCancellable/OnInstall.ts b/packages/mask/background/tasks/NotCancellable/OnInstall.ts deleted file mode 100644 index 7688cd0047ca..000000000000 --- a/packages/mask/background/tasks/NotCancellable/OnInstall.ts +++ /dev/null @@ -1,42 +0,0 @@ -// ALL IMPORTS MUST BE DEFERRED -import type { DashboardRoutes } from '@masknet/shared-base' -import * as base from /* webpackDefer: true */ '@masknet/shared-base' - -type DashboardRoutes_Welcome = DashboardRoutes.Welcome extends `${infer T}` ? T : never -function openWelcome() { - const welcome: DashboardRoutes_Welcome = '/setup/welcome' - browser.tabs.create({ - url: browser.runtime.getURL(`dashboard.html#${welcome}`), - }) -} - -browser.runtime.onInstalled.addListener(async (detail) => { - if (detail.reason === 'install') { - openWelcome() - } else if (detail.reason === 'update') { - const connect = await import('../../services/site-adaptors/connect.js') - const groups = await connect.getOriginsWithoutPermission() - if (groups.length) openWelcome() - const localStorage = (globalThis as any).localStorage - if (localStorage) { - const backupPassword = localStorage.getItem('backupPassword') - if (backupPassword) { - const backupMethod = localStorage.getItem('backupMethod') - base.PersistentStorages.Settings.storage.backupConfig.setValue({ - backupPassword, - email: localStorage.getItem('email'), - phone: localStorage.getItem('phone'), - cloudBackupAt: backupMethod && backupMethod === 'cloud' ? localStorage.getItem('backupAt') : null, - localBackupAt: backupMethod && backupMethod === 'local' ? localStorage.getItem('backupAt') : null, - cloudBackupMethod: null, - }) - } - // remove old data after migrate - localStorage.removeItem('backupPassword') - localStorage.removeItem('backupMethod') - localStorage.removeItem('email') - localStorage.removeItem('phone') - localStorage.removeItem('backupAt') - } - } -}) diff --git a/packages/mask/background/tasks/NotCancellable/PendingTasks.ts b/packages/mask/background/tasks/NotCancellable/PendingTasks.ts deleted file mode 100644 index 3289cf47cff1..000000000000 --- a/packages/mask/background/tasks/NotCancellable/PendingTasks.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NetworkPluginID, PersistentStorages } from '@masknet/shared-base' -import { MessageStateType, type ReasonableMessage } from '@masknet/web3-shared-base' - -function checkMessages(messages: Array>) { - const pendingTasks = messages - .filter((x) => x.state === MessageStateType.NOT_DEPEND) - .sort((a, z) => a.createdAt.getTime() - z.createdAt.getTime()) - const length = Math.min(pendingTasks.length, 99) - const action = browser.action || browser.browserAction - action.setBadgeBackgroundColor({ - color: '#D92F0E', - }) - action.setBadgeText({ - text: length ? length.toString() : '', - }) -} - -async function watchTasks() { - const { storage } = PersistentStorages.Web3.createSubScope(`${NetworkPluginID.PLUGIN_EVM}_Message`, { - messages: {} as Record>, - }) - await storage.messages.initializedPromise - checkMessages(Object.values(storage.messages.value)) - storage.messages.subscription.subscribe(() => { - const messages = Object.values(storage.messages.value) - checkMessages(messages) - }) -} - -watchTasks() diff --git a/packages/mask/background/tasks/NotCancellable/PrintBuildFlags.ts b/packages/mask/background/tasks/NotCancellable/PrintBuildFlags.ts deleted file mode 100644 index 83e6c0e2e4da..000000000000 --- a/packages/mask/background/tasks/NotCancellable/PrintBuildFlags.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { env } from '@masknet/flags' -if (process.env.NODE_ENV === 'production') console.log(env) diff --git a/packages/mask/background/tasks/setup.hmr.ts b/packages/mask/background/tasks/setup.hmr.ts deleted file mode 100644 index 9d5e4dc370d8..000000000000 --- a/packages/mask/background/tasks/setup.hmr.ts +++ /dev/null @@ -1,10 +0,0 @@ -import './Cancellable/InjectContentScripts_imperative.js' -import './Cancellable/InjectContentScripts_declarative.js' -import './Cancellable/FetchRemoteFlags.js' -import './Cancellable/CleanProfileAndAvatar.js' -import './Cancellable/SettingsListener.js' -import './Cancellable/StartPluginHost.js' -import './Cancellable/StartSandboxedPluginHost.js' -import './Cancellable/WalletAutoLock.js' - -import.meta.webpackHot?.accept() diff --git a/packages/mask/background/tasks/setup.ts b/packages/mask/background/tasks/setup.ts deleted file mode 100644 index ddc76773a77e..000000000000 --- a/packages/mask/background/tasks/setup.ts +++ /dev/null @@ -1,5 +0,0 @@ -import './setup.hmr.js' - -// NotCancellable tasks here -import './NotCancellable/PrintBuildFlags.js' -import './NotCancellable/PendingTasks.js' diff --git a/packages/mask/background/tsconfig.json b/packages/mask/background/tsconfig.json deleted file mode 100644 index fa4fbde5e2de..000000000000 --- a/packages/mask/background/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "rootDir": "./", - "outDir": "../dist/background/", - "tsBuildInfoFile": "../dist/background.tsbuildinfo", - // MV3 = WebWorker, MV2 = DOM - // but we cannot build the same code in two env - "lib": ["ES2022", "WebWorker", "DOM.Iterable"] - }, - "include": ["./"], - "references": [ - { "path": "../shared/tsconfig.json" }, - { "path": "../utils-pure/tsconfig.json" }, - { "path": "../../encryption/tsconfig.json" }, - { "path": "../../mask-sdk/server/tsconfig.json" }, - { "path": "../../backup-format/tsconfig.json" }, - { "path": "../../gun-utils/tsconfig.json" }, - { "path": "../../flags/tsconfig.json" }, - { "path": "../../web3-telemetry/tsconfig.json" }, - { "path": "../../web3-providers/tsconfig.json" }, - { "path": "../../sandboxed-plugin-runtime/src/background/tsconfig.json" } - ] -} diff --git a/packages/mask/background/utils/deprecated-storage.ts b/packages/mask/background/utils/deprecated-storage.ts deleted file mode 100644 index d32d4e8058cf..000000000000 --- a/packages/mask/background/utils/deprecated-storage.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { timeout } from '@masknet/kit' -import { None, type Option, Some } from 'ts-results-es' - -/** - * Make sure that the storage is used serially. - */ -class MutexStorage { - private tasks: Array<() => void> = [] - private locked = false - - private lock() { - this.locked = true - } - private unlock() { - this.locked = false - } - private async continue() { - if (!this.locked) this.tasks.shift()?.() - } - public async getStorage(key: string) { - return new Promise(async (resolve, reject) => { - const callback = (e: unknown, storage?: T) => { - if (e) reject(e) - else resolve(storage) - this.unlock() - this.continue() - } - const run = async () => { - try { - this.lock() - const stored = await timeout( - browser.storage.local.get(key), - 5000, - `Get ${key} timeout in mutex storage.`, - ) - callback(null, stored?.[key] as T) - } catch (error) { - callback(error) - } - } - if (this.locked) this.tasks.push(run) - else run() - }) - } - public async setStorage(key: string, value: T) { - return new Promise(async (resolve, reject) => { - const callback = (e: unknown) => { - if (e) reject(e) - else resolve() - this.unlock() - this.continue() - } - const run = async () => { - try { - this.lock() - await timeout( - browser.storage.local.set({ [key]: value }), - 5000, - `Set ${key} to ${value} timeout in mutex storage.`, - ) - callback(null) - } catch (error) { - callback(error) - } - } - if (this.locked) this.tasks.push(run) - else run() - }) - } -} - -const storage = new MutexStorage() - -/** - * Avoid using this. - * @deprecated - * @internal - */ -export async function __deprecated__getStorage(key: string): Promise> { - if (typeof browser === 'undefined') return None - if (!browser.storage) return None - const value = await storage.getStorage(key) - if (value === undefined) return None - return Some(value as any) -} - -/** - * Avoid using this. - * @deprecated - * @internal - */ -export async function __deprecated__setStorage(key: string, value: T): Promise { - if (typeof browser === 'undefined') return - if (!browser.storage) return - return storage.setStorage(key, value) -} diff --git a/packages/mask/background/utils/injectScript.ts b/packages/mask/background/utils/injectScript.ts deleted file mode 100644 index fa3075f73a40..000000000000 --- a/packages/mask/background/utils/injectScript.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { memoize } from 'lodash-es' - -export const injectedScriptURL = '/js/injected-script.js' -export const maskSDK_URL = '/js/mask-sdk.js' -const contentScriptURL = '/generated__content__script.html' - -export async function evaluateContentScript(tabId: number | undefined, frameId?: number) { - if (browser.scripting) { - if (tabId === undefined) { - const activeTab = await browser.tabs.query({ active: true }) - if (!activeTab.length) return - tabId = activeTab[0].id - } - if (!tabId) return - await browser.scripting.executeScript({ - target: { tabId, frameIds: frameId ? [frameId] : undefined }, - files: await fetchInjectContentScriptList(), - world: 'ISOLATED', - }) - } else { - for (const script of await fetchInjectContentScriptList()) { - await browser.tabs.executeScript(tabId, { - file: script, - frameId, - runAt: 'document_idle', - }) - } - } -} -async function fetchInjectContentScriptList_raw() { - const contentScripts: string[] = [] - const html = await fetch(contentScriptURL).then((x) => x.text()) - // We're not going to use DOMParser because it is not available in MV3. - Array.from(html.matchAll(/', '') - .split('"') - .forEach((script) => { - if (!script) return - contentScripts.push(new URL(script, browser.runtime.getURL('')).pathname) - }) - return contentScripts -} -export const fetchInjectContentScriptList = - process.env.NODE_ENV === 'development' ? - fetchInjectContentScriptList_raw - : memoize(fetchInjectContentScriptList_raw) - -async function injectUserScriptMV2_raw(url: string) { - try { - const code = await fetch(url).then((x) => x.text()) - return `{ - const script = document.createElement("script") - script.innerHTML = ${JSON.stringify(code)} - document.documentElement.appendChild(script) - }` - } catch (error) { - console.error(error) - return `console.log('[Mask] User script ${url} failed to load.')` - } -} -export const injectUserScriptMV2 = - process.env.NODE_ENV === 'development' ? injectUserScriptMV2_raw : memoize(injectUserScriptMV2_raw) - -export function ignoreInjectError(arg: unknown): (reason: Error) => void { - return (error) => { - const ignoredErrorMessages = ['non-structured-clonable data', 'No tab with id'] - if (ignoredErrorMessages.some((x) => error.message.includes(x))) return - console.error('[Mask] Inject error', error.message, arg, error) - } -} diff --git a/packages/mask/content-script/components/CompositionDialog/Composition.tsx b/packages/mask/content-script/components/CompositionDialog/Composition.tsx deleted file mode 100644 index f01d45815308..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/Composition.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { useAsync } from 'react-use' -import { DialogContent, alpha } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { useCurrentPersonaConnectStatus, InjectedDialog, PersonaAction } from '@masknet/shared' -import { CrossIsolationMessages, EMPTY_OBJECT, MaskMessages, currentPersonaIdentifier } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import type { CompositionType } from '@masknet/plugin-infra/content-script' -import Services from '#services' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { useCurrentIdentity, useLastRecognizedIdentity } from '../DataSource/useActivatedUI.js' -import { CompositionDialogUI, type CompositionRef, E2EUnavailableReason } from './CompositionUI.js' -import { useCompositionClipboardRequest } from './useCompositionClipboardRequest.js' -import { useRecipientsList } from './useRecipientsList.js' -import { useSubmit } from './useSubmit.js' -import { usePersonasFromDB, useCurrentPersona } from '../../../shared-ui/hooks/index.js' -import { EncryptionMethodType } from './EncryptionMethodSelector.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' - -const useStyles = makeStyles()((theme) => ({ - dialogRoot: { - minWidth: 400, - width: 600, - boxShadow: 'none', - backgroundImage: 'none', - maxWidth: 'none', - }, - hideDialogRoot: { - visibility: 'hidden', - }, - dialogContent: { - padding: 0, - }, - persona: { - padding: 0, - background: alpha(theme.palette.maskColor.bottom, 0.8), - width: 'auto', - boxShadow: 'none', - }, -})) - -interface PostDialogProps { - type?: CompositionType - requireClipboardPermission?: boolean -} - -export function Composition({ type = 'timeline', requireClipboardPermission }: PostDialogProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - const currentIdentity = useCurrentIdentity()?.identifier - const allPersonas = usePersonasFromDB() - const lastRecognized = useLastRecognizedIdentity() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - const { value: connectStatus } = useCurrentPersonaConnectStatus( - allPersonas, - currentIdentifier, - Services.Helper.openDashboard, - lastRecognized, - ) - /** @deprecated */ - const { value: hasLocalKey } = useAsync( - async () => (currentIdentity ? Services.Identity.hasLocalKey(currentIdentity) : false), - [currentIdentity, connectStatus], - ) - - const [reason, setReason] = useState<'timeline' | 'popup' | 'reply'>('timeline') - const [initialMetas, setInitialMetas] = useState>(EMPTY_OBJECT) - - const [open, setOpen] = useState(false) - const [isOpenFromApplicationBoard, setIsOpenFromApplicationBoard] = useState(false) - - const onClose = useCallback(() => { - setOpen(false) - setInitialMetas(EMPTY_OBJECT) - - UI.current?.reset() - }, []) - - const { onQueryClipboardPermission, hasClipboardPermission, onRequestClipboardPermission } = - useCompositionClipboardRequest(requireClipboardPermission || false) - - useEffect(() => { - return MaskMessages.events.requestExtensionPermission.on(() => onQueryClipboardPermission?.()) - }, [onQueryClipboardPermission]) - - useEffect(() => { - return CrossIsolationMessages.events.compositionDialogEvent.on(({ reason, open, content, options }) => { - if ((reason !== 'reply' && reason !== type) || (reason === 'reply' && type === 'popup')) return - - setOpen(open) - setReason(reason) - setIsOpenFromApplicationBoard(!!options?.isOpenFromApplicationBoard) - setInitialMetas(options?.initialMetas ?? EMPTY_OBJECT) - if (content) UI.current?.setMessage(content) - if (options?.target) UI.current?.setEncryptionKind(options.target) - if (options?.startupPlugin) UI.current?.startPlugin(options.startupPlugin, options.startupPluginProps) - }) - }, [type]) - - useEffect(() => { - if (!open) return - - Telemetry.captureEvent(EventType.Access, EventID.EntryMaskComposeOpen) - Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeVisibleAll) - - return MaskMessages.events.replaceComposition.on((message) => { - if (!UI.current) return - UI.current.setMessage(message) - }) - }, [open]) - - const onSubmit_ = useSubmit(onClose, reason) - - const UI = useRef(null) - const networkSupport = activatedSiteAdaptorUI!.injection.newPostComposition?.supportedOutputTypes - const recipients = useRecipientsList() - const isE2E_Disabled = (encode: EncryptionMethodType) => { - if (!connectStatus.currentPersona && !connectStatus.hasPersona) return E2EUnavailableReason.NoPersona - if (!connectStatus.connected && connectStatus.hasPersona) return E2EUnavailableReason.NoConnection - if (!hasLocalKey && encode === EncryptionMethodType.Image) return E2EUnavailableReason.NoLocalKey - return - } - const persona = useCurrentPersona() - - return ( - - - - : null - } - /> - - - ) -} diff --git a/packages/mask/content-script/components/CompositionDialog/CompositionUI.tsx b/packages/mask/content-script/components/CompositionDialog/CompositionUI.tsx deleted file mode 100644 index 4404708f2151..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/CompositionUI.tsx +++ /dev/null @@ -1,356 +0,0 @@ -import { - forwardRef, - startTransition, - useCallback, - useEffect, - useImperativeHandle, - useMemo, - useRef, - useState, -} from 'react' -import { LoadingButton } from '@mui/lab' -import { Button, DialogActions, Typography, alpha } from '@mui/material' -import type { EncryptTargetPublic } from '@masknet/encryption' -import { Icons } from '@masknet/icons' -import { - TypedMessageEditor, - type TypedMessageEditorRef, - CharLimitIndicator, - PluginEntryRender, - type PluginEntryRenderRef, -} from '@masknet/shared' -import { CompositionContext, type CompositionType } from '@masknet/plugin-infra/content-script' -import { EncryptionTargetType, type ProfileInformation } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import type { SerializableTypedMessages, TypedMessage } from '@masknet/typed-message' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { SelectRecipientsUI } from '../shared/SelectRecipients/SelectRecipients.js' -import { EncryptionMethodSelector, EncryptionMethodType } from './EncryptionMethodSelector.js' -import { EncryptionTargetSelector } from './EncryptionTargetSelector.js' -import type { EncryptTargetE2EFromProfileIdentifier } from '../../../background/services/crypto/encryption.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - '& > *': { - height: '36px !important', - }, - minHeight: 450, - maxHeight: 464, - height: 464, - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(2), - }, - flex: { - width: '100%', - display: 'flex', - alignItems: 'center', - flexWrap: 'wrap', - }, - between: { - justifyContent: 'space-between', - }, - optionTitle: { - lineHeight: '18px', - fontSize: 14, - color: theme.palette.text.secondary, - marginRight: 12, - }, - editorWrapper: { - flex: 1, - width: 568, - background: theme.palette.maskColor.bottom, - padding: 0, - boxSizing: 'border-box', - borderRadius: 8, - marginBottom: 16, - }, - icon: { - width: 18, - height: 18, - fill: theme.palette.text.buttonText, - }, - action: { - height: 68, - padding: '0 16px', - boxShadow: - theme.palette.mode === 'light' ? - ' 0px 0px 20px rgba(0, 0, 0, 0.05)' - : '0px 0px 20px rgba(255, 255, 255, 0.12);', - background: alpha(theme.palette.maskColor.bottom, 0.8), - justifyContent: 'space-between', - display: 'flex', - }, - personaAction: { - flex: 1, - }, -})) - -export interface LazyRecipients { - request(): void - recipients?: ProfileInformation[] -} -export interface CompositionProps { - type: CompositionType - maxLength?: number - onSubmit(data: SubmitComposition): Promise - onChange?(message: TypedMessage): void - isOpenFromApplicationBoard: boolean - e2eEncryptionDisabled(encode: EncryptionMethodType): E2EUnavailableReason | undefined - recipients: LazyRecipients - // Enabled features - supportTextEncoding: boolean - supportImageEncoding: boolean - // Requirements - requireClipboardPermission?: boolean - hasClipboardPermission?: boolean - onRequestClipboardPermission?(): void - onQueryClipboardPermission?(): void - initialMetas?: Record - personaAction?: React.ReactNode -} -export interface SubmitComposition { - target: EncryptTargetPublic | EncryptTargetE2EFromProfileIdentifier - content: SerializableTypedMessages - encode: 'text' | 'image' - version: -37 | -38 -} -export interface CompositionRef { - setMessage(message: SerializableTypedMessages): void - setEncryptionKind(kind: EncryptionTargetType): void - startPlugin(id: string, props?: any): void - reset(): void -} -export const CompositionDialogUI = forwardRef( - function CompositionDialogUI(props, ref) { - const { classes, cx } = useStyles() - const t = useMaskSharedTrans() - - const [currentPostSize, __updatePostSize] = useState(0) - - const [isSelectRecipientOpen, setSelectRecipientOpen] = useState(false) - const Editor = useRef(null) - const PluginEntry = useRef(null) - - const [sending, setSending] = useState(false) - - const updatePostSize = useCallback((size: number) => { - startTransition(() => __updatePostSize(size)) - }, []) - - const { encodingKind, setEncoding } = useEncryptionEncode(props) - const { setEncryptionKind, encryptionKind, recipients, setRecipients } = useSetEncryptionKind( - props, - encodingKind, - ) - const reset = useCallback(() => { - startTransition(() => { - Editor.current?.reset() - setEncryptionKind(EncryptionTargetType.Public) - setRecipients([]) - // Don't clean up the image/text selection across different encryption. - // setEncoding('text') - setSending(false) - }) - }, []) - - const refItem = useMemo( - (): CompositionRef => ({ - setMessage: (msg) => { - if (Editor.current) Editor.current.value = msg - }, - setEncryptionKind, - startPlugin: (id, props) => { - PluginEntry.current?.openPlugin(id, props) - }, - reset, - }), - [reset], - ) - - useImperativeHandle(ref, () => refItem, [refItem]) - - useEffect(() => { - if (!props.initialMetas || !Editor.current) return - for (const [meta, data] of Object.entries(props.initialMetas)) { - Editor.current.attachMetadata(meta, data) - } - }, [props.initialMetas, Editor.current]) - - const context = useMemo( - (): CompositionContext => ({ - type: props.type, - getMetadata: () => Editor.current?.value.meta, - attachMetadata: (meta, data) => Editor.current?.attachMetadata(meta, data), - dropMetadata: (meta) => Editor.current?.dropMetadata(meta), - }), - [props.type, Editor.current], - ) - - const submitAvailable = currentPostSize > 0 && currentPostSize < (props.maxLength ?? Number.POSITIVE_INFINITY) - const onSubmit = useCallback(() => { - if (!Editor.current) return - setSending(true) - props - .onSubmit({ - content: Editor.current.value, - encode: encodingKind, - target: - encryptionKind === EncryptionTargetType.Public ? - { type: 'public' } - : { - type: 'E2E', - target: recipients.map((x) => ({ - profile: x.identifier, - persona: x.linkedPersona, - })), - }, - version: encodingKind === EncryptionMethodType.Text ? -37 : -38, - }) - .finally(reset) - }, [encodingKind, encryptionKind, recipients, props.onSubmit]) - return ( - -
-
- { - Editor.current = element - if (element) updatePostSize(element.estimatedLength) - }} - onChange={(message) => { - startTransition(() => props.onChange?.(message)) - updatePostSize(Editor.current?.estimatedLength || 0) - }} - /> -
- -
- {t.plugins()} - -
-
- { - setEncryptionKind(target) - if (target === EncryptionTargetType.E2E) { - Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeVisibleSelected) - setSelectRecipientOpen(true) - } - if (target === EncryptionTargetType.Public) { - Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeVisibleAll) - } - if (target === EncryptionTargetType.Self) { - Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeVisiblePrivate) - } - }} - /> - - setSelectRecipientOpen(false)} - disabled={sending} - items={props.recipients} - selected={recipients} - onSetSelected={setRecipients} - /> -
-
- -
-
- - {props.personaAction ? -
{props.personaAction}
- :
} -
- {props.maxLength ? - - : null} - {props.requireClipboardPermission && !props.hasClipboardPermission ? - - : null} - }> - {t.post_dialog__button()} - -
- - - ) - }, -) - -export enum E2EUnavailableReason { - // These reasons only applies to E2E encryption. - NoPersona = 1, - NoLocalKey = 2, - NoConnection = 3, -} -function useSetEncryptionKind(props: Pick, encoding: EncryptionMethodType) { - const [internal_encryptionKind, setEncryptionKind] = useState(EncryptionTargetType.Public) - // TODO: Change to ProfileIdentifier - const [recipients, setRecipients] = useState([]) - - let encryptionKind = internal_encryptionKind - if (encryptionKind === EncryptionTargetType.E2E && recipients.length === 0) - encryptionKind = EncryptionTargetType.Self - if (props.e2eEncryptionDisabled(encoding)) encryptionKind = EncryptionTargetType.Public - - return { - recipients, - setRecipients, - encryptionKind, - setEncryptionKind, - } -} - -function useEncryptionEncode(props: Pick) { - const [encoding, setEncoding] = useState( - props.supportTextEncoding ? EncryptionMethodType.Text : EncryptionMethodType.Image, - ) - - const imagePayloadSelected = - props.supportImageEncoding && (encoding === EncryptionMethodType.Image || !props.supportTextEncoding) - // XOR - const imagePayloadReadonly = - (props.supportImageEncoding && !props.supportTextEncoding) || - (!props.supportImageEncoding && props.supportTextEncoding) - const imagePayloadVisible = props.supportImageEncoding - const encodingKind = imagePayloadSelected ? EncryptionMethodType.Image : EncryptionMethodType.Text - - return { - encodingKind, - imagePayloadSelected, - imagePayloadReadonly, - imagePayloadVisible, - setEncoding, - } -} diff --git a/packages/mask/content-script/components/CompositionDialog/EncryptionMethodSelector.tsx b/packages/mask/content-script/components/CompositionDialog/EncryptionMethodSelector.tsx deleted file mode 100644 index f4b96d665602..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/EncryptionMethodSelector.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { makeStyles } from '@masknet/theme' -import { Typography } from '@mui/material' -import { PopoverListTrigger } from './PopoverListTrigger.js' -import { PopoverListItem } from './PopoverListItem.js' -import { type PropsWithChildren, useState } from 'react' - -const useStyles = makeStyles()((theme) => ({ - optionTitle: { - fontFamily: 'sans-serif', - fontSize: 14, - lineHeight: '18px', - color: theme.palette.text.secondary, - marginRight: 12, - }, - divider: { - width: '100%', - height: 1, - background: theme.palette.divider, - margin: '8px 0', - }, -})) - -interface EncryptionMethodSelectorProps extends PropsWithChildren<{}> { - onChange(v: EncryptionMethodType): void - method: EncryptionMethodType - textDisabled: boolean - imageDisabled: boolean -} -export enum EncryptionMethodType { - Text = 'text', - Image = 'image', -} -export function EncryptionMethodSelector(props: EncryptionMethodSelectorProps) { - const t = useMaskSharedTrans() - const { classes } = useStyles() - const [anchorEl, setAnchorEl] = useState(null) - - return ( - <> - {t.post_dialog_encryption_method()} - - -
- - - - ) -} diff --git a/packages/mask/content-script/components/CompositionDialog/EncryptionTargetSelector.tsx b/packages/mask/content-script/components/CompositionDialog/EncryptionTargetSelector.tsx deleted file mode 100644 index 5586e973061c..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/EncryptionTargetSelector.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useState } from 'react' -import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { unreachable } from '@masknet/kit' -import { ConnectPersonaBoundary } from '@masknet/shared' -import { EncryptionTargetType, currentPersonaIdentifier } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { PopoverListTrigger } from './PopoverListTrigger.js' -import { PopoverListItem } from './PopoverListItem.js' -import { E2EUnavailableReason } from './CompositionUI.js' -import { usePersonasFromDB } from '../../../shared-ui/hooks/usePersonasFromDB.js' -import { useLastRecognizedIdentity } from '../DataSource/useActivatedUI.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import Services from '#services' - -const useStyles = makeStyles()((theme) => ({ - optionTitle: { - lineHeight: '18px', - fontSize: 14, - color: theme.palette.text.secondary, - marginRight: 12, - }, - divider: { - width: '100%', - height: 1, - background: theme.palette.divider, - margin: '8px 0', - }, - mainTitle: { - color: theme.palette.text.primary, - fontWeight: 700, - }, - flex: { - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: 4, - boxSizing: 'border-box', - }, - create: { - cursor: 'pointer', - fontWeight: 700, - color: theme.palette.maskColor.primary, - textAlign: 'right', - }, - rightIcon: { - marginLeft: 'auto', - }, -})) - -interface EncryptionTargetSelectorProps { - target: EncryptionTargetType - e2eDisabled: E2EUnavailableReason | undefined - onClick(v: EncryptionTargetType): void - selectedRecipientLength: number -} -export function EncryptionTargetSelector(props: EncryptionTargetSelectorProps) { - const t = useMaskSharedTrans() - const { classes } = useStyles() - - const [anchorEl, setAnchorEl] = useState(null) - const allPersonas = usePersonasFromDB() - const lastRecognized = useLastRecognizedIdentity() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - - const e2eDisabledMessage = - props.e2eDisabled && props.e2eDisabled !== E2EUnavailableReason.NoLocalKey ? -
- {t.persona_required()} - - - {(s) => { - if (!s.hasPersona) return {t.create()} - // TODO: how to handle verified - if (!s.connected || !s.verified) - return {t.connect()} - - return null - }} - -
- : null - const noLocalKeyMessage = props.e2eDisabled === E2EUnavailableReason.NoLocalKey && ( -
- {t.compose_no_local_key()} -
- ) - - const selectedTitle = () => { - const selected = props.target - const shareWithNum = props.selectedRecipientLength - if (selected === EncryptionTargetType.E2E) - return shareWithNum > 1 ? - t.compose_shared_friends_other({ count: shareWithNum }) - : t.compose_shared_friends_one() - else if (selected === EncryptionTargetType.Public) return t.compose_encrypt_visible_to_all() - else if (selected === EncryptionTargetType.Self) return t.compose_encrypt_visible_to_private() - unreachable(selected) - } - return ( - <> - {t.post_dialog_visible_to()} - { - props.onClick(v as EncryptionTargetType) - if (v === EncryptionTargetType.E2E) setAnchorEl(null) - }}> - -
- - {e2eDisabledMessage} - {noLocalKeyMessage} -
- } - disabled={!!props.e2eDisabled} - value={EncryptionTargetType.E2E} - title={t.compose_encrypt_visible_to_share()} - subTitle={t.compose_encrypt_visible_to_share_sub()} - onClick={(v: string) => { - if (props.e2eDisabled) return - props.onClick(v as EncryptionTargetType) - if (v === EncryptionTargetType.E2E) setAnchorEl(null) - }} - /> - {e2eDisabledMessage} - {noLocalKeyMessage} - - - ) -} diff --git a/packages/mask/content-script/components/CompositionDialog/PopoverListItem.tsx b/packages/mask/content-script/components/CompositionDialog/PopoverListItem.tsx deleted file mode 100644 index 59648ec68b93..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/PopoverListItem.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { makeStyles } from '@masknet/theme' -import { Box, FormControlLabel, Radio, Typography } from '@mui/material' -import { type ReactNode } from 'react' - -const useStyles = makeStyles()((theme) => ({ - root: { marginLeft: 'unset', marginRight: 'unset' }, - label: { - display: 'flex', - alignItems: 'center', - flex: 1, - }, - mainTitle: { - fontWeight: 700, - fontSize: 14, - }, - subTitle: { - whiteSpace: 'nowrap', - fontSize: 14, - }, - pointer: { - cursor: 'pointer', - }, -})) -interface PopoverListItemProps { - value: string - itemTail?: ReactNode - title: string - subTitle?: string - disabled?: boolean - onClick?: (v: string) => void -} -export function PopoverListItem(props: PopoverListItemProps) { - const { title, subTitle, value, itemTail, disabled } = props - const { classes, cx } = useStyles() - return ( - } - onClick={() => props.onClick?.(value)} - label={ - <> - - {title} - {subTitle} - - {itemTail} - - } - /> - ) -} diff --git a/packages/mask/content-script/components/CompositionDialog/PopoverListTrigger.tsx b/packages/mask/content-script/components/CompositionDialog/PopoverListTrigger.tsx deleted file mode 100644 index 44339b9b2163..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/PopoverListTrigger.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Icons } from '@masknet/icons' -import { makeStyles, usePortalShadowRoot } from '@masknet/theme' -import { RadioGroup, Typography, Popover } from '@mui/material' -import type { PropsWithChildren } from 'react' - -const useStyles = makeStyles()((theme) => ({ - popper: { - overflow: 'visible', - boxShadow: '0px 0px 16px 0px rgba(101, 119, 134, 0.2)', - borderRadius: 4, - }, - paperRoot: { - background: theme.palette.maskColor.bottom, - boxShadow: - theme.palette.mode === 'dark' ? - '0px 4px 30px rgba(255, 255, 255, 0.15)' - : '0px 4px 30px rgba(0, 0, 0, 0.1)', - }, - popperText: { - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - display: 'flex', - justifyContent: 'flex-end', - alignItems: 'center', - gap: 4, - cursor: 'pointer', - padding: 0, - border: 0, - background: 'none', - minWidth: 70, - }, - paper: { - width: 280, - padding: 12, - boxSizing: 'border-box', - }, - selected: { - lineHeight: '18px', - fontSize: 14, - fontWeight: 700, - color: theme.palette.maskColor.main, - }, -})) - -interface PopoverListTriggerProp extends PropsWithChildren<{}> { - anchorEl: HTMLElement | null - setAnchorEl(v: HTMLElement | null): void - onChange(v: string): void - selected: string - selectedTitle: string | undefined -} - -export function PopoverListTrigger({ - anchorEl, - selected, - selectedTitle, - children, - setAnchorEl, - onChange, -}: PopoverListTriggerProp) { - const { classes } = useStyles() - - return usePortalShadowRoot((ref) => ( - <> - - setAnchorEl(null)} - anchorOrigin={{ vertical: 'top', horizontal: 'right' }} - transformOrigin={{ vertical: 'bottom', horizontal: 'right' }}> - onChange(e.target.value)}> - {children} - - - - )) -} diff --git a/packages/mask/content-script/components/CompositionDialog/SteganographyPayload.ts b/packages/mask/content-script/components/CompositionDialog/SteganographyPayload.ts deleted file mode 100644 index 4cf33b7550a8..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/SteganographyPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GrayscaleAlgorithm, SteganographyPreset } from '@masknet/encryption' -import { SteganographyPresetImage } from '../../resources/image-payload/index.js' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import Services from '#services' -import { downloadUrl } from '../../utils/downloadUrl.js' - -export async function SteganographyPayload(data: string | Uint8Array) { - const password = activatedSiteAdaptorUI!.configuration.steganography?.password?.() || 'mask' - const preset = typeof data === 'string' ? SteganographyPreset.Preset2022 : SteganographyPreset.Preset2023 - const blankImageUrl = SteganographyPresetImage[preset] - if (!blankImageUrl) throw new Error('No preset image found.') - const blankImage = await downloadUrl(blankImageUrl).then((x) => x.arrayBuffer()) - const secretImage = await Services.Crypto.steganographyEncodeImage(new Uint8Array(blankImage), { - preset, - data, - password, - grayscaleAlgorithm: - activatedSiteAdaptorUI!.configuration.steganography?.grayscaleAlgorithm ?? GrayscaleAlgorithm.NONE, - }) - const blob = new Blob([secretImage], { type: 'image/png' }) - return blob -} diff --git a/packages/mask/content-script/components/CompositionDialog/useCompositionClipboardRequest.tsx b/packages/mask/content-script/components/CompositionDialog/useCompositionClipboardRequest.tsx deleted file mode 100644 index 0457404d4466..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/useCompositionClipboardRequest.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useCallback } from 'react' -import { useAsyncRetry } from 'react-use' -import Services from '#services' -import type { CompositionProps } from './CompositionUI.js' -import { MaskMessages } from '@masknet/shared-base' - -export function useCompositionClipboardRequest( - requireClipboardPermission: boolean, -): Pick< - CompositionProps, - | 'hasClipboardPermission' - | 'requireClipboardPermission' - | 'onRequestClipboardPermission' - | 'onQueryClipboardPermission' -> { - const { retry, value: hasClipboardPermission = true } = useAsyncRetry(async () => { - if (!requireClipboardPermission) return true - return Services.Helper.queryExtensionPermission({ permissions: ['clipboardRead'] }) - }, [requireClipboardPermission]) - - const onRequestClipboardPermission = useCallback(() => { - if (!requireClipboardPermission) return - Services.Helper.requestExtensionPermissionFromContentScript({ permissions: ['clipboardRead'] }).finally(() => { - MaskMessages.events.requestExtensionPermission.sendToAll({ permissions: ['clipboardRead'] }) - }) - }, [requireClipboardPermission]) - - return { - onQueryClipboardPermission: retry, - requireClipboardPermission, - hasClipboardPermission, - onRequestClipboardPermission, - } -} diff --git a/packages/mask/content-script/components/CompositionDialog/useRecipientsList.ts b/packages/mask/content-script/components/CompositionDialog/useRecipientsList.ts deleted file mode 100644 index 3c0c1ffeade4..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/useRecipientsList.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useCallback, useMemo, useState } from 'react' -import { useAsync } from 'react-use' -import type { ProfileInformation } from '@masknet/shared-base' -import Services from '#services' -import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' -import type { LazyRecipients } from './CompositionUI.js' - -export function useRecipientsList(): LazyRecipients { - const current = useCurrentIdentity()?.identifier - const { value: hasRecipients = false } = useAsync( - async () => (current ? Services.Crypto.hasRecipientAvailable(current) : undefined), - [current], - ) - const [recipients, setRecipients] = useState(undefined) - const request = useCallback(() => { - if (!current) return - if (recipients) return - Services.Crypto.getRecipients(current).then(setRecipients) - }, [current, !!recipients]) - - return useMemo( - () => ({ - request, - recipients, - hasRecipients, - }), - [request, recipients, hasRecipients], - ) -} diff --git a/packages/mask/content-script/components/CompositionDialog/useSelectedRecipientsList.ts b/packages/mask/content-script/components/CompositionDialog/useSelectedRecipientsList.ts deleted file mode 100644 index c1164fdfb67e..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/useSelectedRecipientsList.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { usePostInfoDetails } from '@masknet/plugin-infra/content-script' -import Services from '#services' -import { EMPTY_LIST } from '@masknet/shared-base' - -export function useSelectedRecipientsList() { - const iv = usePostInfoDetails.postIVIdentifier() - return useAsyncRetry(async () => (iv ? Services.Crypto.getIncompleteRecipientsOfPost(iv) : EMPTY_LIST), [iv]) -} diff --git a/packages/mask/content-script/components/CompositionDialog/useSubmit.ts b/packages/mask/content-script/components/CompositionDialog/useSubmit.ts deleted file mode 100644 index 1a33f05194b1..000000000000 --- a/packages/mask/content-script/components/CompositionDialog/useSubmit.ts +++ /dev/null @@ -1,114 +0,0 @@ -import Services from '#services' -import { encodeByNetwork } from '@masknet/encryption' -import { PluginID, Sniffings, SOCIAL_MEDIA_NAME } from '@masknet/shared-base' -import type { Meta } from '@masknet/typed-message' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import { useCallback } from 'react' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { useLastRecognizedIdentity } from '../DataSource/useActivatedUI.js' -import type { SubmitComposition } from './CompositionUI.js' -import { SteganographyPayload } from './SteganographyPayload.js' - -export function useSubmit(onClose: () => void, reason: 'timeline' | 'popup' | 'reply') { - const t = useMaskSharedTrans() - const lastRecognizedIdentity = useLastRecognizedIdentity() - - return useCallback( - async (info: SubmitComposition) => { - const { content, encode, target } = info - if (encode === 'image' && !lastRecognizedIdentity) throw new Error('No Current Profile') - - // rawEncrypted is either string or Uint8Array - // string is the old format, Uint8Array is the new format. - const rawEncrypted = await Services.Crypto.encryptTo( - info.version, - content, - target, - lastRecognizedIdentity.identifier, - activatedSiteAdaptorUI!.encryptPayloadNetwork, - ) - // Since we cannot directly send binary in the composition box, we need to encode it into a string. - const encrypted = encodeByNetwork(activatedSiteAdaptorUI!.encryptPayloadNetwork, rawEncrypted) - - const decoratedText = - encode === 'image' ? - decorateEncryptedText('', t, content.meta) - : decorateEncryptedText(encrypted, t, content.meta) - - const options = { interpolation: { escapeValue: false } } - const defaultText: string = - encode === 'image' ? - t.additional_post_box__encrypted_post_pre({ - encrypted: 'https://mask.io/', - }) - : t.additional_post_box__encrypted_post_pre({ encrypted, ...options }) - const mediaObject = - encode === 'image' ? - // We can send raw binary through the image, but for the text we still use the old way. - // For text, it must send the text _after_ encodeByNetwork, otherwise it will break backward compatibility. - await SteganographyPayload(typeof rawEncrypted === 'string' ? encrypted : rawEncrypted) - : undefined - - if (encode === 'image') { - if (!mediaObject) throw new Error('Failed to create image payload.') - // Don't await this, otherwise the dialog won't disappear - activatedSiteAdaptorUI?.automation.nativeCompositionDialog?.attachImage?.(mediaObject, { - recover: true, - relatedTextPayload: decoratedText || defaultText, - reason, - }) - } else { - activatedSiteAdaptorUI?.automation.nativeCompositionDialog?.attachText?.(decoratedText || defaultText, { - recover: true, - reason, - }) - } - - if (content.meta?.has(`${PluginID.RedPacket}:1`) || content.meta?.has(`${PluginID.RedPacket}_nft:1`)) - Telemetry.captureEvent(EventType.Interact, EventID.EntryAppLuckSend) - Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeEncrypt) - - onClose() - }, - [t, lastRecognizedIdentity, onClose, reason], - ) -} - -// TODO: Provide API to plugin to post-process post content, -// then we can move these -PreText's and meta readers into plugin's own context -function decorateEncryptedText( - encrypted: string, - t: ReturnType, - meta?: Meta, -): string | null { - if (!meta) return null - const hasOfficialAccount = Sniffings.is_twitter_page || Sniffings.is_facebook_page - const officialAccount = Sniffings.is_twitter_page ? t.twitter_account() : t.facebook_account() - const token = meta.has(`${PluginID.RedPacket}:1`) ? t.redpacket_a_token() : t.redpacket_an_nft() - const sns = SOCIAL_MEDIA_NAME[activatedSiteAdaptorUI?.networkIdentifier!] - const options = { interpolation: { escapeValue: false }, token, sns } - - // Note: since this is in the composition stage, we can assume plugins don't insert old version of meta. - if (meta.has(`${PluginID.RedPacket}:1`) || meta.has(`${PluginID.RedPacket}_nft:1`)) { - return hasOfficialAccount ? - t.additional_post_box__encrypted_post_pre_red_packet_sns_official_account({ - encrypted, - account: officialAccount, - ...options, - }) - : t.additional_post_box__encrypted_post_pre_red_packet({ encrypted, ...options }) - } else if (meta.has(`${PluginID.FileService}:3`)) { - return hasOfficialAccount ? - t.additional_post_box__encrypted_post_pre_file_service_sns_official_account({ - encrypted, - ...options, - }) - : t.additional_post_box__encrypted_post_pre_file_service({ - encrypted, - ...options, - }) - } - return null -} diff --git a/packages/mask/content-script/components/DataSource/useActivatedUI.ts b/packages/mask/content-script/components/DataSource/useActivatedUI.ts deleted file mode 100644 index cb69e5fc5760..000000000000 --- a/packages/mask/content-script/components/DataSource/useActivatedUI.ts +++ /dev/null @@ -1,124 +0,0 @@ -import type { IdentityResolved } from '@masknet/plugin-infra' -import { MaskMessages, ValueRef, type ProfileInformation } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { NextIDProof } from '@masknet/web3-providers' -import { FontSize, ThemeColor, ThemeMode, type ThemeSettings } from '@masknet/web3-shared-base' -import { useQuery } from '@tanstack/react-query' -import { first, isEqual } from 'lodash-es' -import { useEffect, useMemo } from 'react' -import { useSubscription, type Subscription } from 'use-subscription' -import Services from '#services' -import { activatedSiteAdaptorUI, activatedSiteAdaptor_state } from '../../site-adaptor-infra/index.js' - -async function queryPersonaFromDB(identityResolved: IdentityResolved) { - if (!identityResolved.identifier) return - return Services.Identity.queryPersonaByProfile(identityResolved.identifier) -} - -async function queryPersonasFromNextID(identityResolved: IdentityResolved) { - if (!identityResolved.identifier) return - if (!activatedSiteAdaptorUI?.configuration.nextIDConfig?.platform) return - return NextIDProof.queryAllExistedBindingsByPlatform( - activatedSiteAdaptorUI.configuration.nextIDConfig.platform, - identityResolved.identifier.userId, - ) -} - -const CurrentIdentitySubscription: Subscription = { - getCurrentValue() { - const all = activatedSiteAdaptor_state!.profiles.value - const current = (activatedSiteAdaptorUI!.collecting.identityProvider?.recognized || defaultIdentityResolved) - .value.identifier - return all.find((i) => i.identifier === current) || first(all) - }, - subscribe(sub) { - const a = activatedSiteAdaptor_state!.profiles.addListener(sub) - const b = activatedSiteAdaptorUI!.collecting.identityProvider?.recognized.addListener(sub) - return () => [a(), b?.()] - }, -} - -const defaults = { - mode: ThemeMode.Light, - size: FontSize.Normal, - color: ThemeColor.Blue, -} -const defaultIdentityResolved = new ValueRef({}, isEqual) -const defaultThemeSettings = new ValueRef>({}, isEqual) - -export function useCurrentIdentity() { - return useSubscription(CurrentIdentitySubscription) -} - -export function useLastRecognizedIdentity() { - return useValueRef(activatedSiteAdaptorUI!.collecting.identityProvider?.recognized || defaultIdentityResolved) -} - -export function useCurrentVisitingIdentity() { - return useValueRef( - activatedSiteAdaptorUI!.collecting.currentVisitingIdentityProvider?.recognized || defaultIdentityResolved, - ) -} - -/** - * Get the social identity of the given identity - */ -export function useSocialIdentity(identity: IdentityResolved | null | undefined) { - const result = useQuery({ - enabled: !!identity, - queryKey: ['social-identity', identity], - queryFn: async () => { - try { - if (!identity) return null - - const persona = await queryPersonaFromDB(identity) - if (!persona) return identity - - const bindings = await queryPersonasFromNextID(identity) - if (!bindings) return identity - - const personaBindings = - bindings?.filter((x) => x.persona === persona?.identifier.publicKeyAsHex.toLowerCase()) ?? [] - return { - ...identity, - publicKey: persona?.identifier.publicKeyAsHex, - hasBinding: personaBindings.length > 0, - binding: first(personaBindings), - } - } catch { - return identity - } - }, - }) - - useEffect(() => MaskMessages.events.ownProofChanged.on(() => result.refetch()), [result.refetch]) - - return result -} - -export function useSocialIdentityByUserId(userId?: string) { - const { data: identity } = useQuery({ - queryKey: ['social-identity', 'by-id', userId], - enabled: !!userId, - queryFn: async () => { - return activatedSiteAdaptorUI!.utils.getUserIdentity?.(userId!) - }, - networkMode: 'always', - }) - return useSocialIdentity(identity) -} - -export function useThemeSettings() { - const themeSettings = useValueRef( - (activatedSiteAdaptorUI?.collecting.themeSettingsProvider?.recognized || - defaultThemeSettings) as ValueRef, - ) - return useMemo( - () => ({ - ...defaults, - ...activatedSiteAdaptorUI?.configuration.themeSettings, - ...themeSettings, - }), - [activatedSiteAdaptorUI?.configuration.themeSettings, themeSettings], - ) -} diff --git a/packages/mask/content-script/components/DataSource/usePersonaPerSiteConnectStatus.ts b/packages/mask/content-script/components/DataSource/usePersonaPerSiteConnectStatus.ts deleted file mode 100644 index bd47826e604f..000000000000 --- a/packages/mask/content-script/components/DataSource/usePersonaPerSiteConnectStatus.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useCallback } from 'react' -import { useAsyncRetry } from 'react-use' -import type { PersonaPerSiteConnectStatus } from '@masknet/shared' -import type { PersonaInformation } from '@masknet/shared-base' -import Services from '#services' -import { useLastRecognizedIdentity } from './useActivatedUI.js' -import { usePersonasFromDB } from '../../../shared-ui/hooks/usePersonasFromDB.js' -import { useSetupGuideStatus } from '../GuideStep/useSetupGuideStatus.js' - -export function usePersonaPerSiteConnectStatus() { - const personas = usePersonasFromDB() - const lastState = useSetupGuideStatus() - const lastRecognized = useLastRecognizedIdentity() - const username = lastState.username || lastRecognized.identifier?.userId - const checkSiteConnectedToCurrentPersona = useCallback( - (persona: PersonaInformation) => - username ? persona.linkedProfiles.some((x) => x.identifier.userId === username) : false, - [username], - ) - - return useAsyncRetry(async () => { - const currentPersonaIdentifier = await Services.Settings.getCurrentPersonaIdentifier() - const currentPersona = (await Services.Identity.queryOwnedPersonaInformation(true)).find( - (x) => x.identifier === currentPersonaIdentifier, - ) - const currentSiteConnectedPersona = personas.find(checkSiteConnectedToCurrentPersona) - if (!currentPersona || !currentSiteConnectedPersona) return - return { - isSiteConnectedToCurrentPersona: - currentPersona ? checkSiteConnectedToCurrentPersona(currentPersona) : false, - currentPersonaPublicKey: currentPersona.identifier.rawPublicKey, - currentSiteConnectedPersonaPublicKey: currentSiteConnectedPersona.identifier.rawPublicKey, - } - }, [checkSiteConnectedToCurrentPersona, personas.map((x) => x.identifier.toText()).join(',')]) -} diff --git a/packages/mask/content-script/components/DataSource/usePluginHostPermission.ts b/packages/mask/content-script/components/DataSource/usePluginHostPermission.ts deleted file mode 100644 index 2608c8c97314..000000000000 --- a/packages/mask/content-script/components/DataSource/usePluginHostPermission.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useEffect } from 'react' -import { useAsyncFn, useAsyncRetry } from 'react-use' -import type { Plugin } from '@masknet/plugin-infra' -import { MaskMessages } from '@masknet/shared-base' -import Services from '#services' - -export function usePluginHostPermissionCheck(plugins: Plugin.Shared.Definition[]) { - const plugins_ = plugins.filter((x) => x.enableRequirement.host_permissions?.length) - // query if plugin is disabled due to lack of permission - const { retry, value: lackPermission } = useAsyncRetry(async () => { - const lackPermission = new Set() - - await Promise.allSettled( - plugins_.map((plugin) => - Services.Helper.hasHostPermission(plugin.enableRequirement.host_permissions!).then( - (result) => !result && lackPermission.add(plugin.ID), - ), - ), - ) - return lackPermission - }, [plugins_.map((x) => x.ID).join(',')]) - - useEffect(() => MaskMessages.events.hostPermissionChanged.on(retry), [retry]) - return lackPermission -} - -export function useCheckPermissions(permissions: string[]) { - const asyncResult = useAsyncRetry(async () => { - if (!permissions.length) return true - return Services.Helper.hasHostPermission(permissions) - }, [permissions]) - - useEffect(() => MaskMessages.events.hostPermissionChanged.on(asyncResult.retry), [asyncResult.retry]) - - return asyncResult -} - -export function useGrantPermissions(permissions?: string[]) { - return useAsyncFn(async () => { - if (!permissions?.length) return - return Services.Helper.requestExtensionPermissionFromContentScript({ origins: permissions }) - }, [permissions]) -} diff --git a/packages/mask/content-script/components/DataSource/useSearchedKeyword.ts b/packages/mask/content-script/components/DataSource/useSearchedKeyword.ts deleted file mode 100644 index 9db5f84f3da4..000000000000 --- a/packages/mask/content-script/components/DataSource/useSearchedKeyword.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect, useState } from 'react' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' - -export function useSearchedKeyword() { - const [keyword, setKeyword] = useState('') - - useEffect(() => { - const onLocationChange = () => { - if (!activatedSiteAdaptorUI?.collecting?.getSearchedKeyword) return - const kw = activatedSiteAdaptorUI!.collecting.getSearchedKeyword() - setKeyword(kw) - } - onLocationChange() - window.addEventListener('locationchange', onLocationChange) - return () => { - window.removeEventListener('locationchange', onLocationChange) - } - }, []) - return keyword -} diff --git a/packages/mask/content-script/components/GuideStep/index.tsx b/packages/mask/content-script/components/GuideStep/index.tsx deleted file mode 100644 index fffbdfcb4980..000000000000 --- a/packages/mask/content-script/components/GuideStep/index.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { cloneElement, useRef, useState, type ReactElement, useLayoutEffect } from 'react' -import { makeStyles, usePortalShadowRoot } from '@masknet/theme' -import { Box, Modal, styled, Typography } from '@mui/material' -import { sayHelloShowed, userGuideFinished, userGuideStatus } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' - -const useStyles = makeStyles()((theme) => ({ - container: { - position: 'absolute', - boxShadow: `0 0 0 3000px ${theme.palette.mode === 'light' ? 'rgba(0,0,0,.3)' : 'rgba(110,118,125,.3)'}`, - borderRadius: 8, - }, - noBoxShadowCover: { - boxShadow: `0 0 0 3000px ${theme.palette.mode === 'light' ? 'rgba(0,0,0,.2)' : 'rgba(110,118,125,.2)'}`, - }, - target: { - background: 'transparent', - }, - mask: { - position: 'fixed', - top: 0, - width: '100vw', - height: '100vh', - background: 'transparent', - zIndex: 1000, - }, - card: { - position: 'absolute', - left: 0, - width: 256, - padding: '16px', - borderRadius: '16px', - background: 'rgba(0,0,0,.85)', - boxShadow: '0 4px 8px rgba(0,0,0,.1)', - boxSizing: 'border-box', - color: '#fff', - '&.arrow-top:after': { - content: '""', - display: 'inline-block', - width: 0, - height: 0, - border: 'solid 8px transparent', - borderBottomColor: 'rgba(0,0,0,.85)', - borderBottomWidth: '13px', - borderTopWidth: 0, - position: 'absolute', - top: '-13px', - left: '24px', - }, - '&.arrow-bottom:after': { - content: '""', - display: 'inline-block', - width: 0, - height: 0, - border: 'solid 8px transparent', - borderTopColor: 'rgba(0,0,0,.85)', - borderTopWidth: '13px', - borderBottomWidth: 0, - position: 'absolute', - bottom: '-13px', - left: '24px', - }, - }, - buttonContainer: { - display: 'flex', - justifyContent: 'space-between', - paddingTop: '16px', - }, -})) - -const ActionButton = styled('button')({ - boxSizing: 'border-box', - width: 104, - height: 32, - lineHeight: '32px', - borderRadius: 16, - textAlign: 'center', - border: 'solid 1px #000', - borderColor: '#fff', - cursor: 'pointer', - fontFamily: 'PingFang SC', - background: 'none', - color: 'inherit', -}) - -const NextButton = styled(ActionButton)({ - border: 'none', - color: '#111418', - background: '#fff', -}) - -interface GuideStepProps { - // cloneElement is used. - // eslint-disable-next-line @typescript-eslint/ban-types - children: ReactElement - total: number - step: number - tip: string - arrow?: boolean - onComplete?: () => void -} - -export default function GuideStep({ total, step, tip, children, arrow = true, onComplete }: GuideStepProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - const childrenRef = useRef() - const [clientRect, setClientRect] = useState>() - const [bottomAvailable, setBottomAvailable] = useState(true) - const { networkIdentifier } = activatedSiteAdaptorUI! - const currentStep = useValueRef(userGuideStatus[networkIdentifier]) - const finished = useValueRef(userGuideFinished[networkIdentifier]) - const isCurrentStep = +currentStep === step - - const box1Ref = useRef(null) - const box2Ref = useRef(null) - const box3Ref = useRef(null) - - const stepVisible = isCurrentStep && !finished && !!clientRect?.top && !!clientRect.left - - const onSkip = () => { - sayHelloShowed[networkIdentifier].value = true - userGuideFinished[networkIdentifier].value = true - } - - const onNext = () => { - if (step !== total) { - userGuideStatus[networkIdentifier].value = String(step + 1) - } - if (step === total - 1) { - document.body.scrollIntoView() - } - } - - const onTry = () => { - userGuideFinished[networkIdentifier].value = true - onComplete?.() - } - - useLayoutEffect(() => { - let stopped = false - requestAnimationFrame(function fn() { - if (stopped) return - requestAnimationFrame(fn) - if (!childrenRef.current) return - const cr = childrenRef.current.getBoundingClientRect() - if (!cr.height) return - const bottomAvailable = window.innerHeight - cr.height - cr.top > 200 - setBottomAvailable(bottomAvailable) - setClientRect((old) => { - if ( - old && - (old.height === cr.height || old.left === cr.left || old.top === cr.top || old.width === cr.width) - ) - return old - return cr - }) - if (box1Ref.current) { - box1Ref.current.style.top = cr.top + 'px' - box1Ref.current.style.left = cr.left + 'px' - } - if (box2Ref.current) { - box2Ref.current.style.width = cr.width + 'px' - box2Ref.current.style.height = cr.height + 'px' - } - if (box3Ref.current) { - box3Ref.current.style.left = (cr.width < 50 ? -cr.width / 2 : 0) + 'px' - box3Ref.current.style.top = bottomAvailable ? cr.height + 16 + 'px' : '' - box3Ref.current.style.bottom = bottomAvailable ? '' : cr.height + 16 + 'px' - } - }) - return () => void (stopped = true) - }, []) - - return ( - <> - {cloneElement(children, { ref: childrenRef })} - {usePortalShadowRoot((container) => { - if (!stepVisible) return null - return ( - - {/* this extra div is feed to If we remove it, it will show a blue outline on the box1 */} -
-
- -
- - - {step}/{total} - - -
- - {tip} - -
-
- {step === total ? - - {t.try()} - - : <> - - {t.skip()} - - - {t.next()} - - - } -
-
-
-
-
-
- ) - })} - - ) -} diff --git a/packages/mask/content-script/components/GuideStep/useSetupGuideStatus.ts b/packages/mask/content-script/components/GuideStep/useSetupGuideStatus.ts deleted file mode 100644 index 62c69b644040..000000000000 --- a/packages/mask/content-script/components/GuideStep/useSetupGuideStatus.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useMemo } from 'react' -import { currentSetupGuideStatus, type SetupGuideContext } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' - -export function useSetupGuideStatus() { - const context = useValueRef(currentSetupGuideStatus[activatedSiteAdaptorUI!.networkIdentifier]) - return useMemo(() => { - try { - return JSON.parse(context) - } catch { - return {} - } - }, [context]) -} diff --git a/packages/mask/content-script/components/InjectedComponents/AdditionalPostContent.tsx b/packages/mask/content-script/components/InjectedComponents/AdditionalPostContent.tsx deleted file mode 100644 index 4fba46362bf4..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/AdditionalPostContent.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Typography, Card, Box, CircularProgress, type CircularProgressProps, colors } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { type TypedMessage, makeTypedMessageText } from '@masknet/typed-message' -import { TypedMessageRender } from '@masknet/typed-message-react' -import { TypedMessageRenderContext } from '../../../shared-ui/TypedMessageRender/context.js' -import { Check as CheckIcon, Close as CloseIcon } from '@mui/icons-material' -import { memo, useCallback, useMemo } from 'react' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/ui.js' -import { Icons } from '@masknet/icons' - -enum AdditionalIcon { - check = 'check', - error = 'error', -} -interface AdditionalContentProps { - title: string - titleIcon?: keyof typeof AdditionalIcon - headerActions?: React.ReactNode - progress?: boolean | CircularProgressProps - /** this component does not accept children */ - children?: never - /** Can handle typed message or normal string */ - message?: TypedMessage | string -} -const useStyles = makeStyles()((theme) => ({ - root: { boxSizing: 'border-box', width: '100%', backgroundColor: 'transparent', borderColor: 'transparent' }, - title: { display: 'flex', alignItems: 'center', fontSize: 'inherit' }, - icon: { marginRight: theme.spacing(1), display: 'flex', width: 18, height: 18 }, - content: { margin: theme.spacing(1, 0), padding: 0, overflowWrap: 'break-word' }, - rightIcon: { paddingLeft: theme.spacing(0.75) }, -})) - -export const AdditionalContent = memo(function AdditionalContent(props: AdditionalContentProps): JSX.Element { - const { classes } = useStyles() - const stop = useCallback((ev: React.MouseEvent) => ev.stopPropagation(), []) - const { progress, title, message } = props - const ProgressJSX = - !progress ? null - : progress === true ? - : - const RightIconJSX = ((icon) => { - const props = { fontSize: 'small', className: classes.rightIcon } as const - if (icon === AdditionalIcon.check) return - if (icon === AdditionalIcon.error) return - return null - })(props.titleIcon) - const header = ( - - {ProgressJSX || } - - {title} - {RightIconJSX} - - {props.headerActions} - - ) - const TypedMessage = useMemo(() => { - if (typeof message === 'string') return makeTypedMessageText(message) - if (typeof message === 'undefined') return makeTypedMessageText('') - return message - }, [message]) - return ( - -
{header}
- {message ? -
- - - -
- : null} -
- ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/AutoPasteFailedDialog.tsx b/packages/mask/content-script/components/InjectedComponents/AutoPasteFailedDialog.tsx deleted file mode 100644 index ac424066d8b5..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/AutoPasteFailedDialog.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useState } from 'react' -import { useCopyToClipboard } from 'react-use' -import { format as formatDateTime } from 'date-fns' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' -import { - DialogActions, - DialogContent, - DialogTitle, - DialogContentText, - TextField, - Box, - IconButton, - Paper, - Link, - Button, - Typography, -} from '@mui/material' -import { Image } from '@masknet/shared' -import type { AutoPasteFailedEvent } from '@masknet/shared-base' -import { useMatchXS } from '@masknet/shared-base-ui' -import { DraggableDiv } from '../shared/DraggableDiv.js' -import { Close as CloseIcon, Download, OpenInBrowser } from '@mui/icons-material' -import { saveFileFromUrl } from '../../../shared/index.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' - -interface AutoPasteFailedDialogProps { - data: AutoPasteFailedEvent - onClose: () => void -} -const useStyles = makeStyles()((theme) => ({ - title: { marginLeft: theme.spacing(1) }, - paper: {}, - button: { marginRight: theme.spacing(1) }, -})) - -function AutoPasteFailedDialog(props: AutoPasteFailedDialogProps) { - const { onClose, data } = props - const t = useMaskSharedTrans() - const { classes } = useStyles() - const url = data.image ? URL.createObjectURL(data.image) : undefined - const { showSnackbar } = useCustomSnackbar() - const [, copy] = useCopyToClipboard() - const isMobile = useMatchXS() - const fileName = `masknetwork-encrypted-${formatDateTime(Date.now(), 'yyyyMMddHHmmss')}.png` - - return ( - - - - - - - {t.auto_paste_failed_dialog_content()} - - - {props.data.text ? - <> - - - - - : null} - - - {data.image ? - // It must be img - - : null} - - - {url ? - - : null} - {url ? - - : null} - - - {/* To leave some bottom padding */} - - - - ) -} -export function useAutoPasteFailedDialog() { - const [open, setOpen] = useState(false) - const [data, setData] = useState({ text: '' }) - return [ - (data: AutoPasteFailedEvent) => { - setData(data) - setOpen(true) - }, - open ? setOpen(false)} data={data} /> : null, - ] as const -} diff --git a/packages/mask/content-script/components/InjectedComponents/Avatar.tsx b/packages/mask/content-script/components/InjectedComponents/Avatar.tsx deleted file mode 100644 index 7b68c7d1a8d1..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/Avatar.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useMemo } from 'react' -import { createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { useSocialAccountsAll } from '@masknet/web3-hooks-base' -import type { Plugin } from '@masknet/plugin-infra' -import { makeStyles } from '@masknet/theme' -import { useSocialIdentityByUserId } from '../DataSource/useActivatedUI.js' - -const useStyles = makeStyles()(() => ({ - root: {}, -})) - -interface AvatarProps extends withClasses<'root'> { - userId: string - sourceType?: Plugin.SiteAdaptor.AvatarRealmSourceType -} - -export function Avatar(props: AvatarProps) { - const { userId, sourceType } = props - const { classes } = useStyles(undefined, { props }) - - const { data: identity } = useSocialIdentityByUserId(userId) - const [socialAccounts, { isPending: loadingSocialAccounts }] = useSocialAccountsAll(identity) - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => { - const shouldDisplay = - plugin.AvatarRealm?.Utils?.shouldDisplay?.(identity, socialAccounts, sourceType) ?? true - return shouldDisplay ? plugin.AvatarRealm?.UI?.Decorator : undefined - }, - ) - - return - }, [identity, socialAccounts, sourceType]) - - if (loadingSocialAccounts || !component) return null - return
{component}
-} diff --git a/packages/mask/content-script/components/InjectedComponents/CommentBox.tsx b/packages/mask/content-script/components/InjectedComponents/CommentBox.tsx deleted file mode 100644 index 3cc3a6c72203..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/CommentBox.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { makeStyles } from '@masknet/theme' -import { Box, InputBase } from '@mui/material' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { EnhanceableSite } from '@masknet/shared-base' - -interface StyleProps { - site: EnhanceableSite -} - -const useStyles = makeStyles()((_theme, { site }) => ({ - root: { - flex: 1, - fontSize: 13, - background: '#3a3b3c', - width: site === EnhanceableSite.Minds ? '96%' : '100%', - height: 34, - borderRadius: 20, - padding: '2px 1em', - boxSizing: 'border-box', - marginTop: 6, - color: '#e4e6eb', - }, - input: { - '&::placeholder': { - color: '#b0b3b8', - opacity: 1, - }, - '&:focus::placeholder': { - color: '#d0d2d6', - }, - }, -})) - -export interface CommentBoxProps { - onSubmit: (newVal: string) => void - inputProps?: Partial> -} -export function CommentBox(props: CommentBoxProps) { - const { classes } = useStyles({ site: activatedSiteAdaptorUI!.networkIdentifier }) - const t = useMaskSharedTrans() - return ( - - { - const node = event.target as HTMLInputElement - if (!node.value) return - if (event.key !== 'Enter') return - props.onSubmit(node.value) - node.value = '' // clear content - }} - {...props.inputProps} - /> - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx deleted file mode 100644 index 97db9e00dec0..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo } from 'react' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { AdditionalContent } from '../AdditionalPostContent.js' -import type { DecryptionProgress } from './types.js' -import type { ProfileIdentifier } from '@masknet/shared-base' -import { useAuthorDifferentMessage } from './authorDifferentMessage.js' -interface DecryptPostAwaitingProps { - type?: DecryptionProgress - /** The author in the payload */ - author: ProfileIdentifier | null - /** The author of the encrypted post */ - postedBy: ProfileIdentifier | null -} -export const DecryptPostAwaiting = memo(function DecryptPostAwaiting(props: DecryptPostAwaitingProps) { - const { author, postedBy, type } = props - const t = useMaskSharedTrans() - const key = { - finding_post_key: t.decrypted_postbox_decrypting_finding_post_key(), - finding_person_public_key: t.decrypted_postbox_decrypting_finding_person_key(), - init: t.decrypted_postbox_decrypting(), - decode_post: t.decrypted_postbox_decoding(), - iv_decrypted: t.decrypted_postbox_decoding(), - payload_decrypted: t.decrypted_postbox_decoding(), - intermediate_success: 'unreachable case. it should display success UI', - undefined: t.decrypted_postbox_decrypting(), - } as const - return ( - - ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx deleted file mode 100644 index f9853b63bdfa..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { memo } from 'react' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { AdditionalContent } from '../AdditionalPostContent.js' -import type { ProfileIdentifier } from '@masknet/shared-base' -import { useAuthorDifferentMessage } from './authorDifferentMessage.js' - -interface DecryptPostFailedProps { - error: Error - /** The author in the payload */ - author: ProfileIdentifier | null - /** The author of the encrypted post */ - postedBy: ProfileIdentifier | null -} -export const DecryptPostFailed = memo(function DecryptPostFailed(props: DecryptPostFailedProps) { - const { author, postedBy, error } = props - const t = useMaskSharedTrans() - - return ( - - ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx deleted file mode 100644 index 0f353ec3f9af..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { Fragment, useContext, useEffect, useReducer } from 'react' -import { extractTextFromTypedMessage, isTypedMessageEqual, type TypedMessage } from '@masknet/typed-message' -import type { ProfileIdentifier } from '@masknet/shared-base' - -import Services, { GeneratorServices } from '#services' -import type { DecryptionProgress, FailureDecryption, SuccessDecryption } from './types.js' -import { DecryptPostSuccess } from './DecryptedPostSuccess.js' -import { DecryptPostAwaiting } from './DecryptPostAwaiting.js' -import { DecryptPostFailed } from './DecryptPostFailed.js' -import { encodeArrayBuffer, safeUnreachable } from '@masknet/kit' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' -import type { DecryptionContext, EncodedPayload } from '../../../../background/services/crypto/decryption.js' -import { DecryptIntermediateProgressKind, DecryptProgressKind } from '@masknet/encryption' -import { type PostContext, usePostInfoDetails, PostInfoContext } from '@masknet/plugin-infra/content-script' -import { Some } from 'ts-results-es' -import { uniqWith } from 'lodash-es' - -type PossibleProgress = SuccessDecryption | FailureDecryption | DecryptionProgress - -function progressReducer( - state: Array<{ - key: string - progress: PossibleProgress - }>, - payload: { - type: 'refresh' - key: string - progress: PossibleProgress - }, -) { - const { key, progress } = payload - const currentProgressIndex = state.findIndex((x) => x.key === key) - if (currentProgressIndex === -1) { - return [ - ...state, - { - key, - progress, - }, - ] - } - const currentProgress = state[currentProgressIndex].progress - if (currentProgress && currentProgress.type !== 'progress' && progress.type === 'progress') return state - state[currentProgressIndex] = { - key, - progress, - } - return [...state] -} - -interface DecryptPostProps { - whoAmI: ProfileIdentifier | null -} -function isProgressEqual(a: PossibleProgress, b: PossibleProgress) { - if (a.type !== b.type) return false - if (a.internal !== b.internal) return false - if (a.type === 'success') return isTypedMessageEqual(a, b as SuccessDecryption) - if (a.type === 'error') return a.error === (b as FailureDecryption).error - if (a.type === 'progress') return a.progress === (b as DecryptionProgress).progress - safeUnreachable(a) - return false -} -export function DecryptPost(props: DecryptPostProps) { - const { whoAmI } = props - const deconstructedPayload = usePostInfoDetails.hasMaskPayload() - const currentPostBy = usePostInfoDetails.author() - // TODO: we should read this from the payload. - const authorInPayload = usePostInfoDetails.author() - const postBy = authorInPayload || currentPostBy - const postMetadataImages = usePostInfoDetails.postMetadataImages() - const mentionedLinks = usePostInfoDetails.mentionedLinks() - const postInfo = useContext(PostInfoContext)! - - const [progress, dispatch] = useReducer(progressReducer, []) - - useEffect(() => { - function setCommentFns(iv: Uint8Array, message: TypedMessage) { - const text = extractTextFromTypedMessage(message).expect('TypedMessage should have one or more text part') - postInfo.encryptComment.value = async (comment) => Services.Crypto.encryptComment(iv, text, comment) - postInfo.decryptComment.value = async (encryptedComment) => - Services.Crypto.decryptComment(iv, text, encryptedComment) - } - const signal = new AbortController() - const postURL = postInfo.url.getCurrentValue()?.toString() - const report = - (key: string): ReportProgress => - (kind, message) => { - if (kind === 'e2e') { - dispatch({ - type: 'refresh', - key, - progress: { type: 'progress', progress: 'finding_post_key', internal: false }, - }) - } else { - dispatch({ - type: 'refresh', - key, - progress: { type: 'error', error: message, internal: false }, - }) - } - } - if (deconstructedPayload) { - makeProgress( - postURL, - postBy, - whoAmI, - { - type: 'text', - text: - extractTextFromTypedMessage(postInfo.rawMessage.getCurrentValue()).unwrapOr('') + - ' ' + - mentionedLinks.join(' '), - }, - (message, iv) => { - setCommentFns(iv, message) - dispatch({ - type: 'refresh', - key: 'text', - progress: { - type: 'success', - content: message, - internal: false, - iv: encodeArrayBuffer(iv), - }, - }) - }, - postInfo.decryptedReport, - report('text'), - signal.signal, - ) - } - postMetadataImages.forEach((url) => { - if (signal.signal.aborted) return - makeProgress( - postURL, - postBy, - whoAmI, - { type: 'image-url', image: url }, - (message, iv) => { - setCommentFns(iv, message) - dispatch({ - type: 'refresh', - key: url, - progress: { - type: 'success', - content: message, - internal: false, - iv: encodeArrayBuffer(iv), - }, - }) - }, - postInfo.decryptedReport, - report(url), - signal.signal, - ) - }) - return () => signal.abort() - }, [deconstructedPayload, postBy, postMetadataImages.join(','), whoAmI, mentionedLinks.join(',')]) - - if (!deconstructedPayload && progress.every((x) => x.progress.internal)) return null - return ( - <> - {uniqWith(progress, (a, b) => isProgressEqual(a.progress, b.progress)) - // the internal progress should not display to the end-user - .filter(({ progress }) => !progress.internal) - .map(({ progress, key }, index) => ( - {renderProgress(progress)} - ))} - - ) - - function renderProgress(progress: SuccessDecryption | FailureDecryption | DecryptionProgress) { - switch (progress.type) { - case 'success': - return ( - - ) - case 'error': - return ( - - ) - case 'progress': - return - default: - return null - } - } -} - -type ReportProgress = (type: 'e2e' | 'error', message: string) => void -async function makeProgress( - postURL: string | undefined, - authorHint: ProfileIdentifier | null, - currentProfile: ProfileIdentifier | null, - payload: EncodedPayload, - done: (message: TypedMessage, iv: Uint8Array) => void, - reporter: PostContext['decryptedReport'], - reportProgress: ReportProgress, - signal: AbortSignal, -) { - const context: DecryptionContext = { - postURL, - authorHint, - currentProfile, - encryptPayloadNetwork: activatedSiteAdaptorUI!.encryptPayloadNetwork, - } - let iv: Uint8Array | undefined - for await (const progress of GeneratorServices.decrypt(payload, context)) { - if (signal.aborted) return - if (progress.type === DecryptProgressKind.Success) { - done(progress.content, iv || new Uint8Array()) - } else if (progress.type === DecryptProgressKind.Info) { - iv ??= progress.iv - if (typeof progress.isAuthorOfPost === 'boolean') - reporter({ isAuthorOfPost: Some(progress.isAuthorOfPost) }) - if (progress.iv) reporter({ iv: encodeArrayBuffer(progress.iv) }) - if (progress.version) reporter({ version: progress.version }) - if (typeof progress.publicShared === 'boolean') reporter({ sharedPublic: Some(progress.publicShared) }) - } else if (progress.type === DecryptProgressKind.Progress) { - if (progress.event === DecryptIntermediateProgressKind.TryDecryptByE2E) reportProgress('e2e', '') - else safeUnreachable(progress.event) - } else if (progress.type === DecryptProgressKind.Error) { - } else safeUnreachable(progress) - } -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx deleted file mode 100644 index 2292416076cd..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { memo, useContext, useEffect, useState } from 'react' -import { attachNextIDToProfile } from '../../../../shared/index.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { AdditionalContent } from '../AdditionalPostContent.js' -import { SelectProfileDialog } from '../SelectPeopleDialog.js' -import { makeStyles } from '@masknet/theme' -import { Typography, useTheme } from '@mui/material' -import type { TypedMessage } from '@masknet/typed-message' -import { - EMPTY_LIST, - MaskMessages, - type ProfileIdentifier, - type ProfileInformation, - type ProfileInformationFromNextID, -} from '@masknet/shared-base' -import { useAuthorDifferentMessage } from './authorDifferentMessage.js' -import { DecryptedUI_PluginRendererWithSuggestion } from '../DecryptedPostMetadataRender.js' -import { PostInfoContext, usePostInfoDetails } from '@masknet/plugin-infra/content-script' -import { useRecipientsList } from '../../CompositionDialog/useRecipientsList.js' -import { useSelectedRecipientsList } from '../../CompositionDialog/useSelectedRecipientsList.js' -import Services from '#services' -import type { LazyRecipients } from '../../CompositionDialog/CompositionUI.js' -import { delay } from '@masknet/kit' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' -import { RecipientsToolTip } from './RecipientsToolTip.js' -import { Icons } from '@masknet/icons' - -interface DecryptPostSuccessProps { - message: TypedMessage - /** The author in the payload */ - author: ProfileIdentifier | null - /** The author of the encrypted post */ - postedBy: ProfileIdentifier | null - whoAmI: ProfileIdentifier | null -} - -function useCanAppendShareTarget(whoAmI: ProfileIdentifier | null): whoAmI is ProfileIdentifier { - const version = usePostInfoDetails.version() - const sharedPublic = usePostInfoDetails.publicShared() - const currentPostBy = usePostInfoDetails.author() - // TODO: this should be read from the payload. - const authorInPayload = currentPostBy - const postAuthor = authorInPayload || currentPostBy - - if (sharedPublic) return false - if (version !== -38 && version !== -37) return false - if (!whoAmI) return false - if (whoAmI !== postAuthor) return false - return true -} -const DecryptPostSuccessBase = memo(function DecryptPostSuccessNoShare( - props: React.PropsWithChildren, -) { - const { message, author, postedBy } = props - const t = useMaskSharedTrans() - const iv = usePostInfoDetails.postIVIdentifier() - - useEffect(() => { - if (message.meta || !iv?.toText()) return - MaskMessages.events.postReplacerHidden.sendToLocal({ hidden: true, postId: iv.toText() }) - }, [message, iv?.toText()]) - - return ( - <> - - - - ) -}) - -const useStyles = makeStyles<{ canAppendShareTarget: boolean }>()((theme, { canAppendShareTarget }) => { - return { - visibilityBox: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - padding: theme.spacing(0.5, 1), - background: theme.palette.maskColor.bg, - borderRadius: '999px', - cursor: canAppendShareTarget ? 'pointer' : 'default', - }, - iconAdd: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - marginLeft: 8, - background: theme.palette.maskColor.primary, - borderRadius: '50%', - height: 16, - width: 16, - }, - } -}) -export const DecryptPostSuccess = memo(function DecryptPostSuccess(props: DecryptPostSuccessProps) { - const canAppendShareTarget = useCanAppendShareTarget(props.whoAmI) - const { classes } = useStyles({ canAppendShareTarget }) - const t = useMaskSharedTrans() - const [showDialog, setShowDialog] = useState(false) - const theme = useTheme() - const recipients = useRecipientsList() - const { value: selectedRecipients = EMPTY_LIST, retry } = useSelectedRecipientsList() - - const rightActions = - props.author?.userId === props.whoAmI?.userId ? - canAppendShareTarget && props.whoAmI ? - <> - {selectedRecipients.length ? - setShowDialog(true)} /> - :
setShowDialog(true)}> - - {t.decrypted_postbox_only_visible_to_yourself()} - -
- -
-
- } - - {showDialog ? - setShowDialog(false)} - recipients={recipients} - /> - : null} - - :
- - {t.decrypted_postbox_visible_to_all()} - -
- : null - return {rightActions} -}) - -interface Props { - onClose(): void - recipients: LazyRecipients - whoAmI: ProfileIdentifier - selectedRecipients: ProfileInformation[] - retry(): void -} -function AppendShareDetail({ recipients, selectedRecipients, onClose, whoAmI, retry }: Props) { - const info = useContext(PostInfoContext)! - const iv = usePostInfoDetails.postIVIdentifier()! - - useEffect(recipients.request, []) - - return ( - { - for (const item of profiles) { - await attachNextIDToProfile(item as ProfileInformationFromNextID) - } - await Services.Crypto.appendShareTarget( - info.version.getCurrentValue()!, - iv, - profiles.map((x) => ({ profile: x.identifier, persona: x.linkedPersona })), - whoAmI, - activatedSiteAdaptorUI!.encryptPayloadNetwork, - ) - await delay(1500) - retry() - }} - /> - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/RecipientsToolTip.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/RecipientsToolTip.tsx deleted file mode 100644 index 67baf9aef8c9..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/RecipientsToolTip.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { type ProfileInformation } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { useTheme } from '@mui/material' -import { Avatar } from '../../../../shared-ui/components/Avatar.js' -import { Icons } from '@masknet/icons' - -const useStyles = makeStyles<{ isMore: boolean }>()((theme, { isMore }) => { - return { - iconStack: { - padding: theme.spacing(0.5), - background: theme.palette.maskColor.bg, - borderRadius: '999px', - cursor: 'pointer', - display: 'inline-flex', - boxSizing: 'border-box', - minWidth: 'auto', - }, - icon: { - marginLeft: '-3.5px', - fontSize: 'inherit', - width: 16, - height: 16, - ':nth-of-type(1)': { - zIndex: 1, - marginLeft: 0, - }, - ':nth-of-type(2)': { - zIndex: 2, - }, - ':nth-of-type(3)': { - zIndex: 3, - }, - }, - iconMore: { - transform: 'translate(-6px, 3px)', - zIndex: 4, - }, - iconAdd: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - marginLeft: isMore ? 4 : 10, - background: theme.palette.maskColor.primary, - borderRadius: '50%', - height: 16, - width: 16, - }, - } -}) - -interface RecipientsToolTipProps { - recipients: ProfileInformation[] - openDialog(): void -} - -export function RecipientsToolTip({ recipients, openDialog }: RecipientsToolTipProps) { - const isMore = recipients.length > 3 - const { classes } = useStyles({ isMore }) - const theme = useTheme() - if (!recipients.length) return null - return ( -
- {recipients.slice(0, 3).map((recipient) => ( - - ))} - {isMore ? - - : null} -
- -
-
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/authorDifferentMessage.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/authorDifferentMessage.tsx deleted file mode 100644 index cd1581cd130f..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/authorDifferentMessage.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { ProfileIdentifier } from '@masknet/shared-base' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' - -export function useAuthorDifferentMessage( - author: ProfileIdentifier | null, - postBy: ProfileIdentifier | null, - jsx: React.ReactNode, -) { - const t = useMaskSharedTrans() - if (!author || !postBy) return jsx - if (author === postBy) return jsx - return ( - <> - {t.decrypted_postbox_author_mismatch({ name: author.userId })} - {jsx} - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/types.ts b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/types.ts deleted file mode 100644 index a2f1fddef97b..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { TypedMessage } from '@masknet/typed-message' - -export type SuccessDecryption = { - type: 'success' - iv: string - content: TypedMessage - internal: boolean -} -export type FailureDecryption = { - error: string - type: 'error' - internal: boolean -} -export type DecryptionProgress = ( - | { progress: 'finding_person_public_key' | 'finding_post_key' | 'init' | 'decode_post' } - | { progress: 'intermediate_success'; data: SuccessDecryption } - | { progress: 'iv_decrypted'; iv: string } -) & { - type: 'progress' - /** if this is true, this progress should not cause UI change. */ - internal: boolean -} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx deleted file mode 100644 index fa4702c0b940..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import type { MetadataRenderProps } from '@masknet/typed-message-react' -import { extractTextFromTypedMessage } from '@masknet/typed-message' -import { - PossiblePluginSuggestionUI, - useDisabledPluginSuggestionFromMeta, - useDisabledPluginSuggestionFromPost, -} from './DisabledPluginSuggestion.js' -import { MaskPostExtraPluginWrapperWithPermission } from './PermissionBoundary.js' - -const Decrypted = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (x) => x.DecryptedInspector, - MaskPostExtraPluginWrapperWithPermission, -) - -export function DecryptedUI_PluginRendererWithSuggestion(props: MetadataRenderProps) { - const a = useDisabledPluginSuggestionFromMeta(props.metadata) - const b = useDisabledPluginSuggestionFromPost(extractTextFromTypedMessage(props.message), []) - const suggest = Array.from(new Set(a.concat(b))) - - return ( - <> - - - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx b/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx deleted file mode 100644 index b66ae40abfcf..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { type ReactNode, useCallback } from 'react' -import { useAsync } from 'react-use' -import type { Option } from 'ts-results-es' -import { useSubscription } from 'use-subscription' -import { Icons } from '@masknet/icons' -import { - type Plugin, - PluginTransFieldRender, - registeredPlugins, - useActivatedPluginsSiteAdaptor, - usePostInfoDetails, -} from '@masknet/plugin-infra/content-script' -import { MaskPostExtraInfoWrapper } from '@masknet/shared' -import { BooleanPreference, EMPTY_LIST } from '@masknet/shared-base' -import { makeStyles, MaskLightTheme } from '@masknet/theme' -import { extractTextFromTypedMessage } from '@masknet/typed-message' -import { Box, type BoxProps, Button, Skeleton, Typography, useTheme } from '@mui/material' -import Services from '#services' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' - -function useDisabledPlugins() { - const activated = new Set(useActivatedPluginsSiteAdaptor('any').map((x) => x.ID)) - const minimalMode = new Set(useActivatedPluginsSiteAdaptor(true).map((x) => x.ID)) - const disabledPlugins = useSubscription(registeredPlugins) - .filter((plugin) => !activated.has(plugin[0]) || minimalMode.has(plugin[0])) - .map((x) => x[1]) - return disabledPlugins -} - -export function useDisabledPluginSuggestionFromPost(postContent: Option, metaLinks: readonly string[]) { - const disabled = useDisabledPlugins().filter((x) => x.contribution?.postContent) - - const matches = disabled.filter((x) => { - for (const pattern of x.contribution!.postContent!) { - if (postContent.isSome() && postContent.value.match(pattern)) return true - if (metaLinks.some((link) => link.match(pattern))) return true - } - return false - }) - return matches -} - -export function useDisabledPluginSuggestionFromMeta(meta: undefined | ReadonlyMap) { - const disabled = useDisabledPlugins().filter((x) => x.contribution?.metadataKeys) - - if (!meta) return EMPTY_LIST - - const matches = disabled.filter((x) => { - const contributes = x.contribution!.metadataKeys! - return [...meta.keys()].some((key) => contributes.has(key)) - }) - return matches -} - -export function PossiblePluginSuggestionPostInspector() { - const message = extractTextFromTypedMessage(usePostInfoDetails.rawMessage()) - const metaLinks = usePostInfoDetails.mentionedLinks() - const matches = useDisabledPluginSuggestionFromPost(message, metaLinks) - return -} - -export function PossiblePluginSuggestionUI(props: { plugins: Plugin.Shared.Definition[] }) { - const { plugins } = props - const _plugins = useActivatedPluginsSiteAdaptor('any') - if (!plugins.length) return null - return ( - <> - {plugins.map((define) => ( - y.ID === define.ID)?.wrapperProps} - /> - ))} - - ) -} - -export function PossiblePluginSuggestionUISingle(props: { - lackHostPermission?: boolean - define: Plugin.Shared.Definition - wrapperProps?: Plugin.SiteAdaptor.PluginWrapperProps | undefined - content?: ReactNode -}) { - const { define, lackHostPermission, wrapperProps, content } = props - const t = useMaskSharedTrans() - const theme = useTheme() - const onClick = useCallback(() => { - if (lackHostPermission && define.enableRequirement.host_permissions) { - Services.Helper.requestExtensionPermissionFromContentScript({ - origins: define.enableRequirement.host_permissions, - }) - } else { - Services.Settings.setPluginMinimalModeEnabled(define.ID, false) - } - }, [lackHostPermission, define]) - - const { value: disabled } = useAsync(async () => { - const status = await Services.Settings.getPluginMinimalModeEnabled(define.ID) - return status === BooleanPreference.True - }, [define.ID]) - - const ButtonIcon = lackHostPermission ? Icons.Approve : Icons.Plugin - const wrapperContent = content ?? - const buttonLabel = lackHostPermission ? t.approve() : t.plugin_enable() - - return ( - } - publisher={ - define.publisher ? - - : undefined - } - publisherLink={define.publisher?.link} - wrapperProps={wrapperProps} - action={ - - } - content={wrapperContent} - /> - ) -} - -const useStyles = makeStyles()(() => ({ - content: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - }, - text: { - color: MaskLightTheme.palette.maskColor.main, - }, - rectangle: { - backgroundColor: 'rgba(255,255,255,0.5)', - }, -})) - -interface FallbackContentProps extends BoxProps { - disabled?: boolean -} - -function FallbackContent({ disabled, ...rest }: FallbackContentProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - if (disabled) - return ( - - {t.plugin_disabled_tip()} - - ) - return ( - - - - - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/PageInspector.tsx b/packages/mask/content-script/components/InjectedComponents/PageInspector.tsx deleted file mode 100644 index 1a9d7838b894..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PageInspector.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useEffect } from 'react' -import { useCustomSnackbar } from '@masknet/theme' -import { Button, Box, Typography } from '@mui/material' -import { createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { MaskMessages } from '@masknet/shared-base' -import { useMatchXS } from '@masknet/shared-base-ui' -import { useAutoPasteFailedDialog } from './AutoPasteFailedDialog.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' - -const GlobalInjection = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useAnyMode, - (x) => x.GlobalInjection, -) - -export function PageInspector() { - const t = useMaskSharedTrans() - const { showSnackbar, closeSnackbar } = useCustomSnackbar() - const [autoPasteFailed, JSX] = useAutoPasteFailedDialog() - const xsMatched = useMatchXS() - - useEffect( - () => - MaskMessages.events.autoPasteFailed.on((data) => { - const key = data.image ? Math.random() : data.text - const close = () => { - closeSnackbar(key) - } - const timeout = setTimeout(close, 15 * 1000 /** 15 seconds */) - showSnackbar( - <> - {t.auto_paste_failed_snackbar()} - - - - - , - { - variant: 'info', - preventDuplicate: true, - anchorOrigin: - xsMatched ? - { - vertical: 'bottom', - horizontal: 'center', - } - : { horizontal: 'right', vertical: 'top' }, - key, - action: <>, - }, - ) - }), - [], - ) - return ( - <> - {JSX} - - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/PermissionBoundary.tsx b/packages/mask/content-script/components/InjectedComponents/PermissionBoundary.tsx deleted file mode 100644 index c83fdf3a02c8..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PermissionBoundary.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { - forwardRef, - memo, - type PropsWithChildren, - type ReactNode, - useImperativeHandle, - useMemo, - useRef, - useState, -} from 'react' -import type { AsyncState } from 'react-use/lib/useAsyncFn.js' -import type { PluginWrapperComponent, Plugin, PluginWrapperMethods } from '@masknet/plugin-infra/content-script' -import { MaskPostExtraPluginWrapper, useSharedTrans } from '@masknet/shared' -import { EMPTY_LIST } from '@masknet/shared-base' -import { Typography, useTheme } from '@mui/material' -import { useCheckPermissions, useGrantPermissions } from '../DataSource/usePluginHostPermission.js' -import { PossiblePluginSuggestionUISingle } from './DisabledPluginSuggestion.js' - -interface PermissionBoundaryProps extends PropsWithChildren<{}> { - permissions: string[] - fallback?: - | ReactNode - | ((grantState: AsyncState, onGrantPermissions: () => Promise) => ReactNode) -} - -const PermissionBoundary = memo(function PermissionBoundary({ - permissions, - fallback, - children, -}) { - const { value: hasPermissions = true } = useCheckPermissions(permissions) - - const [grantState, onGrant] = useGrantPermissions(permissions) - - if (!hasPermissions && fallback && permissions.length) - return <>{typeof fallback === 'function' ? fallback(grantState, onGrant) : fallback} - - return <>{children} -}) - -export const MaskPostExtraPluginWrapperWithPermission: PluginWrapperComponent = - forwardRef((props, ref) => { - const wrapperMethodsRef = useRef(null) - const theme = useTheme() - const t = useSharedTrans() - const [open, setOpen] = useState(false) - - const refItem = useMemo((): PluginWrapperMethods => { - return { - setWidth: (width) => wrapperMethodsRef.current?.setWidth(width), - setWrap: (open) => { - setOpen(open) - wrapperMethodsRef.current?.setWrap(open) - }, - setWrapperName: (name) => wrapperMethodsRef.current?.setWrapperName(name), - } - }, []) - - useImperativeHandle(ref, () => refItem, [refItem]) - - return ( - - {t.authorization_descriptions()} - - {props.definition.enableRequirement.host_permissions?.join(',')} - - - } - /> - : undefined - }> - { - if (methods) wrapperMethodsRef.current = methods - }} - /> - - ) - }) diff --git a/packages/mask/content-script/components/InjectedComponents/PostActions.tsx b/packages/mask/content-script/components/InjectedComponents/PostActions.tsx deleted file mode 100644 index 7ccdd824a350..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PostActions.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - createInjectHooksRenderer, - Plugin, - useActivatedPluginsSiteAdaptor, - usePostInfoDetails, -} from '@masknet/plugin-infra/content-script' - -const ActionsRenderer = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, -) - -export function PostActions() { - const identifier = usePostInfoDetails.author() - if (!identifier) return null - return -} diff --git a/packages/mask/content-script/components/InjectedComponents/PostComments.tsx b/packages/mask/content-script/components/InjectedComponents/PostComments.tsx deleted file mode 100644 index 0ae716760307..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PostComments.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect } from 'react' -import { useAsync } from 'react-use' -import { Chip } from '@mui/material' -import type { ChipProps } from '@mui/material/Chip' -import { Lock } from '@mui/icons-material' -import type { ValueRef } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { makeStyles } from '@masknet/theme' -import { usePostInfoDetails } from '@masknet/plugin-infra/content-script' - -const useStyle = makeStyles()({ - root: { - height: 'auto', - width: 'calc(98% - 10px)', - padding: '6px', - }, - label: { - width: '90%', - overflowWrap: 'break-word', - whiteSpace: 'normal', - textOverflow: 'clip', - }, -}) -type PostCommentDecryptedProps = React.PropsWithChildren<{ ChipProps?: ChipProps }> -function PostCommentDecrypted(props: PostCommentDecryptedProps) { - const { classes } = useStyle(undefined, { props: props.ChipProps || {} }) - return ( - <> - } - label={props.children} - color="secondary" - {...props.ChipProps} - classes={{ root: classes.root, label: classes.label }} - /> - - ) -} -export interface PostCommentProps { - comment: ValueRef - needZip(): void -} -export function PostComment(props: PostCommentProps) { - const { needZip } = props - const comment = useValueRef(props.comment) - const decrypt = usePostInfoDetails.decryptComment() - - const { value } = useAsync(async () => decrypt?.(comment), [decrypt, comment]) - - useEffect(() => void (value && needZip()), [value, needZip]) - if (value) return {value} - return null -} diff --git a/packages/mask/content-script/components/InjectedComponents/PostDialogHint.tsx b/packages/mask/content-script/components/InjectedComponents/PostDialogHint.tsx deleted file mode 100644 index e136116b9daa..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PostDialogHint.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Icons } from '@masknet/icons' -import { MaskColors, ShadowRootTooltip, makeStyles } from '@masknet/theme' -import { IconButton } from '@mui/material' -import { memo } from 'react' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import GuideStep from '../GuideStep/index.js' - -interface TooltipConfigProps { - placement?: 'bottom' | 'top' - disabled?: boolean -} - -interface PostDialogHintUIProps extends withClasses<'buttonTransform' | 'iconButton' | 'tooltip'> { - disableGuideTip?: boolean - size?: number - tooltip?: TooltipConfigProps - iconType?: string - onHintButtonClicked: () => void -} - -const useStyles = makeStyles()((theme) => ({ - button: { - padding: 'var(--icon-padding, 10px)', - }, -})) - -const ICON_MAP: Record = { - minds: , - default: ( - - ), -} - -const EntryIconButton = memo(function EntryIconButton(props: PostDialogHintUIProps) { - const t = useMaskSharedTrans() - const { tooltip, disableGuideTip } = props - const { classes, cx } = useStyles(undefined, { props }) - - const Entry = ( - - - {ICON_MAP[props.iconType ?? 'default']} - - - ) - - return disableGuideTip ? Entry : ( - - {Entry} - - ) -}) - -export const PostDialogHint = memo(function PostDialogHintUI(props: PostDialogHintUIProps) { - const { onHintButtonClicked, size, ...others } = props - const { classes } = useStyles(undefined, { props }) - const t = useMaskSharedTrans() - return ( -
- -
- ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx b/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx deleted file mode 100644 index 11a7794abb42..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useSubscription } from 'use-subscription' -import { - usePostInfoDetails, - createInjectHooksRenderer, - useActivatedPluginsSiteAdaptor, -} from '@masknet/plugin-infra/content-script' -import { DecryptPost } from './DecryptedPost/DecryptedPost.js' -import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' -import { PossiblePluginSuggestionPostInspector } from './DisabledPluginSuggestion.js' -import { MaskPostExtraPluginWrapperWithPermission } from './PermissionBoundary.js' -import { PersistentStorages } from '@masknet/shared-base' - -const PluginHooksRenderer = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.PostInspector, - MaskPostExtraPluginWrapperWithPermission, -) - -export interface PostInspectorProps { - zipPost(): void - /** @default 'before' */ - slotPosition?: 'before' | 'after' -} -export function PostInspector(props: PostInspectorProps) { - const postBy = usePostInfoDetails.author() - const hasEncryptedPost = usePostInfoDetails.hasMaskPayload() - const postImages = usePostInfoDetails.postMetadataImages() - const isDebugging = useSubscription(PersistentStorages.Settings.storage.debugging.subscription) - const whoAmI = useCurrentIdentity() - - if (hasEncryptedPost || postImages.length) { - if (!isDebugging) props.zipPost() - return withAdditionalContent() - } - return withAdditionalContent(null) - function withAdditionalContent(x: JSX.Element | null) { - const slot = hasEncryptedPost ? null : - return ( - <> - {process.env.NODE_ENV === 'development' && !postBy ? -

Please fix me. Post author is not detected.

- : null} - {props.slotPosition !== 'after' && slot} - {x} - - - {props.slotPosition !== 'before' && slot} - - ) - } -} diff --git a/packages/mask/content-script/components/InjectedComponents/PostReplacer.tsx b/packages/mask/content-script/components/InjectedComponents/PostReplacer.tsx deleted file mode 100644 index eddd333751c0..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/PostReplacer.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useEffect, useMemo, useState } from 'react' -import { produce } from 'immer' -import { makeStyles } from '@masknet/theme' -import { - type TransformationContext, - type TypedMessage, - isTypedMessageEqual, - emptyTransformationContext, - FlattenTypedMessage, - forEachTypedMessageChild, - isTypedMessageAnchor, - makeTypedMessageText, - isTypedMessageText, -} from '@masknet/typed-message' -import { MaskMessages } from '@masknet/shared-base' -import { TypedMessageRender, useTransformedValue } from '@masknet/typed-message-react' -import { usePostInfoDetails } from '@masknet/plugin-infra/content-script' -import { TypedMessageRenderContext } from '../../../shared-ui/TypedMessageRender/context.js' -import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/ui.js' - -const useStyles = makeStyles()({ - root: { - overflowWrap: 'break-word', - }, -}) - -export interface PostReplacerProps { - zip: () => void - unzip: () => void -} - -export function PostReplacer(props: PostReplacerProps) { - const { classes } = useStyles() - - const [postMessage, setPostMessage] = useState(usePostInfoDetails.rawMessage()) - const iv = usePostInfoDetails.postIVIdentifier() - useEffect(() => { - if (postMessage?.meta || !iv?.toText()) return - return MaskMessages.events.postReplacerHidden.on(() => { - setPostMessage( - produce((draft) => { - return { ...draft, items: [makeTypedMessageText('')] } - }), - ) - }) - }, [postMessage?.meta, iv?.toText]) - - const author = usePostInfoDetails.author() - const currentProfile = useCurrentIdentity()?.identifier - const url = usePostInfoDetails.url() - - const initialTransformationContext = useMemo((): TransformationContext => { - return { - authorHint: author || undefined, - currentProfile, - postURL: url?.href, - } - }, [author, currentProfile, url]) - - return ( - - - - - - ) -} - -function Transformer({ - message, - zip, - unzip, -}: { - message: TypedMessage -} & PostReplacerProps) { - const after = useTransformedValue(message) - - const shouldReplace = useMemo(() => { - const flatten = FlattenTypedMessage(message, emptyTransformationContext) - if (!isTypedMessageEqual(flatten, after)) return true - if (hasCashOrHashTag(after)) return true - if (shouldHiddenPostReplacer(message)) return true - return false - }, [message, after]) - - useEffect(() => { - if (shouldReplace) zip?.() - else unzip?.() - - return () => unzip?.() - }, []) - - if (shouldReplace) return - return null -} -function hasCashOrHashTag(message: TypedMessage): boolean { - let result = false - function visitor(node: TypedMessage): 'stop' | void { - if (isTypedMessageAnchor(node)) { - if (node.category === 'cash' || node.category === 'hash') { - result = true - return 'stop' - } - } else forEachTypedMessageChild(node, visitor) - } - visitor(message) - forEachTypedMessageChild(message, visitor) - return result -} - -function shouldHiddenPostReplacer(message: TypedMessage): boolean { - return isTypedMessageText(message) && message.content === '' -} diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx deleted file mode 100644 index cac55715c26a..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Twitter } from '@masknet/web3-providers' -import { NFTBadgeTimeline } from '@masknet/plugin-avatar' -import { useQuery } from '@tanstack/react-query' - -interface Props { - className?: string - size: number - userId?: string -} -export function AvatarDecoration({ userId, className, size }: Props) { - const { data: user } = useQuery({ - queryKey: ['twitter', 'profile', 'check-nft-avatar', userId], - queryFn: () => { - if (!userId) return null - return Twitter.getUserByScreenName(userId, true) - }, - }) - - if (!userId || !user) return null - - return ( - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileBar.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileBar.tsx deleted file mode 100644 index 28175e084c77..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileBar.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import { memo, useCallback, useEffect, useRef, useState } from 'react' -import { v4 as uuid } from 'uuid' -import { Icons } from '@masknet/icons' -import { AddressItem, CopyButton, Image, TokenWithSocialGroupMenu, useCollectionByTwitterHandle } from '@masknet/shared' -import { CrossIsolationMessages, EMPTY_LIST, type SocialAccount, type SocialIdentity } from '@masknet/shared-base' -import { useAnchor } from '@masknet/shared-base-ui' -import { makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useChainContext, useWeb3Utils } from '@masknet/web3-hooks-base' -import { TrendingAPI } from '@masknet/web3-providers/types' -import { isSameAddress } from '@masknet/web3-shared-base' -import { ChainId } from '@masknet/web3-shared-evm' -import { Box, Link, Skeleton, Typography } from '@mui/material' -import type { BoxProps } from '@mui/system' -import { PluginTraderMessages } from '@masknet/plugin-trader' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { AvatarDecoration } from './AvatarDecoration.js' - -const useStyles = makeStyles()((theme, _, refs) => ({ - root: { - display: 'flex', - alignItems: 'center', - columnGap: 4, - }, - avatar: { - position: 'relative', - height: 40, - width: 40, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexShrink: 0, - flexGrow: 0, - filter: 'drop-shadow(0px 6px 12px rgba(28, 104, 243, 0.2))', - backdropFilter: 'blur(16px)', - '& img': { - position: 'absolute', - borderRadius: '100%', - // Adjust to fit the rainbow border. - transform: 'scale(0.94, 0.96) translate(0, 1px)', - }, - [`& .${refs.avatarDecoration}`]: { - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - transform: 'scale(1)', - }, - }, - avatarImageContainer: { - borderRadius: '50%', - }, - avatarDecoration: {}, - description: { - height: 40, - marginLeft: 10, - overflow: 'auto', - flexGrow: 1, - scrollbarWidth: 'none', - '&::-webkit-scrollbar': { - display: 'none', - }, - '& :focus:not(:focus-visible)': { - outline: 0, - }, - }, - nickname: { - color: theme.palette.text.primary, - fontWeight: 700, - fontSize: 18, - lineHeight: '22px', - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - addressRow: { - fontSize: 14, - display: 'flex', - alignItems: 'center', - columnGap: 2, - }, - address: { - color: theme.palette.text.primary, - fontSize: 14, - height: 18, - fontWeight: 400, - lineHeight: '18px', - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - linkIcon: { - lineHeight: '14px', - height: 14, - overflow: 'hidden', - color: theme.palette.maskColor.second, - cursor: 'pointer', - flexShrink: 0, - }, -})) - -interface ProfileBarProps extends BoxProps { - identity: SocialIdentity - socialAccounts: Array> - address?: string - onAddressChange?: (address: string) => void -} - -/** - * What a Profile includes: - * - Website info - * - Wallets - */ -export const ProfileBar = memo(function ProfileBar({ - socialAccounts, - address, - identity, - onAddressChange, - className, - children, - ...rest -}) { - const { classes, theme, cx } = useStyles() - const t = useMaskSharedTrans() - const { current: avatarClipPathId } = useRef(uuid()) - const { anchorEl, anchorBounding } = useAnchor() - - const collectionList = useCollectionByTwitterHandle(identity.identifier?.userId) - - const Utils = useWeb3Utils() - const { chainId } = useChainContext() - - const [walletMenuOpen, setWalletMenuOpen] = useState(false) - const closeMenu = useCallback(() => setWalletMenuOpen(false), []) - useEffect(() => { - const closeMenu = () => setWalletMenuOpen(false) - window.addEventListener('scroll', closeMenu, false) - return () => { - window.removeEventListener('scroll', closeMenu, false) - } - }, []) - const selectedAccount = socialAccounts.find((x) => isSameAddress(x.address, address)) - - return ( - -
- {identity.nickname} - -
- - - {identity.nickname} - - {address ? -
- - - { - event.stopPropagation() - }} - sx={{ outline: 0 }} - className={classes.linkIcon}> - - - { - setWalletMenuOpen((v) => !v) - }} - /> -
- : null} -
- - { - setWalletMenuOpen(false) - if (!anchorBounding) return - PluginTraderMessages.trendingAnchorObserved.sendToLocal({ - name: identity.identifier?.userId || '', - identity, - address, - anchorBounding, - anchorEl, - type: TrendingAPI.TagType.HASH, - isCollectionProjectPopper: true, - currentResult, - }) - - CrossIsolationMessages.events.profileCardEvent.sendToLocal({ open: false }) - }} - anchorPosition={{ - top: 60, - left: 60, - }} - anchorReference="anchorPosition" - /> - - {children} -
- ) -}) - -type ProfileBarSkeletonProps = Omit - -// This Skeleton is not fully empty, but also has user address -export const ProfileBarSkeleton = memo(function ProfileBarSkeleton({ - socialAccounts, - address, - className, - children, - ...rest -}) { - const { classes, cx } = useStyles() - const t = useMaskSharedTrans() - - const Utils = useWeb3Utils() - const { chainId } = useChainContext() - - const selectedAccount = socialAccounts.find((x) => isSameAddress(x.address, address)) - - return ( - -
- -
- - - {address ? -
- - - { - event.stopPropagation() - }} - sx={{ outline: 0 }} - className={classes.linkIcon}> - - -
- : null} -
- {children} -
- ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileCardTitle.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileCardTitle.tsx deleted file mode 100644 index 647c369eb3b9..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileCard/ProfileCardTitle.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { Icons } from '@masknet/icons' -import { useIsMinimalMode } from '@masknet/plugin-infra/content-script' -import { TipButton } from '@masknet/plugin-tips' -import { PersonaSelectPanelModal, SocialAccountList, useCurrentPersonaConnectStatus } from '@masknet/shared' -import { - CrossIsolationMessages, - EMPTY_LIST, - PluginID, - type SocialAccount, - type SocialIdentity, -} from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { NextIDProof } from '@masknet/web3-providers' -import { useQuery } from '@tanstack/react-query' -import type { HTMLProps } from 'react' -import { useLastRecognizedIdentity } from '../../DataSource/useActivatedUI.js' -import { useCurrentPersona, usePersonasFromDB } from '../../../../shared-ui/hooks/index.js' -import { ProfileBar, ProfileBarSkeleton } from './ProfileBar.js' - -const useStyles = makeStyles()((theme) => { - return { - title: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - }, - profileBar: { - width: '100%', - }, - operations: { - display: 'flex', - alignItems: 'center', - marginLeft: 'auto', - }, - gearIcon: { - marginLeft: theme.spacing(1), - color: theme.palette.text.primary, - cursor: 'pointer', - }, - tipButton: { - marginLeft: theme.spacing(1), - width: 40, - height: 40, - borderRadius: 40, - border: `1px solid ${theme.palette.maskColor.line}`, - }, - } -}) - -function openWeb3ProfileSettingDialog() { - CrossIsolationMessages.events.web3ProfileDialogEvent.sendToLocal({ - open: true, - }) -} - -function Web3ProfileSettingButton() { - const { classes } = useStyles() - - const personas = usePersonasFromDB() - const persona = useCurrentPersona() - const identity = useLastRecognizedIdentity() - const { value: status, loading } = useCurrentPersonaConnectStatus( - personas, - persona?.identifier, - undefined, - identity, - ) - - if (loading) return null - - return ( - { - if (status.connected && status.verified) { - openWeb3ProfileSettingDialog() - } else { - PersonaSelectPanelModal.open({ - enableVerify: !status.verified, - finishTarget: PluginID.Web3Profile, - }) - } - }} - /> - ) -} - -interface ProfileCardTitleProps extends HTMLProps { - identity?: SocialIdentity - socialAccounts: Array> - address?: string - onAddressChange?(address: string): void -} -export function ProfileCardTitle({ - className, - socialAccounts, - address, - identity, - onAddressChange, - ...rest -}: ProfileCardTitleProps) { - const me = useLastRecognizedIdentity() - const { classes, cx } = useStyles() - - const userId = identity?.identifier?.userId - const itsMe = !!userId && userId === me?.identifier?.userId - const { data: nextIdBindings = EMPTY_LIST } = useQuery({ - queryKey: ['next-id', 'profiles-by-twitter-id', userId], - enabled: !!userId, - queryFn: async () => { - if (!userId) return EMPTY_LIST - return NextIDProof.queryProfilesByTwitterId(userId) - }, - }) - const tipsDisabled = useIsMinimalMode(PluginID.Tips) - - if (!identity) - return ( -
- -
- ) - - return ( -
- -
- {nextIdBindings.length ? - - : null} - {itsMe ? - - : !tipsDisabled ? - - : null} -
-
-
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileCard/index.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileCard/index.tsx deleted file mode 100644 index f9bf004cca63..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileCard/index.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { useEffect, useMemo, useState, memo } from 'react' -import { Trans } from 'react-i18next' -import { useUpdateEffect } from 'react-use' -import { first } from 'lodash-es' -import { TabContext } from '@mui/lab' -import { Tab, Typography } from '@mui/material' -import { Icons } from '@masknet/icons' -import { - useActivatedPluginsSiteAdaptor, - usePluginTransField, - getProfileCardTabContent, -} from '@masknet/plugin-infra/content-script' -import { addressSorter, useSocialAccountsBySettings } from '@masknet/shared' -import { getAvailablePlugins } from '@masknet/plugin-infra' -import { useLocationChange } from '@masknet/shared-base-ui' -import { - EMPTY_LIST, - PluginID, - NetworkPluginID, - type SocialIdentity, - MaskMessages, - type SocialAddress, - SocialAddressType, -} from '@masknet/shared-base' -import { makeStyles, MaskTabList, useTabs } from '@masknet/theme' -import { isSameAddress } from '@masknet/web3-shared-base' -import { ChainId } from '@masknet/web3-shared-evm' -import { EVMWeb3ContextProvider, ScopedDomainsContainer } from '@masknet/web3-hooks-base' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' -import Services from '#services' -import { ProfileCardTitle } from './ProfileCardTitle.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' - -interface Props extends withClasses<'text' | 'button' | 'root'> { - identity?: SocialIdentity - currentAddress?: string -} - -const useStyles = makeStyles()((theme) => { - return { - root: { - position: 'relative', - display: 'flex', - flexDirection: 'column', - overflow: 'auto', - height: '100%', - overscrollBehavior: 'contain', - borderRadius: theme.spacing(1.5), - boxShadow: theme.palette.shadow.popup, - backgroundColor: theme.palette.maskColor.bottom, - }, - header: { - background: theme.palette.maskColor.modalTitleBg, - padding: theme.spacing(2, 2, 0, 2), - boxSizing: 'border-box', - flexShrink: 0, - }, - content: { - position: 'relative', - flexGrow: 1, - backgroundColor: theme.palette.maskColor.bottom, - overflow: 'auto', - scrollbarWidth: 'none', - '::-webkit-scrollbar': { - display: 'none', - }, - }, - tabs: { - display: 'flex', - position: 'relative', - paddingTop: 0, - marginTop: theme.spacing(2), - }, - tabRoot: { - color: 'blue', - }, - footer: { - position: 'absolute', - height: 48, - left: 0, - bottom: 0, - right: 0, - display: 'flex', - justifyContent: 'flex-end', - alignItems: 'center', - background: theme.palette.maskColor.bg, - backdropFilter: 'blur(5px)', - padding: theme.spacing(1.5), - boxSizing: 'border-box', - fontWeight: 700, - zIndex: 2, - }, - cardIcon: { - filter: 'drop-shadow(0px 6px 12px rgba(0, 65, 185, 0.2))', - marginLeft: theme.spacing(0.25), - }, - cardName: { - color: theme.palette.maskColor.main, - fontWeight: 700, - marginRight: 'auto', - marginLeft: theme.spacing(0.5), - }, - powered: { - color: theme.palette.text.secondary, - fontWeight: 700, - }, - } -}) - -export const ProfileCard = memo(({ identity, currentAddress, ...rest }: Props) => { - const { classes } = useStyles(undefined, { props: { classes: rest.classes } }) - - const t = useMaskSharedTrans() - const translate = usePluginTransField() - const fallbackAccounts = useMemo(() => { - return [ - { - address: currentAddress, - type: SocialAddressType.Address, - pluginID: NetworkPluginID.PLUGIN_EVM, - chainId: ChainId.Mainnet, - label: '', - }, - ] as Array> - }, [currentAddress]) - const { - data: allSocialAccounts, - isPending, - refetch: retrySocialAddress, - } = useSocialAccountsBySettings(identity, undefined, addressSorter, (a, b, c, d) => - Services.Identity.signWithPersona(a, b, c, location.origin, d), - ) - const socialAccounts = useMemo(() => { - const accounts = isPending && !allSocialAccounts.length ? fallbackAccounts : allSocialAccounts - return accounts.filter((x) => x.pluginID === NetworkPluginID.PLUGIN_EVM) - }, [allSocialAccounts, fallbackAccounts, isPending]) - - const [selectedAddress, setSelectedAddress] = useState(currentAddress) - const firstAddress = first(socialAccounts)?.address - const activeAddress = selectedAddress || firstAddress - - const selectedSocialAccount = useMemo( - () => socialAccounts.find((x) => isSameAddress(x.address, activeAddress)), - [activeAddress, socialAccounts], - ) - - const userId = identity?.identifier?.userId - - useEffect(() => { - return MaskMessages.events.ownProofChanged.on(() => { - retrySocialAddress() - }) - }, [retrySocialAddress]) - - const activatedPlugins = useActivatedPluginsSiteAdaptor('any') - const displayPlugins = getAvailablePlugins(activatedPlugins, (plugins) => { - return plugins - .flatMap((x) => x.ProfileCardTabs?.map((y) => ({ ...y, pluginID: x.ID })) ?? EMPTY_LIST) - .filter((x) => { - const isAllowed = x.pluginID === PluginID.RSS3 || x.pluginID === PluginID.Collectible - const shouldDisplay = x.Utils?.shouldDisplay?.(identity, selectedSocialAccount) ?? true - return isAllowed && shouldDisplay - }) - .sort((a, z) => a.priority - z.priority) - }) - const tabs = displayPlugins.map((x) => ({ - id: x.ID, - label: typeof x.label === 'string' ? x.label : translate(x.pluginID, x.label), - })) - - const [currentTab, onChange] = useTabs(first(tabs)?.id ?? PluginID.Collectible, ...tabs.map((tab) => tab.id)) - - const component = useMemo(() => { - if (currentTab === `${PluginID.RSS3}_Social`) - Telemetry.captureEvent(EventType.Access, EventID.EntryTimelineHoverUserSocialSwitchTo) - if (currentTab === `${PluginID.RSS3}_Activities`) - Telemetry.captureEvent(EventType.Access, EventID.EntryTimelineHoverUserActivitiesSwitchTo) - if (currentTab === `${PluginID.RSS3}_Donation`) - Telemetry.captureEvent(EventType.Access, EventID.EntryTimelineHoverUserDonationsSwitchTo) - const Component = getProfileCardTabContent(currentTab) - return - }, [currentTab, identity?.publicKey, selectedSocialAccount]) - - useLocationChange(() => { - onChange(undefined, first(tabs)?.id) - }) - - useUpdateEffect(() => { - onChange(undefined, first(tabs)?.id) - }, [userId]) - - const scopedDomainsMap: Record = useMemo(() => { - return socialAccounts.reduce((map, account) => { - if (!account.label) return map - return { - ...map, - [account.address.toLowerCase()]: account.label, - } - }, {}) - }, [socialAccounts]) - - return ( - - -
-
- - {tabs.length > 0 && currentTab ? -
- - - {tabs.map((tab) => ( - - ))} - - -
- : null} -
-
{component}
-
- - {t.web3_profile_card_name()} - - theme.palette.text.primary} - /> - ), - }} - /> - - -
-
-
-
- ) -}) - -ProfileCard.displayName = 'ProfileCard' diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileCover.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileCover.tsx deleted file mode 100644 index 89cba595fa92..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileCover.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useMemo } from 'react' -import { makeStyles } from '@masknet/theme' -import { PluginID } from '@masknet/shared-base' -import { useActivatedPluginsSiteAdaptor, createInjectHooksRenderer } from '@masknet/plugin-infra/content-script' -import { useCurrentVisitingIdentity } from '../DataSource/useActivatedUI.js' - -interface ProfileCoverProps extends withClasses<'root'> {} - -const useStyles = makeStyles()(() => ({ - root: { - position: 'absolute', - top: 0, - width: '100%', - height: '100%', - }, -})) -export function ProfileCover(props: ProfileCoverProps) { - const { classes } = useStyles(undefined, { props: { classes: props.classes } }) - const currentVisitingIdentity = useCurrentVisitingIdentity() - - // TODO: Multi-plugin rendering support - const component = useMemo(() => { - const Component = createInjectHooksRenderer(useActivatedPluginsSiteAdaptor.visibility.useAnyMode, (x) => { - const cover = x.ProfileCover?.find((x) => x.ID === `${PluginID.Debugger}_cover`) - return cover?.UI?.Cover - }) - - return - }, [currentVisitingIdentity]) - - if (!component) return null - return
{component}
-} diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileTab.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileTab.tsx deleted file mode 100644 index dcb74f692905..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileTab.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useCallback, useEffect, useState, type PropsWithChildren } from 'react' -import { useMount } from 'react-use' -import { Typography } from '@mui/material' -import { MaskMessages, ProfileTabs, Sniffings } from '@masknet/shared-base' -import { useMatchXS, useLocationChange } from '@masknet/shared-base-ui' - -interface ProfileTabProps extends withClasses<'tab' | 'button' | 'selected'>, PropsWithChildren<{}> { - clear(): void - reset(): void - // Required! This component don't have it own style. - classes: Record<'root' | 'button' | 'selected', string> - title: string - type?: ProfileTabs - icon?: React.ReactNode -} - -export function ProfileTab(props: ProfileTabProps) { - const { reset, clear, children, classes, title, type = ProfileTabs.WEB3 } = props - const [active, setActive] = useState(false) - const isMobile = useMatchXS() - - const switchToTab = useCallback(() => { - MaskMessages.events.profileTabUpdated.sendToLocal({ show: true, type }) - setActive(true) - clear() - }, [clear, type]) - - const onClick = useCallback(() => { - // Change the url hashtag to trigger `locationchange` event from e.g. 'hostname/medias#web3 => hostname/medias' - Sniffings.is_twitter_page && location.assign('#' + type) - switchToTab() - }, [switchToTab, type]) - - useMount(() => { - if (location.hash !== '#' + type || active || location.pathname === '/search') return - switchToTab() - }) - - useLocationChange(() => { - const testId = (document.activeElement as HTMLElement | null)?.dataset.testid - if (testId === 'SearchBox_Search_Input') return - - MaskMessages.events.profileTabUpdated.sendToLocal({ show: false }) - setActive(false) - reset() - }) - - useEffect(() => { - return MaskMessages.events.profileTabActive.on((data) => { - setActive(data.active) - }) - }, []) - - return ( -
- - {props.icon} - {isMobile && props.icon ? null : title} - {active && children ? children : null} - -
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/ProfileTabContent.tsx b/packages/mask/content-script/components/InjectedComponents/ProfileTabContent.tsx deleted file mode 100644 index 17b65a56229d..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ProfileTabContent.tsx +++ /dev/null @@ -1,523 +0,0 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useUpdateEffect } from 'react-use' -import { first } from 'lodash-es' -import { TabContext } from '@mui/lab' -import { Link, Button, Stack, Tab, ThemeProvider, Typography } from '@mui/material' -import { Icons } from '@masknet/icons' -import { useQuery } from '@tanstack/react-query' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' -import { - useActivatedPluginsSiteAdaptor, - useIsMinimalMode, - usePluginTransField, - getProfileTabContent, -} from '@masknet/plugin-infra/content-script' -import { getAvailablePlugins } from '@masknet/plugin-infra' -import { - AddressItem, - ConnectPersonaBoundary, - GrantPermissions, - PluginCardFrameMini, - useCurrentPersonaConnectStatus, - useSocialAccountsBySettings, - TokenWithSocialGroupMenu, - SocialAccountList, - useCollectionByTwitterHandle, - addressSorter, - WalletSettingsEntry, -} from '@masknet/shared' -import { - CrossIsolationMessages, - EMPTY_LIST, - MaskMessages, - NextIDPlatform, - PluginID, - ProfileTabs, - Sniffings, - currentPersonaIdentifier, -} from '@masknet/shared-base' -import { useValueRef, useLocationChange } from '@masknet/shared-base-ui' -import { makeStyles, MaskLightTheme, MaskTabList, useTabs } from '@masknet/theme' -import { NextIDProof } from '@masknet/web3-providers' -import { isSameAddress } from '@masknet/web3-shared-base' -import { ScopedDomainsContainer, useSnapshotSpacesByTwitterHandle } from '@masknet/web3-hooks-base' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { - useCurrentVisitingIdentity, - useLastRecognizedIdentity, - useSocialIdentity, - useSocialIdentityByUserId, -} from '../DataSource/useActivatedUI.js' -import { useGrantPermissions, usePluginHostPermissionCheck } from '../DataSource/usePluginHostPermission.js' -import { SearchResultInspector } from './SearchResultInspector.js' -import { usePersonasFromDB } from '../../../shared-ui/hooks/usePersonasFromDB.js' -import Services from '#services' - -const useStyles = makeStyles()((theme) => ({ - root: { - width: Sniffings.is_facebook_page ? 876 : 'auto', - color: theme.palette.maskColor.main, - }, - container: { - background: - 'linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%), linear-gradient(90deg, rgba(28, 104, 243, 0.2) 0%, rgba(69, 163, 251, 0.2) 100%), #FFFFFF;', - padding: '16px 16px 0 16px', - }, - title: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: '16px', - }, - walletItem: { - display: 'flex', - alignItems: 'center', - fontSize: 18, - fontWeight: 700, - }, - settingLink: { - cursor: 'pointer', - marginTop: 4, - zIndex: 0, - '&:hover': { - textDecoration: 'none', - }, - }, - content: { - position: 'relative', - }, - walletButton: { - padding: 0, - fontSize: '18px', - minWidth: 0, - background: 'transparent', - '&:hover': { - background: 'none', - }, - }, - settingItem: { - display: 'flex', - alignItems: 'center', - }, - tabs: { - display: 'flex', - position: 'relative', - }, - gearIcon: { - color: theme.palette.maskColor.dark, - }, - linkOutIcon: { - color: theme.palette.maskColor.secondaryDark, - }, - mainLinkIcon: { - margin: '0px 2px', - color: theme.palette.maskColor.secondaryDark, - }, - reload: { - borderRadius: 20, - minWidth: 254, - }, -})) - -interface ProfileTabContentProps extends withClasses<'text' | 'button' | 'root'> {} - -export function ProfileTabContent(props: ProfileTabContentProps) { - return ( - - - - ) -} - -function openWeb3ProfileSettingDialog() { - CrossIsolationMessages.events.web3ProfileDialogEvent.sendToLocal({ - open: true, - }) -} - -function Content(props: ProfileTabContentProps) { - const { classes } = useStyles(undefined, { props }) - - const t = useMaskSharedTrans() - const translate = usePluginTransField() - - const [hidden, setHidden] = useState(true) - const [profileTabType, setProfileTabType] = useState(ProfileTabs.WEB3) - const [menuOpen, setMenuOpen] = useState(false) - const closeMenu = useCallback(() => setMenuOpen(false), []) - const allPersonas = usePersonasFromDB() - const lastRecognized = useLastRecognizedIdentity() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - - const { - value: personaStatus, - loading: loadingPersonaStatus, - error: loadPersonaStatusError, - retry: retryLoadPersonaStatus, - } = useCurrentPersonaConnectStatus(allPersonas, currentIdentifier, Services.Helper.openDashboard, lastRecognized) - - const currentVisitingSocialIdentity = useCurrentVisitingIdentity() - const { data: currentSocialIdentity } = useSocialIdentity(currentVisitingSocialIdentity) - const currentVisitingUserId = currentVisitingSocialIdentity?.identifier?.userId - const isOwnerIdentity = currentVisitingSocialIdentity?.isOwner - - const { - data: socialAccounts = EMPTY_LIST, - isPending: loadingSocialAccounts, - error: loadSocialAccounts, - refetch: retrySocialAccounts, - } = useSocialAccountsBySettings(currentSocialIdentity, undefined, addressSorter, (a, b, c, d) => - Services.Identity.signWithPersona(a, b, c, location.origin, d), - ) - const [selectedAddress = first(socialAccounts)?.address, setSelectedAddress] = useState() - const selectedSocialAccount = socialAccounts.find((x) => isSameAddress(x.address, selectedAddress)) - const { setPair } = ScopedDomainsContainer.useContainer() - useEffect(() => { - if (selectedSocialAccount?.address && selectedSocialAccount.label) { - setPair(selectedSocialAccount.address, selectedSocialAccount.label) - } - }, [selectedSocialAccount?.address, selectedSocialAccount?.label]) - - useEffect(() => { - return MaskMessages.events.ownProofChanged.on(() => { - retrySocialAccounts() - }) - }, [retrySocialAccounts]) - - const activatedPlugins = useActivatedPluginsSiteAdaptor('any') - const displayPlugins = getAvailablePlugins(activatedPlugins, (plugins) => { - return plugins - .flatMap((x) => x.ProfileTabs?.map((y) => ({ ...y, pluginID: x.ID })) ?? EMPTY_LIST) - .filter((x) => { - const shouldDisplay = - x.Utils?.shouldDisplay?.(currentVisitingSocialIdentity, selectedSocialAccount) ?? true - return x.pluginID !== PluginID.NextID && shouldDisplay - }) - .sort((a, z) => a.priority - z.priority) - }) - - const tabs = displayPlugins.map((x) => ({ - id: x.ID, - label: typeof x.label === 'string' ? x.label : translate(x.pluginID, x.label), - })) - - const [currentTab, onChange] = useTabs(first(tabs)?.id ?? PluginID.Collectible, ...tabs.map((tab) => tab.id)) - - const isWeb3ProfileDisabled = useIsMinimalMode(PluginID.Web3Profile) - - const isOnTwitter = Sniffings.is_twitter_page - const doesOwnerHaveNoAddress = - isOwnerIdentity && personaStatus.proof?.findIndex((p) => p.platform === NextIDPlatform.Ethereum) === -1 - - // the owner persona and site not verify on next ID - const myPersonaNotVerifiedYet = isOwnerIdentity && !personaStatus.verified - const showNextID = - isOnTwitter && - // enabled the plugin - (isWeb3ProfileDisabled || - myPersonaNotVerifiedYet || - // the owner persona and site verified on next ID but not verify the wallet - doesOwnerHaveNoAddress || - // the visiting persona not have social address list - (!isOwnerIdentity && !socialAccounts.length)) - - const componentTabId = showNextID ? `${PluginID.NextID}_tabContent` : currentTab - - const contentComponent = useMemo(() => { - const Component = getProfileTabContent(componentTabId) - if (!Component) return null - - return - }, [componentTabId, selectedSocialAccount, currentSocialIdentity]) - - const lackHostPermission = usePluginHostPermissionCheck(activatedPlugins.filter((x) => x.ProfileCardTabs?.length)) - - const lackPluginId = first(lackHostPermission ? [...lackHostPermission] : []) - const lackPluginDefine = activatedPlugins.find((x) => x.ID === lackPluginId) - - const [, onGrant] = useGrantPermissions(lackPluginDefine?.enableRequirement.host_permissions) - useLocationChange(() => { - onChange(undefined, first(tabs)?.id) - }) - - useUpdateEffect(() => { - onChange(undefined, first(tabs)?.id) - setSelectedAddress(undefined) - }, [currentVisitingUserId]) - - useEffect(() => { - if (profileTabType !== ProfileTabs.WEB3) return - if (currentTab === `${PluginID.RSS3}_Social`) - Telemetry.captureEvent(EventType.Access, EventID.EntryProfileUserSocialSwitchTo) - if (currentTab === `${PluginID.RSS3}_Activities`) - Telemetry.captureEvent(EventType.Access, EventID.EntryProfileUserActivitiesSwitchTo) - if (currentTab === `${PluginID.RSS3}_Donation`) - Telemetry.captureEvent(EventType.Access, EventID.EntryProfileUserDonationsSwitchTo) - }, [profileTabType, currentTab]) - - useEffect(() => { - return MaskMessages.events.profileTabHidden.on((data) => { - if (data.hidden) setHidden(data.hidden) - }) - }, [currentVisitingUserId]) - - const [isHideInspector, hideInspector] = useState(false) - - useEffect(() => { - return CrossIsolationMessages.events.hideSearchResultInspectorEvent.on((ev) => { - hideInspector(ev.hide) - }) - }, []) - - useEffect(() => { - return MaskMessages.events.profileTabUpdated.on((data) => { - setHidden(!data.show) - data.type && setProfileTabType(data.type) - }) - }, [currentVisitingUserId]) - - useEffect(() => { - const listener = () => setMenuOpen(false) - window.addEventListener('scroll', listener, false) - // not work, when it is out of shadow root. - window.addEventListener('click', listener, false) - - return () => { - window.removeEventListener('scroll', listener, false) - window.removeEventListener('click', listener, false) - } - }, []) - - const buttonRef = useRef(null) - const onSelect = (address: string) => { - setSelectedAddress(address) - setMenuOpen(false) - } - - const collectionList = - useCollectionByTwitterHandle(profileTabType === ProfileTabs.WEB3 ? currentVisitingUserId : '') ?? EMPTY_LIST - - const { data: spaces } = useSnapshotSpacesByTwitterHandle( - profileTabType === ProfileTabs.DAO ? currentVisitingUserId ?? '' : '', - ) - - const [currentTrendingIndex, setCurrentTrendingIndex] = useState(0) - const trendingResult = collectionList[currentTrendingIndex] - - const { data: identity } = useSocialIdentityByUserId(currentVisitingUserId) - - const { data: nextIdBindings = EMPTY_LIST } = useQuery({ - queryKey: ['profiles', 'by-twitter-id', currentVisitingUserId], - queryFn: () => { - if (!currentVisitingUserId) return EMPTY_LIST - return NextIDProof.queryProfilesByTwitterId(currentVisitingUserId) - }, - }) - - if (hidden) return null - - const keyword = - profileTabType === ProfileTabs.WEB3 ? trendingResult?.address || trendingResult?.name : currentVisitingUserId - - const searchResults = profileTabType === ProfileTabs.WEB3 ? collectionList : spaces - - if (keyword && !isHideInspector) - return ( -
- -
- ) - - if (lackHostPermission?.size) { - return ( - -
- - - -
-
- ) - } - - if (!currentVisitingUserId || (loadingSocialAccounts && !socialAccounts.length) || loadingPersonaStatus) - return ( - -
- -
-
- ) - - if (((isOwnerIdentity && loadPersonaStatusError) || loadSocialAccounts) && socialAccounts.length === 0) { - const handleClick = () => { - if (loadPersonaStatusError) retryLoadPersonaStatus() - if (loadSocialAccounts) retrySocialAccounts() - } - return ( - -
- - - t.palette.maskColor.danger}> - {t.load_failed()} - - - - -
-
- ) - } - - // Maybe should merge in NextIdPage - if (socialAccounts.length === 0 && !showNextID && !isOnTwitter) { - return ( - -
- - - t.palette.maskColor.publicMain}> - {t.web3_profile_no_social_address_list()} - - - -
-
- ) - } - - if (!socialAccounts.length && !showNextID) { - return ( - -
- - - - - -
-
- ) - } - - return ( -
- {tabs.length > 0 && !showNextID ? -
-
-
- - - { - setCurrentTrendingIndex(i) - hideInspector(false) - setMenuOpen(false) - }} - fromSocialCard - /> - - -
-
- theme.palette.maskColor.secondaryDark}> - {t.powered_by()} - - theme.palette.maskColor.dark}> - {t.mask_network()} - - {isOwnerIdentity && isOnTwitter ? - - - - : - - - } -
-
-
- - - {tabs.map((tab) => ( - - ))} - - -
-
- : null} -
{contentComponent}
-
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SearchResultInspector.tsx b/packages/mask/content-script/components/InjectedComponents/SearchResultInspector.tsx deleted file mode 100644 index 262bbefe1c26..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SearchResultInspector.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useEffect, useLayoutEffect, useMemo } from 'react' -import { useAsyncRetry } from 'react-use' -import { first } from 'lodash-es' -import { TabContext } from '@mui/lab' -import { Stack, Tab } from '@mui/material' -import { - getSearchResultContent, - getSearchResultContentForProfileTab, - getSearchResultTabContent, - getSearchResultTabs, - useActivatedPluginsSiteAdaptor, - usePluginTransField, - useIsMinimalMode, -} from '@masknet/plugin-infra/content-script' -import { EMPTY_LIST, PluginID, type SocialIdentity, type ProfileTabs } from '@masknet/shared-base' -import { makeStyles, MaskTabList, useTabs } from '@masknet/theme' -import { DSearch } from '@masknet/web3-providers' -import type { Web3Helper } from '@masknet/web3-helpers' -import { ScopedDomainsContainer } from '@masknet/web3-hooks-base' -import { type SearchResult, SearchResultType } from '@masknet/web3-shared-base' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import { useSearchedKeyword } from '../DataSource/useSearchedKeyword.js' - -const useStyles = makeStyles<{ isProfilePage?: boolean; searchType?: SearchResultType }>()( - (theme, { isProfilePage, searchType }) => ({ - contentWrapper: { - background: - isProfilePage || (searchType !== SearchResultType.EOA && searchType !== SearchResultType.Domain) ? - 'transparent' - : 'linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%), linear-gradient(90deg, rgba(28, 104, 243, 0.2) 0%, rgba(69, 163, 251, 0.2) 100%), #FFFFFF;', - }, - tabContent: { - position: 'relative', - maxHeight: 478, - borderBottom: isProfilePage ? 'unset' : `1px solid ${theme.palette.divider}`, - overflow: 'auto', - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - }), -) - -interface SearchResultInspectorProps { - keyword?: string - identity?: SocialIdentity | null - isProfilePage?: boolean - profileTabType?: ProfileTabs - searchResults?: Array> - currentSearchResult?: SearchResult -} - -export function SearchResultInspector(props: SearchResultInspectorProps) { - const { identity, profileTabType, isProfilePage } = props - - const translate = usePluginTransField() - const isMinimalMode = useIsMinimalMode(PluginID.Handle) - - const keyword_ = useSearchedKeyword() - const keyword = props.keyword || keyword_ - const activatedPlugins = useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode() - - const resultList = useAsyncRetry(async () => { - if (!keyword) return - return props.searchResults ?? DSearch.search(keyword) - }, [keyword, props.searchResults]) - - useEffect(() => { - if (profileTabType || !resultList.value?.length) return - const type = resultList.value[0].type - let timer: NodeJS.Timeout | undefined - if ( - type === SearchResultType.CollectionListByTwitterHandle || - type === SearchResultType.NonFungibleCollection || - type === SearchResultType.NonFungibleToken - ) - timer = setTimeout(() => Telemetry.captureEvent(EventType.Access, EventID.EntryTimelineDsearchNft), 500) - if (type === SearchResultType.FungibleToken) - timer = setTimeout(() => Telemetry.captureEvent(EventType.Access, EventID.EntryTimelineDsearchToken), 500) - return () => timer && clearTimeout(timer) - }, [resultList, profileTabType]) - - const currentResult = props.currentSearchResult ?? resultList.value?.[0] - - const { classes } = useStyles({ isProfilePage, searchType: currentResult?.type }) - const contentComponent = useMemo(() => { - if (!currentResult || !resultList.value?.length) return null - - const Component = - profileTabType ? getSearchResultContentForProfileTab(currentResult) : getSearchResultContent(currentResult) - - return ( - - ) - }, [currentResult, resultList.value, isProfilePage, identity, profileTabType]) - - const tabs = useMemo(() => { - if (!currentResult) return EMPTY_LIST - return getSearchResultTabs(activatedPlugins, currentResult, translate) - }, [activatedPlugins, resultList.value, translate]) - - const defaultTab = first(tabs)?.id ?? PluginID.Collectible - const [currentTab, onChange, , setTab] = useTabs(defaultTab, ...tabs.map((tab) => tab.id)) - useLayoutEffect(() => { - setTab(defaultTab) - }, [currentResult, defaultTab]) - - const tabContentComponent = useMemo(() => { - if (!currentResult) return null - const Component = getSearchResultTabContent(currentTab) - return - }, [currentTab, resultList.value]) - - if (isMinimalMode && !isProfilePage) return null - if (!keyword && !currentResult) return null - if (!contentComponent) return null - - return ( -
- -
-
{contentComponent}
- {tabs.length ? - - - - {tabs.map((tab) => ( - - ))} - - - - : null} -
- {tabContentComponent ? -
{tabContentComponent}
- : null} -
-
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SelectPeopleDialog.tsx b/packages/mask/content-script/components/InjectedComponents/SelectPeopleDialog.tsx deleted file mode 100644 index edb0a50dbe0a..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SelectPeopleDialog.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { ActionButton, makeStyles } from '@masknet/theme' -import { Button, DialogActions, DialogContent, alpha } from '@mui/material' -import { InjectedDialog, resolveNextIDPlatform, resolveValueToSearch, usePersonasFromNextID } from '@masknet/shared' -import { EMPTY_LIST, NextIDPlatform, type ProfileInformation as Profile } from '@masknet/shared-base' -import { uniqBy } from 'lodash-es' -import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' -import { useRecipientsList } from '../CompositionDialog/useRecipientsList.js' -import { useTwitterIdByWalletSearch } from '../shared/SelectRecipients/useTwitterIdByWalletSearch.js' -import { SelectProfileUI } from '../shared/SelectProfileUI/index.js' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' - -interface SelectProfileDialogProps { - open: boolean - profiles: Profile[] - selectedProfiles: Profile[] - onClose: () => void - onSelect: (people: Profile[]) => Promise -} -const useStyles = makeStyles()((theme) => ({ - content: { padding: '0 12px' }, - body: { - '::-webkit-scrollbar': { - display: 'none', - }, - padding: theme.spacing(2), - height: 450, - }, - action: { - display: 'flex', - gap: 16, - padding: 16, - boxSizing: 'border-box', - alignItems: 'center', - background: alpha(theme.palette.maskColor.bottom, 0.8), - boxShadow: - theme.palette.mode === 'light' ? - ' 0px 0px 20px rgba(0, 0, 0, 0.05)' - : '0px 0px 20px rgba(255, 255, 255, 0.12);', - borderRadius: '0px 0px 12px 12px', - flex: 1, - backdropFilter: 'blur(8px)', - }, - - cancel: { - color: theme.palette.maskColor.main, - background: theme.palette.maskColor.thirdMain, - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - height: 40, - '&:hover': { - color: theme.palette.maskColor.main, - background: theme.palette.maskColor.thirdMain, - }, - }, - share: { - color: theme.palette.maskColor.bottom, - background: theme.palette.maskColor.main, - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - height: 40, - }, -})) - -export function SelectProfileDialog({ open, profiles, selectedProfiles, onClose, onSelect }: SelectProfileDialogProps) { - const t = useMaskSharedTrans() - const { classes } = useStyles() - const [people, select] = useState([]) - const [committed, setCommitted] = useState(false) - const handleClose = useCallback(() => { - onClose() - setCommitted(false) - select([]) - }, [onClose]) - - const recipientsList = useRecipientsList() - const [rejection, onReject] = useState() - const share = useCallback(() => { - setCommitted(true) - onSelect(uniqBy([...people, ...selectedProfiles], (x) => x.identifier)).then(handleClose, onReject) - }, [handleClose, people, selectedProfiles, onSelect]) - - const [valueToSearch, setValueToSearch] = useState('') - const currentIdentity = useCurrentIdentity() - const type = resolveNextIDPlatform(valueToSearch) - - const value = resolveValueToSearch(valueToSearch) - const { isPending: searchLoading, data: NextIDResults } = usePersonasFromNextID( - value, - type ?? NextIDPlatform.NextID, - false, - ) - - const NextIDItems = useTwitterIdByWalletSearch(NextIDResults, value, type) - const myUserId = currentIdentity?.identifier.userId - const searchedList = useMemo(() => { - if (!recipientsList?.recipients) return EMPTY_LIST - const profileItems = recipientsList.recipients.filter((x) => x.identifier.userId !== myUserId) - // Selected might contain profiles that fetched asynchronously from - // Next.ID, which are not stored locally - return uniqBy(profileItems.concat(NextIDItems, profiles), ({ linkedPersona }) => linkedPersona?.rawPublicKey) - }, [NextIDItems, profiles, recipientsList.recipients, myUserId]) - - useEffect(() => { - if (!open) return - recipientsList.request() - }, [open, recipientsList.request]) - - useEffect(() => { - if (!open) return - Telemetry.captureEvent(EventType.Access, EventID.EntryMaskComposeVisibleSelected) - }, [open]) - - const canCommit = committed || people.length === 0 - - return ( - - - - - {rejection ? - - <> - Error: {rejection.message} {console.error(rejection)} - - - : null} - - - - {t.done()} - - - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/AccountConnectStatus.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/AccountConnectStatus.tsx deleted file mode 100644 index b35fca168d57..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/AccountConnectStatus.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { Icons } from '@masknet/icons' -import { BindingDialog, LoadingStatus, SOCIAL_MEDIA_ROUND_ICON_MAPPING, type BindingDialogProps } from '@masknet/shared' -import { SOCIAL_MEDIA_NAME } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { Box, Button, Typography } from '@mui/material' -import { memo } from 'react' -import { Trans } from 'react-i18next' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/ui.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { SetupGuideContext } from './SetupGuideContext.js' - -const useStyles = makeStyles()((theme) => { - return { - main: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: theme.spacing(3), - height: '100%', - boxSizing: 'border-box', - }, - icon: { - marginTop: theme.spacing(3), - }, - title: { - fontSize: 18, - margin: theme.spacing(1.5), - fontWeight: 700, - }, - loadingBox: { - width: 320, - height: 130, - padding: theme.spacing(2), - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - text: { - fontSize: 16, - textAlign: 'center', - }, - } -}) - -function Frame({ children, ...rest }: BindingDialogProps) { - const { classes } = useStyles() - const t = useMaskSharedTrans() - const site = activatedSiteAdaptorUI!.networkIdentifier - const Icon = SOCIAL_MEDIA_ROUND_ICON_MAPPING[site] || Icons.Globe - return ( - -
- - {t.connect_persona()} - {children} -
-
- ) -} - -interface Props extends BindingDialogProps { - currentUserId?: string - expectAccount: string - /** Loading current userId */ - loading?: boolean -} - -export const AccountConnectStatus = memo(function AccountConnectStatus({ - expectAccount, - currentUserId, - loading, - ...rest -}) { - const { classes } = useStyles() - const t = useMaskSharedTrans() - const site = activatedSiteAdaptorUI!.networkIdentifier - const siteName = SOCIAL_MEDIA_NAME[site] || '' - - const { connected } = SetupGuideContext.useContainer() - - if (loading) - return ( - -
- -
- - ) - - if (connected) - return ( - - - , - }} - /> - - - {t.switch_for_more_connections()} - - - - - - ) - - if (currentUserId) - return ( - - {t.not_current_account()} - - , - }} - /> - - - ) - - return ( - - {t.request_to_login({ siteName })} - - ) -}) diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/CheckConnection.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/CheckConnection.tsx deleted file mode 100644 index e91849ad970c..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/CheckConnection.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { BindingDialogProps } from '@masknet/shared' -import { AccountConnectStatus } from './AccountConnectStatus.js' -import { SetupGuideContext } from './SetupGuideContext.js' - -export function CheckConnection({ onClose }: BindingDialogProps) { - const { userId, loadingCurrentUserId, currentUserId } = SetupGuideContext.useContainer() - - return ( - - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/PinExtension.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/PinExtension.tsx deleted file mode 100644 index f9b647b95064..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/PinExtension.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Icons } from '@masknet/icons' -import { SetupGuideStep } from '@masknet/shared-base' -import { MaskColorVar, makeStyles } from '@masknet/theme' -import { Extension as ExtensionIcon } from '@mui/icons-material' -import { Box, Button, Typography } from '@mui/material' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { WizardDialog } from './WizardDialog.js' - -interface PinExtensionProps { - onDone?: () => void - onClose?: () => void -} - -const useStyles = makeStyles()((theme) => ({ - button: { - minWidth: 150, - height: 40, - minHeight: 40, - marginLeft: 0, - marginTop: 0, - [theme.breakpoints.down('sm')]: { - width: '100%', - }, - fontSize: 14, - wordBreak: 'keep-all', - '&,&:hover': { - color: `${MaskColorVar.twitterButtonText} !important`, - background: `${MaskColorVar.twitterButton} !important`, - }, - }, - tip: { - fontSize: 16, - fontWeight: 500, - lineHeight: '22px', - paddingTop: 16, - }, - connection: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-around', - }, - connectItem: { - flex: 1, - height: 75, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'space-between', - }, - line: { - width: 100, - height: 1, - borderTop: `dashed 1px ${MaskColorVar.borderSecondary}`, - }, - name: { - fontSize: 16, - fontWeight: 500, - }, -})) - -export function PinExtension({ onDone, onClose }: PinExtensionProps) { - const pinImg = new URL('../../../resources/extensionPinned.png', import.meta.url).toString() - const { classes } = useStyles() - const t = useMaskSharedTrans() - - return ( - - - - - Mask Network - - - - - - - - } - tip={ - -
{t.setup_guide_pin_tip()}
-
    -
  1. - {t.setup_guide_pin_tip_step_click_left()} - - {t.setup_guide_pin_tip_step_click_right()} -
  2. -
  3. - {t.setup_guide_pin_tip_step_find_left()} - - {t.setup_guide_pin_tip_step_find_right()} -
  4. -
  5. {t.setup_guide_pin_tip_successfully()}
  6. -
-
- } - footer={ - - } - onClose={onClose} - /> - ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/SetupGuideContext.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/SetupGuideContext.tsx deleted file mode 100644 index 9929202d4ee7..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/SetupGuideContext.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import Services from '#services' -import { - EnhanceableSite, - MaskMessages, - SetupGuideStep, - userPinExtension, - type PersonaIdentifier, -} from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { useQuery } from '@tanstack/react-query' -import { useEffect, useMemo, useState } from 'react' -import { createContainer } from 'unstated-next' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' -import { useLastRecognizedIdentity } from '../../DataSource/useActivatedUI.js' -import { useSetupGuideStatus } from '../../GuideStep/useSetupGuideStatus.js' -import { useCurrentUserId } from './hooks/useCurrentUserId.js' -import { useConnectedVerified } from './hooks/useConnectedVerified.js' - -export function useSetupGuideStepInfo(persona?: PersonaIdentifier) { - // #region parse setup status - const lastPinExtensionSetting = useValueRef(userPinExtension) - const setupGuide = useSetupGuideStatus() - // #endregion - - const myIdentity = useLastRecognizedIdentity() - const [loadingCurrentUserId, currentUserId] = useCurrentUserId() - const userId = setupGuide.username || currentUserId || '' - - const { - data: personaInfo, - isFetching: checkingConnected, - refetch, - } = useQuery({ - enabled: !!persona?.publicKeyAsHex, - queryKey: ['query-persona-info', persona?.publicKeyAsHex], - queryFn: async () => { - if (!persona?.publicKeyAsHex) return null - return Services.Identity.queryPersona(persona) - }, - }) - useEffect(() => MaskMessages.events.ownPersonaChanged.on(() => refetch()), []) - const { data: currentTabId } = useQuery({ - queryKey: ['current-tab-id'], - queryFn: async () => Services.Helper.getActiveTab().then((x) => x?.id), - refetchOnWindowFocus: true, - }) - const { networkIdentifier: site, configuration } = activatedSiteAdaptorUI! - const nextIdPlatform = configuration.nextIDConfig?.platform - const [checkingVerified, verified] = useConnectedVerified(personaInfo?.identifier?.publicKeyAsHex, userId) - const connected = personaInfo?.linkedProfiles.some( - (x) => x.identifier.network === site && x.identifier.userId === userId, - ) - - useEffect(() => { - if (userId || site !== EnhanceableSite.Twitter) return - // In order to collect user info after login, need to reload twitter once - let reloaded = false - const handler = () => { - // twitter will redirect to home page after login - if (!(!reloaded && location.pathname === '/home')) return - reloaded = true - location.reload() - } - window.addEventListener('locationchange', handler) - return () => window.removeEventListener('locationchange', handler) - }, [userId]) - - const [isFirstConnection, setIsFirstConnection] = useState(false) - const step = useMemo(() => { - if (!setupGuide.status) { - // Should show pin extension when not set - if (!lastPinExtensionSetting) { - return SetupGuideStep.PinExtension - } else { - return SetupGuideStep.Close - } - } - const nextStep = isFirstConnection ? SetupGuideStep.VerifyOnNextID : SetupGuideStep.CheckConnection - if (checkingVerified || checkingConnected || loadingCurrentUserId) return nextStep - if (!connected || (nextIdPlatform && !verified)) { - return SetupGuideStep.VerifyOnNextID - } - return nextStep - }, [ - setupGuide.status, - checkingVerified, - checkingConnected, - connected, - verified, - isFirstConnection, - loadingCurrentUserId, - ]) - const skip = !personaInfo || currentTabId !== setupGuide.tabId - // Will show connect result the first time for sites that don't need to verify nextId. - return { - step: skip ? SetupGuideStep.Close : step, - userId, - currentUserId, - loadingCurrentUserId, - myIdentity, - personaInfo, - isFirstConnection, - setIsFirstConnection, - checkingConnected, - checkingVerified, - verified, - connected, - } -} - -export const SetupGuideContext = createContainer(useSetupGuideStepInfo) -SetupGuideContext.Provider.displayName = 'SetupGuideProvider' diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/VerifyNextID.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/VerifyNextID.tsx deleted file mode 100644 index 59bb10fe326d..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/VerifyNextID.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import { Icons } from '@masknet/icons' -import { delay } from '@masknet/kit' -import { - BindingDialog, - EmojiAvatar, - type BindingDialogProps, - useVerifyContent, - useBaseUIRuntime, - useVerifyNextID, -} from '@masknet/shared' -import { - MaskMessages, - currentSetupGuideStatus, - formatPersonaFingerprint, - resolveNetworkToNextIDPlatform, -} from '@masknet/shared-base' -import { ActionButton, MaskColorVar, MaskTextField, makeStyles } from '@masknet/theme' -import { NextIDProof } from '@masknet/web3-providers' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import { Box, Link, Skeleton, Typography } from '@mui/material' -import { useQuery, useQueryClient } from '@tanstack/react-query' -import { useCallback, useMemo, useState } from 'react' -import { Trans } from 'react-i18next' -import { useAsyncFn } from 'react-use' -import Services from '../../../../shared-ui/service.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { AccountConnectStatus } from './AccountConnectStatus.js' -import { SetupGuideContext } from './SetupGuideContext.js' -import { useConnectPersona } from './hooks/useConnectPersona.js' -import { useNotifyConnected } from './hooks/useNotifyConnected.js' - -const useStyles = makeStyles()((theme) => ({ - body: { - display: 'flex', - flexDirection: 'column', - height: '100%', - overflow: 'auto', - }, - main: { - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - avatar: { - display: 'block', - width: 36, - height: 36, - borderRadius: '50%', - border: `solid 1px ${MaskColorVar.border}`, - '&.connected': { - borderColor: MaskColorVar.success, - }, - }, - button: { - minWidth: 150, - height: 40, - minHeight: 40, - marginLeft: 0, - marginTop: 0, - [theme.breakpoints.down('sm')]: { - width: '100%', - }, - fontSize: 14, - wordBreak: 'keep-all', - '&,&:hover': { - color: `${MaskColorVar.twitterButtonText} !important`, - background: `${MaskColorVar.twitterButton} !important`, - }, - }, - tip: { - fontSize: 12, - fontWeight: 500, - lineHeight: '16px', - color: theme.palette.maskColor.second, - marginTop: theme.spacing(2), - }, - connection: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - padding: theme.spacing(1.5), - gap: theme.spacing(1.5), - color: theme.palette.maskColor.main, - }, - connectItem: { - flex: 1, - width: 148, - flexShrink: 0, - display: 'flex', - alignItems: 'center', - gap: theme.spacing(0.5), - }, - input: { - width: 136, - }, - postContentTitle: { - fontSize: 12, - color: theme.palette.maskColor.main, - fontWeight: 700, - }, - postContent: { - color: theme.palette.maskColor.main, - fontSize: 12, - backgroundColor: theme.palette.maskColor.bg, - borderRadius: 12, - padding: theme.spacing(1), - marginTop: theme.spacing(1.5), - whiteSpace: 'pre-line', - wordBreak: 'break-all', - }, - text: { - fontSize: 12, - fontWeight: 400, - color: theme.palette.maskColor.second, - }, - info: { - overflow: 'auto', - }, - name: { - display: 'block', - fontSize: 14, - fontWeight: 500, - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - second: { - color: theme.palette.maskColor.second, - fontSize: 12, - display: 'block', - alignItems: 'center', - marginTop: theme.spacing(0.5), - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - linkIcon: { - fontSize: 0, - color: theme.palette.maskColor.second, - marginLeft: 2, - }, - send: { - marginRight: theme.spacing(1), - }, - footer: { - borderRadius: 12, - backdropFilter: 'blur(8px)', - boxShadow: theme.palette.maskColor.bottomBg, - padding: theme.spacing(2), - marginTop: 'auto', - }, -})) - -interface VerifyNextIDProps extends BindingDialogProps {} - -export function VerifyNextID({ onClose }: VerifyNextIDProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - const queryClient = useQueryClient() - - const { userId, myIdentity, personaInfo, checkingVerified, verified, loadingCurrentUserId, currentUserId } = - SetupGuideContext.useContainer() - const { nickname: username, avatar } = myIdentity - const personaName = personaInfo?.nickname - const personaIdentifier = personaInfo?.identifier - - const [customUserId, setCustomUserId] = useState('') - const { data: verifyInfo, isPending: creatingPostContent } = useVerifyContent( - personaIdentifier, - userId || customUserId, - ) - const { networkIdentifier } = useBaseUIRuntime() - const nextIdPlatform = resolveNetworkToNextIDPlatform(networkIdentifier) - - const { data: personaAvatar } = useQuery({ - queryKey: ['@@my-own-persona-info'], - queryFn: () => Services.Identity.queryOwnedPersonaInformation(false), - refetchOnMount: true, - networkMode: 'always', - select(data) { - const pubkey = personaInfo?.identifier.publicKeyAsHex - const info = data.find((x) => x.identifier.publicKeyAsHex === pubkey) - return info?.avatar - }, - }) - - const disableVerify = useMemo(() => { - return !myIdentity?.identifier || !userId ? false : myIdentity.identifier.userId !== userId - }, [myIdentity, userId]) - // Show connect result for the first time. - const { loading: connecting } = useConnectPersona() - - const [, handleVerifyNextID] = useVerifyNextID() - const [{ loading: verifying, value: verifiedSuccess }, onVerify] = useAsyncFn(async () => { - if (!userId) return - if (!personaInfo) return - if (!nextIdPlatform) return - - const isBound = await NextIDProof.queryIsBound(personaInfo.identifier.publicKeyAsHex, nextIdPlatform, userId) - if (!isBound) { - await handleVerifyNextID(personaInfo, userId) - Telemetry.captureEvent(EventType.Access, EventID.EntryPopupSocialAccountVerifyTwitter) - } - await queryClient.invalidateQueries({ - queryKey: ['@@next-id', 'bindings-by-persona', personaInfo.identifier.publicKeyAsHex], - }) - - await delay(1000) - - MaskMessages.events.ownProofChanged.sendToAll() - return true - }, [userId, personaInfo, queryClient]) - - const notify = useNotifyConnected() - - const onConfirm = useCallback(() => { - currentSetupGuideStatus[networkIdentifier].value = '' - notify() - }, [nextIdPlatform, notify]) - - // Need to verify for next id platform - if (currentUserId !== userId || loadingCurrentUserId || connecting) { - return ( - - ) - } - - if (!personaIdentifier) return null - - const disabled = !(userId || customUserId) || !personaName || disableVerify || checkingVerified - - return ( - -
- - - {userId ? - - - - - - {username} - @{userId} - - - : - - - { - setCustomUserId(e.target.value.trim()) - }} - /> - - - } - - - {personaAvatar ? - - : } - - {personaName} - - {formatPersonaFingerprint(personaIdentifier.rawPublicKey, 4)} - - - - - - - - {!nextIdPlatform || verified || verifiedSuccess ? - - }} - /> - - : creatingPostContent ? - <> - {t.setup_guide_post_content()} - - - - - - - - - - {t.setup_guide_verify_tip()} - - - : verifyInfo ? - <> - {t.setup_guide_post_content()} - {verifyInfo.post} - - {t.setup_guide_verify_tip()} - - - : null} - - - - {!nextIdPlatform || (nextIdPlatform && (verified || verifiedSuccess)) ? - - {t.ok()} - - : - - {t.send()} - - } - -
-
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/WizardDialog.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/WizardDialog.tsx deleted file mode 100644 index b0ead17ab9d7..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/WizardDialog.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Box, IconButton, Paper, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { SetupGuideStep } from '@masknet/shared-base' -import { Icons } from '@masknet/icons' - -interface ContentUIProps { - dialogType: SetupGuideStep - content?: React.ReactNode - footer?: React.ReactNode - tip?: React.ReactNode - dismiss?: React.ReactNode -} - -const useStyles = makeStyles()((theme) => ({ - content: { - marginBottom: theme.spacing(2), - }, - footer: { - marginLeft: 0, - marginTop: theme.spacing(3), - flex: 1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'column', - }, -})) - -function ContentUI(props: ContentUIProps) { - const { classes } = useStyles() - switch (props.dialogType) { - case SetupGuideStep.PinExtension: - return ( - -
{props.content}
-
{props.tip}
- {props.footer ? -
{props.footer}
- : null} - {props.dismiss ? -
{props.dismiss}
- : null} -
- ) - default: - return null - } -} - -const useWizardDialogStyles = makeStyles()((theme) => ({ - root: { - padding: theme.spacing(3), - position: 'relative', - boxShadow: theme.palette.mode === 'dark' ? 'none' : theme.shadows[4], - border: `${theme.palette.mode === 'dark' ? 'solid' : 'none'} 1px ${theme.palette.divider}`, - borderRadius: 20, - [theme.breakpoints.down('sm')]: { - position: 'fixed', - bottom: 0, - left: 0, - margin: 0, - alignSelf: 'center', - borderRadius: 0, - boxShadow: 'none', - border: `solid 1px ${theme.palette.divider}`, - width: '100%', - }, - userSelect: 'none', - boxSizing: 'border-box', - width: 480, - '&.small': { - width: 384, - }, - overflow: 'hidden', - }, - close: { - color: theme.palette.text.primary, - position: 'absolute', - right: 10, - top: 10, - cursor: 'pointer', - }, - header: { - height: 40, - }, - content: {}, - footer: {}, -})) - -interface WizardDialogProps { - small?: boolean - title?: string - dialogType: SetupGuideStep - optional?: boolean - content?: React.ReactNode - tip?: React.ReactNode - footer?: React.ReactNode - dismiss?: React.ReactNode - onClose?: () => void -} - -export function WizardDialog(props: WizardDialogProps) { - const { small, title, dialogType, content, tip, footer, dismiss, onClose } = props - const { classes, cx } = useWizardDialogStyles() - - return ( - -
- - {title} - -
- - {onClose ? - - - - : null} -
- ) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectPersona.ts b/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectPersona.ts deleted file mode 100644 index 230206a92d17..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectPersona.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MaskMessages, ProfileIdentifier } from '@masknet/shared-base' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType } from '@masknet/web3-telemetry/types' -import { useAsync } from 'react-use' -import { useQueryClient } from '@tanstack/react-query' -import Services from '../../../../../shared-ui/service.js' -import { EventMap } from '../../../../../shared/definitions/event.js' -import { activatedSiteAdaptorUI } from '../../../../site-adaptor-infra/ui.js' -import { SetupGuideContext } from '../SetupGuideContext.js' - -export function useConnectPersona() { - const { userId, myIdentity, personaInfo, setIsFirstConnection, connected } = SetupGuideContext.useContainer() - const site = activatedSiteAdaptorUI!.networkIdentifier - const persona = personaInfo?.identifier - const queryClient = useQueryClient() - return useAsync(async () => { - if (!persona || !userId || connected) return - const id = ProfileIdentifier.of(site, userId) - if (!id.isSome()) return - // attach persona with site profile - await Services.Identity.attachProfile(id.value, persona, { - connectionConfirmState: 'confirmed', - }) - - setIsFirstConnection(true) - if (myIdentity.avatar) { - await Services.Identity.updateProfileInfo(id.value, { - avatarURL: myIdentity.avatar, - }) - } - // auto-finish the setup process - if (!personaInfo) throw new Error('invalid persona') - await Services.Identity.setupPersona(personaInfo.identifier) - queryClient.removeQueries({ queryKey: ['query-persona-info', persona.publicKeyAsHex] }) - MaskMessages.events.ownPersonaChanged.sendToAll() - - Telemetry.captureEvent(EventType.Access, EventMap[activatedSiteAdaptorUI!.networkIdentifier]) - }, [site, persona, userId, myIdentity.avatar, connected, queryClient]) -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectedVerified.ts b/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectedVerified.ts deleted file mode 100644 index 1dd177af478e..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useConnectedVerified.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { usePersonaProofs } from '@masknet/shared' -import { activatedSiteAdaptorUI } from '../../../../site-adaptor-infra/ui.js' - -export function useConnectedVerified(pubkey: string | undefined, userId: string) { - const { data: proofs, isLoading } = usePersonaProofs(pubkey) - const platform = activatedSiteAdaptorUI!.configuration.nextIDConfig?.platform - const checking = isLoading - if (!platform || !proofs?.length) return [checking, false] - const verified = proofs.some((x) => x.platform === platform && x.identity === userId.toLowerCase() && x.is_valid) - return [checking, verified] as const -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useCurrentUserId.ts b/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useCurrentUserId.ts deleted file mode 100644 index ca62f7eb60a3..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useCurrentUserId.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useTimeout } from 'react-use' -import { useLastRecognizedIdentity } from '../../../DataSource/useActivatedUI.js' - -export function useCurrentUserId() { - const lastRecognized = useLastRecognizedIdentity() - const currentUserId = lastRecognized.identifier?.userId - // There is not state for getting current userId, setting a timeout for that. - const [timeout] = useTimeout(5000) - const [delay] = useTimeout(800) - const fakeLoading = !delay() // Getting userId is instantly fast, add a fake loading - const loading = timeout() ? false : fakeLoading || !currentUserId - return [loading, fakeLoading ? undefined : currentUserId] as const -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useNotifyConnected.ts b/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useNotifyConnected.ts deleted file mode 100644 index 062aee2b17d1..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/hooks/useNotifyConnected.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useCallback } from 'react' -import { useCustomSnackbar } from '@masknet/theme' -import { useMaskSharedTrans } from '../../../../../shared-ui/index.js' -import { activatedSiteAdaptorUI } from '../../../../site-adaptor-infra/ui.js' - -export function useNotifyConnected() { - const t = useMaskSharedTrans() - const { showSnackbar } = useCustomSnackbar() - const { configuration } = activatedSiteAdaptorUI! - const platform = configuration.nextIDConfig?.platform - const notify = useCallback(() => { - if (!platform) return - showSnackbar(t.setup_guide_connected_title(), { - variant: 'success', - message: t.setup_guide_connected_description(), - }) - }, [t, showSnackbar]) - return notify -} diff --git a/packages/mask/content-script/components/InjectedComponents/SetupGuide/index.tsx b/packages/mask/content-script/components/InjectedComponents/SetupGuide/index.tsx deleted file mode 100644 index 43742df2e038..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/SetupGuide/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - EncryptionTargetType, - EnhanceableSite, - SetupGuideStep, - currentSetupGuideStatus, - userGuideFinished, - userGuideStatus, - userPinExtension, - type PersonaIdentifier, -} from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { makeTypedMessageText } from '@masknet/typed-message' -import { memo, useCallback } from 'react' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { PinExtension } from './PinExtension.js' -import { SetupGuideContext } from './SetupGuideContext.js' -import { VerifyNextID } from './VerifyNextID.js' -import { CheckConnection } from './CheckConnection.js' - -// #region setup guide ui - -function SetupGuideUI() { - const t = useMaskSharedTrans() - - const { step } = SetupGuideContext.useContainer() - const { networkIdentifier } = activatedSiteAdaptorUI! - - const onClose = useCallback(() => { - currentSetupGuideStatus[networkIdentifier].value = '' - userPinExtension.value = true - }, []) - - const onCreate = useCallback(() => { - let content = t.setup_guide_say_hello_content() - if (networkIdentifier === EnhanceableSite.Twitter) { - content += t.setup_guide_say_hello_follow({ account: '@realMaskNetwork' }) - } - - activatedSiteAdaptorUI!.automation.maskCompositionDialog?.open?.(makeTypedMessageText(content), { - target: EncryptionTargetType.Public, - }) - }, [t]) - - const onPinClose = useCallback(() => { - userPinExtension.value = true - onClose() - }, []) - - const onPinDone = useCallback(() => { - const network = networkIdentifier - if (!userPinExtension.value) { - userPinExtension.value = true - } - if (network === EnhanceableSite.Twitter && !userGuideFinished[network].value) { - userGuideStatus[network].value = '1' - } else { - onCreate() - } - }, [onCreate]) - - switch (step) { - case SetupGuideStep.CheckConnection: - return - case SetupGuideStep.VerifyOnNextID: - return - case SetupGuideStep.PinExtension: - return - default: - return null - } -} -// #endregion - -// #region setup guide -const useSetupGuideStyles = makeStyles()({ - root: { - position: 'fixed', - zIndex: 9999, - maxWidth: 550, - top: '2em', - right: '2em', - }, -}) - -interface SetupGuideProps { - persona: PersonaIdentifier -} - -export const SetupGuide = memo(function SetupGuide({ persona }: SetupGuideProps) { - const { classes } = useSetupGuideStyles() - - return ( -
- - - -
- ) -}) -// #endregion diff --git a/packages/mask/content-script/components/InjectedComponents/ToolboxUnstyled.tsx b/packages/mask/content-script/components/InjectedComponents/ToolboxUnstyled.tsx deleted file mode 100644 index 8d6b49b5c415..000000000000 --- a/packages/mask/content-script/components/InjectedComponents/ToolboxUnstyled.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { useCallback } from 'react' -import { - CircularProgress, - type ListItemButtonProps, - type ListItemIconProps, - type ListItemTextProps, - type TypographyProps, - Typography as MuiTypography, - ListItemButton as MuiListItemButton, - ListItemIcon as MuiListItemIcon, - ListItemText as MuiListItemText, - Box, - useTheme, -} from '@mui/material' -import { FiberManualRecord as FiberManualRecordIcon } from '@mui/icons-material' -import { ProviderType } from '@masknet/web3-shared-evm' -import { TransactionStatusType } from '@masknet/web3-shared-base' -import { - useProviderDescriptor, - useChainContext, - useChainColor, - useChainIdValid, - useWeb3Utils, - useReverseAddress, - useChainIdMainnet, - useRecentTransactions, -} from '@masknet/web3-hooks-base' -import { WalletIcon, SelectProviderModal, WalletStatusModal } from '@masknet/shared' -import { Icons } from '@masknet/icons' -import { makeStyles } from '@masknet/theme' -import { useMaskSharedTrans } from '../../../shared-ui/index.js' -import GuideStep from '../GuideStep/index.js' -import { useOpenApplicationBoardDialog } from '../shared/openApplicationBoardDialog.js' - -const useStyles = makeStyles()(() => ({ - title: { - display: 'flex', - alignItems: 'center', - }, - chainIcon: { - fontSize: 18, - width: 18, - height: 18, - }, -})) - -interface ToolboxHintProps { - Container?: React.ComponentType> - ListItemButton?: React.ComponentType> - ListItemText?: React.ComponentType> - ListItemIcon?: React.ComponentType> - Typography?: React.ComponentType> - iconSize?: number - badgeSize?: number - mini?: boolean - category: 'wallet' | 'application' -} - -export function ToolboxHintUnstyled(props: ToolboxHintProps) { - return props.category === 'wallet' ? : -} - -function ToolboxHintForApplication(props: ToolboxHintProps) { - const { - ListItemButton = MuiListItemButton, - ListItemIcon = MuiListItemIcon, - Container = 'div', - Typography = MuiTypography, - iconSize = 24, - mini, - ListItemText = MuiListItemText, - } = props - const { classes } = useStyles() - const t = useMaskSharedTrans() - - const openApplicationBoardDialog = useOpenApplicationBoardDialog() - - return ( - - - - - - - {mini ? null : ( - - {t.mask_network()} - - } - /> - )} - - - - ) -} - -function ToolboxHintForWallet(props: ToolboxHintProps) { - const t = useMaskSharedTrans() - const { - ListItemButton = MuiListItemButton, - ListItemText = MuiListItemText, - ListItemIcon = MuiListItemIcon, - Container = 'div', - Typography = MuiTypography, - iconSize = 24, - badgeSize = 12, - mini, - } = props - const { classes } = useStyles() - const { onClickToolbox, title, chainColor, shouldDisplayChainIndicator, account, provider } = useToolbox() - - const theme = useTheme() - - return ( - - - - - {account && provider && provider.type !== ProviderType.MaskWallet ? - - : } - - {mini ? null : ( - - {title} - {shouldDisplayChainIndicator ? - - : null} - - } - /> - )} - - - - ) -} - -function useToolbox() { - const t = useMaskSharedTrans() - const { account } = useChainContext() - const chainColor = useChainColor() - const chainIdValid = useChainIdValid() - const chainIdMainnet = useChainIdMainnet() - const provider = useProviderDescriptor() - const Utils = useWeb3Utils() - const pendingTransactions = useRecentTransactions(undefined, TransactionStatusType.NOT_DEPEND) - const { data: domain } = useReverseAddress(undefined, account, true) - - function getToolboxTitle() { - if (!account || !provider) return t.plugin_wallet_connect_wallet() - if (pendingTransactions.length <= 0) - return Utils.formatDomainName?.(domain) || Utils.formatAddress(account, 4) || account - return ( - <> - - {t.plugin_wallet_pending_transactions({ - count: pendingTransactions.length, - })} - - - - ) - } - - const onClickToolbox = useCallback(() => { - return account && provider ? WalletStatusModal.open() : SelectProviderModal.open() - }, [account, provider]) - - return { - account, - chainColor, - provider, - onClickToolbox, - title: getToolboxTitle(), - shouldDisplayChainIndicator: account && chainIdValid && !chainIdMainnet, - } -} diff --git a/packages/mask/content-script/components/Welcomes/Banner.tsx b/packages/mask/content-script/components/Welcomes/Banner.tsx deleted file mode 100644 index 3bfd9f429160..000000000000 --- a/packages/mask/content-script/components/Welcomes/Banner.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useCallback, useState } from 'react' -import { useMount } from 'react-use' -import { IconButton } from '@mui/material' -import { Icons } from '@masknet/icons' -import { useCurrentPersonaConnectStatus } from '@masknet/shared' -import { DashboardRoutes, currentPersonaIdentifier } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { MaskColors, makeStyles } from '@masknet/theme' -import Services from '#services' -import { activatedSiteAdaptorUI, activatedSiteAdaptor_state } from '../../site-adaptor-infra/index.js' -import { useLastRecognizedIdentity } from '../DataSource/useActivatedUI.js' -import { usePersonasFromDB } from '../../../shared-ui/hooks/usePersonasFromDB.js' - -interface BannerUIProps extends withClasses<'header' | 'content' | 'actions' | 'buttonText'> { - description?: string - nextStep: - | 'hidden' - | { - onClick(): void - } - username?: - | 'hidden' - | { - isValid(username: string): boolean - value: string - defaultValue: string - onChange(nextValue: string): void - } - iconType?: string -} - -const ICON_MAP: Record = { - minds: , - default: , -} -const useStyles = makeStyles()({ - buttonText: { - width: 38, - height: 38, - margin: '10px 0', - }, -}) - -function BannerUI(props: BannerUIProps) { - const { classes } = useStyles(undefined, { props }) - - return props.nextStep === 'hidden' ? - null - : - {ICON_MAP[props.iconType ?? 'default']} - -} - -interface BannerProps extends Partial {} - -export function Banner(props: BannerProps) { - const lastRecognizedIdentity = useLastRecognizedIdentity() - const allPersonas = usePersonasFromDB() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - const { value: personaConnectStatus } = useCurrentPersonaConnectStatus( - allPersonas, - currentIdentifier, - Services.Helper.openDashboard, - lastRecognizedIdentity, - ) - const { nextStep } = props - const networkIdentifier = activatedSiteAdaptorUI!.networkIdentifier - const identities = useValueRef(activatedSiteAdaptor_state!.profiles) - const [value, onChange] = useState('') - const defaultNextStep = useCallback(() => { - if (nextStep === 'hidden') return - if (!networkIdentifier) { - nextStep?.onClick() - nextStep ?? console.warn('You must provide one of networkIdentifier or nextStep.onClick') - return - } - - Services.Helper.openDashboard( - personaConnectStatus.hasPersona ? DashboardRoutes.Personas : DashboardRoutes.SignUpPersona, - ) - }, [networkIdentifier, nextStep]) - const defaultUserName = - networkIdentifier ? - { - defaultValue: lastRecognizedIdentity.identifier?.userId ?? '', - value, - onChange, - isValid: activatedSiteAdaptorUI!.utils.isValidUsername || (() => true), - } - : ('hidden' as const) - - const [mounted, setMounted] = useState(false) - useMount(() => setMounted(true)) - - return identities.length === 0 && mounted ? - - : null -} diff --git a/packages/mask/content-script/components/shared/DraggableDiv.tsx b/packages/mask/content-script/components/shared/DraggableDiv.tsx deleted file mode 100644 index 9b908ced1595..000000000000 --- a/packages/mask/content-script/components/shared/DraggableDiv.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useRef } from 'react' -import Draggable, { type DraggableProps } from 'react-draggable' -import { makeStyles } from '@masknet/theme' -const useStyle = makeStyles()((theme) => ({ - root: { - position: 'fixed', - width: '100vw', - height: '100vh', - top: 0, - left: 0, - zIndex: 9999, - pointerEvents: 'none', - }, - paper: { - [theme.breakpoints.up('sm')]: { - top: '2em', - right: '2em', - }, - [theme.breakpoints.down('sm')]: { - bottom: '2em', - }, - maxWidth: 550, - position: 'fixed', - pointerEvents: 'initial', - }, -})) - -export function DraggableDiv({ - DraggableProps, - ...props -}: React.HTMLAttributes & { DraggableProps?: Partial }) { - const { classes } = useStyle() - const ref = useRef(null) - return ( -
- } - /> -
- ) -} diff --git a/packages/mask/content-script/components/shared/SelectProfileUI/SelectProfileUI.tsx b/packages/mask/content-script/components/shared/SelectProfileUI/SelectProfileUI.tsx deleted file mode 100644 index 2359597c5e37..000000000000 --- a/packages/mask/content-script/components/shared/SelectProfileUI/SelectProfileUI.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { Icons } from '@masknet/icons' -import { EmptyStatus, LoadingStatus } from '@masknet/shared' -import { EMPTY_LIST, type ProfileInformation as Profile, type ProfileInformationFromNextID } from '@masknet/shared-base' -import { Boundary, makeStyles } from '@masknet/theme' -import { useLookupAddress } from '@masknet/web3-hooks-base' -import Fuse from 'fuse.js' -import { Box, Checkbox, InputAdornment, InputBase, Stack, Typography } from '@mui/material' -import { compact, uniqBy } from 'lodash-es' -import { startTransition, useCallback, useDeferredValue, useMemo, useState } from 'react' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { ProfileInList } from '../SelectRecipients/ProfileInList.js' -import { useContacts } from '../SelectRecipients/useContacts.js' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/ui.js' - -interface SelectProfileUIProps extends withClasses<'root'> { - items: Profile[] - selected: Profile[] - frozenSelected: Profile[] - onSetSelected(selected: Profile[]): void - disabled?: boolean - onSearch(v: string): void - loading: boolean -} -const useStyles = makeStyles()((theme) => ({ - selectedArea: { - flexDirection: 'row', - flexWrap: 'wrap', - display: 'flex', - padding: 0, - }, - input: { - flex: 1, - marginBottom: theme.spacing(2), - }, - empty: { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%,-50%)', - display: 'flex', - alignItems: 'center', - flexDirection: 'column', - gap: 12, - color: theme.palette.text.secondary, - whiteSpace: 'nowrap', - }, - listParent: { - height: 400, - display: 'flex', - flexDirection: 'column', - }, - listBody: { - height: 400, - '::-webkit-scrollbar': { - display: 'none', - }, - overflowY: 'auto', - flex: 1, - }, - list: { - gridGap: '12px', - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - alignItems: 'flex-start', - }, - mainText: { - color: theme.palette.text.primary, - }, -})) - -export function SelectProfileUI(props: SelectProfileUIProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles(undefined, { props }) - const { frozenSelected, onSetSelected, disabled, items, selected } = props - const [search, setSearch] = useState('') - const { value: registeredAddress = '' } = useLookupAddress(undefined, useDeferredValue(search)) - const keyword = registeredAddress || search - const selectedPubkeyList = compact(selected.map((x) => x.linkedPersona?.publicKeyAsHex)) - const frozenPubkeyList = useMemo( - () => compact(frozenSelected.map((x) => x.linkedPersona?.publicKeyAsHex)), - [frozenSelected], - ) - const { value = EMPTY_LIST } = useContacts(activatedSiteAdaptorUI!.networkIdentifier) - - const onSelectedAllChange = useCallback( - (checked: boolean) => { - if (checked) { - onSetSelected([...items]) - } else { - onSetSelected([]) - } - }, - [items], - ) - - const onSelectedProfile = useCallback( - (item: Profile, checked: boolean) => { - if (checked) { - onSetSelected([...selected, item]) - } else - onSetSelected( - selected.filter((x) => x.linkedPersona?.publicKeyAsHex !== item.linkedPersona?.publicKeyAsHex), - ) - }, - [selected], - ) - - const fuse = useMemo(() => { - return new Fuse(items, { - keys: [ - 'identifier.userId', - 'nickname', - 'walletAddress', - 'linkedPersona.rawPublicKey', - 'linkedPersona.publicKeyAsHex', - 'linkedTwitterNames', - ], - isCaseSensitive: false, - ignoreLocation: true, - threshold: 0, - }) - }, [items]) - - const results = useMemo(() => { - if (!keyword) return items - return fuse - .search(keyword) - .map((item) => item.item) - .filter((x) => !frozenPubkeyList.includes(x.linkedPersona?.publicKeyAsHex!)) - }, [keyword, frozenPubkeyList, fuse, items]) - - const profiles = uniqBy([...frozenSelected, ...results, ...value], (x) => x.identifier) - - return ( -
- - ) => setSearch(e.target.value), - [], - )} - onKeyDown={(e) => { - if (e.code !== 'Enter') return - startTransition(() => props.onSearch(keyword)) - }} - startAdornment={ - - - - } - placeholder={t.post_dialog_share_with_input_placeholder()} - disabled={disabled} - /> - - {props.loading ? -
- -
- : -
-
- - {profiles.length === 0 ? - - {t.compose_encrypt_share_dialog_empty()} - - : profiles.map((item) => { - const pubkey = item.linkedPersona?.publicKeyAsHex as string - const selected = selectedPubkeyList.includes(pubkey) - const disabled = frozenPubkeyList.includes(pubkey) - return ( - - ) - }) - } - -
- {profiles.length ? - - onSelectedAllChange(e.currentTarget.checked)} - /> - {t.select_all()} - - : null} -
-
- } -
- ) -} diff --git a/packages/mask/content-script/components/shared/SelectProfileUI/index.tsx b/packages/mask/content-script/components/shared/SelectProfileUI/index.tsx deleted file mode 100644 index 797b209208b4..000000000000 --- a/packages/mask/content-script/components/shared/SelectProfileUI/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './SelectProfileUI.js' diff --git a/packages/mask/content-script/components/shared/SelectRecipients/ProfileInList.tsx b/packages/mask/content-script/components/shared/SelectRecipients/ProfileInList.tsx deleted file mode 100644 index dcb33da5c3a9..000000000000 --- a/packages/mask/content-script/components/shared/SelectRecipients/ProfileInList.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { Icons } from '@masknet/icons' -import { CopyButton } from '@masknet/shared' -import { EMPTY_LIST, formatPersonaFingerprint, type ProfileInformationFromNextID } from '@masknet/shared-base' -import { makeStyles, ShadowRootTooltip } from '@masknet/theme' -import { Checkbox, ListItem, ListItemAvatar, ListItemText } from '@mui/material' -import { truncate } from 'lodash-es' -import { memo, useCallback, useMemo } from 'react' -import Highlighter from 'react-highlight-words' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { Avatar } from '../../../../shared-ui/components/Avatar.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - borderRadius: 8, - cursor: 'pointer', - padding: 0, - }, - overflow: { - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - highlighted: { - backgroundColor: 'inherit', - color: 'inherit', - fontWeight: 'bold', - }, - flex: { - display: 'flex', - alignItems: 'center', - }, - actionIcon: { - cursor: 'pointer', - marginLeft: theme.spacing(0.5), - }, - badge: { - width: 32, - height: 18, - marginLeft: theme.spacing(0.5), - }, - highLightBg: { - background: theme.palette.maskColor.bg, - }, - avatarBox: { - padding: '6px 0px 6px 8px', - minWidth: 46, - }, - avatar: { - width: 36, - height: 36, - }, - highLightBase: { - lineHeight: '20px', - fontSize: 14, - }, - highLightSecond: { - fontSize: 16, - lineHeight: '20px', - }, - listItemRoot: { - margin: '4px 0', - }, - columnReverse: { - margin: '4px 0', - display: 'flex', - flexDirection: 'column-reverse', - }, - toolTip: { - fontSize: 14, - lineHeight: '18px', - padding: 10, - boxSizing: 'border-box', - borderRadius: 4, - whiteSpace: 'normal', - marginTop: 0, - }, -})) - -interface ProfileInListProps { - profile: ProfileInformationFromNextID - highlightText?: string - selected?: boolean - disabled?: boolean - onChange: (profile: ProfileInformationFromNextID, checked: boolean) => void -} - -export const ProfileInList = memo(function ProfileInList(props: ProfileInListProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - const { profile, selected, disabled, highlightText, onChange } = props - const searchWords = useMemo(() => (highlightText ? [highlightText] : EMPTY_LIST), [highlightText]) - - const rawPublicKey = profile.linkedPersona?.rawPublicKey - const primaryText = (() => { - if (!profile.fromNextID) return `@${profile.identifier.userId || profile.nickname}` - const mentions = profile.linkedTwitterNames?.map((x) => '@' + x).join(' ') ?? '' - if (mentions.length < 15) return mentions - const len = profile.linkedTwitterNames?.length ?? 0 - return truncate(mentions, { length: 15 }) + (len > 1 ? `(${len})` : '') - })() - - const tooltipTitle = (() => { - const linkedNames = profile.linkedTwitterNames ?? [] - if (linkedNames.length < 2) - return `${t.select_friends_dialog_persona_connect({ count: 1 })} @${profile.identifier.userId}.` - const mentions = profile.linkedTwitterNames?.map((username) => '@' + username) ?? [] - return `${t.select_friends_dialog_persona_connect({ count: linkedNames.length })} ${mentions.join(', ')}.` - })() - - const handleClick = useCallback(() => onChange(profile, !selected), [onChange, selected]) - const secondaryText = formatPersonaFingerprint(profile.linkedPersona?.rawPublicKey?.toUpperCase() ?? '', 3) - return ( - - }> - - - - -
- -
- - } - secondaryTypographyProps={{ component: 'div' }} - secondary={ -
- - {rawPublicKey ? - - : null} - {profile.fromNextID ? - - : null} -
- } - /> -
- ) -}) diff --git a/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipients.tsx b/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipients.tsx deleted file mode 100644 index 01fd72f0d10e..000000000000 --- a/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipients.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { uniqBy } from 'lodash-es' -import { useEffect, useMemo, useState } from 'react' -import { EMPTY_LIST, NextIDPlatform, type ProfileInformation as Profile } from '@masknet/shared-base' -import type { LazyRecipients } from '../../CompositionDialog/CompositionUI.js' -import { useCurrentIdentity } from '../../DataSource/useActivatedUI.js' -import { SelectRecipientsDialogUI } from './SelectRecipientsDialog.js' -import { useTwitterIdByWalletSearch } from './useTwitterIdByWalletSearch.js' -import { resolveNextIDPlatform, resolveValueToSearch, usePersonasFromNextID } from '@masknet/shared' -import { useContacts } from './useContacts.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' - -interface SelectRecipientsUIProps { - items: LazyRecipients - selected: Profile[] - disabled?: boolean - hideSelectAll?: boolean - hideSelectNone?: boolean - open: boolean - onClose(): void - onSetSelected(selected: Profile[]): void -} - -export function SelectRecipientsUI(props: SelectRecipientsUIProps) { - const { items, selected, onSetSelected, open, onClose } = props - const t = useMaskSharedTrans() - const [valueToSearch, setValueToSearch] = useState('') - const currentIdentity = useCurrentIdentity() - const type = resolveNextIDPlatform(valueToSearch) - const _value = resolveValueToSearch(valueToSearch) - const { isLoading: searchLoading, data: NextIDResults } = usePersonasFromNextID( - _value, - type ?? NextIDPlatform.NextID, - false, - ) - - const NextIDItems = useTwitterIdByWalletSearch(NextIDResults, _value, type) - const myUserId = currentIdentity?.identifier.userId - const searchedList = useMemo(() => { - if (!items.recipients) return EMPTY_LIST - const profileItems = items.recipients.filter((x) => x.identifier.userId !== myUserId) - // Selected might contain profiles that fetched asynchronously from - // Next.ID, which are not stored locally - return uniqBy(profileItems.concat(NextIDItems, selected), ({ linkedPersona }) => linkedPersona?.rawPublicKey) - }, [NextIDItems, selected, items.recipients, myUserId]) - - const { value = EMPTY_LIST } = useContacts(currentIdentity?.identifier.network!) - - useEffect(() => { - if (!open) return - items.request() - }, [open, items.request]) - return ( - x.linkedPersona?.publicKeyAsHex)} - selected={selected} - disabled={false} - submitDisabled={false} - onSubmit={onClose} - onClose={onClose} - onSetSelected={onSetSelected} - /> - ) -} diff --git a/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipientsDialog.tsx b/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipientsDialog.tsx deleted file mode 100644 index b99642e2bf8b..000000000000 --- a/packages/mask/content-script/components/shared/SelectRecipients/SelectRecipientsDialog.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { startTransition, useCallback, useDeferredValue, useMemo, useState } from 'react' -import { compact } from 'lodash-es' -import { Icons } from '@masknet/icons' -import { ActionButtonPromise, EmptyStatus, InjectedDialog } from '@masknet/shared' -import type { ProfileInformation as Profile, ProfileInformationFromNextID } from '@masknet/shared-base' -import { Boundary, LoadingBase, makeStyles } from '@masknet/theme' -import { useLookupAddress } from '@masknet/web3-hooks-base' -import Fuse from 'fuse.js' -import { - Button, - Checkbox, - DialogActions, - DialogContent, - InputAdornment, - InputBase, - Stack, - Typography, - alpha, -} from '@mui/material' -import { attachNextIDToProfile } from '../../../../shared/index.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { ProfileInList } from './ProfileInList.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - minHeight: 400, - minWidth: 400, - overflow: 'hidden', - }, - inputRoot: { - padding: '4px 10px', - borderRadius: 8, - width: '100%', - background: theme.palette.maskColor.input, - fontSize: 14, - marginBottom: 16, - }, - inputFocused: { - background: theme.palette.maskColor.bottom, - borderColor: theme.palette.text.third, - }, - paper: { - height: 450, - position: 'relative', - padding: theme.spacing(2), - '::-webkit-scrollbar': { - display: 'none', - }, - overflow: 'hidden', - }, - empty: { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%,-50%)', - display: 'flex', - alignItems: 'center', - flexDirection: 'column', - gap: 12, - color: theme.palette.text.secondary, - whiteSpace: 'nowrap', - }, - mainText: { - color: theme.palette.text.primary, - }, - listParent: { - height: 400, - display: 'flex', - flexDirection: 'column', - }, - listBody: { - height: 400, - '::-webkit-scrollbar': { - display: 'none', - }, - overflowY: 'auto', - flex: 1, - backgroundColor: theme.palette.maskColor.bottom, - }, - list: { - gridGap: '12px', - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - alignItems: 'flex-start', - }, - actions: { - display: 'flex', - gap: 16, - padding: 16, - boxSizing: 'border-box', - alignItems: 'center', - background: alpha(theme.palette.maskColor.bottom, 0.8), - boxShadow: - theme.palette.mode === 'light' ? - ' 0px 0px 20px rgba(0, 0, 0, 0.05)' - : '0px 0px 20px rgba(255, 255, 255, 0.12);', - borderRadius: '0px 0px 12px 12px', - flex: 1, - backdropFilter: 'blur(8px)', - }, - back: { - color: theme.palette.maskColor.main, - background: theme.palette.maskColor.thirdMain, - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - '&:hover': { - color: theme.palette.maskColor.main, - background: theme.palette.maskColor.thirdMain, - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - }, - }, - done: { - color: theme.palette.maskColor.bottom, - background: theme.palette.maskColor.main, - fontSize: 14, - fontWeight: 700, - lineHeight: '18px', - }, -})) - -interface SelectRecipientsDialogUIProps { - open: boolean - items: Profile[] - selected: Profile[] - disabled: boolean - submitDisabled: boolean - loading?: boolean - searchEmptyText?: string - onSubmit: () => void - onClose: () => void - onSearch(v: string): void - onSetSelected(selected: Profile[]): void -} -export function SelectRecipientsDialogUI(props: SelectRecipientsDialogUIProps) { - const t = useMaskSharedTrans() - const { classes, cx } = useStyles() - const { items, onSearch } = props - const [searchInput, setSearchInput] = useState('') - const { value: registeredAddress = '' } = useLookupAddress(undefined, useDeferredValue(searchInput)) - const [selectedAllProfiles, setSelectedAllProfiles] = useState([]) - const keyword = registeredAddress || searchInput - - const results = useMemo(() => { - if (!keyword) return items - - return new Fuse(items, { - keys: [ - 'identifier.userId', - 'nickname', - 'walletAddress', - 'linkedPersona.rawPublicKey', - 'linkedPersona.publicKeyAsHex', - 'linkedTwitterNames', - ], - isCaseSensitive: false, - ignoreLocation: true, - threshold: 0, - }) - .search(keyword) - .map((item) => item.item) - }, [keyword, items]) - - const handleClose = () => { - props.onClose() - setSearchInput('') - onSearch('') - } - - const handleSubmit = useCallback(async () => { - props.onSetSelected([...selectedAllProfiles]) - for (const item of selectedAllProfiles) { - await attachNextIDToProfile(item as ProfileInformationFromNextID) - } - props.onSubmit() - setSearchInput('') - onSearch('') - }, [selectedAllProfiles]) - - const onSelectedProfile = useCallback((item: Profile, checked: boolean) => { - if (checked) { - setSelectedAllProfiles((profiles) => [...profiles, item]) - } else setSelectedAllProfiles((profiles) => profiles.filter((x) => x !== item)) - }, []) - - const selectedPubkeyList = compact(selectedAllProfiles.map((x) => x.linkedPersona?.publicKeyAsHex)) - - const onSelectedAllChange = useCallback( - (checked: boolean) => { - if (checked) { - setSelectedAllProfiles([...results]) - } else { - setSelectedAllProfiles([]) - } - }, - [results], - ) - - return ( - - - { - if (e.code !== 'Enter') return - startTransition(() => onSearch(keyword)) - }} - onChange={(e) => setSearchInput(e.target.value.trim())} - onBlur={() => onSearch(keyword)} - startAdornment={ - - - - } - placeholder={t.post_dialog_share_with_input_placeholder()} - /> - {props.loading ? -
- - {t.loading()} -
- : -
-
-
- {results.length === 0 ? - - {props.searchEmptyText ?? t.compose_encrypt_share_dialog_empty()} - - : results.map((item, index) => { - const pubkey = item.linkedPersona?.publicKeyAsHex as string - const selected = selectedPubkeyList.includes(pubkey) - return ( - - ) - }) - } -
-
- {results.length > 0 ? - - onSelectedAllChange(e.currentTarget.checked)} - /> - {t.select_all()} - - : null} -
-
- } -
- -
- - -
-
-
- ) -} diff --git a/packages/mask/content-script/components/shared/SelectRecipients/useContacts.ts b/packages/mask/content-script/components/shared/SelectRecipients/useContacts.ts deleted file mode 100644 index e97ce9cd1447..000000000000 --- a/packages/mask/content-script/components/shared/SelectRecipients/useContacts.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { EMPTY_LIST, type ProfileInformation } from '@masknet/shared-base' -import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry.js' -import { useCurrentPersona } from '../../../../shared-ui/hooks/index.js' -import Services from '#services' -import { isProfileIdentifier } from '@masknet/shared' - -export function useContacts(network: string): AsyncStateRetry { - const currentPersona = useCurrentPersona() - - return useAsyncRetry(async () => { - const values = await Services.Identity.queryRelationPaged( - currentPersona?.identifier, - { - network, - pageOffset: 0, - }, - 1000, - ) - if (values.length === 0) return EMPTY_LIST - - const identifiers = values.map((x) => x.profile).filter(isProfileIdentifier) - return (await Services.Identity.queryProfilesInformation(identifiers)).filter( - (x) => x.linkedPersona && x.linkedPersona !== currentPersona?.identifier, - ) - }, [network, currentPersona]) -} diff --git a/packages/mask/content-script/components/shared/SelectRecipients/useTwitterIdByWalletSearch.tsx b/packages/mask/content-script/components/shared/SelectRecipients/useTwitterIdByWalletSearch.tsx deleted file mode 100644 index c9d557ba886f..000000000000 --- a/packages/mask/content-script/components/shared/SelectRecipients/useTwitterIdByWalletSearch.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - ECKeyIdentifier, - EMPTY_LIST, - type NextIDPersonaBindings, - NextIDPlatform, - ProfileIdentifier, -} from '@masknet/shared-base' -import { compact, uniqBy } from 'lodash-es' - -export function useTwitterIdByWalletSearch( - bindings: NextIDPersonaBindings[] | undefined, - value: string, - type?: NextIDPlatform, -) { - if (!bindings?.length || !type) return EMPTY_LIST - - const nextIdAccounts = bindings.map((binding) => { - const proofs = uniqBy( - binding.proofs.filter((x) => x.platform === NextIDPlatform.Twitter), - (proof) => proof.identity, - ) - if (!proofs.length) return null - const linkedTwitterNames = proofs.map((x) => x.identity) - return { - nickname: proofs[0].identity, - identifier: ProfileIdentifier.of('twitter.com', proofs[0].identity).expect( - `${proofs[0].identity} should be a valid user id`, - ), - walletAddress: type === NextIDPlatform.Ethereum ? value : undefined, - fromNextID: true, - linkedTwitterNames, - linkedPersona: ECKeyIdentifier.fromHexPublicKeyK256(binding.persona).expect( - `${binding.persona} should be a valid hex public key in k256`, - ), - } - }) - return compact(nextIdAccounts) -} diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/dividerActive.png b/packages/mask/content-script/components/shared/assets/stepAssets/dividerActive.png deleted file mode 100644 index 4b797dfa5eb0b0fb616a4c65f25c881fa377a162..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmV-c0jd6pP)o0PR2nv7nJ;NCTK?t|3~0 zlMGTU)Q~u&i7?!Z#Dv5NEeT6%f=uo^&?Gog7hr{15=ZI(nA-&ghJZ;L-C@9Ok7P*` zNbROUYpOcCSeN~Rm&K4Sgo&=O+|r8n4YU<4mE3S)sb-dV!}}a?DHDii(}%*R>I#c8 z3NchmP|Wl~yQE?F>KTaR>H^GUuKT1pT+G>$&p-Q_7^hDc?#}Jv9*{n=eCkZ^JARR- z2fJtL@e%X*=)DV@Cm6Q*1)kT;C!cuf{$}wPpFO8l{r#Fhleg2a{`vqv>-RkB5&t}Y o`seSSd}m;Ot^N9c_Ur6_0R9Hwmg(sBw*UYD07*qoM6N<$g3l$P!~g&Q diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/dividerDisable.png b/packages/mask/content-script/components/shared/assets/stepAssets/dividerDisable.png deleted file mode 100644 index e660effcb97a0546f18e0d451f7484a58fb9b891..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71 zcmeAS@N?(olHy`uVBq!ia0vp^OhD|)0VEh)8!~i&6rZPyV@SoVWQ(GfLD;Nd`|>KbLh*2~7Yvof0Vk diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/step1Active.png b/packages/mask/content-script/components/shared/assets/stepAssets/step1Active.png deleted file mode 100644 index 31b0ceef7577ee6148d6222e88e2d0a77efadc52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 778 zcmV+l1NHogP)nJF`A8tyOc+?o5$IrnzEC)4U$!%5hOAvHFDI~D4L-exiTw0nK8jt!wrz^WhI zX8Sf^uMIT@E0$aG=-bzJ8~rc%p!czVj0uSE4Z_^>6k zpD&@JiL%yIBCRFzkm!nHdh+<$02eWixc`rdtf7-lz_CbRFqul^)vK_KjJ&=7dwTfz zmA!rzz)=tX^_cAzXS|3{xhxyk5^{DXifBK3Vy97cQu|jk?`bnrjOt5> z@*P!OSJ&EBpvfqlu|xgcXVsj3NXaVdN-INIhY%1|39J)!<}-g!K*L-?KA2s0gk5(; zH=j~*)cyr$J^K$*P?nz~zW{YrU26EBjm#Mi(x)@?<4peRpHNhn0FLkol`{u`-Bfjj z*!(~W$ru4r0RV$q*z4yCW5-3nEfv!tHNlg>y(E#m1>0o=S0&LsPUIjvo8x4uxEKo> zW0B61-XtBN6#~ap9$sYxX8Tg{;!`Nk3(mpL;EIzm6r~av$9=6@kgbu?+q&*8@3~Vy zpMdrNX9m+jFzcU~xJS4+y9mqEYHtDD`qr$a(4)aleD+P6vWOQDGfFu{yuErqdhObB zB!JtOs>`bXT%s|KwK5P2SX*yu0U+pJxT@TTd_ecT`&T2vcX28p!1{btvF}lNxTF`T zGsVfzUal^IGx(rf!llN>!rCyT^yJ07p!;W$plje1qxTmr(xXakNSh|F`ng~DRjT5Z z_I&+J7J|+3^oMSG7AK+N+5#F14Y7skC^W`=@=wn@IWUiZ0eqQr{j`rW*8l(j07*qo IM6N<$f&#;Mw*UYD diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/step2Active.png b/packages/mask/content-script/components/shared/assets/stepAssets/step2Active.png deleted file mode 100644 index ba9f93b3f7c79b8eeeda4e60a7982b47cc608e23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 zcmV-L1G@Z)P)}rJXDHDOp+{&nKAmyT&>3`rx)RKaUT4}q6BtTo3=Va& zsaP1ax5!mCrwN+W8j1_x26bTF_P6;yMRaizwo>^?D@BYIrLstl3zl+vi zYP*bydO@D(i{4;M$h6OwRQ~3Qz7>rxug{wW)%fA$P%DDo?s>{?xmdlyeEEl2u)-MP z#3087t;kl@Mr&eu&15Tk2rLO~5TQ!aNn=wcjmucG!3^l5&97%a*6_az3K6k{K-fm| z*;%QSO|mTnVO0_$90dpLmh$gHbIJpC@0usN?)VZ1?VjqM_Z>F`0>V@RYh0G4VS|yzp&{=e}-rnFmo`$K6(|BIO1P zfeT5(q4~&x=*nX<*45y?4_o{;w*U(ik4&_`aiOXv-PL$maELKoaoLd>+l&xj{86xE zHGo;X+I;z^iFCYuKGF1hH=G6O%>WYEF{58^eCwo4bl}hV=7!gsaRu#Gf{Z3>^1~j| zw`f*DgTacOV_(cIkXh85N`V{2t@LrYM zCZ!btoV9W4G%LQB$c;m-41g_YZ+E!@KrrKm6Vhz~MBv<~*XLr(Z*eTZ02GKQV}ZPN zav2u_%`nM?Yme28C`=UZm2MQ5avKW^pyPy+u)WnUm{Ds*(GJ3q%8RWs(j|&(==!W+ zMbYxDWXUb%L$!hkq8qM$*?B>0JW3`dkdtT>wlFhD^aJLTTEV031tb3h2~Vm~kF-#^ P00000NkvXXu0mjfPs)wb diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/step2Disable.png b/packages/mask/content-script/components/shared/assets/stepAssets/step2Disable.png deleted file mode 100644 index 0e7f76cf84488e5599c3b55121056851515e0ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 501 zcmVtX};?E8Ut$X zr#W-xcpi8dEbHL8cJr+1UZ@5ad{PasapF4GN6)oewaed4E(@AmO0&>b?Z4HQ(^hTT zw52FR;aarwmUFb)Q5`ybHYK_nN~gU2dRO)2iYvNwOYKTew00@G^6X;Ot6ih%alJ#z zNvZ@-|8~#mQy_gYE<}+rF&VqGzx8Zucsi5;LxT)?0^W>`OIs-{riM^s*KJl0 r?GOKl%GsReUapkn;C6ARyn*P*2ZruWzt$aL> zYy>KgO#O2pTaTbptKEhFcw~Cz!@F|h8I*NhwhO?u+j7mV*$V4uZw9PUYPaDKY&}Pu srUBID&CLN}h4Sw>OncHrbN(Iw0vme618G|?NdN!<07*qoM6N<$f}Io=8vp - ApplicationBoardModal.open({ - allPersonas, - lastRecognized, - openDashboard: Services.Helper.openDashboard, - currentSite: activatedSiteAdaptorUI!.networkIdentifier, - queryOwnedPersonaInformation: Services.Identity.queryOwnedPersonaInformation, - setPluginMinimalModeEnabled: Services.Settings.setPluginMinimalModeEnabled, - personaPerSiteConnectStatusLoading, - applicationCurrentStatus, - quickMode, - focusPluginID, - }), - [ - allPersonas, - lastRecognized, - applicationCurrentStatus, - personaPerSiteConnectStatusLoading, - quickMode, - focusPluginID, - ], - ) -} diff --git a/packages/mask/content-script/components/useMaskSiteAdaptorMixedTheme.ts b/packages/mask/content-script/components/useMaskSiteAdaptorMixedTheme.ts deleted file mode 100644 index 842748dcf47e..000000000000 --- a/packages/mask/content-script/components/useMaskSiteAdaptorMixedTheme.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useRef } from 'react' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { MaskDarkTheme, MaskLightTheme } from '@masknet/theme' -import { languageSettings } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { ThemeMode } from '@masknet/web3-shared-base' -import { useThemeLanguage } from '../../shared-ui/hooks/useThemeLanguage.js' -import { activatedSiteAdaptorUI } from '../site-adaptor-infra/index.js' -import { useThemeSettings } from './DataSource/useActivatedUI.js' - -const defaultUseTheme = (t: Theme) => t - -export function useMaskSiteAdaptorMixedTheme() { - const { mode } = useThemeSettings() - const useMixedTheme = useRef(activatedSiteAdaptorUI!.customization.useTheme || defaultUseTheme).current - - const [localization] = useThemeLanguage(useValueRef(languageSettings)) - const theme = unstable_createMuiStrictModeTheme( - mode === ThemeMode.Dark ? MaskDarkTheme : MaskLightTheme, - localization, - ) - return useMixedTheme(theme) -} diff --git a/packages/mask/content-script/env.d.ts b/packages/mask/content-script/env.d.ts deleted file mode 100644 index 346a35e30da2..000000000000 --- a/packages/mask/content-script/env.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// -/// -/// diff --git a/packages/mask/content-script/index.ts b/packages/mask/content-script/index.ts deleted file mode 100644 index 357a9d2b37ea..000000000000 --- a/packages/mask/content-script/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Note: due to race condition between the navigation event and the executeScript, -// the content script might be injected twice. - -const loaded = Symbol.for('mask_init_content_script') -if (!Reflect.get(globalThis, loaded)) { - Reflect.set(globalThis, loaded, true) - - const { matchesAnySiteAdaptor } = await import(/* webpackMode: 'eager' */ '../shared/site-adaptors/definitions.js') - await import(/* webpackMode: 'eager' */ '../shared-ui/initialization/index.js') - if (matchesAnySiteAdaptor(location.href)) { - await import('./site-adaptors/index.js') - const { activateSiteAdaptorUI } = await import('./site-adaptor-infra/define.js') - await activateSiteAdaptorUI() - } - const { startMaskSDK } = await import(/* webpackMode: 'eager' */ '../entry-sdk/index.js') - startMaskSDK() -} -export {} diff --git a/packages/mask/content-script/resources/extensionPinned.png b/packages/mask/content-script/resources/extensionPinned.png deleted file mode 100644 index 0351680cac1d68c9f7150b3bb57872be774089d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4366 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 IM6N<$g1%k>3;+NC diff --git a/packages/mask/content-script/resources/image-payload/index.ts b/packages/mask/content-script/resources/image-payload/index.ts deleted file mode 100644 index 58f4ed05b9b5..000000000000 --- a/packages/mask/content-script/resources/image-payload/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SteganographyPreset } from '@masknet/encryption' -export const SteganographyPresetImage: Record = { - [SteganographyPreset.Preset2021]: new URL('./normal/payload-2021.png', import.meta.url).toString(), - [SteganographyPreset.Preset2022]: new URL('./normal/payload-2022.png', import.meta.url).toString(), - [SteganographyPreset.Preset2023]: new URL('./normal/payload-2023.png', import.meta.url).toString(), - [SteganographyPreset.Preset2023_Firefly]: null, -} diff --git a/packages/mask/content-script/resources/image-payload/normal/payload-2021.png b/packages/mask/content-script/resources/image-payload/normal/payload-2021.png deleted file mode 100644 index f40c8af0060502d333f1a07726fae1d7b7a5d5ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176781 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

`*!+zkdd6CS;i8PJ2Ca{ii%^Tkf-8 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 z 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 TMFZ-e00000NkvXXu0mjf+|)}A diff --git a/packages/mask/content-script/resources/image-payload/normal/payload-2022.png b/packages/mask/content-script/resources/image-payload/normal/payload-2022.png deleted file mode 100644 index d82d945d66583443b1b3cf8d5e66faf45ef2d370..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210938 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

aW~4Pfj~I* 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ő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%0A5T^r2td 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

dX?Da2!*In@Itmtp6$h5)m=}w=h9$E~U=&Y5 zpUA6VkqbVQuA z`_3;r?^cZ`~Zk1su@UkTT~-dZ9m}Y|ke;W9wvJ5r_rwmC%_;q%HB4c-m))`&IyHpT!kbfz|7GHskiXj+4}0 zD-pC~5`dKlE&!)84Ol0@D>&K9DX?r-=5!anBU;O~xRR}VAQA=EZiP(&us|q7RxLN} z0aXg#!+r_gxJ05)7Cz-dk{ofc$@{ZGJ>99hR88nC35-gThG9ArWmRZScbJ|z3M~BjE2zYQ^$r&*6Ad}~aj+H70|(Z^-&FxvM?jbMlg-f< z(D3$fBGu86@W4`ATk4oVSMO5YVD<_3bu=;9K(_PWCS%XSnPhCq3UwgEJ1w zk#sn`HHPmL1C10|8(%$EfweJ>=P@?6MwXdaUUp6ox$A+QxuLWHJahmQGpW_i0G?|& zoB@9Q2}j(rl1|6t7FRrtOU8W>vr5JXLqlE+gFSr%51L7n3@rMx)U6Fr+O`#dW#zzP z^@wb%tyrn@)I;B*Nr6y3i$}-Jg7^ya!|hiXK40Wf`Z~a39$4v{pboM0$I1wp(&d!| ztS%W?tXOlAOT1zO7G%)}7W2Co16GSJu|z3SOwT!B6Y7zR151>HQ+4E+g?$ixUP;Bp za3q_AAJw6+ni+&Tz!E=FLiB;fd|2GQRl+nZo>j`nVNuhujW&kn8L+6sy%?|*@}n8B z=;M)qRo`{&jQcJQ?yU!d(TK$fU~O%7!jWE1o)v1hdn^vBVxirYrSRoUMo5#P3cuEUY`~!i6T+*OvjTt`-qkbuzG;IIyfLrx>uT#eoG7 z^#E9SMYZfLGGHkZuoU*j;k6WE5qt))*hn(nDt5jkr35UN(B;*kz~U?O6?0rMzdFYi zrTx2VF+L3apFnK4@A|aO#?)qZ6&X%mk>0H5X4?A{L9!w-SlEe0XvzzIZ%B zHLR)iAt#>V`Kxdlwi4;-X>C;$30P%dlZK-vWLgDap`98EECT};J)sOC0`cBV|1YrY zp(A+XL~J6k*s;DSvgiW~@_E4GywV{SnyecZJ3Nr}{ z>XU@6?r!`iV@mSNGh{s!qS_b3)`1bEIFP18^YC9%bbS#`>e{Ubq7kt}szd39NtS3; zIKbyXY;|Thpb@dD13oM(^$pjRlrm=n1(rReeICpK9aLDB9a$Q%h)`frLhaYL8QxJ~ zQN^kZH>}WM4`ldNb#=Avcr8lnj^hg?0~Q|92YPaSsi6&+sA_+x&>73a>7ax7bRGa$ z9W&47DFpkdhSlPCXFB0X263LPt!-syI)ba6_)P;h7fz){u5UGIX(u%g1sq2?}@h=HVTrp4R}P+4WxB zb|x7JFJ)F~;8P|K&n=V-NiyF3D}V*f2UAXOzu#v>;{)T@rW8~+rc3!X;^C!Ozm}LX zucT7(Xv7@3p*_8H9Kf0!Tpz+cbYB^L4esQz&2nHd0f!i{a0z*M7Nki47AVG~mH|sq zbbyry*4}1uZRH4VgOf=h70WpsSo(GJ!*~@(83mTKRFU3dGKWsKffW4=$bqHOg&oSk z;s7exe#q4o30T}kC6lR}i;ktJ`Q3>yQ0a-@;08u4=Eo`)@U;-#p8-oS84JF5fmlVf zD}g!KVkh|A8Lo<}&|Cyy^-$JOZA%0eA4XKghH>$%3Pm{xthOovSf>SGDY?QqdSOd$`j?N%eu@<*TEP4|2&(YbLg1_9p z=rP7I4(rU%f09^U6j;AMedJ+R-toFeaAG31wt*fUU5d90z&du_R7hO4v_ak4OcKXc zxT8Y=mUW9}WyU1A~-2qy!YKYKCl zTUoJ~&8|pPt1#`u!K{18`GM+a%@}h=Wj7?6gu;+cX7FW2VDWUhW)d2ZXUY|-kcMQF zP&o*!&@T63RjR-`IMrQED~E;uW!nRV#a99sglEYjT7!oJOAl8N2B>0VI#~ho8M(-j zMkYxZ)fmUHSl*`tOGf~z(a}*|Vu4x0^2ucPNyK8BmJBSeV(9=&6r?G$q?v~juOKq8 zAR@5XAA$UNxG>UQbnR=v0WZ5boVmPpIhnj1jv>3AqIdDyK+8n`^apCWY8uv) z`~B}}&t8iAhll+`t_A>Wx_=fZIA<9&`$H74<^DiyDVE6u2@Fw^N;nefsL{OQ^VU$@ zJJYHUYu!(Q)jxc%kD_evgQ=k2)KaC3WpWSIhh6058L%{Us)G5jRC^4S@DlkHb^`}i zC54j;ta`f=Qkl0`s@SbLd~9J1Vv$O)FE;mvSJ6Z#auWlV@~OZQomXtHKiT=vsez0> z1x6JG7SAsFi4fJuh6C%4Xf5gs8P*`ERqRmPj#6Hctv<2X*ailyPa>9P7mCC>3xO{W zp9w!p7_bB+6K>?i8$TkZ0c9^&4rudlywa1N@CWQF)L zTZ#}R1Fcv&W(7os8X9cc-&EgmYtIQ7gOarM^z^i;ho&A=-`Vy%*No}ls?Rbs7|+~a za-_oX+wF}+3vFyXc71eYD(XlW<3P|!!mHS3%n`Q?8W+1C-kx7Jt^^$@@F_N6+v+FrfOjBO<^$F`oWjdia00;wT)YSq}>(mOJ}v=kpSj(tfS z?w82g%^Tl*{Y~8O^o2K9rad0_v}M{eG4AoZXUXCXM1yjheU8-!b5n_qSS*kU1OlB7 z5+!=dW$~pPk$BqWB8paec4B^R&|)FU>GgHDA8U^e56?Ca+_2TwH<`4T%bo6~+9s1# znl4jQQy;Q&xlmi%bU}0;(fai@H7D4@RCw9gazUC;m7s5&9@#@Styr2E&-wE)uVi3} z6@^+%^h`o2SRDI{?$cXe^sx093X}Q{0E-_@WoceM%s(D)1nP$fO7bp%fGWi&40MG8G^oBJ@pgy6Adq?s=Yv|FZW% zD#w#U5!3JO##+VDmGy-CaqlmCf43yCFnh4l4mU9{ucUxQ1O}FO8nDJckHEshI|dda z_+$I%v!R7lcmi1B7$F@bu<+j_V&NbL7QTukupr0{sFH2+lz#*DTH+tXpcISY`AI=M zBeqxfwA#Hi-&QI;^_JDrEjMA)yK`^#Jp$J1oHw{rar6`l_HQ9tsml?VhAOPK*v?*V zg=^Jg7ru@Ahr5T{Pat6dv3|bx7x=oM0hff!PIcFvUuUyjfAZbN-NW6-V%xkK6fPY* zITd2nvKLk)E#$WHy8V{Y-myS)l)Yark9^(6W!#rlhSa0vP;F;j%estE7>3W2=VuVf zvjgySPlgj^y{}a3@DY!buL|7m;qDQ`Mx(7Z5K9>X+xe8G1Agw!J~)qFqOGc-tPl!S zJ8i)Z&sPPWS19Urg#un*Z#ir+n~jY`BF-Jvs~^aC1Qu4XMLtosFXRd@dg>3ZMFWfY z%SBk#iZ3ULg}0m#?*7>Lk%HD2L(c@TBm*05&(8+dwq)-W1uU|xqk%O?0c&cI$`*#& z-XvxK3u{=qr-5-L!7FOLeU1bc!7I_{j}+bsuNl%bSvqiGh?^X+LN37?F)*-*%@dU5 zJ`H-r4c^{lcylBet!A&aI{@6`=!a@8qkSMciM{Qz)v`*8i~)6YNs*>7$? z3;_XgjT#C^C3_gy=Rgn(H$Ds=?gj@MSn$j?JBPWgJ=H?a3vat>Z_B%o*Tq6{zE?AQ zU87=QSkw>8r9vUw21lCmNMLt#Ia8(9Eg!&Sj^BTFo;(|G$VfDbO?|;qw?`u!XRX_! z!DB-vV{pdmg5BPRhARtLW2x2f&&T_kKG?d0k-GqGSe7dEVfA1;w1TgL=O_>KQrOYj zdRwL9j7E-jUD~%VXkba$kHXzI&-JOoz~pgOFcUV}KLdLhJ* zD^BfkDM&cS;geTl%w9*2*K`pIyLj)(tPCd3)F8VS$k?M`g>x%(c)U9nv?PH=idJGF zNCbLNs-slGI9U={3k0x;hIcs$EDBoqAyVDKNtPJ1YeMneq|zP&A2Y6jy#OiGcWGb| zfj?W4gnUFXiK|(^koT+F{Mr$03{)u1Lc3pX*3|0O-YNhKew*@~H+am+tabq@JI6w* zYRM|BGJ|eTr8B^qzwG(V?H$nS;o;|>AAbJn`u3&^tLC$ybLoppXFj+u3=^=WIXQT^ zy*Vk>BP&PQX3V%J-^+Oyyv7__NeK&gO~PedsHaB7W~tmPj`Z+NUy9hhn44nT~8SLU?0P4p+UEg_tOuzzHUyApWKAa+2`7md z17Mu2Jw-o2)(pV5MpqIb>Mx;TZtd z3QXuRieuD%pa2ahtE|^I174NU9qV6~&taF>9Y|Q$*8r?f*LNRE)$CB+PrGVM{)@HMci@GodbA)*aem?|n5@D0bt;gL1K492L4v zO~=yjX0z?6+Z$v$g(B?rdIy~HClAix=07+Hmwm%gUtw>hBNe`5u^#IJ0hfVGx7xir z4?f6{Om%8CH2~|fuc@hFa}E@QK&xI`;6pv=B85!LV~c@)#mi)Ms|VU#D@-IBH5{s5 zyp;wPNh~6sJ3BKQSmZ~91Qun=APuZ1uOZ6;^23JD$(Ri+Y8YostO=@!QD&(I&~F0P z-b`SLindrFj)Ml)q~D5$6%iO%&rh-zP>>Q90+z%WG=%&qu(qXuMFp&1rR^M~i8U@B z$H)>F(?1q;^qY&`P;AnVuOXQ@ku)N+bLrKt5)~-58(=oDwA*{fCKnT`dt#MVNAClb zS{dAuzXSnmb$f3ix0AN^S~B;WxAXf7CDcxK$D+{~%;{Xc@YVgs^{4CW!QcS_>(j&i zNi(J7VH7a#0~_|B>6G!`-1y^`^3~PNyM)CYJiPG5ojTQ=c22eB-I@br<&0i$-XN=u zQYly)*3_4M`eZol8^z-nV18O_E7C!u*Cfl1ip7KEK{AQdD}Ms06%LmZKt**=8BN<| zvR)^a-UVCu-HO>(uiHUcZ@1f3a8m_bK}iVPVrv>@8<`|`s)Ecegh08HS@~AX7OPaO zkY%Bo&{5&o=+V)lX`65!{WUW1Bh9D?r*|4xlE9kOW;C$KAWgi*1N?}hJE62c2Uy~+ zU(!}a@=qn6Oq(Zm@k;UPY0Sv1Xk>kL=4KC9dE}Ko*MpBJ5Xm(MM+`7@7z0Zbt8^5w z*49X1J;t{HYk>xqYSM=F1X$?odBB4EQowpv(7^iK#D~rQC}2$+T%0;>ibHBC;@erg zk^~kxJRuFNRqa-0XWyoP9xNNL>2^!~z74b`02ch+l&kVPo3L!AUg12pmbC)8h;2V- zbgk%?Y9BW?Zm&VO`t-CLtv0LgEdexdmCQGk?k3+^%=#~rR zVzH4xrC`{fXe7enL}ED9$3q_0=``CwP?x2bBF;$E>13^T;FVGU!$D}!(DdVleimBu zEzss}7doMyf^YYFmYy9rWrYl@0*zety2AnN;wrW%2w_VBdJV|$1XwS=30PRc6sd); z@N`4njFONH@>2`P=Ku?W1QzYiK(cD}nZSA$#vaMDHF@R>U;zNc+(rC>mv3QdYm77W zG60qsm{e{m#1u@Td(=p7w5J*w@D4C>rTqk2DDYb7iPaaL(Rp0kPyfe>zeXg9g5Qz% zlE@q}5P{%_O2(gNSR$XugiL}}5(*ZI$=S3OyyFuQ>*yd~W;R`B zd&`>U6_@&WO{0nIspKHjqYogA&1?!6myV-KjPW=k4%WG8oLk@BiPk<0?gtMK4?kVs zef)U$@Ck^up|?goO11i;uZb-!p6+;jkXa>D=I+MFlW;+?Yhs%PMS6XC0X|Leo-U)V z;bnKuYl>D2qin6DZ|H}kav3VUl6t7OggOErT`~pSE}ME(Xbc+=NeKTzGU4|hlzqN( zcciY_TU^u$z_JLHr~`iSXE03IlDKTwHom1qN7wOA+@sCApr}u>sDT{ zI=C3anmtewwqodeLe}hzW_C@huRp`&DRRUUq_hL5099}T_t4q1oZ7J`6_F&(SbvE^ z%&w*x^@+-Y#9Dy2(Bb!FN#5hrvsr)l2*w{G!a2^=@B~;{p^@X zMC?tC?|@SzurRkEFtLarcqIWW5?KVWwk8!JI>L=?ZoqC8i1-AsL|F@+kidG0dGbxb z5>+gSC;WgLnc2X_rvOU~3Rt8rg>L^>U_p@W$e)ot4OrTBZga(8jXBM-3txGp>9$wq z7FN-&)m8b{R^X6Tw5x11mWCY5mfjC8>>1a0?8PgTR^9&mn+IUhy8)zHgOh|hX12Cr z(anX!wQ?NfiyOCtcc}>1IRAKF=rMVxx~MT9ujS@I!ODTwu0q{-MsI$fQG-!Tvy|`^ zn!ZNkpsd$74BCSLphnCInyst;RZ*Kb&a5WBq0v8nMhCzHYBxI9fcL zi7*j!nrrd(HqfbBZNoJ6CG<8xX;=e4tPp^zS69?!0xZ~(@w@|o<>4v_aV?OuDz-|6 zO-C}D`^I_12r|PGfdw}9@GTzdYIq@jNeKA*I#NnVk_qH01q%bIBJ zIJv?_AvzIjLM;I-IO5_E{Y+_u#}hjfR6RBWd>vR=cPHLzc!B~JW+1+flvz9_Own!< z603lo;{z!PEcj)~54ok_qQ@5Dl$S+)sWfUW%eN5a0a&n~ewVBEt6Vg~z<@BjnBS7; zj}Kdo6O>;;cl*QD$-%putJ{acee#lv*?YZ^CCe}B*7qab50HH6D~#piyMtjTa=M?@ z*Bm>?rvR*lE$?#9s~W%Q3+^RP7q&QSO5vh!nD8Zv`f?XKJ$n|QiTQ9P5Xh)~MIXw% z5|CLzPea(>@HGoJX0(w1H(SGKB)PbxT_&r20^!Uc&B0 zO9+6aP(FIffX_@D3c^;4so2amrPS~0MP=QwB{H5hhJ=f(t>3M@d=T;9DPpMU$} zjkwcP(oF%kLE*sWLbIolX<)&RL>uU9@swZ|(Qk_3MF2~bPKgiNqXa_hyI+)pxFjTi zMY2bZ$sALepnj8pCi{KSH(4m4SvEU8B!a>jq-Kd}wEDS8nD{9Fq1{R!;K{Wvu z)r>{(3Vw+bU_s0R)+4nhsCvAikN_63sFmvU6g%t)V3DRa;shx{EJ+%XIho{ZC<#l7 zRv1~LagkU)FC1}8>#j=LY|CnVdg!#YEzj*ufThX@ZICl5BN=nr$uP%jTCH)vt}cGq zxP|5`=)Jl-@h5J;S>}G@Tw}NKA*ChT2L1(t$D#AV;4Yc+q((Q%ekilzl9dX)VadFc z+uB0vRc<+_%6S1>x@f22PxLhxYe^zR`Ql zruDV#3bA4au--l&SOn)V?dY9NbOS0*#z@s21B=eEB!KmvG?e+mu$@es$Oca_=;x(@ z^{D)aR~Wx^!Y~n4=s*VvvT;c$1uQ9KO##-^&IWodbPfY6hkyk^0t=fqU|_8hE73%5 zK~TIR)OWZm-mq{j2`p*tn<@z*NMO;IkeoMtOvof(MFI=&iGz@CVt54lHryts+U3oo zL(^g!yjf~-ee<%bjxOZni1Mr2J=pc^yy$1!3=95NM~iFF?hp`42O9&_EebOG-8-K? zd3F2g_WmTSo*iy$E;Zk#mio%2QGhkL26s&TiFzoi5 z&5q286+!(owP0{3DIrUHvJ?1P|&aA?!LJse`i_RYW&15Xc% zz?vBtSVRek4-kPhF}`FL2`m(tSt7LvV2x9&$(f2I7GC;=fu;S7tjX~aRB-w%V7-(C z76Gr>y#=)B4)Kgj(7jxe>&D=|grgb>ECN!PTH82{9|KFS+cm7fz#yBpb_!h$*nKW} z-SYQiV0qWpcUVg;B&$Rj2V>aT(|Yp(tN!F3MhCAU?lwjRa2mZGY@7_$g2H3(*_r)d z&;*p}!tmf#qF2{65;aT2?cTJPHQcewRx#z~wv5Y=GNUQdylz?LPPd1NM4>w@7Ry7Q zA1Xq9R}-qYJeAC$F~4u9_DiEtVWclM$_bFL4i5Z@a3c}+m3`pTJhDKWs5z#n!fX#r z7O@e4RpcnjazY5&+IdK{s&REngCwj_r~{m;wv~{12}-3>(P}xY6&6^`F-(-M#*Iu{Dv>ph+AMJJ1dVci>Dbamm`~N!u*7$VV+8Y{Ja32Fp zd_V*iaXqPj8UJ}EG!3G$kL(v62Obh6aU~`gIMEE5w0SEKzbNcdJ5t&aTi6g3ho=W&xXsb1Z0K#adF&q8oxqc96V6 zM^edxh4~X05tt$_iGc;r5DgCaWmAa-NAiCe{uR9XumAbWS43~}xLTfD+e{zoOcB;8 z%j!$nez$$9b88=mErQ3vLz^{JYuh55X)ECq8!Nl5HX-c|vqR!mS9=JUEW_rec9{0f+bLu&5$_x}B#exIz_ zq=AJ)q!Ph7svQDvZX$yEK2UPqFX88g3yLW-6cPoYXbXdm@+Z6!Ifa3RWpw#W0gFt} zCY;0frS&V!Dgsz!P>I#_1d9MxUV>P3h;|Jmu;wQ5*j_uq6u~Pp2yD^$76~kAVqx)} z1QvlS5m;klQSsT?@Bc%*m;ei%e-F=KU_p?K#90^(EJD7b`x%h?$sb{0O)^mJUceAB zc~>CA5V|7}sJr~>x(>Zc3nmWC%3{>oPF)}`7P(H2yl4y;A8)Q6J`Eo1tP|GOA3$|@ za~7}0^4@)GjM-c9nmeCcU|Y%=zX8DE{h7YekjS8hN(! z6*lIGFcpSNXMk8vTMUZAJ!HVe9BnQJ=cx>960fx?ckt8|gpv?G_W8W(wTPzK<>2>Y zW5sUE*5_J~dIINI(q?`%vCxsyvoNr*;!Z4r7SmKrE}CF3B?G5ekGI)P4ib6J z)XPeyYGd=zKSCFp*z)~S>{5LyWl3x?d4}Bn1X$z!gI{>r!cDujxvR^$(yYzWPyhJ+ zn_XvcB|ksbu=28&>xi{lQVKMk@@91l0n5?d7~Dbngw{#?NjH3QKls?FwX0yb_eV_3 z;6B~iT{6WIR}X{1N!QX$Bx;`H{rqyI)I|T!R(m?&Md_7$8K$3&##Lm+Y1jD98igJ* z$tuMABRv4C8d+itLHC;J{U3k-OIEKh!TKD%epEyq?f|z&1N1Cku?yn-g_5_k(E7>C zENW%j5oOHIdE_kEvO-_J-NA6}i|nN)n@SnLr9B8`qP@lQvbr5IEg3X3RGeJJ3@z@_ zNc4!YO@Q^roX)_24q1fZ4C|HL8!)ZF*O%*176x?2h527iwbetL>7N4Y7vs$qaLs>! zCq!aB16X4jN@NZO7G=jS5{s-Op9w6iwG)~xm0A&9?oZ4az692+%8in-B!NX$fwPK2 zu>d2XHTHO+frU##s@q)*Y3l|WSrE?$mK*~M6N?H7U`ctDN&$;N7UBx-;lc^l7xky; zpD@jJ=DYEVx^c4gKZsqg$iUf{$j2$Lk{Wcz$vX<<7WW5YE-zMv0IdAxX5Q<{ICxFm zg|aE{{GX$RU+Qh596_nE0|wV#QSI>Pa&As7IH?AuJZY1NVQhS8>2772mX=* z0V}TWmb-mTmCr1iGmu~%qO9Z)0ZU)h>-C9)voKVJjbZ{)G1P+5ge??pE61=E;FCoV zY|f~eSw-CEJl48kWzA{cQoB@lF0uh+5)UR0$Zphb7kYMu3`8uJsW@2HVK$@<2DSn+ z*4#M5LUrZ(%eUj8I>%puwg(j<(&|=B>%q05`Cgm?0O})5IcEEsPUMZiy27*CuudW{Btz2|)r2 z7n`K%%-H4qFJ428S4+Hjd{pe47p?i8W>y%I(!e5HWhTFsKltv~+aj>AwxwNO+%y^w zBaEWfJVnKn>o~Ip8J2ccA;DqdP)LQgHx`f*2rR{nOcp4+qUQmmV+I z*xgxQS=?EwG~eAnJlvg(V0&SzvJVXaE4ER|j)0XCB5OuB2v?q@cF>dIHh1Rn~@b=ekJ_^0Sg9okPB(S2a>x#QEq^g)o4J<)h(227af@=a^#Fu zcqQ~U#5iZvxsB)=8IKfNIRva4WLRThy;p`nZ3hjju4vnlr;LM*FgA0VjV|qOW+2Io z*|rd{%v>5q3Kw2a1=fp~+PU1i39%{=TyqE3-CJ)byrO|cSS0-T-FHwCej~8nlEA{f zQ_{e~n3@W#8T(QvloF+g=K_o36`cd^sYqhcZJ-pe;D#T>z``d}aV5_1;Q)M0fF+_y z>}SB-5+A`>BY*|U6=~c6$FEw>V2@;x+JtlJ7s=FXB8(&~U$+20S8bWhoHjGa z*QzIDSf?}c9{ETcGA)qSTM;MQ>!h-&S_h?v+dVWCUFUhoz7#xk!rFjum2`$NZyrUC zqGpiYRpXM7feLXm04Ww}i}!f{rCh$34?>Ey0(}iYt-SXYstF+^Gc0m==6_i`7p5kX zu8$iOP`QSH1Ooyg35tM5NP-YS10=hfD6nfFULX*HqFjOtcw?)!*mAe@0|bPCK$%*f ze9O20Cq0vwyz3{L|rmjo=yvZxS&rCmw{mZ|}OCrt|TvA#-e$9ltl}V(+B^SkV`HPVhHVU0u+j}?KC)XZSK{-P zu;h{ zgGJdHuWtOdPpk>du-W~LludSeW*-LYHg}TE_kC5yp`X^rL(Q9e+dCVz&t@J~0!VMG zCvACW@`|cj`{RB~%))`Sb9H^3XgI~j{mtAxlTLjuG+FGmWtTQEOFA0_6SA>EdoP|m zxyoL-nJ3Sd8?9E)`pn~rlCAB7e1Cg;sM=XpR#6_Xo;e@3L18(aPFGo3NmF}!b;J0= z)Rj5|kb3J@Mdiw3!h0p}AhTH#>GisuquxNIJ=EvynVe3ilf_m5tG&H%B3QDwok^dw zHWF%zcQYxJn2b8V`{m1YdEr4_qaiP^hB2$ZqisK#EV3q<6>eqNVT@Ka$s`s6!#*8Y z6uYX(Rh$-BqGCEZRDY`7vV50-Riy?Nw?n{!sevVAL|`4`5*`aIIdCNd>mCOdP$dJ4 z5Tynd3rDJfC7K8<vTUK3heqmuu!8MzOEpaP|*F#Hd-924%-Vx zqqcf;rkT|u#X%XC(GX`uA#0!cq0?yZo3LnCRYqLinF9-M-5H%TLI7wrQJUw!)L)30x~vaIpWExTEYs$lJG?Z5hIg>S9}(mda| z&^Lq|emiJSp{msrkD;rujo0tbzfS;IZ(n^JrU#*e9&YH3ja0GCPzy34(@FdkObzd=|wM=%a7F^YA9`Hp3lI6N9z(bNh_x#kfrAmUNQ zKNoE@Vo~sl7U&aKo6lH{MkF{&02T_eWmsc4F$XLIIE8Bwxs<@dc|M<&ptFO*ss=0^ zRw`Vj$XyADJgkm_N~EwpJ^!hTzxC(5^*ooX9WZl3!SWZni}6Ym!u%tx09IER2NYVE zIs7rdsY<^9sz6o0#?|g^TBZ3a@#-r!EzvJIVD(|g&p#I-S$bK>lGPVYdiAb5P^)F5 z09M(3BF4a&n+=U-z2I}e>Td`f=mldq9G)ACgahrJ+X7+c+OH!8!f2uHBE9OfFQy^B zcorD1@ObPo+;NQ#ESw|)OYgGzKLFMV@pg6Z^~?I^ZDM%$)PGYgG z1SoO09IMI!OXQaB1`3eEM>GPKT@9=tnWV5FuoNOoh-&c-fQ63;EM%0x5;PW!cg8vq zSip!tEy`-*=mcPCrSGiSoh71*SQ5FY6~s2J^kPt#24co$A+150_Z?uRHnR@d-sT>X z3Re@7gY)_OSztAy$;Pa}`q!5OmchqZh9fsKbfB*UWa)4K0PY88H$bng-Hj3(a+wD~ zUmE1A?AT<<-F6SC^!Fz==H~Xx1%2hfRwK_OC&ND6Q>VK!iDuV&w;(|x3C6>-aX)}G zcmN!B^vC1<=!RwT(OqEE24EeGUxN^vHurSwFgS$T&K>{~1^M}bHSAbgAih3+g>c`; zS=^(|UV(kJy&PCy4L!J@Iu{D2#8|*Rw>-Z(-eTB5`v!wy;L#=I9&{kdM8ZzDchnhb zM|Eo2MKrSZ&ZQZ2Ecb&TRY^-5Jzc&5u3V`d-xUt77&75$J$AbR)ujZiopTqHM6{(ucP9PT2-0Bs~a=?-t!|wr0=shX|>ll?K?)1?{)L9x;#E90> zY5$G@i+v9FEI$;^S9_rnw>0(5mo?rr8Vj?aGBz{dq&ToHD}Ys(^&R)v^$(zOs`52S zwMW1rEU_pSDldq@%GMNBWvlWDSUUx`T$N+>Y}WHmfWTya7$apC9v_|?fMyvYehp1p z@M8Y+$KQY5e!8~$3@@ftQ$x+Te{(O68~o$15A)zsckLdM2Z5Vc2R#-G!4UW0Z;i94 zdo1R#_({ktkdUe)mvEfjP{v%_dgUAty7Kl4`FU_uegtv)*-U-l z=(*)t)EMqA_fEJpZ@DV4?4D@vO(fX3=ao3RY^<^l?uiV$kb80Q(KNo*iTjbciJtIX ze48CT(a~l=r5D#}9;vmUo7`>-%auWui!{{%yM=t>&<1geI7FF7 z5U;Tm4QN+BPYTO-hE{#n&eIk;PB#yaU%fVhSKmJ|>1bq`Z>y`rUNc_)2L!Bp_F&c} z{O7M5Yr``)0IVqYM9W$L%(*TA>hHgPxd91O6xF?_qPV5QqdTm2_Fs+rV-APkK|%NT zGyA;iAkhVnP_vPhMG$S0Mc2FL!z@?HfX+nEb%eg=*V&;vZ`qn+y^urheq4LCVR?B} z^E1Di-M!r*cW8N#jd@^Gcf=iJ2Vo;Dd9eSsFxI3BZyl#f!pZ!7$ZuX$~_DQPiRw!xEvzA)%21 z{1SNO7k~vt6%@>)0G7~kRO6PAMWXDOBjkxnN7OSi+w(Fi7+JYDI@ z>vXG$rKS30TxAqw=A5!(&$r`%<>J7qB48n`t^=zy2P`hJuy;d)@C6ZA+Iha_j25?= zdOSnGiiezjD=4!%K0o&mZ>-G+4RqOpS3UXZ(;v^DExf<+Cx8_WWY>$(2=#uD`i?ZFqQL zC+j@+K@u{Fb90@Yw6&V(s_#t#SgpAByOp`IkWCMag+kq-XkVy%@!h+50@nQSVj=+i zusQ!8MM18B(>d1{b(aHPmCo?!WpWAciSZ}`Rx|ZlFf!5tSS7WdV30-I@nstEl^2$l zTC)LmmiZ3@mZq>oBVe_tfu-5Wz*2|H#go;CP^tzNGKZ$I%=I0FiM_A1po$`b^__|J za=(=c9luWimYB=7CpRo;H%(pXCdaV!p*WCK9&Q4#5Wtdo#ZBXtXvzuS{eBr(f{Db! zV+l?npSs~kO#q3=u;YOx|088wDJdz8H_o-Mj{@2zK4ty9GEGLg04(AcWRS)ys%nyf z^$lAQSR}Dz$d^FqBy(l>6jQQA^neNraIVY}SGo8i#4S+|< zzi!`Nn!5J%1}UtudP0u1zYUGQ_^iRzUpNHEWvH{*%n@o74w;3ec3{KWMF+`#b4 z3JA0twiB=#5`nP?xtMzUn3r{V7#n2s^#N$sdA3e{Hy(01QT@mP3cJnbP{14kn3JuQ zP8!$K7U%%oMh5Q2uQyFMw*c40j6l=rgJhW_pMYf#4$ThPO*}Ne)5xlEMNq96Pr`xK zBm+xJo7BLPVoyI#EY7X}Kd_{J?Se_uiym1sl~0MRG3Fjj*{f3mR*qOWTg2A`8DL6a zIfRBmEQwO$>da>lw&Jd!04%VIV++8-pU5k|AeewF0a)3M5Xe$2r2}H=+>%x(q({cG zLfSm|rzDmbFINIf9nn(YN?4gqzD%wwz6w~Z1gVuNcC~yXe~*;aUfIP1RO^#5U}qES zVRZ;t&>pG$s>b}3_Ol?slu%?EsL8B<|)D zuv&5OgNE1S5`Or7t!w7S>m{h7zV3!17E!P5!1d3q|8Bw&^S{1_OS#{Qc+kYt^4654V%M=P>9F`aVp#mygyK(6pS-Nix`fMJHq^8K@$_X|Y_Z|(EL_r&==Fg|;~_f4 zyyT;F_2x$tK;^yD2Oa5)S_oLfY`pUo0$UTdBcZ`i*xS9lI6Q9#SM0S{?k?s*=(z99 zrXk`YBNJopNI38n*R?x4@Kj|zk*~PrzDRjZaltM6VJ}nCQn80R2LOC&$P@H*c6NHW zOI%^(z$&V-7NeOY7wTm69nc!m;f$Giw>{OZn=A<_=;5 zh;L6W-5`ba=X%%F&(D{}am8?b_oS=lUTb_BjQIWbN8z;nj(cdf&Q!#loX$sEc%!+rzQ{y|?#>y8L)+oFBatz70^^b2Ba_-~5 zcPC;L!|GbO(+h=_=j;K;l3@m{iNIL(%E~f-wyJlN+qxA5+>;Z50P8yH67IZWAXbUd)cdKIwPN6jliWXWT4$`1pJkRo+vFo_P5JOWx??-@B1MN<$Y&o5+;F(x&18@HJ?O$fB}d5Gf+Z%N2uJ^sKa^ z1wUULPNkbt=BYKD4y?wE-d)q>LPKZB@2HfjqNbD8I2kKcMRQlee#)z5e-eFW%z&p-5!1v%jOY6d1hy;SY1}w9ghx zv6fH8R-fU1$0}#3>x9E^LO9aWiOOeeLDZ{@J^*WZu{Zctiyh6aT>Cf^cH#Zzv2PQ4VEFj1C> zEU~|ZMlI@9t3C2{E%;7C>xY6_GO&cAdJaG?xU?jPz#Op3_1safP;6KR7KmlYkg6%8 zUuT;3NlsvwHW6SJST2_YEH(&-&O_}j8;l}f7i@=sbw&o3&A5>wu$9lhpGquO+ja<8 zHe0zBKuWC>lQ=ID+M09MRz z?`I`sUCy<2+@06_Fqp!HnZ&vmi`ng{eaR-adjEE%f-$PM+Z(YI5gLjE4O3H7k8gTD z6R_UB`Z&kZA+|6ye6L|P3(e!1L=83nZD2Iq10EUX-`z=616b41i3mA`!B99n_Q=cD z$!X{(6?)-aeSK}VcWPTtU#PFr>7AQk(e`Lf8v6jMHuLOAPaDaq-SU{x+G1_qCB}Td z7qbLG;fo%Nqr;AAM@eFt;^mDkR^*a|+G#5RYnn=_5)Q0fu)U2}lxCP8e-tm+2=*Om zB?C)+bPh;7b26{g0!tAAIW#6mM2Z}`GRgj+mgYp33@l*>Z(7~D7;Tq<6(iLHU=fZK zywX$_>T(hbyn<=GIssT6BC!0*^$LYAfCGzt2=`E^Cj(0ciC9uum8h_^yl|qlb|khS zs7x%|;YL-)2E4WC<9M1Xv_f{h43;_0F4xtS3gERu$`}P7R5TILx2C6KkFH*wUm5f! zddh84XXiZzEOY(n$dI26j&AjK_jbkt&tCue;lpz_SKs)}-+p~Fey9Fj{akiz8Kq}Y-e?rwR~M+PxTKZ+qsxz{o1x$BR&_y4+hzMs z{`2o4x{#@EB8?H$A?5hEHeW@$P>%He?vq_d`4nmG}|8 zlyMIjP3KjK(hN?l!C3{SkjZ5Hi;OBPn%M`MCg|-u>RxU z|J#56_iq&%{_(fJ{h$B+*sui8yZX8xZNIx_4D0K@ZDv28*|{_b@1|Fs2zz3~*Vpth*j zof5e0AnooQc^gSO1nr>NaW$hHy*zI7HNWoah2OsV2DTv)PD0qmE`-6YcmKRn%?pT8uF|rte4JPxlRnSu&&x? zF|VX%s_QOvrk@7T+RsP#upm zv%*80dON(O>5`dMDOKxPoEpzjl0+$ar4&slG&c}ALC?kh13EuEG=K0#W1dnxQKk#$i#nWEPXlWg zS9s?M99TOgUWmZD0wXv6Lcl^P;op`hV!U$UYjF^;T+R>{3^uS52v`?eU?ui!d)QgB z0oPh&VBx&NB^DwU&Mk~syTIE1)>u{+Z2mN`?E45<$KUpVp>vagh1cpLpWRWP3n@4U zvG7e2u+A}HVgJTMbrY07iz`KB=xF;Qk(0{la=GKvPFH!?@v(=S)Q!;j@s*Nt3&mjI z-K*a}{Qlu{H{88^-%nQk%YJJz(CLcH7fJ|$VdMJ4AAkP#;qCR!ZFtNEJ)R(ZQpE0M zq+Ic|i@9>+1{#RX%PcQT$_2coF;CIc4$6!;ZuPq#8_VXxU*mY%6zL%3JG7(RY`)yI z`qSI{)7zn67>twQ`#Y#8^Xc;L^z#1x^!|3*=toB3(DtG$(Pa#S5yHQ&^g%Sd)%- zE`X*C#|cv9{A(5UUtJyeJST!spP_hk5=!uVUMbo#HUYpo|Mge2JP-pG*iQiqMjTQI zGtUJUdK3cIuTF9GEMSp%b@J#@pmeD{R_!YcFlYWc8CX6P;UNIah0KYmU0}7p0xO6E zEAg)cma{(v=sFLQdtZU|IBf`qszhVS8%R&0me41dJq;~l=~5dAHvLeH{oyf>Z`uQ} zj`j_YWRY|hmU0h<673Hf;r~1YEN2PG|N4-Sfb9Fg@?BN5@?bC<6hq8SQwrI@@()Vz znk$C89CsP_pI^QD@a6NTW$W(KeX_n>g`0tSnW_|_G!!#Q_CJ33?YBREgQmv8%Io zD4LzD7L955b~?SEt|sDaK5X6JLu*3lX8-XLYWdyvyM3sJ6;2N4%T`L{Sd-P6Y}P#< zjwgXxO$Aix5+KMF8I?9vuAH5?SJmNgu^0~LnL;*ex8|o!K`>Zes4=|G%Pc2YJbwU0=IEzwp3>(L>-W>N@ciWs~Qr|6(VNCK4AN-!DX9A#gc5L_7yr zKpET)0PB~-!1{#%EL3~@nZP;*+pp4qg~n}?kV8za?PdtRt0+&4Oe_poTU-%QiQBn46dGs0d*|g?N|6nF0gO24mRMw zDjf9NKK#OnR}Xg_(0U49=lD1N&~en@2;(KTiGW2gQk=}*Nv0I(-ofy~!!knj8qqNX zEGOQ4L0%BKS-D-TpSA>W31h6AZXX}V1ZI$TRl09KfWrE6zii!K_E(vCxY-;AYJ9@w zJF94!DxA3s0J2NP&xje6Tpk;3{M5o5X$ff~5-RZENs?}G`{^{u**v-vdzkdfM zNV|>odX!8qldbAFtx1-t@nWh5e(X5o9|(r2o03FpoT+n?sS4r%^4+VeYIU_*`6sCY zR7cK=s;L^REExk%)$_6}h;m$}6^k`AqYOguA_vD*0qUWjhuhjmImUtF6MqS_1BNn?GE{id3ukkUbvXf2fmKPIAFffD_rA#gHAvAom+w z?uOiL5wz3MEA)*hZA=H@l^PD$OGe*ce5(B`7WZn5Zk<6e3{hKbZ8`PGEJ$leV2KMQ z99UacJt7MUtbHQ_3$+6C?AFHm>W*T-+ENPx77l5|Xfh_}l+99TZE zp9U5f>a+hOqMiWOzk!i{6Y`QrUr158fm3cbMA|&eDVC`$g0iQP$h5;!20mv z&2ZR%->Ulqt>t35%82o{&z0cDkmm3ffCU}tKb?*Oevyhs;q!7Kc5hDIQU_x1iCX{D z-Fm&uh?FeT@n{E#bpZ;iA{(>C8pK!FU#JgMTFIH-_8CNn;@V`m7=_o3Mq}C zHQBwr2bb^?dga{S-a|*QKIp4vYuOAhlhw3oQsB$k!18D2vs8`KOvU1Li`8||LAu73 znQ}Jc_YYUY6@;*6g>(TPq)M70sk|w2rl#jDrcB2ZN-k$<0wB(;L`$#@B22#e7qO$`mm#mDYkK@2|M*|OP?n`sJg;`_7QP0@38qJX5R+Nt zG%PXz;kpAX#Hn3iofBNb-7@Yw(Qvq8#M;6P2M_+$#iRxdw%9<1aOmLGqrM^o3m07f zwx!YD3}GdK11sTFSY%*fD+)1{wKWV_9voPB%Ek5%K_u2Aun2~{L_9tHM+`;9cHT4x zuy(0QJ~x3^j{VNK`uQ_>_0&?z$NFn{I?Yj!DGw%Wk!qaIrQ%DUhl)by=R|s9Mm^&6 z`qk%G*YDT$@Mf9f0;6@amCP`-#}~TLtKt6T^@l(I`RC`)?=GhccY#jW<=S19#pT^l zjDtTPwUVbF@5A9m!C)qXReM}uP;Zo z!s>^Ut-x&Jug$~I>C2W_5WVU{yuAzkC&I1pXi-frmi2)~1F!}RQvjNd#}j)!j;2eL zrK@ULx4`ohG%l5z1Xh7+bv_1xRV&nMoX#5tPtl^t@T@7*0!LL!IYm);L1$Hiu6X61 z!ka3ac>NzBv0l51Ah5tGjK*N;P*DcRnJmvs6quDSo&Wj^xy2Sj)#LcI5o8ct5vXv6 z?EnjmL@ZJQgX3%%uwEVp7UA1)7+7apV6{NT ztB8PwRt|1&_rh1W!m^140~uH-dWBZz5P{`Ez_PD6xp1!#uUCPjYB_p^Y7o=<+!&SZPkngV-BRLF{DtH^Yx6WRq$pFO^pCkZtrT%6^b z*Izz=c=c{wUqqIokY1ibLcutj_naKXOngx>VxIJi=~d(Oz6+@lFhAIZaC@UWjWpKH$Y>N^ESKYKnYI*3mudi_iGS|TrpsAT zH)(UgaVDc#v?gj=E#;rvz3PWmcea+Dxw9h6YC5k-49Dnjn`Z^u>+N~tmAoOalBUWf z#ZqL8rj>JBFrCDT(ZmC=OZuu$*{*;5J&feJe)7Y_f{Ww94qlVDt1vyi znf6DSte&$ZsaFD^dD^a6G@*@KjsD$qw5q!WmXa+i*uFXnb#jCHBD`sBz$08P!^!bX z5d7=z^cHA!YAdV_9N9!#%~2#08CKV$MLliuyk*k0RKe}92U3N!V2B)eg3Q2>3{$dn zh^}*~F%YY|n)_#ka<*2;8j{58tYWDQ@QGIxmNR?73-Al#ykJO_NymGV;?2{v9C`r) z>!{cc!h#&gFuOWFK2vz$mC0^_W#zqXRB8%t_J9TUSAc~YDB*x101Gob(gj9*1|Ips z%I6*imTgX8fnmJDfc1bCN5L1{_VTdsj$pWtvQsPyE&0KL1%^2FWDE;gDM;p(JwB4_ zdp)5E)fy174z?`Bf#viHAE!OAuIJ-d+xCsn%Gs0?Fj)HZm+n zPflD4Q|;c|T)+DB&p*DrhxE_YEN#U)7qMcoqjoP_oY!;ZY6p4$-R0dTvPyB3WtqHO zvESYhBwo_#NfQLtrZuh)3^S+5N+RU*c`AG%kPJs60H*0E92w5a%wPfA-EJ3@TDRMn zHl~qqWYi3|hC@ii8YKsez_GkUGuf2CUax2Bg={&SRdrP;YZ6P#d0IDxa&|nQS2O-h zeUcK>>2!?)p*8%nIF)&0|mA2Nt=0E-~m%0M->2bKg-| zd({i}d#WN}AzZDd2$Fp`6AFp!ob?K~_5zEM0;Z*1yp==|Jwa#i8doTsRT-@p0t<;%MTCu9ne#H`jJ zeyX+7$@x`&He24mzy9$1AHThN|9;)HHB_lx?7+KpvAVmj(h0k$nb%l+x@<(6tD0ct zz{8R+JP>w23l%HstQx+(zg;gUHACd#khget6pHqk%px4#MAn<>bhB>x1(iz;H`4~x z9fd#^WRmu$5RM;>mZNa9xm-qCLxwR;-codfa|hh1l;74+14c7hz084cXG*lno9UY0 z&cVt|GWD{UP1itt@vNN~W*8!^67scer7fdcL!V4>JL1}LPj zw)b}mzms_d2IJ2I>zl|r+7IE`b{JS#IP1s5SeW^2VEHg$9g%@WJioKIKA7{+z8=T! z;I|m?$eNI#u=e(x1UW8VNp03M$py*pCnY0eMB8-p)AidgpWlyaMtRUvfC#HrIFRn0 z9D8J85xG5m|LOxIjK2MJ2l)pR#vALvyL8qr-`o$mUbGVnxfFln2IdGyb5JxEO<8g@ z20^S)v6Is#kqAW4M}eAPXq3g%7m!XE>+#vuXcKNkrr`SZtL_=Ar!&j`{S^E`&{+M= zZND+yjKbmMve{fz!|TX0lQvnJmt|c@x$BeJcs{9>8P3pIH7n3GrLwfnN@_W<8dn2Q zBiTJj7iw8xnI*CCk~D_VXo<>6I%~xf9Zxh`;SGlAm4eZnmCxIm4zip`6g$DhMYI$> zd->vJTefj!i#iF+k}F5Q?gHycWDzL! z0>glXeLLZb1QuRd#tE!%e6kI9$lwkv&A}~L!kj@ws1g!bj#Y(ZUXd!h?=q`M1QzZV zZh^JcSwv)Eddq3#+6v@#J>&-6hk=DnRUTAWh*0-I|3GNhk|YgfrW+n;a6$v zMxs)0G_w@Mu#Sol5sJ4f?%Pja-oAO)57bnaveYuv!W)iL)|oHlO^=h)&BqU~e*69N z_4}LdY8Xh%b`SbkyX|dVcGEc!E3smXZ(UyAMnEYIc#tE!@=MfN8(+1cmZLjik&!@K#ZNI;_9YWhxT(_33=CYY=R)>+uW|`r5 zD$h$6tEMO55khP|l@&#iQS|{w$udRLyv~|peHF0H@2=IvLaofP6sz-j8f2KDOI}%1 z4g1AKgRu)s*EHp#a}iglT#x2iE0>72FJMVdCssUp`TFF7HhKO*VDWIxE1j3SZ2E~p z?f4AfUk4T#R`A`vkhRr8V!htO)%k9`9Rt?Y&Z?O`0pyIk?*!eQGgjZ^g+0Ipk6__h zSdLixqr!UN)K^v^VC@90(8f0{g@v!kVJt!bD~1CLw;u);*cMn|PDc&bR*pZ2bC+#9 zJ|Z4@BJ=8LU?FqD17Qknl53$J4!R@Kp}qhlBLZt@UJ@Qv$_L|I*PlLi=Nx0Om3*z9a=XU`%6IHi%ERWWb^7_$hu>cTuV8{OQ!^5t7$niTqUGD$ zvFWwbM?LY``tE)+id1Vlr&~&%%7LTkf$S`=I2mp>{YGTyhp4vmCMn>KvELm(snv=%GCKv z2Z&|mKw<%tAU~sX(Sg@}bo~0|QT$|v{~aCNp*VAKI8GO);oqE$gu+Z`2m zM@AM{ZG77i>TvK;KbdbGoiA$A|_A_L3j&7lR<_``^F({yPw@zg&zn9Bi()N?Qe8$Gkb2bx zVl~$NMkBI;INAczR~O4+bu>zj(>%@R74YsjZsyOQ! zMj?ANTP|~1h`Mt+Mcbfb1<};lyh59dPJvHbN?ZivGOOox#rV6_he z%O0*Ab_BK;$iZ$gU~SKU*XzS!wKbf{vKwD0XT zgeK#Pl;=TY7AozB0c$Iih;eviL}FpGm|%Ff#NC+L0qH~Z>Tn+rcLLEiDS-Ykl7NLm z?hZ}&czbmgEHsAHmCqNWL^F6st?qB$y?Jx9P+6&$kcCuW>@SFV?C2=S&sw8(_tS?z zpn}7j_cxni#$T|u|HXtnnY(F$4%&frkJnh zAHp3!W6`{6QHo`%+0>wr8N)nbffG1UP^CNwEH9<;5?>zs{h9H^T^NY9?0_>&`&qPQ zaty@Ka*C;oD&0$5^m@T~Po@pF)Q;MPkaIFm#iPY&DOgE#Tp+N{V=`q~6l((uL>46% zUjPt(+9w2RVBjJ!jO4!|-aJ5Nk3Y@|@1!S_YBhi%UhVd;BdzX0`U?4YSVxHc?Ah4YIqUeszEHbdbo((LpueDss zz=ADwY)hz}7#A+Oa9R8eVBy^p$tWQJ3tO%102WeQ`=GoR;p=!*#cQ@f$o{vWpY&Herr684#j-tw}xmhWxNo?k@8nM!Y#W?}Z?jCl&`SwCMIXt)@NsNayqUMEmT_ z6Qt7fMdbbgO%qTtIbO(4hGCc)>;lAYApvU|Sx>{Q<{}wxjgrG^b+m4+${I_V`8?oF z$j-8}lz&oB4H!WaR8_KIcaDNkmdRwQ<3J|mt}!B)WjGZCmI=~K>#jSKMgE!FA%Vec7>m$$7k)NWAh{-_VD2&e1160_s3o`epWTDOtrayVm5T8{S= zUJioO=!xg0oweObhj~e+*?B zY9dn}h=w51z$uAkb%U|NMyrCTd*eAck5wvp^C}IW@VHm0^dNQ2<*`4%(KZOIm+ibA zy0T>mnS?fbJ&Qqp%7l`6h-Ut2Dlo{$x|`}pb8`%6DB zmEaS?)>7_4R;*kdhkE73XtVCVxdwj_mJr@Ws{VP33dV|dT4gA|*i`jk5#EhoWR0o4{@Fy0u~qo7OAr;f)<*> zBmj$;=5h3WU_FeWzEB3p*p*ZBOVcQ3Dwf2MYJ=#wm`E!><(==Hbos_We8H)msQ)HRg~* z4k=ikj-3%&Om7x71?pY7LY_+Ae|veqNw$`&f~wP0&#o_AYI{P(1XZXeo3OgMRWECK znq>i4#db-WH6!7*ooE#vXUk9+bUqI(mf>|{4OtGLuo{tdBs_|Qo7KfIum~^a^JEj^ zS@nXZn=~a!oS_a1g6IzT{i!UteY!x)_SB>oBvw8@$c+8&)J$a3gDfLzps>_D;7K(M zfo1bFc#yO=5mb1SE=A)qmCwUPp{!mIoY)vlBt?t9moHwo6`j>tFrKILId39%4mkK% z0qcpW!2LrAGbCUUO1L`|)>FVDS4DO@7bx%ZyTIDsbpmVe+v0En@$4>~SM9y&LC-}f&NSI-C5Hi!i;FjV5p@sMY*xdVOmvB~8siS|h+pYfN|Os;)= zaxti`!;$_CB*4G=0{W`kOs)d9Ll`THJzk2Y^RiG6 z)ZM8<8uH-FqG*_`%IA4gHw<`jR!-JAjp`*Tl4hDpFqrV>?K~^l^5&FY&uiC7@`O&H znn9E@`E42&%S$ZnExvw%p!3%a=mD4K+AgqmNQLbtgs|Y|yTCfkDI$3cchbuK*_j^nJZ;D&MD|$79R=LKO%tNR4y6kWSbM}e2&+ez5C)8zB)RG2 z6;{B)QM}=R-r63sZ%+21w!PX+0_ z02VnC_hb_PmHpCUvMRYk%JTN~=F??AtJrl#&r0HCQWN>uiLb3EH;q>09+pJI5<>`J ztrx>#hAnxDAdn%{X zlxeosu)28~o)^UIpf*W1!;whyp_bpe(Y4*dWiuRJHtkh8i)3>Fx$Dg^Bx1Q4)3kVd zIkH|B(}QxMUiZ&ZBF7m570<~a>?ojCzFdb=SZO$(78y=c4W5^PIyR_4Tq(4!nZ3A| zVg`avTNX|8v;}4dz$5@|#}hh-Z*oyKb=}lWUgvpW7M1Yfy#Sw+c}1>d4AX|sF~k`Z zd?%hA0~SIS1}wA}6Igg#sKdaz+5r}+sGdVrI6^f$A!a;feuq~mU6kw&K2u>;@LE_T z+XGh69>~NZ0V@W;da`H(iCB(_LLbRx!*}2h*->BU)^QNUu+X7hgu&H$(1#_KB7sc+ z*0bA_;r2XKA%l*=(}=gy9cS|)sY*>5&3zkK<8 z{qEi6dYMeN+&%D@+90q(y+E=q#EUNYiRQ#%_ufu(Zw|)=hEq*Di9nO`7sZau1d`3> zauaS2Cj*n0HAe4sVi!6r)*0DiYL#ZP?hJ^jR(LvUwVG>i3L8_~9~`xs&E^nNMyo>z zya)XA1&FZm%mDC6ys808g!HV;6eatb$(=E0gA|mB_be|uS^0DYXAP4Qm%J_-MsU zF*Qgt-jJ_D&-~51TZl5fdj-Jyc)xCe{HdymD7;Y+XN;GZ^NElP+D#~;c`gZV@9&SU6rzNWwg75pUFYw~U&j%L9E3kuTB5h)so?rxx^++ZJtVeXg1xYQE zz(NzzKMbr8kyk~`1H#tN9bv?RVJAHT$De(5%nv#j;`|h_;wbVCSJ*<(svH2;1qLj< zQu1?vWxw>NqV6b$^)#@2`@ljE#K?s&)Wm^>3=t9o7BUQ2FyQH=V3?=(mw<&JNCwuy zrI9}PytEa%9f}q<&`RsHQHzG`8tYa$mC2Ub;FU+OrL(Qg^zQD{^=DWUeb;X$fhB3V z5-e4M@a}QV^{~K~V(><^gR(meo#pQuqjfWr=5#v`OVKGg2+?>m0HG;+#&13rRo=1; z*1CXd2nE_ zJjGJ+M7+lvps+w%f$E}Z3SKkMQj`*ng?#q115e%t7N8C;0IYoF#Qy3(P~eAvg|i84 zuaU%IT-iuMT!HI1!l2?+NMkwczoU57Hm1KdGO+N;K`iMH`&_~Gd%z+jN$v6qpCi1o z4FQYDD;rpkqr)34dPu+mP>@~13dStlDFjB54E2IWcDQl_-ayA5P!Nl}M#o95GY7N| zXa1Ajx81N5p{oRjFQPrM+Tfc?!>~u{YyV@deXxBb?M^uH!IPd`fpx4z$U`k5R({&8 zS0FawiqW-Hs!--H&e}O9HOMw2x0g3>uiw6ZciM+=(kQY>TRAUeDHUC%NuycSN<~fIIv-wJe(k;ls^i;UH?b@{>)k#KBbs&SOcsy}y9Nw&x{!{_N zOSMHGnx%$^O_(QKjz;0>IvkEHA;J#CN>=A{eISnK zQ%+By^5~!}mJK-v(JKgI<$6j_&!zxYgF<=$(JjpcV>K1Fc!`oEZNMp&oEKa|&|e(K z+AbfJ&&fPX+l!w=C+DwUp2U=guotTXuOyn5V=pnmM2x263!#6(4aL4Nf_*=CVLPCB z6+Wf+xb4WtLT9&kAdcBvI~ffKesG@eug5{Q`;jMCQP^GUO8^#v7Qq)psM-@)o~Mlj zEZij|nqP6)ty*m}}+drsYXQgDoP z3ViV-{I%MNN-^ZEN9>*t%lH+Du40ax_}zjkpPg0A>^QTW-oJbE>CNZsHy`gKDYtvs z${4+pr)W<%E5p&)?FC-7J?)Y#R=bxUp&eHw>1V8bFW<8>1?XI`)1g!2WNT3cs~1Gp zq-iDZje1Jz5WL6nq|8b}ZQg($s*T7J9KZ-@sBj~^jiusVOJyWk%Wt3%k; zT2=#TK~*`XRuGxO%v~$b2C3D&UKkAAX@k=&MX{(vFIV9QgF#vpL4&EPoz}ntuUO4S zm&EE!t;{RA9ItYsAP6dJD=CvwEQzO-M9_0=iwjpYp0_}l*>NlhNY=Z0@g%S?`|H5k zUSZqez=4s0g?FlO0*kmT2hMc}SU9f8rTb8Kub%`Kf*z{K0EQISzZO_XW8v;#VkaI? zKo)^myX9u=0Y%-Bk;^tX-9o$t*ps%49|SC-9Tr*G3aJBzO$Z+Z7I|{e0Uq*$owcB0 z*tc(>a@MEt*;75igeMd<)_n+5H4Wb}@G4=r{S)^`sj*Zg)C0UrYvqVX%^|qEubZE0D6lF=dg-)>9nt3Zm`2X&9=Z z^1vjPOG7wKj>l<1;5bGzVZSQqvTUyxq$n>e`3|`}(TYOzEXztX&%;Pne6N2NSjb5O zdm30nR*IXUPKT(72F&_EPFug0*m9)%UN^ga0OCwK|jwyV&TBTJY}cH{A@c6tP>Q~ z!Xy>i+<8kL?Fv6D?q3qS>pZQZ4nG>z5<+&cDAe;y}t&#xttCMMtL}46i9G~bvW%9H6G9SL=pY~uHr>cn6FRo zPVd%><=idgZDRElp64r_3#GPbwid}12&|eW8MH+yrD(^ljT}gcnyhGrWaAd<^+8GZ z1r!M#Ho*W`>u|FP?1F>_NOMnR=6?624(AB3AxuA>a3)tCq-!ELg!9!nRV%pXbT@z zkHH7DQ;}(2x8;>(Nwj3eu#gb49|sm(eiT@6dHGcVJw~n2@Poj@5oHhP00u0CtLFgA zk%EP+xCg8)mGHz+WJhPXeLE2X7CPti1r`phI5J>U#UZM`90G99M$7?h@S5fbZCNgSwr55Hh zZUt65cHxTUCnEq>W9~hI?l7H-KA2}_vwYiE)KZZ2G`)EPw0igM)8*x2!0D-B-HdlU z#kQ;1PH6R+9{03EZChZO=~V-2jrRW^Yj47wHkR)H&%V!p(t~=?H2ecI(=hQ3Lq%6a zxP*}8MxdTTEUJ+}@>C?5At_~KLtNmBv1?1Mc>#CdQ+I!tzvt*U%HeQ~o$mW|gbo6P z%Z)!g&sWa^9pJ}Z4+-CT9ETp)uZ%eVa18?o#}^AfaR(l+b8)BE5j(c$2HqslrYA3v zi+B9!-~?J4w-?LB#mNCk?nu9?{OH>`2qibmWqX-#`~G5lh9=f86mtTh+!Pe`=@ggL@m=x%Zd4%U<7t4C!&2_w7cjBL*J zxJu12mlua8C*V4%sx=WDA>tY%J>mr}vK?OPt{!~jyWjm@_5AAOps`iC-CeHR?Q(kq!qx?NTT@9h&8caciP}Z|Wt6(VSXoIT33}W|Gw5T7 z=RH@G61m+qt-9$=G|%$@Qla2Y08$fS;(5R-R|CZzqC-!UTmuyr8g`TpFRRTf^G4A@ z1wgIHMjdP70zD0o2`qNu4SEFwSWE|tbgDkdRCbzI1h7bm(uky3MO=6!f%W59^3gJPjK@Lq@EOgdfi$RGrl`bxyL0IRn z+S8U{IFX_Ec>n;Pjt4Z&A0aCJ?h&LdSao%k&o9eJ!g>VS`{CdJ1G43!U6M-+zrEN% zZ4Pj3y!I7MQ@V;WtxKjV`z60?scjiT#DI5zgLXVJblsSQo~eTOrJ5+-3xp7;6@;!7 zf~N(*3S18z2mN&1M*q3KnktGNTP+2gQS=Y(g7d ziO6bJ3P$ltWYFq8un2g;j&YT+nE{J!=mpFRhHM{;Y~ire=ba5KHnC`gd>{wdy@^HC zNwcCs2CrymivZTG31RQ~!3GxXOQAj0mVpHE|8}W99Jr`b@2SicUS( z-NnUN)sG%mx|e6?FQ0t*>i2(q_397cIgQnc*)6Y41JoV#nXGvJ)NP18B)Rv*iqXA1 ze*5?;Cdb7wY3%Jy`E|=8AatAgCQv9gjoXXb($7(M z(9(JX1vqCXko$F&CPMsg3M_xIDEkXFfvybY^#!Dwo^INSXlKu8lmP|h{ zD{jd1a8gHa*hSqjc&(na{VAyGlH`UHAqYbV2`56DBN|S;mK#W5LD=F2*NZxa0M3*h z&u0Y@K9-e6tQ#DfPj4Vmu2Fz2_lyHOz+$v)&x9)`uwZ{Tu-L{R7TZ~)&QHI|R>~;( z70JpLg^lu!W^4ms5jrm&gKTS8V1)h!>unhy8yi?Sn}9Y9EJCA#4Q}sawD?XFi`j>i z1{N6EI)YVFNFeKoiVA6?o5Qe;a7viCVgU<`YF36_vUJA)77=;UT%w!7Z$0oHSQw~m zU=jD(mh!5nHy=K%ax#qi8#|BT1N$IX4CT{P-|IaTU1(GM_U(%ofByX+{|r+p4#u)O zQA^_z*R0KeP@rHXeln6g<>dV3>o?cX74%dI4A8Lp4P&wbU^!lXb8&iE zhJ34JqP1+ED^_Z;9!Iu5YJ##*41bEC^p}e=w7fb6k^Su83_3KRfbj5H8WMi9yeyae ze7CzSE!P_mv7j?nDX}bw2B(Q6CFaUsLV01bTFQzv0j<4QbPPBVu{N!mS zJJHt)X<$)GJHZcvB5d{QE>^K+EGqD$fc2Dm8wRkb3@qwvmIl@lnbANw6L_E%BN(Co zbjZ{N{MU@Po%6Dz!8r?901&dh40)CC{Q-1~_7urzE$jA-EXG6%f?M0YZrB~(!Prg~ z+VCxDcM}W#-P^ySGb@JKgx4FjaR-MaPcj(MMk?zmKGBD0OS<}~3kp<8hNGiAZrT&Ob%)xd8*6r$FQCL*SS}g9a95?0zp_+@Dx1qe`8# z1v90A0v7%Gr^kCScm;+dLb^36Hu63#f~Bv-(7KCPo*#s(G`CdO9*7HssF%=_#S3Rv{p-VLm6WML+;iA7%BgB!Fb z9i{u*?)N1K=g`70@}WkrI|m2r@q<}IS!_y5quPs>P=5UO#hX`uc=hVln>WudOQt6z zrExxtJLxQ=Ld{hLz0*T&WuT03WGk1~Z{9wKR(qxPD&fGv8rgO<33QNwtTw77Tmwvk zdKhX0y(88NwWecpbtU2SqVw(4!#X`Zh4#l#m4kQ%d&tN-IlMeQ-HglSO#3HtX^^h1$w$(~vuZMLf%pO8g!|>s-l9b1X3*!Om+%RlP-Azg6 zX1Q27JAV7+pI?0WXK=5+1P*P~R?Ad3o2gML6lRduE;j3G)JN%9fGn{Shg}#e2n~== zPS<7CGD1f$j-1E>g}$RJ})KWSR}B(7|v+MkGq*g)kf36B7WzXS*))T8dzvg!z2X} z1{SHN6Tl+ma;BU16?0_aXMgzM$jPtQo8TyYr`cp$D$9cSF!nDWJ%97&1vJQd^~WdI z&#zC{gVk=-Mjzzk01a4j?~R7(svQCemXup zy^tYy73v$INdxk*4lfQ{@F)3HTtZlvELG)KKSpQj=K)xG)shlLS((#1x=vW~{Vp7k zm#ajY@J6!Qu<%{wh?>Ldowc;Ux8nOVIlC&sbQGnYEEDRn3Np}J(E8$6Dm+dHg z5y|t&sFRSz6=Z|mTY-g##}mF0>0lAOqJgy)58^YxYw=$-8(7Fl$BGoU5gwSpB5Q9N z+eRFrRIJ8MolcVg7TIrRclsB_D`c$34HU7md@PFR**y%Ldq#zn;7t8?!02d9FWjXKl3l(z=eT%>R z618}L@#Z-=JQpg@)%~@v8J&3!V!&DlHHfK*Jygp1>wiG@AFzS_z4;ro1N+y%;6l%? zbqYU0_p)a=T-+D`1s2$Tov8jYea6qqo3Z*1t^O&2l4q!YK7dpREV@JOv%FG&`Tj-k zW*6&rerf`*&_w5{luYw}DW3$I+U_c#NEv!OLZc{bgVTHn9xYV{P$^o=OcbjwL0ISt zEy42!E#4XF9xBa|yr?r1b3GA0m7_*&-XD#0J8JQf0nAE0EQk$@4`~vi?QUS**RT+d z{(YQfe$#tn0gHC6_I5?4zYggcT*!_-0Bb8p5?vx>@`?@zv&02b=ppUqF%noL6K)F& z0a&9wz#``kWi(&_cbh?2*pFF*qvdO%Uhi;s`#bojLS=9&r#0 z3Os-+vYi-q{2=N-{#B+CWn7|Mteb;h1y~tO_5tfwC5tk$k21+J#_1Vt8koGI6R2*0 zg`tv-twF#d*v7OpRWt(@x@GbTpB5X*r@bB)*f+?Gs}xE5fCWYh^CYmE+d3A$#lRwn zMWk1-GaQhyiIt;(^|1h$-US;k1iYF}rY_eT*Dj=k!hwV%>^S>@?@#Y0c*SbhuAsN1S7W6wh%3qw+XI5vCKZ zw&ReG0v6bPfkk;(poQ-O3k=;NBY4FS;$fe$3zoMgp%M+SviSg{9zutNnTwPoyr4pX z1lI6Q|0-7{mGvyAVaw7p;tVX(m%1Y)oCB~hfihx|HX=ML#_=s5i%uaWfz=^^1y-Pi zcYK(t)S2xd3v1xFaf1R@7HOx(1{S=Cq!nf9x6GZ$8s0LSnmj^ugvfw}1@`NIio=IDz{)**T#ef0alVK~`#czSTIR}hsy%*vef<0hiU=VI3;e6`#pP1cwN-l| z3l;e2!1rDawa4p&7Xl$+uJ!0v zMCx24E_QlHv+BIi(YY2#@(zFx&IDT4K%aj>5Gj+~NnqU#r#p#-{_S(8P|DN}us+=j zEOc5LSYI&wt86SfE#MA!HFxyE&v=y?1{U4^nAN6lOK-;+4QvIgfQlLD@-hWn-TP0!0NGutUaF2mV*Tw>73l=#q0$Zm2J;} zRR*k^WV+1o5%kb*5Sm^&WO!5Lk4{%M-VQ9{&M+k+BsG94CTitz*_)xujl-E^PF;I8 zi`oaM5*Zr2zX31n{Nw`UqWWN9F307>sr5wk#n-p(!KhQn6+mAt%rUTX;EQ3`=|_Xe z!K>eO#P6nySe5f#{5)}9`)joSxhqdCG57h?QOo^N3M>c=!5%|a*eSHYIvhiv(bLV- z6j;!&;RmUBrL_Hal4ieul25?QL{+AVYAI9SU*%VmQ1$cEsRlARP{y$xT^}gRWfz`P zx3oYCH53s-I4BH)z~ddx3p5ub_(>~j)C!{5>&^SIUJ-M{DmYtBX8<0S0cP8g5ea-O z!XN(@urecn^{bC#7Mv*)%@I~;hbbcy+@>qwOXg#-lO1m0lo>N?9-k9zr=Fhq(PIIN z)vKyEqk%HzX3lR%j2CN%q zVQw*Wce+rAG%_ljkwO;R*uY9bKo|`ynpZTi*bU|xm4@`m(<8(M0cNrmWyFupF3-9T zu;86xKh)**Ch4a~jw%x^9QAr}`{3&Q^^>=6QeGWD+gx7w^?@-_^9#RKnWw*RvF{i< z=%y^q2g0M@A3=!n$^ z1##9B8xh}%fPInSAPI|$3y3SX1MB^rd%03O2VkR#g16MA%7BGu>@p%omSJA)1=i;b zV9}pEvJX2ETI>fFK{rNcQ4(2T6t2)gGS*}QYkrIBet%$5#7b|rvPUtg@E%W%#!435 z9gFt#(82x0!oa$lSa$;p4Do`U-_8UU*azwTKETR!CwiJ9X*95yUw15EG3^8EkgpHV zgd;txRL;l4^nI#*DVkTSk^ku6`sM2qjY7s=Tg8DU^QN>Hj0z3;s%#JQoA2UwMG`*xr4*)PzY2ENnks zEq$mc1Y$u?nTyQ^Ygn62k|M6_=b@{6JAI|BCYFUpcp(^>iR{Z=weGqr09I0$TDA_j za-xMDRio+QY#h1X0V?yaC5WI^@X_ z&&zUqJx+nuR@3nkAYy^@R99AVURBJ%P1;Lel_oCN0LX9#HxN|tu#h)qX_{3xwa^1z zX%Gd5ZUDS;dBD}c7=db96T!7Qs*P%>b#$RuYZL)gHVQ9MTEk#qH**m4kr5xGr10Yg z3xW{~%KOQ%g7)N$2z-Ax{~aE<2gSZ1jN(!}jUH_1JW5+wc)JL<0*~T(CZQ)O?#PnA-&w z9#4NKv2y7RI?HXqD$u~9Jglz`EZXRr!alZ}=rB954J`1d=pTm8Dutt2MfA2{#}?k< z4l^ixK+uDf6mRZq18e7O7+{2el^)*bVUdGgvB!leO4+RmRk%3ugMdXaiq0gaxJBhO zpp#UOqUGsgk{hDW=dfC-^h8HmUS6F)KY#rec=h7^aJ_zXe%)=wMk^`Fs?dj+34ibP z(b(tot71NH@^cKVtLw9I3alf221aA{6m)fhNmRQAQjgzfTj^3aH^jxhEc(M>N*7?;L*8zT z^q8{ce%FdIlXoZakIIUnvlLR)^tS9K;K3di`Nd4Uh%#!TVU8@)p>VZj1i7-{@c#-} z$eIiTT#>+{>!2xP91_8pBUTy2Vvk$BRSP14#U7pb_M=M5$2|`-m z1=f%OEXwu+E0x^g<)Q}dh~puAry`F_tc*Bwe0g$p{Tw1fu$L$6#l`XQ+T|R(A11(7al*FcAaj8@2B7g&^Dg-C>zEOH!N${vDJRV;pGX5BUoj7TJL7snkw5$p0488Va}(QBrsfK38Hb_L>-2 zS1^w`Wzu8RTLuHG*f9PL_M{~z<N|K2OF`GG8KZr3HxB#pKg@l$>uWOSQ>XM9z zrG>)4>5q!sL?~7|wV3BRLvVOO%j#Epx%sGQpu`9}vSU5scrXqXKVuh*0v1Nr9f;b# zM;U`GH*w#4U=hC2%@W_-Vq|!ky#kgQ?%2RWD_Tt5Wl#0?ehMfLi{=$gEOHV(Wmy7h z;Uj=Wli~jsSae=iWyi(3Ke7tgZk_+d1{SM!9IZ$OkjfTK7vjt$uTrD@JJ?gk<_lB( zBA|&^bohD?8s7WVta9ugH`(dj$E4X_@aEY%xe8HSES!jWTFAfKHy_1Rfri#)3vVEqKd^Cmu& zUw6_)t-Qt|A)1#{+Gmg$Cpng{v4?t(C7^R_-{dDE4u zAYds9XzreeCKOJB0HDR|O>nG2w>2uv`wq`Hq4`8FC)R2eG?u!7fMpxJ4Yv_L0Akt2 zTJFKqPkxo@QUIy<#QJ#uipJHQz@kY*8~L)#h{ME-851WmVr70f%_|yMU{pkiJuD*g ziZa{$)M)BNh$ z_1QB;D8@!zZm$N78bpCvHeaqFqJ5#$jD%W>kb_4jQxgLVFoiv=A2sYqXuv_jd{0GS^Sq4BUbqORa2}OXOC3m~()C|L9 zC3jWJ6*yhDIeQSgR;s!urjnTTK=2?I6asLygcelcG=)!Ls_E+--4JB#4NiZK1_*)frU0&Zr@8O7OrkD?aRC+I+UZ0ek@bgX~eN@ ze<2v{UJ<~W?MPS{SbM*i!L}xQ^@ysR9-CJm4J?)wZ|n{ai>+ltYl)kG3U)mW9f6;QBH8FZ}}No|N^+Y=XpktI3|VAX0A zu!z=vw}pi_9N7!3;n7`b0r)5ln;WuVP?5l*xkC-o-{L3i$v$Qj>$dwllR2UEk9(&f zK#@a)U;z%+Tx-kW4E%mzm)Nued3<w%=ZtRuc`mOAx8a`TU{;IqsWNmG{f*a(Pi|FWPy3=>xD5*^dkJ zdDza&Aj`|LtSCsEl9JTJlBTQGRJA-|qIOkPwgS%PbzsfFH6bEI^*d4>&GV{*-tM|# zH~{o?jFuOAd|W70bip%fkjwxOE;I&V2&5j}xL5!x+fIoU8G3QlsnVSoAS&ELDYT>s zg*`2zf`(a(OlwwmxJfCd(1LcWnC=-dHvslDvWV|CTF1aSpG0G05;MIt$7fp$VN36@ z*a6nvqrB)K3y^TPhxKv5qW!CM+5cb_T6V`oB9R3LY8^}~_O(K6%&UboulD1gN){&` z*#_282CSR#Gs70L3~BGDCDJk%c8T#WD@TM|5_b$Ju|4g35R1^m09LS7V|h94!>1GO zRXee}DnmlU)$#H5@yplGj}P*7bpt~J7kSsI>B?zI)6wrA6+UO*gTSjEiUt|>14liFGA75P^Umw6_#p6fg#lp{* zd?k_F?ffJX6n|MRE!G>TCWPL|i^T$v)o%L;TC&{D=a-B18sv6$38p~cXDanNP-`-A z2X1#YRl12QgjRQ@!ehJ*4v{ku!a7RBvJwTtKn{GJr+9 zZ~JvD1T3(lnzoc%v*8ept1t8;WqAkyIX;FB`WzoGt!eq<*~L;xqDFtOAljcvM z?&k3S)%G^5iEPXM|L-O2Kk=g6$WDN5*qbAQ#G}E-o};aRI!5sE!l*lV!He0-MFW#_ z-~9hmt6k%=vNX6l8STZ~=PV;C>bXUBL}ejksO#-mc0RcM^0I95Io_^H^GPYuAS6UN8Us@c zl2^%qL%>zY-TbS>gr9W|y~6zBgU^65f2x)xXw3N)}Rm^7U(0IPQfSZ%Y>9+s{sv~)^aVDUC*)!bDzR_8?nOGjyL`rSNr zPV4hv>*!%&k-q*&D_)qLWsaMSmrL!Ahn(hu1Hn8qfIi2R=l2#*uerct@jk~8n!{`u^8n zui&TxmF^hhwF4;Q>HX%NlAQNXLf&Sqb9_rsxC<*Ac@Yx)QKSQf02bC_Ei z-_UwB6`Pmngq4s2!dL=H1H@v^;wqr+@?uq3rT^hy6TCLZj(t)#^WVdd-Lm7s#_po+ z0ZyksaKbCSEXdQl;G~;^V(vddxuq?zE(&-Iv6PnU8er+z5Hh&om?)-$VEk9Xr zzCCYuyLD|)k5yjp5^bg+%WqGaKVhCtcGNuwjcj=<({28I8lx^?j26?qRzW*eNcPgV z1sZiGMjbPZt5#}ETCK``ag4U093oTLR$kxUzJGta-+%j^$*9i+dFxiKv%R|~PNeb~ zvfb|)Q7=g>XjLS=CBvWDb~5F#NDl_%H+77Boz@GJd^tqu&>D5SnWCsk^RFxHikIK5>%h-DYaI)Cic-+ueGzR1qAbCv~7R~fk`wm?0) zN)BSCDc~UW^7UUXaO$KRgO5~afd#7kys~srkkbB=m>IPjo%z+$C_<8+9w)mCyuyC* z0qe#moU-;K;CMJ22k7Y1f{NYG0?RlqOYPi5zyd|CDL>0@ya_C?ra_wpmgP}IP3uiz z<{kF}r-VnV-r4ZE@wBGeD6>(?BEP0UrQ6*sx!WM)@FluuB38P<^m~6w@9RqS9TAo78lBnd;-;WU7qkLeM!V zBxW-Ifg(PUc`})6%ZNexUTxl)?)BGSWca65oS@?N?!9vN`i$-OB(e77u$Z3;vq)XV z?~3E%ZjG2BWZ`fehPYRNsYPOHJ|D6?2$*WwZf(4VW3@?tP)bmDGZbJk4s3>kfrg{f zxG$HKq4cKzajDPf#2~R)QfHK+4@EnL=LNA!*;1^*`w6b&${<*+lC+@^X%-iK`TM_m zO9i!rU5|Y@Z?@R^LdNO5DB(Dp%WOaP*_N%8Wm%?Ka2?;4nk}bWtyej7xbNL?P^$h^ zDPa^@c>i5J*y$5TV0m0Cw=J^DxXae=91(#7EwTUSg6bo$?D?G6eiB%gNYVB@uyhl6 z2Ur*z>-<=AM4je^k{p~-11R6+aG<_84~}f+-A^kqXKm4b|0l#c0?SKO#Vcip)2=G^ zAaTd9`f?Ksi;V|qGlO@Z8{U7Tr_4WPz}6rC@n64(rEKx8QY^1g36#>g-M8)B^j;CY z=&jtNlFE9K$FiNv=C^m>ag!=zCJtt1C0IZ+b&=Sif@tHR%v*61P();tI_d+k!Wtwq@oJ#PY%Vr_gV9*y2t0Z$&SIH!+p73fA(9iz2GeP0c1M8E7 zyT#50U;&(H9$W-0JrDpEVfLkag>xAfIZ_5Yt3_o;J_ZDeHj$^`=Bae3;Qnqkf-Y5q z^I^8L2Uysrz*?OEOVR3DU}-x8EbPE1*OVCE@v<;YEN?lq3of>oqbR47hOY)n&)C*> zbmqj)Uh(4G0II%7{c6Izu^`*?z)}^8R%N!F#an3(Wcu@Cto+9Eof)?L`@j9?9~Elt zaAcS+Gp^Pax%|^xC)P8c@5`g17S9N+be2hGTbsA<y%cEhwRo+#0;Vw$ML!zPdXs1f2N@jhsm@r|X<4NCQ=Y{EqpwI$~ zjlPc!!j9AN)XJc{=~D*4KH=9Z%7{Nta0{M#SToy2T59^(@690l{rpLeBI9aK^4EF<4= zuk2K($9ADVX>FdqfvjzfmMyIRH-cVrCA?zBUuqRgWGV5(TjReL)=IG$Y0Y5K@1lch zmB~VpPnUr3O_mHr^;O)Fm;zBO51O2N1qTa$85LF-?#lUfNQRyNE9QAg!#?SA#feOf zKB7#x=~TxdX|U{|TcZ$%q#C`Dy8@pzSBlI%(*kFsd6HXlKqkF}zKY(Yxq8r@cT+UZ zF~mwEhK&rp9qljgfnxc-Io~8`Wp_=FXiC8pk53U6k<^`5N zi{-I1z{32%5?g9w+4P?VmV%bjrQ58JC+I$2r+~#%ja2iwTPMK!Fap;7$&bL|i}g8B z?wM2J@C9Jm!TGX(oR2fNC>mHTd~7em{r5kB6>@`r{_$UbwveV%>M|qc(B|xq4&F^XnZ!UvWo=K9cVESLhlxbn}Z(2`b~9-_f3- zx*jvTnLh00op@X!nSQBh&f{NEW7DbwHT}bB3iL zi5Dp8_`x6+8x2>f0-AozbIhoZ0YfyQ)A>^U>#0bUa^g?c8O*4F@{!{3IJVT zxoo-~SYifP*jde$<`q{vNq6O9Ig5kZbP==*D8I|4rh^@U#qKI#X(!7MEbWw?{(^Zq znuj{h4>T_WOEzvQ%dJkK3p?v=`hLU(Ehv~WHZx^@TOYBB`J+YU2cSn|i88NvP{TEwL=m5(i%Jr$NqjeRmbOQymal$?xK3-vFY9(8jK^IK}%enq+#lJy~ z;+30ovf;)t+x~E@lk@y@I*lkFt;w;&tCNyJJ(}iBxpDgctvri^NuPtI&N=NG6to<3 zrhm21#)Q#<5Y5A`532VI9@Yu9gbI=R!8$MY&}yhK_xfmSv0^9QIw$mySLI%P# zCr^3kiPrYu1XB-}?O2McbvqxA>fE5jhV@{$dt-_2k3W9@{g2=O`RDt* z-zd#TjUXVzn&fw{zrU3eeC*5HB@y8=(v9%of4r1A@c2Tn`wgmc*3#yLO}*S8S18C& zq}!`R2FHk^zc5oP4_l!mhjJ-^Wv`3`**f_5Uo&4vut z1`!sLEtUtvcnJ0I$neUf4iKk_F*&$c450u^HHrzd3`Wi1f0a8khBar*L>-Z5F>j)j zkp_Oabd>RAOOykei4p@KMAwEyuMl*zF%}74qJ@+ur8z(<#vfFILZd8OiRL{aigyxp zF!_byIe(XRLRlkJPL*=0IQ(=h>c?t9(NNhIpr`h+rLjs{$>tN|0(%)-_pHrdDbvZ= z!(Du<%z9X=6$AflY<|NQ;qu;@7BVUvRHi09cwoieT_yZnFPc;k>v;!$Mdmhckk^7M zUtE}0drvyc1=hKc5R3E;_Q3E-PO$q(0@Z=xs9bPD_)}n=l!2ZC%R!kl>()AhkzdV2 zPcGB0p}&=lV^;m;hKn2su)ai(FX?=BaYGZ4hcC-jBY*z}s)z^w{QdVo?>j_1^IC$q zuHVPQy2tey2Id16r@zSue4rIC5jB)D+k0|Q`}nZCD0Oi_l0l)jLX+7`y*#Gs3yNey zqPZ!+nw2^PWs{N!28MY?%ug%T5FZJjsCY+2`1FdCwb?QCw7Q<+VM+3e>=js(qQM;) z8;3ixQ280PgEf4#HRnj>i+nvJvv26tKANp1RaNUQ5<}J-(NIu=vmj8Vu?**hVMBx$ zxj}%k3cVPGgtbw!&0=OO*?f^HK)*1o{^s`fu!fk$N}_F$u2_kfJQ@S;>p`;cH6j%c zh@{6VE1!yEKdao?H40d`OLj;gTQ^Vs!N1~C1*|KO<>Q)p?E>rTaj8cE>ypw_i1l$L z=|C*A^#SV<`)OdMWpfEwD@QGQt}aI|TYbo4d4c7g@OAT3=0C3{M)Tk!I}a@Pz{;n1 zd~uWxO8#&jSSM{3^_&moOt~n-4k`BIIsJ?3(vpE)t?>iP1(_TlfrU}~e6yt9V* zncjT8ef{>=pTvYufBfx1gH%dep=entMKMtjP3~XwVV$ZiSGUHj=HPmV{fF2zd zFqDoB=txJ|u5=mAbjWp;N(ll;U};!Jmn>-{+8t$yTvv%&Enf;&?Ov{M`|AVQ_DB3H z=H)DtNy%09V?=~Fj@2Xf z@WEXuY@Y(l-z4iCuEh1?`5CdCyt^+F(_N%3u>752tUOeOggyldFO)g!hx+%CM1a1Z z@mEDghn@va%#)GVZ@9pc-D6_Cc#wS7L%pA=yng>3U_E`q!Fq}(+Og8874%r<@6KD( z?f(0FCfBaF>q}{g%pWV#jLNY3dc7BPXT^;aDNjeGfX?Tmes0xctY5F)uJc9I7o+m7 zS|p?stPHb+aZ^-Mq?;JZ6{mI(c~#{OIzg$uB;5%L_lViIbVx?szQ@bjb5U{2YN9l_ zk_6TmF;yM|TV<&&tP&B{hH`E~4y`m|pei-alWb$uVmAF4?Pvl~!dyUxc$A-R)sR(< zI8CQ6*xbrfYJ)u>Nqdq0IjD^RRxh_&1=7Ax0@xV1YDkZngxU3jUM>i7>3{kE{!Ucr z4*$cB!1^R*W!)+duxurb6?KZ*(^af%I{@n1knnn5SzIY#`Aca|x@p>r+%qQ~PJFzw z7~<%%UFTutPJxA;;gvUD^m$m9dd*-yUMcfMg_aeV^U-RhgOV0l6uHTw7pvk}lzQ0jc^Uc`X{V>&@ zPx5nqZ_xHG+u3cOzE=~ywkbxBK2?Av#-B6Q?hQ(<_#R%}?XogvS@h#Q!mU-WU8r+K zaz0}Q7x}38ZoICI64^N!uW49g&~k^>L8@#377~W|E{t!CuHp6lc~jhMs?|8W+VGH7 z!V0=msdQe$D;i(rhjeC_k@IVkW8;vJa5xlk)kLZUCwb#XO-g;VSKtkr;UY1qwIjfi zafP)Jm*!b1%r&Uxsb%JaUaC-kG3)#EmU$E)Kh~2XUdZ*5BEsrM+6GK&ppA<~Gq)VF z29sy^1M8w42HzaJvY*xu+Bnal(UDE(fc1+Dte=LI+4@>m`UXbaHXXCk!J`dt#OxdP ze&*%L4eUf54z4t?%qnip8I(d1GKg_sXmnAx0% z)=^Mu38r-?ZYunOK6iN%7W$(7rskA$V2)2c#8Y5>)RehPpQq8`fmgQo-!Bh0{Z`^e z0>WlHTYmrk?dj?J>l@X}-`~rP-fGsWtqS-~+1k8S-F$nhE%^Q)BQt9ZHomRr3XM^D z^GdqBlS!Cq?yCV`@hSBbNrm#6!^xLEf*HxO$}__r&!#^o9{J>j^+B%FMUG<+G~je6!0k2y<} zjY%HeLyHMLi^YsId$lv8PKQu+-Vz}gXC!)LT79hFVJmApw#NDW>j%~anZotJvV9xQE8S&( zP49-DnAtjDI_#80%i)zGmUA+7C9pKE&YNWbyXdJtT@T;%lI$t4oa{e#Hu%#&&w`-~W7jdwPEQ6KG9iy+Xg!DW%%U zE*{J*KW6fBEGgBbQH4ZwuNt)~w=@GuBV`2k?sDO)KsKaj??886Ee? zncwf3AULAIRH9Zc-dEPc8KSHme$&R3Iip;_3zHnXlsiA)qa_7cckg?N2}Rf-SHL}Z zLq0t}E>=Z5O6Dqz@9?@HmmrIULy_9gGGl2#Ix2Nhw*#o!L{K*IwTI0?GddHGX-N`~9&k6*xqwKskEOth6b};nfiIK|dj_SFW5>VZA(ez% z`5zY-cX86_>u}crJL_5o(W$6GX?iRan#}+f194hZJ+nHk<7?sl~WdT+#KkW8Ww-0*i%+}Q5`B00uXO^r=kvHJvA7Ed2WZ7N{df}9WXb~g>8VuMEoENe=s zfE~h{ANk+^BVJ)A!20M`CH)GlE2|3q0(gf`_CNzmnFW^M-C>0rX`jhq$yufE^jq`e z8MPYgPe5L5mvMOE)jfL z!5XdIyK~dqZ;Z!fbP;n#qQ*)?cts?cEwq=if+aa_;^fCvs<%uRU#5z1@x-d4X`YSjJFlB$pMxNDoAx6SZYG$S-8q0OKZ;=@U>&6jN{i*Z^qjDLaL|lYtj%RFR+@)M zr(UumH1j(fJ$~t z^z0kiK37PVhnr`LpgDom)TG_-0Tv`HhN3c!OOBmmI>&$_ngXJVr%;ELKRh<^va*DP zBUT635($>NL*bR^7^Jrqv!5ek;bINvS@iFvnTSnU1I(qCO_U-MbNwbuh3#HFof2Z< zSYfO(lE@+$6q6mUR*u`TaH|&s{fN3;+w3(9EDNNghvhC5a$YB|{J^qJKR*V3>b1_c z-|*DE8T_e?$ECZ>7uhM09*;M%vp)F0;a8cB*bxN)OGxAemT>ITK{~vSaP<*bsrGfi zYP-NXnhPuq_@he1&A(saKk52pdloy&JzZdF4M~)ObhGQ)i*~=;lPsdVbl9urk+{I( zVeK5S&=t$$|H4u;%YtJC5XEo6oq0H{%xnrx$MC}Zo4OeV~Baj-xf2cwwi zVw*1lEP9Q;;2Ne<%Q_thS6w586-u!+2R4Zn^tc_HXKAGvrSn(U1It+?Jmj#@kRA+R zxlWd+Ajj3MD>utOB0zMa3RovmNA&AcC2Os3r55_4^Qz9*uGRm~dG+YFXi&{u46tlK z=yHjzLoD^)`Tp*`D}qAIlF4gz=SaC&k6M6f4-d$>+MiM{E%@Ydz@U8SjzbiINFFv3 zbp}}ayVBD&bUMyeW}UXsx={lDNW1TvQx$>H{%SBuJ&)Hp4VNnJfwJX^;ELQSI@7d zI&h<5dLGOar~68g`aU|!1PS7^+Bm+UCI{~aNbdL2_3HWg^?7qwN+ql#Y(tAPrhlbO5m3CMZIZGy;UdN+u31P#S_c9}TiDt&QVkqoK zvBW%&OD7E*I{0RQDzO-e-jwCTd3Hq*K~VS$K8p-nyiK7ZBk!Ke83)TcSAwp-#D&Nj z`V=3LsC)U_HBI?2d-&V6rq!Uf!=62~Xkv5GAd|_rpNL%SZp}_LARJHypYqSd56}6v1$G4p`=BQMvhqhrfozzZy`es`lm4Xkr>FD$Sg z0M_H8(WK4jOJOK6+1=fqn&S%U^3mhNGS^Qu1}mHoqX9tiSzF0{5aB8kHrzzNm2X~_ zL1Ti0MK6ci0$>^cs?ZbJC=0+iScHTKx#BxXn8(8q9ad?9=sZ*FP{dpw&erAPj%LU> zRPuPfM}d$5SnsbuOq#wkl|rg>rWFE2kQMHvm=JQ&Hb$Cjk#z6SU?yB+5GX&(B5jy= z6QxnDMM+M6Ldq=PS&yZfkmFL#EE29|p>aq{cZvw}nanKFZ6va}B@WfHDgTadZkhzW zk20YNaVTM9DHi!xY#^2ZiwqW)?2+kPEx#h6!q0NplmMZ@H#5`IB z_oc7tx>Gu+igBEX&=N{+cE`PPuw~EbUMg4kSLrK&<#bR_+v8)?gtNfn@OofjJv-;? z8ej>X{Jm(75#mSns80=|>SZj7ym6w15)YnwR-}2)$wcvpOitK%&;rX1@GsEHcAn5# z#t+GS3oOhBEY~!!Bpl><(@ylj*2810Q=Lvp6p_H9cYA!&{?e>7V1Y)!qdfU5kWkrI z<^)ILZrD%qB?=h`9h#|?s7}9s-?WB{!NN3L38Z3_r>I7k_1<8<9@8pDfVHd6=5wY5 z5^~n^;dGsCl*nQ+ZsJ{%U~ja;x@%>`bt-O{l!L=1z?zb+myr{uoqJLGi!v(5K#LmY z(VXnM080o&`kZN~Ql%)s!h0(tSC}V8Eva6d&4v>b5{?b9T6xN$7me(!m63G32o{os zLawlU!5O)cW~zKRb#O(!vs6d}D{FGthgj%0gjm5KC^Uc5IVvBou7{P42R+6~1*|Jf zO_I3AjB@YTPgk`+Qn>nHPMZDWd-}Kbr}_lv%*Tt4BWXA?@7uWW2e5KJ59|L7SeOGW zTbbsGSaE7GpN`_yaw@Z`t{roo26cY-39WEL9ol znjcthm%o=Fk(P+AFx*k{Qj|Uk&%5%joV$5=>`@9hNF-`4*4K97c(ZK{Br0D%8a0FC zszE#!{u`~WbP3H+zyjtLgF?H9yPc!9itG6|-_O?8RUD(~7QZN;UG!IdX*4>kZacA6 zf1YWP0(&I@G{Bn5fI*s&-tV6!DBMp&e$>dX;&J&8Y>~qQEav8rX(w$yq_x#pYI9g8 zG}S(n(j1Es5=!U3%n&L~)`GK{u*`&nCh1N;ECi;D5xqzGkys z;}T({G-}V~Rw%Fr)a9g5xtr#Fwdz#5ic$`Uqr);{qPrl`#2CR?ZqUe1hP(aSGrW>s z$Xvkih?dB&#O-<(wYvZdFK`O2;*}lc&w@WPS;Ut$#G!}zv?Rgku7so`#ibdzzV&r<{f!xTML+Nihtu|aD`x~C}W&CDWqaPe#}LGJv>UAt{3 z7qjWNr`Jj*83(-||mTEh$_IU;i43~6^HTQYOCB!ttbuzebJEL)6iYhIa(T$RF z&(3G0+@aAtG0!r>KC`A62MN4%bq@tta0==BtR_y_e4bzpG}o)w3&~_Vjcaj3!Bt%Z zqXdJ}->(6&%z%2+(j8ur!Qw1imO_{90*d$c%WtP6P(Q?iSK-RC%5KjB3yW}c0a&`z z@h8BtCCSHrO1d`#&yS-au2OQk#BrpD?lpen{yzt-!v!9T{^=-SsW{L<)5*EZgX;m- z8De?qR5$}KJ%05(vE;Sg9a-c?TO!8QA)r0<+M@;*r%wC%L>n=eUG68ZyQ?03+-U0t z|5q#*+?=yRo0*q(?3SDKc9B8SL=TtmPj_Rq;f*H3ssc(Xn)hX@0)h&?sA@s#@sTBe zi3d#*4AG(Ncs6@Msm%C@O?8wdFybeT&~8OqN9qV^)I#Afy&Oz?QRZJ1O4e7~($C4nV;+Qs9$@)%x`3@b|KhMkb7 zGnQ=Dn&m=@ZEQ@46pLJYMsy9*yJ1{5J+bE5QCWI#m8E)`t`U|VMDMXqku&oZ znUAr2pe^f5J4unVQMe1IhympxN75=;PzARJE6Ka3>P!uS(=RAU|8dlX9&4F7x&SA8 zsAqMF`Eyv-sk%NSygb6(=Gf)7;ppa&K~P1?idW|vRz3QIHg;~TU`+E$F7(=Y4(iC{ zG)A$&64UMZ&gibN^MeXGa9k{H9$;Y>SRC1F4!a|IMO2+R{&aUg@mv!UEBhCY5hG)x9c@QHXK|8 z76;nH@&QZ5f>)Xj2i2@4J0j3d(X_xipKQmpBJ{i+IOJ-MRM_Z(#~xh?EZvV-nE}=c zQ}G_VOD&T9L19h<>oT(rr_NmF5^EXxBua4WY~|p*DKa?#yI8iYw7pk;nJRtd`Rw(Q z@HK*rGrW4SM~^}8;pXdYFhaJ8C-e4Rt<}b@0OdLY?;gE5^JDlhu2Orv8?91xVI_5# zdZ5KkJ=drepNR;E{iO6;IcV&1tTHBZ%_hL&umrG7P-kWZmdxatEDqMZv0`#*A?T0s zu!L9pN;n$jo-cQP^OezzWz#WL%HyJ(6J@n5I9BR(86cY=jXIZH@57ODGO z;=vL;9M%L!wFwdj!c3M1^E1LlMia_^VU1%}<7$G&(R?zWcas3CkDHYn&_yQKL=Vew z`mUs~qQ-rPRYEh2a3D*BSg?Xx{GC$_;UEBB1--|={arX^G35q<9QiZr_6R#3oOV3^ z#e*v;TfcsV5c_OYxB=8LB=pp$DPUc#_!MBN1+lAtVLQaK^tv1lUIx}mEFzL?I`7Wj z>tpGe(7j8*x=_qxU}bqF<^anPL^=8H%fM1udQTILAEtrjPpF@%gVN$$r_RWpQX#Il ztsgJ03kiSB?Z0UcmfWbfiB23fIgwX9JIahl%P%)eDh^Oe4)bBLT$E_>wb(82$r)ma zgXQvy-kiZAUwM1FFDF?2&n*BJhM1CDqeo{MDx-V!yE7ivw6YDy6!4YE&660CfyyyF zEEKB!@#fG{W=c@Ej*lta!SORFeSdzhgvc0>xo<<6N4Q48P$dwgYkOGDh^}=lz*?^* zVI^}3$zTy~Ca8es48WouyHs13NpYdX8cyhaiqY6zfQ43`#Y3)7Bn#yK3utBsIB1DB zu_0VFtO8qb%LIeMDG7WfB0S`=1{@XA+{fI6Xk}kcN|`mKw0&qBss`ZLnO)b7l<>Uu zRmX!m5EMAM{q!Pgd537Kdgn)D*9@*X0t@pI>k@bzO0jic)ISGS>If{0s~|#_HUp~* z9h7_4$Kv4vOLndb{t~ZjRET|?wBsx;IMi~}NIEP$3oOqHq01(F$^n)~*~as#DEveB zue4II*cFzuk^ehUm4nbjsPVSP!l8R@R17(E7dYm6-@*J;s@T&<6w;@rspWol8gCkws5;(XTXG?0kwm-sUXJ7 zSfO5!PNRohJIs=F{<(&nCLuRACE17Jsr%k$iDA&+j$9=Y0GbG^LQ=KOjQ ziw=0!Wyg8fzrwtqxAv}lZNAjxi!;nRypW5c7fm8U;=)9^Ed5(v$v~B6nb8udBws@^ zF_ak&MW$zMX<^mv)#Xnu9+t4mcBqkqmsBCB-Mu~U$FWr)G14I-Tp_3&BxU4ks*O;5 zUfsYPnSe^>JDWfwz~K54u1mJdf?~2iL5KAg$=p$Sb|~1XB7U#HxV!!SnX2ewC zFo3p`Q8n2h1u5pcz2FK*FH_3SIw64{N`||w=|4&uYagco8)Tst4FMKwd{HeRy7u`9 zVZ3Ct$}ot079nAGUXtunmZ8v6bu*$ETCS2}!+dRGTKMtfT#17P;bsZeEW6^@rygY} z?6Nn&M`JlaT&x8ai$%|c@v5M*!4%Lsm`Jfm+6`7RgrL{E{lu}l9$5a_%obS6zFz9g zF39>6Sh|O;cCTDs*&-Z^C%ZcXEFXY=V#&$H(8(0i1O3iST8@AftgIa)LjjmtJ-iGo ze-2&~>v}sB>8KuAm%?UU>Zfoc^t#Z#%1b~$u}*IR&OvU<;PUzh*o{q!J~-{h_6aOg%ap)Pyh_s;vP{0 z`So~fMD&IbG@MGqWEi!HIyj7F9zAN&P!v~Hp%o&ld?r5}3X>=rlr*{^3pHq0272LU zWhZn#CbiBo;erB1;-80KDiLB8&Dg zN*pX9mN1LwpnZmvoL%kN+v;H2zKR{dPtg?cE)ij| zmZb);Op?7Kz}m&7j{`-8dxAtMBi!+4s*XcZFvt_t0v$$W_|6&#wJ>y}b7Tso#Awnj zO<pZ)-+4<|l!IzERnC*y9rH##GBKX_Qa z?z)023oNamQaUJi_3j+6w10IiurOb+SU+zOuq?4`(p|76N5>9WS4D&WtHkn;GCY2A zKLDXXqns@#?s8qPV|TLSfoDbJxc=y3E&44Wz_R*VakQ+5rC`MYrikSNO9!mi_o~Mp zegNw>cqIJG<;UY9>u}+uFEd8#(xM=h>o*uJ!%))@LR8*2L$W!h*QhvW$Eo*h#KG#t z#`kaU#r!-N#QO3tHc262O5|8UkDHwScl1xr*1{>Qk?NXGHBSqw!2+cwv$f{6&H~RS z)*|nZJ>Q4JE)ZiSaJQwQOofn;tot*}x?^}MrO&1vmUvh#qCoMnU=BVR5#L$@zE%ec zQGWw&u*qyhF|@1`LM)S;9|N>3G$tgJADv`pBHL<^QJ1^6x4gl{J0a#x@0oK=}>GB&( zD(obOWm%>5SE`lFp=j26M=qUCYBsI_mVNtLV5tD=3IkXXu_7=Dv4Vq_Wj1nNJ6bv# z{M-(}`U!GrVEIz-=ZYw`^!!noMTFU(wj_I0Jr$Ih?@w1dacI{(j&Y#d^Gid^h2oFe zMN2GCI?E5QqleYJx$U8-dTgM$obF%mvXuTMN(0hcw{=qBT{6_`uqYwn{#oWxNJK~i z-K-Hhz``G^ukx^_QY{Tw>NiV-@TX@o(5p6o$TqM0v230MlytKSr{fVhZkhbf5@9Tc z^AjsD7P@u!EaRTvE9E&7gjqR^6O7)Wypo^WJilYoRz~{wQASu5-n6P6nW2hORF(xt zqE8^gYYB$XG|W*dAZkaq(4vW-DBOob)3KpO+0M|ELT98FIuEcCB3e)3Sgz_XGK(-J z(=!HLi3)L~76}2C4EAC>SS97|A4L8Y8*nAUcUc*gb3xRmA|3fs8d%u%z|zQaPw@Dv zf#q!F;M6>mZ`3$T-MrQ*`BKs=Pj3;OK{{tC->+f++up2H+QuKezRtLu9G0saT^S1^ zHi0f44zO0yB^-V>*j}6E)sa~3D|mGVST<-*`*`I-%00BgEtQOq-f(;@=f4|W?ApJgtOMOt5!S6^XZxhy*sW}CHBqA(I*ge7gl~kQX^AL7bnanmGDC=tZ-XV^MZVu&5}DpVOA705+CuVF8lpv)v0u}Wu6!il-sl&q|u21%b}>NX2=#=;HoAeo(xho4f#g1VTQ}{aB)Qbol3&6!*trvSV6(z&h#o9=T&C=l*ch#K8&~ zR}-%4N!+8}E#5`Cz3O(?m7FqX}G-_y33AtnVAldhIwOk{~&Jo@fVs+_pHRL4^ zv|00sB)X`omh$KISRAaH$zo9^lo)VF1`UveqF+>KD>+_}c_&-pGJmOPRj1{$k5X2 zLdx9>tjnf5hDMBXHx7(G7FxT^@&@zz+r+GosG`Csrc675tIA|SAwRG%7g+LBGU=vS zC;Y3=%VmI7sSyqJV?muCBE)OVYoLIfdznTJn|-;2#wcZG_e2qc11vNE$>EkR4;i`{ zNGem}!Ggr|z>5j)theE059pB>(rJN^kTSptBtYe!O^5t$Krik%!=U`4FbNv*3> z@NBK0b;02hap{GSP_Ux({Z|r8^|G?LdG}Y16OfDDdRoV7VyUc^ZK=wUbrY?56z^_v zwL>hM$nq%PU0}%sc1bJqBB1sT8iWa^(Z@HFYk(-5-3@8BVaWU zOqCjiUZO)s2=TG@oynphqXTy|ZQjtOAv0Tt(@NCR{eByYdURJchKtpyi7Y|Z2Wt}$ zyC%wO0j&&C9?=}M$`UFP2?D7SNi7arWobYJrC{2Jg89@lc1}jn=ef$E!@QQvj%r^9Q4!sO4&bq+*AO!UQ zi;X7M*{;Z1h{fLTTj^pPJ1@n(>(eeN^bISA?c+&%%#j-sUI3O?p5*+Wq`VkAcyT9V aApU>&(X71Uy@YB200007#f6 diff --git a/packages/mask/content-script/resources/maskFilledIcon.png b/packages/mask/content-script/resources/maskFilledIcon.png deleted file mode 100644 index a2f6658623097af0257e9b750e01eec6b08174d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5456 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 diff --git a/packages/mask/content-script/resources/tool-icon/encryptedmsg.png b/packages/mask/content-script/resources/tool-icon/encryptedmsg.png deleted file mode 100644 index 2d0432199d1e103e752f9d6df8a4c32a601284b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 476 zcmeAS@N?(olHy`uVBq!ia0vp^jzH|d!3-peN?%<9QWFAvLR^9L_S5fyXv>NBJI{Pr zfByaU{xPm2nUvE40e&exsKr5E-f4y|y>&?gCuRQo>!_jwZ z4!vD*;0;jk_R}D9fX;ZpR8|LclR`<5Uob<1!~TB)3ilNZ)+aRFFUYT--@ktU`-X=7 z_u}_2c>iwSIj#lHK()6$T^vIyZoR#5obQkWk85DMLZd*{dy6mU{{NRv5`4hQqpZx* zy;D2CCS_*Kx_t34E@q9(d;3`R{oj9*o|CuD`or2=(#j`3E*4-8tn&Q9+2GXm!E@cI z^Q4dY*S_I^bk3G|`t(nWU{GsYuL4?QCGn#_~fq+t@Y(0|?kw>%ws&(aM&xuSsHWbkzLb6Mw< G&;$TOIrM}8 diff --git a/packages/mask/content-script/resources/tool-icon/markets.png b/packages/mask/content-script/resources/tool-icon/markets.png deleted file mode 100644 index 229024ab5747766877406dda1c71396905eb008e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 583 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!HU{{FxB_X0T}GnkJs5VIGHkaH z05MEJ?DYMOo6fj=N-h(gJhZZn*aa* z-~U$Ghd*DGFNWki=#9JE*!64)P^Hn8_#>Y$9{qgL=t@HMk~~Evh>A;s z^xW?`x$3c5p~JM;Pd^zW%TF~FS0|idcy;kVYg4Mrskl`x+&+wsU0Mp4nHcOs9C#eK zJs$Y7?wQTNUFH+VRg>A6?y!o1b5B+y)2aO$3?fkq?VqIO7*(Pgg6dfO>?g_zFf3y% zjb`Au%3yz}$LcJ@l@z8YLJmJ~@A2tj;JP?PmB;s@$gF$EBz!l;2s{4lTz2D+m3=~} zn7fn9X_3`jvuh`uznEpWF-AjCIYs71{*n#T>@Sy!e2K{v77SGiv?^C;gw@*AU(YD~(Mz?LB#pTZL|MmIn-LmVy4RsXKI$a(+e!T;H#|H*m(*`oP3 zH{2T=_q0?0^5Fb=NdM7}^Po!m;=TR4SpAAk{+dziDk}SFME0{%`@3KMt5*N>;Qf+M z|M%+kvQYTShu$n8_(wYWSU~u-Q}(x9_qkoSut`Kg zRCr$P&sB5VKoo{yCzb8kVNRL(EQrj^6lSI}Gc*0}oK4lqBqJrNulJqN&8N|$UC-gk zd;^`+(}R7!CIKZF4gof%_A5<&-xdNZ(%to+K)WxXv}FMH1f{(z;1&pU42Q#&MH9A| zl?I_joPwv^9lg^O|EvH|W<>#>5Zu%bS69!x#b!_`M63z`<_wN;b?9i|v>9dCQbwF0 zkz(SZ0OSvj2^cwZIB>T2d;{VJ_7o7H+xaTZ=cxcb;#ddsyT8Tj2zXC${&NkYO#i@c z69H&{GZ1*4)E6j_$DDH~!t2ME?-58-n*J3+U@orbaysOS0C(=Dt8)lE;F#ZDILEPN z0pKNW#SmbrsHPPjG76wRG9uXsjQ!xEUR&Te7yTwtmMUt-IzfOfBoax60NF5#^&|yz zQvl_Ywm@s?)#RtoUt(VwW8c`Q3R%r63+gCCNGFZ*n|qV@OFa(2ZY!%(wek6h*=I5M z7sQ^=PRx(jre3@(iJXChz1!{J>K?Fa9*}?pBp?9^NI(J-kiZ4^z(tR3m)ruEJ@#F> kdd(3yw%7F=H}?Uu9dJ86EKqJHjQ{`u07*qoM6N<$f=GDxO8@`> diff --git a/packages/mask/content-script/resources/tool-icon/swap.png b/packages/mask/content-script/resources/tool-icon/swap.png deleted file mode 100644 index 3ce3ba44ed2655b635eacd4e4ce3a4783ea5a299..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 915 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!ZUp#*xB}__XBhr(WB7ka;{Ps@ z|Az(t@8ti#N96w=-v6r@{_kh_zlq`hPVxW8#r`)i{NEw^f1A+%E&Tsy0;PohAL9AH zmHYo;$^Sb=|L+v}ze^Nk0+0<3NCe0h11l5$e@y28eo>GWK&fp!V2was z!vFs>{J$vvf1k+zBO?Fz3V_5R#_j+!fQnavjRKl;5a>dPX(Aw9d!+yG7x=$}@BbFL z|674(O8o!taB1qd8$iE#lmz(&GqCcBO35jz3G=XUDT*_x@CmZYh)T$|DdD(Q^vSg>7H(rH`4iW7@4i;f^D#?N{P8s=VF49E2~n2N=BHr{3`|O% zE{-7;j7zVFCVjRNVR@k0xBm4pz3meZKQS`?{4YFY*<=~bOMmyLpXXq1JA2~AjUPu; z?)&)lxc@)OVPtA{((dFJ5u2%trmRxw{`+{BiBy!~<3!23r=H!NR4TZc zv;D-?wk=O;nYG<=JN}dhB;lU;G`6|ZKvw9;RWS-Qzrrv)FFt@-WclVj@~7jiFf zUwNYUuvV*SDl6~f4RHY;4Ut}(bKbdDR{@T(e%M?O&kA_?0PtK@r#M5L5m$56@j_`ZUpU zTD68+craIu=&AHZ7bo9~M!n1Y9hLe5@9k;xKPXrBbx)C)@7e^J&sA}j-NK*K<_EQ$ znWeAs#-yi+Yx#_=PtLDuEr>IJziLX7Lf|K>)JXB>{aoJ16!kS?nO^*=U;K3To6ARM zPpRM9d$n%zDXs>OSm!#`*z2ogCOo|ByUHx&&wnMCKl6FFH_U0uv3M*XZn*DGVD62D z(QWd@Z-Q^%zaPK!_EGzPCwKLKJTBX8>mZT5Ao@#X$^>_w83_%R8gkCZ!tY+XEUfb` zX#MBvhO2K{kAF(uks|)@@aweW?_@vT7HXS)R*&h?u6K_ejz0+&cTC&9`0o4b|L-n; cBPq7N@ZkJS4i2vbfGLo{)78&qol`;+0HcAzDF6Tf diff --git a/packages/mask/content-script/resources/tool-icon/token.png b/packages/mask/content-script/resources/tool-icon/token.png deleted file mode 100644 index c7f6d38f8a66f4ec409f99713c13cb8387ed0278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1696 zcmV;R24DG!P)$-MPE--o4$OnP5)U z|Ij}F;XOSQ)ubjhsYy+0l3%1_bi8zlZjoM5lXQ<>xA;LokEo-?K+*r%$1z=_EA8Ls z|C<$Vg(4*dH(E$WY%E>lUP%1sD~{!`XgP)Y>?1O8f01Da?T*0*#QXa1+Z-VSA>-en zc(1(#rOepF#7u81W^SFB`3?TyoC!y84X))9u~3PqzN`0$K1e?tRy0%FsKUizax2Ji ziTHqLsGjoOqlu2zyB4)K0GbAB`XQ&DZX_q(S4)c5XxfV0(#Y~JLYu*uH z#@qj=SWK(;CW3pWb3yi+di1INB9M9iu^UVoKnT^h`RLK(%l)R>utUClEnhAlv z{vn{y=i%8ZN&xJ8J>ckitnO}4hX4d=2&kuodqAZL5F3BFLSS7S0{qQ3O_Z81yvQAM zX!QsHX5tZHAuy;+6+rR+xPN=^awrqE3RI5(fE6-zce1V&0&_E*t9?LK3Fx~|a`Qp^ zYfkV-cqd4=$-R>M4%oM91kmqm9ZoRh0Qz(jg^*Az0h;uR7=A3hR$Z9iVpiT)O!8dQlnxDI>ii-J&~m6HFsrqa zhx~0xxYa$b&vji-$V-4blpv9X6S44A9xk8q;UrA7XjMoVL}9lClFdniK4b_PJQl*B zc*UIrnUdFwA)tIOkd(e5@pPD~K|+KWwTg<_@NT$5%Weuk2f`CMzWC)Z*`AEU;~`N{vBRS(As zW5Ko0^EY)K0{A;9r|CdNc{fCVS22apq- zBv?5Du&Boi(K-l8mcsM|EPKO4fGU9NW=(>{hkz!%KntRD;Ym=-V)(isdlJku5!NzD zIr2XTCEX-QZxH7N@|>RvAC_|aaTCzMD4ZM+0up9= zI@Buj=L1m2$@ndg#ubn?@6GI9fEAU1?31A%vVJmbV@u0n9gQ;^DBBP8ZI#Uane_*E za~FqOKbaJ7%sd<9^EjILg0eryk{bZzfR!(bO4uYg975j7F%omVV4=FQyZFhvG$s_Z zFDM*KuS?GW21I#9dPc`934jvl=7jLL{F_KvetTN^1?u{RlYlqy-X2%aMFOIgAr~4& z*1al7xbopS!7YFKIpNGC@HZ`M2F0go%Db1Y-6GKKLAebL2yd`}mRs3RwIY*X?G-l(50#dsX~DB_%SNEQpaG qEC!a8{ebPoDP5DA)TAagsmY&V0Ko6-i6(gf0000 Promise | boolean) { - return async function ( - url: string | Blob, - { recover, relatedTextPayload }: SiteAdaptorUI.AutomationCapabilities.NativeCompositionAttachImageOptions, - ) { - const image = typeof url === 'string' ? await downloadUrl(url) : url - await waitDocumentReadyState('interactive') - if (relatedTextPayload) { - const p: Promise | undefined = - activatedSiteAdaptorUI!.automation.nativeCompositionDialog?.attachText?.(relatedTextPayload, { - recover: false, - }) - await p - } - await pasteImageToActiveElements(image) - - if (await hasSucceed()) return - if (recover) { - MaskMessages.events.autoPasteFailed.sendToLocal({ text: relatedTextPayload || '', image }) - } - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/index.ts b/packages/mask/content-script/site-adaptor-infra/defaults/index.ts deleted file mode 100644 index be81551a5ff8..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './automation/AttachImageToComposition.js' -export * from './inject/CommentBox.js' -export * from './inject/Comments.js' -export * from './inject/PostInspector.js' -export * from './state/InitProfiles.js' -export * from './inject/PageInspector.js' -export * from './inject/PostReplacer.js' -export * from './inject/StartSetupGuide.js' diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/CommentBox.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/CommentBox.tsx deleted file mode 100644 index b780d65b2f9b..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/CommentBox.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { memo, useCallback, useContext } from 'react' -import { type PostInfo, usePostInfoDetails, PostInfoContext } from '@masknet/plugin-infra/content-script' -import { type DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { MaskMessages } from '@masknet/shared-base' -import { CommentBox, type CommentBoxProps } from '../../../components/InjectedComponents/CommentBox.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' - -async function defaultOnPasteToCommentBox( - encryptedComment: string, - _current: PostInfo, - _realCurrent: HTMLElement | null, -) { - MaskMessages.events.autoPasteFailed.sendToLocal({ text: encryptedComment }) -} - -// TODO: should not rely on onPasteToCommentBoxFacebook. -// Use automation.nativeCommentBox.appendText -export const injectCommentBoxDefaultFactory = function ( - onPasteToCommentBox = defaultOnPasteToCommentBox, - additionPropsToCommentBox: (classes: Record) => Partial = () => ({}), - useCustomStyles: (props?: any) => { - classes: Record - } = makeStyles()({}) as any, - mountPointCallback?: (node: DOMProxy) => void, -) { - const CommentBoxUI = memo(function CommentBoxUI({ dom }: { dom: HTMLElement | null }) { - const info = useContext(PostInfoContext) - const encryptComment = usePostInfoDetails.encryptComment() - const { classes } = useCustomStyles() - const props = additionPropsToCommentBox(classes) - const onCallback = useCallback( - async (content: string) => { - if (!encryptComment) return - const encryptedComment = await encryptComment(content) - onPasteToCommentBox(encryptedComment, info!, dom) - }, - [encryptComment, info, dom], - ) - - if (!encryptComment) return null - return - }) - return (signal: AbortSignal, current: PostInfo) => { - if (!current.comment?.commentBoxSelector) return - const commentBoxWatcher = new MutationObserverWatcher( - current.comment.commentBoxSelector.clone(), - document.body, - ).useForeach((node, key, meta) => { - try { - mountPointCallback?.(meta) - } catch {} - const root = attachReactTreeWithContainer(meta.afterShadow, { signal }) - root.render( - - - , - ) - return root.destroy - }) - startWatch(commentBoxWatcher, signal) - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/Comments.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/Comments.tsx deleted file mode 100644 index a943b8e0372c..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/Comments.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { memo } from 'react' -import { type PostInfo, PostInfoProvider } from '@masknet/plugin-infra/content-script' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { ValueRef } from '@masknet/shared-base' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { PostComment, type PostCommentProps } from '../../../components/InjectedComponents/PostComments.js' -import { collectNodeText } from '../../../utils/index.js' - -interface injectPostCommentsDefaultConfig { - needZip?(): void -} -/** - * Create a default implementation of injectPostComments - */ -export function injectPostCommentsDefault( - config: injectPostCommentsDefaultConfig = {}, - additionalPropsToPostComment: (classes: Record) => Partial = () => ({}), - useCustomStyles: (props?: any) => { - classes: Record - } = makeStyles()({}) as any, -) { - const { needZip } = config - const PostCommentDefault = memo(function PostCommentDefault(props: Pick) { - const { classes } = useCustomStyles() - const additional = additionalPropsToPostComment(classes) - return - }) - return function injectPostComments(signal: AbortSignal, current: PostInfo) { - const selector = current.comment?.commentsSelector - if (!selector) return - const commentWatcher = new MutationObserverWatcher(selector, document.body).useForeach( - (commentNode, key, meta) => { - const commentRef = new ValueRef(collectNodeText(commentNode)) - const needZipF = needZip || (() => undefined) - const root = attachReactTreeWithContainer(meta.afterShadow, { signal }) - root.render( - - - , - ) - return { - onNodeMutation() { - commentRef.value = collectNodeText(commentNode) - }, - onTargetChanged() { - commentRef.value = collectNodeText(commentNode) - }, - onRemove() { - root.destroy() - }, - } - }, - ) - startWatch(commentWatcher, signal) - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PageInspector.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PageInspector.tsx deleted file mode 100644 index c3c9dfa12e05..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PageInspector.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { memo } from 'react' -import { PageInspector } from '../../../components/InjectedComponents/PageInspector.js' -import { attachReactTreeWithoutContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' - -export function injectPageInspectorDefault() { - const PageInspectorDefault = memo(function PageInspectorDefault() { - return - }) - - return function injectPageInspector(signal: AbortSignal) { - attachReactTreeWithoutContainer('page-inspector', , signal) - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostActions.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostActions.tsx deleted file mode 100644 index fa6db9c16ba8..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostActions.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { noop } from 'lodash-es' -import { PostInfoProvider, type PostInfo } from '@masknet/plugin-infra/content-script' -import { PostActions } from '../../../components/InjectedComponents/PostActions.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' - -function createRootElement() { - const root = document.createElement('div') - Object.assign(root.style, { - height: '100%', - display: 'flex', - alignItems: 'center', - }) - return root -} - -export function createPostActionsInjector() { - return function injectPostActions(postInfo: PostInfo, signal: AbortSignal) { - if (postInfo.actionsElement) { - const jsx = ( - - - - ) - const root = attachReactTreeWithContainer(postInfo.actionsElement.afterShadow, { - tag: createRootElement, - key: 'post-actions', - untilVisible: true, - signal, - }) - if (postInfo.actionsElement?.realCurrent?.parentNode) { - const actionsContainer = postInfo.actionsElement.realCurrent.parentNode as HTMLDivElement - actionsContainer.style.maxWidth = '100%' - } - root.render(jsx) - return root.destroy - } - return noop - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx deleted file mode 100644 index 7aa5f52c2bd3..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { memo } from 'react' -import type { DOMProxy } from '@dimensiondev/holoflows-kit' -import { type PostInfo, PostInfoProvider } from '@masknet/plugin-infra/content-script' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { PostInspector, type PostInspectorProps } from '../../../components/InjectedComponents/PostInspector.js' -import { noop } from 'lodash-es' - -export function injectPostInspectorDefault( - config: InjectPostInspectorDefaultConfig = {}, - props?: Pick, -) { - const PostInspectorDefault = memo(function PostInspectorDefault(props: { zipPost(): void }) { - return - }) - - const { zipPost, injectionPoint } = config - const zipPostF = zipPost || noop - - return function injectPostInspector(current: PostInfo, signal: AbortSignal) { - const jsx = ( - - zipPostF(current.rootElement)} /> - - ) - const root = attachReactTreeWithContainer(injectionPoint?.(current) ?? current.rootElement.afterShadow, { - key: 'post-inspector', - untilVisible: true, - signal, - }) - root.render(jsx) - return root.destroy - } -} - -interface InjectPostInspectorDefaultConfig { - zipPost?(node: DOMProxy): void - injectionPoint?: (postInfo: PostInfo) => ShadowRoot -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostReplacer.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostReplacer.tsx deleted file mode 100644 index e62f76120e58..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostReplacer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { memo } from 'react' -import type { DOMProxy } from '@dimensiondev/holoflows-kit' -import { PostInfoProvider, type PostInfo } from '@masknet/plugin-infra/content-script' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { PostReplacer, type PostReplacerProps } from '../../../components/InjectedComponents/PostReplacer.js' - -interface InjectPostReplacerConfig { - zipPost(node: DOMProxy): void - unzipPost(node: DOMProxy): void -} - -export function injectPostReplacer({ zipPost, unzipPost }: InjectPostReplacerConfig) { - const PostReplacerDefault = memo(function PostReplacerDefault(props: { - zipPost: PostReplacerProps['zip'] - unZipPost: PostReplacerProps['unzip'] - }) { - return - }) - - return function injectPostReplacer(current: PostInfo, signal: AbortSignal) { - signal.addEventListener('abort', unzipPost as () => void) - - attachReactTreeWithContainer(current.rootElement.afterShadow, { - key: 'post-replacer', - untilVisible: true, - signal, - }).render( - - zipPost(current.rootElement)} - unZipPost={() => unzipPost(current.rootElement)} - {...current} - /> - , - ) - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/StartSetupGuide.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/StartSetupGuide.tsx deleted file mode 100644 index f440abb3ecec..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/StartSetupGuide.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { PersonaIdentifier } from '@masknet/shared-base' -import { attachReactTreeWithoutContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { SetupGuide } from '../../../components/InjectedComponents/SetupGuide/index.js' - -export function createTaskStartSetupGuideDefault() { - return (signal: AbortSignal, persona: PersonaIdentifier) => { - attachReactTreeWithoutContainer('setup-guide', , signal) - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/state/InitProfiles.ts b/packages/mask/content-script/site-adaptor-infra/defaults/state/InitProfiles.ts deleted file mode 100644 index 14d433fceb84..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/state/InitProfiles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Services from '#services' -import type { SiteAdaptorUI } from '@masknet/types' -import { type ValueRef, type ProfileInformation, MaskMessages } from '@masknet/shared-base' - -export function InitAutonomousStateProfiles( - signal: AbortSignal, - ref: SiteAdaptorUI.AutonomousState['profiles'], - network: string, -) { - query(network, ref) - signal.addEventListener( - 'abort', - MaskMessages.events.ownPersonaChanged.on(() => query(network, ref)), - ) - - async function query(network: string, ref: ValueRef) { - const val = await Services.Identity.queryOwnedProfilesInformation(network) - if (signal.aborted) return - ref.value = val - } -} diff --git a/packages/mask/content-script/site-adaptor-infra/define.ts b/packages/mask/content-script/site-adaptor-infra/define.ts deleted file mode 100644 index 786e0d6abb87..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/define.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { env } from '@masknet/flags' -import type { SiteAdaptorUI } from '@masknet/types' - -const definedSiteAdaptorsUILocal = new Map() -export const definedSiteAdaptorsUI: ReadonlyMap = definedSiteAdaptorsUILocal - -export async function activateSiteAdaptorUI(): Promise { - const ui_deferred = [...definedSiteAdaptorsUI.values()].find((x) => x.shouldActivate(location)) - if (!ui_deferred) return - const { activateSiteAdaptorUIInner } = await import('./ui.js') - try { - await activateSiteAdaptorUIInner(ui_deferred) - } catch (error) { - console.error('Mask: Failed to initialize Social Network Adaptor', error) - } -} -export function defineSiteAdaptorUI(UI: SiteAdaptorUI.DeferredDefinition) { - if (UI.notReadyForProduction) { - if (env.channel === 'stable' && process.env.NODE_ENV === 'production') return UI - } - definedSiteAdaptorsUILocal.set(UI.networkIdentifier, UI) - return UI -} diff --git a/packages/mask/content-script/site-adaptor-infra/index.ts b/packages/mask/content-script/site-adaptor-infra/index.ts deleted file mode 100644 index 218fc7faa525..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './utils.js' -export * from './ui.js' -export * from './define.js' diff --git a/packages/mask/content-script/site-adaptor-infra/sandboxed-plugin.ts b/packages/mask/content-script/site-adaptor-infra/sandboxed-plugin.ts deleted file mode 100644 index d1328dee077b..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/sandboxed-plugin.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Children, createElement } from 'react' -import { type SiteAdaptorInstance, SiteAdaptorPluginHost } from '@masknet/sandboxed-plugin-runtime/site-adaptor' -import { Flags } from '@masknet/flags' -import { type Plugin, registerPlugin } from '@masknet/plugin-infra' -import { ApplicationBoardModal } from '@masknet/shared' -import type { PluginID } from '@masknet/shared-base' -import { hmr } from '../../utils-pure/index.js' -import { createHostAPIs } from '../../shared/sandboxed-plugin/host-api.js' - -const { signal } = hmr(import.meta.webpackHot) -import.meta.webpackHot?.accept() - -let hot: - | Map< - string, - ( - hot: Promise<{ - default: Plugin.SiteAdaptor.Definition - }>, - ) => void - > - | undefined -if (process.env.NODE_ENV === 'development') { - const sym = Symbol.for('sandboxed plugin bridge hot map') - hot = (globalThis as any)[sym] ??= new Map() -} - -if (Flags.sandboxedPluginRuntime) { - const host = new SiteAdaptorPluginHost( - { - ...createHostAPIs(false), - // TODO: implement this - attachCompositionMetadata(plugin, id, meta) {}, - // TODO: implement this - dropCompositionMetadata(plugin, id) {}, - closeApplicationBoardDialog() { - ApplicationBoardModal.close() - }, - }, - process.env.NODE_ENV === 'development', - signal, - ) - host.__builtInPluginInfraBridgeCallback__ = __builtInPluginInfraBridgeCallback__ - host.onPluginListUpdate() -} -function __builtInPluginInfraBridgeCallback__(this: SiteAdaptorPluginHost, id: string) { - let instance: SiteAdaptorInstance | undefined - - const base: Plugin.Shared.Definition = { - enableRequirement: { - supports: { type: 'opt-out', sites: {} }, - target: 'beta', - }, - ID: id as PluginID, - // TODO: read i18n files - // TODO: read the name from the manifest - name: { fallback: '__generated__bridge__plugin__' + id }, - experimentalMark: true, - } - const def: Plugin.DeferredDefinition = { - ...base, - SiteAdaptor: { - hotModuleReload: (reload) => hot?.set(id, reload), - async load() { - return { default: site } - }, - }, - } - - const site: Plugin.SiteAdaptor.Definition = { - ...base, - init: async (signal, context) => { - try { - const [i] = await this.startPlugin_bridged(id, signal) - instance = i - } catch (error) { - console.error(`[Sandboxed-plugin] Plugin ${id} stopped due to an error when starting.`, error) - } - }, - get CompositionDialogEntry() { - if (!instance?.CompositionEntry) return undefined - return { - label: Children.only(instance.CompositionEntry.label), - dialog({ onClose, open }: Plugin.SiteAdaptor.CompositionDialogEntry_DialogProps) { - if (open) return createElement(instance!.CompositionEntry!.dialog as any, { onClose, open }) - return null - }, - } - }, - CompositionDialogMetadataBadgeRender(key, meta) { - if (!key.startsWith(id + ':')) return null - const k = key.slice(id.length + 1) - return instance!.CompositionDialogMetadataBadgeRender(k, meta) - }, - } - if (hot?.has(id)) hot.get(id)!(def.SiteAdaptor!.load()) - else registerPlugin(def) -} diff --git a/packages/mask/content-script/site-adaptor-infra/ui.ts b/packages/mask/content-script/site-adaptor-infra/ui.ts deleted file mode 100644 index 7edae09a942c..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/ui.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { createElement } from 'react' -import stringify from 'json-stable-stringify' -import { assertNotEnvironment, Environment } from '@dimensiondev/holoflows-kit' -import { delay, waitDocumentReadyState } from '@masknet/kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { type Plugin, startPluginSiteAdaptor, __setSiteAdaptorContext__ } from '@masknet/plugin-infra/content-script' -import { Modals, sharedUIComponentOverwrite, sharedUINetworkIdentifier, type ModalProps } from '@masknet/shared' -import { - createSubscriptionFromValueRef, - currentPersonaIdentifier, - currentSetupGuideStatus, - DashboardRoutes, - ECKeyIdentifier, - i18NextInstance, - queryRemoteI18NBundle, - type SetupGuideContext, - SetupGuideStep, - setDebugObject, -} from '@masknet/shared-base' -import { Flags } from '@masknet/flags' -import { Telemetry } from '@masknet/web3-telemetry' -import { ExceptionID, ExceptionType } from '@masknet/web3-telemetry/types' -import { createSharedContext, createPluginHost } from '../../shared/plugin-infra/host.js' -import Services from '#services' -import { getCurrentIdentifier } from '../site-adaptors/utils.js' -import { attachReactTreeWithoutContainer, setupReactShadowRootEnvironment } from '../utils/shadow-root.js' -import { configureSelectorMissReporter } from '../utils/startWatch.js' -import { setupUIContext } from '../../shared-ui/initUIContext.js' -import { definedSiteAdaptorsUI } from './define.js' - -const definedSiteAdaptorsResolved = new Map() - -export let activatedSiteAdaptorUI: SiteAdaptorUI.Definition | undefined -export let activatedSiteAdaptor_state: Readonly | undefined - -export async function activateSiteAdaptorUIInner(ui_deferred: SiteAdaptorUI.DeferredDefinition): Promise { - assertNotEnvironment(Environment.ManifestBackground) - - console.log('[Mask] Activating provider', ui_deferred.networkIdentifier) - - const injectSwitchSettings = await Services.Settings.getAllInjectSwitchSettings() - if (!injectSwitchSettings[ui_deferred.networkIdentifier]) return - - configureSelectorMissReporter((name) => { - const error = new Error(`Selector "${name}" does not match anything ${location.href}.`) - error.stack = '' - Telemetry.captureException(ExceptionType.Error, ExceptionID.Debug, error, { - sampleRate: 0.01, - }) - }) - setupReactShadowRootEnvironment() - const ui = (activatedSiteAdaptorUI = await loadSiteAdaptorUI(ui_deferred.networkIdentifier)) - - sharedUINetworkIdentifier.value = ui_deferred.networkIdentifier - if (ui.customization.sharedComponentOverwrite) { - sharedUIComponentOverwrite.value = ui.customization.sharedComponentOverwrite - } - - console.log('[Mask] Provider activated. globalThis.ui =', ui) - setDebugObject('ui', ui) - - const abort = new AbortController() - const { signal } = abort - if (import.meta.webpackHot) { - console.log('Site adaptor HMR enabled.') - ui_deferred.hotModuleReload?.(async (newDefinition) => { - console.log('Site adaptor updated. Uninstalling current adaptor.') - abort.abort() - await delay(200) - definedSiteAdaptorsResolved.set(ui_deferred.networkIdentifier, newDefinition) - activateSiteAdaptorUIInner(ui_deferred) - }) - } - - await waitDocumentReadyState('interactive') - - i18nOverwrite() - - await ui.collecting.themeSettingsProvider?.start(signal) - - activatedSiteAdaptor_state = await ui.init(signal) - - startIntermediateSetupGuide() - $unknownIdentityResolution() - - ui.collecting.postsProvider?.start(signal) - startPostListener() - ui.collecting.currentVisitingIdentityProvider?.start(signal) - - ui.injection.pageInspector?.(signal) - ui.injection.toolbox?.(signal, 'wallet') - ui.injection.toolbox?.(signal, 'application') - ui.injection.newPostComposition?.start?.(signal) - ui.injection.searchResult?.(signal) - ui.injection.userBadge?.(signal) - - ui.injection.profileTab?.(signal) - ui.injection.profileTabContent?.(signal) - - ui.injection.profileCover?.(signal) - ui.injection.userAvatar?.(signal) - ui.injection.profileAvatar?.(signal) - ui.injection.tips?.(signal) - ui.injection.nameWidget?.(signal) - ui.injection.farcaster?.(signal) - ui.injection.lens?.(signal) - - ui.injection.enhancedProfileNFTAvatar?.(signal) - ui.injection.openNFTAvatar?.(signal) - ui.injection.postAndReplyNFTAvatar?.(signal) - - ui.injection.avatar?.(signal) - ui.injection.profileCard?.(signal) - - ui.injection.switchLogo?.(signal) - ui.injection.PluginSettingsDialog?.(signal) - ui.injection.calendar?.(signal) - - // Update user avatar - ui.collecting.currentVisitingIdentityProvider?.recognized.addListener((ref) => { - if (!(ref.avatar && ref.identifier)) return - Services.Identity.updateProfileInfo(ref.identifier, { avatarURL: ref.avatar, nickname: ref.nickname }) - const currentProfile = getCurrentIdentifier() - if (currentProfile?.linkedPersona) { - Services.Identity.createNewRelation(ref.identifier, currentProfile.linkedPersona) - } - }) - - signal.addEventListener('abort', queryRemoteI18NBundle(Services.Helper.queryRemoteI18NBundle)) - - const lastRecognizedSub = createSubscriptionFromValueRef(ui.collecting.identityProvider.recognized, signal) - const currentVisitingSub = createSubscriptionFromValueRef( - ui.collecting.currentVisitingIdentityProvider.recognized, - signal, - ) - const connectPersona = async () => { - const currentPersonaIdentifier = await Services.Settings.getCurrentPersonaIdentifier() - currentSetupGuideStatus[activatedSiteAdaptorUI!.networkIdentifier].value = stringify({ - status: SetupGuideStep.FindUsername, - persona: currentPersonaIdentifier?.toText(), - }) - } - - setupUIContext() - __setSiteAdaptorContext__({ - lastRecognizedProfile: lastRecognizedSub, - currentVisitingProfile: currentVisitingSub, - currentNextIDPlatform: ui.configuration.nextIDConfig?.platform, - currentPersonaIdentifier: createSubscriptionFromValueRef(currentPersonaIdentifier, signal), - getPostURL: ui.utils.getPostURL || (() => null), - getProfileURL: ui.utils.getProfileURL || (() => null), - share: ui.utils.share, - getPostIdFromNewPostToast: ui.configuration.nextIDConfig?.getPostIdFromNewPostToast, - connectPersona, - postMessage: ui.automation?.nativeCompositionDialog?.attachText, - publishPost: ui.automation.endpoint?.publishPost, - getSearchedKeyword: ui.collecting.getSearchedKeyword, - getUserIdentity: ui.utils.getUserIdentity, - }) - - startPluginSiteAdaptor( - ui.networkIdentifier, - createPluginHost( - signal, - (id, def, signal): Plugin.SiteAdaptor.SiteAdaptorContext => { - return { - setMinimalMode(enabled) { - Services.Settings.setPluginMinimalModeEnabled(id, enabled) - }, - ...createSharedContext(id, signal), - } - }, - Services.Settings.getPluginMinimalModeEnabled, - Services.Helper.hasHostPermission, - ), - ) - attachReactTreeWithoutContainer( - 'Modals', - createElement(Modals, { - createWallet: () => Services.Helper.openDashboard(DashboardRoutes.CreateMaskWalletForm), - } satisfies ModalProps), - ) - - // TODO: receive the signal - if (Flags.sandboxedPluginRuntime) import('./sandboxed-plugin.js') - - function i18nOverwrite() { - const i18n = ui.customization.i18nOverwrite || {} - for (const namespace of Object.keys(i18n)) { - const ns = i18n[namespace] - for (const i18nKey of Object.keys(ns)) { - const pair = i18n[namespace][i18nKey] - for (const language of Object.keys(pair)) { - const value = pair[language] - i18NextInstance.addResource(language, namespace, i18nKey, value) - } - } - } - } - - function $unknownIdentityResolution() { - const provider = ui.collecting.identityProvider - if (!provider) return - provider.start(signal) - provider.recognized.addListener((newValue, oldValue) => { - if (document.visibilityState === 'hidden') return - if (newValue.identifier === oldValue.identifier) return - if (!newValue.identifier) return - }) - if (provider.hasDeprecatedPlaceholderName) { - provider.recognized.addListener((id) => { - if (signal.aborted) return - if (!id.identifier) return - Services.Identity.resolveUnknownLegacyIdentity(id.identifier) - }) - } - } - - function startPostListener() { - const posts = ui.collecting.postsProvider?.posts - if (!posts) return - const abortSignals = new WeakMap() - posts.event.on('set', async (key, value) => { - await unmount(key) - const abort = new AbortController() - signal.addEventListener('abort', () => abort.abort()) - abortSignals.set(key, abort) - const { signal: postSignal } = abort - ui.injection.postReplacer?.(postSignal, value) - ui.injection.postInspector?.(postSignal, value) - ui.injection.postActions?.(postSignal, value) - ui.injection.commentComposition?.compositionBox(postSignal, value) - ui.injection.commentComposition?.commentInspector(postSignal, value) - }) - posts.event.on('delete', unmount) - function unmount(key: object) { - if (!abortSignals.has(key)) return - abortSignals.get(key)!.abort() - // AbortSignal need an event loop - // unmount a React root need another one. - // let's guess a number that the React root will unmount. - return delay(16 * 3) - } - } - - function startIntermediateSetupGuide() { - const network = ui.networkIdentifier - const id = currentSetupGuideStatus[network].value - let started = false - const onStatusUpdate = (id: string) => { - const { persona, status }: SetupGuideContext = JSON.parse(id || '{}') - if (persona && status && !started) { - started = true - ui.injection.setupWizard?.( - signal, - ECKeyIdentifier.from(persona).expect(`${persona} should be a valid ECKeyIdentifier`), - ) - } - } - currentSetupGuideStatus[network].addListener(onStatusUpdate) - currentSetupGuideStatus[network].readyPromise.then(onStatusUpdate) - onStatusUpdate(id) - } -} - -async function loadSiteAdaptorUI(identifier: string): Promise { - if (definedSiteAdaptorsResolved.has(identifier)) return definedSiteAdaptorsResolved.get(identifier)! - const define = definedSiteAdaptorsUI.get(identifier) - if (!define) throw new Error('Site adaptor not found') - const ui = (await define.load()).default - definedSiteAdaptorsResolved.set(identifier, ui) - if (import.meta.webpackHot) { - define.hotModuleReload?.((ui) => definedSiteAdaptorsResolved.set(identifier, ui)) - } - return ui -} diff --git a/packages/mask/content-script/site-adaptor-infra/utils.ts b/packages/mask/content-script/site-adaptor-infra/utils.ts deleted file mode 100644 index df8f827afaa8..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isEqual } from 'lodash-es' -import { ValueRef, ObservableWeakMap, type ProfileInformation } from '@masknet/shared-base' -import type { SiteAdaptorUI } from '@masknet/types' -import { ThemeMode, FontSize, ThemeColor, type ThemeSettings } from '@masknet/web3-shared-base' - -export const stateCreator: { - readonly [key in keyof SiteAdaptorUI.AutonomousState]-?: () => SiteAdaptorUI.AutonomousState[key] -} = { - profiles: () => new ValueRef([], isEqual), -} -export const creator = { - EmptyIdentityResolveProviderState: (): SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'] => - new ValueRef({}, isEqual), - EmptyPostProviderState: (): SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'] => new ObservableWeakMap(), - EmptyThemeSettingsProviderState: (): SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'] => - new ValueRef( - { - size: FontSize.Normal, - mode: ThemeMode.Light, - color: ThemeColor.Blue, - isDim: false, - }, - isEqual, - ), -} diff --git a/packages/mask/content-script/site-adaptor-infra/utils/create-post-context.ts b/packages/mask/content-script/site-adaptor-infra/utils/create-post-context.ts deleted file mode 100644 index b50b2c20f8b6..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/utils/create-post-context.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { difference, noop } from 'lodash-es' -import type { Subscription } from 'use-subscription' -import type { SupportedPayloadVersions } from '@masknet/encryption' -import { - ValueRef, - ObservableMap, - ObservableSet, - parseURLs, - PostIdentifier, - type ProfileIdentifier, - createSubscriptionFromValueRef, - SubscriptionDebug as debug, - mapSubscription, - EMPTY_LIST, - PostIVIdentifier, - EnhanceableSite, - NULL, -} from '@masknet/shared-base' -import type { - PostContext, - PostContextAuthor, - PostContextCoAuthor, - PostContextCreation, - PostContextActions, -} from '@masknet/plugin-infra/content-script' -import { extractTextFromTypedMessage, makeTypedMessageEmpty, type TypedMessage } from '@masknet/typed-message' -import { resolveFacebookLink } from '../../site-adaptors/facebook.com/utils/resolveFacebookLink.js' - -export function createSiteAdaptorSpecializedPostContext(site: EnhanceableSite, actions: PostContextActions) { - return function createPostContext(opt: PostContextCreation): PostContext { - const cancel: Array<() => void> = [] - opt.signal?.addEventListener('abort', () => cancel.forEach((fn) => fn?.())) - - // #region Mentioned links - const linksSubscribe: Subscription = (() => { - const isFacebook = site === EnhanceableSite.Facebook - const links = new ValueRef(EMPTY_LIST) - - function evaluate() { - const text = parseURLs(extractTextFromTypedMessage(opt.rawMessage.getCurrentValue()).unwrapOr('')) - .concat(opt.postMentionedLinksProvider?.getCurrentValue() || EMPTY_LIST) - .map(isFacebook ? resolveFacebookLink : (x: string) => x) - if (difference(text, links.value).length === 0) return - if (!text.length) links.value = EMPTY_LIST - else links.value = text - } - cancel.push(opt.rawMessage.subscribe(evaluate)) - const f = opt.postMentionedLinksProvider?.subscribe(evaluate) - f && cancel.push(f) - return createSubscriptionFromValueRef(links) - })() - // #endregion - const author: PostContextAuthor = { - source: null, - handle: NULL, - avatarURL: opt.avatarURL, - nickname: opt.nickname, - author: opt.author, - site, - postID: opt.postID, - } - const postIdentifier = debug({ - getCurrentValue: () => { - const by = opt.author.getCurrentValue() - const id = opt.postID.getCurrentValue() - if (!id || !by) return null - return new PostIdentifier(by, id) - }, - subscribe: (sub) => { - const a = opt.author.subscribe(sub) - const b = opt.postID.subscribe(sub) - return () => void [a(), b()] - }, - }) - const postIVIdentifier = new ValueRef(null) - const isPublicShared = new ValueRef(undefined) - const isAuthorOfPost = new ValueRef(undefined) - const version = new ValueRef(undefined) - return { - author: author.author, - source: null, - handle: NULL, - coAuthors: opt.coAuthors, - avatarURL: author.avatarURL, - nickname: author.nickname, - site, - postID: author.postID, - - get rootNode() { - return opt.rootElement.realCurrent - }, - rootElement: opt.rootElement, - actionsElement: opt.actionsElement, - isFocusing: opt.isFocusing, - suggestedInjectionPoint: opt.suggestedInjectionPoint, - - comment: opt.comments, - encryptComment: new ValueRef(null), - decryptComment: new ValueRef(null), - - identifier: postIdentifier, - url: mapSubscription(postIdentifier, (id) => { - if (id) return actions.getURLFromPostIdentifier?.(id) || null - return null - }), - - mentionedLinks: linksSubscribe, - postMetadataImages: - opt.postImagesProvider || - debug({ - getCurrentValue: () => EMPTY_LIST, - subscribe: () => noop, - }), - - rawMessage: opt.rawMessage, - - hasMaskPayload: (() => { - const hasMaskPayload = new ValueRef(false) - function evaluate() { - const msg = - extractTextFromTypedMessage(opt.rawMessage.getCurrentValue()).unwrapOr('') + - '\n' + - [...linksSubscribe.getCurrentValue()].join('\n') - hasMaskPayload.value = actions.hasPayloadLike(msg) - } - evaluate() - cancel.push(linksSubscribe.subscribe(evaluate)) - cancel.push(opt.rawMessage.subscribe(evaluate)) - return createSubscriptionFromValueRef(hasMaskPayload) - })(), - postIVIdentifier: createSubscriptionFromValueRef(postIVIdentifier), - publicShared: createSubscriptionFromValueRef(isPublicShared), - isAuthorOfPost: createSubscriptionFromValueRef(isAuthorOfPost), - version: createSubscriptionFromValueRef(version), - decryptedReport(opts) { - const currentAuthor = author.author.getCurrentValue() - if (opts.iv && currentAuthor) - postIVIdentifier.value = new PostIVIdentifier(currentAuthor.network, opts.iv) - if (opts.sharedPublic?.isSome()) isPublicShared.value = opts.sharedPublic.value - if (opts.isAuthorOfPost) isAuthorOfPost.value = opts.isAuthorOfPost.value - if (opts.version) version.value = opts.version - }, - } - } -} -export function createRefsForCreatePostContext() { - const avatarURL = new ValueRef(null) - const nickname = new ValueRef(null) - const postBy = new ValueRef(null) - const postCoAuthors = new ValueRef([]) - const postID = new ValueRef(null) - const postMessage = new ValueRef(makeTypedMessageEmpty()) - const postMetadataImages = new ObservableSet() - const postMetadataMentionedLinks = new ObservableMap() - const subscriptions: Omit< - PostContextCreation, - 'rootElement' | 'actionsElement' | 'suggestedInjectionPoint' | 'site' - > = { - avatarURL: mapSubscription(createSubscriptionFromValueRef(avatarURL), (x) => { - if (!x) return null - if (!URL.canParse(x)) return null - return new URL(x) - }), - handle: NULL, - nickname: createSubscriptionFromValueRef(nickname), - author: createSubscriptionFromValueRef(postBy), - postID: createSubscriptionFromValueRef(postID), - source: null, - rawMessage: createSubscriptionFromValueRef(postMessage), - postImagesProvider: debug({ - getCurrentValue: () => postMetadataImages.asValues, - subscribe: (sub) => postMetadataImages.event.on(postMetadataImages.ALL_EVENTS, sub), - }), - postMentionedLinksProvider: debug({ - getCurrentValue: () => postMetadataMentionedLinks.asValues, - subscribe: (sub) => postMetadataMentionedLinks.event.on(postMetadataMentionedLinks.ALL_EVENTS, sub), - }), - coAuthors: createSubscriptionFromValueRef(postCoAuthors), - } - return { - subscriptions, - avatarURL, - nickname, - postBy, - postID, - postCoAuthors, - postMessage, - postMetadataMentionedLinks, - postMetadataImages, - } -} diff --git a/packages/mask/content-script/site-adaptors/README.md b/packages/mask/content-script/site-adaptors/README.md deleted file mode 100644 index 8e3ba20a49a5..000000000000 --- a/packages/mask/content-script/site-adaptors/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Social Network Adaptors of Mask Network - -To support a new network, you can refer our adaptor for [Facebook](./facebook.com) and [Twitter](./twitter.com). - -You should follow the structure as following: - -```plaintext -./some-network.com - index.ts - base.ts - shared.ts - ui-provider.ts - worker-provider.ts -``` - -⚠ If you're going to support decentralized network like Mastdon, please contact `@Jack-Works`. - -The current architecture is not friendly to that kind of website. diff --git a/packages/mask/content-script/site-adaptors/facebook.com/automation/openComposeBox.ts b/packages/mask/content-script/site-adaptors/facebook.com/automation/openComposeBox.ts deleted file mode 100644 index 6e9042bdac86..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/automation/openComposeBox.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' -import { CrossIsolationMessages, type CompositionDialogEvent } from '@masknet/shared-base' -import { i18n } from '../../../../shared-ui/locales_legacy/index.js' -import { makeTypedMessageText, type SerializableTypedMessages } from '@masknet/typed-message' -import { delay, waitDocumentReadyState } from '@masknet/kit' - -function nativeComposeButtonSelector() { - return new LiveSelector() - .querySelector( - [ - '[role="region"] [role="link"]+[role="button"]', - '#MComposer [role="button"]', // mobile - ].join(','), - ) - .enableSingleMode() -} - -function nativeComposeTextareaSelector() { - return new LiveSelector() - .querySelector( - [ - '#structured_composer_form .mentions textarea', // mobile - ].join(','), - ) - .enableSingleMode() -} - -function nativeComposeDialogIndicatorSelector() { - return new LiveSelector().querySelector( - [ - // PC - the form of compose dialog - '[role="dialog"] form[method="post"]', - - // mobile - the submit button - '#composer-main-view-id button[type="submit"]', - ].join(','), - ) -} - -function nativeComposeDialogCloseButtonSelector() { - return new LiveSelector().querySelector('[role="dialog"] form[method="post"] [role="button"]') -} - -export async function taskOpenComposeBoxFacebook( - content: string | SerializableTypedMessages, - options?: CompositionDialogEvent['options'], -) { - await waitDocumentReadyState('interactive') - await delay(200) - - // active the compose dialog - const composeTextarea = nativeComposeTextareaSelector().evaluate() - const composeButton = nativeComposeButtonSelector().evaluate() - if (composeTextarea) composeTextarea.focus() - if (composeButton) composeButton.click() - await delay(200) - - // the indicator only available when compose dialog opened successfully - const composeIndicator = nativeComposeDialogIndicatorSelector().evaluate() - if (!composeIndicator) { - // eslint-disable-next-line no-alert - alert(i18n.t('automation_request_click_post_button')) - return - } - - await delay(2000) - CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ - reason: 'popup', - open: true, - content: typeof content === 'string' ? makeTypedMessageText(content) : content, - options, - }) -} - -export async function taskCloseNativeComposeBoxFacebook() { - await waitDocumentReadyState('interactive') - await delay(200) - const closeDialogButton = nativeComposeDialogCloseButtonSelector().evaluate()?.[0] - closeDialogButton?.click() -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteTextToComposition.ts b/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteTextToComposition.ts deleted file mode 100644 index 3716018c6dbe..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteTextToComposition.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { inputText, pasteText } from '@masknet/injected-script' -import { delay, waitDocumentReadyState } from '@masknet/kit' -import { MaskMessages } from '@masknet/shared-base' - -/** - * Access: https://(www|m).facebook.com/ - */ -export async function pasteTextToCompositionFacebook( - text: string, - options: SiteAdaptorUI.AutomationCapabilities.NativeCompositionAttachTextOptions, -) { - const { recover } = options - await waitDocumentReadyState('interactive') - // Save the scrolling position - const scrolling = document.scrollingElement || document.documentElement - const scrollBack = ( - (top) => () => - scrolling.scroll({ top }) - )(scrolling.scrollTop) - - const activated = new LiveSelector().querySelectorAll( - // cspell:disable-next-line - 'div[role=presentation] .notranslate[role=textbox]', - ) - - // Select element with fb customize background image. - const activatedCustom = new LiveSelector().querySelectorAll( - '.notranslate[aria-label]', - ) - - activatedCustom.filter((x) => x.parentElement?.parentElement?.parentElement?.parentElement?.hasAttribute('style')) - - const element = activated.evaluate()[0] ?? activatedCustom.evaluate()[0] - try { - element.focus() - await delay(100) - - const selection = window.getSelection() - if (selection) { - if (selection.rangeCount > 0) { - selection.removeAllRanges() - } - if (element.firstChild) { - const range = document.createRange() - range.selectNode(element.firstChild) - selection.addRange(range) - } - } - if ('value' in document.activeElement!) inputText(text) - else pasteText(text) - await delay(200) - // Prevent Custom Paste failed, this will cause service not available to user. - if (!element.innerText.includes(text) || ('value' in element && !element.value.includes(text))) - copyFailed('Not detected') - } catch (error) { - copyFailed(error) - } - scrollBack() - function copyFailed(error: unknown) { - console.warn('Text not pasted to the text area', error) - if (recover) MaskMessages.events.autoPasteFailed.sendToLocal({ text }) - } -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteToCommentBoxFacebook.ts b/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteToCommentBoxFacebook.ts deleted file mode 100644 index 2211a43036b2..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/automation/pasteToCommentBoxFacebook.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { selectElementContents } from '../../../utils/selectElementContents.js' -import { delay } from '@masknet/kit' -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { pasteText } from '@masknet/injected-script' -import { MaskMessages } from '@masknet/shared-base' - -export async function pasteToCommentBoxFacebook(encryptedComment: string, current: PostInfo, dom: HTMLElement | null) { - const fail = () => { - MaskMessages.events.autoPasteFailed.sendToLocal({ text: encryptedComment }) - } - const root = dom || current.rootNode - if (!root) return fail() - const input = root.querySelector('[contenteditable] > *') - if (!input) return fail() - selectElementContents(input) - input.focus() - pasteText(encryptedComment) - await delay(200) - if (!root.innerText.includes(encryptedComment)) return fail() -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/base.ts b/packages/mask/content-script/site-adaptors/facebook.com/base.ts deleted file mode 100644 index c2f6ae6f7fee..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/base.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { EncryptPayloadNetwork } from '@masknet/encryption' -import { EnhanceableSite } from '@masknet/shared-base' -import type { SiteAdaptor } from '@masknet/types' - -const origins = ['https://www.facebook.com/*', 'https://m.facebook.com/*', 'https://facebook.com/*'] - -export const facebookBase: SiteAdaptor.Base = { - encryptPayloadNetwork: EncryptPayloadNetwork.Facebook, - networkIdentifier: EnhanceableSite.Facebook, - declarativePermissions: { origins }, - shouldActivate(location) { - // facebook share widget - return location.hostname.endsWith('facebook.com') && location.pathname !== '/v2.0/plugins/like.php' - }, -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/collecting/getSearchedKeyword.ts b/packages/mask/content-script/site-adaptors/facebook.com/collecting/getSearchedKeyword.ts deleted file mode 100644 index be6bad8b6082..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/collecting/getSearchedKeyword.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default function getSearchedKeywordAtFacebook() { - const hashKeyword = location.pathname.match(/^\/hashtag\/([A-za-z0\u20139_]+)$/u)?.[1] - if (hashKeyword) return '#' + hashKeyword - - if (/\/search\/top\/?$/.test(location.pathname)) { - const params = new URLSearchParams(location.search) - return params.get('q') ?? '' - } - - return '' -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/collecting/identity.ts b/packages/mask/content-script/site-adaptors/facebook.com/collecting/identity.ts deleted file mode 100644 index aa6c5e9a0bef..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/collecting/identity.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/index.js' -import { getProfileIdentifierAtFacebook, getUserID } from '../utils/getProfileIdentifier.js' -import { ProfileIdentifier, EnhanceableSite, type ValueRef } from '@masknet/shared-base' -import { searchFacebookAvatarSelector } from '../utils/selector.js' -import { getAvatar, getBioDescription, getFacebookId, getNickName, getPersonalHomepage } from '../utils/user.js' -import { delay } from '@masknet/kit' -import type { IdentityResolved } from '@masknet/plugin-infra' - -export const IdentityProviderFacebook: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: true, - recognized: creator.EmptyIdentityResolveProviderState(), - start(signal) { - resolveLastRecognizedIdentityFacebookInner(this.recognized, signal) - }, -} - -function resolveLastRecognizedIdentityFacebookInner(ref: ValueRef, signal: AbortSignal) { - const self = myUsernameLiveSelector.clone().map((x) => getProfileIdentifierAtFacebook(x, false)) - new MutationObserverWatcher(self) - .addListener('onAdd', (e) => assign(e.value)) - .addListener('onChange', (e) => assign(e.newValue)) - .startWatch({ childList: true, subtree: true, characterData: true }, signal) - function assign(i: IdentityResolved) { - if (i.identifier) ref.value = i - } - fetch(new URL('/me', location.href), { method: 'HEAD', signal }) - .then((x) => x.url) - .then(getUserID) - .then((id) => { - const nickname = getNickName(id) - const avatar = getAvatar() - assign({ - ...ref.value, - nickname, - avatar, - isOwner: true, - identifier: ProfileIdentifier.of(EnhanceableSite.Facebook, id).unwrapOr(undefined), - }) - }) -} - -function resolveCurrentVisitingIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - ownerRef: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const assign = async () => { - await delay(3000) - const nickname = getNickName() - const bio = getBioDescription() - const handle = getFacebookId() - const ownerHandle = ownerRef.value.identifier?.userId - const isOwner = !!(handle && ownerHandle && handle.toLowerCase() === ownerHandle.toLowerCase()) - const homepage = getPersonalHomepage() - const avatar = getAvatar() - - ref.value = { - identifier: ProfileIdentifier.of(EnhanceableSite.Facebook, handle).unwrapOr(undefined), - nickname, - avatar, - bio, - homepage, - isOwner, - } - } - - const createWatcher = (selector: LiveSelector) => { - new MutationObserverWatcher(selector) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - }, - cancel, - ) - window.addEventListener('locationchange', assign, { signal: cancel }) - } - - assign() - - createWatcher(searchFacebookAvatarSelector()) -} - -export const CurrentVisitingIdentityProviderFacebook: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveCurrentVisitingIdentityInner(this.recognized, IdentityProviderFacebook.recognized, cancel) - }, -} - -// Try to resolve my identities -const myUsernameLiveSelector = new LiveSelector() - .querySelectorAll( - '[data-pagelet="LeftRail"] > [data-visualcompletion="ignore-dynamic"]:first-child > div:first-child > ul [role="link"]', - ) - - .filter((x) => x.innerText) diff --git a/packages/mask/content-script/site-adaptors/facebook.com/collecting/posts.tsx b/packages/mask/content-script/site-adaptors/facebook.com/collecting/posts.tsx deleted file mode 100644 index 532513721696..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/collecting/posts.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { None, Some, type Option } from 'ts-results-es' -import { Flags } from '@masknet/flags' -import type { SiteAdaptorUI } from '@masknet/types' -import { EnhanceableSite } from '@masknet/shared-base' -import { DOMProxy, LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { type TypedMessage, makeTypedMessageText, makeTypedMessageTuple } from '@masknet/typed-message' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { getProfileIdentifierAtFacebook } from '../utils/getProfileIdentifier.js' -import { clickSeeMore } from '../injection/PostInspector.js' -import { facebookShared } from '../shared.js' -import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' -import { collectNodeText } from '../../../utils/index.js' -import { startWatch } from '../../../utils/startWatch.js' - -const posts = new LiveSelector().querySelectorAll('[role=article] [id] span[dir="auto"]') - -export const PostProviderFacebook: SiteAdaptorUI.CollectingCapabilities.PostsProvider = { - posts: creator.EmptyPostProviderState(), - start(signal) { - collectPostsFacebookInner(this.posts, signal) - }, -} -function collectPostsFacebookInner( - store: SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'], - signal: AbortSignal, -) { - startWatch( - new MutationObserverWatcher(posts).useForeach((node, key, metadata) => { - clickSeeMore(node) - }), - signal, - ) - - startWatch( - new MutationObserverWatcher(posts).useForeach((node, key, metadata) => { - const root = new LiveSelector() - .replace(() => [metadata.realCurrent]) - .closest('[role=article] [id] span[dir="auto"]') - - const rootProxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - beforeShadowRootInit: Flags.shadowRootInit, - }) - rootProxy.realCurrent = root.evaluate()[0] as HTMLElement - - // ? inject after comments - const commentsSelector = root - .clone() - .querySelectorAll('[role=article] [id] span[dir="auto"]') - .closest(3) - - // ? inject comment text field - const commentBoxSelector = root - .clone() - .querySelectorAll('[role="article"] [role="presentation"]:not(img)') - .map((x) => x.parentElement) - - const { subscriptions, ...info } = createRefsForCreatePostContext() - const postInfo = facebookShared.utils.createPostContext({ - site: EnhanceableSite.Facebook, - rootElement: rootProxy, - suggestedInjectionPoint: metadata.realCurrent!, - signal, - comments: { commentBoxSelector, commentsSelector }, - ...subscriptions, - }) - - store.set(metadata, postInfo) - function collectPostInfo() { - rootProxy.realCurrent = root.evaluate()[0] as HTMLElement - const nextTypedMessage: TypedMessage[] = [] - info.postBy.value = getPostBy(metadata, postInfo.hasMaskPayload.getCurrentValue())?.identifier || null - info.postID.value = getPostID(metadata, rootProxy.realCurrent) - // parse text - const text = collectNodeText(node, { - onHTMLAnchorElement(node: HTMLAnchorElement): Option { - const href = node.getAttribute('href') - if (!href) { - return None - } - return Some( - '\n' + - (href.includes('l.facebook.com') ? - new URL(href).searchParams.get('u') - : node.innerText), - ) - }, - }) - nextTypedMessage.push(makeTypedMessageText(text)) - // parse image - const images = getMetadataImages(metadata) - for (const url of images) { - info.postMetadataImages.add(url) - } - info.postMessage.value = makeTypedMessageTuple(nextTypedMessage) - } - - function collectLinks() { - if (metadata.destroyed) return - const linkElements = metadata.current.querySelectorAll('[role=article] [id] a') - const links = [...Array.from(linkElements).filter((x) => x.href)] - - const seen = new Set() - for (const x of links) { - if (seen.has(x.href)) continue - seen.add(x.href) - info.postMetadataMentionedLinks.set(x, x.href) - } - } - - function run() { - collectPostInfo() - collectLinks() - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => store.delete(metadata), - } - }), - signal, - ) -} - -function getPostBy(node: DOMProxy, allowCollectInfo: boolean) { - if (node.destroyed) return - const dom = [(node.current.closest('[role="article"]') ?? node.current.parentElement)!.querySelectorAll('a')[1]] - // side effect: save to service - return getProfileIdentifierAtFacebook(Array.from(dom), allowCollectInfo) -} - -function getPostID(node: DOMProxy, root: HTMLElement): null | string { - if (node.destroyed) return null - // In single url - if (location.href.match(/plugins.+(perma.+story_fbid%3D|posts%2F)?/)) { - const url = new URL(location.href) - return url.searchParams.get('id') - } else { - try { - // In timeline - const postTimeNode1 = root.closest('[role=article]')?.querySelector('[href*="permalink"]') - const postIdMode1 = - postTimeNode1 ? - postTimeNode1 - .getAttribute('href') - ?.match(/story_fbid=(\d+)/g)?.[0] - .split('=')[1] ?? null - : null - - if (postIdMode1) return postIdMode1 - - const postTimeNode2 = root.closest('[role=article]')?.querySelector('[href*="posts"]') - const postIdMode2 = - postTimeNode2 ? - postTimeNode2 - .getAttribute('href') - ?.match(/posts\/(\w+)/g)?.[0] - .split('/')[1] ?? null - : null - if (postIdMode2 && /^-?\w+$/.test(postIdMode2)) return postIdMode2 - } catch { - return null - } - - const parent = node.current.parentElement - if (!parent) return null - const idNode = Array.from(parent.querySelectorAll('[id]')) - .map((x) => x.id.split(';')) - .filter((x) => x.length > 1) - if (!idNode.length) return null - return idNode[0][2] - } -} - -function getMetadataImages(node: DOMProxy): string[] { - if (node.destroyed) return [] - const parent = node.current.parentElement?.parentElement?.parentElement?.parentElement?.parentElement - if (!parent) return [] - const imgNodes = parent.querySelectorAll('img') || [] - if (!imgNodes.length) return [] - const imgUrls = Array.from(imgNodes, (node) => node.src).filter(Boolean) - if (!imgUrls.length) return [] - return imgUrls -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/collecting/theme.ts b/packages/mask/content-script/site-adaptors/facebook.com/collecting/theme.ts deleted file mode 100644 index 95038d95ec92..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/collecting/theme.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { ThemeMode } from '@masknet/web3-shared-base' -import { creator } from '../../../site-adaptor-infra/utils.js' - -function resolveThemeSettingsInner( - ref: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'], - cancel: AbortSignal, -) { - function updateThemeColor(isDarkMode: boolean) { - ref.value = { - ...ref.value, - mode: isDarkMode ? ThemeMode.Dark : ThemeMode.Light, - } - } - - updateThemeColor(document.documentElement.className.includes('dark-mode')) - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - updateThemeColor(!mutation.oldValue?.includes('dark-mode')) - }) - }) - - observer.observe(document.querySelector('html') as Node, { - attributes: true, - attributeOldValue: true, - attributeFilter: ['class'], - }) - - cancel.addEventListener('abort', () => observer.disconnect()) -} - -export const ThemeSettingsProviderFacebook: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider = { - recognized: creator.EmptyThemeSettingsProviderState(), - async start(cancel) { - resolveThemeSettingsInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/customization/custom.ts b/packages/mask/content-script/site-adaptors/facebook.com/customization/custom.ts deleted file mode 100644 index 209efb0758a8..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/customization/custom.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useMemo } from 'react' -import { produce, setAutoFreeze } from 'immer' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { fromRGB, shade, toRGB } from '@masknet/plugin-infra/content-script' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' - -export function useThemeFacebookVariant(baseTheme: Theme) { - const themeSettings = useThemeSettings() - - return useMemo(() => { - const primaryColorRGB = fromRGB(themeSettings.color)! - const primaryContrastColorRGB = fromRGB('rgb(255, 255, 255)') - setAutoFreeze(false) - - const FacebookTheme = produce(baseTheme, (theme) => { - theme.palette.primary = { - light: toRGB(shade(primaryColorRGB, 10)), - main: toRGB(primaryColorRGB), - dark: toRGB(shade(primaryColorRGB, -10)), - contrastText: toRGB(primaryContrastColorRGB), - } - theme.shape.borderRadius = 15 - theme.breakpoints.values = { xs: 0, sm: 687, md: 1024, lg: 1280, xl: 1920 } - theme.components = theme.components || {} - theme.components.MuiTypography = { - styleOverrides: { - root: { - // cspell:ignore SFNS - fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', sans-serif", - }, - }, - } - theme.components.MuiPaper = { - defaultProps: { - elevation: 0, - }, - } - theme.components.MuiTab = { - styleOverrides: { - root: { - textTransform: 'none', - }, - }, - } - }) - setAutoFreeze(true) - return unstable_createMuiStrictModeTheme(FacebookTheme) - }, [baseTheme, themeSettings]) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/customization/render-fragments.tsx b/packages/mask/content-script/site-adaptors/facebook.com/customization/render-fragments.tsx deleted file mode 100644 index db44affbc5bb..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/customization/render-fragments.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { DefaultRenderFragments, type RenderFragmentsContextType } from '@masknet/typed-message-react' -import { memo } from 'react' -import { Link } from '@mui/material' -import { useTagEnhancer } from '../../../../shared-ui/TypedMessageRender/Components/Text.js' - -function Hash(props: RenderFragmentsContextType.HashLinkProps | RenderFragmentsContextType.CashLinkProps) { - const text = props.children.slice(1) - const target = `/hashtag/${encodeURIComponent(text)}` - const { hasMatch, ...events } = useTagEnhancer('hash', text) - return -} -export const FacebookRenderFragments: RenderFragmentsContextType = { - // AtLink: not supported - HashLink: memo(Hash), - // Facebook has no native cashtag support. Treat it has a hash. - CashLink: memo(Hash), - Image: memo((props) => { - if (props.src.includes('emoji.php')) return null - return - }), -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/index.ts b/packages/mask/content-script/site-adaptors/facebook.com/index.ts deleted file mode 100644 index 9cde3d959667..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { facebookBase } from './base.js' - -defineSiteAdaptorUI({ - ...facebookBase, - load: () => import('./ui-provider.js'), - hotModuleReload(callback) { - if (import.meta.webpackHot) { - import.meta.webpackHot.accept('./ui-provider.ts', async () => { - callback((await import('./ui-provider.js')).default) - }) - } - }, -}) diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/Avatar/index.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/Avatar/index.tsx deleted file mode 100644 index 78cd47c620d4..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/Avatar/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { inpageAvatarSelector } from '../../utils/selector.js' - -export async function injectAvatar(signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(inpageAvatarSelector()).useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele.firstChild as HTMLElement - // create stacking context - ele.style.position = 'relative' - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -

, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/Banner.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/Banner.tsx deleted file mode 100644 index efe8a37c1d33..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/Banner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { Banner } from '../../../components/Welcomes/Banner.js' - -const composeBox: LiveSelector = new LiveSelector() - .querySelectorAll( - '[role="dialog"] form [role="button"][tabindex="0"], [role="dialog"] form [role="button"][tabindex="-1"]', - ) - .map((x) => x.parentElement) - .at(-1) - -export function injectBannerAtFacebook(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(composeBox.clone()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - - - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/Composition.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/Composition.tsx deleted file mode 100644 index 24900ab5e1f9..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/Composition.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useCallback } from 'react' -import { makeStyles } from '@masknet/theme' -import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { CrossIsolationMessages } from '@masknet/shared-base' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { Composition } from '../../../components/CompositionDialog/Composition.js' -import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint.js' -import { taskOpenComposeBoxFacebook, taskCloseNativeComposeBoxFacebook } from '../automation/openComposeBox.js' -import { startWatch } from '../../../utils/startWatch.js' - -const useStyles = makeStyles()(() => ({ - tooltip: { - borderRadius: 8, - padding: 8, - marginBottom: '0 !important', - fontSize: 12, - background: 'rgba(0,0,0,.75)', - boxShadow: '0 4px 10px 0 rgba(0,0,0,.5)', - color: '#ddd', - }, -})) - -function isGroup() { - const matched = location.href.match(/\/groups/) - if (!matched) return false - return matched[0] -} - -const composeBox: LiveSelector = - isGroup() ? - new LiveSelector() - .querySelector('[id="toolbarLabel"]') - .closest(1) - .querySelector('div:nth-child(2) > div:nth-child(4)') - : new LiveSelector() - .querySelectorAll( - '[role="dialog"] form > div:first-child > div:first-child > div:first-child > div:first-child > div:first-child > div:last-child > div:first-child > div:last-child > div > div', - ) - .at(-2) - -export function injectCompositionFacebook(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(composeBox.clone()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() - - signal.addEventListener( - 'abort', - CrossIsolationMessages.events.compositionDialogEvent.on((data) => { - if (data.reason === 'popup') return - if (data.open === false) { - if (data.options?.isOpenFromApplicationBoard) taskCloseNativeComposeBoxFacebook() - return - } - taskOpenComposeBoxFacebook(data.content || '', data.options) - }), - ) -} -function UI() { - const { classes } = useStyles() - const onHintButtonClicked = useCallback( - () => CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ reason: 'popup', open: true }), - [], - ) - return ( - - - - - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx deleted file mode 100644 index b594950aae38..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useLayoutEffect, useState } from 'react' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { useLocationChange } from '@masknet/shared-base-ui' -import { NFTAvatarButton } from '@masknet/plugin-avatar' -import { startWatch } from '../../../../utils/startWatch.js' -import { searchFacebookEditProfileSelector, searchFacebookProfileSettingButtonSelector } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' - -export function injectOpenNFTAvatarEditProfileButton(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchFacebookProfileSettingButtonSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { untilVisible: true, signal }).render( - , - ) -} - -interface StyleProps { - minHeight: number - fontSize: number - marginTop: number - backgroundColor?: string - color?: string -} - -const useStyles = makeStyles()((theme, props) => ({ - root: { - minHeight: props.minHeight, - fontSize: props.fontSize, - marginTop: props.marginTop, - backgroundColor: theme.palette.maskColor.main, - marginRight: theme.spacing(0.5), - marginLeft: theme.spacing(1.25), - borderRadius: '6px !important', - border: 'none !important', - color: props.color, - }, -})) - -export function openNFTAvatarSettingDialog() { - const editDom = searchFacebookEditProfileSelector().evaluate() - editDom?.click() -} - -function OpenNFTAvatarEditProfileButtonInFacebook() { - const [style, setStyle] = useState({ minHeight: 36, fontSize: 15, marginTop: 6 }) - - const setStyleWithSelector = () => { - const editDom = searchFacebookProfileSettingButtonSelector().evaluate() - if (!editDom) return - - const buttonDom = editDom.querySelector('div > div[role="button"]') - if (!buttonDom) return - - const editCss = window.getComputedStyle(editDom) - const buttonCss = window.getComputedStyle(buttonDom) - - setStyle({ - fontSize: Number(editCss.fontSize.replace('px', '')), - marginTop: Number(editCss.paddingTop.replace('px', '')), - minHeight: 36, - backgroundColor: buttonCss.backgroundColor, - color: buttonCss.color, - }) - } - - useLayoutEffect(() => { - setStyleWithSelector() - }, []) - - useLocationChange(() => { - setStyleWithSelector() - }) - - const { classes } = useStyles(style) - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInFacebook.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInFacebook.tsx deleted file mode 100644 index f88826ef0d95..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInFacebook.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useLayoutEffect, useMemo, useSyncExternalStore } from 'react' -import { useWindowSize } from 'react-use' -import { max } from 'lodash-es' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { AvatarStore } from '@masknet/web3-providers' -import { NFTBadge } from '@masknet/plugin-avatar' -import { searchFacebookAvatarSelector } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { getAvatarId } from '../../utils/user.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { useSaveAvatarInFacebook } from './useSaveAvatarInFacebook.js' - -export function injectNFTAvatarInFacebook(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchFacebookAvatarSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( - , - ) - return -} - -const useStyles = makeStyles()(() => ({ - root: { - position: 'absolute', - textAlign: 'center', - color: 'white', - width: '100%', - height: '100%', - top: 0, - left: 0, - }, - text: { - fontSize: '20px !important', - fontWeight: 700, - }, - icon: { - width: '19px !important', - height: '19px !important', - }, -})) - -function NFTAvatarInFacebook() { - const { classes } = useStyles() - - const identity = useCurrentVisitingIdentity() - const savedAvatar = useSaveAvatarInFacebook(identity) - - const store = useSyncExternalStore(AvatarStore.subscribe, AvatarStore.getSnapshot) - const avatar = savedAvatar ?? store.retrieveAvatar(identity.identifier?.userId) - const token = store.retrieveToken(identity.identifier?.userId) - - const windowSize = useWindowSize() - const showAvatar = getAvatarId(identity.avatar ?? '') === avatar?.avatarId - - const size = useMemo(() => { - const ele = searchFacebookAvatarSelector().evaluate() - if (!ele) return 0 - const style = window.getComputedStyle(ele) - return max([148, Number.parseInt(style.width.replace('px', '') ?? 0, 10)]) - }, [windowSize, avatar]) - - // #region clear white border - useLayoutEffect(() => { - const node = searchFacebookAvatarSelector().closest(3).evaluate() - if (!node) return - - if (showAvatar) { - node.setAttribute('style', 'padding: 0') - } else { - node.removeAttribute('style') - } - }) - // #endregion - - if (!avatar || !token || !size || !showAvatar) return null - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInTimeline.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInTimeline.tsx deleted file mode 100644 index e7f51e11df88..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/NFTAvatarInTimeline.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { memo } from 'react' -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { makeStyles } from '@masknet/theme' -import { NFTBadgeTimeline } from '@masknet/plugin-avatar' -import { DOMProxy, type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { getInjectNodeInfo } from '../../utils/avatar.js' -import { searchFaceBookPostAvatarSelector } from '../../utils/selector.js' - -const useStyles = makeStyles()(() => ({ - root: { - transform: 'scale(1)!important', - }, -})) - -const TimelineRainbow = memo( - ({ userId, avatarId, width, height }: { userId: string; avatarId: string; width: number; height: number }) => { - const { classes } = useStyles() - return ( -
- -
- ) - }, -) - -function getFacebookId(element: HTMLElement | SVGElement) { - const node = element.parentNode?.parentNode as HTMLLinkElement - if (!node) return '' - - const url = new URL(node.href, location.href) - if (url.pathname === '/profile.php' && url.searchParams.get('id')) { - return url.searchParams.get('id') - } - - if (url.pathname.includes('/groups')) { - const match = url.pathname.match(/\/user\/(\w+)\//) - if (!match) return '' - return match[1] - } - - return url.pathname.replace('/', '') -} - -function _(selector: () => LiveSelector, signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(selector()).useForeach((element, key) => { - let remove = noop - - const run = async () => { - const facebookId = getFacebookId(element) - if (!facebookId) return - - const info = getInjectNodeInfo(element) - if (!info) return - - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = info.element - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -
- -
, - ) - - remove = root.destroy - } - - run() - - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => remove(), - } - }), - signal, - ) -} - -export async function injectUserNFTAvatarAtFacebook(signal: AbortSignal) { - _(searchFaceBookPostAvatarSelector, signal) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/ProfileNFTAvatar.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/ProfileNFTAvatar.tsx deleted file mode 100644 index 0cb326b908ff..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/ProfileNFTAvatar.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useCallback, useEffect } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { NFTAvatar, toPNG } from '@masknet/plugin-avatar' -import { hookInputUploadOnce } from '@masknet/injected-script' -import type { SelectTokenInfo } from '@masknet/plugin-avatar' -import { MaskMessages, NetworkPluginID } from '@masknet/shared-base' -import { ChainId, SchemaType } from '@masknet/web3-shared-evm' -import { - searchFacebookAvatarListSelector, - searchFacebookAvatarOpenFilesSelector, - searchFacebookConfirmAvatarImageSelector, - searchFacebookSaveAvatarButtonSelector, -} from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { getAvatarId } from '../../utils/user.js' - -export async function injectProfileNFTAvatarInFacebook(signal: AbortSignal) { - // The first step in setting an avatar - const watcher = new MutationObserverWatcher(searchFacebookAvatarListSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( - , - ) - - // The second step in setting an avatar - const saveButtonWatcher = new MutationObserverWatcher(searchFacebookSaveAvatarButtonSelector()).useForeach( - (node, key, proxy) => { - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render() - return () => root.destroy() - }, - ) - - startWatch(saveButtonWatcher, signal) -} - -const useStyles = makeStyles()({ - // eslint-disable-next-line tss-unused-classes/unused-classes - root: { - padding: '8px 0', - margin: '0 16px', - }, -}) - -async function changeImageToActiveElements(image: File | Blob): Promise { - const imageBuffer = await image.arrayBuffer() - hookInputUploadOnce('image/png', 'avatar.png', new Uint8Array(imageBuffer)) - searchFacebookAvatarOpenFilesSelector().evaluate()?.click() -} - -function NFTAvatarInFacebookFirstStep() { - const { classes } = useStyles() - - const identity = useCurrentVisitingIdentity() - - const onChange = useCallback( - async (info: SelectTokenInfo) => { - if (!identity.identifier) return - if (!info.token.metadata?.imageURL || !info.token.contract?.address) return - - const image = await toPNG(info.token.metadata.imageURL) - if (!image) return - - await changeImageToActiveElements(image) - - MaskMessages.events.NFTAvatarUpdated.sendToLocal({ - userId: identity.identifier.userId, - avatarId: '', - address: info.token.contract.address, - tokenId: info.token.tokenId, - pluginID: info.pluginID ?? NetworkPluginID.PLUGIN_EVM, - chainId: info.token.chainId ?? ChainId.Mainnet, - schema: info.token.schema ?? SchemaType.ERC721, - }) - }, - [identity], - ) - - return -} - -function NFTAvatarInFacebookSecondStep() { - useEffect(() => { - const save = searchFacebookSaveAvatarButtonSelector().evaluate().at(0) - if (!save) return - - const handler = () => { - const image = searchFacebookConfirmAvatarImageSelector().evaluate() - if (!image) return - - const imageURL = image.getAttribute('src') - if (!imageURL) return - - const avatarId = getAvatarId(imageURL) - - if (avatarId) { - MaskMessages.events.NFTAvatarUpdated.sendToLocal({ - userId: '', - address: '', - tokenId: '', - avatarId, - }) - } - } - - save.addEventListener('click', handler) - - return () => save.removeEventListener('click', handler) - }, []) - return null -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/useSaveAvatarInFacebook.ts b/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/useSaveAvatarInFacebook.ts deleted file mode 100644 index bbfbf4e76b8c..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/NFT/useSaveAvatarInFacebook.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { pickBy } from 'lodash-es' -import { useCallback, useEffect, useState } from 'react' -import { useChainContext } from '@masknet/web3-hooks-base' -import { useSaveStringStorage } from '@masknet/plugin-avatar' -import { MaskMessages, NetworkPluginID, type NFTAvatarEvent } from '@masknet/shared-base' -import { getAvatarId } from '../../utils/user.js' -import type { IdentityResolved } from '@masknet/plugin-infra' -import { useAsync } from 'react-use' -import type { AvatarNextID } from '@masknet/web3-providers/types' - -export function useSaveAvatarInFacebook(identity: IdentityResolved) { - const { account } = useChainContext() - - const [NFTEvent, setNFTEvent] = useState(null) - const saveNFTAvatar = useSaveStringStorage(NetworkPluginID.PLUGIN_EVM) - - const onSave = useCallback(async () => { - if (!account || !identity.identifier) return - - const isNFTEventValid = NFTEvent?.address && NFTEvent?.tokenId && NFTEvent?.avatarId - if (!isNFTEventValid) return - - try { - const savedAvatar = await saveNFTAvatar(identity.identifier.userId, account, { - ...NFTEvent, - avatarId: getAvatarId(identity.avatar ?? ''), - } as AvatarNextID) - setNFTEvent(null) - if (savedAvatar) return savedAvatar - return - } catch (error) { - setNFTEvent(null) - return - } - }, [account, NFTEvent, identity]) - - useEffect(() => { - return MaskMessages.events.NFTAvatarUpdated.on((data) => - setNFTEvent((prev) => { - if (!prev) return data - return { ...prev, ...pickBy(data, (item) => !!item) } - }), - ) - }, []) - - const { value } = useAsync(() => { - return onSave() - }, [identity.avatar]) - - return value -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/PostInspector.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/PostInspector.tsx deleted file mode 100644 index 84f9062a879d..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/PostInspector.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import type { DOMProxy } from '@dimensiondev/holoflows-kit' -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/inject/PostInspector.js' -import { Flags } from '@masknet/flags' - -const map = new WeakMap() -function getShadowRoot(node: HTMLElement) { - if (map.has(node)) return map.get(node)! - const dom = node.attachShadow(Flags.shadowRootInit) - map.set(node, dom) - return dom -} -export function injectPostInspectorFacebook(signal: AbortSignal, current: PostInfo) { - clickSeeMore(current.rootElement.current?.parentElement) - return injectPostInspectorDefault({ - zipPost(node) { - zipEncryptedPostContent(node) - zipPostLinkPreview(node) - }, - injectionPoint: (post) => getShadowRoot(post.suggestedInjectionPoint), - })(current, signal) -} -function zipPostLinkPreview(node: DOMProxy) { - if (node.destroyed) return - const parentEle = node.current.parentElement - if (!parentEle) return - const img = - parentEle.querySelector('a[href*="maskbook.io"] img') ?? - parentEle.querySelector('a[href*="mask.io"] img') ?? - parentEle.querySelector('a[href*="maskbook.com"] img') - const parent = img?.closest('span') - if (img && parent) { - parent.style.display = 'none' - } -} -function zipEncryptedPostContent(node: DOMProxy) { - if (node.destroyed) return - const parent = node.current.parentElement - // It's image based encryption, skip zip post. - if (!node.current.innerText.includes('\uD83C\uDFBC')) return - // Style modification for repost - if (!node.current.className.includes('userContent') && node.current.innerText.length > 0) { - node.after.setAttribute( - 'style', - `border: 1px solid #ebedf0; -display: block; -border-top: none; -border-bottom: none; -margin-bottom: 0; -padding: 0 10px;`, - ) - } - if (parent) { - // post content - const p = parent.querySelector('p') - if (p) { - p.style.display = 'block' - p.style.maxHeight = '20px' - p.style.overflow = 'hidden' - p.style.marginBottom = '0' - } - } -} -export function clickSeeMore(node: HTMLElement | undefined | null) { - if (!node) return - const more = node.querySelector( - '[role=article] span[dir="auto"] div[dir="auto"] [role="button"]', - ) - - if (more && node.querySelector('img[alt="\uD83C\uDFBC"]')) { - const trap = (e: Event) => { - e.preventDefault() - } - more.parentNode!.addEventListener('click', trap) - more.click() - setTimeout(() => { - if (more.parentNode) more.parentNode.removeEventListener('click', trap) - }, 0) - } -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/PostReplacer.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/PostReplacer.tsx deleted file mode 100644 index 3bd99a6ae383..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/PostReplacer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { injectPostReplacer } from '../../../site-adaptor-infra/defaults/inject/PostReplacer.js' -import type { PostInfo } from '@masknet/plugin-infra/content-script' - -function resolveContentNode(node: HTMLElement) { - return node.querySelector('[role=article] div[dir="auto"] > [id] > div > div > span') -} - -export function injectPostReplacerAtFacebook(signal: AbortSignal, current: PostInfo) { - return injectPostReplacer({ - zipPost(node) { - if (node.destroyed) return - const langNode = resolveContentNode(node.current) - if (langNode) langNode.style.display = 'none' - }, - unzipPost(node) { - if (node.destroyed || !node.current) return - const langNode = resolveContentNode(node.current) - if (langNode) langNode.style.display = 'unset' - }, - })(current, signal) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileContent.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileContent.tsx deleted file mode 100644 index a0f568a61d70..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileContent.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { getMaskColor, makeStyles } from '@masknet/theme' -import { ProfileTabContent } from '../../../components/InjectedComponents/ProfileTabContent.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { profileSectionSelector, searchProfileTabPageSelector } from '../utils/selector.js' - -function injectProfileTabContentState(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileTabPageSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} - -export function injectProfileTabContentAtFacebook(signal: AbortSignal) { - injectProfileTabContentState(signal) -} - -function getStyleProps() { - const EMPTY_STYLE = {} as CSSStyleDeclaration - const profileSection = profileSectionSelector().evaluate() - const style = profileSection ? window.getComputedStyle(profileSection) : EMPTY_STYLE - return { - borderRadius: style.borderRadius, - backgroundColor: style.backgroundColor, - fontFamily: style.fontFamily, - boxShadow: style.boxShadow, - } -} - -const useStyles = makeStyles()((theme) => { - const props = getStyleProps() - - return { - root: { - position: 'relative', - marginBottom: 16, - paddingBottom: 16, - background: props.backgroundColor, - borderRadius: props.borderRadius, - boxShadow: props.boxShadow, - }, - text: { - paddingTop: 29, - paddingBottom: 29, - '& > p': { - fontSize: 28, - fontFamily: props.fontFamily, - fontWeight: 700, - color: getMaskColor(theme).textPrimary, - }, - }, - button: { - backgroundColor: props.backgroundColor, - color: 'white', - marginTop: 18, - '&:hover': { - backgroundColor: props.backgroundColor, - }, - }, - } -}) - -function ProfileTabContentAtFacebook() { - const { classes } = useStyles() - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileCover.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileCover.tsx deleted file mode 100644 index 135133cfb401..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileCover.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { searchFacebookProfileCoverSelector } from '../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { ProfileCover } from '../../../components/InjectedComponents/ProfileCover.js' - -export function injectFacebookProfileCover(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchFacebookProfileCoverSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -function ProfileCoverAtFacebook() { - return -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileTab.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileTab.tsx deleted file mode 100644 index 3801cc98556a..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/ProfileTab.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { useEffect, useState } from 'react' -import { debounce } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { - profileTabSelectedSelector, - profileTabUnselectedSelector, - searchProfileTabSelector, - web3TabSelector, -} from '../utils/selector.js' -import { ProfileTab } from '../../../components/InjectedComponents/ProfileTab.js' - -function getStyleProps() { - const EMPTY_STYLE = {} as CSSStyleDeclaration - const divEle = profileTabUnselectedSelector().evaluate()?.querySelector('div') as Element - const spanEle = profileTabUnselectedSelector().evaluate()?.querySelector('div span') as Element - const selectedSpanEle = profileTabSelectedSelector().evaluate()?.querySelector('div span') as Element - const divStyle = divEle ? window.getComputedStyle(divEle) : EMPTY_STYLE - const spanStyle = spanEle ? window.getComputedStyle(spanEle) : EMPTY_STYLE - const selectedSpanStyle = selectedSpanEle ? window.getComputedStyle(selectedSpanEle) : EMPTY_STYLE - - return { - color: 'var(--secondary-text)', - font: spanStyle.font, - fontSize: spanStyle.fontSize, - padding: divStyle.paddingLeft, - height: divStyle.height ?? '60px', - hover: 'var(--hover-overlay)', - line: selectedSpanStyle.color, - } -} - -const useStyles = makeStyles()((theme) => { - const props = getStyleProps() - - return { - root: { - '&:hover': { - cursor: 'pointer', - }, - height: props.height, - display: 'flex', - flexDirection: 'column', - alignContent: 'center', - justifyContent: 'center', - padding: '4px 0', - boxSizing: 'border-box', - }, - button: { - flex: 1, - zIndex: 1, - position: 'relative', - display: 'flex', - minWidth: 56, - justifyContent: 'center', - alignItems: 'center', - textAlign: 'center', - padding: theme.spacing(0, props.padding || 0), - color: props.color, - font: props.font, - fontSize: props.fontSize, - fontWeight: 600, - '&:hover': { - backgroundColor: props.hover, - color: props.color, - }, - borderRadius: 6, - }, - selected: { - color: 'var(--accent)', - }, - line: { - borderRadius: 9999, - position: 'absolute', - bottom: -4, - width: '100%', - alignSelf: 'center', - height: 3, - backgroundColor: 'var(--accent)', - }, - } -}) - -function styleTab(textColor: string, borderColor: string) { - const ele = profileTabSelectedSelector().evaluate() - if (!ele) return - - const textEle = ele.querySelector('span') - const borderEle = ele.querySelector('span ~ div:last-child') as HTMLDivElement - if (!textEle || !borderEle) return - textEle.style.color = textColor - borderEle.style.backgroundColor = borderColor - - const iconEle = ele.querySelector('svg') - if (!iconEle) return - iconEle.style.fill = textColor -} -function ProfileTabAtFacebook() { - const { classes } = useStyles() - const [action, setAction] = useState('reset') - - function clear() { - setAction('clear') - styleTab(getStyleProps().color, 'transparent') - } - - function reset() { - setAction('reset') - styleTab('', getStyleProps().line) - } - - // handle cleared tab will be reactivated after scroll - useEffect(() => { - const handler = debounce(() => { - if (action === 'clear') { - clear() - } - }, 1000) - window.addEventListener('scroll', handler) - - return () => { - window.removeEventListener('scroll', handler) - } - }, [action]) - - return ( - } - /> - ) -} - -export function injectProfileTabAtFacebook(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileTabSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() - - const assign = () => { - const web3Tab = web3TabSelector().evaluate() - if (web3Tab) web3Tab.style.float = 'left' - } - - new MutationObserverWatcher(web3TabSelector()) - .addListener('onChange', assign) - .addListener('onAdd', assign) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - }, - signal, - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/SearchResultInspector.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/SearchResultInspector.tsx deleted file mode 100644 index be89ae685c52..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/SearchResultInspector.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { SearchResultInspector } from '../../../components/InjectedComponents/SearchResultInspector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { searchResultHeadingSelector } from '../utils/selector.js' - -export function injectSearchResultInspectorAtFacebook(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchResultHeadingSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/Toolbar.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/Toolbar.tsx deleted file mode 100644 index a9e77bd03cd4..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/Toolbar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { - toolboxInSidebarSelector, - toolboxInSidebarSelectorWithNoLeftRailStart, - toolboxInSpecialSidebarSelector, -} from '../utils/selector.js' -import { ToolboxAtFacebook } from './ToolbarUI.js' - -function hasSpecificLeftRailStartBar() { - const ele = document - .querySelector('[role="banner"] [role="navigation"] > ul > li:last-child a[href="/bookmarks/"]') - ?.closest('li') - if (!ele) return true - const style = window.getComputedStyle(ele) - return style.display === 'none' -} - -function isNormalLeftRailStartBar() { - return !!document.querySelector('[data-pagelet="LeftRail"]') -} - -export function injectToolboxHintAtFacebook(signal: AbortSignal, category: 'wallet' | 'application') { - const watcher = new MutationObserverWatcher( - isNormalLeftRailStartBar() ? toolboxInSidebarSelector() : toolboxInSpecialSidebarSelector(), - ) - startWatch(watcher, signal) - const hasTopNavBar = !!document.querySelector('#ssrb_top_nav_start ~ [role="banner"]') - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - , - ) - - const watcherNoLeftRailStart = new MutationObserverWatcher(toolboxInSidebarSelectorWithNoLeftRailStart()) - startWatch(watcherNoLeftRailStart, signal) - attachReactTreeWithContainer(watcherNoLeftRailStart.firstDOMProxy.afterShadow, { signal }).render( - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/injection/ToolbarUI.tsx b/packages/mask/content-script/site-adaptors/facebook.com/injection/ToolbarUI.tsx deleted file mode 100644 index e786ea979c0b..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/injection/ToolbarUI.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { styled, ListItemButton, Typography, ListItemIcon } from '@mui/material' -import { ToolboxHintUnstyled } from '../../../components/InjectedComponents/ToolboxUnstyled.js' -import { useMemo } from 'react' - -const Container = styled('div')` - padding: 0 4px; -` -const ContainerHasNavBar = styled('div')` - padding: 0 8px; -` - -const Item = styled(ListItemButton)` - border-radius: 8px; - padding-left: 10px; -` -const Text = styled(Typography)` - font-size: 0.9375rem; - /* This CSS variable is inherit from Facebook. */ - color: var(--primary-text); - font-weight: 500; - padding-left: 0.1rem; -` -const Icon = styled(ListItemIcon, { - shouldForwardProp(name) { - return name !== 'hasTopNavBar' && name !== 'hasSpecificLeftRailStartBar' - }, -})<{ - hasTopNavBar: boolean - hasSpecificLeftRailStartBar: boolean -}>` - min-width: ${(props) => - !props.hasSpecificLeftRailStartBar ? '24px' - : props.hasTopNavBar ? '46px' - : 'auto'}; - margin-right: ${(props) => (props.hasTopNavBar && props.hasSpecificLeftRailStartBar ? '0px' : '12px')}; - padding-left: 4px; -` - -export function ToolboxAtFacebook(props: { - category: 'wallet' | 'application' - hasTopNavBar: boolean - hasSpecificLeftRailStartBar: boolean -}) { - const ListItemIcon = useMemo(() => { - return ({ children }: React.PropsWithChildren<{}>) => ( - - {children} - - ) - }, [props.hasTopNavBar, props.hasSpecificLeftRailStartBar]) - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/shared.ts b/packages/mask/content-script/site-adaptors/facebook.com/shared.ts deleted file mode 100644 index 5de8399c5b29..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/shared.ts +++ /dev/null @@ -1,38 +0,0 @@ -import urlcat from 'urlcat' -import type { SiteAdaptor } from '@masknet/types' -import { facebookBase } from './base.js' -import { getPostUrlAtFacebook, isValidFacebookUsername } from './utils/parse-username.js' -import { type ProfileIdentifier, type PostIdentifier } from '@masknet/shared-base' -import { hasPayloadLike } from '../../utils/index.js' -import { createSiteAdaptorSpecializedPostContext } from '../../site-adaptor-infra/utils/create-post-context.js' -import { openWindow } from '@masknet/shared-base-ui' - -function getPostURL(post: PostIdentifier): URL | null { - return new URL(getPostUrlAtFacebook(post)) -} -function getProfileURL(profile: ProfileIdentifier): URL | null { - return new URL('https://www.facebook.com') -} -function getShareURL(text: string): URL | null { - return new URL( - urlcat('https://www.facebook.com/sharer/sharer.php', { - quote: text, - u: 'mask.io', - }), - ) -} -export const facebookShared: SiteAdaptor.Shared & SiteAdaptor.Base = { - ...facebookBase, - utils: { - isValidUsername: (v) => !!isValidFacebookUsername(v), - getPostURL, - getProfileURL, - share(message) { - openWindow(getShareURL?.(message)) - }, - createPostContext: createSiteAdaptorSpecializedPostContext(facebookBase.networkIdentifier, { - hasPayloadLike, - getURLFromPostIdentifier: getPostURL, - }), - }, -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/ui-provider.ts b/packages/mask/content-script/site-adaptors/facebook.com/ui-provider.ts deleted file mode 100644 index fd898b8faf63..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/ui-provider.ts +++ /dev/null @@ -1,228 +0,0 @@ -/* eslint-disable tss-unused-classes/unused-classes */ -import type { SiteAdaptorUI } from '@masknet/types' -import { makeStyles } from '@masknet/theme' -import { ProfileIdentifier, EnhanceableSite } from '@masknet/shared-base' -import { stateCreator } from '../../site-adaptor-infra/utils.js' -import { facebookBase } from './base.js' -import { facebookShared } from './shared.js' -import { getProfilePageUrlAtFacebook } from './utils/parse-username.js' -import { taskOpenComposeBoxFacebook } from './automation/openComposeBox.js' -import { pasteTextToCompositionFacebook } from './automation/pasteTextToComposition.js' -import { CurrentVisitingIdentityProviderFacebook, IdentityProviderFacebook } from './collecting/identity.js' -import { InitAutonomousStateProfiles } from '../../site-adaptor-infra/defaults/state/InitProfiles.js' -import { injectCompositionFacebook } from './injection/Composition.js' -import { injectBannerAtFacebook } from './injection/Banner.js' -import { injectPostCommentsDefault } from '../../site-adaptor-infra/defaults/inject/Comments.js' -import { pasteToCommentBoxFacebook } from './automation/pasteToCommentBoxFacebook.js' -import { injectCommentBoxDefaultFactory } from '../../site-adaptor-infra/defaults/inject/CommentBox.js' -import { injectPostInspectorFacebook } from './injection/PostInspector.js' -import getSearchedKeywordAtFacebook from './collecting/getSearchedKeyword.js' -import { injectSearchResultInspectorAtFacebook } from './injection/SearchResultInspector.js' -import { PostProviderFacebook } from './collecting/posts.js' -import { ThemeSettingsProviderFacebook } from './collecting/theme.js' -import { pasteImageToCompositionDefault } from '../../site-adaptor-infra/defaults/automation/AttachImageToComposition.js' -import { injectPageInspectorDefault } from '../../site-adaptor-infra/defaults/inject/PageInspector.js' -import { createTaskStartSetupGuideDefault } from '../../site-adaptor-infra/defaults/inject/StartSetupGuide.js' -import { useThemeFacebookVariant } from './customization/custom.js' -import { activatedSiteAdaptor_state } from '../../site-adaptor-infra/index.js' -import { injectToolboxHintAtFacebook as injectToolboxAtFacebook } from './injection/Toolbar.js' -import { injectProfileNFTAvatarInFacebook } from './injection/NFT/ProfileNFTAvatar.js' -import { injectNFTAvatarInFacebook } from './injection/NFT/NFTAvatarInFacebook.js' -import { injectUserNFTAvatarAtFacebook } from './injection/NFT/NFTAvatarInTimeline.js' -import { - injectOpenNFTAvatarEditProfileButton, - openNFTAvatarSettingDialog, -} from './injection/NFT/NFTAvatarEditProfile.js' -import { injectProfileTabAtFacebook } from './injection/ProfileTab.js' -import { injectPostReplacerAtFacebook } from './injection/PostReplacer.js' -import { injectProfileTabContentAtFacebook } from './injection/ProfileContent.js' -import { FacebookRenderFragments } from './customization/render-fragments.js' -import { enableFbStyleTextPayloadReplace } from '../../../shared-ui/TypedMessageRender/transformer.js' -import { injectFacebookProfileCover } from './injection/ProfileCover.js' -import { injectAvatar } from './injection/Avatar/index.js' - -const useInjectedDialogClassesOverwriteFacebook = makeStyles()((theme) => { - const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` - return { - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - [smallQuery]: { - display: 'block !important', - }, - }, - container: { - alignItems: 'center', - }, - paper: { - width: '600px !important', - maxWidth: 'none', - boxShadow: 'none', - backgroundImage: 'none', - [smallQuery]: { - display: 'block !important', - borderRadius: '0 !important', - }, - }, - dialogTitle: { - display: 'flex', - alignItems: 'center', - padding: '3px 16px', - borderBottom: `1px solid ${theme.palette.mode === 'dark' ? '#2f3336' : '#eff3f4'}`, - '& > h2': { - display: 'inline-block', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - [smallQuery]: { - display: 'flex', - justifyContent: 'space-between', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px 11px !important', - }, - }, - dialogContent: { - padding: 16, - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px !important', - }, - }, - dialogActions: { - padding: '6px 16px', - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px !important', - }, - }, - dialogBackdropRoot: { - backgroundColor: theme.palette.mode === 'dark' ? 'rgba(110, 118, 125, 0.4)' : 'rgba(0, 0, 0, 0.4)', - }, - } -}) - -const facebookUI: SiteAdaptorUI.Definition = { - ...facebookBase, - ...facebookShared, - automation: { - redirect: { - gotoProfilePage(profile) { - // there is no PWA way on Facebook desktop. - // mobile not tested - location.assign(getProfilePageUrlAtFacebook(profile)) - }, - gotoNewsFeed() { - const homeLink = document.querySelector( - [ - '[data-click="bluebar_logo"] a[href]', - '#feed_jewel a[href]', // mobile - ].join(','), - ) - if (homeLink) homeLink.click() - else if (location.pathname !== '/') location.assign('/') - }, - }, - maskCompositionDialog: { open: taskOpenComposeBoxFacebook }, - nativeCompositionDialog: { - attachText: pasteTextToCompositionFacebook, - // TODO: make a better way to detect - attachImage: pasteImageToCompositionDefault(() => false), - }, - nativeCommentBox: { - attachText: pasteToCommentBoxFacebook, - }, - }, - collecting: { - identityProvider: IdentityProviderFacebook, - currentVisitingIdentityProvider: CurrentVisitingIdentityProviderFacebook, - postsProvider: PostProviderFacebook, - themeSettingsProvider: ThemeSettingsProviderFacebook, - getSearchedKeyword: getSearchedKeywordAtFacebook, - }, - customization: { - sharedComponentOverwrite: { - InjectedDialog: { - classes: useInjectedDialogClassesOverwriteFacebook, - }, - }, - componentOverwrite: { - RenderFragments: FacebookRenderFragments, - }, - useTheme: useThemeFacebookVariant, - }, - init(signal) { - const profiles = stateCreator.profiles() - InitAutonomousStateProfiles(signal, profiles, facebookShared.networkIdentifier) - enableFbStyleTextPayloadReplace() - return { profiles } - }, - injection: { - newPostComposition: { - start: injectCompositionFacebook, - supportedOutputTypes: { - text: true, - image: true, - }, - supportedInputTypes: { - text: true, - image: true, - }, - }, - userBadge: undefined, - searchResult: injectSearchResultInspectorAtFacebook, - banner: injectBannerAtFacebook, - commentComposition: { - compositionBox: injectPostCommentsDefault(), - commentInspector: injectCommentBoxDefaultFactory( - pasteToCommentBoxFacebook, - undefined, - undefined, - (node) => { - setTimeout(() => { - node.after.style.flexBasis = '100%' - node.current.parentElement!.style.flexWrap = 'wrap' - }) - }, - ), - }, - userAvatar: injectUserNFTAvatarAtFacebook, - enhancedProfileNFTAvatar: injectProfileNFTAvatarInFacebook, - profileAvatar: injectNFTAvatarInFacebook, - profileCover: injectFacebookProfileCover, - openNFTAvatar: injectOpenNFTAvatarEditProfileButton, - openNFTAvatarSettingDialog, - postReplacer: injectPostReplacerAtFacebook, - postInspector: injectPostInspectorFacebook, - pageInspector: injectPageInspectorDefault(), - setupWizard: createTaskStartSetupGuideDefault(), - toolbox: injectToolboxAtFacebook, - profileTab: injectProfileTabAtFacebook, - profileTabContent: injectProfileTabContentAtFacebook, - avatar: injectAvatar, - }, - configuration: { - steganography: { - // ! Change this might be a breaking change ! - password() { - const id = - IdentityProviderFacebook.recognized.value.identifier?.userId || - activatedSiteAdaptor_state!.profiles.value?.[0].identifier.userId - if (!id) throw new Error('Cannot figure out password') - return ProfileIdentifier.of(EnhanceableSite.Facebook, id) - .expect(`${id} should be a valid user id`) - .toText() - }, - }, - }, -} -export default facebookUI diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/avatar.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/avatar.ts deleted file mode 100644 index a917beac7d9c..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/avatar.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getAvatarId } from './user.js' - -export function getInjectNodeInfo(element: HTMLElement | SVGElement) { - const imgEle = element.querySelector('image') - if (!imgEle) return - - const nftDom = imgEle.parentNode?.parentNode as HTMLElement - - const width = Number(window.getComputedStyle(nftDom).width.replace('px', '') ?? 0) - const height = Number(window.getComputedStyle(nftDom).height.replace('px', '') ?? 0) - - const avatarId = getAvatarId(imgEle.href.baseVal) - if (!avatarId) return - - return { element: nftDom, width, height, avatarId } -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/getProfileIdentifier.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/getProfileIdentifier.ts deleted file mode 100644 index ef1c24f775e1..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/getProfileIdentifier.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ProfileIdentifier, EnhanceableSite } from '@masknet/shared-base' -import type { IdentityResolved } from '@masknet/plugin-infra' -import Services from '#services' -import { getCurrentIdentifier } from '../../utils.js' - -type link = HTMLAnchorElement | null | undefined - -/** - * - * @param allowCollectInfo - * @param links - * Could be a group of anchor element. Seems like this: - * [ - *
- * [USERNAME HERE] - * - * ([USER SCREEN NAME HERE]) - * - * - * ] - */ -export function getProfileIdentifierAtFacebook(links: link[] | link, allowCollectInfo?: boolean): IdentityResolved { - const unknown: IdentityResolved = {} - try { - if (!Array.isArray(links)) links = [links] - const result = links - .filter(Boolean) - .map((x) => ({ nickname: x!.ariaLabel || x!.textContent?.trim(), id: getUserID(x!.href), dom: x })) - .filter((x) => x.id) - const { dom, id, nickname } = result[0] || {} - const identifier = ProfileIdentifier.of(EnhanceableSite.Facebook, id).unwrapOr(null) - if (identifier) { - const currentProfile = getCurrentIdentifier() - let avatar: string | null = null - try { - const image = dom!.closest('.clearfix')!.parentElement!.querySelector('img')! - avatar = image.src - if (allowCollectInfo && image.getAttribute('aria-label') === nickname && nickname) { - Services.Identity.updateProfileInfo(identifier, { nickname, avatarURL: image.src }) - if (currentProfile?.linkedPersona) { - Services.Identity.createNewRelation(identifier, currentProfile.linkedPersona) - } - } - } catch {} - try { - const image = dom!.querySelector('img')! - avatar = image.src - if (allowCollectInfo && avatar) { - Services.Identity.updateProfileInfo(identifier, { nickname, avatarURL: image.src }) - if (currentProfile?.linkedPersona) { - Services.Identity.createNewRelation(identifier, currentProfile.linkedPersona) - } - } - } catch {} - try { - const image = dom!.querySelector('image')! - avatar = image.getAttribute('xlink:href') - if (allowCollectInfo && avatar) { - Services.Identity.updateProfileInfo(identifier, { nickname, avatarURL: avatar }) - if (currentProfile?.linkedPersona) { - Services.Identity.createNewRelation(identifier, currentProfile.linkedPersona) - } - } - } catch {} - return { - identifier, - avatar: avatar ?? undefined, - nickname: nickname ?? undefined, - } - } - return unknown - } catch (error) { - console.error(error) - } - return unknown -} -export function getUserID(x: string) { - if (!x) return null - const relative = !x.startsWith('https://') && !x.startsWith('http://') - const url = relative ? new URL(x, location.host) : new URL(x) - - if (url.hostname !== 'www.facebook.com') return null - - if (url.pathname.endsWith('.php')) { - if (!url.search) return null - const search = new URLSearchParams(url.search) - return search.get('id') - } - const val = url.pathname.replace(/^\//, '').replace(/\/$/, '').split('/')[0] - if (val === 'me') return null - return val -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/parse-username.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/parse-username.ts deleted file mode 100644 index 39feea284bcb..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/parse-username.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ProfileIdentifier, PostIdentifier } from '@masknet/shared-base' -import { i18n } from '../../../../shared-ui/locales_legacy/index.js' - -const HOST_NAME = 'https://www.facebook.com' - -/** - * @see https://www.facebook.com/help/105399436216001#What-are-the-guidelines-around-creating-a-custom-username? - * ! Start to use this in a breaking change! - */ -export function isValidFacebookUsername(name: string) { - if (!name) return null - // Avoid common mistake - if (name === 'photo.php') return null - const n = name.toLowerCase().replaceAll('.', '') - if (n.match(/^[\da-z]+$/)) { - return n - } - return null -} -/** - * Normalize post url - */ -export function getPostUrlAtFacebook(post: PostIdentifier) { - const id = post.identifier - const { postId } = post - const { userId } = id - if (!isValidFacebookUsername(userId)) return HOST_NAME - if (Number.parseFloat(userId)) return `${HOST_NAME}/permalink.php?story_fbid=${postId}&id=${userId}` - return `${HOST_NAME}/${userId}/posts/${postId}` -} -/** - * Normalize profile url - */ -export function getProfilePageUrlAtFacebook(user: ProfileIdentifier) { - if (user.network !== 'facebook.com') throw new Error('Wrong origin') - - const username = user.userId - if (!isValidFacebookUsername(username)) throw new TypeError(i18n.t('service_username_invalid')) - if (Number.parseFloat(username)) return `${HOST_NAME}/profile.php?id=${username}` - return `${HOST_NAME}/${username}` -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/resolveFacebookLink.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/resolveFacebookLink.ts deleted file mode 100644 index abc662cd7634..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/resolveFacebookLink.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function resolveFacebookLink(link: string) { - return link.replace(/\?fbclid=[\S\s]*#/, '#') -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/selector.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/selector.ts deleted file mode 100644 index 366090f2a9ce..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/selector.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' -import { compact } from 'lodash-es' - -type E = HTMLElement - -function querySelector(selector: string, singleMode = true) { - const ls = new LiveSelector().querySelector(selector) - return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector -} - -function querySelectorAll(selector: string) { - return new LiveSelector().querySelectorAll(selector) -} - -export function searchAvatarSelector() { - return querySelector( - '[role="navigation"] svg g image, [role="main"] [role="link"] [role="img"] image, [role="main"] [role="button"] [role="img"] image', - ) -} - -export function searchNickNameSelector(userId?: string | null) { - return querySelector( - compact([userId ? `a[href$="id=${userId}"]` : undefined, 'span[dir="auto"] div h1']).join(','), - ) -} - -export function searchUserIdSelector() { - return querySelector('div[role="tablist"][data-visualcompletion="ignore-dynamic"] a[role="tab"]') -} - -export function searchFacebookAvatarListSelector() { - return querySelector('[role="dialog"] > div:nth-child(3) input[type=file] + [role="button"]') - .closest(3) - .querySelector('div') -} - -export function searchFacebookAvatarSelector() { - return querySelector('[role="main"] [role="button"] svg[role="img"],[role="main"] [role="link"] svg[role="img"]') -} - -export function searchFaceBookPostAvatarSelector() { - return new LiveSelector().querySelectorAll( - '[type="nested/pressable"] > a > div > svg, ul div[role="article"] a > div > svg[role="none"]', - ) -} - -export function searchFacebookAvatarOpenFilesSelector() { - return querySelector('[role="dialog"] input[type=file] ~ div') -} - -export function searchFacebookProfileSettingButtonSelector() { - return querySelector( - '[role="main"] > div > div > div > div > div input[accept*="image"] + div[role="button"]', - ).closest(2) -} - -export function searchFacebookProfileCoverSelector() { - return querySelector('[role="button"] [role="img"]') - .closest(10) - .querySelector('input[type="file"] ~ div') - .closest(6) - .querySelector('div') -} - -export function searchFacebookEditProfileSelector() { - return querySelector('[role="main"] [role="button"] [role="img"]') - .closest(1) - .querySelector('i[data-visualcompletion="css-img"]') -} - -export function searchFacebookSaveAvatarButtonSelector() { - return new LiveSelector() - .querySelector('[role="dialog"] [role="slider"]') - .closest(7) - .querySelectorAll('div') - .map((x) => x.parentElement?.parentElement) - .at(-1) -} - -export function searchFacebookConfirmAvatarImageSelector() { - return querySelector('[role="dialog"] [role="slider"]').closest(7).querySelector('img') -} - -export function inpageAvatarSelector() { - return querySelectorAll('[type="nested/pressable"]').closest(2) -} - -export function toolboxInSidebarSelector() { - return querySelector('[data-pagelet="LeftRail"] > div > div > :nth-child(2) > ul > li:nth-child(2)') -} - -export function toolboxInSpecialSidebarSelector() { - return querySelector( - '[role="navigation"] > div > div > div > :nth-child(2) > div > div > :nth-child(2) ul > li:nth-child(2)', - ) -} -export function toolboxInSidebarSelectorWithNoLeftRailStart() { - return querySelector('[role="banner"]') - .closest(1) - .querySelector('div + div > div > div > div > div > div > div > div > ul') - .closest(1) - .querySelector('div:nth-child(2) > ul > li:nth-child(2)') -} - -// for getting normal tab style -export function profileTabUnselectedSelector() { - return querySelector('[role="tablist"] a[aria-selected="false"]') -} - -// for getting activated tab style -export function profileTabSelectedSelector() { - return querySelector('[role="tablist"] a[aria-selected="true"]') -} - -// for inserting web3 tab -export function searchProfileTabSelector() { - return querySelector('[role="tablist"] > div > div > :last-child') -} - -// for getting the inserted web3 tab -export function web3TabSelector() { - return querySelector('[role="tablist"] a:nth-child(7)+span') -} - -// for inserting web3 tab content -export function searchProfileTabPageSelector() { - return querySelector('[role="main"] > div:last-child > div') -} - -// for getting profile section style -export function profileSectionSelector() { - return querySelector('[role="main"]').querySelector('[style]') -} - -export function searchIntroSectionSelector() { - return querySelector('[role="main"] > div:last-child > div:last-child') -} - -export function searchBioSelector() { - return querySelector( - '[role="main"] > div:last-child > div:last-child > div > div > div:last-child > div > div > div > div > div > div > div:nth-child(2) > div span', - ) -} - -export function searchResultHeadingSelector() { - return querySelector('[role="article"]') -} - -export function searchHomepageSelector() { - return querySelector('[data-pagelet="ProfileTilesFeed_0"] ul a span[dir="auto"]') -} diff --git a/packages/mask/content-script/site-adaptors/facebook.com/utils/user.ts b/packages/mask/content-script/site-adaptors/facebook.com/utils/user.ts deleted file mode 100644 index 8b4123ce4ea3..000000000000 --- a/packages/mask/content-script/site-adaptors/facebook.com/utils/user.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { ValueRef } from '@masknet/shared-base' -import { - searchAvatarSelector, - searchBioSelector, - searchHomepageSelector, - searchIntroSectionSelector, - searchNickNameSelector, - searchUserIdSelector, -} from './selector.js' -import { collectNodeText } from '../../../utils/index.js' - -export function getNickName(userId?: string | null) { - const node = searchNickNameSelector(userId).evaluate() - if (!node) return '' - - return collectNodeText(node) -} - -export function getAvatar() { - const node = searchAvatarSelector().evaluate() - if (!node) return - - const imageURL = node.getAttribute('xlink:href') ?? '' - return imageURL.trim() -} - -const bioDescription = new ValueRef('') -export function getBioDescription() { - const intro = searchIntroSectionSelector().evaluate() - const node = searchBioSelector().evaluate() - - if (intro && node) { - bioDescription.value = collectNodeText(node) - } else if (intro) { - bioDescription.value = '' - } - - return bioDescription.value -} - -export function getFacebookId() { - const node = searchUserIdSelector().evaluate() - if (!node?.href) return '' - - if (!URL.canParse(node.href, location.href)) return '' - const url = new URL(node.href, location.href) - if (url.pathname === '/profile.php') return url.searchParams.get('id') - return url.pathname.replaceAll('/', '') -} - -const FACEBOOK_AVATAR_ID_MATCH = /(\w+).(?:png|jpg|gif|bmp)/ - -export function getAvatarId(avatarURL: string) { - if (!avatarURL) return '' - const _url = new URL(avatarURL) - const match = _url.pathname.match(FACEBOOK_AVATAR_ID_MATCH) - if (!match) return '' - return match[1] -} - -const homepageCache = new ValueRef('') -export function getPersonalHomepage() { - const intro = searchIntroSectionSelector().evaluate() - const node = searchHomepageSelector().evaluate() - if (intro && node) { - let text = collectNodeText(node) - if (text && !text.startsWith('http')) { - text = 'http://' + text - } - homepageCache.value = text - } else if (intro) { - homepageCache.value = '' - } - - return homepageCache.value -} diff --git a/packages/mask/content-script/site-adaptors/index.ts b/packages/mask/content-script/site-adaptors/index.ts deleted file mode 100644 index c60dcabf7105..000000000000 --- a/packages/mask/content-script/site-adaptors/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import './facebook.com/index.js' -import './twitter.com/index.js' -import './instagram.com/index.js' -import './minds.com/index.js' -import './mirror.xyz/index.js' diff --git a/packages/mask/content-script/site-adaptors/instagram.com/base.ts b/packages/mask/content-script/site-adaptors/instagram.com/base.ts deleted file mode 100644 index a5915375b9f7..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/base.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EncryptPayloadNetwork } from '@masknet/encryption' -import type { SiteAdaptor } from '@masknet/types' -import { EnhanceableSite } from '@masknet/shared-base' - -const origins = ['https://www.instagram.com/*', 'https://m.instagram.com/*', 'https://instagram.com/*'] -export const instagramBase: SiteAdaptor.Base = { - networkIdentifier: EnhanceableSite.Instagram, - encryptPayloadNetwork: EncryptPayloadNetwork.Instagram, - declarativePermissions: { origins }, - shouldActivate(location) { - return location.host.endsWith(EnhanceableSite.Instagram) - }, -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity-provider.ts b/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity-provider.ts deleted file mode 100644 index 3af6f7a55af2..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity-provider.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/index.js' -import { ProfileIdentifier } from '@masknet/shared-base' -import { instagramBase } from '../base.js' -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { delay } from '@masknet/kit' -import { searchInstagramHandleSelector } from '../utils/selector.js' -import { getPersonalHomepage, getUserId, getAvatar } from '../utils/user.js' - -function resolveLastRecognizedIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const handleSelector = searchInstagramHandleSelector() - const assign = async () => { - await delay(500) - const homepage = getPersonalHomepage() - const handle = getUserId() - const avatar = getAvatar() - - ref.value = { - identifier: ProfileIdentifier.of(instagramBase.networkIdentifier, handle).unwrapOr(undefined), - nickname: handle, - avatar, - homepage, - } - } - - assign() - - const createWatcher = (selector: LiveSelector) => { - new MutationObserverWatcher(selector) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['href'], - }, - cancel, - ) - - window.addEventListener('locationchange', assign, { signal: cancel }) - } - createWatcher(handleSelector) -} - -export const IdentityProviderInstagram: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - async start(signal) { - resolveLastRecognizedIdentityInner(this.recognized, signal) - }, - recognized: creator.EmptyIdentityResolveProviderState(), -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity.ts b/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity.ts deleted file mode 100644 index 54c9c484eca6..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/collecting/identity.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { delay } from '@masknet/kit' -import { ProfileIdentifier } from '@masknet/shared-base' -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/index.js' -import { instagramBase } from '../base.js' -import { searchInstagramAvatarSelector } from '../utils/selector.js' -import { getAvatar, getBioDescription, getNickname, getPersonalHomepage, getUserId } from '../utils/user.js' - -function resolveCurrentVisitingIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const avatarSelector = searchInstagramAvatarSelector() - const assign = async () => { - await delay(500) - const bio = getBioDescription() - const homepage = getPersonalHomepage() - const nickname = getNickname() - const handle = getUserId() - const avatar = getAvatar() - - ref.value = { - identifier: ProfileIdentifier.of(instagramBase.networkIdentifier, handle).unwrapOr(undefined), - nickname, - avatar, - bio, - homepage, - } - } - const createWatcher = (selector: LiveSelector) => { - new MutationObserverWatcher(selector) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['src', 'content'], - }, - cancel, - ) - - window.addEventListener('locationchange', assign, { signal: cancel }) - } - - assign() - - createWatcher(avatarSelector) -} - -export const CurrentVisitingIdentityProviderInstagram: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveCurrentVisitingIdentityInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/collecting/posts.ts b/packages/mask/content-script/site-adaptors/instagram.com/collecting/posts.ts deleted file mode 100644 index c77c4e8fa90a..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/collecting/posts.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type DOMProxy, LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { type TypedMessage, makeTypedMessageImage, makeTypedMessageTuple } from '@masknet/typed-message' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import type { SiteAdaptorUI } from '@masknet/types' -import { startWatch } from '../../../utils/startWatch.js' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { instagramBase } from '../base.js' -import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' -import { instagramShared } from '../shared.js' - -const posts = new LiveSelector().querySelectorAll( - 'main[role="main"] article[role="presentation"][tabindex="-1"]', -) - -export const PostProviderInstagram: SiteAdaptorUI.CollectingCapabilities.PostsProvider = { - posts: creator.EmptyPostProviderState(), - start(signal) { - collectPostsInstagramInner(this.posts, signal) - }, -} -function collectPostsInstagramInner( - store: SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'], - signal: AbortSignal, -) { - startWatch( - new MutationObserverWatcher(posts).useForeach((node, key, metadata) => { - const { subscriptions, ...info } = createRefsForCreatePostContext() - const postInfo = instagramShared.utils.createPostContext({ - site: EnhanceableSite.Instagram, - comments: undefined, - rootElement: metadata, - suggestedInjectionPoint: - metadata.realCurrent!.querySelector('header+div+div') || metadata.realCurrent!, - ...subscriptions, - }) - - store.set(metadata, postInfo) - function collectPostInfo() { - const nextTypedMessage: TypedMessage[] = [] - info.postBy.value = getPostBy(metadata) - info.postID.value = getPostID(metadata) - const img = node.querySelectorAll('img')[1] - if (img) { - nextTypedMessage.push(makeTypedMessageImage(img.src, img)) - info.postMetadataImages.add(img.src) - } else nextTypedMessage.push(makeTypedMessageImage('')) - info.postMessage.value = makeTypedMessageTuple(nextTypedMessage) - } - collectPostInfo() - return { - onNodeMutation: collectPostInfo, - onTargetChanged: collectPostInfo, - onRemove: () => store.delete(metadata), - } - }), - signal, - ) -} - -function getPostBy(node: DOMProxy): ProfileIdentifier | null { - if (node.destroyed) return null - // the first a - const author = node.current.querySelector('a') - if (!author) return null - const href = new URL(author.href).pathname - if (href.startsWith('/') && href.endsWith('/') && href.slice(1, -1).includes('/') === false) { - return ProfileIdentifier.of(instagramBase.networkIdentifier, href.slice(1, -1)).unwrapOr(null) - } - return null -} -function getPostID(node: DOMProxy): null | string { - if (node.destroyed) return null - return node.current?.querySelector('span a[href^="/"]')?.text || null -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/collecting/theme.ts b/packages/mask/content-script/site-adaptors/instagram.com/collecting/theme.ts deleted file mode 100644 index 7d47ffe8a875..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/collecting/theme.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { ThemeMode } from '@masknet/web3-shared-base' - -function resolveThemeSettingsInner( - ref: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'], - cancel: AbortSignal, -) { - function updateThemeColor(isDarkMode: boolean) { - ref.value = { - ...ref.value, - mode: isDarkMode ? ThemeMode.Dark : ThemeMode.Light, - } - } - - updateThemeColor( - getComputedStyle(document.documentElement).getPropertyValue('--ig-primary-background') === '0, 0, 0', - ) - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - updateThemeColor( - getComputedStyle(document.documentElement).getPropertyValue('--ig-primary-background') === '0, 0, 0', - ) - }) - }) - - observer.observe(document.querySelector('html') as Node, { - attributes: true, - attributeOldValue: true, - attributeFilter: ['class'], - }) - - cancel.addEventListener('abort', () => observer.disconnect()) -} - -export const ThemeSettingsProviderInstagram: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider = { - recognized: creator.EmptyThemeSettingsProviderState(), - async start(cancel) { - resolveThemeSettingsInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/customization/custom.ts b/packages/mask/content-script/site-adaptors/instagram.com/customization/custom.ts deleted file mode 100644 index 7b9fd20fdebc..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/customization/custom.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useMemo } from 'react' -import { produce, setAutoFreeze } from 'immer' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' - -export function useThemeInstagramVariant(baseTheme: Theme) { - const themeSettings = useThemeSettings() - - return useMemo(() => { - setAutoFreeze(false) - - const InstagramTheme = produce(baseTheme, (theme) => { - theme.components = theme.components || {} - theme.components.MuiTypography = { - styleOverrides: { - root: { - fontFamily: - "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", - }, - }, - } - }) - setAutoFreeze(true) - return unstable_createMuiStrictModeTheme(InstagramTheme) - }, [baseTheme, themeSettings]) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/index.ts b/packages/mask/content-script/site-adaptors/instagram.com/index.ts deleted file mode 100644 index 7dd70859b495..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { instagramBase } from './base.js' - -defineSiteAdaptorUI({ - ...instagramBase, - load: () => import('./ui-provider.js'), - hotModuleReload(callback) { - if (import.meta.webpackHot) { - import.meta.webpackHot.accept('./ui-provider.ts', async () => { - callback((await import('./ui-provider.js')).default) - }) - } - }, -}) diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/Avatar/index.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/Avatar/index.tsx deleted file mode 100644 index ccc1dffc979a..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/Avatar/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Plugin } from '@masknet/plugin-infra' -import { Avatar } from '../../../../components/InjectedComponents/Avatar.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { inpageAvatarSelector } from '../../utils/selector.js' - -export async function injectAvatar(signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(inpageAvatarSelector()).useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele.firstChild as HTMLElement - // create stacking context - ele.style.position = 'relative' - // TODO fetch userId - const userId = '' - - const root = attachReactTreeWithContainer(proxy.afterShadow, { signal }) - root.render( -
- {userId ? - - : null} -
, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarEditProfile.tsx deleted file mode 100644 index 7911a65a5df4..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarEditProfile.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useMemo } from 'react' -import { useLocation } from 'react-use' -import { makeStyles } from '@masknet/theme' -import { MaskMessages } from '@masknet/shared-base' -import { NFTAvatarButton } from '@masknet/plugin-avatar' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { - searchInstagramAvatarSettingDialog, - searchInstagramProfileAvatarButtonSelector, - searchInstagramProfileEditButton, -} from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { NFTAvatarSettingDialog } from './NFTAvatarSettingDialog.js' - -export function injectOpenNFTAvatarEditProfileButton(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchInstagramProfileAvatarButtonSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - , - ) - - const dialogWatcher = new MutationObserverWatcher(searchInstagramAvatarSettingDialog()) - startWatch(dialogWatcher, signal) - attachReactTreeWithContainer(dialogWatcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()(() => ({ - root: { - marginTop: 5, - marginLeft: 'auto', - marginRight: 'auto', - borderRadius: '4px !important', - height: 30, - width: 134, - }, - text: { - fontSize: 12, - lineHeight: '12px', - }, -})) - -export function openNFTAvatarSettingDialog() { - MaskMessages.events.nftAvatarSettingDialogUpdated.sendToLocal({ open: true }) -} - -function OpenNFTAvatarEditProfileButtonInInstagram() { - const location = useLocation() - - const { classes } = useStyles() - - const editButton = useMemo(() => searchInstagramProfileEditButton().evaluate(), [location.pathname]) - - if (location.pathname?.includes('/edit') || !editButton) return null - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInInstagram.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInInstagram.tsx deleted file mode 100644 index 7d0c005373c0..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInInstagram.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { max } from 'lodash-es' -import { useEffect, useMemo, useSyncExternalStore } from 'react' -import { useLocation, useWindowSize } from 'react-use' -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { AvatarStore } from '@masknet/web3-providers' -import { NFTBadge, rainbowBorderKeyFrames } from '@masknet/plugin-avatar' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { getAvatarId } from '../../utils/user.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { searchInstagramAvatarSelector } from '../../utils/selector.js' - -export function injectNFTAvatarInInstagram(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchInstagramAvatarSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()(() => ({ - root: { - position: 'absolute', - textAlign: 'center', - color: 'white', - width: '100%', - height: '100%', - top: 0, - left: 0, - }, - text: { - fontSize: '20px !important', - fontWeight: 700, - }, - icon: { - width: '19px !important', - height: '19px !important', - }, -})) - -function NFTAvatarInInstagram() { - const { classes } = useStyles() - - const location = useLocation() - const identity = useCurrentVisitingIdentity() - - const store = useSyncExternalStore(AvatarStore.subscribe, AvatarStore.getSnapshot) - const avatar = store.retrieveAvatar(identity.identifier?.userId) - const token = store.retrieveToken(identity.identifier?.userId) - - const windowSize = useWindowSize() - const showAvatar = useMemo(() => { - if (location.pathname?.includes('/edit')) return false - return getAvatarId(identity.avatar ?? '') === avatar?.avatarId - }, [avatar?.avatarId, identity.avatar, location.pathname]) - - const size = useMemo(() => { - const ele = searchInstagramAvatarSelector().evaluate() - - if (!ele) return 0 - - const style = window.getComputedStyle(ele) - return max([146, Number.parseInt(style.width.replace('px', '') ?? 0, 10)]) - }, [windowSize]) - - useEffect(() => { - if (!showAvatar) return - - let containerDom: LiveSelector - - if (searchInstagramAvatarSelector().evaluate()?.parentElement?.tagName === 'SPAN') { - containerDom = searchInstagramAvatarSelector().closest(1) - } else { - containerDom = searchInstagramAvatarSelector().closest(2) - } - - const style = document.createElement('style') - style.innerText = ` - ${rainbowBorderKeyFrames.styles} - - .rainbowBorder { - animation: ${rainbowBorderKeyFrames.name} 6s linear infinite; - box-shadow: 0 5px 15px rgba(0, 248, 255, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2); - transition: .125s ease; - border: 2px solid #00f8ff; - } - ` - - const parentDom = searchInstagramAvatarSelector().closest(2).evaluate() - - parentDom?.appendChild(style) - - containerDom.evaluate()?.classList.add('rainbowBorder') - return () => { - if (parentDom?.lastElementChild?.tagName === 'STYLE') { - parentDom.removeChild(parentDom.lastElementChild) - } - containerDom.evaluate()?.classList.remove('rainbowBorder') - } - }, [location.pathname, showAvatar]) - - if (!avatar || !size || !showAvatar) return null - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInTimeline.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInTimeline.tsx deleted file mode 100644 index 966295cd9419..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarInTimeline.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { memo } from 'react' -import { noop } from 'lodash-es' -import { makeStyles } from '@masknet/theme' -import { Flags } from '@masknet/flags' -import { DOMProxy, type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { getInjectNodeInfo } from '../../utils/avatar.js' -import { NFTBadgeTimeline } from '@masknet/plugin-avatar' -import { searchInstagramPostAvatarSelector } from '../../utils/selector.js' - -const useStyles = makeStyles()(() => ({ - root: { - transform: 'scale(1)!important', - }, -})) - -const TimeLineRainbow = memo( - ({ userId, avatarId, width, height }: { userId: string; avatarId: string; width: number; height: number }) => { - const { classes } = useStyles() - return ( -
- -
- ) - }, -) - -function _(selector: () => LiveSelector, signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(selector()).useForeach((element) => { - let remove = noop - - const run = async () => { - const href = (element.parentNode as HTMLAnchorElement | null)?.href - if (!href) return - - const id = new URL(href).pathname.replaceAll('/', '') - if (!id) return - - const info = getInjectNodeInfo(element) - if (!info) return - - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = info.element - - const root = attachReactTreeWithContainer(proxy.afterShadow, { signal }) - - root.render( - , - ) - - remove = root.destroy - } - - run() - - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => remove(), - } - }), - signal, - ) -} - -export async function injectUserNFTAvatarAtInstagram(signal: AbortSignal) { - _(searchInstagramPostAvatarSelector, signal) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx deleted file mode 100644 index 557aaf589308..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useCallback, useState } from 'react' -import { useMount } from 'react-use' -import { toPNG, NFTAvatar, type SelectTokenInfo, useSaveStringStorage } from '@masknet/plugin-avatar' -import { InjectedDialog, SelectProviderModal } from '@masknet/shared' -import { Button, DialogContent } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { Instagram } from '@masknet/web3-providers' -import { useChainContext } from '@masknet/web3-hooks-base' -import { MaskMessages, NetworkPluginID } from '@masknet/shared-base' -import { ChainId, SchemaType } from '@masknet/web3-shared-evm' -import type { AvatarNextID } from '@masknet/web3-providers/types' -import { useMaskSharedTrans } from '../../../../../shared-ui/index.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { getAvatarId } from '../../utils/user.js' - -const useStyles = makeStyles()(() => ({ - root: {}, - wallet: { - height: 120, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, -})) - -export function NFTAvatarSettingDialog() { - const t = useMaskSharedTrans() - const [open, setOpen] = useState(false) - const { classes } = useStyles() - const { account } = useChainContext() - const identity = useCurrentVisitingIdentity() - const saveNFTAvatar = useSaveStringStorage(NetworkPluginID.PLUGIN_EVM) - - const onChange = useCallback( - async (info: SelectTokenInfo) => { - try { - if (!info.token.metadata?.imageURL || !info.token.contract?.address) return - if (!identity.identifier) return - - const image = await toPNG(info.token.metadata.imageURL) - if (!image || !account) return - - const { profile_pic_url_hd } = await Instagram.uploadUserAvatar(image, identity.identifier.userId) - const avatarId = getAvatarId(profile_pic_url_hd) - const avatarInfo = await saveNFTAvatar(identity.identifier.userId, account, { - address: info.token.contract.address, - userId: identity.identifier.userId, - tokenId: info.token.tokenId, - avatarId, - chainId: (info.token.chainId ?? ChainId.Mainnet) as ChainId, - schema: (info.token.schema ?? SchemaType.ERC721) as SchemaType, - pluginId: info.pluginID, - } as AvatarNextID) - - if (!avatarInfo) { - setOpen(false) - return - } - - // If the avatar is set successfully, reload the page - window.location.reload() - - setOpen(false) - } catch (error) { - // eslint-disable-next-line no-alert - if (error instanceof Error) alert(error.message) - } - }, - [identity, account, saveNFTAvatar], - ) - - const onClose = useCallback(() => setOpen(false), []) - - useMount(() => { - return MaskMessages.events.nftAvatarSettingDialogUpdated.on((data) => setOpen(data.open)) - }) - - const onClick = useCallback(() => { - SelectProviderModal.open() - }, []) - return ( - - - {account ? - - :
- -
- } -
-
- ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/ProfileNFTAvatar.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/ProfileNFTAvatar.tsx deleted file mode 100644 index 1a58ecc0c573..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/NFT/ProfileNFTAvatar.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useCallback, useLayoutEffect, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { MaskMessages } from '@masknet/shared-base' -import { useLocationChange } from '@masknet/shared-base-ui' -import { startWatch } from '../../../../utils/startWatch.js' -import { searchInstagramAvatarEditPageSettingDialog, searchInstagramAvatarListSelector } from '../../utils/selector.js' -import { useMaskSharedTrans } from '../../../../../shared-ui/index.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root.js' -import { NFTAvatarSettingDialog } from './NFTAvatarSettingDialog.js' - -export async function injectProfileNFTAvatarInInstagram(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchInstagramAvatarListSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() - - const dialogWatcher = new MutationObserverWatcher(searchInstagramAvatarEditPageSettingDialog()) - startWatch(dialogWatcher, signal) - attachReactTreeWithContainer(dialogWatcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()((theme, props) => ({ - root: { - width: '100%', - fontSize: props.fontSize, - lineHeight: 1.5, - minHeight: props.minHeight, - borderTop: props.borderTop, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - color: '#ED4956', - fontWeight: 600, - cursor: 'pointer', - }, -})) - -interface StyleProps { - fontSize: number - minHeight: number - color?: string - borderTop?: string -} - -function NFTAvatarButtonInDialog() { - const t = useMaskSharedTrans() - const [style, setStyle] = useState({ - fontSize: 12, - minHeight: 48, - // Instagram css var - borderTop: '1px solid rgba(var(--b6a,219,219,219),1)', - }) - const { classes } = useStyles(style) - - const setStyleWithSelector = useCallback(() => { - const dom = searchInstagramAvatarListSelector().evaluate() - if (!dom) return - const css = window.getComputedStyle(dom) - setStyle({ - minHeight: Number(css.minHeight.replace('px', '')), - fontSize: Number(css.fontSize.replace('px', '')), - color: css.color, - borderTop: css.borderTop, - }) - }, []) - - const onClick = useCallback(() => { - MaskMessages.events.nftAvatarSettingDialogUpdated.sendToLocal({ open: true }) - }, []) - - useLayoutEffect(setStyleWithSelector, []) - - useLocationChange(setStyleWithSelector) - - return ( -
- 🔥 {t.use_nft()} -
- ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTab.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTab.tsx deleted file mode 100644 index e097893f5d42..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTab.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { useLayoutEffect, useMemo, useState } from 'react' -import { useLocation } from 'react-use' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Icons } from '@masknet/icons' -import { makeStyles } from '@masknet/theme' -import { MaskMessages } from '@masknet/shared-base' -import { useMatchXS } from '@masknet/shared-base-ui' -import { ProfileTab } from '../../../components/InjectedComponents/ProfileTab.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { - searchProfileActiveTabSelector, - searchProfileTabListLastChildSelector, - searchProfileTabPageSelector, - searchProfileTabSelector, -} from '../utils/selector.js' -import { startWatch } from '../../../utils/startWatch.js' - -export function injectProfileTabAtInstagram(signal: AbortSignal) { - let tabInjected = false - const contentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => { - const elePage = searchProfileTabPageSelector().evaluate() - if (elePage && !tabInjected) { - const watcher = new MutationObserverWatcher(searchProfileTabListLastChildSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - , - ) - tabInjected = true - } - }) - - startWatch(contentWatcher, signal) -} - -function getStyleProps(activeColor: { activeColor: string; color: string }) { - const EMPTY_STYLE = {} as CSSStyleDeclaration - const eleTab = searchProfileTabSelector().evaluate() - const style = eleTab ? window.getComputedStyle(eleTab) : EMPTY_STYLE - return { - color: activeColor.color, - fontSize: style.fontSize, - padding: style.paddingBottom, - height: style.height, - hover: activeColor.activeColor, - line: activeColor.activeColor, - } -} - -const useStyles = makeStyles()((theme, props) => { - return { - root: { - '&:hover': { - cursor: 'pointer', - }, - display: '-webkit-box', - alignItems: 'center', - justifyContent: 'center', - marginRight: 60, - }, - button: { - fontSize: props.fontSize, - height: props.height, - justifyContent: 'center', - alignItems: 'center', - display: 'flex', - borderTop: '1px solid transparent', - fontWeight: 'var(--font-weight-system-semibold)', - color: 'rgb(var(--ig-secondary-text))', - }, - selected: { - borderTop: `1px solid ${props.hover}`, - color: props.hover, - [`@media (max-width: ${theme.breakpoints.values.sm}px)`]: { - borderTop: 'unset', - }, - }, - icon: { - [`@media (min-width: ${theme.breakpoints.values.sm}px)`]: { - height: props.fontSize, - width: props.fontSize, - paddingRight: 4, - }, - }, - } -}) - -interface StyleProps { - color: string - hover: string - fontSize: string - height: string - padding: string -} - -function getActiveColor() { - const activeTab = searchProfileActiveTabSelector().evaluate()?.firstElementChild - if (!activeTab) return '' - const activeStyle = window.getComputedStyle(activeTab) - return activeStyle.color -} - -function getColor() { - const tab = searchProfileTabSelector().evaluate() - if (!tab) return '' - const style = window.getComputedStyle(tab) - return style.color -} - -function handler() { - MaskMessages.events.profileTabActive.sendToLocal({ active: false }) - MaskMessages.events.profileTabHidden.sendToLocal({ hidden: true }) - const activeTab = searchProfileActiveTabSelector().evaluate() - if (activeTab?.style) { - activeTab.style.borderTop = '' - activeTab.style.color = '' - } - const ele = searchProfileTabPageSelector().evaluate() - if (ele?.style) { - ele.style.display = '' - } -} - -function ProfileTabAtInstagram() { - const isMobile = useMatchXS() - const location = useLocation() - const [styles, setStyles] = useState({ - color: '', - hover: '', - fontSize: '', - height: '', - padding: '', - }) - - const { activeColor, color } = useMemo(() => { - const activeColor = getActiveColor() - const color = getColor() - - return { activeColor, color } - }, [location.pathname]) - - useLayoutEffect(() => { - const tabStyles = getStyleProps({ activeColor, color }) - setStyles(tabStyles) - }, []) - - const { classes } = useStyles(styles) - function reset() { - const activeTab = searchProfileActiveTabSelector().evaluate() - if (activeTab?.style) { - activeTab.style.borderTop = '' - activeTab.style.color = '' - } - activeTab?.removeEventListener('click', handler) - - if (isMobile) { - const activeTab = searchProfileActiveTabSelector().evaluate()?.firstElementChild - - if (activeTab?.tagName.toUpperCase() === 'SVG') { - const ele = activeTab as HTMLOrSVGImageElement - if (ele.style) { - ele.style.color = '' - ele.style.fill = '' - } - } - } - const ele = searchProfileTabPageSelector().evaluate() - if (ele?.style) { - ele.style.display = '' - } - } - function clear() { - const style = getStyleProps({ activeColor, color }) - const activeTab = searchProfileActiveTabSelector().evaluate() - if (activeTab?.style) { - activeTab.style.setProperty('border-top', 'none', 'important') - activeTab.style.color = style.color - } - - activeTab?.addEventListener('click', handler) - - if (isMobile) { - const activeTab = searchProfileActiveTabSelector().evaluate()?.firstElementChild - if (activeTab?.tagName.toUpperCase() === 'SVG') { - const ele = activeTab as HTMLOrSVGImageElement - if (ele.style) { - ele.style.color = style.color - ele.style.fill = style.color - } - } - } - const ele = searchProfileTabPageSelector().evaluate() - if (ele?.style) { - ele.style.display = 'none' - } - } - return ( - } - classes={{ - root: classes.root, - button: classes.button, - selected: classes.selected, - }} - reset={reset} - clear={clear} - /> - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTabContent.tsx b/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTabContent.tsx deleted file mode 100644 index 32621f16d3f1..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/ProfileTabContent.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { getMaskColor, makeStyles } from '@masknet/theme' -import { ProfileTabContent } from '../../../components/InjectedComponents/ProfileTabContent.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { searchProfileActiveTabSelector, searchProfileTabArticlePageSelector } from '../utils/selector.js' - -export function injectProfileTabContentAtInstagram(signal: AbortSignal) { - injectProfileTabContentHaveArticle(signal) -} - -function injectProfileTabContentHaveArticle(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileTabArticlePageSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -function getStyleProps() { - const activeTab = searchProfileActiveTabSelector().evaluate() as HTMLDivElement - return { - backgroundColor: activeTab ? window.getComputedStyle(activeTab).backgroundColor : undefined, - fontFamily: activeTab ? window.getComputedStyle(activeTab as HTMLElement).fontFamily : undefined, - } -} - -const useStyles = makeStyles()((theme) => { - const props = getStyleProps() - - return { - root: { - position: 'relative', - }, - text: { - paddingTop: 29, - paddingBottom: 29, - '& > p': { - fontSize: 28, - fontFamily: props.fontFamily, - fontWeight: 700, - color: getMaskColor(theme).textPrimary, - }, - }, - button: { - backgroundColor: props.backgroundColor, - color: 'white', - marginTop: 18, - '&:hover': { - backgroundColor: props.backgroundColor, - }, - }, - } -}) - -function ProfileTabContentAtInstagram() { - const { classes } = useStyles() - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/injection/post-inspector.ts b/packages/mask/content-script/site-adaptors/instagram.com/injection/post-inspector.ts deleted file mode 100644 index 37435eb5e35f..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/injection/post-inspector.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Flags } from '@masknet/flags' -import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/index.js' -import type { PostInfo } from '@masknet/plugin-infra/content-script' - -const map = new WeakMap() -function getShadowRoot(node: HTMLElement) { - if (map.has(node)) return map.get(node)! - const dom = node.attachShadow(Flags.shadowRootInit) - map.set(node, dom) - return dom -} -export function injectPostInspectorInstagram(signal: AbortSignal, current: PostInfo) { - return injectPostInspectorDefault( - { - injectionPoint: (post) => getShadowRoot(post.suggestedInjectionPoint), - }, - { slotPosition: 'after' }, - )(current, signal) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/shared.ts b/packages/mask/content-script/site-adaptors/instagram.com/shared.ts deleted file mode 100644 index 85a996967507..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/shared.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { SiteAdaptor } from '@masknet/types' -import { createSiteAdaptorSpecializedPostContext } from '../../site-adaptor-infra/utils/create-post-context.js' -import { hasPayloadLike } from '../../utils/index.js' -import { instagramBase } from './base.js' - -function getProfileURL(): URL | null { - return new URL('https://www.instagram.com/') -} - -export const instagramShared: SiteAdaptor.Shared & SiteAdaptor.Base = { - ...instagramBase, - utils: { - getProfileURL, - createPostContext: createSiteAdaptorSpecializedPostContext(instagramBase.networkIdentifier, { - hasPayloadLike, - }), - }, -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/ui-provider.ts b/packages/mask/content-script/site-adaptors/instagram.com/ui-provider.ts deleted file mode 100644 index 562294ef180f..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/ui-provider.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { stateCreator } from '../../site-adaptor-infra/index.js' -import { instagramShared } from './shared.js' -import { instagramBase } from './base.js' -import { IdentityProviderInstagram } from './collecting/identity-provider.js' -import { PostProviderInstagram } from './collecting/posts.js' -import { ThemeSettingsProviderInstagram } from './collecting/theme.js' -import { - createTaskStartSetupGuideDefault, - InitAutonomousStateProfiles, - injectPageInspectorDefault, -} from '../../site-adaptor-infra/defaults/index.js' -import { pasteInstagram } from '@masknet/injected-script' -import { injectPostInspectorInstagram } from './injection/post-inspector.js' -import { CurrentVisitingIdentityProviderInstagram } from './collecting/identity.js' -import { injectProfileNFTAvatarInInstagram } from './injection/NFT/ProfileNFTAvatar.js' -import { injectNFTAvatarInInstagram } from './injection/NFT/NFTAvatarInInstagram.js' -import { - injectOpenNFTAvatarEditProfileButton, - openNFTAvatarSettingDialog, -} from './injection/NFT/NFTAvatarEditProfile.js' -import { injectUserNFTAvatarAtInstagram } from './injection/NFT/NFTAvatarInTimeline.js' -import { injectProfileTabAtInstagram } from './injection/ProfileTab.js' -import { injectProfileTabContentAtInstagram } from './injection/ProfileTabContent.js' -import { injectAvatar } from './injection/Avatar/index.js' -import { useThemeInstagramVariant } from './customization/custom.js' - -const define: SiteAdaptorUI.Definition = { - ...instagramShared, - ...instagramBase, - automation: { - nativeCompositionDialog: { - async attachImage(url, options) { - pasteInstagram(new Uint8Array(await url.arrayBuffer())) - }, - }, - }, - collecting: { - identityProvider: IdentityProviderInstagram, - currentVisitingIdentityProvider: CurrentVisitingIdentityProviderInstagram, - postsProvider: PostProviderInstagram, - themeSettingsProvider: ThemeSettingsProviderInstagram, - }, - configuration: {}, - customization: { - useTheme: useThemeInstagramVariant, - }, - init(signal) { - const profiles = stateCreator.profiles() - InitAutonomousStateProfiles(signal, profiles, instagramBase.networkIdentifier) - // No need to init cause this network is not going to support those features now. - return { profiles } - }, - injection: { - setupWizard: createTaskStartSetupGuideDefault(), - postInspector: injectPostInspectorInstagram, - profileAvatar: injectNFTAvatarInInstagram, - enhancedProfileNFTAvatar: injectProfileNFTAvatarInInstagram, - openNFTAvatar: injectOpenNFTAvatarEditProfileButton, - userAvatar: injectUserNFTAvatarAtInstagram, - pageInspector: injectPageInspectorDefault(), - profileTab: injectProfileTabAtInstagram, - profileTabContent: injectProfileTabContentAtInstagram, - openNFTAvatarSettingDialog, - /* newPostComposition: { - start: newPostCompositionInstagram, - supportedInputTypes: { text: true, image: true }, - supportedOutputTypes: { text: false, image: true }, - },*/ - avatar: injectAvatar, - }, -} -export default define diff --git a/packages/mask/content-script/site-adaptors/instagram.com/utils/avatar.ts b/packages/mask/content-script/site-adaptors/instagram.com/utils/avatar.ts deleted file mode 100644 index a32846f95f03..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/utils/avatar.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getAvatarId } from './user.js' - -export function getInjectNodeInfo(element: HTMLImageElement) { - const avatarId = getAvatarId(element.src) - - if (!avatarId) return - - // instagram bug, when page routing is switched, the avatar size on the timeline will initially be 150. - return { - element, - width: element.width === 150 ? 32 : element.width, - height: element.height === 150 ? 32 : element.height, - avatarId, - } -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/utils/selector.ts b/packages/mask/content-script/site-adaptors/instagram.com/utils/selector.ts deleted file mode 100644 index edec9650771c..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/utils/selector.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' - -type E = HTMLElement - -function querySelector(selector: string, singleMode = true) { - const ls = new LiveSelector().querySelector(selector) - return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector -} -function querySelectorAll(selector: string) { - return new LiveSelector().querySelectorAll(selector) -} - -export function searchProfileTabListLastChildSelector() { - return querySelector('section main div[role="tablist"] > :last-child') -} - -export function searchProfileTabPageSelector() { - return querySelector('section main[role="main"] > div > :last-child') -} - -export function searchProfileTabSelector() { - return querySelector('section main div[role="tablist"] a[aria-selected="false"]') -} - -export function searchProfileActiveTabSelector() { - return querySelector('section main div[role="tablist"] a[aria-selected="true"]') -} - -export function bioDescriptionSelector() { - return querySelector('section main header section > div:last-child h1') -} - -export function searchNickNameSelector() { - return querySelector('section main header section > div:last-child > div > span') -} - -export function searchProfileTabArticlePageSelector() { - return querySelector('section main div[role="tablist"]') -} - -export function searchInstagramAvatarListSelector() { - return querySelector('[role="dialog"] .piCib > div > form').closest(1).querySelector('button') -} - -export function searchInstagramAvatarSelector() { - return querySelector('header img, img[data-testid="user-avatar"]') -} - -export function searchInstagramProfileAvatarButtonSelector() { - return querySelector('section main header button > img').closest(3) -} - -export function searchInstagramAvatarSettingDialog() { - return querySelector('#ssrb_root_start').closest(1) -} - -export function searchInstagramAvatarEditPageSettingDialog() { - return querySelector('#react-root') -} - -export function searchInstagramProfileEditButton() { - return querySelector('a[href="/accounts/edit/"]') -} - -export function searchInstagramPostAvatarSelector() { - return new LiveSelector().querySelectorAll( - '[role="button"] > a > img[crossorigin="anonymous"]', - ) -} - -export function inpageAvatarSelector() { - return querySelectorAll('[role=main] article[role=presentation] header [role=button]') -} - -export function searchInstagramHandleSelector() { - return querySelector('a[role=link]:has(img[alt$=" profile picture"])') -} -export function searchInstagramSelfAvatarSelector() { - return querySelector( - 'div[style="transform: translateX(0px);"] > div > div > div:last-child > div > span[aria-describedby] > div > a img[crossorigin="anonymous"]', - ) -} diff --git a/packages/mask/content-script/site-adaptors/instagram.com/utils/user.ts b/packages/mask/content-script/site-adaptors/instagram.com/utils/user.ts deleted file mode 100644 index 474ae632c7d3..000000000000 --- a/packages/mask/content-script/site-adaptors/instagram.com/utils/user.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { compact } from 'lodash-es' -import { collectNodeText } from '../../../utils/index.js' -import { - bioDescriptionSelector, - searchInstagramHandleSelector, - searchNickNameSelector, - searchInstagramSelfAvatarSelector, -} from './selector.js' - -export function getBioDescription() { - const bio = bioDescriptionSelector().evaluate() - return bio ? collectNodeText(bio) : '' -} - -export function getPersonalHomepage() { - const node = searchInstagramHandleSelector().evaluate() - - if (!node) return - return node.href -} - -export function getNickname() { - const node = searchNickNameSelector().evaluate() - return node ? collectNodeText(node) : '' -} - -export function getUserId() { - const node = searchInstagramHandleSelector().evaluate() - if (!node) return - return compact(node.getAttribute('href')?.split('/')).pop() -} - -export function getAvatar() { - const node = searchInstagramSelfAvatarSelector().evaluate() - - if (!node) return '' - const imageURL = node.getAttribute('src') ?? '' - return imageURL.trim() -} - -const INSTAGRAM_AVATAR_ID_MATCH = /(\w+).(?:png|jpg|gif|bmp)/ - -export function getAvatarId(avatarURL: string) { - if (!avatarURL) return '' - const _url = new URL(avatarURL) - const match = _url.pathname.match(INSTAGRAM_AVATAR_ID_MATCH) - if (!match) return '' - return match[1] -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/AttachImageToComposition.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/AttachImageToComposition.ts deleted file mode 100644 index b1e104740e20..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/AttachImageToComposition.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { downloadUrl } from '../../../utils/downloadUrl.js' -import { composerModalTextAreaSelector, composerPreviewSelector } from '../utils/selector.js' -import { pasteTextToCompositionMinds } from './pasteTextToComposition.js' -import { MaskMessages } from '@masknet/shared-base' - -function hasSucceed() { - return composerPreviewSelector().evaluate() -} - -export function pasteImageToCompositionMinds() { - return async function ( - url: string | Blob, - { recover, relatedTextPayload }: SiteAdaptorUI.AutomationCapabilities.NativeCompositionAttachImageOptions, - ) { - const image = typeof url === 'string' ? await downloadUrl(url) : url - const data = [new ClipboardItem({ [image.type]: image })] - - pasteTextToCompositionMinds!(relatedTextPayload || '', { recover: false }) - - await navigator.clipboard.write(data) - composerModalTextAreaSelector().evaluate()?.focus() - document.execCommand('paste') - - if (hasSucceed()) { - // clear clipboard - return navigator.clipboard.writeText('') - } else if (recover) { - MaskMessages.events.autoPasteFailed.sendToLocal({ text: relatedTextPayload || '', image }) - } - } -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/gotoNewsFeedPage.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/gotoNewsFeedPage.ts deleted file mode 100644 index 469a90056952..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/gotoNewsFeedPage.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function gotoNewsFeedPageMinds() { - const path = '/newsfeed/subscriptions' - if (location.pathname.includes(path)) return - location.assign(path) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/gotoProfilePage.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/gotoProfilePage.ts deleted file mode 100644 index 6c714eaa50fe..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/gotoProfilePage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ProfileIdentifier } from '@masknet/shared-base' - -export function gotoProfilePageMinds(profile: ProfileIdentifier) { - const path = `/${profile.userId}` - ;(document.querySelector(`[href="${path}"]`) as HTMLElement | undefined)?.click() - setTimeout(() => { - // The classic way - if (!location.pathname.startsWith(path)) location.assign(path) - }, 400) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/openComposeBox.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/openComposeBox.ts deleted file mode 100644 index a1e97b0766d1..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/openComposeBox.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { makeTypedMessageText, type SerializableTypedMessages } from '@masknet/typed-message' -import { CrossIsolationMessages, type CompositionDialogEvent } from '@masknet/shared-base' -import { delay, waitDocumentReadyState } from '@masknet/kit' -import { i18n } from '../../../../shared-ui/locales_legacy/index.js' -import { composeButtonSelector, composeDialogIndicatorSelector, composeTextareaSelector } from '../utils/selector.js' - -export async function openComposeBoxMinds( - content: string | SerializableTypedMessages, - options?: CompositionDialogEvent['options'], -) { - await waitDocumentReadyState('interactive') - await delay(800) - - // active the compose dialog - const composeTextarea = composeTextareaSelector().evaluate() - const composeButton = composeButtonSelector().evaluate() - if (composeButton) composeButton.click() - if (composeTextarea) composeTextarea.focus() - await delay(800) - - // the indicator only available when compose dialog opened successfully - const composeIndicator = composeDialogIndicatorSelector().evaluate() - if (!composeIndicator) { - // eslint-disable-next-line no-alert - alert(i18n.t('automation_request_click_post_button')) - return - } - - await delay(800) - CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ - reason: 'popup', - open: true, - content: typeof content === 'string' ? makeTypedMessageText(content) : content, - options, - }) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/pasteTextToComposition.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/pasteTextToComposition.ts deleted file mode 100644 index 89fe3e02002f..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/pasteTextToComposition.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { untilElementAvailable } from '../../../utils/untilElementAvailable.js' -import { selectElementContents } from '../../../utils/selectElementContents.js' -import { delay } from '@masknet/kit' -import { inputText } from '@masknet/injected-script' -import { getEditorContent, hasEditor, hasFocus, isCompose } from '../utils/postBox.js' -import { composeButtonSelector, postEditorDraftContentSelector } from '../utils/selector.js' -import { MaskMessages } from '@masknet/shared-base' - -/** - * Wait for up to 5000 ms - * If not complete, let user do it. - */ -export const pasteTextToCompositionMinds: SiteAdaptorUI.AutomationCapabilities.NativeCompositionDialog['attachText'] = ( - text, - opt, -) => { - const interval = 500 - const timeout = 5000 - const worker = async function (abort: AbortSignal) { - const checkSignal = () => { - if (abort.aborted) throw new Error('Abort to paste text to the composition dialog at minds.') - } - - if (!isCompose() && !hasEditor()) { - // open the composer - await untilElementAvailable(composeButtonSelector()) - composeButtonSelector().evaluate()!.click() - checkSignal() - } - - // get focus - const i = postEditorDraftContentSelector() - const textarea = i.evaluate()! - await untilElementAvailable(i) - checkSignal() - while (!hasFocus(i)) { - textarea?.focus() - checkSignal() - await delay(interval) - } - - selectElementContents(textarea) - - // paste - inputText(text) - - // Simulate textarea input - SimulateTextareaInput(textarea.id) - - await delay(interval) - if (!getEditorContent().replaceAll('\n', '').includes(text.replaceAll('\n', ''))) { - fail(new Error('Unable to paste text automatically')) - } - } - - const fail = (e: Error) => { - if (opt?.recover) MaskMessages.events.autoPasteFailed.sendToLocal({ text }) - throw e - } - - return worker(AbortSignal.timeout(timeout)).then(undefined, (error) => fail(error)) -} - -function SimulateTextareaInput(id: string) { - document.getElementById(id)?.dispatchEvent(new Event('input', { bubbles: true })) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/automation/pasteToCommentBoxMinds.ts b/packages/mask/content-script/site-adaptors/minds.com/automation/pasteToCommentBoxMinds.ts deleted file mode 100644 index 0b07907beeb6..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/automation/pasteToCommentBoxMinds.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { delay } from '@masknet/kit' -import { selectElementContents } from '../../../utils/selectElementContents.js' -import { pasteText } from '@masknet/injected-script' -import { MaskMessages } from '@masknet/shared-base' - -export async function pasteToCommentBoxMinds(encryptedComment: string, current: PostInfo, dom: HTMLElement | null) { - const fail = () => { - MaskMessages.events.autoPasteFailed.sendToLocal({ text: encryptedComment }) - } - const root = dom || current.rootNode - if (!root) return fail() - const input = root.querySelector('[contenteditable]') - if (!input) return fail() - selectElementContents(input) - pasteText(encryptedComment) - await delay(200) - if (!root.innerText.includes(encryptedComment)) return fail() -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/base.ts b/packages/mask/content-script/site-adaptors/minds.com/base.ts deleted file mode 100644 index ae92c47d5150..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/base.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EncryptPayloadNetwork } from '@masknet/encryption' -import type { SiteAdaptor } from '@masknet/types' -import { EnhanceableSite } from '@masknet/shared-base' - -const origins = ['https://www.minds.com/*', 'https://minds.com/*', 'https://cdn.minds.com/*'] -export const mindsBase: SiteAdaptor.Base = { - networkIdentifier: EnhanceableSite.Minds, - encryptPayloadNetwork: EncryptPayloadNetwork.Minds, - declarativePermissions: { origins }, - shouldActivate(location) { - return location.hostname.endsWith('minds.com') - }, -} - -export function isMinds(ui: SiteAdaptor.Base) { - return ui.networkIdentifier === EnhanceableSite.Minds -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/collecting/getSearchedKeyword.ts b/packages/mask/content-script/site-adaptors/minds.com/collecting/getSearchedKeyword.ts deleted file mode 100644 index 16c8b3052349..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/collecting/getSearchedKeyword.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function getSearchedKeywordAtMinds() { - const params = new URLSearchParams(location.search) - if (location.pathname === '/discovery/search') { - return params.get('q') ?? '' - } - - return '' -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/collecting/identity.ts b/packages/mask/content-script/site-adaptors/minds.com/collecting/identity.ts deleted file mode 100644 index 73dc80b0f190..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/collecting/identity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ProfileIdentifier } from '@masknet/shared-base' -import type { SiteAdaptorUI } from '@masknet/types' -import { Minds } from '@masknet/web3-providers' -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { creator } from '../../../site-adaptor-infra/index.js' -import { mindsBase } from '../base.js' -import { handleSelector, selfInfoSelectors } from '../utils/selector.js' - -async function resolveLastRecognizedIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - async function assign() { - const { handle, avatar } = selfInfoSelectors() - - ref.value = { - identifier: ProfileIdentifier.of(mindsBase.networkIdentifier, handle).unwrapOr(undefined), - nickname: undefined, - avatar, - } - const user = await Minds.getUserByScreenName(handle) - - if (user) { - ref.value = { - identifier: ProfileIdentifier.of(mindsBase.networkIdentifier, user.username).unwrapOr(undefined), - nickname: user.name, - avatar: user.avatar_url?.medium, - } - } - } - function createWatcher(selector: LiveSelector) { - new MutationObserverWatcher(selector) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - }, - cancel, - ) - } - assign() - createWatcher(handleSelector()) -} - -export const IdentityProviderMinds: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveLastRecognizedIdentityInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/collecting/post.ts b/packages/mask/content-script/site-adaptors/minds.com/collecting/post.ts deleted file mode 100644 index 20f25ce66025..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/collecting/post.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { - makeTypedMessageEmpty, - makeTypedMessagePromise, - makeTypedMessageTuple, - makeTypedMessageTupleFromList, - makeTypedMessageImage, -} from '@masknet/typed-message' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' -import { untilElementAvailable } from '../../../utils/untilElementAvailable.js' -import { startWatch } from '../../../utils/startWatch.js' -import { mindsBase } from '../base.js' -import { mindsShared } from '../shared.js' -import { postParser } from '../utils/fetch.js' -import { postContentSelector } from '../utils/selector.js' -import { getCurrentIdentifier } from '../../utils.js' -import Services from '#services' - -export const PostProviderMinds: SiteAdaptorUI.CollectingCapabilities.PostsProvider = { - posts: creator.EmptyPostProviderState(), - start(signal) { - collectPostsMindsInner(this.posts, signal) - }, -} - -function collectPostsMindsInner( - store: SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'], - signal: AbortSignal, -) { - startWatch( - new MutationObserverWatcher(postContentSelector()).useForeach((node, key, metadata) => { - const activitySelector = new LiveSelector() - .replace(() => [metadata.realCurrent]) - .closest('m-activity, m-activity__modal') - const activityNode = activitySelector.evaluate()[0]! as HTMLElement - - // ? inject after comments - const commentsSelector = activitySelector - .clone() - .querySelectorAll('m-activity__content .m-comment__message') - - // ? inject comment text field - const commentBoxSelector = activitySelector - .clone() - .querySelectorAll('.m-commentPoster__form') - .map((x) => x.parentElement) - - const { subscriptions, ...info } = createRefsForCreatePostContext() - const postInfo = mindsShared.utils.createPostContext({ - site: EnhanceableSite.Minds, - comments: { commentBoxSelector, commentsSelector }, - rootElement: metadata, - suggestedInjectionPoint: node, - ...subscriptions, - }) - - store.set(metadata, postInfo) - - function collectLinks() { - if (!activityNode) return - - const links = [...activityNode.querySelectorAll('a')].filter((x) => x.rel) - const seen = new Set() - for (const x of links) { - if (seen.has(x.href)) continue - seen.add(x.href) - info.postMetadataMentionedLinks.set(x, x.href) - } - } - - function collectPostInfo() { - const { pid, messages, handle, name, avatar } = postParser(activityNode) - if (!pid) return - const postBy = ProfileIdentifier.of(mindsBase.networkIdentifier, handle).unwrapOr(null) - info.postID.value = pid - info.postBy.value = postBy - info.nickname.value = name - info.avatarURL.value = avatar || null - - if (name && postBy) { - const currentProfile = getCurrentIdentifier() - - Services.Identity.updateProfileInfo(postBy, { - nickname: name, - avatarURL: avatar, - }) - if (currentProfile?.linkedPersona) - Services.Identity.createNewRelation(postBy, currentProfile.linkedPersona) - } - // decode steganographic image - // don't add await on this - const images = untilElementAvailable( - new LiveSelector([activityNode]).querySelectorAll( - '.m-activityContent__media--image img', - ), - 10000, - ) - .then(() => getMetadataImages(activityNode)) - .then((urls) => { - for (const url of urls) info.postMetadataImages.add(url) - if (urls.length) - return makeTypedMessageTupleFromList(...urls.map((x) => makeTypedMessageImage(x))) - return makeTypedMessageEmpty() - }) - .catch(() => makeTypedMessageEmpty()) - - info.postMessage.value = makeTypedMessageTuple([...messages, makeTypedMessagePromise(images)]) - } - - function run() { - collectPostInfo() - collectLinks() - } - - run() - - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => store.delete(metadata), - } - }), - signal, - ) -} - -function getMetadataImages(activityNode: HTMLElement): string[] { - const imgNodes = activityNode.querySelectorAll('.m-activityContent__media--image img') || [] - - if (!imgNodes.length) return [] - const imgUrls = Array.from(imgNodes) - .map((node) => node.src) - // FIXME! there's a CORS issue on the CDN - .map((src) => src.replace('cdn.minds.com', 'minds.com')) - // Use the master version of the image so the dimensions don't change - .map((src) => src.replace('xlarge', 'master')) - .filter(Boolean) - if (!imgUrls.length) return [] - return imgUrls -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/collecting/theme.ts b/packages/mask/content-script/site-adaptors/minds.com/collecting/theme.ts deleted file mode 100644 index 3dadc1d26594..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/collecting/theme.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { fromRGB, getBackgroundColor, isDark } from '@masknet/plugin-infra/content-script' -import { ThemeMode } from '@masknet/web3-shared-base' -import { creator } from '../../../site-adaptor-infra/utils.js' - -function resolveThemeSettingsInner( - ref: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'], - cancel: AbortSignal, -) { - function updateThemeColor() { - const backgroundColor = getBackgroundColor(document.body) - ref.value = { - ...ref.value, - mode: isDark(fromRGB(backgroundColor)!) ? ThemeMode.Dark : ThemeMode.Light, - } - } - - updateThemeColor() - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - updateThemeColor() - }) - }) - - observer.observe(document.body, { - attributes: true, - attributeOldValue: true, - attributeFilter: ['class'], - }) - - cancel.addEventListener('abort', () => observer.disconnect()) -} - -export const ThemeSettingsProviderMinds: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider = { - recognized: creator.EmptyThemeSettingsProviderState(), - async start(cancel) { - resolveThemeSettingsInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/customization/custom.ts b/packages/mask/content-script/site-adaptors/minds.com/customization/custom.ts deleted file mode 100644 index aef7ef6ac015..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/customization/custom.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useMemo } from 'react' -import { produce, setAutoFreeze } from 'immer' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { fromRGB, shade, toRGB } from '@masknet/plugin-infra/content-script' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' - -export function useThemeMindsVariant(baseTheme: Theme) { - const themeSettings = useThemeSettings() - - return useMemo(() => { - const primaryColorRGB = fromRGB(themeSettings.color)! - const primaryContrastColorRGB = fromRGB('rgb(255, 255, 255)') - setAutoFreeze(false) - - const MindsTheme = produce(baseTheme, (theme) => { - theme.palette.primary = { - light: toRGB(shade(primaryColorRGB, 10)), - main: toRGB(primaryColorRGB), - dark: toRGB(shade(primaryColorRGB, -10)), - contrastText: toRGB(primaryContrastColorRGB), - } - theme.shape.borderRadius = 15 - theme.breakpoints.values = { xs: 0, sm: 687, md: 1024, lg: 1220, xl: 1920 } - theme.components = theme.components || {} - theme.components.MuiTypography = { - styleOverrides: { - root: { - fontFamily: 'Roboto,Helvetica,sans-serif', - }, - }, - } - theme.components.MuiPaper = { - defaultProps: { - elevation: 0, - }, - } - theme.components.MuiTab = { - styleOverrides: { - root: { - textTransform: 'none', - }, - }, - } - }) - setAutoFreeze(true) - return unstable_createMuiStrictModeTheme(MindsTheme) - }, [baseTheme, themeSettings]) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/customization/render-fragments.tsx b/packages/mask/content-script/site-adaptors/minds.com/customization/render-fragments.tsx deleted file mode 100644 index 25aa92aa1744..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/customization/render-fragments.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { RenderFragmentsContextType } from '@masknet/typed-message-react' -import { memo } from 'react' -import { Link } from '@mui/material' -import { useTagEnhancer } from '../../../../shared-ui/TypedMessageRender/Components/Text.js' - -export const MindsRenderFragments: RenderFragmentsContextType = { - AtLink: memo(function (props) { - const target = '/' + props.children.slice(1) - return - }), - HashLink: memo(function (props) { - const text = props.children.slice(1) - const target = `/discovery/search?q=%23${encodeURIComponent(text)}` - const { hasMatch, ...events } = useTagEnhancer('hash', text) - return ( - { - e.stopPropagation() - }} - /> - ) - }), - CashLink: memo(function (props) { - const text = props.children.slice(1) - const target = `/discovery/search?q=$${encodeURIComponent(text)}` - const { hasMatch, ...events } = useTagEnhancer('cash', text) - return ( - { - e.stopPropagation() - }} - /> - ) - }), - Image: () => null, -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/index.ts b/packages/mask/content-script/site-adaptors/minds.com/index.ts deleted file mode 100644 index 3fb129ecd6cb..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { mindsBase } from './base.js' - -defineSiteAdaptorUI({ - ...mindsBase, - load: () => import('./ui-provider.js'), - hotModuleReload(callback) { - if (import.meta.webpackHot) { - import.meta.webpackHot.accept('./ui-provider.ts', async () => { - callback((await import('./ui-provider.js')).default) - }) - } - }, -}) diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/Avatar/index.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/Avatar/index.tsx deleted file mode 100644 index 497917cb6203..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/Avatar/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Plugin } from '@masknet/plugin-infra' -import { Avatar } from '../../../../components/InjectedComponents/Avatar.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { inpageAvatarSelector } from '../../utils/selector.js' - -export async function injectAvatar(signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(inpageAvatarSelector()).useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele.firstChild as HTMLElement - // TODO fetch userId - const userId = '' - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -
- {userId ? - - : null} -
, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/Banner.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/Banner.tsx deleted file mode 100644 index f19f12fa6b3f..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/Banner.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { Banner } from '../../../components/Welcomes/Banner.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { postEditorInTimelineSelector, postEditorInDialogSelector } from '../utils/selector.js' - -export function injectBannerAtMinds(signal: AbortSignal) { - injectBanner(postEditorInTimelineSelector(), signal, ) - injectBanner(postEditorInDialogSelector(), signal, ) -} - -function injectBanner(ls: LiveSelector, signal: AbortSignal, element: JSX.Element) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, signal) - - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { - signal, - }).render(element) -} - -const useStyles = makeStyles()({ - buttonText: { - margin: '-2px 0 !important', - transform: 'translateX(200px) translateY(-78px)', - }, - content: { - marginRight: 5, - }, - buttonNoMargin: { - margin: '0 !important', - }, -}) - -function MindsBannerTimeline() { - const { classes } = useStyles() - return ( - - ) -} - -function MindsBannerPopup() { - const { classes } = useStyles() - return -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/CommentBox.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/CommentBox.tsx deleted file mode 100644 index 69313cf008f6..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/CommentBox.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable tss-unused-classes/unused-classes */ -import { makeStyles } from '@masknet/theme' -import { injectCommentBoxDefaultFactory } from '../../../site-adaptor-infra/defaults/index.js' -import { pasteToCommentBoxMinds } from '../automation/pasteToCommentBoxMinds.js' -import type { PostContext } from '@masknet/plugin-infra/content-script' - -export default function injectCommentBoxAtMinds(): (signal: AbortSignal, current: PostContext) => void { - return injectCommentBoxDefaultFactory( - pasteToCommentBoxMinds, - (classes) => ({ - inputProps: { - classes, - }, - }), - makeStyles()((theme) => ({ - root: { - fontSize: 16, - background: 'transparent', - // FIXME: A weird issue with margins - width: '96.2%', - height: 44, - borderRadius: 2, - padding: '2px 1em', - border: `1px solid ${theme.palette.mode === 'dark' ? '#414c57' : '#d3dbe3'}`, - margin: '0 10px 10px', - color: theme.palette.mode === 'dark' ? '#fff' : '#43434d', - fontWeight: 400, - }, - input: { - '&::placeholder': { - color: theme.palette.mode === 'dark' ? '#b8c1c' : '#72727c', - opacity: 1, - fontWeight: 400, - }, - '&:focus::placeholder': { - color: 'transparent', - }, - }, - })), - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialog.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialog.tsx deleted file mode 100644 index 9b346ac7f672..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialog.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Composition } from '../../../components/CompositionDialog/Composition.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { composerModalSelector, rootSelector } from '../utils/selector.js' - -export function injectPostDialogAtMinds(signal: AbortSignal) { - renderPostDialogTo('popup', composerModalSelector(), signal) - renderPostDialogTo('timeline', rootSelector(), signal) -} - -function renderPostDialogTo(reason: 'timeline' | 'popup', ls: LiveSelector, signal: AbortSignal) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, signal) - - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialogHint.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialogHint.tsx deleted file mode 100644 index f2051461342b..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/PostDialogHint.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useCallback } from 'react' -import { type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { CrossIsolationMessages } from '@masknet/shared-base' -import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { postEditorInDialogSelector, postEditorInTimelineSelector } from '../utils/selector.js' -import { isMinds } from '../base.js' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/ui.js' -import type { CompositionType } from '@masknet/plugin-infra/content-script' - -export function injectPostDialogHintAtMinds(signal: AbortSignal) { - renderPostDialogHintTo(postEditorInDialogSelector(), signal, 'popup') - renderPostDialogHintTo(postEditorInTimelineSelector(), signal, 'timeline') -} - -function renderPostDialogHintTo(ls: LiveSelector, signal: AbortSignal, reason: CompositionType) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, signal) - - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { - signal, - }).render() -} - -interface StyleProps { - reason: string -} - -const useStyles = makeStyles()((theme, { reason }) => ({ - buttonTransform: { - ...(reason === 'timeline' ? - { - width: '40px', - transform: !isMinds(activatedSiteAdaptorUI!) ? 'translateX(200px) translateY(-78px)' : '', - } - : {}), - }, - iconButton: { - '&:hover': { - background: 'none', - }, - }, -})) - -function PostDialogHintAtMinds({ reason }: { reason: 'timeline' | 'popup' }) { - const { classes } = useStyles({ reason }) - - const onHintButtonClicked = useCallback( - () => CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ reason, open: true }), - [reason], - ) - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/PostInspector.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/PostInspector.tsx deleted file mode 100644 index 2d464eba09aa..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/PostInspector.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/inject/PostInspector.js' -import type { PostInfo } from '@masknet/plugin-infra/content-script' - -export function injectPostInspectorAtMinds(signal: AbortSignal, current: PostInfo) { - return injectPostInspectorDefault()(current, signal) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/PostReplacer.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/PostReplacer.tsx deleted file mode 100644 index af778cf4da1a..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/PostReplacer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { injectPostReplacer } from '../../../site-adaptor-infra/defaults/inject/PostReplacer.js' -import type { PostInfo } from '@masknet/plugin-infra/content-script' - -function resolveContentNode(node: HTMLElement) { - return node.closest( - [ - 'm-activity__content .m-activityContentText__body > m-readmore span:first-child', - 'm-activity__content .m-activityContent__mediaDescriptionText', - ].join(',') as any, - ) -} - -export function injectPostReplacerAtMinds(signal: AbortSignal, current: PostInfo) { - return injectPostReplacer({ - zipPost(node) { - if (node.destroyed) return - const langNode = resolveContentNode(node.current) - if (langNode) langNode.style.display = 'none' - }, - unzipPost(node) { - if (node.destroyed || !node.current) return - const langNode = resolveContentNode(node.current) - if (langNode) langNode.style.display = 'unset' - }, - })(current, signal) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/ProfileCover.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/ProfileCover.tsx deleted file mode 100644 index 7154177bc3f2..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/ProfileCover.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { searchMindsProfileCover } from '../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { ProfileCover } from '../../../components/InjectedComponents/ProfileCover.js' - -export function injectMindsProfileCover(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchMindsProfileCover()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -function ProfileCoverAtMinds() { - return -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/SearchResultInspector.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/SearchResultInspector.tsx deleted file mode 100644 index 5fb0b99d3bd8..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/SearchResultInspector.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { SearchResultInspector } from '../../../components/InjectedComponents/SearchResultInspector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { searchResultHeadingSelector } from '../utils/selector.js' - -export function injectSearchResultInspectorAtMinds(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchResultHeadingSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint.tsx deleted file mode 100644 index df64cb12bc74..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { toolboxInSidebarSelector } from '../utils/selector.js' -import { ToolboxHintAtMinds } from './ToolboxHint_UI.js' - -export function injectToolboxHintAtMinds(signal: AbortSignal, category: 'wallet' | 'application') { - const watcher = new MutationObserverWatcher(toolboxInSidebarSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint_UI.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint_UI.tsx deleted file mode 100644 index 24d3844ff5fc..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/ToolboxHint_UI.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { ToolboxHintUnstyled } from '../../../components/InjectedComponents/ToolboxUnstyled.js' -import { styled, ListItemButton, Typography, ListItemIcon, useMediaQuery } from '@mui/material' - -const mindsBreakPoint = 1221 /** px */ - -const Container = styled('div')` - height: 45px; - margin-bottom: 10px; -` -const Item = styled(ListItemButton)` - border-radius: 8px; - height: 45px; - padding: 4px 12px 4px 0; - color: ${({ theme }) => theme.palette.primary.main}; - &:hover { - background: unset; - color: rgb(48, 153, 242); - } - @media screen and (max-width: ${mindsBreakPoint}px) { - padding: 12px 0; - justify-content: center; - } -` -const Text = styled(Typography)` - font-size: 0.9375rem; - font-weight: 500; - color: inherit !important; - /* Minds font */ - font-family: Roboto, Helvetica, sans-serif; - font-weight: 700; - font-size: 17px; - line-height: 44px; -` -const Icon = styled(ListItemIcon)` - color: inherit; - min-width: 48px; - margin-left: 6px; - @media screen and (max-width: ${mindsBreakPoint}px) { - min-width: 0; - } -` - -export function ToolboxHintAtMinds(props: { category: 'wallet' | 'application' }) { - const mini = useMediaQuery(`(max-width: ${mindsBreakPoint}px)`) - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/injection/inject.tsx b/packages/mask/content-script/site-adaptors/minds.com/injection/inject.tsx deleted file mode 100644 index 8f7c7290d9b6..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/injection/inject.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { injectPostDialogAtMinds } from './PostDialog.js' -import { injectPostDialogHintAtMinds } from './PostDialogHint.js' - -export function injectPostBoxComposed(signal: AbortSignal) { - injectPostDialogAtMinds(signal) - injectPostDialogHintAtMinds(signal) -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/shared.ts b/packages/mask/content-script/site-adaptors/minds.com/shared.ts deleted file mode 100644 index f8b2161512f5..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/shared.ts +++ /dev/null @@ -1,40 +0,0 @@ -import urlcat from 'urlcat' -import { type ProfileIdentifier, type PostIdentifier } from '@masknet/shared-base' -import { openWindow } from '@masknet/shared-base-ui' -import type { SiteAdaptor } from '@masknet/types' -import { createSiteAdaptorSpecializedPostContext } from '../../site-adaptor-infra/utils/create-post-context.js' -import { hasPayloadLike } from '../../utils/index.js' -import { mindsBase } from './base.js' -import { usernameValidator } from './utils/user.js' - -function getPostURL(post: PostIdentifier): URL { - return new URL(`https://minds.com/newsfeed/${post.postId}`) -} - -function getProfileURL(profile: ProfileIdentifier): URL | null { - return new URL('https://www.minds.com') -} - -function getShareURL(text: string): URL | null { - return new URL( - urlcat('https://www.minds.com/newsfeed/subscriptions', { - intentUrl: text, - }), - ) -} - -export const mindsShared: SiteAdaptor.Shared & SiteAdaptor.Base = { - ...mindsBase, - utils: { - isValidUsername: usernameValidator, - getPostURL, - getProfileURL, - share(message) { - openWindow(getShareURL(message)) - }, - createPostContext: createSiteAdaptorSpecializedPostContext(mindsBase.networkIdentifier, { - hasPayloadLike, - getURLFromPostIdentifier: getPostURL, - }), - }, -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/ui-provider.ts b/packages/mask/content-script/site-adaptors/minds.com/ui-provider.ts deleted file mode 100644 index f2489c84aa0a..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/ui-provider.ts +++ /dev/null @@ -1,207 +0,0 @@ -/* eslint-disable tss-unused-classes/unused-classes */ -import type { SiteAdaptorUI } from '@masknet/types' -import { makeStyles } from '@masknet/theme' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { activatedSiteAdaptor_state, creator, stateCreator } from '../../site-adaptor-infra/index.js' -import { injectPostCommentsDefault } from '../../site-adaptor-infra/defaults/index.js' -import { injectPageInspectorDefault } from '../../site-adaptor-infra/defaults/inject/PageInspector.js' -import { createTaskStartSetupGuideDefault } from '../../site-adaptor-infra/defaults/inject/StartSetupGuide.js' -import { InitAutonomousStateProfiles } from '../../site-adaptor-infra/defaults/state/InitProfiles.js' -import { pasteImageToCompositionMinds } from './automation/AttachImageToComposition.js' -import { gotoNewsFeedPageMinds } from './automation/gotoNewsFeedPage.js' -import { gotoProfilePageMinds } from './automation/gotoProfilePage.js' -import { openComposeBoxMinds } from './automation/openComposeBox.js' -import { pasteTextToCompositionMinds } from './automation/pasteTextToComposition.js' -import { mindsBase } from './base.js' -import getSearchedKeywordAtMinds from './collecting/getSearchedKeyword.js' -import { IdentityProviderMinds } from './collecting/identity.js' -import { ThemeSettingsProviderMinds } from './collecting/theme.js' -import { PostProviderMinds } from './collecting/post.js' -import { useThemeMindsVariant } from './customization/custom.js' -import injectCommentBoxAtMinds from './injection/CommentBox.js' -import { injectPostBoxComposed } from './injection/inject.js' -import { injectPostInspectorAtMinds } from './injection/PostInspector.js' -import { injectPostReplacerAtMinds } from './injection/PostReplacer.js' -import { injectSearchResultInspectorAtMinds } from './injection/SearchResultInspector.js' -import { injectBannerAtMinds } from './injection/Banner.js' -import { injectToolboxHintAtMinds } from './injection/ToolboxHint.js' -import { MindsRenderFragments } from './customization/render-fragments.js' -import { enableFbStyleTextPayloadReplace } from '../../../shared-ui/TypedMessageRender/transformer.js' -import { injectMindsProfileCover } from './injection/ProfileCover.js' -import { injectAvatar } from './injection/Avatar/index.js' -import { mindsShared } from './shared.js' - -const CurrentVisitingIdentityProviderDefault: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(signal) {}, -} - -const useInjectedDialogClassesOverwriteMinds = makeStyles()((theme) => { - const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` - return { - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - [smallQuery]: { - display: 'block !important', - }, - }, - container: { - alignItems: 'center', - }, - paper: { - width: '600px !important', - minHeight: 400, - maxHeight: 620, - maxWidth: 'none', - boxShadow: 'none', - backgroundImage: 'none', - [smallQuery]: { - display: 'block !important', - margin: 12, - }, - scrollbarWidth: 'none', - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - dialogTitle: { - display: 'grid', - gridTemplateColumns: '1fr auto 1fr', - alignItems: 'center', - padding: 16, - position: 'relative', - background: theme.palette.maskColor.modalTitleBg, - borderBottom: 'none', - '& > p': { - fontSize: 18, - lineHeight: '22px', - display: 'inline-block', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - [smallQuery]: { - display: 'flex', - justifyContent: 'start', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px 11px !important', - }, - }, - dialogContent: { - backgroundColor: theme.palette.maskColor.bottom, - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px', - }, - }, - dialogActions: { - backgroundColor: theme.palette.maskColor.bottom, - padding: '6px 16px', - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px !important', - }, - }, - dialogBackdropRoot: { - backgroundColor: theme.palette.mode === 'dark' ? 'rgba(110, 118, 125, 0.4)' : 'rgba(0, 0, 0, 0.4)', - }, - } -}) - -const mindsUI: SiteAdaptorUI.Definition = { - ...mindsBase, - ...mindsShared, - automation: { - maskCompositionDialog: { - open: openComposeBoxMinds, - }, - nativeCommentBox: undefined, - nativeCompositionDialog: { - attachText: pasteTextToCompositionMinds, - // TODO: make a better way to detect - attachImage: pasteImageToCompositionMinds(), - }, - redirect: { - gotoNewsFeed: gotoNewsFeedPageMinds, - gotoProfilePage: gotoProfilePageMinds, - }, - }, - collecting: { - identityProvider: IdentityProviderMinds, - postsProvider: PostProviderMinds, - themeSettingsProvider: ThemeSettingsProviderMinds, - currentVisitingIdentityProvider: CurrentVisitingIdentityProviderDefault, - getSearchedKeyword: getSearchedKeywordAtMinds, - }, - customization: { - sharedComponentOverwrite: { - InjectedDialog: { - classes: useInjectedDialogClassesOverwriteMinds, - }, - }, - componentOverwrite: { - RenderFragments: MindsRenderFragments, - }, - useTheme: useThemeMindsVariant, - }, - init(signal) { - const profiles = stateCreator.profiles() - InitAutonomousStateProfiles(signal, profiles, mindsShared.networkIdentifier) - enableFbStyleTextPayloadReplace() - return { profiles } - }, - injection: { - toolbox: injectToolboxHintAtMinds, - profileCover: injectMindsProfileCover, - pageInspector: injectPageInspectorDefault(), - postInspector: injectPostInspectorAtMinds, - postReplacer: injectPostReplacerAtMinds, - banner: injectBannerAtMinds, - searchResult: injectSearchResultInspectorAtMinds, - newPostComposition: { - start: injectPostBoxComposed, - supportedInputTypes: { - text: true, - image: true, - }, - supportedOutputTypes: { - text: true, - image: true, - }, - }, - setupWizard: createTaskStartSetupGuideDefault(), - commentComposition: { - compositionBox: injectPostCommentsDefault(), - commentInspector: injectCommentBoxAtMinds(), - }, - // NOT SUPPORTED YET - userBadge: undefined, - avatar: injectAvatar, - }, - configuration: { - steganography: { - // ! Change this might be a breaking change ! - password() { - const id = - IdentityProviderMinds.recognized.value.identifier?.userId || - activatedSiteAdaptor_state!.profiles.value?.[0].identifier.userId - if (!id) throw new Error('Cannot figure out password') - return ProfileIdentifier.of(EnhanceableSite.Minds, id) - .expect(`${id} should be a valid user id`) - .toText() - }, - }, - }, -} -export default mindsUI diff --git a/packages/mask/content-script/site-adaptors/minds.com/utils/fetch.ts b/packages/mask/content-script/site-adaptors/minds.com/utils/fetch.ts deleted file mode 100644 index e54c5b54380e..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/utils/fetch.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { flattenDeep } from 'lodash-es' -import { - isTypedMessageEmpty, - isTypedMessageText, - makeTypedMessageAnchor, - makeTypedMessageEmpty, - makeTypedMessageText, - type TypedMessage, -} from '@masknet/typed-message' -import { assertNonNull } from '@masknet/kit' - -function parseNameArea(nameArea: HTMLAnchorElement) { - const displayNameNode = nameArea.querySelector('span') - - return { - name: displayNameNode && assertNonNull(displayNameNode) ? displayNameNode.innerText : nameArea.innerText, - handle: nameArea.href.slice(8).split('/')[1], - } -} - -function postIdParser(node: HTMLElement) { - const idNode = node.querySelector('m-activity__permalink .m-activityPermalink__wrapper--link') - return idNode ? idNode.getAttribute('href')?.split('/')[2] ?? undefined : undefined -} - -function postNameParser(node: HTMLElement) { - return parseNameArea( - assertNonNull( - node.querySelector( - [ - 'm-activity__ownerblock .m-activityOwnerBlock__primaryName', - 'm-activity__ownerblock .m-activityOwnerBlock__secondaryName', // It's `secondaryName` in detail page - ].join(','), - ), - ), - ) -} - -function postAvatarParser(node: HTMLElement) { - const avatarElement = node.querySelector('m-hovercard img') - return avatarElement ? avatarElement.src : undefined -} - -function resolveType(content: string) { - if (content.startsWith('@')) return 'user' - if (content.startsWith('#')) return 'hash' - if (content.startsWith('$')) return 'cash' - return 'normal' -} -function postContentMessageParser(node: HTMLElement) { - function make(node: Node): TypedMessage | TypedMessage[] { - if (node.nodeType === Node.TEXT_NODE) { - if (!node.nodeValue) return makeTypedMessageEmpty() - return makeTypedMessageText(node.nodeValue) - } else if (node instanceof HTMLAnchorElement && !node.className.includes('m-activityContentMedia__link')) { - const anchor = node - const href = anchor.getAttribute('title') ?? anchor.getAttribute('href') - const content = anchor.textContent - if (!content) return makeTypedMessageEmpty() - return makeTypedMessageAnchor(resolveType(content), href ?? '', content) - } else if (node instanceof HTMLImageElement) { - const image = node - const src = image.getAttribute('src') - const matched = src?.match(/emoji\/v2\/svg\/([\w-]+)\.svg/) - if (!matched) return makeTypedMessageEmpty() - const points = matched[1].split('-').map((point) => Number.parseInt(point, 16)) - return makeTypedMessageText(String.fromCodePoint(...points)) - } else if (node.childNodes.length) { - const flattened = flattenDeep(Array.from(node.childNodes, make)) - // conjunct text messages under same node - if (flattened.every(isTypedMessageText)) - return makeTypedMessageText(flattened.map((x) => x.content).join('')) - return flattened - } else return makeTypedMessageEmpty() - } - - const content = node.querySelector('m-activity__content') - return content ? Array.from(content.childNodes).flatMap(make) : [] -} - -export function postParser(node: HTMLElement) { - return { - ...postNameParser(node), - avatar: postAvatarParser(node), - pid: postIdParser(node), - - messages: postContentMessageParser(node).filter((x) => !isTypedMessageEmpty(x)), - } -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/utils/postBox.ts b/packages/mask/content-script/site-adaptors/minds.com/utils/postBox.ts deleted file mode 100644 index e87439526446..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/utils/postBox.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { LiveSelector } from '@dimensiondev/holoflows-kit' -import { composerModalSelector, postEditorDraftContentSelector } from './selector.js' - -export function getEditorContent() { - const editorNode = postEditorDraftContentSelector().evaluate() - if (!editorNode) return '' - return (editorNode as HTMLTextAreaElement).value -} - -export function isCompose() { - return !!composerModalSelector().evaluate() -} - -export function hasFocus(x: LiveSelector) { - return x.evaluate() === document.activeElement -} - -export function hasEditor() { - return !!postEditorDraftContentSelector().evaluate() -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/utils/selector.ts b/packages/mask/content-script/site-adaptors/minds.com/utils/selector.ts deleted file mode 100644 index 1b77daf5fecf..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/utils/selector.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' - -type E = HTMLElement - -function querySelector(selector: string, singleMode = true) { - const ls = new LiveSelector().querySelector(selector) - return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector -} - -export function rootSelector() { - return querySelector('m-app') -} - -export function composerModalSelector() { - return querySelector('m-composer__modal') -} - -export function postEditorInDialogSelector() { - return querySelector('m-composer__modal m-composer__titlebar m-composertitlebar__dropdown', true) -} - -export function postEditorInTimelineSelector() { - return querySelector('m-composer m-composer__toolbar > div > :nth-child(6)', true) -} - -export function toolboxInSidebarSelector() { - return querySelector('.m-sidebarNavigation__list li:nth-child(7)') -} - -export function postEditorDraftContentSelector() { - return querySelector('m-composer__modal m-composer__textarea textarea.m-composerTextarea__message') -} - -export function handleSelector() { - return querySelector('.m-sidebarNavigation__item--user [data-ref="sidenav-channel"]') -} - -export function selfInfoSelectors() { - return { - handle: handleSelector().evaluate()?.getAttribute('href')?.slice(1).replace(/^@/, ''), // Could include `@` by chance. - avatar: querySelector('.m-sidebarNavigation__item--user > a > div > img').evaluate()?.src, - } -} - -export function inpageAvatarSelector() { - return new LiveSelector().querySelectorAll('.m-activityOwnerBlock__avatar') -} - -export function composeButtonSelector() { - return querySelector( - [ - '.m-sidebarNavigation__item m-sidebarNavigation__item--compose', - '.m-sidebarNavigation__item--compose a', // legacy - ].join(','), - true, - ) -} - -export function composeTextareaSelector() { - return new LiveSelector().querySelector('m-composer__textarea textarea').enableSingleMode() -} - -export function composeDialogIndicatorSelector() { - return new LiveSelector().querySelector('m-composer__modal') -} - -export function composerModalTextAreaSelector() { - return new LiveSelector() - .querySelector('m-composer__modal m-composer__textArea .m-composer__textArea textarea') - .enableSingleMode() -} - -export function composerPreviewSelector() { - return new LiveSelector() - .querySelector('m-composer__modal m-composer__preview img') - .enableSingleMode() -} - -export function searchResultHeadingSelector() { - return querySelector('m-discovery__search') -} - -export function postContentSelector() { - return new LiveSelector().querySelectorAll( - [ - 'm-activity m-activity__content .m-activityTop__mainColumn', - 'm-activity m-activity__content .m-activityContentText__body > m-readmore > span:first-child', - 'm-activity:not(.m-activity--minimalMode) m-activity__content .m-activityContent__messageWrapper > span:first-child', - 'm-activity:not(.m-activity--minimalMode) m-activity__content .m-activityContent__mediaDescriptionText', - ].join(','), - ) -} - -export function searchMindsProfileCover() { - return querySelector('div[data-cy="data-minds-channel-banner"]') -} diff --git a/packages/mask/content-script/site-adaptors/minds.com/utils/user.ts b/packages/mask/content-script/site-adaptors/minds.com/utils/user.ts deleted file mode 100644 index 2250df0cdec5..000000000000 --- a/packages/mask/content-script/site-adaptors/minds.com/utils/user.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { isNull } from 'lodash-es' - -export function usernameValidator(name: string) { - for (const v of [/(minds|admin)/i, /.{16,}/, /\W/]) { - if (!isNull(v.exec(name))) { - return false - } - } - - return name.length >= 4 -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/base.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/base.ts deleted file mode 100644 index b43581f8f9c2..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/base.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EncryptPayloadNetwork } from '@masknet/encryption' -import type { SiteAdaptor } from '@masknet/types' -import { EnhanceableSite } from '@masknet/shared-base' - -const origins = ['https://mirror.xyz/*'] -export const mirrorBase: SiteAdaptor.Base = { - networkIdentifier: EnhanceableSite.Mirror, - encryptPayloadNetwork: EncryptPayloadNetwork.Unknown, - declarativePermissions: { origins }, - shouldActivate(location) { - return location.host.endsWith(EnhanceableSite.Mirror) - }, -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/identity.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/identity.ts deleted file mode 100644 index 1fa81ca46e9d..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/identity.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { delay } from '@masknet/kit' -import { EnhanceableSite, getCookie } from '@masknet/shared-base' -import { Mirror } from '@masknet/web3-providers' -import type { Writer } from '@masknet/web3-providers/types' -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { formatWriter, getMirrorUserId } from './utils.js' - -async function getCurrentUserInfo() { - if (location.host !== EnhanceableSite.Mirror) return - const userAddress = getCookie('user_wallet') - - if (!userAddress) return - return Mirror.getWriter(userAddress) -} - -function resolveLastRecognizedIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const assign = async () => { - await delay(2000) - - const writer = await getCurrentUserInfo() - if (!writer) return - - ref.value = formatWriter(writer, true) - } - - assign() - - window.addEventListener('locationchange', assign, { signal: cancel }) -} - -function resolveCurrentVisitingIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - ownerRef: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const assign = async () => { - // get from mirror api - const userId = getMirrorUserId(location.href) - const ownerId = ownerRef.value.identifier?.userId - const isOwner = !!(userId && ownerId && userId.toLowerCase() === ownerId.toLowerCase()) - if (userId) { - const writer = await Mirror.getWriter(userId) - if (writer) { - ref.value = formatWriter(writer, isOwner) - return - } - } - // Could be `/dashboard` or `/dashboard/settings` - if (location.pathname.startsWith('/dashboard')) { - ref.value = {} - return - } - - // get from local - // why local as second option? - // when location change, then __NEXT_DATA__ data could be stale, - const script = document.getElementById('__NEXT_DATA__')?.innerHTML - if (!script) return - const INIT_DATA = JSON.parse(script) - if (!INIT_DATA) return - - const writer = (INIT_DATA.props?.pageProps?.publicationLayoutProject ?? - INIT_DATA.props?.pageProps?.project) as Writer - ref.value = formatWriter(writer, isOwner) - } - - assign() - - window.addEventListener('locationchange', assign, { signal: cancel }) -} - -export const IdentityProviderMirror: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveLastRecognizedIdentityInner(this.recognized, cancel) - }, -} - -export const CurrentVisitingIdentityProviderMirror: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveCurrentVisitingIdentityInner(this.recognized, IdentityProviderMirror.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/posts.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/posts.ts deleted file mode 100644 index f9b90be4265a..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/posts.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Mirror } from '@masknet/web3-providers' -import type { PostContextCoAuthor } from '@masknet/plugin-infra/content-script' -import type { SiteAdaptorUI } from '@masknet/types' -import { creator } from '../../../site-adaptor-infra/index.js' -import { postsContentSelector } from '../utils/selectors.js' -import { mirrorShared } from '../shared.js' -import { startWatch } from '../../../utils/startWatch.js' -import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' -import { formatWriter, getMirrorPageType, MirrorPageType, MIRROR_ENTRY_ID } from './utils.js' -import { EnhanceableSite, PostIdentifier } from '@masknet/shared-base' -import { getAuthorWallet } from '../utils/user.js' - -const MIRROR_LINK_PREFIX = /https(.*)mirror.xyz(.*)\//i - -function queryInjectPoint(node: HTMLElement) { - const authorWallet = getAuthorWallet() - const isENS = authorWallet.endsWith('.eth') - const id = isENS ? authorWallet.slice(0, -4) : authorWallet - const allANode = node.querySelectorAll( - [ - // post detail header - isENS ? - `:scope [href^="https://${id}.mirror.xyz" i]:has(img[alt^="0x" i][decoding="async"]) > div:last-of-type` // img alt is always address - : `:scope [href$="/${id}" i]:has(img[alt^="0x" i][decoding="async"]) > div:last-of-type`, // img alt is always address - // collection page card footer - ':scope header div:has(> span img[alt="Publisher"])', - ].join(','), - ) - return allANode.item(allANode.length - 1) as HTMLElement -} - -function getPostId(node: HTMLElement | HTMLLinkElement) { - // Handle entry detail page post id - if (getMirrorPageType(location.href) === MirrorPageType.Post) { - return location.pathname.match(MIRROR_ENTRY_ID)?.[0] - } - - const ele = node.querySelector('div > a') - const href = ele?.href || (node as HTMLLinkElement)?.href - - if (href?.startsWith('https')) { - return href.replace(MIRROR_LINK_PREFIX, '') - } - - if (href) return href?.replace('/', '') - - return '' -} - -async function collectPostInfo(node: HTMLElement | null, cancel: AbortSignal) { - if (!node) return - if (cancel?.aborted) return - const postId = getPostId(node) - if (!postId) return - const publisher = await Mirror.getPostPublisher(postId) - if (!publisher) return - return { - postId, - writers: { - author: formatWriter(publisher.author, false), - coAuthors: publisher?.coAuthors.map((x) => formatWriter(x, false)), - }, - } -} - -async function registerPostCollectorInner( - postStore: SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'], - cancel: AbortSignal, -) { - startWatch( - new MutationObserverWatcher(postsContentSelector()).useForeach((node, key, proxy) => { - if (!node) return - - const actionsElementProxy = DOMProxy({}) - actionsElementProxy.realCurrent = queryInjectPoint(node) - - const refs = createRefsForCreatePostContext() - const postInfo = mirrorShared.utils.createPostContext({ - site: EnhanceableSite.Mirror, - actionsElement: actionsElementProxy, - comments: undefined, - rootElement: proxy, - suggestedInjectionPoint: (node.lastElementChild as HTMLElement) || node, - ...refs.subscriptions, - }) - - function run() { - collectPostInfo(node, cancel).then((result) => { - if (!result) return - - refs.postID.value = result.postId - refs.postBy.value = result.writers?.author.identifier || null - refs.nickname.value = result.writers?.author.nickname || null - refs.avatarURL.value = result.writers?.author.avatar || null - refs.postCoAuthors.value = - result?.writers?.coAuthors - .map( - (x): PostContextCoAuthor => - x.identifier ? - { - author: x.identifier, - avatarURL: x.avatar ? new URL(x.avatar) : undefined, - post: new PostIdentifier(x.identifier, result.postId), - nickname: x.nickname, - } - : undefined!, - ) - .filter(Boolean) || [] - }) - } - run() - - postStore.set(proxy, postInfo) - - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => postStore.delete(proxy), - } - }), - cancel, - ) -} - -export const PostProviderMirror: SiteAdaptorUI.CollectingCapabilities.PostsProvider = { - posts: creator.EmptyPostProviderState(), - start(cancel) { - registerPostCollectorInner(this.posts, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/theme.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/theme.ts deleted file mode 100644 index 9b113f951167..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/theme.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { ThemeMode } from '@masknet/web3-shared-base' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { themeSelector } from '../utils/selectors.js' - -function resolveThemeSettingsInner( - ref: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'], - cancel: AbortSignal, -) { - function updateThemeColor() { - ref.value = { - ...ref.value, - mode: (document.documentElement.dataset.theme as ThemeMode) ?? ThemeMode.Light, - } - } - - updateThemeColor() - - new MutationObserverWatcher(themeSelector()) - .addListener('onAdd', updateThemeColor) - .addListener('onChange', updateThemeColor) - .startWatch({ childList: true, subtree: true }, cancel) -} - -export const ThemeSettingsProviderMirror: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider = { - recognized: creator.EmptyThemeSettingsProviderState(), - async start(cancel) { - resolveThemeSettingsInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/utils.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/utils.ts deleted file mode 100644 index 8ac74576a543..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/collecting/utils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { last } from 'lodash-es' -import urlcat from 'urlcat' -import { ProfileIdentifier } from '@masknet/shared-base' -import type { Writer } from '@masknet/web3-providers/types' -import { formatEthereumAddress } from '@masknet/web3-shared-evm' -import { mirrorBase } from '../base.js' - -export function getMirrorProfileUrl(id: string) { - return urlcat('https://mirror.xyz/:id', { id }) -} - -export function formatWriter(writer: Writer, isOwner: boolean) { - return { - avatar: writer.avatarURL, - nickname: writer.displayName, - bio: writer.description, - homepage: writer.domain || getMirrorProfileUrl(writer.address), - identifier: ProfileIdentifier.of(mirrorBase.networkIdentifier, formatEthereumAddress(writer.address)).unwrapOr( - undefined, - ), - isOwner, - } -} - -export enum MirrorPageType { - Profile = 'profile', - Collection = 'collection', - Post = 'post', - Dashboard = 'dashboard', -} - -export const MIRROR_ENTRY_ID = /[\w|-]{43}/i - -export function getMirrorPageType(url?: string) { - if (!url) return - - if (url.includes(`/${MirrorPageType.Dashboard}`)) return MirrorPageType.Dashboard - if (url.includes(`/${MirrorPageType.Collection}`)) return MirrorPageType.Collection - - const urlSplits = url.split('/').filter(Boolean) - - if (MIRROR_ENTRY_ID.test((urlSplits.at(-1) ?? '').trim())) return MirrorPageType.Post - - return MirrorPageType.Profile -} - -export function getMirrorUserId(href?: string) { - if (!href) return null - - const urlObj = new URL(href) - const url = urlObj.href.replace(urlObj.search, '').replace(/\/$/, '') - - const pageType = getMirrorPageType(url) - - // If dashboard, get from local storage - // This localStorage usage is Okay because it is accessing website's localStorage - // eslint-disable-next-line no-restricted-globals - if (pageType === MirrorPageType.Dashboard) return localStorage.getItem('mirror.userAddress') - - let tempURL = url - if (pageType === MirrorPageType.Collection) { - tempURL = url.replace(/\/collection(.*)/, '') - } - if (pageType === MirrorPageType.Post) { - tempURL = url.replace(/\/[\w|-]{43}/i, '') - } - - const ens = last(tempURL.match(/https:\/\/mirror.xyz\/(.*)/)) - if (ens) return ens - const match = last(tempURL.match(/https:\/\/(.*)\.mirror\.xyz/)) - - return match ? `${match}.eth` : match -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/customization/custom.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/customization/custom.ts deleted file mode 100644 index 01832feb4960..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/customization/custom.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useMemo } from 'react' -import { produce, setAutoFreeze } from 'immer' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' - -export function useThemeMirrorVariant(baseTheme: Theme) { - const themeSettings = useThemeSettings() - - return useMemo(() => { - setAutoFreeze(false) - - const MirrorTheme = produce(baseTheme, (theme) => { - theme.components = theme.components || {} - theme.components.MuiTypography = { - styleOverrides: { - root: { - fontFamily: - '"Inter var",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"', - }, - }, - } - }) - setAutoFreeze(true) - return unstable_createMuiStrictModeTheme(MirrorTheme) - }, [baseTheme, themeSettings]) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/customization/ui-overwrite.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/customization/ui-overwrite.ts deleted file mode 100644 index 40638a06c052..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/customization/ui-overwrite.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable tss-unused-classes/unused-classes */ -import { makeStyles } from '@masknet/theme' - -export const useInjectedDialogClassesOverwriteMirror = makeStyles()((theme) => { - const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` - return { - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - [smallQuery]: { - display: 'block !important', - }, - }, - container: { - alignItems: 'center', - }, - paper: { - width: '600px !important', - minHeight: 400, - maxHeight: 620, - maxWidth: 'none', - boxShadow: 'none', - backgroundImage: 'none', - [smallQuery]: { - display: 'block !important', - margin: 12, - }, - scrollbarWidth: 'none', - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - dialogTitle: { - display: 'grid', - gridTemplateColumns: '1fr auto 1fr', - alignItems: 'center', - padding: 16, - position: 'relative', - background: theme.palette.maskColor.modalTitleBg, - borderBottom: 'none', - '& > p': { - fontSize: 18, - lineHeight: '22px', - display: 'inline-block', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - [smallQuery]: { - display: 'flex', - justifyContent: 'start', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px 11px !important', - }, - }, - dialogContent: { - backgroundColor: theme.palette.maskColor.bottom, - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px', - }, - }, - dialogActions: { - backgroundColor: theme.palette.maskColor.bottom, - padding: '6px 16px', - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px !important', - }, - }, - dialogBackdropRoot: { - backgroundColor: theme.palette.action.mask, - }, - } -}) diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/index.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/index.ts deleted file mode 100644 index efabbf6d0137..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { mirrorBase } from './base.js' - -defineSiteAdaptorUI({ - ...mirrorBase, - load: () => import('./ui-provider.js'), - hotModuleReload(callback) { - if (import.meta.webpackHot) { - import.meta.webpackHot.accept('./ui-provider.ts', async () => { - callback((await import('./ui-provider.js')).default) - }) - } - }, -}) diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/PostActions/index.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/PostActions/index.tsx deleted file mode 100644 index 0ffd2b773b42..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/PostActions/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { noop } from 'lodash-es' -import { Plugin } from '@masknet/plugin-infra' -import { - createInjectHooksRenderer, - type PostInfo, - PostInfoProvider, - useActivatedPluginsSiteAdaptor, - usePostInfoDetails, -} from '@masknet/plugin-infra/content-script' -import { EVMWeb3ContextProvider, useWeb3Utils } from '@masknet/web3-hooks-base' -import { NetworkPluginID } from '@masknet/shared-base' -import { Flags } from '@masknet/flags' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' - -const ActionsRenderer = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, -) - -function PostActions() { - const Utils = useWeb3Utils() - - const identifier = usePostInfoDetails.author() - const nickname = usePostInfoDetails.nickname() as string | null - const coAuthors = usePostInfoDetails.coAuthors() - - if (!identifier) return null - return ( - ({ - pluginID: NetworkPluginID.PLUGIN_EVM, - address: x.author.userId, - label: x.nickname ? `(${x.nickname}) ${Utils.formatAddress(x.author.userId, 4)}` : x.author.userId, - })) ?? []), - ]} - identity={identifier} - slot={Plugin.SiteAdaptor.TipsSlot.MirrorEntry} - /> - ) -} - -function createPostActionsInjector() { - return function injectPostActions(postInfo: PostInfo, signal: AbortSignal) { - const jsx = ( - - - - - - ) - if (postInfo.actionsElement) { - const root = attachReactTreeWithContainer(postInfo.actionsElement.afterShadow, { - key: 'post-actions', - signal, - }) - - root.render(jsx) - return root.destroy - } - return noop - } -} - -export function injectPostActionsAtMirror(signal: AbortSignal, postInfo: PostInfo) { - if (!Flags.post_actions_enabled) return - const injector = createPostActionsInjector() - return injector(postInfo, signal) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/MenuAuthorTipButton.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/MenuAuthorTipButton.tsx deleted file mode 100644 index 1baa92e1a17c..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/MenuAuthorTipButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Plugin } from '@masknet/plugin-infra' -import { EVMWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { querySelector } from '../../utils/selectors.js' -import { TipsButtonWrapper } from './TipsButtonWrapper.js' - -function selector() { - return querySelector( - [ - 'div:has(> div > button[data-state="closed"]) a', // More reliable - '.GlobalNavigation a[href="/"]', - 'div[style$="height: 56px;"] a', - ].join(','), - ) -} - -export function injectOnMenu(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - - - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/PostVerification.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/PostVerification.tsx deleted file mode 100644 index bbd709a8d88a..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/PostVerification.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Plugin } from '@masknet/plugin-infra' -import { EVMWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { Mirror } from '@masknet/web3-providers' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { querySelector } from '../../utils/selectors.js' -import { getAuthorWallet } from '../../utils/user.js' -import { TipsButtonWrapper } from './TipsButtonWrapper.js' - -async function selector() { - let authorWallet = getAuthorWallet() - if (authorWallet.endsWith('.eth')) { - const digest = location.pathname.split('/').pop() - const publisher = await Mirror.getPostPublisher(digest!) - authorWallet = publisher?.author.address || authorWallet - } - return querySelector(`#__next a[href$="/address/${authorWallet}" i] div:nth-child(2)`) -} - -export async function injectOnVerification(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(await selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - - - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/ProfilePage.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/ProfilePage.tsx deleted file mode 100644 index 70c37fee1976..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/ProfilePage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Plugin } from '@masknet/plugin-infra' -import { EVMWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { querySelector } from '../../utils/selectors.js' -import { TipsButtonWrapper } from './TipsButtonWrapper.js' -import { getAuthorWallet } from '../../utils/user.js' - -function selector() { - const authorWallet = getAuthorWallet() - // Only the address link - return querySelector( - [ - `#__next div:has([alt="avatar"]) ~ div:has(h2) ~ div a[href$="/address/${authorWallet}" i]`, // address - `#__next div:has([alt="avatar"]) ~ div:has(h2) ~ div a[href$="search=${authorWallet}" i]`, // ENS - ].join(','), - ) -} - -export function injectTipsButtonOnProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - - - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx deleted file mode 100644 index 8728b1395466..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { EMPTY_LIST, PluginID, type SocialAccount } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useNetworkContext, useWeb3Utils } from '@masknet/web3-hooks-base' -import { - createInjectHooksRenderer, - Plugin, - useActivatedPluginsSiteAdaptor, - useIsMinimalMode, -} from '@masknet/plugin-infra/content-script' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - position: 'relative', - marginLeft: theme.spacing(1), - height: 40, - width: 40, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - borderStyle: 'solid', - borderColor: theme.palette.maskColor.line, - borderRadius: 20, - marginRight: theme.spacing(1), - color: theme.palette.text.primary, - }, -})) - -interface Props { - slot: Plugin.SiteAdaptor.TipsSlot -} - -export const TipsButtonWrapper = memo(function TipsButtonWrapper({ slot }: Props) { - const { classes } = useStyles() - - const visitingIdentity = useCurrentVisitingIdentity() - const isMinimalMode = useIsMinimalMode(PluginID.Tips) - const { pluginID } = useNetworkContext() - const Utils = useWeb3Utils() - const [disabled, setDisabled] = useState(false) - - const accounts = useMemo((): Array> => { - if (!visitingIdentity?.identifier) return EMPTY_LIST - return [ - { - pluginID, - address: visitingIdentity.identifier.userId, - label: - visitingIdentity.nickname ? - `(${visitingIdentity.nickname}) ${Utils.formatAddress(visitingIdentity.identifier.userId, 4)}` - : visitingIdentity.identifier.userId, - }, - ] - }, [visitingIdentity, Utils.formatAddress]) - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, - ) - - return ( - - ) - }, [visitingIdentity.identifier, accounts, slot]) - - if (disabled || !component || !visitingIdentity.identifier || isMinimalMode || location.pathname === '/') - return null - - return ( - {component} - ) -}) diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts deleted file mode 100644 index 23c037d8db53..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getAuthorWallet } from '../../utils/user.js' - -function selector() { - const authorWallet = getAuthorWallet() - return `#__next div:has(> div > a[href$="mirror.xyz/${authorWallet}" i] button[data-state] img[alt^="0x" i]) + div` -} - -export function adjustArticleInfoBar(signal: AbortSignal) { - const node = document.querySelector(selector()) - if (!node) return - const timer = setInterval(() => { - if (node.offsetWidth !== node.parentElement?.offsetWidth) return - node.style.justifyContent = 'flex-start' - clearInterval(timer) - }, 250) - signal.addEventListener('abort', () => clearInterval(timer)) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/index.tsx b/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/index.tsx deleted file mode 100644 index 5c6a914724ea..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/injection/Tips/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { injectOnMenu } from './MenuAuthorTipButton.js' -import { injectOnVerification } from './PostVerification.js' -import { injectTipsButtonOnProfile as injectOnProfile } from './ProfilePage.js' -import { adjustArticleInfoBar } from './adjustArticleInfoBar.js' - -export function injectTips(signal: AbortSignal) { - injectOnMenu(signal) - injectOnProfile(signal) - injectOnVerification(signal) - adjustArticleInfoBar(signal) -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/shared.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/shared.ts deleted file mode 100644 index 37a6220a1618..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/shared.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { SiteAdaptor } from '@masknet/types' -import { createSiteAdaptorSpecializedPostContext } from '../../site-adaptor-infra/utils/create-post-context.js' -import { hasPayloadLike } from '../../utils/index.js' -import { mirrorBase } from './base.js' -import { getUserIdentity } from './utils/user.js' - -function getProfileURL() { - return new URL('https://mirror.xyz/dashboard') -} -function getShareURL(text: string) { - return new URL('https://mirror.xyz') -} - -export const mirrorShared: SiteAdaptor.Shared & SiteAdaptor.Base = { - ...mirrorBase, - utils: { - getProfileURL, - getShareURL, - createPostContext: createSiteAdaptorSpecializedPostContext(mirrorBase.networkIdentifier, { - hasPayloadLike, - }), - getUserIdentity, - }, -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/tests/collection-utils.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/tests/collection-utils.ts deleted file mode 100644 index a8e07a24580b..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/tests/collection-utils.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { MirrorPageType, getMirrorPageType, getMirrorUserId } from '../collecting/utils.js' - -describe('test mirror collection utils', () => { - test.each([ - { give: 'https://test_ens.mirror.xyz', expected: MirrorPageType.Profile }, - { give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a', expected: MirrorPageType.Profile }, - { give: 'https://mirror.xyz/test.eth', expected: MirrorPageType.Profile }, - { give: 'https://test_ens.mirror.xyz/collection/', expected: MirrorPageType.Collection }, - { give: 'https://test_ens.mirror.xyz/collection', expected: MirrorPageType.Collection }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/collection', - expected: MirrorPageType.Collection, - }, - { give: 'https://mirror.xyz/test.eth/collection', expected: MirrorPageType.Collection }, - { - give: 'https://test_ens.mirror.xyz/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: MirrorPageType.Post, - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: MirrorPageType.Post, - }, - { - give: 'https://mirror.xyz/test.eth/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: MirrorPageType.Post, - }, - { - give: 'https://mirror.xyz/dashboard/', - expected: MirrorPageType.Dashboard, - }, - ])('.getMirrorPageType($give)', ({ give, expected }) => { - expect(getMirrorPageType(give)).toBe(expected) - }) -}) - -describe('should get mirror id', () => { - test.each([ - // mirror ens - { give: 'https://test_ens.mirror.xyz', expected: 'test_ens.eth' }, - { give: 'https://test_ens.mirror.xyz/', expected: 'test_ens.eth' }, - { give: 'https://test_ens.mirror.xyz?p=test', expected: 'test_ens.eth' }, - { give: 'https://test_ens.mirror.xyz/collection', expected: 'test_ens.eth' }, - { give: 'https://test_ens.mirror.xyz/collection?p=test', expected: 'test_ens.eth' }, - { - give: 'https://test_ens.mirror.xyz/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: 'test_ens.eth', - }, - { - give: 'https://test_ens.mirror.xyz/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro?p=test', - expected: 'test_ens.eth', - }, - // user ens - { give: 'https://mirror.xyz/test.eth', expected: 'test.eth' }, - { give: 'https://mirror.xyz/test.eth/', expected: 'test.eth' }, - { give: 'https://mirror.xyz/test.eth?p=test', expected: 'test.eth' }, - { give: 'https://mirror.xyz/test.eth/collection', expected: 'test.eth' }, - { give: 'https://mirror.xyz/test.eth/collection?p=test', expected: 'test.eth' }, - { - give: 'https://mirror.xyz/test.eth/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: 'test.eth', - }, - { - give: 'https://mirror.xyz/test.eth/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro?p=test', - expected: 'test.eth', - }, - - // address - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a?p=test', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/collection', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/collection?p=test', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - { - give: 'https://mirror.xyz/0x790116d0685eB197B886DAcAD9C247f785987A4a/aCCyRXugL4y4UxvqXgcMcDwh6TiChEFTZ_BHtY1cbro?p=test', - expected: '0x790116d0685eB197B886DAcAD9C247f785987A4a', - }, - ])('.getMirrorUserId($give)', ({ give, expected }) => { - expect(getMirrorUserId(give)).toBe(expected) - }) -}) diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/ui-provider.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/ui-provider.ts deleted file mode 100644 index e30877ae4159..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/ui-provider.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { stateCreator } from '../../site-adaptor-infra/utils.js' -import { injectPageInspectorDefault } from '../../site-adaptor-infra/defaults/index.js' -import { InitAutonomousStateProfiles } from '../../site-adaptor-infra/defaults/state/InitProfiles.js' - -import { mirrorBase } from './base.js' -import { mirrorShared } from './shared.js' -import { CurrentVisitingIdentityProviderMirror, IdentityProviderMirror } from './collecting/identity.js' -import { injectTips } from './injection/Tips/index.js' -import { useInjectedDialogClassesOverwriteMirror } from './customization/ui-overwrite.js' -import { injectPostActionsAtMirror } from './injection/PostActions/index.js' -import { PostProviderMirror } from './collecting/posts.js' -import { ThemeSettingsProviderMirror } from './collecting/theme.js' -import { useThemeMirrorVariant } from './customization/custom.js' - -// TODO: access chrome permission -const define: SiteAdaptorUI.Definition = { - ...mirrorBase, - ...mirrorShared, - automation: {}, - collecting: { - identityProvider: IdentityProviderMirror, - currentVisitingIdentityProvider: CurrentVisitingIdentityProviderMirror, - postsProvider: PostProviderMirror, - themeSettingsProvider: ThemeSettingsProviderMirror, - }, - configuration: { - tipsConfig: { - enableUserGuide: true, - }, - }, - customization: { - sharedComponentOverwrite: { - InjectedDialog: { - classes: useInjectedDialogClassesOverwriteMirror, - }, - }, - useTheme: useThemeMirrorVariant, - }, - init(signal) { - const profiles = stateCreator.profiles() - InitAutonomousStateProfiles(signal, profiles, mirrorShared.networkIdentifier) - return { profiles } - }, - injection: { - pageInspector: injectPageInspectorDefault(), - postActions: injectPostActionsAtMirror, - tips: injectTips, - }, -} -export default define diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/utils/selectors.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/utils/selectors.ts deleted file mode 100644 index 0a889c604185..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/utils/selectors.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' - -type E = HTMLElement - -export function querySelector(selector: string, singleMode = true) { - const ls = new LiveSelector().querySelector(selector) - return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector -} - -function querySelectorAll(selector: string) { - return new LiveSelector().querySelectorAll(selector) -} - -export function postsContentSelector() { - return querySelectorAll( - [ - // In Entries - '[id="__next"] > div:nth-child(2) > div > div:not([class]) > div:not(footer)', - // In collection - '[id="__next"] > div:nth-child(2) a:has(footer)', - '[id="__next"] > div:nth-child(2) a:has(img[alt="Card Header"][loading="lazy"])', - // In Entry detail - '[id="__next"] > div:nth-child(2) > div:has([class]):not(footer):has(p)', - ].join(','), - ).filter((x) => x.childNodes.length !== 0) -} - -export function themeSelector() { - return querySelector('[data-theme]') -} diff --git a/packages/mask/content-script/site-adaptors/mirror.xyz/utils/user.ts b/packages/mask/content-script/site-adaptors/mirror.xyz/utils/user.ts deleted file mode 100644 index 85b5f016d394..000000000000 --- a/packages/mask/content-script/site-adaptors/mirror.xyz/utils/user.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Mirror } from '@masknet/web3-providers' -import { ProfileIdentifier, type SocialIdentity } from '@masknet/shared-base' -import { mirrorBase } from '../base.js' -import { getMirrorProfileUrl } from '../collecting/utils.js' - -export async function getUserIdentity(userAddress: string): Promise { - if (!userAddress) return - const writer = await Mirror.getWriter(userAddress) - if (!writer) return - - return { - avatar: writer.avatarURL, - nickname: writer.displayName, - identifier: ProfileIdentifier.of(mirrorBase.networkIdentifier, writer.address).unwrapOr(undefined), - bio: writer.description, - homepage: writer.domain || getMirrorProfileUrl(writer.address), - } -} - -/** - * Could be ENS or address - */ -export function getAuthorWallet() { - let authorWallet = location.pathname.split('/')[1].toLowerCase() - const matches = location.hostname.match(/(.*)\.mirror\.xyz$/) - authorWallet = matches ? `${matches[1]}.eth` : authorWallet - return authorWallet -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoNewsFeedPage.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoNewsFeedPage.ts deleted file mode 100644 index b5e0c014cdb4..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoNewsFeedPage.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function gotoNewsFeedPageTwitter() { - if (location.pathname.includes('/home')) location.reload() - else location.assign('/home') -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoProfilePage.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoProfilePage.ts deleted file mode 100644 index bb105a7a2ed7..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/gotoProfilePage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ProfileIdentifier } from '@masknet/shared-base' - -export function gotoProfilePageTwitter(profile: ProfileIdentifier) { - const path = `/${profile.userId}` - ;(document.querySelector(`[href="${path}"]`) as HTMLElement | undefined)?.click() - setTimeout(() => { - // The classic way - if (!location.pathname.startsWith(path)) location.assign(path) - }, 400) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/openComposeBox.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/openComposeBox.ts deleted file mode 100644 index 0b2499ed5e43..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/openComposeBox.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CrossIsolationMessages, type CompositionDialogEvent } from '@masknet/shared-base' -import { makeTypedMessageText, type SerializableTypedMessages } from '@masknet/typed-message' - -export function openComposeBoxTwitter( - content: string | SerializableTypedMessages, - options?: CompositionDialogEvent['options'], -) { - CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ - reason: 'timeline', - open: true, - content: typeof content === 'string' ? makeTypedMessageText(content) : content, - options, - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteImageToComposition.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteImageToComposition.ts deleted file mode 100644 index 8fda929e59cd..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteImageToComposition.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { delay } from '@masknet/kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { hasEditor, hasFocus, isCompose } from '../utils/postBox.js' -import { newPostButtonSelector, postEditorDraftContentSelector } from '../utils/selector.js' -import { untilElementAvailable } from '../../../utils/untilElementAvailable.js' -import { pasteImageToCompositionDefault } from '../../../site-adaptor-infra/defaults/automation/AttachImageToComposition.js' - -export async function pasteImageToCompositionTwitter( - url: string | Blob, - options: SiteAdaptorUI.AutomationCapabilities.NativeCompositionAttachImageOptions, -) { - const interval = 500 - - if (!isCompose() && !hasEditor() && options?.reason !== 'reply') { - // open tweet window - await untilElementAvailable(newPostButtonSelector()) - await delay(interval) - newPostButtonSelector().evaluate()!.click() - } - - // get focus - const i = postEditorDraftContentSelector() - await untilElementAvailable(i) - - while (!hasFocus(i)) { - i.evaluate()!.click() - await delay(interval) - } - - pasteImageToCompositionDefault(() => false)(url, options) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteTextToComposition.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteTextToComposition.ts deleted file mode 100644 index 5326f4053af8..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/pasteTextToComposition.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { delay } from '@masknet/kit' -import { pasteText } from '@masknet/injected-script' -import type { SiteAdaptorUI } from '@masknet/types' -import { MaskMessages } from '@masknet/shared-base' -import { newPostButtonSelector, postEditorDraftContentSelector } from '../utils/selector.js' -import { getEditorContent, hasEditor, hasFocus, isCompose } from '../utils/postBox.js' -import { untilElementAvailable } from '../../../utils/untilElementAvailable.js' -import { selectElementContents } from '../../../utils/selectElementContents.js' - -/** - * Wait for up to 5000 ms - * If not complete, let user do it. - */ -export const pasteTextToCompositionTwitter: SiteAdaptorUI.AutomationCapabilities.NativeCompositionDialog['attachText'] = - (text, opt) => { - const interval = 500 - const timeout = 5000 - const worker = async function (abort: AbortSignal) { - const checkSignal = () => { - if (abort.aborted) throw new Error('Abort to paste text to the composition dialog at twitter.') - } - - if ( - (!isCompose() && opt?.reason === 'verify') || - (!isCompose() && !hasEditor() && opt?.reason !== 'reply') - ) { - // open tweet window - await untilElementAvailable(newPostButtonSelector()) - await delay(interval) - newPostButtonSelector().evaluate()!.click() - checkSignal() - } - - // get focus - const i = postEditorDraftContentSelector() - await untilElementAvailable(i) - await delay(interval) - checkSignal() - - if (opt?.reason === 'verify') { - selectElementContents(i.evaluate()!) - } - - while (!hasFocus(i)) { - i.evaluate()!.click() - checkSignal() - await delay(interval) - } - - // paste - pasteText(text) - await delay(interval) - - if (!getEditorContent().replaceAll('\n', '').includes(text.replaceAll('\n', ''))) { - fail(new Error('Unable to paste text automatically')) - } - } - - const fail = (e: Error) => { - if (opt?.recover) MaskMessages.events.autoPasteFailed.sendToLocal({ text }) - throw e - } - - return worker(AbortSignal.timeout(timeout)).then(undefined, (error) => fail(error)) - } diff --git a/packages/mask/content-script/site-adaptors/twitter.com/automation/publishPost.ts b/packages/mask/content-script/site-adaptors/twitter.com/automation/publishPost.ts deleted file mode 100644 index b731fe5aafaa..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/automation/publishPost.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { Twitter } from '@masknet/web3-providers' -import type { TwitterBaseAPI } from '@masknet/web3-providers/types' - -export async function publishPostTwitter( - mediaObjects: Array, - options?: SiteAdaptorUI.AutomationCapabilities.NativeCompositionAttachImageOptions, -) { - const images = mediaObjects.filter((x) => typeof x !== 'string') as Blob[] - const allSettled = await Promise.allSettled(images.map((x) => Twitter.uploadMedia(x))) - const mediaIds = allSettled - .map((x) => x.status === 'fulfilled' && x.value?.media_id_string) - .filter(Boolean) as string[] - - const variables: TwitterBaseAPI.Tweet = { - tweet_text: mediaObjects.filter((x) => typeof x === 'string').join('\n'), - } - - if (mediaIds.length > 0) - variables.media = { - media_entities: mediaIds.map((x) => ({ media_id: x, tagged_users: [] })), - possibly_sensitive: false, - } - - if (options?.reason === 'reply') { - const replyTweetId = location.href.match(/\/status\/(\d+)/)?.[1] - - if (replyTweetId) { - variables.reply = { - in_reply_to_tweet_id: replyTweetId, - exclude_reply_user_ids: [], - } - } - } - - const postId = await Twitter.createTweet(variables) - return postId -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/base.ts b/packages/mask/content-script/site-adaptors/twitter.com/base.ts deleted file mode 100644 index 94698e91cbce..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/base.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EncryptPayloadNetwork } from '@masknet/encryption' -import type { SiteAdaptor } from '@masknet/types' -import { EnhanceableSite } from '@masknet/shared-base' - -const origins = ['https://mobile.twitter.com/*', 'https://twitter.com/*'] -export const twitterBase: SiteAdaptor.Base = { - networkIdentifier: EnhanceableSite.Twitter, - encryptPayloadNetwork: EncryptPayloadNetwork.Twitter, - declarativePermissions: { origins }, - shouldActivate(location) { - const { hostname, pathname } = location - return hostname.endsWith('twitter.com') && !pathname.startsWith('/i/cards-frame/') - }, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/collecting/getSearchedKeyword.ts b/packages/mask/content-script/site-adaptors/twitter.com/collecting/getSearchedKeyword.ts deleted file mode 100644 index 6f6d5ce450d0..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/collecting/getSearchedKeyword.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Listing all possible pathnames start from /search that the search box will keep existing on twitter. - * That means the keyword will not be cleaned and related components keep injecting. - * Otherwise, if a pathname not in this list the keyword will be cleaned and remove relative components from DOM. - */ -const SAFE_PATHNAMES_ON_TWITTER = [ - // redirect to /compose/post - '/compose/tweet', - '/compose/post', - '/search-advanced', - '/settings/trends', - '/settings/search', - '/i/display', - '/account/switch', - '/i/keyboard_shortcuts', -] - -export default function getSearchedKeywordAtTwitter(): string { - const params = new URLSearchParams(location.search) - const hashTagMatched = location.pathname.match(/\/hashtag\/([\dA-Za-z]+)/) - const isTabAvailable = ['top'].includes(params.get('f') ?? '') - if (location.pathname === '/search' && (!params.get('f') || isTabAvailable)) return params.get('q') ?? '' - else if (hashTagMatched) return '#' + hashTagMatched[1] - else if (!SAFE_PATHNAMES_ON_TWITTER.includes(location.pathname)) return '' - - return '' -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/collecting/identity.ts b/packages/mask/content-script/site-adaptors/twitter.com/collecting/identity.ts deleted file mode 100644 index a85db7092677..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/collecting/identity.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { first } from 'lodash-es' -import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' -import { TWITTER_RESERVED_SLUGS } from '@masknet/injected-script/shared' -import { delay } from '@masknet/kit' -import { ProfileIdentifier } from '@masknet/shared-base' -import { queryClient } from '@masknet/shared-base-ui' -import type { SiteAdaptorUI } from '@masknet/types' -import { Twitter } from '@masknet/web3-providers' -import { creator } from '../../../site-adaptor-infra/index.js' -import { twitterBase } from '../base.js' -import { - searchSelfAvatarSelector, - searchSelfHandleSelector, - searchSelfNicknameSelector, - searchWatcherAvatarSelector, - selfInfoSelectors, -} from '../utils/selector.js' - -function collectSelfInfo() { - const handle = selfInfoSelectors().handle.evaluate() - const nickname = selfInfoSelectors().name.evaluate() - const avatar = selfInfoSelectors().userAvatar.evaluate() - - return { handle, nickname, avatar } -} - -function getNickname(nickname?: string) { - const nicknameNode = searchSelfNicknameSelector().closest(1).evaluate() - let _nickname = '' - if (!nicknameNode?.childNodes.length) return nickname - - for (const child of nicknameNode.childNodes) { - const ele = child as HTMLDivElement - if (ele.tagName === 'IMG') { - _nickname += ele.getAttribute('alt') ?? '' - } - if (ele.tagName === 'SPAN') { - _nickname += ele.textContent?.trim() - } - } - - return _nickname ?? nickname -} - -function resolveLastRecognizedIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - signal: AbortSignal, -) { - const assign = async () => { - await delay(2000) - - const selfInfo = collectSelfInfo() - const avatar = (searchSelfAvatarSelector().evaluate()?.getAttribute('src') || selfInfo.avatar) ?? '' - const handle = - searchSelfHandleSelector().evaluate()?.dataset.testid?.trim().slice('UserAvatar-Container-'.length) || - selfInfo.handle - const nickname = getNickname(selfInfo.nickname) ?? '' - - if (handle) { - ref.value = { - avatar, - nickname, - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(undefined), - isOwner: true, - } - } - } - - const createWatcher = (selector: LiveSelector) => { - new MutationObserverWatcher(selector) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['src'], - }, - signal, - ) - } - - assign() - - window.addEventListener('locationchange', assign, { signal }) - createWatcher(searchSelfHandleSelector()) - createWatcher(searchWatcherAvatarSelector()) -} - -function resolveLastRecognizedIdentityMobileInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const onLocationChange = async () => { - const settings = await Twitter.getSettings() - const identifier = ProfileIdentifier.of(twitterBase.networkIdentifier, settings?.screen_name).unwrapOr( - undefined, - ) - - if (identifier && !ref.value.identifier) { - ref.value = { - ...ref.value, - identifier, - isOwner: true, - } - } - } - - onLocationChange() - window.addEventListener('locationchange', onLocationChange, { signal: cancel }) -} - -function getFirstSlug() { - const slugs: string[] = location.pathname.split('/').filter(Boolean) - return first(slugs) -} - -function resolveCurrentVisitingIdentityInner( - ref: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - ownerRef: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider['recognized'], - cancel: AbortSignal, -) { - const update = async (twitterId: string) => { - const user = await queryClient.fetchQuery({ - queryKey: ['twitter', 'profile', twitterId], - queryFn: () => Twitter.getUserByScreenName(twitterId), - }) - if (process.env.NODE_ENV === 'development') { - console.assert(user, `Can't get get user by screen name ${twitterId}`) - } - if (!user) return - - const handle = user.screenName - const ownerHandle = ownerRef.value.identifier?.userId - const isOwner = !!ownerHandle && handle.toLowerCase() === ownerHandle.toLowerCase() - const avatar = user.avatarURL - const bio = user.bio - const homepage = user.homepage - - ref.value = { - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(undefined), - nickname: user.nickname, - avatar, - bio, - homepage, - isOwner, - } - } - - const slug = getFirstSlug() - if (slug && !TWITTER_RESERVED_SLUGS.includes(slug)) { - update(slug) - if (!ownerRef.value.identifier) { - const unsubscribe = ownerRef.addListener((val) => { - update(slug) - if (val) unsubscribe() - }) - } - } - - window.addEventListener( - 'scenechange', - (event) => { - if (event.detail.scene !== 'profile') return - const twitterId = event.detail.value - update(twitterId) - }, - { signal: cancel }, - ) -} - -export const IdentityProviderTwitter: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveLastRecognizedIdentityInner(this.recognized, cancel) - }, -} - -export const CurrentVisitingIdentityProviderTwitter: SiteAdaptorUI.CollectingCapabilities.IdentityResolveProvider = { - hasDeprecatedPlaceholderName: false, - recognized: creator.EmptyIdentityResolveProviderState(), - start(cancel) { - resolveCurrentVisitingIdentityInner(this.recognized, IdentityProviderTwitter.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts b/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts deleted file mode 100644 index 6679dedd2ce1..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { memoize } from 'lodash-es' -import * as web3_utils from /* webpackDefer: true */ 'web3-utils' -import { IntervalWatcher } from '@dimensiondev/holoflows-kit' -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { - FlattenTypedMessage, - extractTextFromTypedMessage, - makeTypedMessageEmpty, - makeTypedMessageImage, - makeTypedMessagePromise, - makeTypedMessageTuple, - makeTypedMessageTupleFromList, -} from '@masknet/typed-message' -import type { SiteAdaptorUI } from '@masknet/types' -import Services from '#services' -import { creator, activatedSiteAdaptor_state } from '../../../site-adaptor-infra/index.js' -import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' -import { parseId } from '../utils/url.js' -import { untilElementAvailable } from '../../../utils/untilElementAvailable.js' -import { getCurrentIdentifier } from '../../utils.js' -import { twitterBase } from '../base.js' -import { injectMaskIconToPostTwitter } from '../injection/MaskIcon.js' -import { twitterShared } from '../shared.js' -import { getPostId, postContentMessageParser, postImagesParser, postParser } from '../utils/fetch.js' -import { - postsContentSelector, - postsImageSelector, - timelinePostContentSelector, - toastLinkSelector, -} from '../utils/selector.js' -import { IdentityProviderTwitter } from './identity.js' - -function getPostActionsNode(postNode: HTMLElement | null) { - if (!postNode) return null - return postNode.closest('[data-testid="tweet"]')?.querySelector( - // query for the share button button which has a popup menu - '[role="group"]:last-child > div:has([aria-haspopup="menu"]):last-child', - ) -} - -const getParentTweetNode = (node: HTMLElement) => { - return node.closest('[data-testid="tweet"]') -} - -function isQuotedTweet(tweetNode: HTMLElement | null) { - return tweetNode?.getAttribute('role') === 'link' -} - -function isDetailTweet(tweetNode: HTMLElement) { - // We can see the retweets status in detail tweet. - const isDetail = !!tweetNode.querySelector('a[role="link"][href$=retweets],a[role="link"][href$=likes]') - return isDetail -} - -function getTweetNode(node: HTMLElement) { - // retweet(quoted tweet) in new twitter - const root = node.closest('div[role="link"]') - // then normal tweet - return root || node.closest('article > div') -} -function shouldSkipDecrypt(node: HTMLElement, tweetNode: HTMLElement) { - const isCardNode = node.matches('[data-testid="card.wrapper"]') - const hasTextNode = !!tweetNode.querySelector( - [ - '[data-testid="tweet"] div[lang]', - '[data-testid="tweet"] + div div[lang]', // detailed - ].join(','), - ) - - // if a text node already exists, it's not going to decrypt the card node - return isCardNode && hasTextNode -} -function registerPostCollectorInner( - postStore: SiteAdaptorUI.CollectingCapabilities.PostsProvider['posts'], - cancel: AbortSignal, -) { - const updateProfileInfo = memoize( - (info: PostInfo) => { - const currentProfile = getCurrentIdentifier() - const profileIdentifier = info.author.getCurrentValue() - if (!profileIdentifier) return - Services.Identity.updateProfileInfo(profileIdentifier, { - nickname: info.nickname.getCurrentValue(), - avatarURL: info.avatarURL.getCurrentValue()?.toString(), - }) - if (currentProfile?.linkedPersona) { - Services.Identity.createNewRelation(profileIdentifier, currentProfile.linkedPersona) - } - }, - (info: PostInfo) => info.author.getCurrentValue(), - ) - new IntervalWatcher(postsContentSelector()) - .useForeach((node, _, proxy) => { - const tweetNode = getTweetNode(node) - if (!tweetNode || shouldSkipDecrypt(node, tweetNode)) return - const refs = createRefsForCreatePostContext() - const info = twitterShared.utils.createPostContext({ - site: EnhanceableSite.Twitter, - comments: undefined, - rootElement: proxy, - isFocusing: isDetailTweet(tweetNode), - suggestedInjectionPoint: tweetNode, - ...refs.subscriptions, - }) - function run() { - collectPostInfo(tweetNode, refs, cancel) - collectLinks(tweetNode, refs, cancel) - } - run() - cancel.addEventListener( - 'abort', - info.hasMaskPayload.subscribe(() => { - const payload = info.hasMaskPayload.getCurrentValue() - if (!payload && refs.postMetadataImages.size === 0) return - updateProfileInfo(info) - }), - ) - injectMaskIconToPostTwitter(info, cancel) - postStore.set(proxy, info) - return { - onTargetChanged: run, - onRemove: () => { - postStore.delete(proxy) - }, - onNodeMutation: run, - } - }) - .assignKeys((node) => { - const tweetNode = getTweetNode(node) - const parentTweetNode = isQuotedTweet(tweetNode) ? getParentTweetNode(tweetNode!) : null - if (!tweetNode || shouldSkipDecrypt(node, tweetNode)) { - return `keccak256:${web3_utils.keccak256(node.innerText)}` - } - const parentTweetId = parentTweetNode ? getPostId(parentTweetNode) : '' - const tweetId = getPostId(tweetNode) - // To distinguish tweet nodes between timeline and detail page - const isDetailPage = isDetailTweet(tweetNode) - const isCollapsed = !!tweetNode.querySelector('[data-testid="tweet-text-show-more-link"]') - return `${isDetailPage ? 'detail' : 'normal'}/${parentTweetId}/${tweetId}/collapse:${isCollapsed}` - }) - .startWatch(250, cancel) -} - -export const PostProviderTwitter: SiteAdaptorUI.CollectingCapabilities.PostsProvider = { - posts: creator.EmptyPostProviderState(), - start(cancel) { - registerPostCollectorInner(this.posts, cancel) - }, -} - -export function getPostIdFromNewPostToast() { - const toastLinkNode = toastLinkSelector().evaluate() - return toastLinkNode?.href ? parseId(toastLinkNode.href) : '' -} - -export function collectVerificationPost(keyword: string) { - const userId = - IdentityProviderTwitter.recognized.value.identifier || activatedSiteAdaptor_state!.profiles.value[0].identifier - const postNodes = timelinePostContentSelector().evaluate() - - for (const postNode of postNodes) { - const tweetNode = postNode.closest('[data-testid=tweet]') - if (!tweetNode) continue - const postId = getPostId(tweetNode) - const postContent = postContentMessageParser(postNode) - const content = extractTextFromTypedMessage(postContent) - const isVerified = - postId && - content.isSome() && - content.value.toLowerCase().replaceAll(/\r\n|\n|\r/gm, '') === - keyword.toLowerCase().replaceAll(/\r\n|\n|\r/gm, '') - - if (isVerified && userId) { - return new PostIdentifier(userId, postId) - } - } - - return null -} - -function collectPostInfo( - tweetNode: HTMLDivElement | null, - info: ReturnType, - cancel: AbortSignal, -) { - if (!tweetNode) return - if (cancel?.aborted) return - const { pid, messages, handle, name, avatar } = postParser(tweetNode) - - if (!pid) return - const postBy = ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(null) - info.postID.value = pid - info.postBy.value = postBy - info.nickname.value = name - info.avatarURL.value = avatar || null - - // decode steganographic image - // don't add await on this - const images = untilElementAvailable(postsImageSelector(tweetNode), 10000) - .then(() => postImagesParser(tweetNode)) - .then((urls) => { - for (const url of urls) info.postMetadataImages.add(url) - if (urls.length) return makeTypedMessageTupleFromList(...urls.map((x) => makeTypedMessageImage(x))) - return makeTypedMessageEmpty() - }) - .catch(() => makeTypedMessageEmpty()) - - info.postMessage.value = FlattenTypedMessage.NoContext( - makeTypedMessageTuple([messages, makeTypedMessagePromise(images)]), - ) -} - -function collectLinks( - tweetNode: HTMLDivElement | null, - info: ReturnType, - cancel: AbortSignal, -) { - if (!tweetNode) return - if (cancel?.aborted) return - const links = [...tweetNode.querySelectorAll('a')].filter((x) => x.rel) - const seen = new Set(['https://help.twitter.com/using-twitter/how-to-tweet#source-labels']) - for (const x of links) { - if (seen.has(x.href)) continue - seen.add(x.href) - info.postMetadataMentionedLinks.set(x, x.href) - Services.Helper.resolveTCOLink(x.href) - .then((val) => { - if (cancel?.aborted) return - if (!val) return - info.postMetadataMentionedLinks.set(x, val) - }) - .catch(() => {}) - } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/collecting/theme.ts b/packages/mask/content-script/site-adaptors/twitter.com/collecting/theme.ts deleted file mode 100644 index 2378a33f4a5c..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/collecting/theme.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { delay } from '@masknet/kit' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createLookupTableResolver } from '@masknet/shared-base' -import type { SiteAdaptorUI } from '@masknet/types' -import { Twitter } from '@masknet/web3-providers' -import { TwitterBaseAPI } from '@masknet/web3-providers/types' -import { FontSize, ThemeMode } from '@masknet/web3-shared-base' -import { creator } from '../../../site-adaptor-infra/utils.js' -import { composeAnchorSelector } from '../utils/selector.js' - -const resolveThemeColor = createLookupTableResolver( - { - [TwitterBaseAPI.ThemeColor.Blue]: 'rgb(29, 155, 240)', - [TwitterBaseAPI.ThemeColor.Yellow]: 'rgb(255, 212, 0)', - [TwitterBaseAPI.ThemeColor.Purple]: 'rgb(120, 86, 255)', - [TwitterBaseAPI.ThemeColor.Magenta]: 'rgb(249, 24, 128)', - [TwitterBaseAPI.ThemeColor.Orange]: 'rgb(255, 122, 0)', - [TwitterBaseAPI.ThemeColor.Green]: 'rgb(0, 186, 124)', - }, - 'rgb(29, 155, 240)', -) - -const resolveThemeMode = createLookupTableResolver( - { - [TwitterBaseAPI.ThemeMode.Dark]: ThemeMode.Dark, - [TwitterBaseAPI.ThemeMode.Dim]: ThemeMode.Dark, - [TwitterBaseAPI.ThemeMode.Light]: ThemeMode.Light, - }, - ThemeMode.Light, -) - -const resolveFontSize = createLookupTableResolver( - { - [TwitterBaseAPI.Scale.X_Large]: FontSize.X_Large, - [TwitterBaseAPI.Scale.Large]: FontSize.Large, - [TwitterBaseAPI.Scale.Normal]: FontSize.Normal, - [TwitterBaseAPI.Scale.Small]: FontSize.Small, - [TwitterBaseAPI.Scale.X_Small]: FontSize.X_Small, - }, - FontSize.Normal, -) - -async function resolveThemeSettingsInner( - ref: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider['recognized'], - cancel: AbortSignal, -) { - const assign = async () => { - await delay(300) - - const userSettings = await Twitter.getUserSettings() - if (!userSettings) return - - ref.value = { - color: userSettings.themeColor ? resolveThemeColor(userSettings.themeColor) : ref.value.color, - size: userSettings.scale ? resolveFontSize(userSettings.scale) : ref.value.size, - mode: userSettings.themeBackground ? resolveThemeMode(userSettings.themeBackground) : ref.value.mode, - isDim: userSettings.themeBackground === TwitterBaseAPI.ThemeMode.Dim, - } - } - - await assign() - - new MutationObserverWatcher(composeAnchorSelector()) - .addListener('onAdd', () => assign()) - .addListener('onChange', () => assign()) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['src'], - }, - cancel, - ) -} - -export const ThemeSettingsProviderTwitter: SiteAdaptorUI.CollectingCapabilities.ThemeSettingsProvider = { - recognized: creator.EmptyThemeSettingsProviderState(), - async start(cancel) { - await resolveThemeSettingsInner(this.recognized, cancel) - }, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/constant.ts b/packages/mask/content-script/site-adaptors/twitter.com/constant.ts deleted file mode 100644 index 25354f9ffbdf..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/constant.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { FontSize } from '@masknet/web3-shared-base' - -export interface ButtonProps { - buttonSize: number - iconSize: number - fontSize: number - lineHeight: string - marginBottom: number -} - -export const ButtonStyle: Record = { - [FontSize.X_Small]: { buttonSize: 32, iconSize: 18, fontSize: 14, lineHeight: '18px', marginBottom: 11 }, - [FontSize.Small]: { buttonSize: 34, iconSize: 19, fontSize: 14, lineHeight: '19px', marginBottom: 11 }, - [FontSize.Normal]: { buttonSize: 36, iconSize: 20, fontSize: 15, lineHeight: '20px', marginBottom: 12 }, - [FontSize.Large]: { buttonSize: 40, iconSize: 22, fontSize: 17, lineHeight: '22px', marginBottom: 13 }, - [FontSize.X_Large]: { buttonSize: 43, iconSize: 24, fontSize: 18, lineHeight: '24px', marginBottom: 14 }, -} - -interface TipButtonProps { - buttonSize: number - iconSize: number -} - -export const TipButtonStyle: Record = { - [FontSize.X_Small]: { buttonSize: 29, iconSize: 18 }, - [FontSize.Small]: { buttonSize: 30, iconSize: 19 }, - [FontSize.Normal]: { buttonSize: 32, iconSize: 20 }, - [FontSize.Large]: { buttonSize: 35, iconSize: 22 }, - [FontSize.X_Large]: { buttonSize: 38, iconSize: 24 }, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/customization/custom.ts b/packages/mask/content-script/site-adaptors/twitter.com/customization/custom.ts deleted file mode 100644 index a671f87ab4f8..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/customization/custom.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useMemo } from 'react' -import { produce, setAutoFreeze } from 'immer' -import { type Theme, unstable_createMuiStrictModeTheme } from '@mui/material' -import { fromRGB, shade, toRGB } from '@masknet/plugin-infra/content-script' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' - -export function useThemeTwitterVariant(baseTheme: Theme) { - const themeSettings = useThemeSettings() - - return useMemo(() => { - const primaryColorRGB = fromRGB(themeSettings.color)! - const primaryContrastColorRGB = fromRGB('rgb(255, 255, 255)') - setAutoFreeze(false) - - const TwitterTheme = produce(baseTheme, (theme) => { - if (themeSettings.isDim) { - theme.palette.maskColor.bottom = '#15202B' - theme.palette.maskColor.secondaryBottom = 'rgba(21, 32, 43, 0.8)' - } - - theme.palette.primary = { - light: toRGB(shade(primaryColorRGB, 10)), - main: toRGB(primaryColorRGB), - dark: toRGB(shade(primaryColorRGB, -10)), - contrastText: toRGB(primaryContrastColorRGB), - } - theme.shape.borderRadius = 15 - theme.breakpoints.values = { xs: 0, sm: 687, md: 1024, lg: 1280, xl: 1920 } - theme.components = theme.components || {} - - theme.components.MuiTypography = { - styleOverrides: { - root: { - fontFamily: - 'TwitterChirp, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif', - }, - }, - } - theme.components.MuiPaper = { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - background: theme.palette.maskColor.bottom, - }, - }, - } - theme.components.MuiTab = { - styleOverrides: { - root: { - textTransform: 'none', - }, - }, - } - theme.components.MuiBackdrop = { - styleOverrides: { - root: { - backgroundColor: theme.palette.action.mask, - }, - invisible: { - opacity: '0 !important', - }, - }, - } - }) - setAutoFreeze(true) - return unstable_createMuiStrictModeTheme(TwitterTheme) - }, [baseTheme, themeSettings]) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/customization/i18n.ts b/packages/mask/content-script/site-adaptors/twitter.com/customization/i18n.ts deleted file mode 100644 index 03cb2d30a91a..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/customization/i18n.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { SiteAdaptorUI } from '@masknet/types' -import { languages } from '../locales/languages.js' - -export const i18NOverwriteTwitter: SiteAdaptorUI.Customization.I18NOverwrite = { - mask: {}, -} -const resource = languages -for (const language of Object.keys(resource) as Array) { - for (const key of Object.keys(resource[language]) as Array) { - i18NOverwriteTwitter.mask[key] ??= {} - i18NOverwriteTwitter.mask[key][language] = resource[language][key] - } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx b/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx deleted file mode 100644 index 52ffce7ffb33..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { memo } from 'react' -import { Link } from '@mui/material' -import type { RenderFragmentsContextType } from '@masknet/typed-message-react' -import { useTagEnhancer } from '../../../../shared-ui/TypedMessageRender/Components/Text.js' - -export const TwitterRenderFragments: RenderFragmentsContextType = { - AtLink: memo(function (props) { - const target = '/' + props.children.slice(1) - return - }), - HashLink: memo(function (props) { - const text = props.children.slice(1) - const target = `/hashtag/${encodeURIComponent(text)}?src=hashtag_click` - const { hasMatch, ...events } = useTagEnhancer('hash', text) - return ( - - {props.children} - {props.suggestedPostImage} - - ) - }), - CashLink: memo(function (props) { - const target = `/search?q=${encodeURIComponent(props.children)}&src=cashtag_click` - const { hasMatch, ...events } = useTagEnhancer('cash', props.children.slice(1)) - return - }), - Image: () => null, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/customization/twitter-color-schema.json b/packages/mask/content-script/site-adaptors/twitter.com/customization/twitter-color-schema.json deleted file mode 100644 index fa7518483dc2..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/customization/twitter-color-schema.json +++ /dev/null @@ -1,226 +0,0 @@ -{ - "light": { - "grey": { - "700": "#536471", - "300": "#b9cad3", - "200": "#cfd9de", - "50": "#eff3f4" - }, - "text": { - "primary": "#07101B", - "secondary": "#767F8D", - "third": "#ACB4C1", - "strong": "#111418", - "buttonText": "#FFFFFF" - }, - "maskColor": { - "main": "#07101B", - "second": "#767F8D", - "third": "#ACB4C1", - "primaryMain": "#B5B7BB", - "secondaryMain": "#CDCFD1", - "thirdMain": "#F3F3F4", - "bg": "#F9F9F9", - "bottom": "#FFFFFF", - "secondaryBottom": "rgba(255, 255, 255, 0.8)", - "input": "#F2F6FA", - "modalTitleBg": "linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 100%), linear-gradient(90deg, rgba(98, 152, 234, 0.2) 1.03%, rgba(98, 152, 234, 0.2) 1.04%, rgba(98, 126, 234, 0.2) 100%)", - "highlight": "#1C68F3", - "line": "#F2F5F6", - "secondaryLine": "#E6E7E8", - "tips": "rgba(0, 0, 0, 0.9)", - "primary": "#1C68F3", - "success": "#3DC233", - "warn": "#FFB100", - "danger": "#FF3545", - "white": "#ffffff", - "secondaryDark": "#767F8D", - "dark": "#07101B" - }, - "background": { - "default": "#F9F9F9", - "input": "#F2F6FA", - "tipMask": "rgba(0, 0, 0, 0.85)", - "messageShadow": "rgba(101, 119, 134, 0.2)", - "paper": "#ffffff" - }, - "error": { - "main": "#F4212E" - }, - "divider": "#EFF3F4", - "secondaryDivider": "#CFD9DE", - "action": { - "buttonHover": "#272C30", - "bgHover": "#EDEFEF", - "mask": "rgba(0, 0, 0, 0.4)" - } - }, - "light_high_contrast": { - "grey": { - "700": "#536471", - "300": "#b9cad3", - "200": "#cfd9de", - "50": "#eff3f4" - }, - "text": { - "primary": "#07101B", - "secondary": "#767F8D", - "third": "#ACB4C1", - "strong": "#111418", - "buttonText": "#FFFFFF" - }, - "maskColor": { - "main": "#07101B", - "second": "#767F8D", - "third": "#ACB4C1", - "primaryMain": "#B5B7BB", - "secondaryMain": "#CDCFD1", - "thirdMain": "#F3F3F4", - "bg": "#F9F9F9", - "bottom": "#FFFFFF", - "secondaryBottom": "rgba(255, 255, 255, 0.8)", - "input": "#F2F6FA", - "modalTitleBg": "linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 100%), linear-gradient(90deg, rgba(98, 152, 234, 0.2) 1.03%, rgba(98, 152, 234, 0.2) 1.04%, rgba(98, 126, 234, 0.2) 100%)", - "highlight": "#1C68F3", - "line": "#F2F5F6", - "secondaryLine": "#E6E7E8", - "tips": "rgba(0, 0, 0, 0.9)", - "primary": "#1C68F3", - "success": "#3DC233", - "warn": "#FFB100", - "danger": "#FF3545", - "white": "#ffffff", - "secondaryDark": "#767F8D", - "dark": "#07101B" - }, - "background": { - "default": "#F9F9F9", - "input": "#F2F6FA", - "tipMask": "rgba(0, 0, 0, 0.85)", - "messageShadow": "rgba(101, 119, 134, 0.2)", - "paper": "#ffffff" - }, - "error": { - "main": "#F4212E" - }, - "divider": "#EFF3F4", - "secondaryDivider": "#CFD9DE", - "action": { - "buttonHover": "#272C30", - "bgHover": "#EDEFEF", - "mask": "rgba(0, 0, 0, 0.4)" - } - }, - "dark": { - "grey": { - "700": "#8899a6", - "300": "#6b7d8c", - "200": "#3d5466", - "50": "#253341" - }, - "maskColor": { - "main": "#F5F5F5", - "second": "#C4C7CD", - "third": "#666C75", - "primaryMain": "#494949", - "secondaryMain": "#181818", - "thirdMain": "#151515", - "bg": "#1C1C1C", - "bottom": "#101010", - "secondaryBottom": "rgba(0, 0, 0, 0.8)", - "input": "#26292C", - "modalTitleBg": "linear-gradient(180deg, #202020 0%, #181818 100%)", - "highlight": "#FFFFFF", - "line": "#2F2F2F", - "secondaryLine": "#6F6F6F", - "tips": "rgba(255, 255, 255, 0.9)", - "primary": "#1C68F3", - "success": "#3DC233", - "warn": "#FFB100", - "danger": "#FF3545", - "white": "#ffffff", - "secondaryDark": "#767F8D", - "dark": "#07101B" - }, - "text": { - "primary": "#F5F5F5", - "secondary": "#C4C7CD", - "third": "#666C75", - "strong": "#FFFFFF", - "buttonText": "#0F1419" - }, - "background": { - "default": "#1C1C1C", - "input": "#26292C", - "tipMask": "rgba(255, 255, 255, 0.85)", - "messageShadow": "rgba(136, 153, 166, 0.2)", - "paper": "#101010" - }, - "error": { - "main": "#FF5555" - }, - "divider": "#38444D", - "secondaryDivider": "#38444D", - "action": { - "buttonHover": "#D7DBDC", - "bgHover": "#1D2933", - "mask": "rgba(91, 112, 131, 0.4)" - } - }, - "dark_high_contrast": { - "grey": { - "700": "#8899a6", - "300": "#6b7d8c", - "200": "#3d5466", - "50": "#253341" - }, - "maskColor": { - "main": "#F5F5F5", - "second": "#C4C7CD", - "third": "#666C75", - "primaryMain": "#494949", - "secondaryMain": "#181818", - "thirdMain": "#151515", - "bg": "#1C1C1C", - "bottom": "#101010", - "secondaryBottom": "rgba(0, 0, 0, 0.8)", - "input": "#26292C", - "modalTitleBg": "linear-gradient(180deg, #202020 0%, #181818 100%)", - "highlight": "#FFFFFF", - "line": "#2F2F2F", - "secondaryLine": "#6F6F6F", - "tips": "rgba(255, 255, 255, 0.9)", - "primary": "#1C68F3", - "success": "#3DC233", - "warn": "#FFB100", - "danger": "#FF3545", - "white": "#ffffff", - "secondaryDark": "#767F8D", - "dark": "#07101B" - }, - "text": { - "primary": "#F5F5F5", - "secondary": "#C4C7CD", - "third": "#666C75", - "strong": "#FFFFFF", - "buttonText": "#0F1419" - }, - "background": { - "default": "#1C1C1C", - "input": "#26292C", - "tipMask": "rgba(255, 255, 255, 0.85)", - "messageShadow": "rgba(136, 153, 166, 0.2)", - "paper": "#101010" - }, - "error": { - "main": "#FF5555" - }, - "divider": "#38444D", - "secondaryDivider": "#38444D", - "action": { - "buttonHover": "#D7DBDC", - "bgHover": "#1D2933", - "mask": "rgba(91, 112, 131, 0.4)" - } - } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/index.ts b/packages/mask/content-script/site-adaptors/twitter.com/index.ts deleted file mode 100644 index 5d1d48379175..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSiteAdaptorUI } from '../../site-adaptor-infra/index.js' -import { twitterBase } from './base.js' - -defineSiteAdaptorUI({ - ...twitterBase, - load: () => import('./ui-provider.js'), - hotModuleReload(callback) { - if (import.meta.webpackHot) { - import.meta.webpackHot.accept('./ui-provider.ts', async () => { - callback((await import('./ui-provider.js')).default) - }) - } - }, -}) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Avatar/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Avatar/index.tsx deleted file mode 100644 index a6996b8d32f2..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Avatar/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { Plugin } from '@masknet/plugin-infra' -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Avatar } from '../../../../components/InjectedComponents/Avatar.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function getTwitterId(ele: HTMLElement) { - const profileLink = ele.querySelector('a[role="link"]') - return profileLink?.getAttribute('href')?.slice(1) -} - -function inpageAvatarSelector() { - return querySelectorAll( - [ - // Avatars in post - 'main[role="main"] [data-testid="cellInnerDiv"] [data-testid="Tweet-User-Avatar"]', - // Avatars in side panel - '[data-testid="UserCell"] [data-testid^="UserAvatar-Container-"]', - // Avatars in space sheet dialog - '[data-testid=sheetDialog] [data-testid^="UserAvatar-Container-"]', - // Avatars in space dock - '[data-testid=SpaceDockExpanded] [data-testid^=UserAvatar-Container-]', - ].join(','), - ) -} - -export async function injectAvatar(signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(inpageAvatarSelector()).useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const twitterId = getTwitterId(ele) - if (!twitterId) return - - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele.firstChild as HTMLElement - const isSuggestion = ele.closest('[data-testid=UserCell]') - const sourceType = - isSuggestion ? - Plugin.SiteAdaptor.AvatarRealmSourceType.Suggestion - : Plugin.SiteAdaptor.AvatarRealmSourceType.Post - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -
- -
, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Banner.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Banner.tsx deleted file mode 100644 index e68e7d6b01c9..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Banner.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' -import { postEditorInTimelineSelector, postEditorInPopupSelector } from '../utils/selector.js' -import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { hasEditor, isCompose } from '../utils/postBox.js' -import { Banner } from '../../../components/Welcomes/Banner.js' - -export function injectBannerAtTwitter(signal: AbortSignal) { - const emptyNode = document.createElement('div') - injectBanner(postEditorInTimelineSelector(), { - signal, - }) - injectBanner( - postEditorInPopupSelector().map((x) => (isCompose() && hasEditor() ? x : emptyNode)), - { signal }, - ) -} - -function injectBanner(ls: LiveSelector, options: WatchOptions) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, options) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal: options.signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Calendar.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Calendar.tsx deleted file mode 100644 index 1e484d8dbc38..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Calendar.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { querySelector } from '../utils/selector.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { CalendarContent } from '@masknet/plugin-calendar' - -function sidebarSearchSelector() { - return querySelector( - '[data-testid="sidebarColumn"] > div > div > div > div[tabindex="0"] > div > div:not(div[tabindex="0"]):empty', - ) -} - -function sidebarExplorePageSelector() { - return querySelector('[data-testid="settingsAppBar"]') - .closest(12) - .querySelector('[data-testid="sidebarColumn"] [tabindex="0"] > div') -} - -function sidebarSearchPageSelector() { - return querySelector('[data-testid="searchBoxOverflowButton"]') - .closest(11) - .querySelector('[data-testid="sidebarColumn"] [tabindex="0"] > div > div') -} -export function injectCalendar(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(sidebarSearchSelector()) - const exploreWatcher = new MutationObserverWatcher(sidebarExplorePageSelector()) - const searchWatcher = new MutationObserverWatcher(sidebarSearchPageSelector()) - startWatch(watcher, signal) - startWatch(exploreWatcher, signal) - startWatch(searchWatcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( - , - ) - attachReactTreeWithContainer(exploreWatcher.firstDOMProxy.beforeShadow, { untilVisible: true, signal }).render( - , - ) - attachReactTreeWithContainer(searchWatcher.firstDOMProxy.beforeShadow, { untilVisible: true, signal }).render( - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx deleted file mode 100644 index 34e296b395d5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { injectFarcasterOnConversation } from './injectFarcasterOnConversation.js' -import { injectFarcasterOnPost } from './injectFarcasterOnPost.js' -import { injectFarcasterOnProfile } from './injectFarcasterOnProfile.js' -import { injectFarcasterOnSpaceDock } from './injectFarcasterOnSpaceDock.js' -import { injectFarcasterOnUserCell } from './injectFarcasterOnUserCell.js' - -export function injectFarcaster(signal: AbortSignal) { - injectFarcasterOnProfile(signal) - injectFarcasterOnPost(signal) - injectFarcasterOnUserCell(signal) - injectFarcasterOnConversation(signal) - injectFarcasterOnSpaceDock(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx deleted file mode 100644 index 4417aa7a2ca2..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - return querySelectorAll('[data-testid=conversation] div:not([tabindex]) div[dir] + div[dir]') -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -const ConversationFarcasterSlot = memo(function ConversationFarcasterSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrapOr(null) - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -}) - -/** - * Inject on conversation, including both DM drawer and message page (/messages/xxx) - */ -export function injectFarcasterOnConversation(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const spans = node - .closest('[data-testid=conversation]') - ?.querySelectorAll('[tabindex] [dir] span:not([data-testid=tweetText])') - if (!spans) return - const userId = [...spans].reduce((id, node) => { - if (id) return id - if (node.textContent?.match(/@\w/)) { - return node.textContent.trim().slice(1) - } - return '' - }, '') - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx deleted file mode 100644 index bd45efd24e4a..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useMemo, useState } from 'react' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function selector() { - return querySelectorAll('[data-testid=User-Name] div').filter((node) => { - return node.firstElementChild?.matches('a[role=link]:not([tabindex])') - }) -} - -// structure: -export function injectFarcasterOnPost(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const link = node.querySelector('a[href][role=link]') - // To simplify the selector above, we do this checking manually - // has tabindex=-1, has a child time element - if (link?.hasAttribute('tabindex') || link?.querySelector('time')) return - const href = link?.getAttribute('href') - const userId = href?.split('/')[1] - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - alignItems: 'center', - display: 'flex', - }) - return span -} -function PostFarcasterSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx deleted file mode 100644 index 0e941017376f..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { startWatch } from '../../../../utils/startWatch.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelector } from '../../utils/selector.js' - -function selector() { - return querySelector('[data-testid=UserName] div[dir]') -} - -export function injectFarcasterOnProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -function ProfileFarcasterSlot() { - const visitingIdentity = useCurrentVisitingIdentity() - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - ) - - return ( - - ) - }, [visitingIdentity.identifier]) - - if (!component || !visitingIdentity.identifier) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx deleted file mode 100644 index 062aa96b02a6..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useMemo, useState } from 'react' -import { makeStyles } from '@masknet/theme' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function avatarSelector() { - return querySelectorAll( - '[data-testid=SpaceDockExpanded] [data-testid^=UserAvatar-Container-],[data-testid=sheetDialog] [data-testid^=UserAvatar-Container-]', - ).map((node) => { - const span = node.parentElement?.parentElement?.nextElementSibling?.querySelector('div > span + span > span') - return span - }) -} - -/** - * Inject on space dock - */ -export function injectFarcasterOnSpaceDock(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(avatarSelector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const avatar = node - .closest('div[dir]') - ?.previousElementSibling?.querySelector('[data-testid^=UserAvatar-Container-]') - if (!avatar) return - const userId = avatar.dataset.testid?.slice('UserAvatar-Container-'.length) - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -function SpaceDockFarcasterSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx deleted file mode 100644 index 145dfc4e01ab..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { querySelectorAll } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - // [href^="/search"] is a hash tag - return querySelectorAll( - '[data-testid=UserCell] div > a[role=link]:not([tabindex]):not([href^="/search"]) [dir]:last-of-type', - ) -} - -/** - * Inject on sidebar user cell - */ -export function injectFarcasterOnUserCell(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const userId = node.closest('[role=link]')?.getAttribute('href')?.slice(1) - if (!userId) return - // Intended to set `untilVisible` to true, but mostly user cells are fixed and visible - attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'flex', - } as CSSStyleDeclaration) - return span -} - -function UserCellFarcasterSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - undefined, - createRootElement, - ) - if (userId.includes('/')) return null - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx deleted file mode 100644 index f88a03d46af4..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { injectLensOnConversation } from './injectLensOnConversation.js' -import { injectLensOnPost } from './injectLensOnPost.js' -import { injectLensOnProfile } from './injectLensOnProfile.js' -import { injectLensOnSpaceDock } from './injectLensOnSpaceDock.js' -import { injectLensOnUserCell } from './injectLensOnUserCell.js' - -export function injectLens(signal: AbortSignal) { - injectLensOnProfile(signal) - injectLensOnPost(signal) - injectLensOnUserCell(signal) - injectLensOnConversation(signal) - injectLensOnSpaceDock(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx deleted file mode 100644 index 5866bb3ad745..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - return querySelectorAll('[data-testid=conversation] div:not([tabindex]) div[dir] + div[dir]') -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -const ConversationLensSlot = memo(function ConversationLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrapOr(null) - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -}) - -/** - * Inject on conversation, including both DM drawer and message page (/messages/xxx) - */ -export function injectLensOnConversation(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const spans = node - .closest('[data-testid=conversation]') - ?.querySelectorAll('[tabindex] [dir] span:not([data-testid=tweetText])') - if (!spans) return - const userId = [...spans].reduce((id, node) => { - if (id) return id - if (node.textContent?.match(/@\w/)) { - return node.textContent.trim().slice(1) - } - return '' - }, '') - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx deleted file mode 100644 index 4238552ce692..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useMemo, useState } from 'react' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function selector() { - return querySelectorAll('[data-testid=User-Name] div').filter((node) => { - return node.firstElementChild?.matches('a[role=link]:not([tabindex])') - }) -} - -// structure: -export function injectLensOnPost(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const link = node.querySelector('a[href][role=link]') - // To simplify the selector above, we do this checking manually - // has tabindex=-1, has a child time element - if (link?.hasAttribute('tabindex') || link?.querySelector('time')) return - const href = link?.getAttribute('href') - const userId = href?.split('/')[1] - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - alignItems: 'center', - display: 'flex', - }) - return span -} -function PostLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx deleted file mode 100644 index 308d72966689..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { startWatch } from '../../../../utils/startWatch.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelector } from '../../utils/selector.js' - -function selector() { - return querySelector('[data-testid=UserName] div[dir]') -} - -export function injectLensOnProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -function ProfileLensSlot() { - const visitingIdentity = useCurrentVisitingIdentity() - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - ) - - return ( - - ) - }, [visitingIdentity.identifier]) - - if (!component || !visitingIdentity.identifier) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx deleted file mode 100644 index 9adf543d77ab..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useMemo, useState } from 'react' -import { makeStyles } from '@masknet/theme' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function avatarSelector() { - return querySelectorAll( - '[data-testid=SpaceDockExpanded] [data-testid^=UserAvatar-Container-],[data-testid=sheetDialog] [data-testid^=UserAvatar-Container-]', - ).map((node) => { - const span = node.parentElement?.parentElement?.nextElementSibling?.querySelector('div > span + span > span') - return span - }) -} - -/** - * Inject on space dock - */ -export function injectLensOnSpaceDock(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(avatarSelector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const avatar = node - .closest('div[dir]') - ?.previousElementSibling?.querySelector('[data-testid^=UserAvatar-Container-]') - if (!avatar) return - const userId = avatar.dataset.testid?.slice('UserAvatar-Container-'.length) - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -function SpaceDockLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx deleted file mode 100644 index 81efeaf3f762..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { querySelectorAll } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - // [href^="/search"] is a hash tag - return querySelectorAll( - '[data-testid=UserCell] div > a[role=link]:not([tabindex]):not([href^="/search"]) [dir]:last-of-type', - ) -} - -/** - * Inject on sidebar user cell - */ -export function injectLensOnUserCell(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const userId = node.closest('[role=link]')?.getAttribute('href')?.slice(1) - if (!userId) return - // Intended to set `untilVisible` to true, but mostly user cells are fixed and visible - attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'flex', - } as CSSStyleDeclaration) - return span -} - -function UserCellLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - if (userId.includes('/')) return null - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/MaskIcon.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/MaskIcon.tsx deleted file mode 100644 index e8992165a8b9..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/MaskIcon.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { memoize, noop } from 'lodash-es' -import { DOMProxy, LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Icons } from '@masknet/icons' -import { memoizePromise } from '@masknet/kit' -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { Flags } from '@masknet/flags' -import Services from '#services' -import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { bioPageUserIDSelector, bioPageUserNickNameSelector, floatingBioCardSelector } from '../utils/selector.js' - -function Icon(props: { size: number }) { - return ( - - ) -} -function _(main: () => LiveSelector, size: number, options: WatchOptions) { - const watcher = new MutationObserverWatcher(main()).useForeach((ele, _, meta) => { - let remover = noop - const remove = () => remover() - const check = () => { - ifUsingMask( - ProfileIdentifier.of(EnhanceableSite.Twitter, bioPageUserIDSelector(main).evaluate()).unwrapOr(null), - ).then(() => { - const root = attachReactTreeWithContainer(meta.afterShadow, { - untilVisible: true, - signal: options.signal, - }) - root.render() - remover = root.destroy - }, remove) - } - check() - return { - onNodeMutation: check, - onTargetChanged: check, - onRemove: remove, - } - }) - startWatch(watcher, options) -} - -export function injectMaskUserBadgeAtTwitter(signal: AbortSignal) { - // profile - _(bioPageUserNickNameSelector, 24, { signal }) - // floating bio - _(floatingBioCardSelector, 20, { signal }) -} -export function injectMaskIconToPostTwitter(post: PostInfo, signal: AbortSignal) { - const ls = new LiveSelector([post.rootElement]) - .map((x) => x.current.querySelector('[data-testid=User-Name]')) - .enableSingleMode() - ifUsingMask(post.author.getCurrentValue()).then(add, remove) - post.author.subscribe(() => ifUsingMask(post.author.getCurrentValue()).then(add, remove)) - let remover = noop - function add() { - if (signal?.aborted) return - const node = ls.evaluate() - if (!node) return - const proxy = DOMProxy({ afterShadowRootInit: Flags.shadowRootInit }) - proxy.realCurrent = node - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render() - remover = root.destroy - } - function remove() { - remover() - } -} -const ifUsingMask = memoizePromise( - memoize, - async (pid: ProfileIdentifier | null) => { - if (!pid) throw new Error() - const p = await Services.Identity.queryProfilesInformation([pid]) - if (!p[0].linkedPersona?.rawPublicKey) throw new Error() - }, - (x) => x, -) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/Avatar.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/Avatar.tsx deleted file mode 100644 index 9f3fbabbaeba..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/Avatar.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Flags } from '@masknet/flags' -import { DOMProxy, type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { getInjectNodeInfo } from '../../utils/avatar.js' -import { followUserAvatarSelector, postAvatarSelector } from '../../utils/selector.js' -import { activatedSiteAdaptorUI } from '../../../../site-adaptor-infra/ui.js' -import { MiniAvatarBorder } from './MiniAvatarBorder.js' - -function getUserId(ele: HTMLElement) { - const attribute = ele.dataset.testid || '' - if (attribute.endsWith('unknown')) { - return ele?.querySelector('a[href][role=link]')?.getAttribute('href')?.slice(1) - } - return attribute.split('-').pop() -} - -function inject(selector: () => LiveSelector, signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(selector()).useForeach((ele) => { - let remover: () => void | undefined - - const run = async () => { - const userId = getUserId(ele) - if (!userId) return - - const info = getInjectNodeInfo(ele) - if (!info) return - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = info.element.firstChild as HTMLElement - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -
- -
, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: () => remover?.(), - } - }), - signal, - ) -} - -export async function injectUserNFTAvatarAtTwitter(signal: AbortSignal) { - inject(postAvatarSelector, signal) - inject(followUserAvatarSelector, signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/MiniAvatarBorder.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/MiniAvatarBorder.tsx deleted file mode 100644 index 9405da144c42..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/MiniAvatarBorder.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { NFTBadgeTimeline } from '@masknet/plugin-avatar' - -interface MiniAvatarBorderProps { - size: number - screenName: string - avatarId?: string -} -export function MiniAvatarBorder(props: MiniAvatarBorderProps) { - const { size, screenName, avatarId } = props - - return -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx deleted file mode 100644 index 2b2babc5c2e5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useEffect } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { NFTAvatarButton } from '@masknet/plugin-avatar' -import { ConnectPersonaBoundary } from '@masknet/shared' -import { PluginID, CrossIsolationMessages, currentPersonaIdentifier } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { searchEditProfileSelector } from '../../utils/selector.js' -import { injectOpenNFTAvatarEditProfileButtonAtEditProfileDialog } from './NFTAvatarEditProfileDialog.js' -import { ButtonStyle, type ButtonProps } from '../../constant.js' -import { useLastRecognizedIdentity, useThemeSettings } from '../../../../components/DataSource/useActivatedUI.js' -import { usePersonasFromDB } from '../../../../../shared-ui/hooks/usePersonasFromDB.js' -import Services from '#services' - -export function injectOpenNFTAvatarEditProfileButton(signal: AbortSignal) { - injectOpenNFTAvatarEditProfileButtonAtProfilePage(signal) - injectOpenNFTAvatarEditProfileButtonAtEditProfileDialog(signal) -} - -function injectOpenNFTAvatarEditProfileButtonAtProfilePage(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchEditProfileSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { untilVisible: true, signal }).render( - , - ) -} - -const useStyles = makeStyles()((theme, props) => ({ - root: { - minHeight: props.buttonSize, - marginBottom: props.marginBottom, - marginTop: 1, - marginRight: theme.spacing(2), - height: props.buttonSize, - }, - text: { - fontWeight: 700, - fontSize: props.fontSize, - }, -})) - -export function openNFTAvatarSettingDialog() { - const editDom = searchEditProfileSelector().evaluate() - editDom?.click() -} - -function useNFTAvatarButtonStyles() { - const themeSettings = useThemeSettings() - const style = ButtonStyle[themeSettings.size] - return useStyles(style) -} -function requestSettingAvatar() { - CrossIsolationMessages.events.avatarSettingsDialogEvent.sendToLocal({ - open: true, - startPicking: true, - }) -} -function OpenNFTAvatarEditProfileButtonInTwitter() { - const { classes } = useNFTAvatarButtonStyles() - const allPersonas = usePersonasFromDB() - const lastRecognized = useLastRecognizedIdentity() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - - useEffect(() => { - const clearTasks = [ - CrossIsolationMessages.events.personaBindFinished.on((ev) => { - if (ev.pluginID === PluginID.Avatar) requestSettingAvatar() - }), - CrossIsolationMessages.events.applicationDialogEvent.on((ev) => { - if (ev.pluginID === PluginID.Avatar && ev.isVerified) requestSettingAvatar() - }), - ] - - return () => { - clearTasks.forEach((task) => task()) - } - }, []) - - return ( - - - - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx deleted file mode 100644 index 95d991711a75..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useEffect } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { NFTAvatarButton } from '@masknet/plugin-avatar' -import { ConnectPersonaBoundary } from '@masknet/shared' -import { CrossIsolationMessages, PluginID, currentPersonaIdentifier } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { makeStyles } from '@masknet/theme' -import { Twitter } from '@masknet/web3-providers' -import { - useCurrentVisitingIdentity, - useLastRecognizedIdentity, - useThemeSettings, -} from '../../../../components/DataSource/useActivatedUI.js' -import { usePersonasFromDB } from '../../../../../shared-ui/hooks/usePersonasFromDB.js' -import Services from '#services' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { ButtonStyle } from '../../constant.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { searchProfileAvatarSelector, searchProfileSaveSelector } from '../../utils/selector.js' - -export function injectOpenNFTAvatarEditProfileButtonAtEditProfileDialog(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileAvatarSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( - , - ) - - // clear cache - const saveButtonWatcher = new MutationObserverWatcher(searchProfileSaveSelector()).useForeach( - (node, key, proxy) => { - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render() - return () => root.destroy() - }, - ) - - startWatch(saveButtonWatcher, signal) -} - -function NFTAvatarSave() { - const identity = useCurrentVisitingIdentity() - useEffect(() => { - if (!identity.identifier?.userId) return - const saveButton = searchProfileSaveSelector().evaluate() - if (!saveButton) return - const clearCache = () => { - Twitter.staleUserByScreenName(identity.identifier?.userId ?? '') - } - saveButton.addEventListener('click', clearCache) - return () => saveButton.removeEventListener('click', clearCache) - }, [identity.identifier?.userId]) - return null -} - -const useStyles = makeStyles<{ buttonSize: number; fontSize: number }>()((theme, { buttonSize, fontSize }) => ({ - root: { - display: 'flex', - top: 211, - right: 16, - position: 'absolute', - }, - button: { - height: buttonSize, - }, - text: { - fontWeight: 700, - fontSize, - }, -})) - -function clickHandler() { - CrossIsolationMessages.events.avatarSettingsDialogEvent.sendToLocal({ - open: true, - }) -} -function OpenNFTAvatarEditProfileButtonInTwitter() { - const personas = usePersonasFromDB() - const lastRecognized = useLastRecognizedIdentity() - const currentIdentifier = useValueRef(currentPersonaIdentifier) - const themeSettings = useThemeSettings() - const buttonStyle = ButtonStyle[themeSettings.size] - - const { classes } = useStyles({ buttonSize: buttonStyle.buttonSize, fontSize: buttonStyle.fontSize }) - - return ( -
- - - -
- ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx deleted file mode 100644 index 1748b56c72dd..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useMemo, useState, useSyncExternalStore } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { makeStyles } from '@masknet/theme' -import { NFTBadge } from '@masknet/plugin-avatar' -import { useLocation, useWindowSize } from 'react-use' -import { AvatarStore, Twitter } from '@masknet/web3-providers' -import { useInjectedCSS } from './useInjectedCSS.js' -import { useUpdatedAvatar } from './useUpdatedAvatar.js' -import { searchAvatarMetaSelector, searchAvatarSelector, searchTwitterAvatarSelector } from '../../utils/selector.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' - -const useStyles = makeStyles()(() => ({ - root: { - transform: 'scale(1.022)', - position: 'absolute', - textAlign: 'center', - color: 'white', - zIndex: 2, - width: '100%', - height: '100%', - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - text: { - fontSize: '20px !important', - fontWeight: 700, - }, - icon: { - width: '19px !important', - height: '19px !important', - }, -})) - -export function NFTAvatarInTwitter() { - const windowSize = useWindowSize() - const _location = useLocation() - const { classes } = useStyles() - const [updatedAvatar, setUpdatedAvatar] = useState(false) - - const size = useMemo(() => { - const ele = searchTwitterAvatarSelector().evaluate()?.querySelector('img') - if (!ele) return 0 - const style = window.getComputedStyle(ele) - return Number.parseInt(style.width.replace('px', '') ?? 0, 10) - }, [windowSize, _location]) - - const { showAvatar, token, avatar } = useNFTCircleAvatar(size) - - useInjectedCSS(showAvatar, updatedAvatar) - useUpdatedAvatar(showAvatar, avatar) - - const handlerWatcher = () => { - const avatarUrl = searchAvatarSelector().evaluate()?.getAttribute('src') - if (!avatarUrl || !avatar?.avatarId) return - setUpdatedAvatar(!!avatar?.avatarId && Twitter.getAvatarId(avatarUrl ?? '') === avatar.avatarId) - } - useEffect(() => { - const abortController = new AbortController() - new MutationObserverWatcher(searchAvatarMetaSelector()) - .addListener('onAdd', handlerWatcher) - .addListener('onChange', handlerWatcher) - .startWatch( - { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['src'], - }, - abortController.signal, - ) - return () => abortController.abort() - }, [handlerWatcher]) - if (!showAvatar) return null - - return ( - - ) -} - -function useNFTCircleAvatar(size: number) { - const identity = useCurrentVisitingIdentity() - - const userId = identity.identifier?.userId || '' - const identityAvatarId = Twitter.getAvatarId(identity.avatar) - const store = useSyncExternalStore(AvatarStore.subscribe, AvatarStore.getSnapshot) - const avatar = store.retrieveAvatar(userId, identityAvatarId) - const token = store.retrieveToken(userId, identityAvatarId) - - useEffect(() => { - AvatarStore.dispatch(userId, identityAvatarId) - }, [userId, identityAvatarId]) - - const showAvatar = avatar?.avatarId ? identityAvatarId === avatar.avatarId : false - - return { - showAvatar: Boolean(size && showAvatar && token), - avatar, - token, - } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/TweetNFTAvatar.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/TweetNFTAvatar.tsx deleted file mode 100644 index 497e797ad615..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/TweetNFTAvatar.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { DOMProxy, type LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { getInjectNodeInfo } from '../../utils/avatar.js' -import { searchRetweetAvatarSelector, searchTweetAvatarSelector } from '../../utils/selector.js' -import { MiniAvatarBorder } from './MiniAvatarBorder.js' -import { activatedSiteAdaptorUI } from '../../../../site-adaptor-infra/ui.js' -import { getUserId } from '../../utils/user.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function _(main: () => LiveSelector, signal: AbortSignal) { - startWatch( - new MutationObserverWatcher(main()).useForeach((ele, _, meta) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const info = getInjectNodeInfo(ele.firstChild as HTMLElement) - if (!info) return - - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = info.element.firstChild as HTMLElement - - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render( -
- -
, - ) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} - -export async function injectUserNFTAvatarAtTweet(signal: AbortSignal) { - _(searchTweetAvatarSelector, signal) - _(searchRetweetAvatarSelector, signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/index.tsx deleted file mode 100644 index c1d79041a26f..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { NFTAvatarInTwitter } from './NFTAvatarInTwitter.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { searchTwitterAvatarSelector } from '../../utils/selector.js' -import { startWatch } from '../../../../utils/startWatch.js' - -export function injectNFTAvatarInTwitter(signal: AbortSignal) { - const defaultWatcher = new MutationObserverWatcher(searchTwitterAvatarSelector()).useForeach((ele, _, proxy) => { - const root = attachReactTreeWithContainer(proxy.afterShadow, { untilVisible: true, signal }) - root.render() - return () => root.destroy() - }) - startWatch(defaultWatcher, signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useInjectedCSS.ts b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useInjectedCSS.ts deleted file mode 100644 index dd969b6969ef..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useInjectedCSS.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { rainbowBorderKeyFrames } from '@masknet/plugin-avatar' -import { useEffect, useRef } from 'react' -import { searchTwitterAvatarLinkSelector } from '../../utils/selector.js' - -export function useInjectedCSS(showAvatar: boolean, updatedAvatar: boolean) { - const rainbowElement = useRef() - const borderElement = useRef() - useEffect(() => { - if (!showAvatar || !updatedAvatar) return - const linkDom = searchTwitterAvatarLinkSelector().evaluate() - - if (linkDom?.firstElementChild && linkDom.childNodes.length === 4) { - const linkParentDom = linkDom.closest('div') - - if (linkParentDom) linkParentDom.style.overflow = 'visible' - - // create rainbow shadow border - if (linkDom.lastElementChild?.tagName !== 'STYLE') { - borderElement.current = linkDom.firstElementChild - // remove useless border - linkDom.removeChild(linkDom.firstElementChild) - const style = document.createElement('style') - style.innerText = ` - ${rainbowBorderKeyFrames.styles} - - .rainbowBorder { - animation: ${rainbowBorderKeyFrames.name} 6s linear infinite; - box-shadow: 0 5px 15px rgba(0, 248, 255, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2); - transition: none; - border: 0 solid #00f8ff; - } - - ` - - rainbowElement.current = linkDom.firstElementChild.nextElementSibling - linkDom.firstElementChild.nextElementSibling?.classList.add('rainbowBorder') - linkDom.appendChild(style) - } - } - - return () => { - if (linkDom?.lastElementChild?.tagName === 'STYLE') { - linkDom.removeChild(linkDom.lastElementChild) - } - - if (borderElement.current && linkDom?.firstElementChild !== borderElement.current) { - linkDom?.insertBefore(borderElement.current, linkDom.firstChild) - } - if (rainbowElement.current) { - rainbowElement.current.classList.remove('rainbowBorder') - } - } - }, [location.pathname, showAvatar, updatedAvatar]) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useSaveAvatarInTwitter.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useSaveAvatarInTwitter.tsx deleted file mode 100644 index d217ec3f39fb..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useSaveAvatarInTwitter.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useSaveStringStorage } from '@masknet/plugin-avatar' -import type { IdentityResolved } from '@masknet/plugin-infra' -import { Twitter } from '@masknet/web3-providers' -import { NetworkPluginID } from '@masknet/shared-base' -import { useChainContext } from '@masknet/web3-hooks-base' -import { useCallback } from 'react' -import type { AvatarNextID } from '@masknet/web3-providers/types' -import { useAsync } from 'react-use' - -export function useSaveAvatarInTwitter(identity: IdentityResolved) { - const { account } = useChainContext() - - const saveNFTAvatar = useSaveStringStorage(NetworkPluginID.PLUGIN_EVM) - - const onSave = useCallback(async () => { - if (!account || !identity.identifier) return - - try { - return await saveNFTAvatar(identity.identifier.userId, account, { - avatarId: Twitter.getAvatarId(identity.avatar ?? ''), - } as AvatarNextID) - } catch (error) { - return - } - }, [account, identity]) - - const { value } = useAsync(() => { - return onSave() - }, [identity.avatar]) - - return value -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useUpdatedAvatar.ts b/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useUpdatedAvatar.ts deleted file mode 100644 index 99a6337c5eee..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NFT/useUpdatedAvatar.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useUpdateEffect } from 'react-use' -import type { AvatarNextID } from '@masknet/web3-providers/types' -import { CrossIsolationMessages, type NetworkPluginID } from '@masknet/shared-base' -import { searchTwitterAvatarLinkSelector } from '../../utils/selector.js' - -export function useUpdatedAvatar(showAvatar: boolean, nftAvatar: AvatarNextID | null) { - useUpdateEffect(() => { - if (!showAvatar) return - - const linkParentDom = searchTwitterAvatarLinkSelector().evaluate()?.closest('div') - if (!linkParentDom) return - - const handler = (event: MouseEvent) => { - event.stopPropagation() - event.preventDefault() - if (!nftAvatar?.tokenId || !nftAvatar?.address || !nftAvatar.pluginId || !nftAvatar.chainId) return - CrossIsolationMessages.events.nonFungibleTokenDialogEvent.sendToLocal({ - open: true, - pluginID: nftAvatar.pluginId, - chainId: nftAvatar.chainId, - tokenId: nftAvatar.tokenId, - tokenAddress: nftAvatar.address, - ownerAddress: nftAvatar.ownerAddress, - origin: 'pfp', - }) - } - - const clean = () => { - linkParentDom.removeEventListener('click', handler, true) - } - - if (!nftAvatar) return - - linkParentDom.addEventListener('click', handler, true) - - return clean - }, [nftAvatar, showAvatar]) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/index.tsx deleted file mode 100644 index 6ca55ada5e8c..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { injectNameWidgetOnPost } from './injectNameWidgetOnPost.js' -import { injectNameWidgetProfile } from './injectNameWidgetOnProfile.js' -import { injectNameWidgetOnUserCell } from './injectNameWidgetOnSidebar.js' - -export function injectNameWidget(signal: AbortSignal) { - injectNameWidgetOnPost(signal) - injectNameWidgetProfile(signal) - injectNameWidgetOnUserCell(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnPost.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnPost.tsx deleted file mode 100644 index 62f1d66e5c14..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnPost.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: '50%', - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - alignItems: 'center', - display: 'flex', - }) - return span -} -const PostNameWidgetSlot = memo(function PostNameWidgetSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.NameWidget?.UI?.Content, - undefined, - createRootElement, - ) - - return - }, [userId]) - - if (!component) return null - - return {component} -}) - -function selector() { - return querySelectorAll('[data-testid=User-Name] div').filter((node) => { - return node.firstElementChild?.matches('a[role=link]:not([tabindex])') - }) -} - -// structure: -export function injectNameWidgetOnPost(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const link = node.querySelector('a[href][role=link]') - // To simplify the selector above, we do this checking manually - // has tabindex=-1, has a child time element - if (link?.hasAttribute('tabindex') || link?.querySelector('time')) return - const href = link?.getAttribute('href') - const userId = href?.split('/')[1] - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnProfile.tsx deleted file mode 100644 index cc8c48620ff5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnProfile.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { startWatch } from '../../../../utils/startWatch.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelector } from '../../utils/selector.js' - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: '50%', - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -const ProfileNameWidgetSlot = memo(function ProfileNameWidgetSlot() { - const visitingIdentity = useCurrentVisitingIdentity() - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const userId = visitingIdentity.identifier?.userId - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.NameWidget?.UI?.Content, - ) - - return ( - - ) - }, [userId]) - - if (!component || !userId) return null - - return {component} -}) - -function selector() { - return querySelector('[data-testid=UserName] div[dir]') -} - -export function injectNameWidgetProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnSidebar.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnSidebar.tsx deleted file mode 100644 index d6344d92b8fd..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/NameWidget/injectNameWidgetOnSidebar.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { querySelectorAll } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - return querySelectorAll('[data-testid=UserCell] div > a[role=link][tabindex]:not(:has(img)) > div') -} - -interface Props { - userId: string -} - -function createRootElement() { - return document.createElement('div') -} - -const UserCellNameWidgetSlot = memo(function UserCellNameWidgetSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.NameWidget?.UI?.Content, - undefined, - createRootElement, - ) - if (userId.includes('/')) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return
{component}
-}) - -/** - * Inject on sidebar user cell - */ -export function injectNameWidgetOnUserCell(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const userId = node.closest('[role=link]')?.getAttribute('href')?.slice(1) - if (!userId) return - // Intended to set `untilVisible` to true, but mostly user cells are fixed and visible - attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostActions/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostActions/index.tsx deleted file mode 100644 index d94542436cb8..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostActions/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { Flags } from '@masknet/flags' -import { createPostActionsInjector } from '../../../../site-adaptor-infra/defaults/inject/PostActions.js' - -export function injectPostActionsAtTwitter(signal: AbortSignal, postInfo: PostInfo) { - if (!Flags.post_actions_enabled) return - const injector = createPostActionsInjector() - return injector(postInfo, signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialog.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialog.tsx deleted file mode 100644 index 749c2d5b8699..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialog.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { Composition } from '../../../components/CompositionDialog/Composition.js' -import { postEditorContentInPopupSelector, rootSelector } from '../utils/selector.js' -import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' - -function renderPostDialogTo(reason: 'timeline' | 'popup', ls: LiveSelector, options: WatchOptions) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, options) - - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal: options.signal }).render( - , - ) -} - -export function injectPostDialogAtTwitter(signal: AbortSignal) { - renderPostDialogTo('popup', postEditorContentInPopupSelector(), { - signal, - }) - renderPostDialogTo('timeline', rootSelector(), { - signal, - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialogHint.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialogHint.tsx deleted file mode 100644 index e355f3388216..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostDialogHint.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useCallback } from 'react' -import { clamp } from 'lodash-es' -import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' -import { CrossIsolationMessages, sayHelloShowed } from '@masknet/shared-base' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { makeTypedMessageText } from '@masknet/typed-message' -import { alpha } from '@mui/material' -import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' -import { twitterBase } from '../base.js' -import { hasEditor, isCompose } from '../utils/postBox.js' -import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector.js' - -const useStyles = makeStyles()((theme) => ({ - iconButton: { - '&:hover': { - background: alpha(theme.palette.primary.main, 0.1), - }, - }, - tooltip: { - marginTop: '2px !important', - borderRadius: 2, - padding: 4, - background: MaskColorVar.twitterTooltipBg, - color: MaskColorVar.white, - }, -})) - -export function injectPostDialogHintAtTwitter(signal: AbortSignal) { - const emptyNode = document.createElement('div') - - renderPostDialogHintTo('timeline', searchReplyToolbarSelector(), { - signal, - }) - - renderPostDialogHintTo( - 'popup', - postEditorInPopupSelector().map((x) => (isCompose() && hasEditor() ? x : emptyNode)), - { - signal, - }, - ) -} - -function renderPostDialogHintTo( - reason: 'timeline' | 'popup', - ls: LiveSelector, - options: WatchOptions, -) { - const watcher = new MutationObserverWatcher(ls) - startWatch(watcher, options) - let tag: HTMLSpanElement - function setTagProperties() { - if (!tag) return - Object.assign(tag.style, { - display: 'inline-flex', - alignItems: 'center', - height: '100%', - }) - const svgIcon = document.querySelector('[data-testid="geoButton"] svg') - const size = svgIcon ? clamp(svgIcon.getBoundingClientRect().width, 18, 24) : undefined - const geoButton = document.querySelector('[data-testid="geoButton"]') - const padding = geoButton && size ? (geoButton.getBoundingClientRect().width - size) / 2 : undefined - if (padding) tag.style.setProperty('--icon-padding', `${padding}px`) - if (size) tag.style.setProperty('--icon-size', `${size}px`) - } - watcher.addListener('onChange', setTagProperties) - - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { - signal: options.signal, - tag: () => { - // Vertical center the button when font size of Twitter is set to `large` or `very large` - tag = document.createElement('span') - setTagProperties() - return tag - }, - }).render() -} - -function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) { - const { classes } = useStyles() - const t = useMaskSharedTrans() - - const onHintButtonClicked = useCallback(() => { - const content = - sayHelloShowed[twitterBase.networkIdentifier].value ? - undefined - : makeTypedMessageText( - t.setup_guide_say_hello_content() + t.setup_guide_say_hello_follow({ account: '@realMaskNetwork' }), - ) - - CrossIsolationMessages.events.compositionDialogEvent.sendToLocal({ - reason: isReplyPageSelector() ? 'reply' : reason, - open: true, - content, - }) - sayHelloShowed[twitterBase.networkIdentifier].value = true - }, [reason, isReplyPageSelector]) - - return ( - - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx deleted file mode 100644 index 36ff3afe2bd3..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint @masknet/unicode-specific-set: ["error", { "only": "code" }] */ -import { TwitterDecoder } from '@masknet/encryption' -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/inject/PostInspector.js' - -export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostInfo) { - return injectPostInspectorDefault({ - zipPost(node) { - if (node.destroyed) return - const contentContainer = node.current.parentElement - if (!contentContainer) return - - const content = contentContainer.querySelector('[lang]') - if (!content) return - - for (const a of content.querySelectorAll('a')) { - if (TwitterDecoder(a.title).isSome()) hideDOM(a) - - if (/^https?:\/\/mask(\.io|book\.com)$/i.test(a.title)) hideDOM(a) - } - for (const span of content.querySelectorAll('span')) { - // match (.) (\n) (—§—) (any space) (/*) - // Note: In Chinese we can't hide dom because "解密这条推文。\n—§—" is in the same DOM - // hide it will break the sentence. - if (span.innerText.match(/^\.\n\u2014\u00A7\u2014 +\/\* $/)) hideDOM(span) - // match (any space) (*/) (any space) - if (span.innerText.match(/^ +\*\/ ?$/)) hideDOM(span) - } - - const parent = content.parentElement?.nextElementSibling as HTMLElement - if (parent && matches(parent.innerText)) { - parent.style.height = '0' - parent.style.overflow = 'hidden' - } - - const cardWrapper = - contentContainer.parentElement?.querySelector('[data-testid="card.wrapper"]') - if (cardWrapper) { - cardWrapper.style.display = 'none' - cardWrapper.setAttribute('aria-hidden', 'true') - } - }, - })(current, signal) -} -function matches(input: string) { - input = input.toLowerCase() - return input.includes('maskbook.com') && input.includes('make privacy protected again') -} - -function hideDOM(a: HTMLElement) { - a.style.width = '0' - a.style.height = '0' - a.style.overflow = 'hidden' - a.style.display = 'inline-block' -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostReplacer.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostReplacer.tsx deleted file mode 100644 index a6707fa657c5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostReplacer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { injectPostReplacer } from '../../../site-adaptor-infra/defaults/inject/PostReplacer.js' - -function resolveLangNode(node: HTMLElement) { - return node.hasAttribute('lang') ? node : ( - node.querySelector('[lang]') ?? node.parentElement?.querySelector('[lang]') - ) -} - -export function injectPostReplacerAtTwitter(signal: AbortSignal, current: PostInfo) { - const isPromotionPost = !!current.rootNode?.querySelector('svg path[d$="996V8h7v7z"]') - const isCollapsedPost = !!current.rootNode?.querySelector('[data-testid="tweet-text-show-more-link"]') - if (isPromotionPost || isCollapsedPost) return - - const hasVideo = !!current.rootNode?.closest('[data-testid="tweet"]')?.querySelector('video') - if (hasVideo) return - - const tags = Array.from( - current.rootNode?.querySelectorAll( - ['a[role="link"][href*="cashtag_click"]', 'a[role="link"][href*="hashtag_click"]'].join(','), - ) ?? [], - ) - if (!tags.map((x) => x.textContent).some((x) => x && /^[#$]\w+$/i.test(x) && x.length <= 9)) return - - return injectPostReplacer({ - zipPost(node) { - if (node.destroyed) return - const langNode = resolveLangNode(node.current) - if (langNode) langNode.style.display = 'none' - }, - unzipPost(node) { - if (node.destroyed || !node.current) return - const langNode = resolveLangNode(node.current) - if (langNode) langNode.style.display = 'unset' - }, - })(current, signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/constants.ts b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/constants.ts deleted file mode 100644 index e03d2a757274..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const CARD_WIDTH = 450 -export const CARD_HEIGHT = 500 diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/index.tsx deleted file mode 100644 index 20f46e920da0..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/index.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { CrossIsolationMessages, ProfileIdentifier, stopPropagation, type SocialIdentity } from '@masknet/shared-base' -import { AnchorProvider } from '@masknet/shared-base-ui' -import { ShadowRootPopper, makeStyles } from '@masknet/theme' -import { Twitter } from '@masknet/web3-providers' -import type { TwitterBaseAPI } from '@masknet/web3-providers/types' -import { Fade } from '@mui/material' -import { useQuery } from '@tanstack/react-query' -import { useEffect, useMemo, useRef, useState } from 'react' -import { useSocialIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { ProfileCard } from '../../../../components/InjectedComponents/ProfileCard/index.js' -import { attachReactTreeWithoutContainer } from '../../../../utils/shadow-root.js' -import { twitterBase } from '../../base.js' -import { CARD_HEIGHT, CARD_WIDTH } from './constants.js' -import { useControlProfileCard } from './useControlProfileCard.js' - -export function injectProfileCardHolder(signal: AbortSignal) { - attachReactTreeWithoutContainer('profile-card', , signal) -} - -const useStyles = makeStyles()({ - root: { - borderRadius: 10, - width: CARD_WIDTH, - maxWidth: CARD_WIDTH, - height: CARD_HEIGHT, - maxHeight: CARD_HEIGHT, - }, -}) - -function ProfileCardHolder() { - const { classes } = useStyles() - const holderRef = useRef(null) - const [twitterId, setTwitterId] = useState('') - const [badgeBounding, setBadgeBounding] = useState() - const { active, placement } = useControlProfileCard(holderRef) - const [address, setAddress] = useState('') - const [anchorEl, setAnchorEl] = useState(null) - - useEffect(() => { - return CrossIsolationMessages.events.profileCardEvent.on((event) => { - if (!event.open) return - setAddress(event.address ?? '') - setTwitterId(event.userId) - setBadgeBounding(event.anchorBounding) - setAnchorEl(event.anchorEl) - }) - }, []) - - const { data: identity } = useQuery({ - queryKey: ['twitter', 'profile', twitterId], - queryFn: () => Twitter.getUserByScreenName(twitterId), - select: (user: TwitterBaseAPI.User | null) => { - if (!user) return null - return { - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, user.screenName).unwrapOr(undefined), - nickname: user.nickname, - avatar: user.avatarURL, - bio: user.bio, - homepage: user.homepage, - } as SocialIdentity - }, - }) - - const { data: resolvedIdentity } = useSocialIdentity(identity) - - const popperOptions = useMemo(() => { - return { - modifiers: [ - { - name: 'detect-glitch', - enabled: true, - phase: 'beforeRead', - fn: (options: any) => { - const reference = options.state.rects?.reference - if (!reference) return - if (!reference.height && !reference.width) { - setAnchorEl(null) - } - }, - }, - ], - } - }, []) - - return ( - - - - - - - - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/useControlProfileCard.ts b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/useControlProfileCard.ts deleted file mode 100644 index a9c43e2ee0f1..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCard/useControlProfileCard.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { CrossIsolationMessages } from '@masknet/shared-base' -import { useDialogStacking } from '@masknet/theme' -import type { PopperPlacementType } from '@mui/material' -import { useCallback, useEffect, useRef, useState, type RefObject } from 'react' -import { CARD_HEIGHT } from './constants.js' - -interface Result { - active: boolean - placement: PopperPlacementType -} - -const LEAVE_DURATION = 500 - -export function useControlProfileCard(holderRef: RefObject): Result { - const hoverRef = useRef(false) - const closeTimerRef = useRef>() - const skipClick = useRef(false) - - const [active, setActive] = useState(false) - const [placement, setPlacement] = useState('bottom') - const hasDialogRef = useRef(false) - const { stack } = useDialogStacking() - hasDialogRef.current = stack.length > 0 - - const hideProfileCard = useCallback((byClick?: boolean) => { - if (hoverRef.current || hasDialogRef.current) return - clearTimeout(closeTimerRef.current) - closeTimerRef.current = setTimeout(() => { - // Discard the click that would open from external - if (byClick && skipClick.current) { - skipClick.current = false - return - } - setActive(false) - }, LEAVE_DURATION) - }, []) - - const showProfileCard = useCallback((placement: PopperPlacementType) => { - clearTimeout(closeTimerRef.current) - setActive(true) - setPlacement(placement) - }, []) - useEffect(() => { - const holder = holderRef.current - if (!holder) { - hideProfileCard() - return - } - const enter = () => { - hoverRef.current = true - clearTimeout(closeTimerRef.current) - } - const leave = () => { - hoverRef.current = false - hideProfileCard() - } - holder.addEventListener('mouseenter', enter) - holder.addEventListener('mouseleave', leave) - return () => { - holder.removeEventListener('mouseenter', enter) - holder.removeEventListener('mouseleave', leave) - } - }, [holderRef.current]) - - useEffect(() => { - return CrossIsolationMessages.events.profileCardEvent.on((event) => { - if (!event.open) { - hideProfileCard() - return - } - if (event.external) skipClick.current = true - const reachedBottom = event.anchorBounding.bottom + CARD_HEIGHT > window.innerHeight - - showProfileCard(reachedBottom ? 'auto' : 'bottom') - }) - }, []) - - useEffect(() => { - const onClick = (event: MouseEvent) => { - // `NODE.contains(other)` doesn't work for cross multiple layer of Shadow DOM - if (event.composedPath()?.includes(holderRef.current!)) return - hoverRef.current = false - hideProfileCard(true) - } - document.body.addEventListener('click', onClick) - return () => { - document.body.removeEventListener('click', onClick) - } - }, []) - - return { placement, active } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCover.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCover.tsx deleted file mode 100644 index 4660102bad54..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileCover.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { searchProfileCoverSelector } from '../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { ProfileCover } from '../../../components/InjectedComponents/ProfileCover.js' - -export function injectProfileCover(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileCoverSelector()) - startWatch(watcher, { - signal, - }) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -function ProfileCoverAtTwitter() { - return -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTab.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTab.tsx deleted file mode 100644 index 08f4394014f9..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTab.tsx +++ /dev/null @@ -1,414 +0,0 @@ -import Services from '#services' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { useCollectionByTwitterHandle } from '@masknet/shared' -import { BooleanPreference, MaskMessages, PluginID, ProfileTabs } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useSnapshotSpacesByTwitterHandle } from '@masknet/web3-hooks-base' -import type { FungibleTokenResult, NonFungibleCollectionResult } from '@masknet/web3-shared-base' -import Color from 'color' -import { useEffect, useRef, useState } from 'react' -import { useAsync, useWindowSize } from 'react-use' -import { useCurrentVisitingIdentity } from '../../../components/DataSource/useActivatedUI.js' -import { ProfileTab } from '../../../components/InjectedComponents/ProfileTab.js' -import { untilElementAvailable } from '../../../utils/index.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root.js' -import { startWatch } from '../../../utils/startWatch.js' -import { - nextTabListSelector, - searchAppBarBackSelector, - searchNameTag, - searchNewTweetButtonSelector, - searchProfileEmptySelector, - searchProfileTabListLastChildSelector, - searchProfileTabListSelector, - searchProfileTabLoseConnectionPageSelector, - searchProfileTabPageSelector, - searchProfileTabSelector, -} from '../utils/selector.js' - -function getStyleProps() { - const EMPTY_STYLE = {} as CSSStyleDeclaration - const eleTab = searchProfileTabSelector().evaluate()?.querySelector('div > div') - const style = eleTab ? window.getComputedStyle(eleTab) : EMPTY_STYLE - const paddingEle = searchProfileTabSelector().evaluate() - const paddingCss = paddingEle ? window.getComputedStyle(paddingEle) : EMPTY_STYLE - const eleNewTweetButton = searchNewTweetButtonSelector().evaluate() - const newTweetButtonColorStyle = eleNewTweetButton ? window.getComputedStyle(eleNewTweetButton) : EMPTY_STYLE - const eleBackButton = searchAppBarBackSelector().evaluate() - const backButtonColorStyle = eleBackButton ? window.getComputedStyle(eleBackButton) : EMPTY_STYLE - - return { - color: style.color, - font: style.font, - fontSize: style.fontSize, - padding: style.paddingBottom, - paddingX: paddingCss.paddingLeft || '16px', - height: style.height || '53px', - hover: backButtonColorStyle.color, - line: newTweetButtonColorStyle.backgroundColor, - } -} - -const useStyles = makeStyles<{ minWidth?: number }>()((theme, { minWidth }) => { - const props = getStyleProps() - return { - root: { - '&:hover': { - backgroundColor: new Color(props.hover).alpha(0.1).toString(), - cursor: 'pointer', - }, - height: props.height, - display: 'inline-block', - }, - button: { - zIndex: 1, - position: 'relative', - display: 'flex', - minWidth: minWidth ?? 56, - justifyContent: 'center', - alignItems: 'center', - textAlign: 'center', - paddingLeft: props.paddingX, - paddingRight: props.paddingX, - color: props.color, - font: props.font, - fontSize: props.fontSize, - fontWeight: 500, - '&:hover': { - color: props.color, - }, - height: props.height, - }, - selected: { - color: `${props.hover} !important`, - fontWeight: 700, - }, - line: { - borderRadius: 9999, - position: 'absolute', - bottom: 0, - minWidth: 56, - alignSelf: 'center', - height: 4, - backgroundColor: props.line, - }, - bar: { - display: 'flex', - zIndex: 0, - position: 'relative', - minWidth: 56, - }, - } -}) - -function nameTagClickHandler() { - MaskMessages.events.profileTabUpdated.sendToLocal({ show: false }) - MaskMessages.events.profileTabActive.sendToLocal({ active: false }) - const nameTag = searchNameTag().evaluate() - if (nameTag) nameTag.style.display = '' - - const eleEmpty = searchProfileEmptySelector().evaluate() - if (eleEmpty) eleEmpty.style.display = 'none' - - const elePage = searchProfileTabPageSelector().evaluate() - if (elePage) { - elePage.style.visibility = 'hidden' - elePage.style.height = 'auto' - } -} - -function tabClickHandler() { - MaskMessages.events.profileTabUpdated.sendToLocal({ show: false }) - MaskMessages.events.profileTabActive.sendToLocal({ active: false }) - - resetTwitterActivatedContent() -} - -// How do we control color of the tab? -// -//
-//
<~~ tab container, Twitter defined -// <~~ tab label, set #yyy to override, unset to reset -// -// Tab container has Twitter's own defined color -// We override the color by setting color to tab label, and removing color from -// tab label when reset tab color - -async function hideTwitterActivatedContent() { - const eleTab = searchProfileTabSelector().evaluate()?.querySelector('div > div') - const loseConnectionEle = searchProfileTabLoseConnectionPageSelector().evaluate() - if (!eleTab) return - const style = window.getComputedStyle(eleTab) - // hide the activated indicator - const tabList = searchProfileTabListSelector().evaluate() - tabList.map((tab) => { - const tabLabel = tab.querySelector('div > div > span') - if (tabLabel) tabLabel.style.color = style.color - - const indicator = tab.querySelector('div > div > div') - if (indicator) indicator.style.display = 'none' - tab.addEventListener('click', tab.closest('#open-nft-button') ? nameTagClickHandler : tabClickHandler) - }) - - if (loseConnectionEle) return - - // hide the empty list indicator on the page - const eleEmpty = searchProfileEmptySelector().evaluate() - if (eleEmpty) eleEmpty.style.display = 'none' - - const nameTag = searchNameTag().evaluate() - if (nameTag) nameTag.style.display = 'none' - - // hide the content page - await untilElementAvailable(searchProfileTabPageSelector()) - - const elePage = searchProfileTabPageSelector().evaluate() - if (elePage) { - elePage.style.visibility = 'hidden' - elePage.style.height = 'auto' - } -} - -function resetTwitterActivatedContent() { - const eleTab = searchProfileTabSelector().evaluate()?.querySelector('div > div') - const loseConnectionEle = searchProfileTabLoseConnectionPageSelector().evaluate() - if (!eleTab) return - - const tabList = searchProfileTabListSelector().evaluate() - tabList.map((tab) => { - const tabLabel = tab.querySelector('div > div > span') - if (tabLabel) tabLabel.style.color = '' - const indicator = tab.querySelector('div > div > div') - if (indicator) indicator.style.display = '' - tab.removeEventListener('click', tab.closest('#open-nft-button') ? nameTagClickHandler : tabClickHandler) - }) - - if (loseConnectionEle) return - - const eleEmpty = searchProfileEmptySelector().evaluate() - if (eleEmpty) eleEmpty.style.display = '' - - const elePage = searchProfileTabPageSelector().evaluate() - if (elePage) { - elePage.style.visibility = 'visible' - elePage.style.height = 'auto' - } -} - -function ProfileTabForTokenAndPersona() { - const [hidden, setHidden] = useState(false) - const currentVisitingSocialIdentity = useCurrentVisitingIdentity() - const currentVisitingUserId = currentVisitingSocialIdentity?.identifier?.userId - const collectionList = useCollectionByTwitterHandle(currentVisitingUserId) - const collectionResult = collectionList?.[0] - const twitterHandle = - (collectionResult as NonFungibleCollectionResult)?.collection - ?.socialLinks?.twitter || - (collectionResult as FungibleTokenResult)?.socialLinks?.twitter - const { classes } = useStyles({ - minWidth: - currentVisitingUserId && twitterHandle?.toLowerCase().endsWith(currentVisitingUserId.toLowerCase()) ? - 0 - : 56, - }) - useEffect(() => { - return MaskMessages.events.profileTabHidden.on((data) => { - setHidden(data.hidden) - }) - }, []) - - return hidden ? null : ( - } - /> - ) -} - -function ProfileTabForDAO() { - const currentVisitingSocialIdentity = useCurrentVisitingIdentity() - const currentVisitingUserId = currentVisitingSocialIdentity?.identifier?.userId ?? '' - const { data: spaceList, isPending } = useSnapshotSpacesByTwitterHandle(currentVisitingUserId) - - const { value: snapshotDisabled } = useAsync(() => { - return Services.Settings.getPluginMinimalModeEnabled(PluginID.Snapshot) - }, []) - - const [hidden, setHidden] = useState(snapshotDisabled === BooleanPreference.True) - const { classes } = useStyles({ minWidth: hidden ? 56 : 0 }) - useEffect(() => { - return MaskMessages.events.profileTabHidden.on((data) => { - setHidden(data.hidden) - }) - }, []) - - return hidden || isPending || !spaceList?.length ? - null - : } - /> -} - -export function injectProfileTabAtTwitter(signal: AbortSignal) { - let tabInjected = false - const contentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => { - const elePage = searchProfileTabPageSelector().evaluate() - if (elePage && !tabInjected) { - const watcher = new MutationObserverWatcher(searchProfileTabListLastChildSelector()) - startWatch(watcher, { - signal, - shadowRootDelegatesFocus: false, - }) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() - tabInjected = true - } - }) - - startWatch(contentWatcher, { - signal, - shadowRootDelegatesFocus: false, - }) -} - -function showNextArrow() { - const next = nextTabListSelector().evaluate() - if (!next) return - - next.style.setProperty('pointer-events', 'auto', 'important') - next.style.opacity = '1' - - const first = next.firstElementChild as HTMLDivElement - if (!first) return - first.style.backgroundColor = 'rgba(39, 44, 48, 0.75)' - first.style.opacity = '1' - const svg = next.querySelector('svg') - if (!svg) return - svg.style.color = 'rgb(255, 255, 255)' -} - -function hiddenNextArrow() { - const next = nextTabListSelector().evaluate() - if (!next) return - next.style.removeProperty('opacity') - next.style.removeProperty('pointer-events') - - const first = next.firstElementChild as HTMLDivElement - if (!first) return - first.style.backgroundColor = 'rgba(15, 20, 25, 0.75)' - first.style.removeProperty('opacity') - const svg = next.querySelector('svg') - if (!svg) return - svg.style.removeProperty('color') -} - -function InjectProfileTab() { - const web3TabRef = useRef(null) - const { classes } = useStyles({ minWidth: 56 }) - const windowSize = useWindowSize() - const timeoutRef = useRef() - const [isClick, setIsClick] = useState(false) - - function onMouseEnter() { - if (isClick) return - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - timeoutRef.current = null - } - const parent = searchProfileTabListLastChildSelector().closest(1).evaluate() - if (!parent || !web3TabRef.current) return - if (Math.abs(parent.scrollWidth - (parent.scrollLeft + parent.clientWidth)) < 10) return - if (parent.clientWidth < parent.scrollWidth) { - showNextArrow() - } - } - - function onNextClick() { - const nextArrow = nextTabListSelector().evaluate() - if (!nextArrow) return - nextArrow.style.removeProperty('cursor') - setIsClick(true) - hiddenNextArrow() - if (timeoutRef.current) clearTimeout(timeoutRef.current) - timeoutRef.current = null - } - - function onMouseLeave() { - if (!timeoutRef.current) timeoutRef.current = setTimeout(hiddenNextArrow, 500) - setIsClick(false) - } - - function onEnterNextArrow() { - if (isClick) return - const nextArrow = nextTabListSelector().evaluate() - if (!nextArrow) return - nextArrow.style.cursor = 'pointer' - if (timeoutRef.current) clearTimeout(timeoutRef.current) - timeoutRef.current = null - - showNextArrow() - } - - function onLeaveNextArrow() { - const nextArrow = nextTabListSelector().evaluate() - if (!nextArrow) return - nextArrow.style.removeProperty('cursor') - onMouseLeave() - } - - const tabList = searchProfileTabListSelector().evaluate() - const nextArrow = nextTabListSelector().evaluate() - useEffect(() => { - web3TabRef.current?.addEventListener('mouseenter', onMouseEnter) - web3TabRef.current?.addEventListener('mouseleave', onMouseLeave) - nextArrow?.addEventListener('click', onNextClick) - nextArrow?.addEventListener('mouseenter', onEnterNextArrow) - nextArrow?.addEventListener('mouseleave', onLeaveNextArrow) - tabList.map((v) => { - v.closest('div')?.addEventListener('mouseenter', onMouseEnter) - v.closest('div')?.addEventListener('mouseleave', onMouseLeave) - }) - return () => { - web3TabRef.current?.removeEventListener('mouseenter', onMouseEnter) - web3TabRef.current?.removeEventListener('mouseleave', onMouseLeave) - nextArrow?.removeEventListener('click', onNextClick) - nextArrow?.removeEventListener('mouseenter', onEnterNextArrow) - nextArrow?.removeEventListener('mouseleave', onLeaveNextArrow) - tabList.map((v) => { - v.closest('div')?.removeEventListener('mouseenter', onMouseEnter) - v.closest('div')?.removeEventListener('mouseleave', onMouseLeave) - }) - } - }, [windowSize, tabList, web3TabRef.current, nextArrow]) - - return ( -
- - -
- ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTabContent.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTabContent.tsx deleted file mode 100644 index d238534a019e..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ProfileTabContent.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { MaskMessages } from '@masknet/shared-base' -import { getMaskColor, makeStyles } from '@masknet/theme' -import { memo } from 'react' -import { ProfileTabContent } from '../../../components/InjectedComponents/ProfileTabContent.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { - searchNewTweetButtonSelector, - searchProfileTabLoseConnectionPageSelector, - searchProfileTabPageSelector, -} from '../utils/selector.js' - -function injectProfileTabContentState(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchProfileTabPageSelector()) - startWatch(watcher, { signal, shadowRootDelegatesFocus: false }) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -export function injectProfileTabContentAtTwitter(signal: AbortSignal) { - const lostConnectionContentWatcher = new MutationObserverWatcher( - searchProfileTabLoseConnectionPageSelector(), - ).useForeach(() => MaskMessages.events.profileTabHidden.sendToLocal({ hidden: true })) - - const contentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => - MaskMessages.events.profileTabHidden.sendToLocal({ hidden: false }), - ) - - startWatch(lostConnectionContentWatcher, { signal, shadowRootDelegatesFocus: false }) - startWatch(contentWatcher, { signal, shadowRootDelegatesFocus: false }) - - injectProfileTabContentState(signal) -} - -function getStyleProps() { - const newTweetButton = searchNewTweetButtonSelector().evaluate() - return { - backgroundColor: newTweetButton ? window.getComputedStyle(newTweetButton).backgroundColor : undefined, - fontFamily: - newTweetButton?.firstChild ? - window.getComputedStyle(newTweetButton.firstChild as HTMLElement).fontFamily - : undefined, - } -} - -const useStyles = makeStyles()((theme) => { - const props = getStyleProps() - - return { - holder: { - position: 'relative', - }, - root: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - zIndex: 1, - }, - text: { - paddingTop: 29, - paddingBottom: 29, - '& > p': { - fontSize: 28, - fontFamily: props.fontFamily, - fontWeight: 700, - color: getMaskColor(theme).textPrimary, - }, - }, - button: { - backgroundColor: props.backgroundColor, - color: 'white', - marginTop: 18, - '&:hover': { - backgroundColor: props.backgroundColor, - }, - }, - } -}) - -interface Props { - floating?: boolean -} -const ProfileTabContentAtTwitter = memo(function ProfileTabContentAtTwitter({ floating }: Props) { - const { classes } = useStyles() - const content = ( - - ) - // If it's floating, for example being attached to emptyState timeline, we - // can fix the position by putting it in a stacking context. - return floating ?
{content}
: content -}) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/SearchResultInspector.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/SearchResultInspector.tsx deleted file mode 100644 index 073a2694e381..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/SearchResultInspector.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { SearchResultInspector } from '../../../components/InjectedComponents/SearchResultInspector.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../utils/startWatch.js' -import { searchResultHeadingSelector } from '../utils/selector.js' - -export function injectSearchResultInspectorAtTwitter(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(searchResultHeadingSelector()) - startWatch(watcher, { - signal, - }) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/SwitchLogo.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/SwitchLogo.tsx deleted file mode 100644 index 38d1f81900a7..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/SwitchLogo.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { SwitchLogoButton } from '@masknet/plugin-switch-logo' -import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' -import { querySelector } from '../utils/selector.js' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' - -const logoSelector: () => LiveSelector = () => { - return querySelector('h1[role="heading"] a > div > svg').closest(1) -} - -export function injectSwitchLogoButton(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(logoSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( - , - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx deleted file mode 100644 index 8e74831af76f..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { noop } from 'lodash-es' -import { Flags } from '@masknet/flags' -import { makeStyles } from '@masknet/theme' -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { useThemeSettings } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { TipButtonStyle } from '../../constant.js' -import { normalFollowButtonSelector as selector } from '../../utils/selector.js' -import { isVerifiedUser } from '../../utils/AvatarType.js' -import { useUserIdentity } from './hooks.js' - -function getUserId(ele: HTMLElement) { - const profileLink = ele.closest('[data-testid="UserCell"]')?.querySelector('a[role="link"]') - if (!profileLink) return - return profileLink.getAttribute('href')?.slice(1) -} - -export function injectTipsButtonOnFollowButton(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch( - watcher.useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const userId = getUserId(ele) - if (!userId) return - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele - - const isVerified = isVerifiedUser(ele) - - const root = attachReactTreeWithContainer(proxy.beforeShadow, { signal }) - root.render(isVerified ? :
) - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} - -const useStyles = makeStyles()(() => ({ - disabled: { - display: 'none', - }, - slot: { - height: 36, - width: 36, - position: 'absolute', - left: -10, - top: 1, - transform: 'translate(-100%)', - }, -})) - -interface Props { - userId: string -} - -const FollowButtonTipsSlot = memo(function FollowButtonTipsSlot({ userId }: Props) { - const themeSetting = useThemeSettings() - const tipStyle = TipButtonStyle[themeSetting.size] - const { classes, cx } = useStyles() - const identity = useUserIdentity(userId) - - const [disabled, setDisabled] = useState(true) - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, - ) - return ( - - ) - }, [identity?.identifier, tipStyle.buttonSize, tipStyle.iconSize]) - - if (!identity?.identifier) return null - - return {component} -}) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx deleted file mode 100644 index 1de56b0372a6..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Flags } from '@masknet/flags' -import { Plugin, createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { noop } from 'lodash-es' -import { memo, useMemo, useState } from 'react' -import { useThemeSettings } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { TipButtonStyle } from '../../constant.js' -import { querySelectorAll } from '../../utils/selector.js' -import { useUserIdentity } from './hooks.js' - -function postShareButtonSelector() { - return querySelectorAll('article[data-testid="tweet"] [role="group"] > div:has([aria-haspopup="menu"]):last-child') -} - -function getUserId(ele: HTMLElement) { - const avatar = ele - .closest('[data-testid="tweet"]') - ?.querySelector('[data-testid^="UserAvatar-Container-"]') - if (!avatar) return - return avatar.dataset.testid?.slice(21) // "UserAvatar-Container-".length === 21 -} - -function createRootElement() { - const root = document.createElement('div') - Object.assign(root.style, { - height: '100%', - display: 'flex', - alignItems: 'center', - }) - return root -} - -export function injectTipsButtonOnPost(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(postShareButtonSelector()) - startWatch( - watcher.useForeach((ele) => { - let remover = noop - const remove = () => remover() - - const run = async () => { - const userId = getUserId(ele) - if (!userId) return - const proxy = DOMProxy({ - afterShadowRootInit: Flags.shadowRootInit, - }) - proxy.realCurrent = ele - ele.style.flex = '1' - - const root = attachReactTreeWithContainer(proxy.afterShadow, { - signal, - tag: createRootElement, - }) - root.render() - remover = root.destroy - } - - run() - return { - onNodeMutation: run, - onTargetChanged: run, - onRemove: remove, - } - }), - signal, - ) -} - -const useStyles = makeStyles()(() => ({ - disabled: { - display: 'none', - }, -})) - -interface Props { - userId: string -} - -const PostTipsSlot = memo(function PostTipsSlot({ userId }: Props) { - const themeSetting = useThemeSettings() - const tipStyle = TipButtonStyle[themeSetting.size] - const { classes } = useStyles() - const identity = useUserIdentity(userId) - - const [disabled, setDisabled] = useState(true) - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, - ) - return ( - - ) - }, [identity?.identifier, tipStyle.buttonSize, tipStyle.iconSize]) - - if (!identity?.identifier) return null - - return {component} -}) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx deleted file mode 100644 index 5b08b57b98b7..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { useCurrentVisitingIdentity, useThemeSettings } from '../../../../components/DataSource/useActivatedUI.js' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { ButtonStyle } from '../../constant.js' -import { profileFollowButtonSelector as selector } from '../../utils/selector.js' - -export function injectOpenTipsButtonOnProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} - -interface StyleProps { - size: number - marginBottom: number -} -const useStyles = makeStyles()((theme, props) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - height: props.size, - width: props.size, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginRight: theme.spacing(1), - marginBottom: props.marginBottom, - verticalAlign: 'top', - }, -})) - -function ProfileTipsSlot() { - const visitingPersona = useCurrentVisitingIdentity() - const themeSettings = useThemeSettings() - const buttonStyle = ButtonStyle[themeSettings.size] - const { classes, cx } = useStyles({ size: buttonStyle.buttonSize, marginBottom: buttonStyle.marginBottom }) - const [disabled, setDisabled] = useState(true) - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.TipsRealm?.UI?.Content, - ) - - return ( - - ) - }, [visitingPersona.identifier, buttonStyle.buttonSize, buttonStyle.iconSize]) - - if (!component || !visitingPersona.identifier) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/hooks.ts b/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/hooks.ts deleted file mode 100644 index 1b77f3ec129c..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/hooks.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { getUserIdentity } from '../../utils/user.js' - -export function useUserIdentity(userId: string) { - const { data: identity } = useQuery({ - queryKey: ['get-user-identity', userId], - queryFn: () => getUserIdentity(userId), - }) - return identity -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/index.tsx deleted file mode 100644 index fdc530d9445f..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Tips/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { injectOpenTipsButtonOnProfile } from './ProfileTipsButton.js' -import { injectTipsButtonOnFollowButton } from './FollowTipsButton.js' -import { injectTipsButtonOnPost } from './PostTipsButton.js' - -export function injectTips(signal: AbortSignal) { - injectOpenTipsButtonOnProfile(signal) - injectTipsButtonOnFollowButton(signal) - injectTipsButtonOnPost(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint.tsx deleted file mode 100644 index 1e2b7fd530c3..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { ValueRef } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' -import { RootWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { startWatch } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelector, sideBarProfileSelector } from '../utils/selector.js' -import { ProfileLinkAtTwitter, ToolboxHintAtTwitter } from './ToolboxHint_UI.js' - -const SideBarNativeItemTextMarginLeftRef = new ValueRef('20px') -const SideBarNativeItemIconSize = new ValueRef('24px') -const SideBarNativeItemPaddingRef = new ValueRef('11px') - -function toolboxInSidebarSelector() { - // Organization account don't have a [data-testid=AppTabBar_More_Menu] in page. see MF-3866 - return querySelector('[role="banner"] nav[role="navigation"] > div[data-testid=AppTabBar_More_Menu]') -} - -export function injectToolboxHintAtTwitter(signal: AbortSignal, category: 'wallet' | 'application') { - const watcher = new MutationObserverWatcher(toolboxInSidebarSelector()) - .addListener('onAdd', updateStyle) - .addListener('onChange', updateStyle) - - startWatch(watcher, { - signal, - }) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render( - - - , - ) - injectProfile(signal) -} - -function updateStyle() { - const SideBarNativeItem = document.querySelector('[role="banner"] [role="navigation"] > div > div') - const SideBarNativeItemText = document.querySelector( - '[role="banner"] [role="navigation"] > div > div > div[dir="auto"]', - ) - const SideBarNativeItemIcon = document.querySelector( - '[role="banner"] [role="navigation"] > div > div > div:first-child', - ) - const SideBarNativeItemStyle = SideBarNativeItem ? window.getComputedStyle(SideBarNativeItem) : null - const SideBarNativeItemTextStyle = SideBarNativeItemText ? window.getComputedStyle(SideBarNativeItemText) : null - const SideBarNativeItemIconStyle = SideBarNativeItemIcon ? window.getComputedStyle(SideBarNativeItemIcon) : null - SideBarNativeItemPaddingRef.value = SideBarNativeItemStyle?.padding ?? '11px' - SideBarNativeItemIconSize.value = SideBarNativeItemIconStyle?.width ?? '24px' - SideBarNativeItemTextMarginLeftRef.value = SideBarNativeItemTextStyle?.marginLeft ?? '20px' -} -export function useSideBarNativeItemStyleVariants() { - return { - textMarginLeft: useValueRef(SideBarNativeItemTextMarginLeftRef), - itemPadding: useValueRef(SideBarNativeItemPaddingRef), - iconSize: useValueRef(SideBarNativeItemIconSize), - } -} - -function injectProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(sideBarProfileSelector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.beforeShadow, { signal }).render() -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint_UI.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint_UI.tsx deleted file mode 100644 index f42e3cb4306d..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/ToolboxHint_UI.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useEffect, useMemo, useState } from 'react' -import { styled, ListItemButton, Typography, ListItemIcon, Box, useMediaQuery } from '@mui/material' -import { ToolboxHintUnstyled } from '../../../components/InjectedComponents/ToolboxUnstyled.js' -import { useSideBarNativeItemStyleVariants } from './ToolboxHint.js' -import GuideStep from '../../../components/GuideStep/index.js' -import { useMaskSharedTrans } from '../../../../shared-ui/index.js' -import { useThemeSettings } from '../../../components/DataSource/useActivatedUI.js' -import { searchHomeLinkName } from '../utils/selector.js' -import { ButtonStyle } from '../constant.js' - -const HORIZONTAL_BREAKPOINT = 1265 -const VERTICAL_BREAKPOINT = 855 - -const Container = styled('div')` - cursor: pointer; - padding: 4px 0; - @media screen and (max-height: ${VERTICAL_BREAKPOINT}px) { - padding: 0; - } -` -const ListItem = styled(ListItemButton)` - border-radius: 9999px; - display: inline-flex; - &:hover { - background: rgba(15, 20, 25, 0.1); - ${({ theme }) => (theme.palette.mode === 'dark' ? 'background: rgba(217, 217, 217, 0.1);' : '')} - } - /* twitter break point */ - @media screen and (max-width: ${HORIZONTAL_BREAKPOINT}px) { - height: 50px; - } -` -const Text = styled(Typography)` - margin-right: 16px; - font-family: inherit; - font-weight: 400; - white-space: nowrap; - color: ${({ theme }) => (theme.palette.mode === 'light' ? 'rgb(15, 20, 25)' : 'rgb(216, 216, 216)')}; -` -const Icon = styled(ListItemIcon)` - color: ${({ theme }) => (theme.palette.mode === 'light' ? 'rgb(15, 20, 25)' : 'rgb(216, 216, 216)')}; - min-width: 0; -` - -export function ToolboxHintAtTwitter(props: { category: 'wallet' | 'application' }) { - const { textMarginLeft, itemPadding, iconSize } = useSideBarNativeItemStyleVariants() - const themeSettings = useThemeSettings() - const buttonStyle = ButtonStyle[themeSettings.size] - const Typography = useMemo(() => { - return ({ children }: React.PropsWithChildren<{}>) => ( - - {children} - - ) - }, [buttonStyle.iconSize, textMarginLeft]) - const _mini = useMediaQuery(`(max-width: ${HORIZONTAL_BREAKPOINT}px)`) - const [mini, setMini] = useState(_mini) - - useEffect(() => { - const searchHomeLinkNameNode = searchHomeLinkName().evaluate() - - if (!searchHomeLinkNameNode) return - - const observer = new MutationObserver((mutations) => { - setMini(!searchHomeLinkName().querySelector('span').evaluate()) - }) - - observer.observe(searchHomeLinkNameNode, { - subtree: true, - childList: true, - }) - - return () => observer.disconnect() - }, []) - - const ListItemButton = useMemo(() => { - return ( - props: React.PropsWithChildren<{ - onClick?: React.MouseEventHandler - }>, - ) => ( - - {props.children} - - ) - }, [itemPadding]) - return ( - - ) -} - -export function ProfileLinkAtTwitter() { - const t = useMaskSharedTrans() - - return ( - <> - - - - - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/inject.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/inject.tsx deleted file mode 100644 index 9497f6a43b94..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/inject.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { injectPostDialogAtTwitter } from './PostDialog.js' -import { injectPostDialogHintAtTwitter } from './PostDialogHint.js' - -export function injectPostBoxComposed(signal: AbortSignal) { - injectPostDialogAtTwitter(signal) - injectPostDialogHintAtTwitter(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/en-US.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/en-US.json deleted file mode 100644 index f5878a6e8748..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/en-US.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "additional_post_box__encrypted_post_pre": "This tweet is encrypted with #mask_io (@realMaskNetwork). 📪🔑\n\n🎭 🎭🎭 Tired of plaintext? Try to send encrypted messages to your friends. Install {{- encrypted}} to send your first encrypted tweet." -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/index.ts b/packages/mask/content-script/site-adaptors/twitter.com/locales/index.ts deleted file mode 100644 index 5d88b2e093aa..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file is auto generated. DO NOT EDIT -// Run `npx gulp sync-languages` to regenerate. -// Default fallback language in a family of languages are chosen by the alphabet order -// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts - -export * from './i18n_generated.js' diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/ja-JP.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/ja-JP.json deleted file mode 100644 index 0967ef424bce..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/ja-JP.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/ko-KR.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/ko-KR.json deleted file mode 100644 index fe28a1504a95..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/ko-KR.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "additional_post_box__encrypted_post_pre": "이 트윗은 이미 #mask_io (@realMaskNetwork)로 암호화되었습니다. 📪🔑\n🎭 🎭🎭 {{- encrypted}} 설치하여 친구에게 암호화 트윗을 보내 보세요!" -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/languages.ts b/packages/mask/content-script/site-adaptors/twitter.com/locales/languages.ts deleted file mode 100644 index bd864a8274a5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/languages.ts +++ /dev/null @@ -1,33 +0,0 @@ -// This file is auto generated. DO NOT EDIT -// Run `npx gulp sync-languages` to regenerate. -// Default fallback language in a family of languages are chosen by the alphabet order -// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts -import en_US from './en-US.json' -import ja_JP from './ja-JP.json' -import ko_KR from './ko-KR.json' -import qya_AA from './qya-AA.json' -import zh_CN from './zh-CN.json' -import zh_TW from './zh-TW.json' -export const languages = { - en: en_US, - ja: ja_JP, - ko: ko_KR, - qy: qya_AA, - 'zh-CN': zh_CN, - zh: zh_TW, -} -import { createI18NBundle } from '@masknet/shared-base' -export const addDO_NOT_USEI18N = createI18NBundle('DO_NOT_USE', languages) -// @ts-ignore -if (import.meta.webpackHot) { - // @ts-ignore - import.meta.webpackHot.accept( - ['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'], - () => - globalThis.dispatchEvent?.( - new CustomEvent('MASK_I18N_HMR', { - detail: ['DO_NOT_USE', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }], - }), - ), - ) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/qya-AA.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/qya-AA.json deleted file mode 100644 index da51658f5494..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/qya-AA.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "additional_post_box__encrypted_post_pre": "crwdns18524:0{{encrypted}}crwdne18524:0" -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-CN.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-CN.json deleted file mode 100644 index b1b4389a1b8b..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-CN.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "additional_post_box__encrypted_post_pre": "此推特使用 #mask_io (@realMaskNetwork) 加密。 📪🔑\n\n🎭 🎭🎭 厌烦了纯文本?尝试向你的朋友发送加密消息。安装 {{- encrypted}} 以发送你第一个加密推文。" -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-TW.json b/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-TW.json deleted file mode 100644 index 0967ef424bce..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/locales/zh-TW.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/shared.ts b/packages/mask/content-script/site-adaptors/twitter.com/shared.ts deleted file mode 100644 index dfb58eaff1f4..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/shared.ts +++ /dev/null @@ -1,58 +0,0 @@ -import urlcat from 'urlcat' -import { type PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { openWindow } from '@masknet/shared-base-ui' -import type { SiteAdaptor } from '@masknet/types' -import { createSiteAdaptorSpecializedPostContext } from '../../site-adaptor-infra/utils/create-post-context.js' -import { hasPayloadLike } from '../../utils/index.js' -import { twitterBase } from './base.js' -import { TwitterDecoder } from '@masknet/encryption' -import { getUserIdentity, usernameValidator } from './utils/user.js' - -function getPostURL(post: PostIdentifier): URL | null { - if (!(post.identifier instanceof ProfileIdentifier)) return null - return new URL(`https://twitter.com/${post.identifier.userId}/status/${post.postId}`) -} -function getProfileURL(profile: ProfileIdentifier): URL | null { - return new URL(`https://twitter.com/${profile.userId}`) -} -function getShareURL(text: string): URL | null { - return new URL(urlcat('https://twitter.com/intent/tweet', { text })) -} -export const twitterShared: SiteAdaptor.Shared & SiteAdaptor.Base = { - ...twitterBase, - utils: { - isValidUsername: usernameValidator, - getPostURL, - getProfileURL, - getShareURL, - share(text) { - const url = getShareURL(text) - const width = 700 - const height = 520 - const openedWindow = openWindow(url, 'share', { - width, - height, - screenX: window.screenX + (window.innerWidth - width) / 2, - screenY: window.screenY + (window.innerHeight - height) / 2, - opener: true, - referrer: true, - behaviors: { - toolbar: true, - status: true, - resizable: true, - scrollbars: true, - }, - }) - if (openedWindow === null && url) { - location.assign(url) - } - }, - createPostContext: createSiteAdaptorSpecializedPostContext(twitterBase.networkIdentifier, { - hasPayloadLike: (text) => { - return TwitterDecoder(text).map(hasPayloadLike).unwrapOr(false) - }, - getURLFromPostIdentifier: getPostURL, - }), - getUserIdentity, - }, -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts b/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts deleted file mode 100644 index bfb8b4460695..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* eslint-disable tss-unused-classes/unused-classes */ -import type { SiteAdaptorUI } from '@masknet/types' -import { EnhanceableSite, NextIDPlatform, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { FontSize, ThemeColor, ThemeMode } from '@masknet/web3-shared-base' -import { activatedSiteAdaptor_state, stateCreator } from '../../site-adaptor-infra/index.js' -import { twitterBase } from './base.js' -import getSearchedKeywordAtTwitter from './collecting/getSearchedKeyword.js' -import { twitterShared } from './shared.js' -import { InitAutonomousStateProfiles } from '../../site-adaptor-infra/defaults/state/InitProfiles.js' -import { openComposeBoxTwitter } from './automation/openComposeBox.js' -import { pasteTextToCompositionTwitter } from './automation/pasteTextToComposition.js' -import { pasteImageToCompositionTwitter } from './automation/pasteImageToComposition.js' -import { gotoNewsFeedPageTwitter } from './automation/gotoNewsFeedPage.js' -import { gotoProfilePageTwitter } from './automation/gotoProfilePage.js' -import { publishPostTwitter } from './automation/publishPost.js' -import { IdentityProviderTwitter, CurrentVisitingIdentityProviderTwitter } from './collecting/identity.js' -import { ThemeSettingsProviderTwitter } from './collecting/theme.js' -import { collectVerificationPost, PostProviderTwitter, getPostIdFromNewPostToast } from './collecting/post.js' -import { useThemeTwitterVariant } from './customization/custom.js' -import { injectToolboxHintAtTwitter } from './injection/ToolboxHint.js' -import { i18NOverwriteTwitter } from './customization/i18n.js' -import { injectSearchResultInspectorAtTwitter } from './injection/SearchResultInspector.js' -import { injectProfileTabAtTwitter } from './injection/ProfileTab.js' -import { injectProfileTabContentAtTwitter } from './injection/ProfileTabContent.js' -import { injectPostReplacerAtTwitter } from './injection/PostReplacer.js' -import { injectPageInspectorDefault } from '../../site-adaptor-infra/defaults/inject/PageInspector.js' -import { injectBannerAtTwitter } from './injection/Banner.js' -import { injectPostBoxComposed } from './injection/inject.js' -import { createTaskStartSetupGuideDefault } from '../../site-adaptor-infra/defaults/inject/StartSetupGuide.js' -import { injectMaskUserBadgeAtTwitter } from './injection/MaskIcon.js' -import { injectPostInspectorAtTwitter } from './injection/PostInspector.js' -import { injectPostActionsAtTwitter } from './injection/PostActions/index.js' -import { injectTips } from './injection/Tips/index.js' -import { injectUserNFTAvatarAtTwitter } from './injection/NFT/Avatar.js' -import { - injectOpenNFTAvatarEditProfileButton, - openNFTAvatarSettingDialog, -} from './injection/NFT/NFTAvatarEditProfile.js' -import { injectUserNFTAvatarAtTweet } from './injection/NFT/TweetNFTAvatar.js' -import { TwitterRenderFragments } from './customization/render-fragments.js' -import { injectProfileCover } from './injection/ProfileCover.js' -import { injectProfileCardHolder } from './injection/ProfileCard/index.js' -import { injectAvatar } from './injection/Avatar/index.js' -import { injectLens } from './injection/Lens/index.js' -import { injectNFTAvatarInTwitter } from './injection/NFT/index.js' -import { injectSwitchLogoButton } from './injection/SwitchLogo.js' -import { injectCalendar } from './injection/Calendar.js' -import { injectNameWidget } from './injection/NameWidget/index.js' -import { injectFarcaster } from './injection/Farcaster/index.js' - -const useInjectedDialogClassesOverwriteTwitter = makeStyles()((theme) => { - const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` - return { - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - [smallQuery]: { - display: 'block !important', - }, - }, - container: { - alignItems: 'center', - }, - paper: { - width: '600px !important', - minHeight: 400, - maxHeight: 620, - maxWidth: 'none', - boxShadow: 'none', - backgroundImage: 'none', - [smallQuery]: { - display: 'block !important', - margin: 12, - }, - scrollbarWidth: 'none', - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - dialogTitle: { - display: 'grid', - gridTemplateColumns: '1fr auto 1fr', - alignItems: 'center', - padding: 16, - position: 'relative', - background: theme.palette.maskColor.modalTitleBg, - borderBottom: 'none', - '& > p': { - fontSize: 18, - lineHeight: '22px', - display: 'inline-block', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - [smallQuery]: { - display: 'flex', - justifyContent: 'start', - maxWidth: 600, - minWidth: '100%', - boxSizing: 'border-box', - margin: '0 auto', - padding: '7px 14px 6px 11px !important', - }, - }, - dialogContent: { - backgroundColor: theme.palette.maskColor.bottom, - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - maxWidth: 600, - minWidth: '100%', - margin: '0 auto', - padding: '7px 14px 6px', - }, - }, - dialogActions: { - backgroundColor: theme.palette.maskColor.bottom, - padding: '6px 16px', - [smallQuery]: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - maxWidth: 600, - margin: '0 auto', - padding: '7px 14px 6px !important', - }, - }, - dialogBackdropRoot: { - backgroundColor: theme.palette.mode === 'dark' ? 'rgba(110, 118, 125, 0.4)' : 'rgba(0, 0, 0, 0.4)', - }, - } -}) - -const twitterUI: SiteAdaptorUI.Definition = { - ...twitterBase, - ...twitterShared, - automation: { - maskCompositionDialog: { - open: openComposeBoxTwitter, - }, - nativeCommentBox: undefined, - nativeCompositionDialog: { - attachText: pasteTextToCompositionTwitter, - // TODO: make a better way to detect - attachImage: pasteImageToCompositionTwitter, - }, - redirect: { - gotoNewsFeed: gotoNewsFeedPageTwitter, - gotoProfilePage: gotoProfilePageTwitter, - }, - endpoint: { - publishPost: publishPostTwitter, - }, - }, - collecting: { - identityProvider: IdentityProviderTwitter, - currentVisitingIdentityProvider: CurrentVisitingIdentityProviderTwitter, - themeSettingsProvider: ThemeSettingsProviderTwitter, - postsProvider: PostProviderTwitter, - getSearchedKeyword: getSearchedKeywordAtTwitter, - }, - customization: { - sharedComponentOverwrite: { - InjectedDialog: { - classes: useInjectedDialogClassesOverwriteTwitter, - }, - }, - componentOverwrite: { - RenderFragments: TwitterRenderFragments, - }, - useTheme: useThemeTwitterVariant, - i18nOverwrite: i18NOverwriteTwitter, - }, - init(signal) { - const profiles = stateCreator.profiles() - InitAutonomousStateProfiles(signal, profiles, twitterShared.networkIdentifier) - return { profiles } - }, - injection: { - toolbox: injectToolboxHintAtTwitter, - searchResult: injectSearchResultInspectorAtTwitter, - profileTab: injectProfileTabAtTwitter, - profileCover: injectProfileCover, - profileTabContent: injectProfileTabContentAtTwitter, - postReplacer: injectPostReplacerAtTwitter, - pageInspector: injectPageInspectorDefault(), - postInspector: injectPostInspectorAtTwitter, - postActions: injectPostActionsAtTwitter, - banner: injectBannerAtTwitter, - newPostComposition: { - start: injectPostBoxComposed, - supportedInputTypes: { - text: true, - image: true, - }, - supportedOutputTypes: { - text: true, - image: true, - }, - }, - setupWizard: createTaskStartSetupGuideDefault(), - userBadge: injectMaskUserBadgeAtTwitter, - commentComposition: undefined, - userAvatar: injectUserNFTAvatarAtTwitter, - profileAvatar: injectNFTAvatarInTwitter, - openNFTAvatar: injectOpenNFTAvatarEditProfileButton, - postAndReplyNFTAvatar: injectUserNFTAvatarAtTweet, - openNFTAvatarSettingDialog, - avatar: injectAvatar, - tips: injectTips, - lens: injectLens, - farcaster: injectFarcaster, - nameWidget: injectNameWidget, - profileCard: injectProfileCardHolder, - switchLogo: injectSwitchLogoButton, - calendar: injectCalendar, - }, - configuration: { - themeSettings: { - color: ThemeColor.Blue, - size: FontSize.Normal, - mode: ThemeMode.Light, - isDim: false, - }, - nextIDConfig: { - enable: true, - platform: NextIDPlatform.Twitter, - collectVerificationPost, - getPostIdFromNewPostToast, - }, - steganography: { - // ! Change this is a breaking change ! - password() { - const id = - IdentityProviderTwitter.recognized.value.identifier?.userId || - activatedSiteAdaptor_state!.profiles.value?.[0].identifier.userId - if (!id) throw new Error('Cannot figure out password') - return ProfileIdentifier.of(EnhanceableSite.Twitter, id) - .expect(`${id} should be a valid user id`) - .toText() - }, - }, - }, -} - -export default twitterUI diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/AvatarType.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/AvatarType.ts deleted file mode 100644 index b2515c7753f8..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/AvatarType.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isVerifiedUser(ele: HTMLElement) { - return !!ele.closest('[data-testid="tweet"]')?.querySelector('[data-testid="icon-verified"]') -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/avatar.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/avatar.ts deleted file mode 100644 index 0f18344f6d21..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/avatar.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Twitter } from '@masknet/web3-providers' - -export function getInjectNodeInfo(ele: HTMLElement) { - const imgEle = ele.querySelector('img') - if (!imgEle) return - - const nftDom = imgEle.closest('a[href][role=link]') - if (!nftDom) return - - nftDom.style.overflow = 'unset' - const avatarParent = nftDom.parentElement - if (avatarParent) { - if (process.env.NODE_ENV === 'development') { - if ( - avatarParent.style.clipPath && - avatarParent.style.clipPath !== 'url("#shape-square-rx-8")' && - !document.getElementById('shape-hex') - ) { - console.error("Twitter DOM might get updated, can not find clip path by 'shape-hex'") - } - } - } - - const { offsetWidth: width, offsetHeight: height } = nftDom - const avatarId = Twitter.getAvatarId(imgEle.src) - if (!avatarId) return - - return { element: nftDom, width, height, avatarId } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts deleted file mode 100644 index 611dd040595a..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts +++ /dev/null @@ -1,171 +0,0 @@ -import * as web3_utils from /* webpackDefer: true */ 'web3-utils' -import { flattenDeep } from 'lodash-es' -import { canonifyImgUrl, parseId } from './url.js' -import { - makeTypedMessageText, - makeTypedMessageAnchor, - makeTypedMessageEmpty, - type TypedMessage, - makeTypedMessageImage, - type Meta, - unstable_STYLE_META, - makeTypedMessageTuple, - FlattenTypedMessage, -} from '@masknet/typed-message' -import { collectNodeText, collectTwitterEmoji } from '../../../utils/index.js' - -/** - * Get post id from dom, including normal tweet, quoted tweet and retweet one - */ -export function getPostId(node: HTMLElement) { - let idNode: HTMLAnchorElement | undefined | null = null - let timeNode = node.querySelector('a[href*="/status/"] time') - if (timeNode) { - idNode = timeNode.parentElement as HTMLAnchorElement - } else { - // Quoted tweet has no `a[href*="/status/"] time` but only `time` - timeNode = node.querySelector('time') - idNode = timeNode?.closest('[role=link]')?.querySelector('a[href*="/status/"]') - } - const isRetweet = !!node.querySelector('[data-testid=socialContext]') - - let pid = '' - if (idNode) { - pid = parseId(idNode.href) - } else if (timeNode) { - // Quoted tweet in timeline has no a status link to detail page, - // so use the timestamp as post id instead - pid = `timestamp-keccak256:${web3_utils.keccak256(timeNode.getAttribute('datetime')!)}` - } else { - pid = `keccak256:${web3_utils.keccak256(node.innerText)}` - } - - // You can't retweet a tweet or a retweet, but only cancel retweeting - return isRetweet ? `retweet:${pid}` : pid -} - -function postNameParser(node: HTMLElement) { - const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node - const name = collectNodeText(tweetElement.querySelector('[data-testid^="User-Name"] a div div > span')) - // Note: quoted tweet has no [data-testid^="User-Name"] - const handle = - Array.from(tweetElement.querySelectorAll('[tabindex]')) - .map((node) => node.innerText || '') - .filter((text) => text.startsWith('@')) - .at(0) || '' - - // post matched, return the result - if (name || handle) { - return { - name: name || '', - handle: handle ? handle.slice(1) : '', - } - } - const quotedTweetName = collectNodeText( - tweetElement.querySelector( - 'div[role="link"] div[data-testid="UserAvatar-Container-unknown"] + div > span', - ), - ) - const quotedTweetHandle = collectNodeText( - tweetElement - .querySelector('[data-testid="UserAvatar-Container-unknown"]') - ?.parentNode?.parentNode?.parentNode?.parentNode?.firstElementChild?.nextElementSibling?.querySelector( - 'span', - ), - ) - - // quoted post matched - return { - name: quotedTweetName || '', - handle: quotedTweetHandle ? quotedTweetHandle.slice(1) : '', - } -} - -function postAvatarParser(node: HTMLElement) { - const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node - const avatarElement = tweetElement.children[0].querySelector('img[src*="twimg.com"]') - return avatarElement ? avatarElement.src : undefined -} - -function resolveType(content: string) { - if (content.startsWith('@')) return 'user' - if (content.startsWith('#')) return 'hash' - if (content.startsWith('$')) return 'cash' - return 'normal' -} - -export function postContentMessageParser(node: HTMLElement): TypedMessage { - function make(node: Node): TypedMessage { - if (node.nodeType === Node.TEXT_NODE) { - if (!node.nodeValue) return makeTypedMessageEmpty() - return makeTypedMessageText(node.nodeValue, getElementStyle(node.parentElement)) - } else if (node instanceof HTMLAnchorElement) { - const anchor = node - const href = anchor.getAttribute('title') ?? anchor.getAttribute('href') - const content = anchor.textContent - if (!content) return makeTypedMessageEmpty() - const altImage = node.querySelector('img') - return makeTypedMessageAnchor( - resolveType(content), - href ?? '', - content, - altImage ? makeTypedMessageImage(altImage.src, altImage) : undefined, - getElementStyle(node), - ) - } else if (node instanceof HTMLImageElement) { - const image = node - const src = image.getAttribute('src') - const alt = image.getAttribute('alt') - const matched = src?.match(/emoji\/v2\/svg\/([\w-]+)\.svg/) - if (matched) { - const points = matched[1].split('-').map((point) => Number.parseInt(point, 16)) - return makeTypedMessageText(collectTwitterEmoji(points)) - } - if (!alt) return makeTypedMessageEmpty() - - return makeTypedMessageText(alt) - } else if (node instanceof HTMLSpanElement) { - return makeTypedMessageText(node.textContent ?? '') - } else if (node.childNodes.length) { - const messages = makeTypedMessageTuple(flattenDeep(Array.from(node.childNodes).map(make))) - return FlattenTypedMessage.NoContext(messages) - } else return makeTypedMessageEmpty() - } - const lang = node.parentElement!.querySelector('[lang]') - return lang ? - FlattenTypedMessage.NoContext(makeTypedMessageTuple(Array.from(lang.childNodes).flatMap(make))) - : makeTypedMessageEmpty() -} - -function getElementStyle(element: Element | null): Meta | undefined { - if (!element) return undefined - const computed = getComputedStyle(element) - const style: React.CSSProperties = {} - if (computed.fontWeight !== '400') style.fontWeight = computed.fontWeight - if (computed.fontStyle !== 'normal') style.fontStyle = computed.fontStyle - if (style.fontWeight || style.fontStyle) return new Map([[unstable_STYLE_META, style]]) - return undefined -} - -export async function postImagesParser(node: HTMLElement): Promise { - const isQuotedTweet = !!node.closest('div[role="link"]') - const imgNodes = node.querySelectorAll('img[src*="twimg.com/media"]') - if (!imgNodes.length) return [] - const imgUrls = Array.from(imgNodes) - .filter((node) => isQuotedTweet || !node.closest('div[role="link"]')) - .flatMap((node) => canonifyImgUrl(node.getAttribute('src') ?? '')) - .filter(Boolean) - if (!imgUrls.length) return [] - return imgUrls -} - -export function postParser(node: HTMLElement) { - return { - ...postNameParser(node), - avatar: postAvatarParser(node), - - pid: getPostId(node), - - messages: postContentMessageParser(node), - } -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/postBox.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/postBox.ts deleted file mode 100644 index e143901876ab..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/postBox.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { postEditorDraftContentSelector } from './selector.js' -import type { LiveSelector } from '@dimensiondev/holoflows-kit' - -export function getEditorContent() { - const editorNode = postEditorDraftContentSelector().evaluate() - if (!editorNode) return '' - if (editorNode.tagName.toLowerCase() === 'div') return (editorNode as HTMLDivElement).innerText - return (editorNode as HTMLTextAreaElement).value -} - -export function isCompose() { - return globalThis.location.pathname === '/compose/post' -} - -export function hasFocus(x: LiveSelector) { - return x.evaluate() === document.activeElement -} - -export function hasEditor() { - return !!postEditorDraftContentSelector().evaluate() -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts deleted file mode 100644 index 99a8c9588535..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { LiveSelector } from '@dimensiondev/holoflows-kit' -import { regexMatch } from '../../../utils/regexMatch.js' -import { isCompose } from './postBox.js' - -type E = HTMLElement - -export function querySelector(selector: string, singleMode = true) { - const ls = new LiveSelector().querySelector(selector) - return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector -} -export function querySelectorAll(selector: string) { - return new LiveSelector().querySelectorAll(selector) -} - -// #region "Enhanced Profile" -export function searchProfileTabListLastChildSelector() { - return querySelector( - '[data-testid="primaryColumn"] div + [role="navigation"][aria-label] [data-testid="ScrollSnap-List"] div[role="presentation"]:last-of-type a[role="tab"]', - ).closest(1) -} -export function nextTabListSelector() { - return querySelector('[data-testid="ScrollSnap-nextButtonWrapper"]') -} -export function searchProfileTabPageSelector() { - return searchProfileTabListLastChildSelector() - .closest(5) - .querySelector('section > div[aria-label]:not([role="progressbar"])') -} - -export function searchProfileTabLoseConnectionPageSelector() { - return querySelector( - '[data-testid="primaryColumn"] [role="navigation"] + * > div[dir="auto"]:not([role="progressbar"])', - ) -} - -export function searchProfileEmptySelector() { - return querySelector('[data-testid="primaryColumn"] [data-testid="emptyState"]') -} -export function searchProfileTabSelector() { - return querySelector('[aria-label][role="navigation"] [role="tablist"] [role="tab"][aria-selected="false"]') -} -export function searchAppBarBackSelector() { - return querySelector('[data-testid="app-bar-back"] > div') -} -export function searchProfileTabListSelector() { - return querySelectorAll('[aria-label][role="navigation"] [role="tablist"][data-testid="ScrollSnap-List"] a') -} -export function searchNewTweetButtonSelector() { - const q = querySelector('[data-testid="FloatingActionButtons_Tweet_Button"]') - if (q.evaluate()) return q - return querySelector('[data-testid="SideNav_NewTweet_Button"]') -} - -export function searchAvatarSelector() { - return querySelector('[data-testid="primaryColumn"] a[href$="/photo"] img[src*="profile_images"]') -} - -export function searchAvatarMetaSelector() { - return querySelector('head meta[property="og:image"]') -} - -export function profileFollowButtonSelector() { - return querySelector( - '[data-testid="primaryColumn"] [aria-haspopup="menu"][data-testid="userActions"] ~ [data-testid="placementTracking"]', - ) -} - -export function normalFollowButtonSelector() { - return querySelectorAll( - '[data-testid="primaryColumn"] [role="button"][data-testid="UserCell"] [data-testid$="follow"]', - ) -} - -export function searchProfileCoverSelector() { - return querySelector( - '[data-testid="primaryColumn"] > div > div:last-child > div > div > div > div > div > div[style], [data-testid="primaryColumn"] > div > div:last-child > div > div > div > a > div > div[style]', - ).closest(1) -} - -export function searchEditProfileSelector() { - return querySelector('[data-testid="primaryColumn"] [data-testid^="UserAvatar-Container-"]') - .closest(1) - .querySelector('a[href="/settings/profile"]') -} -// #endregion - -export function rootSelector() { - return querySelector('#react-root') -} - -// `aside *` selectors are for mobile mode -export function composeAnchorSelector() { - return querySelector( - [ - 'header[role=banner] a[href="/compose/post"]', - 'aside a[href="/compose/post"]', - // can't see the compose button on share popup, use the tweetButton instead - '[role=main] [role=button][data-testid=tweetButton]', - ].join(','), - ) -} - -export function postEditorContentInPopupSelector() { - return querySelector( - '[aria-labelledby="modal-header"] > div:first-child > div:first-child > div:first-child > div:nth-child(3)', - ) -} -export function postEditorInPopupSelector() { - return querySelector( - '[aria-labelledby="modal-header"] div[data-testid="toolBar"] [role="presentation"]:has(> div[data-testid="geoButton"])', - ) -} -export function sideBarProfileSelector() { - return querySelector('[role="banner"] [role="navigation"] [data-testid="AppTabBar_Profile_Link"] > div') -} -export function postEditorInTimelineSelector() { - return querySelector( - '[role="main"] :not(aside) > [role="progressbar"] ~ div [role="button"][aria-label]:nth-child(6)', - ) -} - -export function isReplyPageSelector() { - return !!location.pathname.match(/^\/\w+\/status\/\d+$/) -} -export function postEditorDraftContentSelector() { - if (location.pathname === '/compose/post') { - return querySelector( - '[contenteditable][aria-label][spellcheck],textarea[aria-label][spellcheck]', - ) - } - if (isReplyPageSelector()) { - return querySelector('div[data-testid="tweetTextarea_0"]') - } - return (isCompose() ? postEditorInPopupSelector() : postEditorInTimelineSelector()).querySelector( - '.public-DraftEditor-content, [contenteditable][aria-label][spellcheck]', - ) -} - -export function searchResultHeadingSelector() { - return querySelector('[role="main"] [data-testid="primaryColumn"] > div > :nth-child(2)') -} - -export function newPostButtonSelector() { - return querySelector('[data-testid="SideNav_NewTweet_Button"]') -} - -export function bioPageUserNickNameSelector() { - return querySelector('[data-testid="UserDescription"]') - .map((x) => x.parentElement?.parentElement?.previousElementSibling) - .querySelector('div[dir]') -} - -export function bioPageUserIDSelector(selector: () => LiveSelector) { - return selector().map((x) => (x.parentElement?.nextElementSibling as HTMLElement)?.innerText?.replace('@', '')) -} - -export function floatingBioCardSelector() { - return querySelector( - '[style~="left:"] a[role=link] > div:first-child > div:first-child > div:first-child[dir="auto"]', - ) -} - -export function postsImageSelector(node: HTMLElement) { - return new LiveSelector([node]).querySelectorAll( - [ - '[data-testid="tweet"] > div > div img[src*="media"]', - '[data-testid="tweet"] ~ div img[src*="media"]', // image in detail page for new twitter - ].join(','), - ) -} - -export function timelinePostContentSelector() { - return querySelectorAll( - [ - '[data-testid="tweet"] div + div div[lang]', - '[data-testid="tweet"] div + div div[data-testid="card.wrapper"]', // link box tweets - ].join(','), - ) -} - -export function toastLinkSelector() { - return querySelector('[data-testid="toast"] a') -} - -export function postsContentSelector() { - return querySelectorAll( - [ - // tweets on timeline page - '[data-testid="tweet"] [data-testid="tweetText"]', - '[data-testid="tweet"]:not(:has([data-testid="tweetText"])) [data-testid="tweetPhoto"]', // tweets with only image. - - // tweets on detailed page - '[data-testid="tweet"] + div > div:first-child div[lang]', - '[data-testid="tweet"] + div > div div[data-testid="card.wrapper"]', - - // tweets have only link that rendered as social media card - '[data-testid="tweet"] [data-testid="card.wrapper"]:has([data-testid="card.layoutLarge.media"])', - - // quoted tweets in timeline - '[data-testid="tweet"] [aria-labelledby] div[role="link"] div[lang]', - // quoted tweets in detail page - '[data-testid="tweet"] > div:last-child div[role="link"] div[lang]', - ].join(','), - ) -} - -export function postAvatarSelector() { - return querySelectorAll('[data-testid=tweet] [data-testid^=UserAvatar-Container-]') -} -export function followUserAvatarSelector() { - return querySelectorAll('[data-testid=UserCell] [data-testid^=UserAvatar-Container-]') -} - -const base = querySelector('#react-root ~ script') -const handle = /"screen_name":"(.*?)"/ -const name = /"name":"(.*?)"/ -const bio = /"description":"(.*?)"/ -const avatar = /"profile_image_url_https":"(.*?)"/ -/** - * first matched element can be extracted by index zero, followings are all capture groups, if no 'g' specified. - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match - */ -function p(regex: RegExp, index: number) { - return base.clone().map((x) => regexMatch(x.innerText, regex, index)) -} -export function selfInfoSelectors() { - return { - handle: p(handle, 1), - name: p(name, 1), - bio: p(bio, 1), - userAvatar: p(avatar, 1), - } -} - -// #region self info -export function searchSelfHandleSelector() { - return querySelector( - [ - '[data-testid="SideNav_AccountSwitcher_Button"] > div > div[data-testid^="UserAvatar-Container-"]', // desktop - '#layers [role="group"] [role="dialog"] [tabindex="-1"] [dir="ltr"] > span', // sidebar opened in mobile - ].join(','), - ) -} - -export function searchSelfNicknameSelector() { - return querySelector( - [ - '[data-testid="SideNav_AccountSwitcher_Button"] span span:first-child', - '#layers [role="group"] [role="dialog"] [role="link"] span > span', // sidebar opened in mobile - ].join(','), - ) -} - -export function searchWatcherAvatarSelector() { - return querySelector('[data-testid="SideNav_AccountSwitcher_Button"] img') -} - -export function searchSelfAvatarSelector() { - return querySelector( - [ - '#layers ~ div [role="banner"] [role="button"] img', - '[data-testid="DashButton_ProfileIcon_Link"] [role="presentation"] img', - '#layers [role="group"] [role="dialog"] [role="link"] img', // sidebar opened in mobile - ].join(','), - ) -} -// #endregion - -// #region twitter nft avatar -export function searchProfileAvatarSelector() { - return querySelector('[data-testid="Profile_Save_Button"]') - .closest(8) - .querySelector('[data-testid="UserAvatar-Container-unknown"]') - .closest(3) -} - -export function searchProfileSaveSelector() { - return querySelector('[data-testid="Profile_Save_Button"]') -} - -// #region avatar selector -export function searchTwitterAvatarLinkSelector() { - return querySelector('[data-testid="UserProfileHeader_Items"]').closest(2).querySelector('div a') -} - -export function searchTwitterAvatarSelector() { - return querySelector('a[href$="/photo"]').querySelector('img').closest(1) -} -// #endregion - -export function searchTweetAvatarSelector() { - return querySelector('[data-testid="tweetButtonInline"]').closest(7) -} - -export function searchRetweetAvatarSelector() { - return querySelector('[data-testid="tweetButton"]').closest(6) -} - -export function searchReplyToolbarSelector() { - return querySelector( - 'div[data-testid="primaryColumn"] div[data-testid="toolBar"] [role="presentation"]:has(> div[data-testid="geoButton"])', - ) -} - -// nameTag dom -export function searchNameTag() { - return querySelector('#nft-gallery') -} - -export function searchHomeLinkName() { - return querySelector('[data-testid="AppTabBar_Home_Link"]') -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/url.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/url.ts deleted file mode 100644 index 1c1743af26f3..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/url.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { regexMatch } from '../../../utils/regexMatch.js' - -// more about twitter photo url formatting: -// https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/entities-object#photo_format -export function canonifyImgUrl(url: string) { - const parsed = new URL(url) - if (parsed.hostname !== 'pbs.twimg.com') return url - const { searchParams } = parsed - searchParams.set('name', 'orig') - // we can't understand original image format when given url labeled as webp - if (searchParams.get('format') === 'webp') { - searchParams.set('format', 'png') - const pngURL = parsed.href - searchParams.set('format', 'jpg') - const jpgURL = parsed.href - return [jpgURL, pngURL] - } - return parsed.href -} - -export function parseId(t: string) { - return regexMatch(t, /status\/(\d+)/, 1)! -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/user.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/user.ts deleted file mode 100644 index 69cbede518b0..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/utils/user.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isNull } from 'lodash-es' -import { ProfileIdentifier, type SocialIdentity } from '@masknet/shared-base' -import { Twitter } from '@masknet/web3-providers' -import { twitterBase } from '../base.js' - -/** - * @link https://help.twitter.com/en/managing-your-account/twitter-username-rules - */ -export function usernameValidator(name: string) { - for (const v of [/(twitter|admin)/i, /.{16,}/, /\W/]) { - if (!isNull(v.exec(name))) { - return false - } - } - if (name.length < 4) return false - return true -} - -export async function getUserIdentity(twitterId: string): Promise { - const user = await Twitter.getUserByScreenName(twitterId) - if (!user) return - - return { - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, user.screenName).unwrapOr(undefined), - nickname: user.nickname, - avatar: user.avatarURL, - bio: user.bio, - homepage: user.homepage, - } -} - -export function getUserId(ele: HTMLElement) { - return ele.querySelector('a[href][role=link]')?.getAttribute('href')?.slice(1) -} diff --git a/packages/mask/content-script/site-adaptors/utils.ts b/packages/mask/content-script/site-adaptors/utils.ts deleted file mode 100644 index 46e36f8f3e5e..000000000000 --- a/packages/mask/content-script/site-adaptors/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type PersonaIdentifier, type ProfileIdentifier } from '@masknet/shared-base' -import { activatedSiteAdaptorUI, activatedSiteAdaptor_state } from '../site-adaptor-infra/index.js' - -export function getCurrentIdentifier(): - | { - identifier: ProfileIdentifier - linkedPersona?: PersonaIdentifier - } - | undefined { - const current = activatedSiteAdaptorUI!.collecting.identityProvider?.recognized.value - - return ( - activatedSiteAdaptor_state!.profiles.value.find((i) => i.identifier === current?.identifier) || - activatedSiteAdaptor_state!.profiles.value[0] - ) -} diff --git a/packages/mask/content-script/tsconfig.json b/packages/mask/content-script/tsconfig.json deleted file mode 100644 index 8b5dfdbace43..000000000000 --- a/packages/mask/content-script/tsconfig.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "extends": "../../../tsconfig.leaf.json", - "compilerOptions": { - "rootDir": "./", - "tsBuildInfoFile": "../dist/legacy.tsbuildinfo" - }, - "include": ["./", "./**/*.json"], - "references": [ - { "path": "../../public-api/tsconfig.json" }, - { "path": "../../shared/tsconfig.json" }, - { "path": "../../shared-base/tsconfig.json" }, - { "path": "../../shared-base-ui/tsconfig.json" }, - { "path": "../../typed-message/base/tsconfig.json" }, - { "path": "../../typed-message/react/tsconfig.json" }, - { "path": "../../web3-hooks/base/tsconfig.json" }, - { "path": "../../web3-hooks/evm/tsconfig.json" }, - { "path": "../../web3-providers/tsconfig.json" }, - { "path": "../../web3-shared/base/tsconfig.json" }, - { "path": "../../web3-shared/evm/tsconfig.json" }, - { "path": "../../web3-telemetry/tsconfig.json" }, - { "path": "../../theme/tsconfig.json" }, - { "path": "../../plugin-infra/tsconfig.json" }, - { "path": "../../plugins/Avatar/tsconfig.json" }, - { "path": "../../plugins/Tips/tsconfig.json" }, - { "path": "../../injected-script/sdk/tsconfig.json" }, - { "path": "../../plugins/Trader/tsconfig.json" }, - { "path": "../../plugins/SwitchLogo/tsconfig.json" }, - { "path": "../../plugins/Calendar/tsconfig.json" }, - { "path": "../../sandboxed-plugin-runtime/src/site-adaptor/tsconfig.json" }, - { "path": "../web-workers/tsconfig.json" }, - { "path": "../utils-pure/tsconfig.json" }, - { "path": "../background/tsconfig.json" }, - { "path": "../shared/tsconfig.json" }, - { "path": "../shared-ui/tsconfig.json" }, - { "path": "../../types/tsconfig.json" }, - { "path": "../entry-sdk/tsconfig.json" } - ] -} diff --git a/packages/mask/content-script/utils/collectNodeText.ts b/packages/mask/content-script/utils/collectNodeText.ts deleted file mode 100644 index 9cd27ec2e7bf..000000000000 --- a/packages/mask/content-script/utils/collectNodeText.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Option } from 'ts-results-es' -import { collectTwitterEmoji } from './collectTwitterEmoji.js' - -interface CollectNodeTextOptions { - onHTMLAnchorElement?(node: HTMLAnchorElement): Option -} - -export function collectNodeText(node: HTMLElement | null | undefined, options: CollectNodeTextOptions = {}): string { - if (!node) return '' - if (!node.querySelector('a, img')) return node.innerText - return [...node.childNodes] - .map((each) => { - if (each.nodeType === document.TEXT_NODE) return (each as Text).nodeValue || '' - if (each instanceof HTMLAnchorElement) { - const result = options.onHTMLAnchorElement?.(each) - if (result?.isSome()) return result.value - const href = each.getAttribute('href') - return [href, each.innerText].join(' ') - } - if (each instanceof HTMLImageElement) { - const src = each.getAttribute('src') - const alt = each.getAttribute('alt') ?? '' - const matched = src?.match(/emoji\/v2\/svg\/([\w-]+)\.svg/)?.[1] - if (matched) return collectTwitterEmoji(matched.split('-').map((x) => Number.parseInt(x, 16))) || alt - return alt - } - if (each instanceof HTMLElement) return collectNodeText(each, options) - return '' - }) - .join('') -} diff --git a/packages/mask/content-script/utils/collectTwitterEmoji.ts b/packages/mask/content-script/utils/collectTwitterEmoji.ts deleted file mode 100644 index 0878530f5746..000000000000 --- a/packages/mask/content-script/utils/collectTwitterEmoji.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function collectTwitterEmoji(points: readonly number[]) { - if (points.length === 0) return '' - if (points[0] < 35 || points[0] > 57) return String.fromCodePoint(...points) - if (points.includes(65039)) return String.fromCodePoint(...points) - return String.fromCodePoint(points[0], 65039, ...points.slice(1)) -} diff --git a/packages/mask/content-script/utils/downloadUrl.ts b/packages/mask/content-script/utils/downloadUrl.ts deleted file mode 100644 index 17dd4ad9d962..000000000000 --- a/packages/mask/content-script/utils/downloadUrl.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Services from '#services' - -/** - * Download given url return as Blob - */ -export async function downloadUrl(url: string) { - try { - if (url.startsWith(browser.runtime.getURL(''))) { - return Services.Helper.fetchBlob(url) - } - } catch {} - const res = await fetch(url) - if (!res.ok) throw new Error('Fetch failed.') - return res.blob() -} diff --git a/packages/mask/content-script/utils/hasPayloadLike.ts b/packages/mask/content-script/utils/hasPayloadLike.ts deleted file mode 100644 index 980253eec095..000000000000 --- a/packages/mask/content-script/utils/hasPayloadLike.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function hasPayloadLike(text: string | Uint8Array): boolean { - if (typeof text === 'string') return text.includes('\uD83C\uDFBC') && text.includes(':||') - return true -} diff --git a/packages/mask/content-script/utils/index.ts b/packages/mask/content-script/utils/index.ts deleted file mode 100644 index c914f09348ab..000000000000 --- a/packages/mask/content-script/utils/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './collectNodeText.js' -export * from './collectTwitterEmoji.js' -export * from './untilElementAvailable.js' -export * from './hasPayloadLike.js' -export * from './downloadUrl.js' -export * from './pasteImageToActiveElements.js' -export * from './regexMatch.js' -export * from './selectElementContents.js' diff --git a/packages/mask/content-script/utils/pasteImageToActiveElements.ts b/packages/mask/content-script/utils/pasteImageToActiveElements.ts deleted file mode 100644 index 16a03d18b86d..000000000000 --- a/packages/mask/content-script/utils/pasteImageToActiveElements.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { pasteImage } from '@masknet/injected-script' - -/** - * paste image to activeElements - * @param image - */ - -export async function pasteImageToActiveElements(image: File | Blob): Promise { - pasteImage(new Uint8Array(await image.arrayBuffer())) -} diff --git a/packages/mask/content-script/utils/regexMatch.ts b/packages/mask/content-script/utils/regexMatch.ts deleted file mode 100644 index 1787df8e529f..000000000000 --- a/packages/mask/content-script/utils/regexMatch.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isNull } from 'lodash-es' - -/** - * index starts at one. - */ - -export function regexMatch(input: string, pattern: RegExp, index?: number): string | null -export function regexMatch(input: string, pattern: RegExp, index: null): RegExpMatchArray | null -export function regexMatch(input: string, pattern: RegExp, index: number | null = 1) { - const r = input.match(pattern) - if (isNull(r)) return null - if (index === null) { - return r as any - } - return r[index] -} diff --git a/packages/mask/content-script/utils/selectElementContents.ts b/packages/mask/content-script/utils/selectElementContents.ts deleted file mode 100644 index c0f88ec960bf..000000000000 --- a/packages/mask/content-script/utils/selectElementContents.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Select all text in a node - * @param el Element - */ - -export function selectElementContents(el: Node) { - const range = document.createRange() - range.selectNodeContents(el) - const sel = globalThis.getSelection()! - sel.removeAllRanges() - sel.addRange(range) - return sel -} diff --git a/packages/mask/content-script/utils/shadow-root.ts b/packages/mask/content-script/utils/shadow-root.ts deleted file mode 100644 index b428b9924530..000000000000 --- a/packages/mask/content-script/utils/shadow-root.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { - attachReactTreeWithContainer, - attachReactTreeWithoutContainer, - setupReactShadowRootEnvironment, -} from './shadow-root/renderInShadowRoot.js' diff --git a/packages/mask/content-script/utils/shadow-root/ShadowRootAttachPointRoot.tsx b/packages/mask/content-script/utils/shadow-root/ShadowRootAttachPointRoot.tsx deleted file mode 100644 index 6e4fce85a5e1..000000000000 --- a/packages/mask/content-script/utils/shadow-root/ShadowRootAttachPointRoot.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Suspense } from 'react' -import { useSiteThemeMode } from '@masknet/plugin-infra/content-script' -import { SharedContextProvider } from '@masknet/shared' -import { CSSVariableInjector, MaskThemeProvider } from '@masknet/theme' -import { ErrorBoundary, queryClient } from '@masknet/shared-base-ui' -import { Sniffings, compose } from '@masknet/shared-base' -import { useMaskSiteAdaptorMixedTheme } from '../../components/useMaskSiteAdaptorMixedTheme.js' -import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' -import { queryPersistOptions } from '../../../shared-ui/index.js' - -export function ShadowRootAttachPointRoot(children: React.ReactNode) { - return compose( - (children) => , - (children) => , - (children) => - MaskThemeProvider({ - useMaskIconPalette: useSiteThemeMode, - useTheme: useMaskSiteAdaptorMixedTheme, - CustomSnackbarOffsetY: Sniffings.is_facebook_page ? 80 : undefined, - children, - }), - (children) => ( - - ), - (children) => SharedContextProvider({ children }), - <> - - {children} - , - ) -} diff --git a/packages/mask/content-script/utils/shadow-root/renderInShadowRoot.tsx b/packages/mask/content-script/utils/shadow-root/renderInShadowRoot.tsx deleted file mode 100644 index 95ca2fae1add..000000000000 --- a/packages/mask/content-script/utils/shadow-root/renderInShadowRoot.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { createPortal } from 'react-dom' -import { - attachReactTreeToMountedRoot_noHost, - setupReactShadowRootEnvironment as setupReactShadowRootEnvironmentUpper, - CSSVariableInjector, - usePortalShadowRoot, -} from '@masknet/theme' -import { Flags } from '@masknet/flags' -import { SiteUIProvider } from '@masknet/shared' -import { ShadowRootAttachPointRoot } from './ShadowRootAttachPointRoot.js' - -const captureEvents: Array = [ - 'paste', - 'keydown', - 'keypress', - 'keyup', - 'drag', - 'dragend', - 'dragenter', - 'dragleave', - 'dragover', - 'dragstart', - 'change', -] -export function setupReactShadowRootEnvironment() { - const shadow = setupReactShadowRootEnvironmentUpper(Flags.shadowRootInit, captureEvents, SiteUIProvider) - // Inject variable for Portals - attachReactTreeWithContainer(shadow, { key: 'css-vars' }).render( - <> - - , - ) -} - -export const attachReactTreeWithContainer = attachReactTreeToMountedRoot_noHost(ShadowRootAttachPointRoot) - -function AttachReactTreeWithoutContainerRedirect(props: React.PropsWithChildren<{ debugKey: string }>) { - // Note: since it is the direct children of attachReactTreeWithoutContainer, it MUST inside a ShadowRoot environment. - return usePortalShadowRoot((container) => createPortal(props.children, container!), props.debugKey) -} -/** - * @param debugKey Only used for debug - * @param jsx JSX to render - * @param signal AbortSignal - */ -export function attachReactTreeWithoutContainer(debugKey: string, jsx: React.ReactNode, signal?: AbortSignal) { - // Note: do not attach this DOM to window. We don't need it - const dom = document.createElement('main') - const shadow = dom.attachShadow({ mode: 'closed', delegatesFocus: true }) - - attachReactTreeWithContainer(shadow, { signal, key: debugKey }).render( - , - ) -} diff --git a/packages/mask/content-script/utils/startWatch.ts b/packages/mask/content-script/utils/startWatch.ts deleted file mode 100644 index 2ddf19c691a8..000000000000 --- a/packages/mask/content-script/utils/startWatch.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { Flags } from '@masknet/flags' - -/** - * @example - * ```ts - * startWatch(new MutationObserverWatcher(ls), { - * signal, - * missingReportRule: new URL('https://twitter.com/'), - * name: 'twitter-home-page', - * }) - * ``` - * - * This will be reported only when the current page is https://twitter.com/ and no matches is found. - */ -export function startWatch>(watcher: T, options: WatchOptions): T -export function startWatch>(watcher: T, options: AbortSignal): T -export function startWatch>( - watcher: T, - options: AbortSignal | WatchOptions, -) { - if (options instanceof AbortSignal) { - options = { signal: options } - } - const { signal, missingReportRule, shadowRootDelegatesFocus: delegatesFocus } = options - - if (missingReportRule) { - watchers.set(watcher, missingReportRule) - const timeout = setTimeout(check, 2000) - signal.addEventListener( - 'abort', - () => { - watchers.delete(watcher) - clearTimeout(timeout) - }, - { once: true }, - ) - } - - watcher - .setDOMProxyOption({ - afterShadowRootInit: { ...Flags.shadowRootInit, delegatesFocus }, - beforeShadowRootInit: { ...Flags.shadowRootInit, delegatesFocus }, - }) - .startWatch({ subtree: true, childList: true }, signal) - return watcher -} -/** - * string will be startsWith match, RegExp will be partial match - */ -type MissingReportRuleBasic = string | RegExp -type MissingReportRule = MissingReportRuleBasic | MissingReportRuleBasic[] | (() => boolean | Promise) - -interface MissingReportRuleOptions { - name: string - rule: MissingReportRule -} - -export interface WatchOptions { - signal: AbortSignal - missingReportRule?: MissingReportRuleOptions - shadowRootDelegatesFocus?: boolean -} - -const watchers = new Map, MissingReportRuleOptions>() -if (typeof window === 'object') { - window.addEventListener('locationchange', () => { - setTimeout(check, 2000) - }) -} -let reporter: (name: string) => void = function (name: string) { - console.warn(`[Mask] Watcher "${name}" expected to match something but it didn't.`, location.href) -} -export function configureSelectorMissReporter(newReporter: typeof reporter) { - reporter = newReporter -} -function check() { - for (const [watcher, { name, rule }] of watchers) { - // protected API - // eslint-disable-next-line @typescript-eslint/dot-notation - if (watcher['lastKeyList'].length) continue - if (typeof rule === 'function') { - const result = rule() - if (!result) continue - if (result !== true) { - // eslint-disable-next-line @typescript-eslint/no-loop-func - result.then((x) => x || reporter(name)) - continue - } - } else if (Array.isArray(rule)) { - if (!rule.some(hitBasic)) continue - } else if (!hitBasic(rule)) continue - - reporter(name) - } -} -function hitBasic(rule: MissingReportRuleBasic) { - if (rule instanceof RegExp) { - return rule.test(location.href) - } - return location.href.startsWith(rule) -} diff --git a/packages/mask/content-script/utils/untilElementAvailable.ts b/packages/mask/content-script/utils/untilElementAvailable.ts deleted file mode 100644 index 1bfcdfef6089..000000000000 --- a/packages/mask/content-script/utils/untilElementAvailable.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IntervalWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' - -export function untilElementAvailable(ls: LiveSelector, timeout = 5000) { - return new Promise((resolve, reject) => { - const w = new IntervalWatcher(ls) - setTimeout(() => reject(), timeout) - w.useForeach(() => { - w.stopWatch() - resolve() - }).startWatch(500) - }) -} diff --git a/packages/mask/dashboard/Dashboard.tsx b/packages/mask/dashboard/Dashboard.tsx deleted file mode 100644 index d9fdf40f00a7..000000000000 --- a/packages/mask/dashboard/Dashboard.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useEffect } from 'react' -import { CssBaseline, ThemeProvider, StyledEngineProvider, GlobalStyles } from '@mui/material' -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' -import { - CustomSnackbarProvider, - applyMaskColorVars, - MaskLightTheme, - MaskDarkTheme, - useSystemPreferencePalette, - DialogStackingProvider, -} from '@masknet/theme' -import { I18NextProviderHMR, PersonaContext, SharedContextProvider, Modals } from '@masknet/shared' -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { RootWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { DashboardRoutes, i18NextInstance, queryRemoteI18NBundle, compose } from '@masknet/shared-base' - -import { Pages } from './pages/routes.js' -import { UserContext, useAppearance } from '../shared-ui/index.js' -import Services from '#services' - -const GlobalCss = ( - -) - -const PersonaContextIO = { - queryOwnedPersonaInformation: Services.Identity.queryOwnedPersonaInformation, - queryPersonaAvatarLastUpdateTime: Services.Identity.getPersonaAvatarLastUpdateTime, - queryPersonaAvatar: Services.Identity.getPersonaAvatar, -} -export default function Dashboard() { - useEffect(() => queryRemoteI18NBundle(Services.Helper.queryRemoteI18NBundle), []) - - // #region theme - const appearance = useAppearance() - const mode = useSystemPreferencePalette() - const theme = { - dark: MaskDarkTheme, - light: MaskLightTheme, - default: mode === 'dark' ? MaskDarkTheme : MaskLightTheme, - }[appearance] - - useEffect(() => { - applyMaskColorVars(document.body, appearance === 'default' ? mode : appearance) - }, [appearance]) - // #endregion - - return compose( - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - (children) => , - <> - - {GlobalCss} - {/* https://github.com/TanStack/query/issues/5417 */} - {process.env.NODE_ENV === 'development' ? - - : null} - Services.Helper.openDashboard(DashboardRoutes.CreateMaskWalletForm)} /> - - , - ) -} diff --git a/packages/mask/dashboard/assets/Welcome.splinecode.png b/packages/mask/dashboard/assets/Welcome.splinecode.png deleted file mode 100644 index ca0b1bf0884ebc8370f718b703536020c7faf642..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 486904 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

e8!1&tI2(jPl7wFfbO_s&4Q)0RKkNeQlV_E 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�h)DHv0 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⩔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≺T(2G9V>m`V@{&_^}qlB8Gsqi->6_d_x_~#(jSkE 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 M07*qoM6N<$g5OXX(f|Me diff --git a/packages/mask/dashboard/assets/index.ts b/packages/mask/dashboard/assets/index.ts deleted file mode 100644 index b2fb50a0d48b..000000000000 --- a/packages/mask/dashboard/assets/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Manage local static resource - */ -export const ABOUT_DIALOG_BACKGROUND = new URL('./images/AboutDialogBackground.png', import.meta.url).href -export const WatermarkURL = new URL('./images/MaskWatermark.png', import.meta.url).href -// A workaround for Opera file name validation -export const Welcome = new URL('./Welcome.splinecode.png', import.meta.url).href -export const PrintBackground = new URL('./images/PrintBackground.png', import.meta.url).href -export const Trend = new URL('./images/Trend.png', import.meta.url).href -export const MaskWallet = new URL('./images/MaskWallet.png', import.meta.url).href diff --git a/packages/mask/dashboard/components/ActionCard/index.tsx b/packages/mask/dashboard/components/ActionCard/index.tsx deleted file mode 100644 index ffcbd706935c..000000000000 --- a/packages/mask/dashboard/components/ActionCard/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import type { ReactNode } from 'react' -import { Box, Button, Card, Stack, Typography } from '@mui/material' -import { experimentalStyled as styled } from '@mui/material/styles' - -const ActionCardIcon = styled('div')` - width: 36px; - height: 36px; - - & > svg, - & > span { - width: 100%; - height: 100%; - } -` - -const ActionCardButton = styled('div')( - ({ theme }) => ` - font-size: 14px; - - & > button { - width: 164px; - border-radius: ${theme.spacing(3)}; - } -`, -) - -interface ISetupActionCardProps { - icon: ReactNode - title: string - subtitle?: string - action: { - type: 'secondary' | 'primary' - text: string - handler: () => void - } -} - -export function ActionCard({ icon, title, subtitle, action }: ISetupActionCardProps) { - return ( - theme.spacing(2.5), - marginBottom: (theme) => theme.spacing(2.5), - boxShadow: 'none', - }}> - - {icon} - - - {title} - - - {subtitle} - - - - - - - - ) -} diff --git a/packages/mask/dashboard/components/BackupPreview/index.tsx b/packages/mask/dashboard/components/BackupPreview/index.tsx deleted file mode 100644 index 5f60811dec9e..000000000000 --- a/packages/mask/dashboard/components/BackupPreview/index.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import type { BackupSummary } from '@masknet/backup-format' -import { Icons } from '@masknet/icons' -import { ReversedAddress } from '@masknet/shared' -import { NetworkPluginID } from '@masknet/shared-base' -import { TextOverflowTooltip, makeStyles } from '@masknet/theme' -import { ChainId } from '@masknet/web3-shared-evm' -import { EVMExplorerResolver } from '@masknet/web3-providers' -import { - Card, - CardContent, - CardHeader, - Checkbox, - Link, - List, - ListItem, - ListItemIcon, - ListItemText, - Typography, -} from '@mui/material' -import { Box, type BoxProps } from '@mui/system' -import { memo } from 'react' -import { useDashboardTrans } from '../../locales/i18n_generated.js' - -const useStyles = makeStyles()((theme) => ({ - card: { - backgroundColor: theme.palette.maskColor.bottom, - boxShadow: 'none', - border: `1px solid ${theme.palette.maskColor.line}`, - borderRadius: 8, - marginBottom: theme.spacing(2), - }, - cardHeader: { - padding: theme.spacing(2), - borderBottom: `1px solid ${theme.palette.maskColor.line}`, - }, - title: { - color: theme.palette.maskColor.main, - fontSize: 14, - fontWeight: 700, - }, - cardContent: { - padding: theme.spacing(1, 0), - '&:last-child': { - paddingBottom: theme.spacing(1), - }, - }, - action: { - display: 'flex', - alignItems: 'center', - alignSelf: 'center', - marginRight: 0, - }, - headerAction: { - display: 'flex', - height: theme.spacing(4.5), - alignItems: 'center', - justifyContent: 'center', - color: theme.palette.maskColor.main, - fontSize: 14, - fontWeight: 700, - whiteSpace: 'nowrap', - }, - personas: { - maxWidth: 200, - overflow: 'hidden', - fontWeight: 700, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }, - cardIcon: { - height: 36, - width: 36, - borderRadius: '50%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - personaIcon: { - color: theme.palette.maskColor.white, - backgroundColor: '#FFB100', - boxShadow: '0px 6px 12px rgba(255, 177, 0, 0.2)', - }, - list: { - padding: 0, - }, - walletHeaderIcon: { - backgroundColor: '#1C68F3', - boxShadow: '0px 6px 12px rgba(28, 104, 243, 0.2)', - }, - wallets: { - margin: 0, - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - }, - wallet: { - width: 'auto', - flexWrap: 'nowrap', - }, - listItemIcon: { - marginRight: theme.spacing(1), - width: theme.spacing(4.5), - color: theme.palette.maskColor.second, - minWidth: 'unset', - }, - walletIcon: { - marginRight: theme.spacing(1), - color: theme.palette.maskColor.second, - minWidth: 'unset', - }, - listText: { - fontSize: 14, - }, - link: { - color: theme.palette.maskColor.main, - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start', - }, -})) - -interface PersonasBackupPreviewProps { - info: BackupSummary - selectable?: boolean - selected?: boolean - onChange?: (selected: boolean) => void -} - -export const PersonasBackupPreview = memo(function PersonasBackupPreview({ - info, - selectable, - selected, - onChange, -}) { - const t = useDashboardTrans() - const { classes, cx } = useStyles() - - const personas = info.personas.join(', ') - - return ( - - - -

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

- } - classes={{ action: classes.action }} - title={{t.personas()}} - action={ - info.personas.length ? - - - - {personas} - - {` (${info.personas.length})`} - - {selectable ? - onChange?.(event.target.checked)} /> - : null} - - : null - } - /> - - - {info.accounts}}> - - - - - {t.settings_backup_preview_associated_accounts()} - - - {info.contacts}}> - - - - {t.settings_backup_preview_contacts()} - - {info.files}}> - - - - {t.settings_backup_preview_file()} - - - - - ) -}) - -interface WalletsBackupPreviewProps { - wallets: string[] - selectable?: boolean - selected?: boolean - onChange?: (selected: boolean) => void -} - -export const WalletsBackupPreview = memo(function WalletsBackupPreview({ - wallets, - selectable, - selected, - onChange, -}) { - const t = useDashboardTrans() - const { classes, theme, cx } = useStyles() - - if (!wallets.length) return null - return ( - - - -
- } - title={{`${t.wallets()} (${wallets.length})`}} - action={ - selectable ? - onChange?.(event.target.checked)} /> - : null - } - /> - - - {wallets.map((wallet) => ( - - - - - - - - - - - - ))} - - - - ) -}) -interface BackupPreviewProps extends BoxProps { - info: BackupSummary -} - -export function BackupPreview({ info, ...rest }: BackupPreviewProps) { - return ( - - - - - ) -} diff --git a/packages/mask/dashboard/components/FooterLine/About.tsx b/packages/mask/dashboard/components/FooterLine/About.tsx deleted file mode 100644 index 3a760d052369..000000000000 --- a/packages/mask/dashboard/components/FooterLine/About.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { Icons } from '@masknet/icons' -import { Box, IconButton, Link, Typography } from '@mui/material' -import { makeStyles, getMaskColor } from '@masknet/theme' -import { - Facebook as FacebookIcon, - GitHub as GitHubIcon, - Telegram as TelegramIcon, - Twitter as TwitterIcon, -} from '@mui/icons-material' -import { useDashboardTrans } from '../../locales/index.js' -import { Version } from './Version.js' -import links from './links.json' -import { ABOUT_DIALOG_BACKGROUND } from '../../assets/index.js' -import type { ReactNode } from 'react' - -const useStyles = makeStyles()((theme) => ({ - wrapper: { - width: 580, - minHeight: 660, - lineHeight: 1.75, - }, - header: { - height: 300, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - background: `url(${ABOUT_DIALOG_BACKGROUND}) no-repeat center / cover`, - paddingTop: 75, - }, - version: { - color: '#FFF', - fontSize: 12, - marginTop: 12, - }, - main: { - fontSize: 16, - textAlign: 'center', - margin: '24px 68px 20px', - - '& > p': { - marginBottom: '36px', - }, - }, - getInTouch: { - fontSize: 16, - fontWeight: 'bold', - marginTop: 20, - }, - icon: { - color: theme.palette.text.primary, - }, - brands: { - marginTop: theme.spacing(1), - '& > *': { - margin: theme.spacing(0, 1), - cursor: 'pointer', - }, - }, - footer: { - borderTop: `1px solid ${theme.palette.divider}`, - color: getMaskColor(theme).iconLight, - fontSize: '0.77rem', - margin: theme.spacing(0, 2), - padding: theme.spacing(2, 2, 3, 6), - }, - link: { - color: getMaskColor(theme).iconLight, - }, -})) - -const brands: Record = { - 'https://www.facebook.com/masknetwork': , - 'https://twitter.com/realMaskNetwork': , - 'https://t.me/maskbook_group': , - 'https://discord.gg/4SVXvj7': , - 'https://github.com/DimensionDev/Maskbook': , -} - -function MaskIcon() { - return process.env.NODE_ENV === 'production' ? : -} -function MaskTitleIcon() { - return process.env.NODE_ENV === 'production' ? - - : -} - -export function About() { - const { classes } = useStyles() - const t = useDashboardTrans() - return ( - <> -
-
- - - - - -
-
- - {t.about_dialog_description()} - -
- {t.about_dialog_touch()} -
- {Object.keys(brands).map((href, key) => ( - - {brands[href]} - - ))} -
-
-
-
- - {t.about_dialog_feedback()} - - {links.MASK_EMAIL} - - - - {t.about_dialog_source_code()} - - {links.MASK_GITHUB} - - - - {t.about_dialog_license()} GNU AGPL 3.0 - -
-
- - ) -} diff --git a/packages/mask/dashboard/components/FooterLine/Version.tsx b/packages/mask/dashboard/components/FooterLine/Version.tsx deleted file mode 100644 index 767cf3f565a3..000000000000 --- a/packages/mask/dashboard/components/FooterLine/Version.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Typography } from '@mui/material' -import { useDashboardTrans } from '../../locales/index.js' -import { useBuildInfo } from '@masknet/shared-base-ui' - -export function Version({ className }: { className?: string }) { - const t = useDashboardTrans() - const env = useBuildInfo() - const version = env.VERSION || 'unknown' - - return ( - - {env.channel === 'stable' || !env.COMMIT_HASH ? - t.version_of_stable({ version }) - : t.version_of_unstable({ - version, - build: env.channel, - hash: env.COMMIT_HASH, - }) - } - - ) -} diff --git a/packages/mask/dashboard/components/FooterLine/index.tsx b/packages/mask/dashboard/components/FooterLine/index.tsx deleted file mode 100644 index ef4bcaf8c8b7..000000000000 --- a/packages/mask/dashboard/components/FooterLine/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { memo, useState, type HTMLProps } from 'react' -import { styled, Breadcrumbs, Dialog, IconButton, Link, Typography } from '@mui/material' -import { Close } from '@mui/icons-material' -import { openWindow, useBuildInfo } from '@masknet/shared-base-ui' -import { makeStyles, getMaskColor } from '@masknet/theme' -import { useDashboardTrans } from '../../locales/index.js' -import { About } from './About.js' -import { Version } from './Version.js' -import links from './links.json' - -const useStyles = makeStyles()((theme) => ({ - navRoot: { - padding: '40px 0', - }, - ol: { - justifyContent: 'space-around', - }, - footerLink: { - display: 'inline-flex', - padding: theme.spacing(0.5), - borderRadius: 0, - whiteSpace: 'nowrap', - verticalAlign: 'middle', - }, - separator: { - color: getMaskColor(theme).lineLight, - }, - closeButton: { - position: 'absolute', - right: theme.spacing(2.5), - top: theme.spacing(1), - color: theme.palette.text.secondary, - }, -})) - -const AboutDialog = styled(Dialog)` - padding: 0; - overflow: hidden; -` - -type FooterLinkBaseProps = React.PropsWithChildren<{ - title?: string -}> -type FooterLinkAnchorProps = FooterLinkBaseProps & { - href: string - onClick?(e: React.MouseEvent): void -} - -function FooterLinkItem(props: FooterLinkAnchorProps) { - const { classes } = useStyles() - return ( - - - {props.children} - - - ) -} - -export const FooterLine = memo((props: HTMLProps) => { - const t = useDashboardTrans() - const { classes } = useStyles() - const [isOpen, setOpen] = useState(false) - const env = useBuildInfo() - const version = env.VERSION - - const defaultVersionLink = `${links.DOWNLOAD_LINK_STABLE_PREFIX}/v${version}` - const openVersionLink = (event: React.MouseEvent) => { - // `MouseEvent.prototype.metaKey` on macOS (`Command` key), Windows (`Windows` key), Linux (`Super` key) - if (!env.COMMIT_HASH || (env.channel === 'stable' && !event.metaKey)) { - openWindow(defaultVersionLink) - } else { - openWindow(`${links.DOWNLOAD_LINK_UNSTABLE_PREFIX}/${env.COMMIT_HASH}`) - } - } - return ( -
- - Mask.io - { - e.preventDefault() - setOpen(true) - }}> - {t.about()} - - - - - {t.dashboard_source_code()} - {t.footer_bounty_list()} - {t.privacy_policy()} - - setOpen(false)}> - - setOpen(false)} - edge="end" - color="inherit"> - - - -
- ) -}) -FooterLine.displayName = 'FooterLine' diff --git a/packages/mask/dashboard/components/FooterLine/links.json b/packages/mask/dashboard/components/FooterLine/links.json deleted file mode 100644 index 374d0b1d292c..000000000000 --- a/packages/mask/dashboard/components/FooterLine/links.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MASK_OFFICIAL_WEBSITE": "https://mask.io", - "MASK_GITHUB": "https://github.com/DimensionDev/Maskbook", - "MASK_EMAIL": "info@mask.io", - "MOBILE_DOWNLOAD_LINK": "https://mask.io/download-links/#mobile", - "BOUNTY_LIST": "https://github.com/DimensionDev/Maskbook/issues?q=is%3Aissue+is%3Aopen+label%3A%22Bounty%3A+Open%22", - "DOWNLOAD_LINK_STABLE_PREFIX": "https://github.com/DimensionDev/Maskbook/releases/tag", - "DOWNLOAD_LINK_UNSTABLE_PREFIX": "https://github.com/DimensionDev/Maskbook/tree", - "MASK_PRIVACY_POLICY": "https://legal.mask.io/maskbook/privacy-policy-browser.html" -} diff --git a/packages/mask/dashboard/components/HeaderLine/index.tsx b/packages/mask/dashboard/components/HeaderLine/index.tsx deleted file mode 100644 index 56cea6ad976f..000000000000 --- a/packages/mask/dashboard/components/HeaderLine/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type HTMLProps, memo } from 'react' -import { useTheme } from '@mui/material' -import { Icons } from '@masknet/icons' - -export const HeaderLine = memo((props: HTMLProps) => { - const mode = useTheme().palette.mode - const Icon = mode === 'dark' ? Icons.MaskBanner : Icons.Mask - return ( -
- -
- ) -}) diff --git a/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx b/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx deleted file mode 100644 index 177a1ee5c1e2..000000000000 --- a/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { memo, useCallback } from 'react' -import { useDrop } from 'react-use' -import { makeStyles } from '@masknet/theme' -import { Grid, TextField, Typography } from '@mui/material' - -const useStyles = makeStyles()((theme) => ({ - input: { - backgroundColor: theme.palette.maskColor.input, - fontWeight: 700, - color: theme.palette.maskColor.main, - textAlign: 'center', - }, - no: { - color: theme.palette.maskColor.third, - fontSize: 14, - }, -})) - -interface DesktopMnemonicConfirmProps { - puzzleWords: string[] - indexes?: number[] - onChange(word: string, index: number): void - setAll?(words: string[]): void -} - -function parserPastingAllMnemonic(text: string) { - const result = [...text.matchAll(/([a-z])+/g)] - return result.length === 12 ? result : null -} - -export const DesktopMnemonicConfirm = memo(function DesktopMnemonicConfirm(props: DesktopMnemonicConfirmProps) { - const { classes } = useStyles() - const { puzzleWords, indexes, onChange, setAll } = props - useDrop({ onText: (text) => handlePaster(text) }) - - const handlePaster = useCallback( - (text: string) => { - if (!setAll) return - - const words = parserPastingAllMnemonic(text) - if (!words) return - setAll(words.map((x) => x[0])) - }, - [setAll], - ) - - return ( - - {puzzleWords.map((word, i) => { - const no = i + 1 - return ( - - {no}., - size: 'small', - inputProps: { - style: { - textAlign: 'center', - }, - }, - }} - disabled={!!(indexes && !indexes.includes(i))} - onChange={(e) => { - const text = e.target.value - if ( - (e.nativeEvent as InputEvent).inputType === 'insertFromPaste' && - parserPastingAllMnemonic(text) - ) { - return - } - onChange(text, indexes ? indexes.indexOf(i) : i) - }} - /> - - ) - })} - - ) -}) diff --git a/packages/mask/dashboard/components/Mnemonic/MnemonicReveal.tsx b/packages/mask/dashboard/components/Mnemonic/MnemonicReveal.tsx deleted file mode 100644 index cc65954ce534..000000000000 --- a/packages/mask/dashboard/components/Mnemonic/MnemonicReveal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Typography, Box } from '@mui/material' -import { makeStyles } from '@masknet/theme' - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'grid', - gridTemplateColumns: 'repeat(4,1fr)', - gap: theme.spacing(2), - paddingLeft: 0, - margin: 0, - }, - wordCard: { - backgroundColor: theme.palette.maskColor.bg, - padding: theme.spacing(1), - borderRadius: 8, - listStyleType: 'decimal', - listStylePosition: 'inside', - position: 'relative', - '&::marker': { - backgroundColor: theme.palette.maskColor.bg, - color: theme.palette.maskColor.third, - fontSize: 14, - }, - }, - text: { - width: '100%', - position: 'absolute', - left: 0, - top: 8, - display: 'flex', - justifyContent: 'center', - }, -})) - -interface MnemonicRevealProps extends withClasses<'container' | 'wordCard' | 'text'> { - words: string[] - indexed?: boolean - wordClass?: string - textClass?: string -} - -export function MnemonicReveal(props: MnemonicRevealProps) { - const { words } = props - const { classes } = useStyles(undefined, { props }) - - return ( - - {words.map((item, index) => ( - - - {item} - - - ))} - - ) -} diff --git a/packages/mask/dashboard/components/Mnemonic/index.tsx b/packages/mask/dashboard/components/Mnemonic/index.tsx deleted file mode 100644 index f0647aa79b30..000000000000 --- a/packages/mask/dashboard/components/Mnemonic/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DesktopMnemonicConfirm.js' -export * from './MnemonicReveal.js' diff --git a/packages/mask/dashboard/components/OnboardingWriter/index.tsx b/packages/mask/dashboard/components/OnboardingWriter/index.tsx deleted file mode 100644 index 2d4d7ebdfdfc..000000000000 --- a/packages/mask/dashboard/components/OnboardingWriter/index.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { isArray, sum } from 'lodash-es' -import { useState, useMemo, useEffect, cloneElement } from 'react' -import { makeStyles } from '@masknet/theme' - -const useStyles = makeStyles()((theme) => ({ - typed: { - fontSize: 36, - lineHeight: 1.2, - fontWeight: 700, - '& > strong': { - color: theme.palette.maskColor.highlight, - }, - }, - endTyping: { - opacity: 0.5, - }, -})) - -interface OnboardingWriterProps { - words: JSX.Element[] -} - -export function OnboardingWriter({ words }: OnboardingWriterProps) { - const { classes, cx } = useStyles() - - const [index, setIndex] = useState(0) - - const charSize = useMemo(() => { - return words.reduce((prev, current) => { - if (!isArray(current.props.children)) return prev - const size = sum( - current.props.children.map((x: string) => { - return x.length - }), - ) - - return prev + size - }, 0) - }, [words]) - - useEffect(() => { - const timer = setInterval(() => { - setIndex((prev) => { - if (prev > charSize) { - clearInterval(timer) - return prev - } - - return prev + 1 - }) - }, 50) - - return () => { - clearInterval(timer) - } - }, [charSize]) - - const jsx = useMemo(() => { - const newJsx = [] - let remain = index - for (const fragment of words) { - if (remain <= 0) break - const size = sum( - fragment.props.children.map((x: string) => { - return x.length - }), - ) - const take = Math.min(size, remain) - - remain -= take - - const [text, strongText] = fragment.props.children as string[] - - const props = { - ...fragment.props, - className: cx( - classes.typed, - remain !== 0 && fragment.key !== 'ready' && fragment.key !== 'wallets' ? - classes.endTyping - : undefined, - ), - } - if (take < text.length) newJsx.push(cloneElement(fragment, props, [text.slice(0, take)])) - else - newJsx.push( - cloneElement(fragment, props, [ - text, - {strongText.slice(0, take - text.length)}, - ]), - ) - } - - return newJsx - }, [words, index]) - - return <>{jsx} -} diff --git a/packages/mask/dashboard/components/PasswordField/index.tsx b/packages/mask/dashboard/components/PasswordField/index.tsx deleted file mode 100644 index b898aed1edd7..000000000000 --- a/packages/mask/dashboard/components/PasswordField/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { type ForwardedRef, useState, forwardRef } from 'react' -import { type MaskTextFieldProps } from '@masknet/theme' -import { IconButton, InputAdornment, TextField } from '@mui/material' -import { Icons } from '@masknet/icons' - -type PasswordFieldProps = Exclude & { show?: boolean } - -const PasswordField = forwardRef(({ show = true, ...props }: PasswordFieldProps, ref: ForwardedRef) => { - const [showPassword, setShowPassword] = useState(false) - - return ( - - setShowPassword(!showPassword)} - onMouseDown={(event) => event.preventDefault()} - edge="end" - size="small"> - {showPassword ? - - : } - - - : null, - }} - /> - ) -}) - -PasswordField.displayName = 'PasswordField' - -export default PasswordField diff --git a/packages/mask/dashboard/components/PrimaryButton/index.tsx b/packages/mask/dashboard/components/PrimaryButton/index.tsx deleted file mode 100644 index 329c9d5e3872..000000000000 --- a/packages/mask/dashboard/components/PrimaryButton/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ActionButton, makeStyles } from '@masknet/theme' -import { buttonClasses, type ButtonProps } from '@mui/material/Button' -import { memo } from 'react' - -interface ActionButtonProps extends ButtonProps { - width?: number | string - loading?: boolean -} - -const useStyles = makeStyles()((theme) => ({ - // eslint-disable-next-line tss-unused-classes/unused-classes - root: { - backgroundColor: theme.palette.maskColor.main, - color: theme.palette.maskColor.bottom, - fontWeight: 700, - fontSize: 16, - lineHeight: '20px', - ['&:hover']: { - backgroundColor: theme.palette.maskColor.main, - boxShadow: '0 8px 25px rgba(0, 0, 0, 0.2)', - }, - [`&.${buttonClasses.disabled}`]: { - background: theme.palette.maskColor.primaryMain, - opacity: 0.6, - color: theme.palette.maskColor.bottom, - }, - }, -})) - -export const PrimaryButton = memo(function PrimaryButton(props) { - const { width, loading, children, className, style, ...rest } = props - const { classes } = useStyles(undefined, { props: { classes: rest.classes } }) - return ( - - {children} - - ) -}) diff --git a/packages/mask/dashboard/components/RegisterFrame/ButtonContainer.tsx b/packages/mask/dashboard/components/RegisterFrame/ButtonContainer.tsx deleted file mode 100644 index 9cf9d7069b5b..000000000000 --- a/packages/mask/dashboard/components/RegisterFrame/ButtonContainer.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { PropsWithChildren } from 'react' -import { styled } from '@mui/material/styles' -import { buttonClasses, Stack } from '@mui/material' - -const ButtonContainerUI = styled(Stack)(({ theme }) => ({ - margin: `${theme.spacing(3.75)} auto`, - width: '75%', - [`& > .${buttonClasses.root}`]: { - width: '100%', - fontSize: 16, - }, - [theme.breakpoints.down('md')]: { - margin: `${theme.spacing(4)} auto`, - }, -})) - -interface ButtonGroupProps extends PropsWithChildren<{}> {} - -export function ButtonContainer({ children }: ButtonGroupProps) { - return ( - - {children} - - ) -} diff --git a/packages/mask/dashboard/components/RegisterFrame/ColumnContentHeader.tsx b/packages/mask/dashboard/components/RegisterFrame/ColumnContentHeader.tsx deleted file mode 100644 index eecdd03259b2..000000000000 --- a/packages/mask/dashboard/components/RegisterFrame/ColumnContentHeader.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { memo } from 'react' -import { styled } from '@mui/material/styles' -import { Button, Typography } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' - -const HeaderContainer = styled('header')(({ theme }) => ({ - width: '78%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - flexBasis: '240px', - - [theme.breakpoints.down('lg')]: { - flexBasis: '350px', - }, - - [theme.breakpoints.down('md')]: { - width: '95%', - flexBasis: '180px', - }, -})) - -const TitleContainer = styled('div')` - display: flex; - justify-content: space-between; - align-items: center; -` - -const Subtitle = styled(Typography)( - ({ theme }) => ` - padding-top: 30px; - color: ${theme.palette.mode === 'dark' ? MaskColorVar.textSecondary.alpha(0.8) : MaskColorVar.textPrimary} -`, -) - -const Action = styled(Button)(({ theme }) => ({ - display: 'inline-block', - color: theme.palette.mode === 'dark' ? MaskColorVar.textPrimary : MaskColorVar.primary, - fontWeight: 'bold', - textAlign: 'right', - '&:hover': { - background: 'transparent', - }, -})) - -interface HeaderProps { - title: string - subtitle?: string - action: { - name: string - callback(): void - } -} - -export const Header = memo(({ title, subtitle, action }: HeaderProps) => { - return ( - - - {title} - action.callback()}> - {action.name} - - - {subtitle ? - {subtitle} - : null} - - ) -}) diff --git a/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx b/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx deleted file mode 100644 index 7c34ab188cc6..000000000000 --- a/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { memo, type ComponentType } from 'react' -import { Icons } from '@masknet/icons' -import { Box, Typography } from '@mui/material' -import { styled } from '@mui/material/styles' -import { useDashboardTrans } from '../../locales/index.js' - -export const ColumnContentLayout: ComponentType = styled('div')` - display: flex; - flex-direction: column; - flex: 1; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; -` - -export const Body: ComponentType = styled('main')(({ theme }) => ({ - flex: '1 5', - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -export const Footer: ComponentType = styled('footer')(({ theme }) => ({ - flex: 1, - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -const LogoBoxStyled = styled(Box)(({ theme }) => ({ - marginBottom: theme.spacing(10), - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - [theme.breakpoints.down('md')]: { - marginBottom: theme.spacing(2), - }, -})) as any as typeof Box - -export const SignUpAccountLogo = styled(Icons.SignUpAccount)(() => ({ - width: '100%', - height: '96px', -})) as any as typeof Icons.SignUpAccount - -export const PersonaLogoBox = memo>(({ children }) => { - const t = useDashboardTrans() - return ( - - {children} - - {t.persona()} - - - ) -}) diff --git a/packages/mask/dashboard/components/RegisterFrame/ColumnLayout.tsx b/packages/mask/dashboard/components/RegisterFrame/ColumnLayout.tsx deleted file mode 100644 index e38828e1e187..000000000000 --- a/packages/mask/dashboard/components/RegisterFrame/ColumnLayout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { makeStyles } from '@masknet/theme' -import { Paper } from '@mui/material' -import { styled } from '@mui/material/styles' -import { FooterLine } from '../FooterLine/index.js' -import { HeaderLine } from '../HeaderLine/index.js' - -const Container = styled('div')( - ({ theme }) => ` - position: absolute; - display: flex; - justify-content: center; - align-items: center; - padding: ${theme.spacing(4)}; - height: 100%; - width: 100%; -`, -) - -const Content = styled('div')(` - width: 900px; - max-height: 90%; -`) - -const useStyles = makeStyles()((theme) => ({ - paper: { - padding: theme.spacing(6), - marginBottom: theme.spacing(1), - }, -})) - -interface ColumnLayoutProps extends React.PropsWithChildren<{}> { - haveFooter?: boolean -} - -export function ColumnLayout({ haveFooter = true, children }: ColumnLayoutProps) { - const { classes } = useStyles() - - return ( - - - - - {children} - - {haveFooter ? - - : null} - - - ) -} diff --git a/packages/mask/dashboard/components/Restore/AccountStatusBar.tsx b/packages/mask/dashboard/components/Restore/AccountStatusBar.tsx deleted file mode 100644 index 1e022556f814..000000000000 --- a/packages/mask/dashboard/components/Restore/AccountStatusBar.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { makeStyles } from '@masknet/theme' -import { Box, Button, Typography, type BoxProps } from '@mui/material' -import { memo } from 'react' - -const useStyles = makeStyles()((theme) => ({ - label: { - fontSize: 14, - fontWeight: 700, - }, - actionButton: { - fontSize: 14, - fontWeight: 700, - color: theme.palette.maskColor.main, - }, -})) -interface Props extends BoxProps { - label?: string - actionLabel: string - onAction: () => void -} -export const AccountStatusBar = memo(function AccountStatusBar({ label, actionLabel, onAction, ...rest }: Props) { - const { classes } = useStyles() - return ( - - {label ? - {label} - : null} - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx b/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx deleted file mode 100644 index b8468b20928e..000000000000 --- a/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { memo } from 'react' -import { Typography } from '@mui/material' -import { format as formatDateTime, fromUnixTime } from 'date-fns' -import type { BackupFileInfo } from '../../utils/type.js' -import { formatFileSize } from '@masknet/kit' -import { FileFrame } from '@masknet/shared' -import { makeStyles } from '@masknet/theme' - -const useStyles = makeStyles()((theme) => ({ - file: { - border: `1px solid ${theme.palette.maskColor.highlight}`, - }, - desc: { - fontSize: 12, - fontWeight: 700, - lineHeight: '16px', - }, -})) - -interface BackupInfoProps { - info: BackupFileInfo -} - -const getFileName = (rawUrl: string) => { - const url = new URL(rawUrl) - return url.pathname.split('/').pop() -} - -export const BackupInfoCard = memo(function BackupInfoCard({ info }: BackupInfoProps) { - const { classes } = useStyles() - return ( - {formatFileSize(info.size, true)}}> - {Number.isNaN(info.uploadedAt) ? null : ( - - {formatDateTime(fromUnixTime(info.uploadedAt), 'yyyy-MM-dd HH:mm')} - - )} - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/ConfirmSynchronizePasswordDialog.tsx b/packages/mask/dashboard/components/Restore/ConfirmSynchronizePasswordDialog.tsx deleted file mode 100644 index 7f5b1968ad3f..000000000000 --- a/packages/mask/dashboard/components/Restore/ConfirmSynchronizePasswordDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { memo } from 'react' -import { Icons } from '@masknet/icons' -import { getMaskColor, MaskDialog } from '@masknet/theme' -import { Button, DialogActions, DialogContent, Stack, Typography } from '@mui/material' -import { useDashboardTrans } from '../../locales/index.js' - -interface ConfirmSynchronizePasswordDialogProps { - open: boolean - onClose(): void - onConform(): void -} - -export const ConfirmSynchronizePasswordDialog = memo( - function ConfirmSynchronizePasswordDialog({ open, onClose, onConform }) { - const t = useDashboardTrans() - - return ( - - - - - getMaskColor(t).greenMain }} fontSize={24}> - {t.successful()} - - - - {t.sign_in_account_cloud_backup_synchronize_password_tip()} - - - - - - - - ) - }, -) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx deleted file mode 100644 index 5f975f9d279d..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { decryptBackup } from '@masknet/backup-format' -import { decode, encode } from '@msgpack/msgpack' -import { Box } from '@mui/material' -import { memo, useCallback, useLayoutEffect, useState } from 'react' -import Services from '#services' -import { usePersonaRecovery } from '../../../contexts/index.js' -import { useDashboardTrans } from '../../../locales/index.js' -import { fetchBackupValue } from '../../../utils/api.js' -import PasswordField from '../../PasswordField/index.js' -import { PrimaryButton } from '../../PrimaryButton/index.js' -import { AccountStatusBar } from '../AccountStatusBar.js' -import { BackupInfoCard } from '../BackupInfoCard.js' -import { RestoreContext } from './RestoreProvider.js' -import { RestoreStep } from './restoreReducer.js' - -export const ConfirmBackupInfo = memo(function ConfirmBackupInfo() { - const t = useDashboardTrans() - const [password, setPassword] = useState('') - const [errorMessage, setErrorMessage] = useState('') - const { state, dispatch } = RestoreContext.useContainer() - const { account, backupFileInfo, loading } = state - - const decrypt = useCallback(async (account: string, password: string, encryptedValue: ArrayBuffer) => { - try { - const decrypted = await decryptBackup(encode(account + password), encryptedValue) - return JSON.stringify(decode(decrypted)) - } catch { - return null - } - }, []) - const handleNext = useCallback(async () => { - if (!backupFileInfo) return - dispatch({ type: 'SET_LOADING', loading: true }) - - const backupEncrypted = await fetchBackupValue(backupFileInfo.downloadURL) - const backupDecrypted = await decrypt(account, password, backupEncrypted) - - if (!backupDecrypted) { - dispatch({ type: 'SET_LOADING', loading: false }) - return setErrorMessage(t.sign_in_account_cloud_backup_decrypt_failed()) - } - - const summary = await Services.Backup.generateBackupSummary(backupDecrypted) - if (summary.isErr()) { - dispatch({ type: 'SET_LOADING', loading: false }) - return setErrorMessage(t.sign_in_account_cloud_backup_decrypt_failed()) - } - dispatch({ type: 'SET_LOADING', loading: false }) - - dispatch({ type: 'SET_PASSWORD', password }) - dispatch({ type: 'TO_STEP', step: RestoreStep.Restore }) - dispatch({ type: 'SET_BACKUP_SUMMARY', summary: summary.value, backupDecrypted }) - }, [password, account, backupFileInfo]) - - const handleSwitchAccount = useCallback(() => { - dispatch({ type: 'TO_INPUT' }) - }, []) - - const { fillSubmitOutlet } = usePersonaRecovery() - useLayoutEffect(() => { - return fillSubmitOutlet( - - {t.restore()} - , - ) - }, [handleNext, t, loading]) - - if (!backupFileInfo) return null - - return ( - - - - - - - { - setErrorMessage('') - setPassword(e.currentTarget.value) - }} - error={!!errorMessage} - helperText={errorMessage} - /> - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx deleted file mode 100644 index dd596cbf9e5b..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { SendingCodeField, useCustomSnackbar } from '@masknet/theme' -import { Box, TextField } from '@mui/material' -import { memo, useCallback, useLayoutEffect, useState } from 'react' -import { useAsyncFn } from 'react-use' -import { usePersonaRecovery } from '../../../contexts/RecoveryContext.js' -import { useDashboardTrans } from '../../../locales/index.js' -import { sendCode, type RestoreQueryError } from '../../../utils/api.js' -import { emailRegexp } from '../../../utils/regexp.js' -import { BackupAccountType } from '@masknet/shared-base' -import { Locale, Scenario } from '../../../utils/type.js' -import { PrimaryButton } from '../../PrimaryButton/index.js' -import { useLanguage } from '../../../../shared-ui/index.js' -import { RestoreContext } from './RestoreProvider.js' - -export const EmailField = memo(function EmailField() { - const language = useLanguage() - const t = useDashboardTrans() - const [invalidEmail, setInvalidEmail] = useState(false) - const { showSnackbar } = useCustomSnackbar() - const [error, setError] = useState('') - const [codeError, setCodeError] = useState('') - - const { state, dispatch, downloadBackupInfo } = RestoreContext.useContainer() - const { emailForm, loading } = state - const { account, code } = emailForm - const setCode = useCallback((code: string) => { - dispatch({ type: 'SET_EMAIL', form: { code } }) - }, []) - - const [{ error: sendCodeError }, handleSendCodeFn] = useAsyncFn(async () => { - const type = BackupAccountType.Email - await sendCode({ - account, - type, - scenario: Scenario.backup, - locale: language.includes('zh') ? Locale.zh : Locale.en, - }) - showSnackbar(t.sign_in_account_cloud_backup_send_email_success({ type }), { variant: 'success' }) - }, [account, language]) - - const validCheck = () => { - if (!account) return - - const isValid = emailRegexp.test(account) - setInvalidEmail(!isValid) - } - - const { fillSubmitOutlet } = usePersonaRecovery() - const emailNotReady = !account || invalidEmail - const disabled = emailNotReady || code.length !== 6 - useLayoutEffect(() => { - return fillSubmitOutlet( - { - dispatch({ type: 'SET_LOADING', loading: true }) - try { - const backupFileInfo = await downloadBackupInfo(BackupAccountType.Email, account, code) - dispatch({ type: 'SET_BACKUP_INFO', info: backupFileInfo }) - dispatch({ type: 'NEXT_STEP' }) - } catch (err) { - const message = (err as RestoreQueryError).message - if (['code not found', 'code mismatch'].includes(message)) - setCodeError(t.incorrect_verification_code()) - else setError(message) - } finally { - dispatch({ type: 'SET_LOADING', loading: false }) - } - }} - loading={loading} - disabled={disabled}> - {t.continue()} - , - ) - }, [account, code, loading, disabled]) - - const hasError = sendCodeError?.message.includes('SendTemplatedEmail') || invalidEmail || !!error - const errorMessage = - sendCodeError?.message.includes('SendTemplatedEmail') || invalidEmail ? - t.sign_in_account_cloud_backup_email_format_error() - : error || '' - - return ( - <> - { - setError('') - dispatch({ - type: 'SET_EMAIL', - form: { account: event.target.value }, - }) - }} - error={hasError} - placeholder={t.data_recovery_email()} - helperText={errorMessage} - type="email" - size="small" - /> - - - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/InputForm.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/InputForm.tsx deleted file mode 100644 index 460f94435a3e..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/InputForm.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Icons } from '@masknet/icons' -import { makeStyles } from '@masknet/theme' -import { Box, FormControlLabel, Radio, RadioGroup, type BoxProps } from '@mui/material' -import { memo, useState } from 'react' -import { BackupAccountType } from '@masknet/shared-base' -import { RestoreContext } from './RestoreProvider.js' -import { EmailField } from './EmailField.js' -import { PhoneField } from './PhoneField.js' -import { RestoreStep } from './restoreReducer.js' - -const useStyles = makeStyles()((theme) => ({ - purposes: { - display: 'flex', - flexWrap: 'nowrap', - flexDirection: 'row', - }, - purpose: { - width: '50%', - margin: 0, - }, - control: { - padding: 0, - marginRight: theme.spacing(1), - color: theme.palette.maskColor.second, - }, - selectedLabel: { - fontSize: 16, - fontWeight: 700, - color: theme.palette.maskColor.main, - }, - label: { - fontSize: 16, - fontWeight: 700, - color: theme.palette.maskColor.second, - }, - checked: { - color: theme.palette.maskColor.primary, - boxShadow: '0px 4px 10px rgba(28, 104, 243, 0.2)', - }, -})) - -const StepMap = { - [BackupAccountType.Email]: RestoreStep.InputEmail, - [BackupAccountType.Phone]: RestoreStep.InputPhone, -} as const - -export const InputForm = memo(function InputForm(props: BoxProps) { - const { classes, theme } = useStyles() - const { state, dispatch } = RestoreContext.useContainer() - const [accountType, setAccountType] = useState(BackupAccountType.Email) - - if (![RestoreStep.InputEmail, RestoreStep.InputPhone].includes(state.step)) return null - - return ( - - { - const accountType = e.currentTarget.value as BackupAccountType - dispatch({ type: 'TO_STEP', step: StepMap[accountType] }) - dispatch({ type: 'SET_ACCOUNT_TYPE', accountType }) - setAccountType(accountType) - }}> - } - checkedIcon={} - /> - } - /> - } - checkedIcon={} - /> - } - /> - - - {state.step === RestoreStep.InputEmail ? - - : } - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/PhoneField.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/PhoneField.tsx deleted file mode 100644 index a132bde0aeb2..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/PhoneField.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { SendingCodeField, useCustomSnackbar } from '@masknet/theme' -import { Box } from '@mui/material' -import guessCallingCode from 'guess-calling-code' -import { pick } from 'lodash-es' -import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react' -import { useAsyncFn } from 'react-use' -import { usePersonaRecovery } from '../../../contexts/index.js' -import { useDashboardTrans } from '../../../locales/index.js' -import { useLanguage } from '../../../../shared-ui/index.js' -import { sendCode, type RestoreQueryError } from '../../../utils/api.js' -import { phoneRegexp } from '../../../utils/regexp.js' -import { BackupAccountType } from '@masknet/shared-base' -import { Locale, Scenario } from '../../../utils/type.js' -import { PrimaryButton } from '../../PrimaryButton/index.js' -import { RestoreContext } from './RestoreProvider.js' -import { PhoneNumberField } from '@masknet/shared' - -export const PhoneField = memo(function PhoneField() { - const language = useLanguage() - const t = useDashboardTrans() - const [invalidPhone, setInvalidPhone] = useState(false) - const { showSnackbar } = useCustomSnackbar() - const [error, setError] = useState('') - const [codeError, setCodeError] = useState('') - const { state, dispatch, downloadBackupInfo } = RestoreContext.useContainer() - const { loading, phoneForm } = state - const { account, code, dialingCode } = phoneForm - const phoneConfig = useMemo(() => pick(phoneForm, 'dialingCode', 'phone'), [phoneForm]) - const onPhoneNumberChange = useCallback( - (phoneNumber: string) => { - dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, phone: phoneNumber } }) - }, - [phoneConfig], - ) - const onCountryCodeChange = useCallback( - (code: string) => { - dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, dialingCode: code } }) - }, - [phoneConfig], - ) - - useEffect(() => { - if (dialingCode) return - dispatch({ type: 'SET_PHONE', form: { dialingCode: (guessCallingCode.default || guessCallingCode)() } }) - }, [!dialingCode]) - - const validCheck = () => { - if (!account) return - const isValid = phoneRegexp.test(account) - setInvalidPhone(!isValid) - } - const [{ error: sendCodeError }, handleSendCode] = useAsyncFn(async () => { - const type = BackupAccountType.Phone - showSnackbar(t.sign_in_account_cloud_backup_send_email_success({ type }), { variant: 'success' }) - await sendCode({ - account, - type, - scenario: Scenario.backup, - locale: language.includes('zh') ? Locale.zh : Locale.en, - }) - }, [account, language]) - - const { fillSubmitOutlet } = usePersonaRecovery() - const phoneNotReady = !account || invalidPhone || !phoneRegexp.test(account) - const disabled = phoneNotReady || code.length !== 6 || !!error || loading - useLayoutEffect(() => { - return fillSubmitOutlet( - { - dispatch({ type: 'SET_LOADING', loading: true }) - try { - const backupInfo = await downloadBackupInfo(BackupAccountType.Phone, account, code) - dispatch({ type: 'SET_BACKUP_INFO', info: backupInfo }) - dispatch({ type: 'NEXT_STEP' }) - } catch (err) { - const message = (err as RestoreQueryError).message - if (['code not found', 'code mismatch'].includes(message)) - setCodeError(t.incorrect_verification_code()) - else setError(message) - } finally { - dispatch({ type: 'SET_LOADING', loading: false }) - } - }} - loading={loading} - disabled={disabled}> - {t.continue()} - , - ) - }, [account, code, disabled, loading]) - - return ( - <> - onPhoneNumberChange(event.target.value)} - error={invalidPhone} - helperText={invalidPhone ? t.data_recovery_invalid_mobile() : error || ''} - value={phoneForm.phone} - /> - - { - setCodeError('') - dispatch({ type: 'SET_PHONE', form: { code } }) - }} - errorMessage={sendCodeError?.message || codeError} - onSend={handleSendCode} - placeholder={t.data_recovery_mobile_code()} - disabled={phoneNotReady} - inputProps={{ - maxLength: 6, - }} - /> - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/RestoreProvider.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/RestoreProvider.tsx deleted file mode 100644 index bf8cec531c0e..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/RestoreProvider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback, useReducer } from 'react' -import { createContainer } from 'unstated-next' -import { fetchDownloadLink } from '../../../utils/api.js' -import type { BackupAccountType } from '@masknet/shared-base' -import { initialState, restoreReducer } from './restoreReducer.js' - -function useRestoreState() { - const [state, dispatch] = useReducer(restoreReducer, initialState) - const downloadBackupInfo = useCallback((type: BackupAccountType, account: string, code: string) => { - return fetchDownloadLink({ type, account, code }) - }, []) - - return { state, dispatch, downloadBackupInfo } -} - -export const RestoreContext = createContainer(useRestoreState) -RestoreContext.Provider.displayName = 'RestoreContextProvider' diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx deleted file mode 100644 index 47b2044b6f4e..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import urlcat from 'urlcat' -import { memo, useCallback, useLayoutEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { Box } from '@mui/material' -import { DashboardRoutes, BackupAccountType } from '@masknet/shared-base' -import { useCustomSnackbar } from '@masknet/theme' -import Services from '#services' -import { useDashboardTrans } from '../../../locales/index.js' - -import { ConfirmSynchronizePasswordDialog } from '../ConfirmSynchronizePasswordDialog.js' -import { usePersonaRecovery } from '../../../contexts/index.js' -import { PrimaryButton } from '../../PrimaryButton/index.js' -import { RestoreContext } from './RestoreProvider.js' -import { RestoreStep } from './restoreReducer.js' -import { InputForm } from './InputForm.js' -import { ConfirmBackupInfo } from './ConfirmBackupInfo.js' -import { UserContext } from '../../../../shared-ui/index.js' -import { BackupPreview } from '../../BackupPreview/index.js' -import { PersonaContext } from '@masknet/shared' - -interface RestoreProps { - onRestore: () => Promise -} - -const Restore = memo(function Restore({ onRestore }: RestoreProps) { - const t = useDashboardTrans() - const { fillSubmitOutlet } = usePersonaRecovery() - const { state } = RestoreContext.useContainer() - - useLayoutEffect(() => { - return fillSubmitOutlet( - - {t.restore()} - , - ) - }, [onRestore, state.loading]) - - if (!state.backupSummary) return null - - return -}) - -const RestoreFromCloudInner = memo(function RestoreFromCloudInner() { - const t = useDashboardTrans() - const navigate = useNavigate() - const { showSnackbar } = useCustomSnackbar() - const { user, updateUser } = UserContext.useContainer() - const { currentPersona } = PersonaContext.useContainer() - const { state, dispatch } = RestoreContext.useContainer() - const { account, accountType, backupSummary, password, backupDecrypted } = state - - const [openSynchronizePasswordDialog, toggleSynchronizePasswordDialog] = useState(false) - - const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) - - const restoreCallback = useCallback(async () => { - if (!currentPersona) { - const lastedPersona = await Services.Identity.queryLastPersonaCreated() - if (lastedPersona) { - await changeCurrentPersona(lastedPersona) - } - } - if (account) { - if (!user.email && accountType === BackupAccountType.Email) { - updateUser({ email: account }) - } else if (!user.phone) { - updateUser({ phone: account }) - } - } - toggleSynchronizePasswordDialog(true) - }, [currentPersona, account, accountType, user, toggleSynchronizePasswordDialog, updateUser, changeCurrentPersona]) - - const handleRestore = useCallback(async () => { - dispatch({ type: 'SET_LOADING', loading: true }) - try { - if (backupSummary?.countOfWallets) { - const hasPassword = await Services.Wallet.hasPassword() - if (!hasPassword) await Services.Wallet.setDefaultPassword() - } - - await Services.Backup.restoreBackup(backupDecrypted) - await restoreCallback() - dispatch({ type: 'SET_LOADING', loading: false }) - navigate(urlcat(DashboardRoutes.SignUpPersonaOnboarding, { count: backupSummary?.countOfWallets }), { - replace: true, - }) - } catch { - showSnackbar(t.sign_in_account_cloud_restore_failed(), { variant: 'error' }) - } - }, [user, backupSummary]) - - const onCloseSynchronizePassword = useCallback(() => { - toggleSynchronizePasswordDialog(false) - navigate(DashboardRoutes.Personas, { replace: true }) - }, [navigate]) - - const synchronizePassword = useCallback(() => { - if (!account || !password) return - updateUser({ backupPassword: password }) - onCloseSynchronizePassword() - }, [account, password, updateUser]) - - return ( - - {[RestoreStep.InputEmail, RestoreStep.InputPhone].includes(state.step) ? - - : state.step === RestoreStep.Decrypt ? - - : } - {openSynchronizePasswordDialog ? - onCloseSynchronizePassword()} - onConform={synchronizePassword} - /> - : null} - - ) -}) - -export const RestoreFromCloud = memo(function RestoreFromCloud() { - return ( - - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts b/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts deleted file mode 100644 index dc09023815c6..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { BackupSummary } from '@masknet/backup-format' -import { produce } from 'immer' -import { type BackupFileInfo } from '../../../utils/type.js' -import { BackupAccountType } from '@masknet/shared-base' - -export enum RestoreStep { - InputEmail = 'InputEmail', - InputPhone = 'InputPhone', - Decrypt = 'Decrypt', - Restore = 'Restore', -} - -export interface RestoreState { - loading: boolean - accountType: BackupAccountType - account: string - password: string - step: RestoreStep - emailForm: { - account: string - code: string - } - phoneForm: { - account: string - code: string - dialingCode: string - phone: string - } - backupFileInfo: BackupFileInfo | null - backupSummary: BackupSummary | null - backupDecrypted: string -} - -export const initialState: RestoreState = { - loading: false, - accountType: BackupAccountType.Email, - account: '', - password: '', - step: RestoreStep.InputEmail, - emailForm: { - account: '', - code: '', - }, - phoneForm: { - dialingCode: '', - phone: '', - account: '', - code: '', - }, - backupFileInfo: null, - backupSummary: null, - backupDecrypted: '', -} - -type Action = - | { - type: 'SET_ACCOUNT_TYPE' - accountType: BackupAccountType - } - | { - type: 'NEXT_STEP' - } - | { - type: 'TO_STEP' - step: RestoreStep - } - | { - type: 'TO_INPUT' - } - | { - type: 'SET_EMAIL' - form: Partial - } - | { - type: 'SET_PHONE' - form: Partial - } - | { - type: 'SET_VALIDATION' - } - | { - type: 'SET_BACKUP_INFO' - info: BackupFileInfo - } - | { - type: 'SET_BACKUP_SUMMARY' - summary: BackupSummary - backupDecrypted: string - } - | { - type: 'SET_PASSWORD' - password: string - } - | { - type: 'SET_LOADING' - loading: boolean - } - -function stepReducer(step: RestoreStep) { - switch (step) { - case RestoreStep.InputEmail: - case RestoreStep.InputPhone: - return RestoreStep.Decrypt - case RestoreStep.Decrypt: - return RestoreStep.Restore - default: - return step - } -} - -export function restoreReducer(state: RestoreState, action: Action) { - return produce(state, (draft) => { - switch (action.type) { - case 'SET_ACCOUNT_TYPE': - draft.accountType = action.accountType - break - case 'NEXT_STEP': - draft.step = stepReducer(draft.step) - break - case 'TO_STEP': - draft.step = action.step - break - case 'TO_INPUT': - draft.step = - draft.accountType === BackupAccountType.Email ? RestoreStep.InputEmail : RestoreStep.InputPhone - break - case 'SET_EMAIL': - Object.assign(draft.emailForm, action.form) - if (action.form.code) draft.emailForm.code = action.form.code.replaceAll(/\D/g, '') - break - case 'SET_PHONE': - Object.assign(draft.phoneForm, action.form) - draft.phoneForm.account = `+${draft.phoneForm.dialingCode} ${draft.phoneForm.phone}` - if (action.form.code) draft.phoneForm.code = action.form.code.replaceAll(/\D/g, '') - break - case 'SET_VALIDATION': - break - case 'SET_BACKUP_INFO': - draft.backupFileInfo = action.info - break - case 'SET_PASSWORD': - draft.password = action.password - break - case 'SET_BACKUP_SUMMARY': - draft.backupSummary = action.summary - draft.backupDecrypted = action.backupDecrypted - break - case 'SET_LOADING': - draft.loading = action.loading - break - } - - // Update current account - draft.account = - draft.accountType === BackupAccountType.Email ? draft.emailForm.account : draft.phoneForm.account - }) -} diff --git a/packages/mask/dashboard/components/Restore/RestoreFromMnemonic.tsx b/packages/mask/dashboard/components/Restore/RestoreFromMnemonic.tsx deleted file mode 100644 index 305bb87add45..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromMnemonic.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { makeStyles } from '@masknet/theme' -import { Box, Typography } from '@mui/material' -import { some } from 'lodash-es' -import { useCallback, useLayoutEffect } from 'react' -import { useList } from 'react-use' -import { usePersonaRecovery } from '../../contexts/index.js' -import { useDashboardTrans } from '../../locales/index.js' -import { DesktopMnemonicConfirm } from '../Mnemonic/index.js' -import { PrimaryButton } from '../PrimaryButton/index.js' - -const useStyles = makeStyles()((theme) => ({ - error: { - marginTop: theme.spacing(2), - color: theme.palette.maskColor.danger, - }, -})) - -interface RestoreFromMnemonicProp { - handleRestoreFromMnemonic?: (values: string[]) => Promise - error?: string - setError?: (error: string) => void -} - -export function RestoreFromMnemonic({ handleRestoreFromMnemonic, error, setError }: RestoreFromMnemonicProp) { - const { classes } = useStyles() - const t = useDashboardTrans() - const [values, { updateAt, set: setMnemonic }] = useList(Array.from({ length: 12 }, () => '')) - const { fillSubmitOutlet } = usePersonaRecovery() - const handleWordChange = useCallback((word: string, index: number) => { - updateAt(index, word) - setError?.('') - }, []) - - const handleImport = useCallback(async () => handleRestoreFromMnemonic?.(values), [values]) - - useLayoutEffect(() => { - return fillSubmitOutlet( - !value)}> - {t.continue()} - , - ) - }, [handleImport, values]) - - return ( - - - {error ? - - {error} - - : null} - - ) -} diff --git a/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx b/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx deleted file mode 100644 index bba7e4517fb9..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import { Controller, useForm } from 'react-hook-form' -import type { UseFormSetError, SubmitHandler } from 'react-hook-form' -import { makeStyles } from '@masknet/theme' -import { Box, TextField } from '@mui/material' -import { memo, useCallback, useLayoutEffect } from 'react' -import { useNavigate } from 'react-router-dom' -import { z } from 'zod' -import { useDashboardTrans } from '../../locales/index.js' -import { PrimaryButton } from '../PrimaryButton/index.js' -import { usePersonaRecovery } from '../../contexts/index.js' - -const useStyles = makeStyles()((theme) => ({ - input: { - paddingTop: 12, - backgroundColor: theme.palette.maskColor.input, - color: theme.palette.maskColor.main, - }, -})) - -const schema = z.object({ - privateKey: z.string(), -}) -export type FormInputs = z.infer - -interface RestoreFromPrivateKeyProps { - handleRestoreFromPrivateKey?: (data: FormInputs, onError: UseFormSetError) => Promise - multiline?: boolean -} - -export const RestoreFromPrivateKey = memo(function RestoreFromPrivateKey({ - handleRestoreFromPrivateKey, - multiline, -}: RestoreFromPrivateKeyProps) { - const { classes } = useStyles() - const navigate = useNavigate() - const t = useDashboardTrans() - const { fillSubmitOutlet } = usePersonaRecovery() - - const { - control, - handleSubmit, - setError, - formState: { errors, isSubmitting, isDirty }, - } = useForm({ - mode: 'onChange', - resolver: zodResolver(schema), - defaultValues: { - privateKey: '', - }, - }) - - const onSubmit: SubmitHandler = useCallback( - async (data) => { - await handleRestoreFromPrivateKey?.(data, setError) - }, - [navigate, setError, handleRestoreFromPrivateKey], - ) - - useLayoutEffect(() => { - return fillSubmitOutlet( - - {t.continue()} - , - ) - }, [isSubmitting, isDirty, handleSubmit, onSubmit]) - - return ( - - ( - - )} - name="privateKey" - /> - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestorePersonaFromLocal.tsx b/packages/mask/dashboard/components/Restore/RestorePersonaFromLocal.tsx deleted file mode 100644 index c8ea3442d382..000000000000 --- a/packages/mask/dashboard/components/Restore/RestorePersonaFromLocal.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { memo, useCallback, useLayoutEffect, useMemo, useState } from 'react' -import { useAsync } from 'react-use' -import { Box, Button, Typography } from '@mui/material' -import { type BackupSummary, decryptBackup } from '@masknet/backup-format' -import { Icons } from '@masknet/icons' -import { delay } from '@masknet/kit' -import { FileFrame, UploadDropArea } from '@masknet/shared' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' -import { decode, encode } from '@msgpack/msgpack' -import Services from '#services' -import { usePersonaRecovery } from '../../contexts/RecoveryContext.js' -import { useDashboardTrans } from '../../locales/index.js' -import PasswordField from '../PasswordField/index.js' -import { PrimaryButton } from '../PrimaryButton/index.js' -import { AccountStatusBar } from './AccountStatusBar.js' -import { BackupPreview } from '../BackupPreview/index.js' - -enum RestoreStatus { - WaitingInput = 0, - Verified = 1, - Decrypting = 2, -} - -const supportedFileType = { - json: 'application/json', - octetStream: 'application/octet-stream', - macBinary: 'application/macbinary', -} - -const useStyles = makeStyles()((theme) => ({ - uploadedFile: { - marginTop: theme.spacing(1.5), - }, - desc: { - color: theme.palette.maskColor.second, - fontWeight: 700, - fontSize: 12, - marginTop: 7, - }, -})) -interface RestoreFromLocalProps { - onRestore: (count?: number) => Promise -} - -export const RestorePersonaFromLocal = memo(function RestorePersonaFromLocal({ onRestore }: RestoreFromLocalProps) { - const { classes, theme } = useStyles() - const t = useDashboardTrans() - const { showSnackbar } = useCustomSnackbar() - const { fillSubmitOutlet } = usePersonaRecovery() - - const [file, setFile] = useState(null) - const [summary, setSummary] = useState(null) - const [backupValue, setBackupValue] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState('') - const [restoreStatus, setRestoreStatus] = useState(RestoreStatus.WaitingInput) - const [readingFile, setReadingFile] = useState(false) - const [processing, setProcessing] = useState(false) - - const reset = useCallback(() => { - setFile(null) - setBackupValue('') - setSummary(null) - setPassword('') - setRestoreStatus(RestoreStatus.WaitingInput) - }, []) - - const handleSetFile = useCallback(async (file: File) => { - setFile(file) - if (file.type === supportedFileType.json) { - setReadingFile(true) - const [value] = await Promise.all([file.text(), delay(1000)]) - setBackupValue(value) - setReadingFile(false) - } else if ([supportedFileType.octetStream, supportedFileType.macBinary].includes(file.type)) { - setRestoreStatus(RestoreStatus.Decrypting) - } else { - reset() - showSnackbar(t.sign_in_account_cloud_backup_not_support(), { variant: 'error' }) - } - }, []) - - const { loading: getSummaryLoading } = useAsync(async () => { - if (!backupValue) return - - const summary = await Services.Backup.generateBackupSummary(backupValue) - if (summary.isOk()) { - setSummary(summary.value) - setRestoreStatus(RestoreStatus.Verified) - } else { - showSnackbar(t.sign_in_account_cloud_backup_not_support(), { variant: 'error' }) - setRestoreStatus(RestoreStatus.WaitingInput) - setBackupValue('') - } - }, [backupValue]) - - const decryptBackupFile = useCallback(async () => { - if (!file) return - setProcessing(true) - try { - setReadingFile(true) - const [decrypted] = await Promise.all([ - file.arrayBuffer().then((buffer) => decryptBackup(encode(password), buffer)), - delay(1000), - ]) - const decoded = decode(decrypted) - setBackupValue(JSON.stringify(decoded)) - } catch (error_) { - setError(t.incorrect_backup_password()) - } finally { - setReadingFile(false) - setProcessing(false) - } - }, [file, password, t]) - - const restoreDB = useCallback(async () => { - try { - setProcessing(true) - // If json has wallets, restore in popup. - if (summary?.countOfWallets) { - const hasPassword = await Services.Wallet.hasPassword() - if (!hasPassword) await Services.Wallet.setDefaultPassword() - } - await Services.Backup.restoreBackup(backupValue) - - await onRestore(summary?.countOfWallets) - } catch { - showSnackbar(t.sign_in_account_cloud_backup_failed(), { variant: 'error' }) - } finally { - setProcessing(false) - } - }, [backupValue, onRestore, summary]) - - const loading = readingFile || processing || getSummaryLoading - const disabled = useMemo(() => { - if (loading) return true - if (restoreStatus === RestoreStatus.Verified) return !summary - if (restoreStatus === RestoreStatus.Decrypting) return !password - return !file - }, [loading, !file, restoreStatus, summary, !password]) - - useLayoutEffect(() => { - return fillSubmitOutlet( - - {restoreStatus !== RestoreStatus.Verified ? t.continue() : t.restore()} - , - ) - }, [restoreStatus, decryptBackupFile, restoreDB, disabled, loading]) - - return ( - - {restoreStatus !== RestoreStatus.Verified ? - - : null} - {file && restoreStatus !== RestoreStatus.Verified ? - - - - }> - - {readingFile ? t.file_unpacking() : t.file_unpacking_completed()} - - - : null} - {restoreStatus === RestoreStatus.Decrypting ? - - setPassword(e.target.value)} - error={!!error} - helperText={error} - autoFocus - /> - - : restoreStatus === RestoreStatus.Verified && summary ? - <> - - - - : null} - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx b/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx deleted file mode 100644 index a5c1ffa4d725..000000000000 --- a/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { Icons } from '@masknet/icons' -import { delay } from '@masknet/kit' -import { FileFrame, UploadDropArea } from '@masknet/shared' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' -import { Box, Button, Typography } from '@mui/material' -import { memo, useCallback, useLayoutEffect, useState } from 'react' -import { usePersonaRecovery } from '../../contexts/RecoveryContext.js' -import { useDashboardTrans } from '../../locales/index.js' -import PasswordField from '../PasswordField/index.js' -import { PrimaryButton } from '../PrimaryButton/index.js' - -const useStyles = makeStyles()((theme) => ({ - uploadedFile: { - marginTop: theme.spacing(1.5), - }, - desc: { - color: theme.palette.maskColor.second, - fontWeight: 700, - fontSize: 12, - marginTop: 7, - }, -})) -interface RestoreFromLocalProps { - onRestore: (keyStoreContent: string, keyStorePassword: string) => Promise - setError: (error: string) => void - error: string -} - -export const RestoreWalletFromLocal = memo(function RestorePersonaFromLocal({ - onRestore, - setError, - error, -}: RestoreFromLocalProps) { - const { classes, theme } = useStyles() - const t = useDashboardTrans() - const { fillSubmitOutlet } = usePersonaRecovery() - - const [keyStoreContent, setKeyStoreContent] = useState('') - const [keyStorePassword, setKeyStorePassword] = useState('') - - const [file, setFile] = useState(null) - - const { showSnackbar } = useCustomSnackbar() - const [readingFile, setReadingFile] = useState(false) - - const handleSetFile = useCallback( - async (file: File) => { - setFile(file) - if (file.type === 'application/json') { - setReadingFile(true) - const [value] = await Promise.all([file.text(), delay(1000)]) - setKeyStoreContent(value) - setReadingFile(false) - } else { - showSnackbar(t.create_wallet_key_store_not_support(), { variant: 'error' }) - } - }, - [t], - ) - const reset = useCallback(() => { - setFile(null) - }, []) - - const disabled = readingFile || !file - - useLayoutEffect(() => { - return fillSubmitOutlet( - onRestore(keyStoreContent, keyStorePassword)} - disabled={disabled}> - {t.continue()} - , - ) - }, [t, disabled, keyStoreContent, keyStorePassword]) - - return ( - - - {file ? - <> - - - - }> - - {readingFile ? t.file_unpacking() : t.file_unpacking_completed()} - - - {!readingFile ? - - { - setKeyStorePassword(e.target.value) - setError('') - }} - error={!!error} - helperText={error} - autoFocus - /> - - : null} - - : null} - - ) -}) diff --git a/packages/mask/dashboard/components/SecondaryButton/index.tsx b/packages/mask/dashboard/components/SecondaryButton/index.tsx deleted file mode 100644 index c2ad2497ca96..000000000000 --- a/packages/mask/dashboard/components/SecondaryButton/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ActionButton, makeStyles } from '@masknet/theme' -import { buttonClasses, type ButtonProps } from '@mui/material/Button' - -interface ActionButtonProps extends ButtonProps { - width?: number | string - loading?: boolean -} - -const useStyles = makeStyles()((theme) => ({ - // eslint-disable-next-line tss-unused-classes/unused-classes - root: { - backgroundColor: theme.palette.maskColor.thirdMain, - color: theme.palette.maskColor.main, - border: 'none!important', - fontWeight: 700, - ['&:hover']: { - background: theme.palette.maskColor.bottom, - boxShadow: '0px 8px 25px rgba(0, 0, 0, 0.1)', - border: 'none', - }, - [`&.${buttonClasses.disabled}`]: { - color: theme.palette.maskColor.main, - background: theme.palette.maskColor.thirdMain, - opacity: 0.4, - }, - }, -})) - -export function SecondaryButton = React.ComponentType>( - props: ActionButtonProps & PropsOf, -) { - const { width, loading, children, className, style, ...rest } = props - const { classes } = useStyles(undefined, { props: { classes: rest.classes } }) - return ( - - {children} - - ) -} diff --git a/packages/mask/dashboard/components/SetupFrame/index.tsx b/packages/mask/dashboard/components/SetupFrame/index.tsx deleted file mode 100644 index 3e3ca59c31f2..000000000000 --- a/packages/mask/dashboard/components/SetupFrame/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { memo, useState, type PropsWithChildren } from 'react' -import { Box, Typography } from '@mui/material' -import { Icons } from '@masknet/icons' -import Spline from '@splinetool/react-spline' -import { Welcome } from '../../assets/index.js' -import { LoadingBase, makeStyles } from '@masknet/theme' - -interface SetupFrameProps extends PropsWithChildren { - hiddenSpline?: boolean -} - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'flex', - overflow: 'auto', - minHeight: '100vh', - backgroundColor: theme.palette.maskColor.bottom, - }, - content: { - background: theme.palette.maskColor.bottom, - minWidth: 720, - width: 'clamp(720px, 66.6667%, 66.666%)', - paddingTop: '12.5vh', - paddingBottom: '12.5vh', - marginRight: theme.spacing(8), - display: 'flex', - flexDirection: 'column', - [theme.breakpoints.up('lg')]: { - marginLeft: 'clamp(40px, calc(66.6667% - 720px), 20%)', - }, - [theme.breakpoints.down('lg')]: { - marginLeft: 40, - marginRight: 40, - }, - }, - sidebar: { - // 1024*0.3=307.2 - minWidth: 'clamp(307px, 33.333%, 33.333%)', - flexShrink: 0, - }, -})) - -export const SetupFrame = memo(function SetupFrame({ children, hiddenSpline }) { - const { classes, theme } = useStyles() - const [loading, setLoading] = useState(true) - - return ( - - -
- -
- - {children} -
- - {!hiddenSpline ? -
- - - {/* Don't translate this slogan */} - The Web3 identity for everyone - - - - setLoading(false)} /> -
- : null} - {loading && !hiddenSpline ? - - - - : null} -
-
- ) -}) - -interface SetupFrameControllerProps extends PropsWithChildren {} -export const SetupFrameController = memo(function SetupFrameController({ children }) { - return ( - - {children} - - ) -}) diff --git a/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx b/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx deleted file mode 100644 index 993cba0aa541..000000000000 --- a/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { createContainer } from 'unstated-next' -import { useDashboardTrans } from '../locales/i18n_generated.js' -import { useForm } from 'react-hook-form' -import { zodResolver } from '@hookform/resolvers/zod' -import { z } from 'zod' -import { useTabs } from '@masknet/theme' -import { emailRegexp, phoneRegexp } from '../utils/regexp.js' -import guessCallingCode from 'guess-calling-code' - -export interface CloudBackupFormInputs { - email: string - phone: string - code: string - countryCode: string -} - -function useCloudBackupFormContext() { - const t = useDashboardTrans() - - const [currentTab, onChange, tabs] = useTabs('email', 'mobile') - - const formState = useForm({ - mode: 'onSubmit', - context: { - currentTab, - tabs, - }, - defaultValues: { - email: '', - phone: '', - code: '', - countryCode: (guessCallingCode.default || guessCallingCode)(), - }, - resolver: zodResolver( - z - .object({ - email: - currentTab === tabs.email ? - z - .string() - .refine((email) => emailRegexp.test(email), t.cloud_backup_incorrect_email_address()) - : z.string().optional(), - countryCode: currentTab === tabs.mobile ? z.string() : z.string().optional(), - phone: - currentTab === tabs.mobile ? - z.string().refine((mobile) => phoneRegexp.test(mobile)) - : z.string().optional(), - code: z - .string() - .min(1, t.cloud_backup_incorrect_verified_code()) - .max(6, t.cloud_backup_incorrect_verified_code()), - }) - .refine( - (data) => { - if (currentTab !== tabs.mobile) return true - if (!data.countryCode || !data.phone) return false - return phoneRegexp.test(`+${data.countryCode} ${data.phone}`) - }, - { - message: t.settings_dialogs_incorrect_phone(), - path: ['phone'], - }, - ), - ), - }) - - return { - formState, - currentTab, - onChange, - tabs, - } -} - -export const CloudBackupFormContext = createContainer(useCloudBackupFormContext) diff --git a/packages/mask/dashboard/contexts/RecoveryContext.tsx b/packages/mask/dashboard/contexts/RecoveryContext.tsx deleted file mode 100644 index 5a29e0fff3d3..000000000000 --- a/packages/mask/dashboard/contexts/RecoveryContext.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { noop } from 'lodash-es' -import { - createContext, - memo, - useState, - type ReactNode, - useMemo, - type PropsWithChildren, - useContext, - useCallback, -} from 'react' - -interface ContextOptions { - SubmitOutlet: ReactNode - fillSubmitOutlet(outlet: ReactNode): () => void -} - -export const RecoveryContext = createContext({ - SubmitOutlet: null, - fillSubmitOutlet: () => noop, -}) - -RecoveryContext.displayName = 'RecoveryContext' - -/** - * - * Render some component (the submit button) outside TabPanel's - */ -export const RecoveryProvider = memo>(function RecoveryProvider({ children }) { - const [outlet, setOutlet] = useState(null) - const fillSubmitOutlet = useCallback((outlet: ReactNode) => { - setOutlet(outlet) - return () => setOutlet(null) - }, []) - - const contextValue = useMemo( - () => ({ - SubmitOutlet: outlet, - fillSubmitOutlet, - }), - [outlet], - ) - - return {children} -}) - -export function usePersonaRecovery() { - return useContext(RecoveryContext) -} diff --git a/packages/mask/dashboard/contexts/index.ts b/packages/mask/dashboard/contexts/index.ts deleted file mode 100644 index 294112111e73..000000000000 --- a/packages/mask/dashboard/contexts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './RecoveryContext.js' diff --git a/packages/mask/dashboard/env.d.ts b/packages/mask/dashboard/env.d.ts deleted file mode 100644 index aba047ead1c4..000000000000 --- a/packages/mask/dashboard/env.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// -/// -/// diff --git a/packages/mask/dashboard/hooks/useBackupFormState.ts b/packages/mask/dashboard/hooks/useBackupFormState.ts deleted file mode 100644 index 4ded04dae6a8..000000000000 --- a/packages/mask/dashboard/hooks/useBackupFormState.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import { useState } from 'react' -import { useForm } from 'react-hook-form' -import { useAsync } from 'react-use' -import { z } from 'zod' -import { UserContext } from '../../shared-ui/index.js' -import Services from '#services' -import { passwordRegexp } from '../utils/regexp.js' -import { useDashboardTrans } from '../locales/i18n_generated.js' - -export type BackupFormInputs = { - backupPassword: string - paymentPassword?: string -} - -export function useBackupFormState() { - const t = useDashboardTrans() - const { value: hasPassword } = useAsync(Services.Wallet.hasPassword, []) - const { value: previewInfo, loading } = useAsync(Services.Backup.generateBackupPreviewInfo, []) - const { user } = UserContext.useContainer() - const [backupWallets, setBackupWallets] = useState(false) - - const formState = useForm({ - mode: 'onBlur', - context: { - user, - - backupWallets, - hasPassword, - }, - defaultValues: { - backupPassword: '', - paymentPassword: '', - }, - resolver: zodResolver( - z.object({ - backupPassword: z - .string() - .min(8, t.incorrect_password()) - .max(20, t.incorrect_password()) - .refine((password) => password === user.backupPassword, t.incorrect_password()) - .refine((password) => passwordRegexp.test(password), t.incorrect_password()), - paymentPassword: - backupWallets && hasPassword ? - z - .string({ - required_error: t.incorrect_password(), - }) - .min(6, t.incorrect_password()) - .max(20, t.incorrect_password()) - : z.string().optional(), - }), - ), - }) - - return { - hasPassword, - previewInfo, - loading, - backupWallets, - setBackupWallets, - formState, - } -} diff --git a/packages/mask/dashboard/hooks/useCreatePersonaV2.ts b/packages/mask/dashboard/hooks/useCreatePersonaV2.ts deleted file mode 100644 index ce74921865d1..000000000000 --- a/packages/mask/dashboard/hooks/useCreatePersonaV2.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { delay } from '@masknet/kit' -import { MaskMessages } from '@masknet/shared-base' -import Services from '#services' - -export function useCreatePersonaV2() { - return async (mnemonicWord: string, nickName: string) => { - const identifier = await Services.Identity.createPersonaByMnemonicV2(mnemonicWord, nickName, '') - await delay(300) - MaskMessages.events.ownPersonaChanged.sendToAll(undefined) - return identifier - } -} - -export function useCreatePersonaByPrivateKey() { - return async (privateKey: string, nickName: string) => { - const identifier = await Services.Identity.createPersonaByPrivateKey(privateKey, nickName) - await delay(300) - MaskMessages.events.ownPersonaChanged.sendToAll(undefined) - return identifier - } -} diff --git a/packages/mask/dashboard/hooks/useMnemonicWordsPuzzle.ts b/packages/mask/dashboard/hooks/useMnemonicWordsPuzzle.ts deleted file mode 100644 index 21dc171ca514..000000000000 --- a/packages/mask/dashboard/hooks/useMnemonicWordsPuzzle.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useCallback, useMemo, useState } from 'react' -import { useAsyncRetry } from 'react-use' -import { produce } from 'immer' -import { range, shuffle, remove, clone } from 'lodash-es' -import { EMPTY_LIST } from '@masknet/shared-base' -import Services from '#services' - -const PUZZLE_SIZE = 3 - -const TOTAL_SIZE = 12 - -export interface PuzzleWord { - index: number - rightAnswer: string - options: string[] -} - -export function useMnemonicWordsPuzzle() { - const { value: words = EMPTY_LIST, retry: wordsRetry } = useAsyncRetry( - () => Services.Wallet.createMnemonicWords(), - [], - ) - - const indexes = useMemo( - () => - shuffle(range(TOTAL_SIZE)) - .slice(0, PUZZLE_SIZE) - .sort((a, b) => a - b), - [words], - ) - - const [puzzleAnswer, setPuzzleAnswer] = useState<{ [key: number]: string }>({}) - - const [isMatched, setIsMatch] = useState() - - const puzzleWordList: PuzzleWord[] = useMemo(() => { - let restWords = remove(clone(words), (_word, index) => !indexes.includes(index)) - - return indexes.map((index) => { - const randomWords = shuffle(restWords).slice(0, 2) - const result = { - index, - rightAnswer: words[index], - options: shuffle(randomWords.concat(words[index])), - } - restWords = remove(clone(restWords), (word) => !randomWords.includes(word)) - - return result - }) - }, [words, indexes]) - - const answerCallback = useCallback((index: number, word: string) => { - setPuzzleAnswer( - produce((draft) => { - draft[index] = word - }), - ) - }, []) - - const verifyAnswerCallback = useCallback( - (callback?: () => void) => { - const puzzleAnswerEntries = Object.entries(puzzleAnswer) - const matched = - puzzleAnswerEntries.length === 3 && - puzzleAnswerEntries.every((entry) => { - return words[Number(entry[0])] === entry[1] - }) - setIsMatch(matched) - - if (matched) callback?.() - }, - [puzzleAnswer, words, setIsMatch], - ) - - const refreshCallback = useCallback(() => { - wordsRetry() - setIsMatch(undefined) - setPuzzleAnswer({}) - }, [wordsRetry]) - - return { - words, - refreshCallback, - puzzleWordList, - answerCallback, - puzzleAnswer, - verifyAnswerCallback, - isMatched, - } as const -} diff --git a/packages/mask/dashboard/hooks/useTermsAgreed.ts b/packages/mask/dashboard/hooks/useTermsAgreed.ts deleted file mode 100644 index 5b9b2bc64c21..000000000000 --- a/packages/mask/dashboard/hooks/useTermsAgreed.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Environment, assertEnvironment } from '@dimensiondev/holoflows-kit' -import { useCallback, useState } from 'react' -import { createContainer } from 'unstated-next' - -const KEY = 'dashboard/terms-agreed' -function useTermsAgreed() { - assertEnvironment(Environment.ExtensionProtocol) - // TODO: migrate this code - // eslint-disable-next-line no-restricted-globals - const [agreed, setAgreedState] = useState(!!localStorage.getItem(KEY)) - - const setAgreed = useCallback((val: boolean) => { - // eslint-disable-next-line no-restricted-globals - localStorage.setItem(KEY, JSON.stringify(val)) - setAgreedState(val) - }, []) - - return [agreed, setAgreed] as const -} - -export const TermsAgreedContext = createContainer(useTermsAgreed) diff --git a/packages/mask/dashboard/initialization/i18n.ts b/packages/mask/dashboard/initialization/i18n.ts deleted file mode 100644 index 01b42a50dccd..000000000000 --- a/packages/mask/dashboard/initialization/i18n.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { i18NextInstance } from '@masknet/shared-base' -import { addSharedI18N } from '@masknet/shared' -import { addDashboardI18N } from '../locales/languages.js' -import { initReactI18next } from 'react-i18next' - -initReactI18next.init(i18NextInstance) -addSharedI18N(i18NextInstance) -addDashboardI18N(i18NextInstance) diff --git a/packages/mask/dashboard/initialization/index.ts b/packages/mask/dashboard/initialization/index.ts deleted file mode 100644 index ef236784827a..000000000000 --- a/packages/mask/dashboard/initialization/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '../../shared-ui/initialization/index.js' -await import(/* webpackMode: 'eager' */ './render.js') diff --git a/packages/mask/dashboard/initialization/render.tsx b/packages/mask/dashboard/initialization/render.tsx deleted file mode 100644 index d2b0deeff211..000000000000 --- a/packages/mask/dashboard/initialization/render.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import './i18n.js' -import { StrictMode, lazy } from 'react' -import { createNormalReactRoot } from '../../shared-ui/utils/createNormalReactRoot.js' -import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' -import { queryClient } from '@masknet/shared-base-ui' -import { queryPersistOptions } from '../../shared-ui/utils/persistOptions.js' - -const Dashboard = lazy(() => import(/* webpackMode: 'eager' */ '../Dashboard.js')) -createNormalReactRoot( - - - - - , -) diff --git a/packages/mask/dashboard/locales/en-US.json b/packages/mask/dashboard/locales/en-US.json deleted file mode 100644 index 0b17a1960cfe..000000000000 --- a/packages/mask/dashboard/locales/en-US.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "address": "Address", - "about": "About", - "backup": "Backup", - "continue": "Continue", - "create": "Create", - "congratulations": "Congratulations", - "email": "Email", - "wallet": "Wallet", - "wallets": "Wallets", - "personas": "Personas", - "previous": "Previous", - "persona": "Persona", - "persona_name": "Persona Name", - "public_key": "Public Key", - "refresh": "Refresh", - "next": "Next", - "previous_page": "Previous", - "next_page": "Next", - "cancel": "Cancel", - "back": "Back", - "agree": "Agree", - "confirm": "Confirm", - "verify": "Verify", - "go_back": "Go back", - "connect": "Connect", - "searching": "Searching", - "restore": "Restore", - "save": "Save", - "manage": "Manage", - "recovery": "Recovery", - "sign_up": "Sign Up", - "skip": "Skip", - "search_area": "Search Area", - "successful": "Successful", - "close": "Close", - "send": "Send", - "resend": "Resend", - "print": "Print", - "download": "Download", - "identity": "identity", - "accounts": "accounts", - "uploading": "Uploading", - "data": "data", - "ready": "ready 🚀", - "print_preview": "Print Preview", - "incorrect_password": "Incorrect Password", - "download_preview": "Download Preview", - "download_backup": "Download Backup", - "confirm_password": "Confirm Password", - "about_dialog_license": "License: ", - "footer_bounty_list": "Bounty List", - "about_dialog_source_code": "Source Code: ", - "about_dialog_feedback": "Feedback: ", - "about_dialog_touch": "Get in touch", - "about_dialog_description": "Mask Network is the portal to the new, open internet. Mask allows you to send encrypted posts on social networks. We provide more functions such as sending encrypted lucky drops, purchasing cryptocurrencies, file service, etc.", - "setup_page_title": "Welcome to Mask Network", - "setup_page_description": "Encrypt your posts on social media and only your friends on Mask can decrypt them.", - "setup_page_create_account_title": "Create an Identity", - "setup_page_create_account_subtitle": "Create your digital identity system, explore Web3", - "setup_page_create_account_button": "Create", - "setup_page_create_restore_title": "Restoring from Identity or Backups", - "setup_page_create_restore_subtitle": "Restoring from identity & historical backups.", - "setup_page_create_restore_button": "Recovery or Sign In", - "create_account_mask_id": "MASK ID", - "create_account_private_key": "Private Key", - "create_account_identity_id": "Identity ID", - "create_account_identity_title": "Create an Identity for Mask Network", - "create_account_sign_in_button": "Recovery", - "create_account_persona_exists": "Persona already exists.", - "create_account_mnemonic_download_or_print": "I have kept my identity code safely.", - "create_account_preview_tip": "This QR code contains your identity code, please keep it safely. You can scan QR code to login your persona in Mask App.", - "create_account_mnemonic_confirm_failed": "Incorrect identity code", - "create_account_connect_social_media_button": "Create", - "create_account_connect_social_media": "Connect to {{type}}", - "create_account_persona_title": "Welcome to Mask Network", - "create_account_persona_subtitle": "Connect to social media accounts with your personas.", - "create_account_persona_successfully": "Persona created successfully.", - "create_account_connect_social_media_title": "Connect Social Media", - "create_account_failed": "Create Account Failed", - "follow_us": "Follow us", - "sign_in_account_identity_title": "Recover your persona", - "sign_in_account_tab_identity": "Identity", - "sign_in_account_sign_up_button": "Sign Up", - "sign_in_account_identity_warning": "The digital identity code can only recover your digital identity. It can encrypt and decrypt the social information signed and sent by this digital identity.", - "sign_in_account_private_key_placeholder": "Input your Private Key", - "sign_in_account_private_key_error": "Incorrect Private Key", - "sign_in_account_private_key_persona_not_found": "Can't find persona", - "sign_in_account_private_key_warning": "The private key of your identity code can only recover your persona. It can encrypt and decrypt the social information signed and sent by your persona.", - "sign_in_account_mnemonic_confirm_failed": "Incorrect identity", - "sign_in_account_cloud_backup_send_email_success": "Verification code was sent to your {{type}}. Please check your {{type}}.", - "sign_in_account_local_backup_warning": "Local backup can recover all the data that has been stored locally.", - "sign_in_account_local_backup_payment_password": "Payment Password", - "sign_in_account_local_backup_file_drag": "Please click or drag the file here", - "sign_in_account_cloud_backup_warning": "The cloud backup hosts and encrypts your data.", - "sign_in_account_cloud_backup_not_support": "Unsupported data backup", - "sign_in_account_cloud_send_verification_code_tip": "Send verification code to", - "sign_in_account_cloud_backup_failed": "Restore backup failed, Please try again.", - "sign_in_account_cloud_backup_email_or_phone_number": "E-mail or phone number", - "sign_in_account_cloud_backup_password": "Backup password", - "sign_in_account_cloud_restore_failed": "Restore failed", - "sign_in_account_cloud_backup_download_failed": "Download backup failed", - "sign_in_account_cloud_backup_decrypt_failed": "Decrypt failed, please check password", - "incorrect_backup_password": "Incorrect Backup Password.", - "incorrect_identity_mnemonic": "Incorrect recovery phrase.", - "sign_in_account_cloud_backup_email_format_error": " Invalid email address format.", - "sign_in_account_cloud_backup_phone_format_error": "The phone number is incorrect.", - "sign_in_account_cloud_backup_synchronize_password_tip": "You have successfully verified your cloud password and recovered your backup. To unify backup passwords, do you want to synchronize your cloud password as local backup password?", - "cloud_backup": "Cloud Backup", - "wallets_transfer": "Transfer", - "wallets_assets": "Assets", - "wallets_transfer_memo": "Memo", - "wallets_transfer_amount": "Amount", - "wallets_transfer_to_address": "To Address", - "wallets_print_tips": "This QR saves your mnemonic, please keep it safely.", - "wallets_mnemonic_word": "Mnemonic word", - "wallets_transfer_error_amount_absence": "Enter an amount", - "wallets_transfer_error_address_absence": "Enter recipient address", - "wallets_transfer_error_contract": "Select NFT contract", - "wallets_transfer_error_nft": "Select one NFT", - "wallets_transfer_error_invalid_address": "Incorrect wallet address.", - "wallet_transfer_error_no_address_has_been_set_name": "The address of the receiver is invalid.", - "wallet_transfer_error_no_ens_support": "Network does not support ENS.", - "wallets_transfer_error_insufficient_balance": "Insufficient {{symbol}} balance", - "wallets_transfer_error_same_address_with_current_account": "This receiving address is the same as the sending address. Please check again.", - "wallets_transfer_error_is_contract_address": "The receiving address is contract address. Please check again.", - "wallets_transfer_send": "Send", - "wallets_transfer_memo_placeholder": "Optional message", - "wallets_transfer_contract": "Contract", - "wallets_transfer_contract_placeholder": "Select an NFT Contract", - "wallets_swap": "Swap", - "wallets_red_packet": "Lucky drop", - "wallets_sell": "Sell", - "wallets_history": "History", - "settings": "Settings", - "gas_fee": "Transaction fee", - "transfer_cost": "Cost {{gasFee}} {{symbol}} ≈ ${{usd}}", - "done": "Done", - "labs": "D.Market", - "onboarding_wallet": "wallet", - "wallet_transactions_pending": "Pending", - "wallet_recovery_title": "Recover your wallet", - "wallet_select_address": "Select Address", - "wallet_derivation_path": "Ethereum {{- path }}", - "wallet_recovery_mnemonic_confirm_failed": "Incorrect Mnemonic Phrase Words.", - "wallet_recovery_description": "Please enter the correct mnemonic phrase words, private key, or upload the correct keystore file.", - "wallet_gas_fee_settings_low": "Low", - "wallet_gas_fee_settings_medium": "Medium", - "wallet_gas_fee_settings_high": "High", - "wallets_startup_create": "Create A New Wallet", - "wallets_startup_create_desc": "Mask wallet supports ETH, BSC, and Polygon/Matic networks.", - "wallets_startup_create_action": "Create", - "wallets_startup_import": "Import Wallet", - "wallets_startup_import_desc": "Mask wallet supports Private Key, JSON.File and Mnemonic words.", - "wallets_startup_import_action": "Import", - "wallets_startup_connect": "Connect Wallet", - "wallets_startup_connect_desc": "Supports Mask Wallet, MetaMask and WalletConnect.", - "wallets_startup_connect_action": "Connect", - "wallets_connect_wallet_metamask": "MetaMask", - "wallets_connect_wallet_connect": "Connect Wallet", - "wallets_connect_wallet_polka": "PolkaDot Wallet", - "wallets_create_wallet_input_placeholder": "Wallet Name", - "wallets_create_successfully_title": "Success", - "wallets_create_successfully_tips": "You have created your wallet successfully", - "wallets_create_successfully_unlock": "Unlock Wallet", - "wallets_create_wallet_alert": "Mask Network is a free, open-source, client-side interface. Mask Network allows you to interact directly with the blockchain, while you remain in full control of your keys and funds.Please think about this carefully. YOU are the one who is in control. Mask Network is not a bank or exchange. We don't hold your keys, your funds, or your information. This means we can't access accounts, recover keys, reset passwords, or reverse transactions.", - "wallets_wallet_connect_title": "Scan QR code with WalletConnect-compatible wallet", - "wallets_wallet_mnemonic": "Mnemonic", - "wallets_wallet_json_file": "Local Backup", - "wallets_wallet_private_key": "Private Key", - "wallets_import_wallet_tabs": "Import Wallet Tabs", - "wallets_import_wallet_password_placeholder": "Wallet Password", - "wallets_import_wallet_cancel": "Cancel", - "wallets_import_wallet_import": "Import", - "wallets_create_wallet_tabs": "Create Wallet Tabs", - "wallets_create_wallet_refresh": "Refresh", - "wallets_create_wallet_remember_later": "Remember that later", - "wallets_create_wallet_verification": "Verification", - "wallets_collectible_address": "Collectible Address", - "wallets_collectible_token_id": "Token ID", - "wallets_collectible_field_contract_require": "The collectible address is required", - "wallets_collectible_field_token_id_require": "The token id is required", - "wallets_collectible_load_end": "Load end", - "wallets_balance": "Balance on", - "wallets_balance_all_chain": "all chains", - "wallets_balance_Send": "Send", - "wallets_balance_Buy": "Buy", - "wallets_balance_Swap": "Swap", - "wallets_balance_Receive": "Receive", - "wallets_assets_token": "Token", - "wallets_assets_investment": "Investment", - "wallets_assets_collectibles": "Collectibles", - "wallets_assets_custom_token": "Custom Token", - "wallets_assets_custom_collectible": "Custom Collectible", - "wallets_assets_asset": "Asset", - "wallets_assets_balance": "Balance", - "wallets_assets_price": "Price", - "wallets_assets_value": "Value", - "wallets_assets_operation": "Operation", - "wallets_assets_more$collapsed": "Tokens with small value are not displayed ({{- symbol }} $1). Show all", - "wallets_assets_more$expanded": "Tokens with small value are displayed ({{- symbol }} $1). Hide all", - "wallets_assets_more_show_all": "Show all", - "wallets_address": "Wallet Address", - "wallets_receive_tips": "Scan the QR code and transfer {{chainName}} assets to it", - "wallets_add_collectible": "Add Collectible", - "wallets_incorrect_address": "Incorrect contract address.", - "wallets_collectible_been_added": "The Collectible has already been added.", - "wallets_collectible_error_not_exist": "The collectible does not exist or belong to you.", - "wallets_collectible_contract_is_empty": "Please select contract", - "wallets_collectible_token_id_is_empty": "Please select collectible", - "wallets_collectible_add": "Add", - "wallets_add_token": "Add Token", - "wallets_token_been_added": "Token has already been added.", - "wallets_token_symbol_tips": "Symbol must be 11 characters or fewer.", - "wallets_token_decimals_tips": "Decimals must be at least 0, and not over 18.", - "wallets_add_token_contract_address": "Token Contract Address", - "wallets_add_token_symbol": "Token Symbol", - "wallets_add_token_decimals": "Decimals of Precision", - "wallets_add_token_cancel": "Cancel", - "wallets_add_token_next": "Next", - "wallets_empty_tokens_tip": "No assets were found. Please add tokens.", - "wallets_empty_collectible_tip": "No collectibles were found. Please add Collectibles.", - "wallets_reload": "Reload", - "wallets_address_copied": "Address successfully copied", - "public_key_copied": "Public key successfully copied", - "wallets_address_copy": "Copy", - "wallets_history_types": "Types", - "wallets_history_value": "Value", - "wallets_history_time": "Time", - "wallets_history_receiver": "Interacted with (to)", - "wallets_empty_history_tips": "No transaction history", - "wallets_loading_token": "Loading tokens...", - "personas_setup_connect_tips": "Please connect to your {{type}} account.", - "personas_setup_tip": "Please create or restore a Mask identity.", - "personas_setup_connect": "Connect", - "personas_name_maximum_tips": "Maximum length is {{length}} characters long.", - "personas_name_existed": "The persona name already exists", - "personas_rename_placeholder": "Persona Name", - "personas_confirm": "Confirm", - "personas_cancel": "Cancel", - "personas_export_persona": "Export Persona", - "personas_export_persona_copy": "Copy", - "personas_export_persona_copy_success": "Copied", - "personas_export_persona_copy_failed": "Copy failed", - "personas_export_persona_confirm_password_tip": "You haven’t set up your password. To export your persona, you must set up backup password first.", - "personas_export_private": "Export Private Key", - "personas_export_private_key_tip": "This export is only for exporting private key. We do not export any other data. If you need more data, please go to Settings:", - "personas_delete_confirm_tips": "Please confirm that you have deleted persona {{nickname}} and entered your password.", - "personas_delete_dialog_title": "Delete Persona", - "personas_edit_dialog_title": "Edit Persona", - "personas_edit": "Edit", - "personas_delete": "Delete", - "personas_logout": "Log out", - "personas_logout_confirm_password_tip": "You haven’t set up your password. To logout persona, you must set up backup password first.", - "personas_add_persona": "Add Persona", - "personas_back_up": "Back Up", - "personas_connect_to": "Connect to {{internalName}}", - "personas_disconnect": "Delete Persona Verification", - "personas_disconnect_raw": "Disconnect", - "personas_disconnect_warning": "Are you sure you want to delete persona verification? Your mask friends can no longer send decrypted message to you by this persona or check your Web3 products related with this persona.", - "personas_logout_warning": "After logging out, your associated social accounts can no longer decrypt past encrypted messages. If you need to reuse your account, you can recover your account with your identity, private key, local or cloud backup.", - "personas_logout_manage_wallet_warning": "Please note: This Persona {{persona}} is the management account of SmartPay wallet {{addresses}}. You cannot use SmartPay wallet to interact with blockchain after logging out persona.", - "personas_add": "Add", - "personas_upload_avatar": "Upload an avatar", - "personas_rename": "Rename", - "personas_invite_post": "@{{identifier}} Hi, would you please download Mask so that we can share our posts with encryption? #mask_io install http://mask.io", - "personas_empty_contact_tips": "You don’t have any contacts with Mask Network installed yet. Invite your friends to download {{name}}.", - "personas_contacts_name": "Name", - "personas_contacts_operation": "Operation", - "personas_contacts_invite": "Invite", - "personas_post_is_empty": "You haven't created any post yet.", - "personas_post_create": "Create Post", - "print_tips": "This QR saves your identity code, please keep it safely. ", - "settings_general": "General", - "settings_backup_recovery": "Backup & Recovery", - "settings_local_backup": "Local Backup", - "settings_cloud_backup": "Cloud Backup", - "settings_appearance_default": "Follow system settings", - "settings_appearance_light": "Light", - "settings_appearance_dark": "Dark", - "settings_backup_preview_account": "Account", - "settings_backup_preview_personas": "Personas", - "settings_backup_preview_associated_accounts": "Associated Accounts", - "settings_backup_preview_posts": "Encrypted Post", - "settings_backup_preview_contacts": "Contacts", - "settings_backup_preview_file": "File", - "settings_backup_preview_wallets": "Mask Wallet", - "settings_backup_preview_set_payment_password": "You need to set a password to enable the wallet functionality before you can back up the wallet. Go to settings ", - "settings_backup_preview_created_at": "Backup Time", - "settings_language_title": "Language", - "settings_language_desc": "Select the language you would like to use", - "settings_language_auto": "Follow system", - "settings_appearance_title": "Appearance", - "settings_appearance_desc": "Select the theme you would like to use", - "settings_data_source_title": "Data Source", - "settings_data_source_desc": "Fetch trending data from different platforms", - "settings_sync_with_mobile_title": "Sync With Mobile", - "settings_sync_with_mobile_desc": "You can sync your accounts and information with your mobile device. Open the Mask Network mobile app, go to Settings and tap on Sync With Plug-ins", - "settings_global_backup_title": "Global Backup", - "settings_global_backup_desc": "Provide both local backup and cloud backup", - "settings_global_backup_last": "The most recent backup was made on {{backupAt}}. Backup method: {{backupMethod}}.", - "settings_restore_database_title": "Restore Database", - "settings_restore_database_desc": "Restore from a previous database backup", - "settings_email_title": "Email", - "settings_email_desc": "Please bind your email", - "settings_change_password_title": "Backup Password", - "settings_change_password_desc": "Change your backup password", - "settings_change_password_not_set": "You haven't set up a backup password", - "settings_phone_number_title": "Phone Number", - "settings_phone_number_desc": "Please bind your phone number", - "settings_password_rule": "Backup password must be between 8 and 20 characters and contain at least a number, a uppercase letter, a lowercase letter and a special character.", - "settings_button_cancel": "Cancel", - "settings_button_confirm": "Confirm", - "settings_button_sync": "Sync", - "settings_button_backup": "Backup", - "settings_button_recovery": "Recovery", - "settings_button_setup": "Setup", - "settings_button_change": "Change", - "settings_dialogs_bind_email_or_phone": "Please bind your email or phone number.", - "settings_dialogs_verify_backup_password": "Verify Backup Password", - "settings_dialogs_setting_backup_password": "Setting Backup Password", - "settings_dialogs_change_backup_password": "Change Backup Password", - "settings_dialogs_setting_email": "Setting Email", - "settings_dialogs_change_email": "Change Email", - "settings_dialogs_setting_phone_number": "Setting Phone Number", - "settings_dialogs_change_phone_number": "Change Phone Number", - "settings_dialogs_incorrect_code": "The verification code is incorrect.", - "settings_dialogs_incorrect_email": "The email address is incorrect.", - "settings_dialogs_incorrect_phone": "The phone number is incorrect.", - "settings_dialogs_incorrect_password": "Incorrect password.", - "settings_dialogs_inconsistency_password": "Password inconsistency.", - "settings_dialogs_current_email_validation": "The current email for verification is", - "settings_dialogs_change_email_validation": "To change the Email, you need to verify your current Email address:", - "settings_dialogs_current_phone_validation": "The current phone number for verification is", - "settings_dialogs_change_phone_validation": "To change the phone number, you need to verify your current phone number:", - "settings_dialogs_backup_to_cloud": "Backup to cloud", - "settings_dialogs_merge_to_local_data": "Merge Cloud backup to local and back up to cloud", - "settings_dialogs_backup_action_desc": "There is already a cloud backup, please merge the cloud backup to local before you back up, or back up directly.", - "settings_dialogs_backup_to_cloud_action": "This option overwrites the existing cloud backup with the local data.", - "settings_dialogs_backup_merge_cloud": "This option requires you to enter the password of the existing cloud backup for decryption. The existing cloud backup and local data are combined and then encrypted and uploaded to cloud.", - "settings_dialogs_backup_merged_tip": "You already obtained the cloud backup to your local. If you want to complete your back up, please click on the backup button to update all your data to cloud.", - "settings_label_backup_password": "Backup Password", - "settings_label_new_backup_password": "New Backup Password", - "settings_label_backup_password_cloud": "Backup passwords for files in the cloud", - "settings_label_payment_password": "Enter your Payment password", - "settings_label_re_enter": "Re-enter", - "settings_alert_password_set": "Backup password set up successfully.", - "settings_alert_password_updated": "Backup password updated", - "settings_alert_email_set": "Email set", - "settings_alert_email_updated": "Email updated", - "settings_alert_phone_number_set": "Phone number set", - "settings_alert_phone_number_updated": "Phone number updated", - "settings_alert_backup_fail": "Backup Failed", - "settings_alert_backup_success": "You have successfully backed up your data.", - "settings_alert_validation_code_sent": "Verification code sent", - "settings_alert_merge_success": "You have successfully merged your cloud backup to the local data.", - "labs_file_service": "File Service", - "labs_file_service_desc": "Decentralized file storage, permanently. Upload and share files to your Mask friends on top of Arweave Network.", - "labs_red_packet": "Lucky Drop", - "labs_red_packet_desc": "Gift crypto or NFTs to any users, first come, first served.", - "labs_swap": "Swap", - "labs_swap_desc": "Pop-up trading widget that allows you to instantly view prices of the hottest Crypto/Stock and trade. Can also invest in the best performing managers.", - "labs_transak": "Fiat On-Ramp", - "labs_transak_desc": "Fiat On-Ramp Aggregator on Twitter. Buy crypto in 60+ countries with Transak support.", - "labs_savings": "Savings", - "labs_savings_desc": "Deploy your crypto into various savings protocols and watch your savings grow.", - "labs_snapshot": "Snapshot", - "labs_snapshot_desc": "Display Snapshot proposals on the Twitter page of the respective project or protocol.", - "labs_market_trend": "Market Trend", - "labs_market_trend_desc": "Display token information, price charts and exchange information directly on social media.", - "labs_collectibles": "Collectibles", - "labs_collectibles_desc": "Display specific information of collectibles in OpenSea and Rarible, make an offer directly on social media.", - "labs_gitcoin": "Gitcoin", - "labs_gitcoin_desc": "Display specific information of Gitcoin projects, donate to a project directly on social media.", - "labs_valuables": "Valuables", - "labs_valuables_desc": "Buy & sell tweets autographed by their original creators.", - "labs_mask_box": "MaskBox", - "labs_mask_box_desc": "Professional multi-chain decentralized platform for launching NFT mystery boxes.", - "labs_loot_man": "LootMan by NonFFriend", - "labs_loot_man_desc": "Explore the endless possibilities of NFTs. Link and display your NFTs on social media in a revolutionized way.", - "labs_settings_market_trend": "Market Trend Settings", - "labs_settings_market_trend_source": "Default Trending Source", - "labs_settings_swap": "Swap Settings", - "labs_settings_swap_network": "{{network}} Network Default Trading Source", - "labs_pets": "Non-Fungible Friends by Mint Team", - "labs_pets_desc": "Explore the endless possibilities of NFTs.", - "labs_cyber_connect": "CyberConnect", - "labs_cyber_connect_desc": "Decentralized social graph protocol for user-centric Web3", - "labs_setup_tutorial": "Setup Tutorial", - "labs_do_not_show_again": "Don't show again.", - "labs_art_blocks": "Artblocks", - "labs_art_blocks_desc": "Artblocks allow you to pick a style that you like, pay for the work, and a randomly generated version of the content is created by an algorithm and sent to your Ethereum account.", - "dashboard_mobile_test": "Join Tests for Mobile", - "dashboard_source_code": "Source Code", - "privacy_policy": "Privacy Policy", - "version_of_stable": "Version {{version}}", - "version_of_unstable": "Version {{version}}-{{build}}-{{hash}}", - "register_restore_backups": "Restore Backups", - "register_restore_backups_cancel": "Cancel", - "register_restore_backups_confirm": "Restore", - "register_restore_backups_hint": "Please click or drag the file here", - "register_restore_backups_file": "File", - "register_restore_backups_text": "Text", - "register_restore_backups_tabs": "Restore Backup Tabs", - "create_wallet_mnemonic_tip": "Never share 12-word secret recovery phrase with anyone!", - "create_wallet_mnemonic_verification_fail": "Wrong words selected. Please try again!", - "create_wallet_onboarding_got_it": "Got it", - "create_wallet_onboarding_creating_identity": "Creating your ", - "create_wallet_onboarding_ready": "Your Wallet is on ", - "create_wallet_onboarding_generating_accounts": "Generating your ", - "create_wallet_onboarding_encrypting_data": "Encrypting your ", - "create_wallet_mnemonic_keep_safe": "Kept safely", - "create_wallet_password_uppercase_tip": "Must contain an uppercase character", - "create_wallet_password_lowercase_tip": "Must contain a lowercase character", - "create_wallet_password_number_tip": "Must contain a number", - "create_wallet_password_special_tip": "Must contain a special character", - "create_wallet_password_satisfied_requirement": "The password is not satisfied the requirement.", - "create_wallet_password_match_tip": "The two entered passwords are inconsistent.", - "create_wallet_password_length_error": "Payment password must be 6 to 20 characters in length.", - "create_wallet_name_placeholder": "Enter 1-12 characters", - "create_wallet_form_title": "Create a wallet", - "set_payment_password": "Set Your Payment Password", - "write_down_recovery_phrase": "Write Down Recovery Phrase", - "store_recovery_phrase_tip": "Please write down or copy these words in the correct way and store them in secure places.", - "create_wallet_payment_password_place_holder": "At least 6 charachters", - "create_wallet_re_enter_payment_password": "Confirm Payment Password", - "create_wallet_payment_password_tip_1": "Payment Password should be between 6 and 20 characters.", - "create_wallet_payment_password_tip_2": "Your payment password encrypts wallet data and is needed for trade confirmations and unlocking. It's securely stored on your device, and we cannot retrieve it. Please remember it.", - "create_wallet_your_wallet_address": "Your wallet address", - "create_wallet_key_store_not_support": "Unsupported key store data", - "create_wallet_key_store_password": "Keystore password", - "create_wallet_key_store_incorrect_password": "Incorrect Keystore Password.", - "create_wallet_done": "Done", - "create_wallet_verify_words": "Please select the correct words based on the order of the recovery phases.", - "create_wallet_mnemonic_word_not_match": "The mnemonic word is incorrect", - "recovery_smart_pay_wallet_title": "SmartPay Wallet Recovery", - "recovery_smart_pay_wallet_description_one": "{{count}} local SmartPay wallet has been detected and successfully recovered.", - "recovery_smart_pay_wallet_description_other": "{{count}} local SmartPay wallets have been detected and successfully recovered.", - "welcome_request_to_collect": "Allow us to collect your usage information to help us make improvements.", - "welcome_to_use_mask_network": "Welcome to use Mask Network", - "create_step": "Step {{step}}/{{totalSteps}}", - "persona_create_title": "Create New Mask Identity", - "persona_create_tips": "Create your persona to get started", - "persona_setup_persona_example": "Example: Alice", - "data_recovery_title": "Recover your data", - "data_recovery_description": "Please select the appropriate method to restore your personal data.", - "data_recovery_email": "Email", - "data_recovery_email_code": "Email verification code", - "data_recovery_mobile": "Mobile", - "data_recovery_mobile_code": "Mobile verification code", - "data_recovery_invalid_mobile": "Invalid phone number, please check and try again.", - "data_recovery_set_name": "Set Your Persona Name", - "data_recovery_name_tip": "Set your persona name with maximum length of 24 characters", - "data_backup_no_backups_found": "No backups found", - "data_backup_title": "Select the contents of the backup", - "data_backup_description": "Please select the appropriate method to restore your personal data.", - "cloud_backup_title": "login to your Mask Cloud", - "cloud_backup_backup_exists": "You used {{account}} for the last cloud backup.", - "cloud_backup_no_exist_tips": "Please use your frequently used email account or mobile phone for backup.", - "cloud_backup_email_title": "E-mail", - "cloud_backup_phone_title": "Mobile", - "cloud_backup_incorrect_email_address": "Invalid email address format.", - "cloud_backup_incorrect_verified_code": "The code is incorrect.", - "cloud_backup_email_verification_code": "Email verification code", - "cloud_backup_phone_verification_code": "Phone verification code", - "cloud_backup_preview_title": "Welcome to Mask Cloud Services", - "cloud_backup_preview_description": "Please select the appropriate method to restore your personal data.", - "cloud_backup_preview_switch_other_account": "Switch other account", - "cloud_backup_merge_local_data": "Merge data to local database", - "cloud_backup_overwrite_backup": "Overwrite Backup", - "cloud_backup_overwrite_current_backup": "Overwrite current backup", - "cloud_backup_overwrite_current_backup_tips": "Sure to overwrite the backups stored on Mask Cloud Service?", - "cloud_backup_upload_backup": "Upload backup", - "cloud_backup_upload_to_cloud": "Backup to Cloud", - "cloud_backup_overwrite_tips": "This option will overwrite the existing cloud backup with the local data, and it cannot be recovered anymore.", - "cloud_backup_successfully_tips": "You’ve uploaded backup to Mask Cloud Service successfully.", - "cloud_backup_merge_to_local_database": "Merge data to local database", - "cloud_backup_download_link_expired": "Download link is expired", - "cloud_backup_enter_backup_password_to_decrypt_file": "Please enter cloud backup password to download file.", - "cloud_backup_incorrect_backup_password": "Incorrect cloud backup password, please try again.", - "cloud_backup_merge_to_local": "Merge to local", - "cloud_backup_merge_to_local_congratulation_tips": "Data merged from Mask Cloud Service to local successfully. Re-enter password to encrypt and upload backup to Mask Cloud Service.", - "cloud_backup_download_backup": "Download backup", - "cloud_backup_merge_to_local_successfully": "Backup downloaded and merged to local successfully.", - "cloud_backup_merge_to_local_failed": "Backup downloaded and merged to local failed.", - "cloud_backup_backup_to_mask_cloud_service": "Backup to Mask Cloud Service", - "file_unpacking": "Uppacking", - "file_unpacking_completed": "Completed", - "data_decrypting": "Decrypting", - "data_downloading": "Downloading", - "switch_other_accounts": "Switch other accounts", - "file_reselect": "Reselect", - "mobile_number": "Mobile number", - "persona_phrase_title": "Persona Recovery Phrase", - "persona_phrase_tips": "12-word recovery phrase is used to recover your persona data.", - "persona_phrase_copy_description": "The mnemonic has been copied, please keep it in a safe place.", - "persona_phrase_create_tips": "Never share 12-word secret recovery phrase with anyone!", - "persona_phrase_create_check_tips": "I wrote down the words in the correct order", - "persona_onboarding_to_twitter": "Experience in Twitter", - "persona_onboarding_set_payment_password": "Set Payment Password", - "persona_onboarding_pin_tips": "Pin Mask Network to the toolbar for easier access:", - "persona_onboarding_creating_identity": "Creating your ", - "persona_onboarding_generating_accounts": "Generating your ", - "persona_onboarding_encrypting_data": "Encrypting your ", - "persona_onboarding_ready": "Your Persona is on ", - "persona_onboarding_recovery_wallets": "You have recovered ", - "persona_onboarding_wallets_one": "{{count}} Wallet 🚀", - "persona_onboarding_wallets_other": "{{count}} Wallets 🚀", - "wallet_history_no_data": "No history records.", - "welcome_new_agreement_policy": "By continuing to the app, you agree to these Service Agreement and Privacy Policy.", - "wallets_history_burn": "Burn", - "wallet_connect_tips": "You’re connected to a WalletConnect wallet. Please switch network in that wallet, or switch to another wallet.", - "identity_words": "Recovery Phrase", - "private_key": "Private Key", - "local_backup": "Local Backup", - "incorrect_verification_code": "Invalid verification code.", - "wallet_set_payment_password_successfully": "Set payment password successfully.", - "wallet_open_mask_wallet": "Open Mask Wallet" -} diff --git a/packages/mask/dashboard/locales/index.ts b/packages/mask/dashboard/locales/index.ts deleted file mode 100644 index 5d88b2e093aa..000000000000 --- a/packages/mask/dashboard/locales/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file is auto generated. DO NOT EDIT -// Run `npx gulp sync-languages` to regenerate. -// Default fallback language in a family of languages are chosen by the alphabet order -// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts - -export * from './i18n_generated.js' diff --git a/packages/mask/dashboard/locales/ja-JP.json b/packages/mask/dashboard/locales/ja-JP.json deleted file mode 100644 index 6c11ed781f3c..000000000000 --- a/packages/mask/dashboard/locales/ja-JP.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "address": "アドレス", - "about": "About", - "backup": "バックアップ", - "continue": "続行", - "create": "作成", - "congratulations": "おめでとうございます!", - "email": "メールアドレス", - "wallet": "ウォレット", - "wallets": "ウォレット", - "personas": "ペルソナ", - "previous": "前の", - "persona": "ペルソナ", - "persona_name": "ペルソナ名", - "public_key": "公開キー", - "refresh": "更新", - "next": "Next", - "previous_page": "前の", - "next_page": "次", - "cancel": "Cancel", - "back": "Back", - "agree": "同意", - "confirm": "確認", - "verify": "認証", - "go_back": "戻る", - "connect": "接続", - "searching": "検索中…", - "restore": "復元", - "save": "保存", - "manage": "管理", - "recovery": "復旧", - "sign_up": "新規登録", - "skip": "スキップ", - "search_area": "捜索エリア", - "successful": "成功", - "close": "終了", - "send": "送信", - "resend": "再送信", - "print": "プリント", - "download": "ダウンロード", - "identity": "アイデンティティ", - "accounts": "アカウント", - "uploading": "アップロード中", - "data": "データ", - "ready": "準備完了🚀", - "print_preview": "プリントプレビュー", - "incorrect_password": "パスワードが間違っています", - "download_preview": "ダウンロードプレビュー", - "download_backup": "バックアップのダウンロード", - "confirm_password": "パスワード確認", - "about_dialog_license": "ライセンス: ", - "footer_bounty_list": "報奨金リスト", - "about_dialog_source_code": "ソースコード: ", - "about_dialog_feedback": "フィードバック: ", - "about_dialog_touch": "お問い合わせ", - "about_dialog_description": "Mask Networkは、新しいオープンなインターネットへのポータルです。Maskは、ソーシャルネットワーク上で暗号化された投稿を送ることができます。暗号化されたRedPacketの送信、暗号通貨の購入、ファイルサービスなど、より多くの機能を提供しています。", - "setup_page_title": "Mask Networkへようこそ", - "setup_page_description": "SNS上の投稿とチャットを暗号化し、友達のみが復号化できます。", - "setup_page_create_account_title": "イベントを作成", - "setup_page_create_account_subtitle": "アカウントとデータのローカルストレージ", - "setup_page_create_account_button": "作成", - "setup_page_create_restore_title": "アイデンティティやバックアップから復元する", - "setup_page_create_restore_subtitle": "IDと履歴のバックアップからの復元", - "setup_page_create_restore_button": "復元またはサインイン", - "create_account_mask_id": "MASK ID", - "create_account_private_key": "プライベートキー", - "create_account_identity_id": "アイデンティティ ID", - "create_account_identity_title": "Mask Networkのアイデンティティを作成", - "create_account_sign_in_button": "復元またはサインイン", - "create_account_persona_exists": "ペルソナは既に存在します。", - "create_account_mnemonic_download_or_print": "IDコードは大切に保管しています。", - "create_account_preview_tip": "このQRコードにはあなたのIDコードが含まれていますので、大切に保管してください。QRコードを読み込むと、MASK APPでペルソナにログインすることができます。", - "create_account_mnemonic_confirm_failed": "IDコードが間違っています", - "create_account_connect_social_media_button": "作成", - "create_account_connect_social_media": "{{type}} に接続", - "create_account_persona_title": "Mask Networkへようこそ", - "create_account_persona_subtitle": "ペルソナを作成し、ソーシャルアカウントを接続することができます", - "create_account_persona_successfully": "正常にペルソナを作成しました", - "create_account_connect_social_media_title": "ソーシャルメディアに接続", - "create_account_failed": "アカウント作成が失敗しました", - "follow_us": "フォローしてください", - "sign_in_account_identity_title": "復元またはサインイン", - "sign_in_account_tab_identity": "アイデンティティ", - "sign_in_account_sign_up_button": "サインアップ", - "sign_in_account_identity_warning": "デジタルIDコードは、あなたのデジタルIDのみを復元できます。このデジタルIDによって署名され送信されたソーシャル情報を暗号化して復号することができます。", - "sign_in_account_private_key_placeholder": "秘密鍵を入力してください", - "sign_in_account_private_key_error": "IDコードが間違っています", - "sign_in_account_private_key_persona_not_found": "ペルソナが見つかりません", - "sign_in_account_private_key_warning": "デジタルIDコードは、あなたのデジタルIDのみを復元できます。このデジタルIDによって署名され送信されたソーシャル情報を暗号化して復号することができます。", - "sign_in_account_mnemonic_confirm_failed": "IDが間違っています", - "sign_in_account_cloud_backup_send_email_success": "{{type}}に認証コードが送信されました。 {{type}} を確認してください。", - "sign_in_account_local_backup_warning": "ローカルバックアップは、ローカルに保存されているすべてのデータを回復できます。", - "sign_in_account_local_backup_payment_password": "支払いパスワードの設定", - "sign_in_account_local_backup_file_drag": "ここでファイルをクリックまたはドラッグしてください", - "sign_in_account_cloud_backup_warning": "クラウドバックアップは、データをホストおよび暗号化します。", - "sign_in_account_cloud_backup_not_support": "サポートされていないバックアップ", - "sign_in_account_cloud_send_verification_code_tip": "検証コードを送信", - "sign_in_account_cloud_backup_failed": "バックアップの復元に失敗しました。もう一度やり直してください。", - "sign_in_account_cloud_backup_email_or_phone_number": "メールアドレスまたは電話番号", - "sign_in_account_cloud_backup_password": "バックアップパスワード", - "sign_in_account_cloud_restore_failed": "復元に失敗しました", - "sign_in_account_cloud_backup_download_failed": "バックアップのダウンロードに失敗しました", - "sign_in_account_cloud_backup_decrypt_failed": "復号に失敗しました。パスワードを確認してください。", - "incorrect_backup_password": "バックアップパスワードが正しくありません。", - "incorrect_identity_mnemonic": "アイデンティティニーモニックが正しくありません。", - "sign_in_account_cloud_backup_email_format_error": "メールアドレスが間違っています", - "sign_in_account_cloud_backup_phone_format_error": "電話番号が正しくありません", - "sign_in_account_cloud_backup_synchronize_password_tip": "クラウドパスワードの認証が完了し、バックアップがアップロードされています。バックアップパスワードを統一するために、クラウドのパスワードをローカルのバックアップのパスワードとして同期するかどうかを確認してください。", - "cloud_backup": "クラウドバックアップ", - "wallets_transfer": "転送", - "wallets_assets": "資産", - "wallets_transfer_memo": "メモ", - "wallets_transfer_amount": "金額", - "wallets_transfer_to_address": "宛先アドレス", - "wallets_print_tips": "このQRはニーモニックを保存します。安全に保管してください。", - "wallets_mnemonic_word": "ニーモニック単語", - "wallets_transfer_error_amount_absence": "金額を入力", - "wallets_transfer_error_address_absence": "受信者のアドレス", - "wallets_transfer_error_contract": "NFT のコントラクトを選択", - "wallets_transfer_error_nft": "NFT を1つ選択してください", - "wallets_transfer_error_invalid_address": "無効な受け手のアドレス", - "wallet_transfer_error_no_address_has_been_set_name": "受信者のアドレスが無効です。", - "wallet_transfer_error_no_ens_support": "ネットワークが ENS をサポートしていません。", - "wallets_transfer_error_insufficient_balance": "{{symbol}} の残高が足りません", - "wallets_transfer_error_same_address_with_current_account": "受信アドレスは送信アドレスと同じです。もう一度確認してください。", - "wallets_transfer_error_is_contract_address": "受信アドレスはコントラクトアドレスです。もう一度確認してください。", - "wallets_transfer_send": "送信", - "wallets_transfer_memo_placeholder": "任意のメッセージ", - "wallets_transfer_contract": "コントラクト", - "wallets_transfer_contract_placeholder": "NFT コントラクトを選択してください", - "wallets_swap": "スワップ", - "wallets_red_packet": "レッドパケット", - "wallets_sell": "売る", - "wallets_history": "履歴", - "settings": "設定", - "gas_fee": "取引手数料", - "transfer_cost": "費用 {{gasFee}} {{symbol}} ${{usd}}", - "done": "完了!", - "labs": "Mask Labs", - "onboarding_wallet": "ウォレット", - "wallet_transactions_pending": "承認待ち", - "wallet_recovery_title": "ウォレットを復元する", - "wallet_select_address": "アドレスを選択する", - "wallet_derivation_path": "イーサリアム {{- path }}", - "wallet_recovery_mnemonic_confirm_failed": "ニーモニックフレーズが間違っています", - "wallet_recovery_description": "正しいニーモニックフレーズの単語、秘密キー、または正しいキーストアファイルをアップロードしてください。", - "wallet_gas_fee_settings_low": "低い", - "wallet_gas_fee_settings_medium": "中間", - "wallet_gas_fee_settings_high": "高い", - "wallets_startup_create": "新規ウォレット作成", - "wallets_startup_create_desc": "MaskウォレットはETH、BSC、Polygon/Maticのネットワークをサポートしています。", - "wallets_startup_create_action": "作成", - "wallets_startup_import": "ウォレットをインポート", - "wallets_startup_import_desc": "Mask networkはPrivate KeyとMnemonic wordsをサポートしています。", - "wallets_startup_import_action": "インポート", - "wallets_startup_connect": "プラグインウォレットに接続", - "wallets_startup_connect_desc": "Mask networkはMetamask、WalletConnectをサポートしています。", - "wallets_startup_connect_action": "接続", - "wallets_connect_wallet_metamask": "Metamask", - "wallets_connect_wallet_connect": "ウォレットを接続", - "wallets_connect_wallet_polka": "Polkadot ウォレット", - "wallets_create_wallet_input_placeholder": "ウォレット名", - "wallets_create_successfully_title": "成功", - "wallets_create_successfully_tips": "ウォレットを正常に作成しました", - "wallets_create_successfully_unlock": "ウォレットをアンロック", - "wallets_create_wallet_alert": "Mask Networkは、フリーでオープンソースの、クライアントサイドのインターフェースです。Mask Networkは、あなたが自分の鍵と資金を完全にコントロールしたまま、ブロックチェーンと直接やりとりすることを可能にします。コントロールするのはあなた自身です。Mask Networkは銀行や取引所ではありません。お客様の鍵や資金、情報を預かることはありません。つまり、アカウントへのアクセス、鍵の回収、パスワードのリセット、取引の取り消しなどはできません。", - "wallets_wallet_connect_title": "WalletConnect対応ウォレットでQRコードスキャン", - "wallets_wallet_mnemonic": "ニーモニック", - "wallets_wallet_json_file": "ローカルバックアップ", - "wallets_wallet_private_key": "秘密鍵", - "wallets_import_wallet_tabs": "ウォレットタブのインポート", - "wallets_import_wallet_password_placeholder": "ウォレットのパスワード", - "wallets_import_wallet_cancel": "キャンセル", - "wallets_import_wallet_import": "インポート", - "wallets_create_wallet_tabs": "ウォレットタブを作成", - "wallets_create_wallet_refresh": "更新", - "wallets_create_wallet_remember_later": "後で覚えておいてください", - "wallets_create_wallet_verification": "認証", - "wallets_collectible_address": "受取可能なアドレス", - "wallets_collectible_token_id": "トークンID", - "wallets_collectible_field_contract_require": "受取可能なアドレスが必要です", - "wallets_collectible_field_token_id_require": "トークンIDが必要です", - "wallets_collectible_load_end": "ロード終了", - "wallets_balance": "残高", - "wallets_balance_all_chain": "全てのチェーン", - "wallets_balance_Send": "送信", - "wallets_balance_Buy": "購入", - "wallets_balance_Swap": "スワップ", - "wallets_balance_Receive": "受取", - "wallets_assets_token": "トークン", - "wallets_assets_investment": "投資", - "wallets_assets_collectibles": "コレクション", - "wallets_assets_custom_token": "カスタムトークン", - "wallets_assets_custom_collectible": "カスタムコレクション", - "wallets_assets_asset": "資産", - "wallets_assets_balance": "残高", - "wallets_assets_price": "価格", - "wallets_assets_value": "価値", - "wallets_assets_operation": "操作方法", - "wallets_assets_more$collapsed": "価値の小さいトークンは表示されません ({{direction}} 1ドル)。すべて表示します", - "wallets_assets_more$expanded": "価値の小さいトークンは表示されません ({{- symbol }} $1) 。すべて非表示します", - "wallets_assets_more_show_all": "すべて表示します", - "wallets_address": "ウォレットアドレス", - "wallets_receive_tips": "QRコードをスキャンして {{chainName}} 資産を転送します", - "wallets_add_collectible": "コレクションを追加", - "wallets_incorrect_address": "不正なコントラクトアドレス", - "wallets_collectible_been_added": "コレクションは既に追加されています", - "wallets_collectible_error_not_exist": "コレクションは存在しないか、またはあなたのものです。", - "wallets_collectible_contract_is_empty": "コントラクトを選択してください", - "wallets_collectible_token_id_is_empty": "トークンを選択してください", - "wallets_collectible_add": "追加", - "wallets_add_token": "トークンを追加", - "wallets_token_been_added": "このトークンはすでに追加されてます", - "wallets_token_symbol_tips": "シンボルは11文字以下でなければなりません", - "wallets_token_decimals_tips": "小数点以下は0以上、18以下でなければなりません", - "wallets_add_token_contract_address": "トークンコントラクトアドレス", - "wallets_add_token_symbol": "ティッカーシンボル", - "wallets_add_token_decimals": "小数点の適合率", - "wallets_add_token_cancel": "キャンセル", - "wallets_add_token_next": "Next", - "wallets_empty_tokens_tip": "アセットが見つかりません。トークンを追加してください。", - "wallets_empty_collectible_tip": "コレクションが見つかりませんでした。コレクションを追加してください。", - "wallets_reload": "再度読み込み", - "wallets_address_copied": "アドレスが正常にコピーされました", - "public_key_copied": "公開キーをコピーしました", - "wallets_address_copy": "コピー", - "wallets_history_types": "タイプ", - "wallets_history_value": "価値", - "wallets_history_time": "時間", - "wallets_history_receiver": "受取", - "wallets_empty_history_tips": "取引履歴がありません", - "wallets_loading_token": "トークンを読み込み中", - "personas_setup_connect_tips": "あなたの {{type}} アカウントに接続してください", - "personas_setup_tip": "ペルソナを作成/復元してください。", - "personas_setup_connect": "接続する", - "personas_name_maximum_tips": "最大長は {{length}} 文字です。", - "personas_name_existed": "このペルソナ名は既に存在しています", - "personas_rename_placeholder": "ペルソナ名", - "personas_confirm": "確定", - "personas_cancel": "キャンセル", - "personas_export_persona": "ペルソナのエクスポート", - "personas_export_persona_copy": "コピー", - "personas_export_persona_copy_success": "コピーしました", - "personas_export_persona_copy_failed": "コピーに失敗しました", - "personas_export_persona_confirm_password_tip": "パスワードが設定されていません。秘密鍵をエクスポートするには、まずバックアップパスワードを設定する必要があります。", - "personas_export_private": "秘密鍵をエクスポート", - "personas_export_private_key_tip": "このエクスポートは秘密鍵をエクスポートするためのものです。他のデータはエクスポートしません。さらなるデータが必要な場合は「設定」に進んでください:", - "personas_delete_confirm_tips": "削除するペルソナ名 {{nickname}} とパスワードを入力してください。", - "personas_delete_dialog_title": "ペルソナを削除", - "personas_edit_dialog_title": "ペルソナを編集", - "personas_edit": "編集", - "personas_delete": "削除", - "personas_logout": "ログアウト", - "personas_logout_confirm_password_tip": "パスワードが設定されていません。ペルソナからログアウトするには、まずバックアップパスワードを設定する必要があります。", - "personas_add_persona": "ペルソナを追加", - "personas_back_up": "バックアップ", - "personas_connect_to": "{{internalName}} に接続", - "personas_disconnect": "接続を外す", - "personas_disconnect_raw": "接続解除", - "personas_disconnect_warning": "{{network}} の {{userId}}アカウントを切断してもよろしいですか? 切断後、このアカウントはマスクネットワークで情報を復号化および暗号化することができなくなります。", - "personas_logout_warning": "ログアウト後、あなたに関連づけられたソーシャルアカウントは過去に暗号化されたメッセージを復号することができなくなります。アカウントを再利用する場合は、秘密鍵を利用して復元することができます。", - "personas_logout_manage_wallet_warning": "ご注意ください: この Persona {{persona}} は SmartPay ウォレットの管理アカウント {{addresses}}です。 ペルソナをログアウトした後、SmartPayウォレットを使用してブロックチェーンとやりとりすることはできません。", - "personas_add": "追加", - "personas_upload_avatar": "アバターをアップロード", - "personas_rename": "名前変更", - "personas_invite_post": "@{{identifier}} こんにち!投稿を暗号化して共有できるように Mask をダウンロードしましょう。http://mask.io からダウンロードできます。 #mask_io", - "personas_empty_contact_tips": "Mask Network に認識されているコンタクトがありません。友達を招待して {{name}} をダウンロードしてもらってください。", - "personas_contacts_name": "名前", - "personas_contacts_operation": "操作", - "personas_contacts_invite": "招待", - "personas_post_is_empty": "まだ投稿を作成したことがありません。", - "personas_post_create": "投稿を作成", - "print_tips": "このQRコードはあなたのIDコードを保存します。安全に保管してください。 ", - "settings_general": "一般", - "settings_backup_recovery": "バックアップと復元", - "settings_local_backup": "ローカルバックアップ", - "settings_cloud_backup": "クラウドバックアップ", - "settings_appearance_default": "システム設定に従う", - "settings_appearance_light": "ライト", - "settings_appearance_dark": "ダーク", - "settings_backup_preview_account": "アカウント", - "settings_backup_preview_personas": "ペルソナ", - "settings_backup_preview_associated_accounts": "関連アカウント", - "settings_backup_preview_posts": "暗号化された投稿", - "settings_backup_preview_contacts": "コンタクト", - "settings_backup_preview_file": "ファイル", - "settings_backup_preview_wallets": "Mask ウォレット", - "settings_backup_preview_set_payment_password": "ウォレットをバックアップするには、ウォレットの機能を有効にするためにパスワードを設定する必要があります。 ", - "settings_backup_preview_created_at": "バックアップ時間", - "settings_language_title": "言語", - "settings_language_desc": "使用する言語を選択してください", - "settings_language_auto": "システム設定に従う", - "settings_appearance_title": "外観", - "settings_appearance_desc": "使用したいテーマを選択したください", - "settings_data_source_title": "データソース", - "settings_data_source_desc": "異なるプラットフォームからトレンドデータを取得", - "settings_sync_with_mobile_title": "モバイルと同期", - "settings_sync_with_mobile_desc": "アカウントと情報をモバイルデバイスと同期することができます。Mask Network モバイルアプリを開き、設定に移動して同期プラグインを選択します", - "settings_global_backup_title": "グローバルバックアップ", - "settings_global_backup_desc": "ローカルバックアップとクラウドバックアップの両方を提供", - "settings_global_backup_last": "最新のバックアップは {{backupAt}} に行われました。バックアップ方法: {{backupMethod}}。", - "settings_restore_database_title": "データベースの復元", - "settings_restore_database_desc": "以前のデータベースバックアップから復元", - "settings_email_title": "Eメール", - "settings_email_desc": "メールアドレスを入力してください", - "settings_change_password_title": "バックアップパスワード", - "settings_change_password_desc": "バックアップパスワードの変更", - "settings_change_password_not_set": "バックアップパスワードが設定されていません", - "settings_phone_number_title": "電話番号", - "settings_phone_number_desc": "電話番号を入力してください", - "settings_password_rule": "バックアップパスワードは 8~20 文字で、少なくとも数字、小文字、特殊文字を含める必要があります。", - "settings_button_cancel": "キャンセル", - "settings_button_confirm": "確定", - "settings_button_sync": "同期", - "settings_button_backup": "バックアップ", - "settings_button_recovery": "リカバリー", - "settings_button_setup": "設定", - "settings_button_change": "変更", - "settings_dialogs_bind_email_or_phone": "メールアドレスまたは電話番号を入力してください。", - "settings_dialogs_verify_backup_password": "バックアップパスワードを確認", - "settings_dialogs_setting_backup_password": "バックアップパスワードを設定", - "settings_dialogs_change_backup_password": "バックアップパスワードの変更", - "settings_dialogs_setting_email": "メールアドレスの設定", - "settings_dialogs_change_email": "メールアドレスの変更", - "settings_dialogs_setting_phone_number": "電話番号の設定", - "settings_dialogs_change_phone_number": "電話番号の変更", - "settings_dialogs_incorrect_code": "認証コードが間違っています。", - "settings_dialogs_incorrect_email": "メールアドレスが間違っています。", - "settings_dialogs_incorrect_phone": "電話番号が間違っています。", - "settings_dialogs_incorrect_password": "パスワードが間違っています。", - "settings_dialogs_inconsistency_password": "パスワードが一致しません。", - "settings_dialogs_current_email_validation": "現在の認証用のメールアドレスは", - "settings_dialogs_change_email_validation": "メールアドレスを変更するためには、現在のメールアドレスを認証する必要があります:", - "settings_dialogs_current_phone_validation": "現在の認証用のメールアドレスは", - "settings_dialogs_change_phone_validation": "電話番号を変更するには、現在の電話番号を認証する必要があります:", - "settings_dialogs_backup_to_cloud": "クラウドにバックアップ", - "settings_dialogs_merge_to_local_data": "クラウドバックアップをローカルに統合し、クラウドにバックアップします", - "settings_dialogs_backup_action_desc": "クラウドバックアップは既に存在しています。バックアップ前にクラウドバックアップをローカルに統合するか、直接バックアップしてください。", - "settings_dialogs_backup_to_cloud_action": "このオプションは、既存のクラウドバックアップをローカルデータで上書きします。", - "settings_dialogs_backup_merge_cloud": "このオプションは、復号化のために既存のクラウドバックアップのパスワードの入力が必要です。 既存のクラウドバックアップとローカルデータが組み合わされ、暗号化してクラウドにアップロードされます。", - "settings_dialogs_backup_merged_tip": "既にクラウドバックアップをローカルに取得しています。バックアップを実行したい場合は、バックアップボタンをクリックしてすべてのデータをクラウドに更新します。", - "settings_label_backup_password": "バックアップパスワード", - "settings_label_new_backup_password": "新しいバックアップパスワード", - "settings_label_backup_password_cloud": "クラウド上のファイルのパスワードをバックアップ", - "settings_label_payment_password": "支払いパスワードの設定", - "settings_label_re_enter": "再入力", - "settings_alert_password_set": "バックアップパスワードの設定に成功しました。", - "settings_alert_password_updated": "バックアップパスワードが更新されました", - "settings_alert_email_set": "メールアドレスの設定", - "settings_alert_email_updated": "メールアドレスが更新されました", - "settings_alert_phone_number_set": "電話番号の設定", - "settings_alert_phone_number_updated": "電話番号が更新されました", - "settings_alert_backup_fail": "バックアップ失敗", - "settings_alert_backup_success": "データのバックアップに成功しました。", - "settings_alert_validation_code_sent": "認証コードを送信しました", - "settings_alert_merge_success": "クラウドバックアップをローカルデータに統合できました。", - "labs_file_service": "ファイルサービス", - "labs_file_service_desc": "ユーザー向け分散ファイルストレージです。", - "labs_red_packet": "幸運ドロップ", - "labs_red_packet_desc": "あなたの幸運を暗号化された幸運の小包としてあなたの友人に送信します。", - "labs_swap": "スワップ", - "labs_swap_desc": "追加料金と制限なしに Dex を通じてトークンを購入します。", - "labs_transak": "Transak", - "labs_transak_desc": "Transak より 60 以上の国でクリプトを購入することができます。", - "labs_savings": "貯蓄", - "labs_savings_desc": "さまざまな貯蓄プロトコルに暗号を導入し、貯蓄が増えるのを見守りします。", - "labs_snapshot": "Snapshot", - "labs_snapshot_desc": "ソーシャルメディア上で直接提案にを表示し、投票します。", - "labs_market_trend": "マーケットトレンド", - "labs_market_trend_desc": "トークン情報、価格チャート、取引情報をソーシャルメディアに直接表示します。", - "labs_collectibles": "コレクション", - "labs_collectibles_desc": "OpenSeaとRaribleのコレクションの具体的な情報を表示し、ソーシャルメディアで直接オファーします。", - "labs_gitcoin": "Gitcoin", - "labs_gitcoin_desc": "Gitcoinプロジェクトの具体的な情報を表示し、ソーシャルメディア上で直接プロジェクトに寄付します。", - "labs_valuables": "貴重品", - "labs_valuables_desc": "オリジナルのクリエイターによってサイン入りツイートを購入&販売。", - "labs_mask_box": "MaskBox", - "labs_mask_box_desc": "NFTミステリーボックスを発売するためのプロフェッショナルなマルチチェーン分散型プラットフォームです。", - "labs_loot_man": "NonFFriendによるLootMan", - "labs_loot_man_desc": "NFTsの無限の可能性を探索し、革新的な方法でソーシャルメディア上であなたのNFTを表示します。", - "labs_settings_market_trend": "マーケットトレンドの設定", - "labs_settings_market_trend_source": "既定のトレーディングソース", - "labs_settings_swap": "スワップの設定", - "labs_settings_swap_network": "{{network}} ネットワーク既定のトレーディングソース", - "labs_pets": "ミントチームによる非代替性友達", - "labs_pets_desc": "NFTの無限の可能性を探求します", - "labs_cyber_connect": "CyberConnect", - "labs_cyber_connect_desc": "ユーザー中心のWeb3の分散型ソーシャルグラフプロトコル", - "labs_setup_tutorial": "セットアップのチュートリアル", - "labs_do_not_show_again": "次回から表示しない。", - "labs_art_blocks": "Artblocks", - "labs_art_blocks_desc": "Artblocksでは、好きなスタイルを選び、作品に支払うと、アルゴリズムによってランダムに生成されたバージョンのコンテンツが作成され、イーサリアムアカウントに送られます。", - "dashboard_mobile_test": "モバイル向けのテストに参加", - "dashboard_source_code": "ソースコード", - "privacy_policy": "プライバシー・ポリシー(個人情報に関する方針)", - "version_of_stable": "バージョン {{version}}", - "version_of_unstable": "バージョン {{version}}-{{build}}-{{hash}}", - "register_restore_backups": "バックアップの復元", - "register_restore_backups_cancel": "キャンセル", - "register_restore_backups_confirm": "復元", - "register_restore_backups_hint": "ここでファイルをクリックまたはドラッグしてください", - "register_restore_backups_file": "ファイル", - "register_restore_backups_text": "テキスト", - "register_restore_backups_tabs": "バックアップの復元タブ", - "create_wallet_mnemonic_tip": "ニーモニックフレーズを保存することを忘れないでください。ウォレットにアクセスする際に必要になります。", - "create_wallet_mnemonic_verification_fail": "間違った単語が選択されました。もう一度やり直してください!", - "create_wallet_onboarding_got_it": "了解", - "create_wallet_onboarding_creating_identity": "作成", - "create_wallet_onboarding_ready": "ウォレットが有効です ", - "create_wallet_onboarding_generating_accounts": "作成中 ", - "create_wallet_onboarding_encrypting_data": "暗号化中 ", - "create_wallet_mnemonic_keep_safe": "安全に保管されている", - "create_wallet_password_uppercase_tip": "大文字を含めてください", - "create_wallet_password_lowercase_tip": "小文字を含めてください", - "create_wallet_password_number_tip": "数字を含めてください", - "create_wallet_password_special_tip": "特殊文字を含めてください", - "create_wallet_password_satisfied_requirement": "パスワードが要件を満たしていません。", - "create_wallet_password_match_tip": "パスワードが一致しません。", - "create_wallet_password_length_error": "パスワードの長さが正しくありません。", - "create_wallet_name_placeholder": "1〜12文字を入力してください", - "create_wallet_form_title": "ウォレットを作成します", - "set_payment_password": "支払いパスワードを設定", - "write_down_recovery_phrase": "復元フレーズを書きます", - "store_recovery_phrase_tip": "これらの単語を正しく書き留めるか、コピーして安全な場所に保管してください。", - "create_wallet_payment_password_place_holder": "6 文字以上お願いします。", - "create_wallet_re_enter_payment_password": "支払いパスワードを再入力", - "create_wallet_payment_password_tip_1": "支払いパスワードは6文字以上20文字以下で設定してください。", - "create_wallet_payment_password_tip_2": "支払いパスワードはウォレットデータを暗号化し、取引の確認とロック解除に必要です。 お使いのデバイスに安全に保存されているため、取り戻すことはできません。覚えておいてください。", - "create_wallet_your_wallet_address": "あなたのウォレットアドレス", - "create_wallet_key_store_not_support": "非対応のキーストアデータ", - "create_wallet_key_store_password": "キーストアのパスワード", - "create_wallet_key_store_incorrect_password": "キーストアのパスワードが正しくありません。", - "create_wallet_done": "完了", - "create_wallet_verify_words": "ニーモニックワードを検証します", - "create_wallet_mnemonic_word_not_match": "ニーモニックワードが正しくありません", - "recovery_smart_pay_wallet_title": "SmartPayウォレットの復元", - "recovery_smart_pay_wallet_description_one": "{{count}} ローカルの SmartPay ウォレットが検出され、正常に復元されました。", - "recovery_smart_pay_wallet_description_other": "{{count}} ローカル SmartPay ウォレットが検出され、正常に復元されました。", - "welcome_request_to_collect": "お客様の利用情報を収集し、改善に役立てます。", - "welcome_to_use_mask_network": "Mask Networkへようこそ", - "create_step": "ステップ {{step}}/{{totalSteps}}", - "persona_create_title": "新規Mask IDを作成", - "persona_create_tips": "あなたのペルソナを作成して始めましょう", - "persona_setup_persona_example": "例: アリス(Alice)", - "data_recovery_title": "データを復元する", - "data_recovery_description": "12単語の復元フレーズは、ペルソナデータを回復するために使用されます。", - "data_recovery_email": "メールアドレス", - "data_recovery_email_code": "電子メール検証コード", - "data_recovery_mobile": "モバイル", - "data_recovery_mobile_code": "モバイル認証コード", - "data_recovery_invalid_mobile": "電話番号が無効です。確認してもう一度お試しください。", - "data_recovery_set_name": "ペルソナ名を設定", - "data_recovery_name_tip": "あなたのペルソナ名を24文字以内で設定してください", - "data_backup_no_backups_found": "バックアップが見つかりません", - "data_backup_title": "バックアップの内容を選択します", - "data_backup_description": "個人データを復元するには、適切な方法を選択してください。", - "cloud_backup_title": "マスククラウドにログインする", - "cloud_backup_backup_exists": "前回のクラウドバックアップに {{account}} を使用しました", - "cloud_backup_no_exist_tips": "バックアップには、頻繁に使用する電子メールアカウントまたは携帯電話を使用してください。", - "cloud_backup_email_title": "メールアドレス", - "cloud_backup_phone_title": "モバイル", - "cloud_backup_incorrect_email_address": "メールアドレスが正しくありません。", - "cloud_backup_incorrect_verified_code": "このコードは正しくありません", - "cloud_backup_email_verification_code": "電子メール検証コード", - "cloud_backup_phone_verification_code": "モバイル認証コード", - "cloud_backup_preview_title": "マスククラウドサービスへようこそ", - "cloud_backup_preview_description": "個人データを復元するには、適切な方法を選択してください。", - "cloud_backup_preview_switch_other_account": "他のアカウントを切り替え", - "cloud_backup_merge_local_data": "ローカルデータベースにデータを統合", - "cloud_backup_overwrite_backup": "バックアップを上書き", - "cloud_backup_overwrite_current_backup": "既存のバックアップを上書き", - "cloud_backup_overwrite_current_backup_tips": "マスククラウドサービスに保存されているバックアップを上書きしますか?", - "cloud_backup_upload_backup": "バックアップをアップロード", - "cloud_backup_upload_to_cloud": "クラウドにバックアップ", - "cloud_backup_overwrite_tips": "このオプションは、既存のクラウドバックアップをローカルデータで上書きし、復元することはできません。", - "cloud_backup_successfully_tips": "マスククラウドサービスにバックアップをアップロードしました。", - "cloud_backup_merge_to_local_database": "ローカルデータベースにデータを統合", - "cloud_backup_download_link_expired": "ダウンロードリンクの期限が切れています", - "cloud_backup_enter_backup_password_to_decrypt_file": "ファイルをダウンロードするには、クラウドバックアップパスワードを入力してください。", - "cloud_backup_incorrect_backup_password": "クラウドバックアップのパスワードが正しくありません。もう一度やり直してください。", - "cloud_backup_merge_to_local": "ローカルに統合", - "cloud_backup_merge_to_local_congratulation_tips": "マスククラウドサービスからローカルに正常にデータを統合しました。パスワードを再入力すると、バックアップを暗号化してマスククラウドサービスにアップロードできます。", - "cloud_backup_download_backup": "バックアップをダウンロード", - "cloud_backup_merge_to_local_successfully": "バックアップがダウンロードされ、ローカルに正常に統合されました。", - "cloud_backup_merge_to_local_failed": "バックアップがダウンロードされ、ローカルに統合されました。", - "cloud_backup_backup_to_mask_cloud_service": "マスククラウドサービスにバックアップ", - "file_unpacking": "アップパッキング中", - "file_unpacking_completed": "完了しました", - "data_decrypting": "復号中", - "data_downloading": "ダウンロード中", - "switch_other_accounts": "他のアカウントを切り替え", - "file_reselect": "選びなおす", - "mobile_number": "携帯番号", - "persona_phrase_title": "ペルソナ回復フレーズ", - "persona_phrase_tips": "12単語の復元フレーズは、ペルソナデータを回復するために使用されます。", - "persona_phrase_copy_description": "ニーモニックがコピーされました。安全な場所に保管してください。", - "persona_phrase_create_tips": "12単語の秘密の復元フレーズを誰とも共有しないでください!", - "persona_phrase_create_check_tips": "正しい順番で単語を書き留めました", - "persona_onboarding_to_twitter": "Twitter での経験", - "persona_onboarding_set_payment_password": "支払いパスワードの設定", - "persona_onboarding_pin_tips": "Mask Networkをツールバーにピン留めすると、アクセスしやすくなります。", - "persona_onboarding_creating_identity": "あなたのを作成 ", - "persona_onboarding_generating_accounts": "あなたのを生成 ", - "persona_onboarding_encrypting_data": "あなたの暗号化中", - "persona_onboarding_ready": "ペルソナがオン ", - "persona_onboarding_recovery_wallets": "復元しました ", - "persona_onboarding_wallets_one": "{{count}} ウォレット 🚀", - "persona_onboarding_wallets_other": "{{count}} ウォレット 🚀", - "wallet_history_no_data": "履歴記録はありません。", - "welcome_new_agreement_policy": "アプリを継続することにより、これらの サービス契約 および プライバシーポリシーに同意するものとします。", - "wallets_history_burn": "バーン", - "wallet_connect_tips": "WalletConnect ウォレットに接続しています。そのウォレットのネットワークを切り替えるか、別のウォレットに切り替えてください。", - "identity_words": "復元フレーズ", - "private_key": "秘密鍵", - "local_backup": "ローカルバックアップ", - "incorrect_verification_code": "認証コードが無効です。", - "wallet_set_payment_password_successfully": "支払いパスワードを正常に設定しました", - "wallet_open_mask_wallet": "Maskウォレットを開く" -} diff --git a/packages/mask/dashboard/locales/ko-KR.json b/packages/mask/dashboard/locales/ko-KR.json deleted file mode 100644 index a1efd9793bb8..000000000000 --- a/packages/mask/dashboard/locales/ko-KR.json +++ /dev/null @@ -1,517 +0,0 @@ -{ - "address": "주소", - "about": "알아보기", - "backup": "백업", - "continue": "다음", - "create": "만들기", - "congratulations": "축하합니다", - "email": "이메일", - "wallet": "월렛", - "wallets": "월렛", - "personas": "나의 페르소나", - "previous": "이전", - "persona": "페르소나", - "persona_name": "페르소나 이름", - "public_key": "공개 키", - "refresh": "새로고침", - "next": "다음", - "previous_page": "이전", - "next_page": "다음", - "cancel": "취소", - "back": "뒤로", - "agree": "동의", - "confirm": "확인", - "verify": "인증", - "go_back": "돌아가기", - "connect": "연결", - "searching": "검색 중...", - "restore": "복원하기", - "save": "저장", - "manage": "관리", - "recovery": "복구", - "sign_up": "가입하기", - "skip": "넘어가기", - "search_area": "지역 찾기", - "successful": "성공", - "close": "닫기", - "send": "보내기", - "resend": "다시 보내기", - "print": "프린트", - "download": "다운로드", - "identity": "아이덴티티", - "accounts": "계정", - "uploading": "업로딩중", - "data": "데이터", - "ready": "준비 🚀", - "print_preview": "프린트 미리보기", - "incorrect_password": "잘못된 비밀번호", - "download_preview": "다운로드 미리보기", - "download_backup": "백업 다운로드", - "confirm_password": "비밀번호 확인", - "about_dialog_license": "라이센스: ", - "footer_bounty_list": "상금 리스트", - "about_dialog_source_code": "소스 코드: ", - "about_dialog_feedback": "피드백: ", - "about_dialog_touch": "연락하기", - "about_dialog_description": "Mask Network는 새롭고 오픈한 인터넷으로 통하는 포털이다. Mask를 통해 소셜 네트워크에서 암호화 게시글을 공유할 수도 있다. 레드 패킷 보내기, 암호화폐 구매, 파일 서비스 등 기능도 지원한다.", - "setup_page_title": "환영합니다", - "setup_page_description": "소셜 네트워크에서 개시글과 체팅을 암호화하고 친구만 해독할 수 있다.", - "setup_page_create_account_title": "아이디 만들기", - "setup_page_create_account_subtitle": "계정과 데이터의 로컬 스토리지", - "setup_page_create_account_button": "만들기", - "setup_page_create_restore_title": "아이디나 백업에서 복원하기", - "setup_page_create_restore_subtitle": "아이디나 역사 백업에서 복원하기.", - "setup_page_create_restore_button": "복구 또는 로그인", - "create_account_mask_id": "MASK ID", - "create_account_private_key": "개인 키", - "create_account_identity_id": "아이디", - "create_account_identity_title": "Mask Network 아이디 만들기", - "create_account_sign_in_button": "복구 또는 로그인", - "create_account_persona_exists": "이미 존재된 페르소나입니다.", - "create_account_mnemonic_download_or_print": "나의 아이디 코드가 이미 안정하게 보관되었습니다.", - "create_account_preview_tip": "이 QR 코드가 이이디 코드를 포합되어 있어서 잘 보관하시실 바랍니다. QR 코드를 스캔하여 Mask 앱을 로그인할 수 있습니다.", - "create_account_mnemonic_confirm_failed": "잘못된 아이디 코드", - "create_account_connect_social_media_button": "만들기", - "create_account_connect_social_media": "{{type}} 에 연결하기", - "create_account_persona_title": "환영합니다", - "create_account_persona_subtitle": "페르소나를 만들고 소셜 계정을 연결할 수 있습니다.", - "create_account_persona_successfully": "페르소나 생성 성공", - "create_account_connect_social_media_title": "소셜 미디어 연결하기", - "create_account_failed": "계정 만들기 실패", - "follow_us": "팔로우", - "sign_in_account_identity_title": "복구 또는 로그인", - "sign_in_account_tab_identity": "아이덴티티", - "sign_in_account_sign_up_button": "로그인", - "sign_in_account_identity_warning": "디지털 아이디 코드는 디지털 아이디만 복구할 수 있습니다. 이 디지털 아이디가 서명하고 보낸 소셜 정보를 암호화하고 해독할 수 있습니다.", - "sign_in_account_private_key_placeholder": "개인 키 입력하기", - "sign_in_account_private_key_error": "잘못된 아이디 코드", - "sign_in_account_private_key_persona_not_found": "페르소나를 찾을 수 없습니다.", - "sign_in_account_private_key_warning": "디지털 아이디 코드는 디지털 아이디만 복구할 수 있습니다. 이 디지털 아이디가 서명하고 보낸 소셜 정보를 암호화하고 해독할 수 있습니다.", - "sign_in_account_mnemonic_confirm_failed": "잘못된 아이덴티티", - "sign_in_account_cloud_backup_send_email_success": "인증 코드 이미 {{type}} 로 발송되었습니다. {{type}} 확인하세요.", - "sign_in_account_local_backup_warning": "로컬 백업은 로컬에 저장된 전체 데이터를 복구할 수 있습니다.", - "sign_in_account_local_backup_payment_password": "결재 비밀번호", - "sign_in_account_local_backup_file_drag": "클릭하거나 파일을 여기서 끌어들이세요", - "sign_in_account_cloud_backup_warning": "클라우드 백업은 데이터를 호스트하고 암호화합니다.", - "sign_in_account_cloud_backup_not_support": "지원하지 않는 데이터 백업", - "sign_in_account_cloud_send_verification_code_tip": "인증 코드 보내기", - "sign_in_account_cloud_backup_failed": "백업 복구 실패되었습니다. 다시 시도하세요.", - "sign_in_account_cloud_backup_email_or_phone_number": "이메일이나 휴대폰 번호", - "sign_in_account_cloud_backup_password": "비밀번호 백업", - "sign_in_account_cloud_restore_failed": "복구 실패", - "sign_in_account_cloud_backup_download_failed": "다운로드 백업 실패", - "sign_in_account_cloud_backup_decrypt_failed": "해독 실패, 비밀번호를 확인하세요", - "incorrect_backup_password": "잘못된 백업 비밀번호.", - "incorrect_identity_mnemonic": "잘못된 복구 문구", - "sign_in_account_cloud_backup_email_format_error": "이메일 주소가 잘못되었습니다.", - "sign_in_account_cloud_backup_phone_format_error": "전화번호가 잘못되었습니다.", - "sign_in_account_cloud_backup_synchronize_password_tip": "클라우드 비밀번호는 이미 성공적으로 승인되고 백업은 이미 업로드되었습니다. 백업 비밀번호를 통합하려면 클라우드 비밀번호를 로컬 백업 비밀번호로 동기화할지 여부를 확인하십시오.", - "cloud_backup": "클라우드 백업", - "wallets_transfer": "전송", - "wallets_assets": "자산", - "wallets_transfer_memo": "메모", - "wallets_transfer_amount": "수량", - "wallets_transfer_to_address": "받는 주소", - "wallets_print_tips": "이 QR은 니모닉을 저장하는 것이니, 안전하게 보관해주시기 바랍니다.", - "wallets_mnemonic_word": "니모닉 단어", - "wallets_transfer_error_amount_absence": "수액 입력", - "wallets_transfer_error_address_absence": "받는 주소 입력", - "wallets_transfer_error_contract": "NFT 컨트렉트를 선택하세요.", - "wallets_transfer_error_nft": "NFT 선택", - "wallets_transfer_error_invalid_address": "무효한 받는 주소", - "wallet_transfer_error_no_address_has_been_set_name": "수신자의 주소가 잘못되었습니다.", - "wallet_transfer_error_no_ens_support": "네트워크는 ENS를 지원하지 않습니다.", - "wallets_transfer_error_insufficient_balance": "{{symbol}} 잔액 부족", - "wallets_transfer_error_same_address_with_current_account": "받는 주소는 발송 주소와 동일합니다. 다시 확인해 주세요.", - "wallets_transfer_error_is_contract_address": "받는 주소는 컨트랙트 주소입니다. 다시 확인해 주세요.", - "wallets_transfer_send": "발송", - "wallets_transfer_memo_placeholder": "옵셔널 메시지", - "wallets_transfer_contract": "컨트랙트", - "wallets_transfer_contract_placeholder": "NFT 컨트렉트를 선택하세요.", - "wallets_swap": "스왑", - "wallets_red_packet": "레드 패킷", - "wallets_sell": "매도", - "wallets_history": "역사 기록", - "settings": "설정", - "gas_fee": "거래 수수료", - "transfer_cost": "Cost {{gasFee}} {{symbol}} ≈ ${{usd}}", - "done": "완료", - "labs": "Mask Labs", - "onboarding_wallet": "월렛", - "wallet_transactions_pending": "대기중", - "wallet_recovery_title": "월렛 복구", - "wallet_select_address": "주소 선택", - "wallet_derivation_path": "Ethereum {{- path }}", - "wallet_recovery_mnemonic_confirm_failed": "잘못된 니모닉 문구 단어.", - "wallet_recovery_description": "정확한 니모닉 문구 단어와 개인키를 저장하가나 정확한 키스토어 파일을 업로드하세요.", - "wallet_gas_fee_settings_low": "낮음", - "wallet_gas_fee_settings_medium": "보통", - "wallet_gas_fee_settings_high": "높음", - "wallets_startup_create": "새로운 월렛 생성", - "wallets_startup_create_desc": "Mask 월렛은 ETH, BSC, Polygon/Matic 네트워크를 지원합니다.", - "wallets_startup_create_action": "만들기", - "wallets_startup_import": "월렛 불러오기", - "wallets_startup_import_desc": "Mask 월렛은 개인키, JSON.File, 니모닉 단어를 지원합니다.", - "wallets_startup_import_action": "불러오기", - "wallets_startup_connect": "플러그인 월렛 연결하기", - "wallets_startup_connect_desc": "Mask network는 Metamask, WalletConnect를 지원합니다.", - "wallets_startup_connect_action": "연결", - "wallets_connect_wallet_metamask": "MetaMask", - "wallets_connect_wallet_connect": "월렛 연결하기", - "wallets_connect_wallet_polka": "Polkadot 월렛", - "wallets_create_wallet_input_placeholder": "월렛 이름", - "wallets_create_successfully_title": "성공", - "wallets_create_successfully_tips": "월렛을 성공적으로 만들었습니다.", - "wallets_create_successfully_unlock": "월렛 언락", - "wallets_create_wallet_alert": "Mask Network는 무료하는 오픈 소스 클라이언트 사이드 인터페이스입니다. Mask Network 를 통해 키와 펀드의 지배권을 보유하면서 블록체인과 상호작용을 할 수 있습니다. Mask Network는 뱅크나 거래소가 아니고 당신의 개인키, 펀드, 정보를 수집하지 않어서 계정 액세스, 개인키 복구, 비밀번호 초기화, 거래 리버스 등을 하지 못합니다.", - "wallets_wallet_connect_title": "WalletConnect와 호환성이 있는 월렛으로 QR 코드를 스칸하세요.", - "wallets_wallet_mnemonic": "니모닉", - "wallets_wallet_json_file": "로컬 백업", - "wallets_wallet_private_key": "개인 키", - "wallets_import_wallet_tabs": "월렛 불러오기", - "wallets_import_wallet_password_placeholder": "월렛 비밀번호", - "wallets_import_wallet_cancel": "취소", - "wallets_import_wallet_import": "불러오기", - "wallets_create_wallet_tabs": "월렛 탭 만들기", - "wallets_create_wallet_refresh": "새로고침", - "wallets_create_wallet_remember_later": "나중에 기억하기", - "wallets_create_wallet_verification": "인증", - "wallets_collectible_address": "수집품 주소", - "wallets_collectible_token_id": "토큰 ID", - "wallets_collectible_field_contract_require": "수집품 주소가 필요합니다", - "wallets_collectible_field_token_id_require": "토큰 아이디가 필요합니다.", - "wallets_collectible_load_end": "로드 완료", - "wallets_balance": "잔액", - "wallets_balance_all_chain": "모든 체인", - "wallets_balance_Send": "발송", - "wallets_balance_Buy": "매수", - "wallets_balance_Swap": "스왑", - "wallets_balance_Receive": "받기", - "wallets_assets_token": "토큰", - "wallets_assets_investment": "투자", - "wallets_assets_collectibles": "수집품", - "wallets_assets_custom_token": "맞춤형 토큰", - "wallets_assets_custom_collectible": "맞춤형 수집품", - "wallets_assets_asset": "자산", - "wallets_assets_balance": "잔액", - "wallets_assets_price": "가격", - "wallets_assets_value": "값", - "wallets_assets_operation": "운영", - "wallets_assets_more$collapsed": "잔액이 부족한 토큰은 표시되지 않습니다 ({{- symbol }} $1). 전체 보기", - "wallets_assets_more$expanded": "잔액이 부족한 토큰은 표시됩니다 ({{- symbol }} $1). 전체 숨기기", - "wallets_assets_more_show_all": "잔체 보기", - "wallets_address": "월렛 주소", - "wallets_receive_tips": "QR 코드를 스칸하여 {{chainName}} 자산을 전환하기", - "wallets_add_collectible": "수집품 추가", - "wallets_incorrect_address": "잘못된 컨트렉트 주소", - "wallets_collectible_been_added": "이미 추가된 수집품입니다.", - "wallets_collectible_error_not_exist": "해당 수집품은 존재하지 않거나 유저님의 것이 아닙니다.", - "wallets_collectible_contract_is_empty": "켄트랙트를 선택하세요", - "wallets_collectible_token_id_is_empty": "토큰을 선택하세요", - "wallets_collectible_add": "추가", - "wallets_add_token": "토큰 추가", - "wallets_token_been_added": "이미 추가된 토큰입니다.", - "wallets_token_symbol_tips": "기호는 11자 이하여야 합니다.", - "wallets_token_decimals_tips": "소수점은 0 이상이어야 하며 18을 넘지 않아야 합니다.", - "wallets_add_token_contract_address": "토큰 컨트랙트 주소", - "wallets_add_token_symbol": "토큰 기호", - "wallets_add_token_decimals": "소숫점 정밀도", - "wallets_add_token_cancel": "취소", - "wallets_add_token_next": "다음", - "wallets_empty_tokens_tip": "자산이 없습니다. 토큰을 추가하세요.", - "wallets_empty_collectible_tip": "수집품이 없습니다. 수집품을 추가하세요.", - "wallets_reload": "다시 로드", - "wallets_address_copied": "주소가 복사되었습니다", - "public_key_copied": "개인 키가 복사되었습니다.", - "wallets_address_copy": "복사", - "wallets_history_types": "유형", - "wallets_history_value": "값", - "wallets_history_time": "시간", - "wallets_history_receiver": "받는 사람", - "wallets_empty_history_tips": "거래 내역이 없습니다", - "wallets_loading_token": "토큰 로딩", - "personas_setup_connect_tips": "유저님의 {{type}} 계정을 연결하세요.", - "personas_setup_tip": "페르소나를 만들거나 복구하세요.", - "personas_setup_connect": "연결", - "personas_name_maximum_tips": "최대 길이는 {{length}} 자입니다.", - "personas_name_existed": "이미 존재된 페르소나입니다", - "personas_rename_placeholder": "페르소나 이름", - "personas_confirm": "확인", - "personas_cancel": "취소", - "personas_export_persona": "페르소나 수출", - "personas_export_persona_copy": "복사", - "personas_export_persona_copy_success": "복사됨", - "personas_export_persona_copy_failed": "복사 실패", - "personas_export_persona_confirm_password_tip": "비밀번호가 아직 설정되지 않습니다. 개인 키 수출하려면 백업 비밀번호는 먼저 설정해야 합니다.", - "personas_export_private": "개인 키 수출", - "personas_export_private_key_tip": "개인키만 수출이 가능합니다. 다른 데이터 수출할 수 없습니다.", - "personas_delete_confirm_tips": "페르소나 {{nickname}} 를 삭제되는 것을 확인하시고 비밀번호를 입력하세요.", - "personas_delete_dialog_title": "페르소나 삭제", - "personas_edit_dialog_title": "페르소나 편집", - "personas_edit": "편집", - "personas_delete": "삭제", - "personas_logout": "로그아웃", - "personas_logout_confirm_password_tip": "비밀번호가 아직 설정되지 않습니다. 페르소나에 로그아웃하려면 백업 비밀번호는 먼저 설정해야 합니다.", - "personas_add_persona": "페르소나 추가", - "personas_back_up": "백업", - "personas_connect_to": "{{internalName}} 연결하기", - "personas_disconnect": "연결 끊기", - "personas_disconnect_raw": "연결 끊기", - "personas_disconnect_warning": "{{userId}} 의 {{network}} 계정 연결을 해제하시겠습니까? 연결이 끊긴 후 이 계정은 더 이상 Mask Network를 사용하여 정보를 해독하고 암호화할 수 없습니다.", - "personas_logout_warning": "로그아웃 후, 연결된 소셜 계정은 더 이상 암호화하거나 해독할 수 없습나다. 계정을 다시 이용하려면 아이덴티티, 개인 키, 로컬이나 클라우드 백업으로 복원할 수 있습니다.", - "personas_logout_manage_wallet_warning": "주의: 이 페르소나 {{persona}} 는 SmartPay 월렛 {{addresses}}의 관리자 계정입니다. 페르소나를 로그아웃한 후에 SmartPay 월렛을 사용하여 블록체인과 상호작용할 수 없습니다.", - "personas_add": "추가", - "personas_upload_avatar": "아바타 업로드", - "personas_rename": "이름 바꾸기", - "personas_invite_post": "@{{identifier}} 안녕하세요. 암호화된 게시글을 보낼 수 있기를 위해 Mask 다운로드하세요. #mask_io install http://mask.io", - "personas_empty_contact_tips": "연락처에서 Mask Network 를 설치된 자가 없습니다. 친구를 초대하여 {{name}} 다운로드하세요.", - "personas_contacts_name": "이름", - "personas_contacts_operation": "조작", - "personas_contacts_invite": "초대", - "personas_post_is_empty": "작성된 게시글이 없습니다.", - "personas_post_create": "게시글 작성하기", - "print_tips": "이 QR은 아이디 코드를 저장하는 것이니, 안전하게 보관해주시기 바랍니다. ", - "settings_general": "일반", - "settings_backup_recovery": "백업 맟 복원", - "settings_local_backup": "로컬 백업", - "settings_cloud_backup": "클라우드 백업", - "settings_appearance_default": "시스템 따라 설정하기", - "settings_appearance_light": "라이트", - "settings_appearance_dark": "다크", - "settings_backup_preview_account": "계정", - "settings_backup_preview_personas": "페르소나", - "settings_backup_preview_associated_accounts": "관련 계정", - "settings_backup_preview_posts": "암호화된 게시물", - "settings_backup_preview_contacts": "연락처", - "settings_backup_preview_file": "파일", - "settings_backup_preview_wallets": "로컬 월렛", - "settings_backup_preview_set_payment_password": "월렛 백업하기 전에 비빌번호를 설치하여 월렛 기능을 활성화하셔야 합니다. 설정으로 가기", - "settings_backup_preview_created_at": "백업 시간", - "settings_language_title": "언어", - "settings_language_desc": "이용하고 싶은 언어를 선택하세요", - "settings_language_auto": "시스템에 따라 설정", - "settings_appearance_title": "화면", - "settings_appearance_desc": "이용하고 싶은 테마를 선택하세요", - "settings_data_source_title": "데이터 소스", - "settings_data_source_desc": "다른 플랫폼에서 트렌딩 데이터 가져오기", - "settings_sync_with_mobile_title": "모바일과 싱크하기", - "settings_sync_with_mobile_desc": "모바일 디바이스와 계정과 정보를 싱크할 수 있습니다. Mask Network 모바일 앱을 켜고 설정에서 플러그인 싱크를 탭하세요.", - "settings_global_backup_title": "글로벌 백업", - "settings_global_backup_desc": "로컬 백업과 클라우드 백업을 제공합니다.", - "settings_global_backup_last": "가장 최근 백업은 {{backupAt}} 에서 수행되었습니다. 백업 방법: {{backupMethod}}.", - "settings_restore_database_title": "데이터베이스 복원", - "settings_restore_database_desc": "이전의 데이터베이스 백업에서 복원하기", - "settings_email_title": "이메일", - "settings_email_desc": "이메일을 연동하세요", - "settings_change_password_title": "비밀번호 백업", - "settings_change_password_desc": "백업 비밀번호 변경", - "settings_change_password_not_set": "비밀번호 백업이 설정되지 않습니다.", - "settings_phone_number_title": "전화번호", - "settings_phone_number_desc": "전화번호를 연동해주세요.", - "settings_password_rule": "백업 암호는 8자에서 20자 사이여야 하며 숫자, 대문자, 소문자 및 특수 문자를 포함해야 합니다.", - "settings_button_cancel": "취소", - "settings_button_confirm": "확인", - "settings_button_sync": "동기화", - "settings_button_backup": "백업", - "settings_button_recovery": "복구", - "settings_button_setup": "설정", - "settings_button_change": "변경", - "settings_dialogs_bind_email_or_phone": "이메일이나 전화번호를 연동해주세요.", - "settings_dialogs_verify_backup_password": "백업 비밀번호 인증", - "settings_dialogs_setting_backup_password": "백업 비밀번호 설정", - "settings_dialogs_change_backup_password": "백업 비밀번호 변경", - "settings_dialogs_setting_email": "이메일 설정", - "settings_dialogs_change_email": "이메일 변경", - "settings_dialogs_setting_phone_number": "전화번호 추가", - "settings_dialogs_change_phone_number": "전화 번호 변경", - "settings_dialogs_incorrect_code": "인증 코드가 잘못되었습니다", - "settings_dialogs_incorrect_email": "이메일 주소가 잘못되었습니다.", - "settings_dialogs_incorrect_phone": "전화번호가 잘못되었습니다.", - "settings_dialogs_incorrect_password": "잘못된 비밀번호.", - "settings_dialogs_inconsistency_password": "비밀번호 일치하지 않습니다.", - "settings_dialogs_current_email_validation": "현재 인증 이메일", - "settings_dialogs_change_email_validation": "이메일을 변경하기 위해 현재 이메일 주소를 인증해야 합니다.", - "settings_dialogs_current_phone_validation": "현재 인증 전화번호", - "settings_dialogs_change_phone_validation": "전화번호를 변경하기 위해 현재 전화번호를 인증해야 합니다.", - "settings_dialogs_backup_to_cloud": "클라우드 백업", - "settings_dialogs_merge_to_local_data": "로컬 데이터로 합병하기", - "settings_dialogs_backup_action_desc": "클라우드 백업이 이미 존재되어 있습니다. 클라우드 백업을 로컬에 합병하거나 로컬 데이터를 클라우드에 업로드하기를 선택하세요.", - "settings_dialogs_backup_to_cloud_action": "이 옵션은 기존 클라우드 백업을 로컬 데이터로 덮어씁니다.", - "settings_dialogs_backup_merge_cloud": "기존 클라우드 백업을 해독하려면 비밀번호를 입력해야 합니다. 기존 클라우드 백업과 로컬 데이터가 결합되고 암호화되어 클라우드에 업로드됩니다.", - "settings_dialogs_backup_merged_tip": "클라우드 백업은 이미 로컬로 다운받았습니다. 백업을 완성하려면 백업 버튼을 누르고 오든 테이터를 클라우드로 백업하세요.", - "settings_label_backup_password": "비밀번호 백업", - "settings_label_new_backup_password": "새로운 비밀번호 백업", - "settings_label_backup_password_cloud": "클라우드 파일 백업 비밀번호", - "settings_label_payment_password": "결재 비밀번호", - "settings_label_re_enter": "다시 입력", - "settings_alert_password_set": "백업 비밀번호 설정", - "settings_alert_password_updated": "백업 비밀번호 업데이트", - "settings_alert_email_set": "이메일 설정", - "settings_alert_email_updated": "이메일 업데이트되었습니다", - "settings_alert_phone_number_set": "전화번호 설정", - "settings_alert_phone_number_updated": "전화번호가 업데이트되었습니다", - "settings_alert_backup_fail": "백업 실패", - "settings_alert_backup_success": "데이터가 이미 백업되었습니다.", - "settings_alert_validation_code_sent": "인증 코드가 발송되었습니다", - "settings_alert_merge_success": "클라우드 백업과 로컬 데이터를 이미 합병되었습니다.", - "labs_file_service": "파일 서비스", - "labs_file_service_desc": "탈중앙화 파일 저장", - "labs_red_packet": "레드 패킷", - "labs_red_packet_desc": "축복을 암호화된 레드 패킷으로 포장하고 친구들에게 보내세요.", - "labs_swap": "스왑", - "labs_swap_desc": "추가 비용과 제한 없이 DEX로 토큰을 구매하기", - "labs_transak": "Transak", - "labs_transak_desc": "Transak 지원으로 60+ 국가에서 암호화폐 구매하기", - "labs_savings": "저금", - "labs_savings_desc": "암호화폐를 다양한 저축 프로토콜에 배포하고 수익을 확인하세요.", - "labs_snapshot": "Snapshot", - "labs_snapshot_desc": "소셜 미디어에서 직접 제안을 표시하고 투표하기", - "labs_market_trend": "마켓 추세", - "labs_market_trend_desc": "토큰 정보, 가격 차트, 거래 정보을 직접 소셜 미디어에서 표시하기", - "labs_collectibles": "Collectibles", - "labs_collectibles_desc": "Opensea and Rarible의 지정 정보를 포시하고 소셜미디어에서 경매하기", - "labs_gitcoin": "Gitcoin", - "labs_gitcoin_desc": "Gitcoin의 지정 정보를 표시하고 소셜미디어에서 프로젝트에게 적접 기부하기", - "labs_valuables": "가치", - "labs_valuables_desc": "크리에이터가 서명한 트윗을 구입 및 판매하기", - "labs_mask_box": "MaskBox", - "labs_mask_box_desc": "NFT 미스터리박스를 출시하는 멀티체인 탈중앙화 플랫폼", - "labs_loot_man": "LootMan by MintTeam", - "labs_loot_man_desc": "NFT의 무한한 가능성을 탐색. 소셜미디어에서 보유하는 NFT를 전시하는 혁신적인 방식", - "labs_settings_market_trend": "마켓 추세 설정", - "labs_settings_market_trend_source": "디폴트 추세 소스", - "labs_settings_swap": "스왑 설정", - "labs_settings_swap_network": "{{network}} 네트워크 디폴트 추세 소스", - "labs_pets": "LootMan by MintTeam", - "labs_pets_desc": "NFT의 무한한 가능성을 탐색. 소셜미디어에서 보유하는 NFT를 전시하는 혁신적인 방식", - "labs_cyber_connect": "CyberConnect", - "labs_cyber_connect_desc": "사용자 중심 웹3를 위한 탈중앙화 소셜 그래프 프로토콜", - "labs_setup_tutorial": "튜토리얼 설정", - "labs_do_not_show_again": "다시 보이지 않기", - "labs_art_blocks": "Artblocks", - "labs_art_blocks_desc": "Artblocks는 마음에 드는 스타일을 골라 작업비를 지불하고 무작위로 생성된 콘텐츠 버전이 알고리즘에 의해 생성돼 이더리움 계정으로 전송를 지원하는 겁니다.", - "dashboard_mobile_test": "모바일 테스트 참여", - "dashboard_source_code": "소스 코드", - "privacy_policy": "개인정보처리방침", - "version_of_stable": "버전 {{version}}", - "version_of_unstable": "버전 {{version}}-{{build}}-{{hash}}", - "register_restore_backups": "백업 복원", - "register_restore_backups_cancel": "취소", - "register_restore_backups_confirm": "복원", - "register_restore_backups_hint": "클릭하거나 파일을 여기서 끌어들이세요", - "register_restore_backups_file": "파일", - "register_restore_backups_text": "텍스트", - "register_restore_backups_tabs": "백업 복원 탭", - "create_wallet_mnemonic_tip": "월렛의 자산을 보호하기 위해 니모닉 단어를 잘 저장하고 잊지 마세요.", - "create_wallet_mnemonic_verification_fail": "단어를 잘못하게 선택했습니다. 다시 시도하세요!", - "create_wallet_onboarding_got_it": "알겠습니다", - "create_wallet_onboarding_creating_identity": "생성 ", - "create_wallet_onboarding_ready": "월렛 네트워크 ", - "create_wallet_onboarding_generating_accounts": "생성 ", - "create_wallet_onboarding_encrypting_data": "암호화 ", - "create_wallet_mnemonic_keep_safe": "안전 보관", - "create_wallet_password_uppercase_tip": "대문자 하나가 포함되어야 합니다", - "create_wallet_password_lowercase_tip": "소문자 하나가 포함되어야 합니다", - "create_wallet_password_number_tip": "숫자 하나가 포함되어야 합니다", - "create_wallet_password_special_tip": "특수문자 하나가 포함되어야 합니다", - "create_wallet_password_satisfied_requirement": "비밀번호가 요구 사항을 충족하지 않습니다.", - "create_wallet_password_match_tip": "비밀번호가 일치하지 않습니다", - "create_wallet_password_length_error": "비밀번호 길이가 잘못되었습니다.", - "create_wallet_name_placeholder": "1-12 자 입력하세요", - "create_wallet_form_title": "월렛 만들기", - "set_payment_password": "결제 비밀번호 설정", - "write_down_recovery_phrase": "복원 분구를 기입하세요", - "store_recovery_phrase_tip": "단어를 정확한 방식으로 기입하거나 복사하고 안전한 곳에 보관하시길 바랍니다.", - "create_wallet_payment_password_place_holder": "최소 6자 이상 이용하세요", - "create_wallet_re_enter_payment_password": "결재 비밀번호 다시 설정", - "create_wallet_payment_password_tip_1": "결제 비밀번호는 8~20자 사이어야 합니다.", - "create_wallet_payment_password_tip_2": "회원님의 결제비밀번호는 월렛 데이터를 암호화한 것으로 거래확인 및 잠금해제를 위해 필요하며, 고객님의 기기에 저장되어 있어 다른 경로로 복구할 수 없습니다. 주의하시길 바랍니다.", - "create_wallet_your_wallet_address": "월렛 주소", - "create_wallet_key_store_not_support": "지원하지 않는 키스토어 데이터", - "create_wallet_key_store_password": "키스토어 비밀번호", - "create_wallet_key_store_incorrect_password": "잘못된 키스토어 비밀번호.", - "create_wallet_done": "완료", - "create_wallet_verify_words": "이모닉 단어 인증", - "create_wallet_mnemonic_word_not_match": "니모닉 단어가 불일치합니다", - "recovery_smart_pay_wallet_title": "SmartPay 월렛 복구", - "recovery_smart_pay_wallet_description_one": "{{count}} 로컬에서 감지됩니다. 복구 성공.", - "recovery_smart_pay_wallet_description_other": "{{count}} 로컬에서 감지됩니다. 복구 성공.", - "welcome_request_to_collect": "제품을 개선하기 위해 이용정보 수입을 허용하시길 바랍니다.", - "welcome_to_use_mask_network": "환영합니다", - "create_step": "Step {{step}}/{{totalSteps}}", - "persona_create_title": "새로운 Mask 아이디 만들기", - "persona_create_tips": "이용하기 전에 페르소나 한번 만들어 보세요", - "persona_setup_persona_example": "예: Alice", - "data_recovery_title": "데어터 복구", - "data_recovery_description": "12개 복구 문구로 페르소나 데어터를 복구할 수 있습니다.", - "data_recovery_email": "이메일", - "data_recovery_email_code": "메일 인증번호", - "data_recovery_mobile": "모바일", - "data_recovery_mobile_code": "모바일 인증번호", - "data_recovery_invalid_mobile": "무효한 전화 번호입니다. 다시 시도해 보세요.", - "data_recovery_set_name": "페르소나 이름 설정하기", - "data_recovery_name_tip": "최대 24자로 페로소나 이름 설정합니다", - "data_backup_no_backups_found": "백업 없음", - "data_backup_title": "백업 내용을 선택하세요.", - "data_backup_description": "개인 데이터를 복원할 수 있는 적절한 방법을 선택하세요.", - "cloud_backup_title": "Mask 클라우드 로그인", - "cloud_backup_no_exist_tips": "자주 이용하는 이메일이나 전화번호로 백업하시길 바랍니다.", - "cloud_backup_email_title": "이메일", - "cloud_backup_phone_title": "모바일", - "cloud_backup_incorrect_email_address": "이메일 주소가 잘못되었습니다.", - "cloud_backup_incorrect_verified_code": "코드가 잘못되었습니다.", - "cloud_backup_email_verification_code": "메일 인증번호", - "cloud_backup_preview_title": "Welcome to Mask Cloud Services", - "cloud_backup_preview_description": "개인 데이터를 복원할 수 있는 적절한 방법을 선택하세요.", - "cloud_backup_preview_switch_other_account": "사용자 계정 전환", - "cloud_backup_merge_local_data": "데이터를 로컬 데이터베이스에 병합하기", - "cloud_backup_overwrite_backup": "백업 덮어쓰기", - "cloud_backup_overwrite_current_backup": "기존 백업 덮어쓰기", - "cloud_backup_overwrite_current_backup_tips": "Mask Cloud Service에 저장된 백업을 덮어쓰시겠습니까?", - "cloud_backup_upload_backup": "백업 업로드", - "cloud_backup_upload_to_cloud": "클라우드 백업", - "cloud_backup_overwrite_tips": "이 옵션은 기존 클라우드 백업을 로컬 데이터로 덮어쓰므로 더 이상 복구할 수 없습니다.", - "cloud_backup_successfully_tips": "Mask Cloud Service에 백업을 업로드했습니다.", - "cloud_backup_merge_to_local_database": "데이터를 로컬 데이터베이스에 병합하기", - "cloud_backup_download_link_expired": "다운로드 링크가 만료되었습니다", - "cloud_backup_enter_backup_password_to_decrypt_file": "파일을 다운로드하려면 클라우드 백업 암호를 입력하세요.", - "cloud_backup_incorrect_backup_password": "잘못된 클라우드 백업 비밀번호입니다. 다시 시도하세요.", - "cloud_backup_merge_to_local": "로컬로 합병하기", - "cloud_backup_merge_to_local_congratulation_tips": "데이터가 Mask Cloud Service에서 Local로 병합되었습니다. 암호를 다시 입력하여 암호화한 후 Mask Cloud Service에 백업을 업로드합니다.", - "cloud_backup_download_backup": "백업 다운로드", - "cloud_backup_merge_to_local_successfully": "백업을 다운로드하여 로컬로 병합했습니다.", - "cloud_backup_merge_to_local_failed": "로컬로 백업을 다운로드하여 병합하지 못했습니다.", - "cloud_backup_backup_to_mask_cloud_service": "Mask Cloud Service 백업", - "file_unpacking": "업패킹", - "file_unpacking_completed": "완료됨", - "data_decrypting": "복호화 중", - "data_downloading": "다운로드 중", - "switch_other_accounts": "사용자 계정 전환", - "file_reselect": "다시 선택", - "mobile_number": "휴대폰 번호", - "persona_phrase_title": "페르소나 복구 문구", - "persona_phrase_tips": "12개 복구 문구로 페르소나 데어터를 복구할 수 있습니다.", - "persona_phrase_copy_description": "니모닉이 복사되었습니다. 안전하게 보관하시길 바랍니다.", - "persona_phrase_create_tips": "12단어 비밀 복구 문구를 다른 사람과 공유하지 마세요!", - "persona_phrase_create_check_tips": "단어를 정확한 순서대로 적었습니다", - "persona_onboarding_to_twitter": "트위터에서 체험하기", - "persona_onboarding_set_payment_password": "결제 비밀번호 설정", - "persona_onboarding_pin_tips": "Mask Network를 툴바에 고정하여 보다 쉽게 액세스할 수 있습니다:", - "persona_onboarding_creating_identity": "생성 ", - "persona_onboarding_generating_accounts": "생성 ", - "persona_onboarding_encrypting_data": "암호화 ", - "persona_onboarding_ready": "페로소나 ", - "persona_onboarding_recovery_wallets": "북구 ", - "persona_onboarding_wallets_one": "{{count}} 월렛 🚀", - "persona_onboarding_wallets_other": "{{count}} 월렛 🚀", - "wallet_history_no_data": "역사 기록이 없습니다.", - "welcome_new_agreement_policy": "서비스 약관개인정보처리방침을 동의합니다.", - "wallets_history_burn": "소각", - "wallet_connect_tips": "WalletConnect 월렛에 연결되어 있습니다. 해당 월렛에서 네트워크를 전환하기나 다른 월렛으로 바꾸세요.", - "identity_words": "복구 문구", - "private_key": "개인 키", - "local_backup": "로컬 백업", - "incorrect_verification_code": "유효하지 않은 인증 코드입니다.", - "wallet_set_payment_password_successfully": "결제 비밀번호 설정 성공", - "wallet_open_mask_wallet": "Mask 월렛 열기" -} diff --git a/packages/mask/dashboard/locales/languages.ts b/packages/mask/dashboard/locales/languages.ts deleted file mode 100644 index 56f04baa0e9a..000000000000 --- a/packages/mask/dashboard/locales/languages.ts +++ /dev/null @@ -1,33 +0,0 @@ -// This file is auto generated. DO NOT EDIT -// Run `npx gulp sync-languages` to regenerate. -// Default fallback language in a family of languages are chosen by the alphabet order -// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts -import en_US from './en-US.json' -import ja_JP from './ja-JP.json' -import ko_KR from './ko-KR.json' -import qya_AA from './qya-AA.json' -import zh_CN from './zh-CN.json' -import zh_TW from './zh-TW.json' -export const languages = { - en: en_US, - ja: ja_JP, - ko: ko_KR, - qy: qya_AA, - 'zh-CN': zh_CN, - zh: zh_TW, -} -import { createI18NBundle } from '@masknet/shared-base' -export const addDashboardI18N = createI18NBundle('dashboard', languages) -// @ts-ignore -if (import.meta.webpackHot) { - // @ts-ignore - import.meta.webpackHot.accept( - ['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'], - () => - globalThis.dispatchEvent?.( - new CustomEvent('MASK_I18N_HMR', { - detail: ['dashboard', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }], - }), - ), - ) -} diff --git a/packages/mask/dashboard/locales/qya-AA.json b/packages/mask/dashboard/locales/qya-AA.json deleted file mode 100644 index 4f7943d4cdad..000000000000 --- a/packages/mask/dashboard/locales/qya-AA.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "address": "crwdns21483:0crwdne21483:0", - "about": "crwdns1619:0crwdne1619:0", - "backup": "crwdns22349:0crwdne22349:0", - "continue": "crwdns20457:0crwdne20457:0", - "create": "crwdns21485:0crwdne21485:0", - "congratulations": "crwdns22351:0crwdne22351:0", - "email": "crwdns22353:0crwdne22353:0", - "wallet": "crwdns21487:0crwdne21487:0", - "wallets": "crwdns1621:0crwdne1621:0", - "personas": "crwdns1623:0crwdne1623:0", - "previous": "crwdns21489:0crwdne21489:0", - "persona": "crwdns7557:0crwdne7557:0", - "persona_name": "crwdns21849:0crwdne21849:0", - "public_key": "crwdns20459:0crwdne20459:0", - "refresh": "crwdns7559:0crwdne7559:0", - "next": "crwdns7561:0crwdne7561:0", - "previous_page": "crwdns22197:0crwdne22197:0", - "next_page": "crwdns22199:0crwdne22199:0", - "cancel": "crwdns7563:0crwdne7563:0", - "back": "crwdns7565:0crwdne7565:0", - "agree": "crwdns7567:0crwdne7567:0", - "confirm": "crwdns7569:0crwdne7569:0", - "verify": "crwdns7571:0crwdne7571:0", - "go_back": "crwdns7573:0crwdne7573:0", - "connect": "crwdns7575:0crwdne7575:0", - "searching": "crwdns7863:0crwdne7863:0", - "restore": "crwdns7835:0crwdne7835:0", - "save": "crwdns7955:0crwdne7955:0", - "manage": "crwdns8027:0crwdne8027:0", - "recovery": "crwdns7995:0crwdne7995:0", - "sign_up": "crwdns20561:0crwdne20561:0", - "skip": "crwdns20461:0crwdne20461:0", - "search_area": "crwdns22355:0crwdne22355:0", - "successful": "crwdns8077:0crwdne8077:0", - "close": "crwdns8173:0crwdne8173:0", - "send": "crwdns8203:0crwdne8203:0", - "resend": "crwdns8205:0crwdne8205:0", - "print": "crwdns13039:0crwdne13039:0", - "download": "crwdns13041:0crwdne13041:0", - "identity": "crwdns20463:0crwdne20463:0", - "accounts": "crwdns20465:0crwdne20465:0", - "uploading": "crwdns22357:0crwdne22357:0", - "data": "crwdns20467:0crwdne20467:0", - "ready": "crwdns20469:0crwdne20469:0", - "print_preview": "crwdns13043:0crwdne13043:0", - "incorrect_password": "crwdns22359:0crwdne22359:0", - "download_preview": "crwdns13045:0crwdne13045:0", - "download_backup": "crwdns22361:0crwdne22361:0", - "confirm_password": "crwdns8175:0crwdne8175:0", - "about_dialog_license": "crwdns1625:0crwdne1625:0", - "footer_bounty_list": "crwdns1627:0crwdne1627:0", - "about_dialog_source_code": "crwdns1629:0crwdne1629:0", - "about_dialog_feedback": "crwdns1631:0crwdne1631:0", - "about_dialog_touch": "crwdns1633:0crwdne1633:0", - "about_dialog_description": "crwdns1635:0crwdne1635:0", - "setup_page_title": "crwdns1637:0crwdne1637:0", - "setup_page_description": "crwdns1639:0crwdne1639:0", - "setup_page_create_account_title": "crwdns1641:0crwdne1641:0", - "setup_page_create_account_subtitle": "crwdns1643:0crwdne1643:0", - "setup_page_create_account_button": "crwdns1645:0crwdne1645:0", - "setup_page_create_restore_title": "crwdns1647:0crwdne1647:0", - "setup_page_create_restore_subtitle": "crwdns1649:0crwdne1649:0", - "setup_page_create_restore_button": "crwdns1651:0crwdne1651:0", - "create_account_mask_id": "crwdns13047:0crwdne13047:0", - "create_account_private_key": "crwdns13049:0crwdne13049:0", - "create_account_identity_id": "crwdns13051:0crwdne13051:0", - "create_account_identity_title": "crwdns7577:0crwdne7577:0", - "create_account_sign_in_button": "crwdns7579:0crwdne7579:0", - "create_account_persona_exists": "crwdns13053:0crwdne13053:0", - "create_account_mnemonic_download_or_print": "crwdns13055:0crwdne13055:0", - "create_account_preview_tip": "crwdns13057:0crwdne13057:0", - "create_account_mnemonic_confirm_failed": "crwdns7583:0crwdne7583:0", - "create_account_connect_social_media_button": "crwdns7585:0crwdne7585:0", - "create_account_connect_social_media": "crwdns7587:0{{type}}crwdne7587:0", - "create_account_persona_title": "crwdns7589:0crwdne7589:0", - "create_account_persona_subtitle": "crwdns7591:0crwdne7591:0", - "create_account_persona_successfully": "crwdns7593:0crwdne7593:0", - "create_account_connect_social_media_title": "crwdns7595:0crwdne7595:0", - "create_account_failed": "crwdns7597:0crwdne7597:0", - "follow_us": "crwdns18582:0crwdne18582:0", - "sign_in_account_identity_title": "crwdns7599:0crwdne7599:0", - "sign_in_account_tab_identity": "crwdns10039:0crwdne10039:0", - "sign_in_account_sign_up_button": "crwdns7601:0crwdne7601:0", - "sign_in_account_identity_warning": "crwdns7603:0crwdne7603:0", - "sign_in_account_private_key_placeholder": "crwdns7605:0crwdne7605:0", - "sign_in_account_private_key_error": "crwdns7607:0crwdne7607:0", - "sign_in_account_private_key_persona_not_found": "crwdns7609:0crwdne7609:0", - "sign_in_account_private_key_warning": "crwdns7611:0crwdne7611:0", - "sign_in_account_mnemonic_confirm_failed": "crwdns8191:0crwdne8191:0", - "sign_in_account_cloud_backup_send_email_success": "crwdns8079:0{{type}}crwdnd8079:0{{type}}crwdne8079:0", - "sign_in_account_local_backup_warning": "crwdns7613:0crwdne7613:0", - "sign_in_account_local_backup_payment_password": "crwdns9329:0crwdne9329:0", - "sign_in_account_local_backup_file_drag": "crwdns7615:0crwdne7615:0", - "sign_in_account_cloud_backup_warning": "crwdns7617:0crwdne7617:0", - "sign_in_account_cloud_backup_not_support": "crwdns8081:0crwdne8081:0", - "sign_in_account_cloud_send_verification_code_tip": "crwdns8193:0crwdne8193:0", - "sign_in_account_cloud_backup_failed": "crwdns8083:0crwdne8083:0", - "sign_in_account_cloud_backup_email_or_phone_number": "crwdns7619:0crwdne7619:0", - "sign_in_account_cloud_backup_password": "crwdns7621:0crwdne7621:0", - "sign_in_account_cloud_restore_failed": "crwdns7623:0crwdne7623:0", - "sign_in_account_cloud_backup_download_failed": "crwdns7625:0crwdne7625:0", - "sign_in_account_cloud_backup_decrypt_failed": "crwdns7627:0crwdne7627:0", - "incorrect_backup_password": "crwdns21651:0crwdne21651:0", - "incorrect_identity_mnemonic": "crwdns21653:0crwdne21653:0", - "sign_in_account_cloud_backup_email_format_error": "crwdns8085:0crwdne8085:0", - "sign_in_account_cloud_backup_phone_format_error": "crwdns8087:0crwdne8087:0", - "sign_in_account_cloud_backup_synchronize_password_tip": "crwdns8089:0crwdne8089:0", - "cloud_backup": "crwdns7629:0crwdne7629:0", - "wallets_transfer": "crwdns1653:0crwdne1653:0", - "wallets_assets": "crwdns8003:0crwdne8003:0", - "wallets_transfer_memo": "crwdns8005:0crwdne8005:0", - "wallets_transfer_amount": "crwdns8007:0crwdne8007:0", - "wallets_transfer_to_address": "crwdns8009:0crwdne8009:0", - "wallets_print_tips": "crwdns21491:0crwdne21491:0", - "wallets_mnemonic_word": "crwdns21493:0crwdne21493:0", - "wallets_transfer_error_amount_absence": "crwdns8011:0crwdne8011:0", - "wallets_transfer_error_address_absence": "crwdns8013:0crwdne8013:0", - "wallets_transfer_error_contract": "crwdns9331:0crwdne9331:0", - "wallets_transfer_error_nft": "crwdns9333:0crwdne9333:0", - "wallets_transfer_error_invalid_address": "crwdns8015:0crwdne8015:0", - "wallet_transfer_error_no_address_has_been_set_name": "crwdns10429:0crwdne10429:0", - "wallet_transfer_error_no_ens_support": "crwdns10431:0crwdne10431:0", - "wallets_transfer_error_insufficient_balance": "crwdns8017:0{{symbol}}crwdne8017:0", - "wallets_transfer_error_same_address_with_current_account": "crwdns10617:0crwdne10617:0", - "wallets_transfer_error_is_contract_address": "crwdns10619:0crwdne10619:0", - "wallets_transfer_send": "crwdns8019:0crwdne8019:0", - "wallets_transfer_memo_placeholder": "crwdns8021:0crwdne8021:0", - "wallets_transfer_contract": "crwdns9335:0crwdne9335:0", - "wallets_transfer_contract_placeholder": "crwdns9337:0crwdne9337:0", - "wallets_swap": "crwdns1655:0crwdne1655:0", - "wallets_red_packet": "crwdns1657:0crwdne1657:0", - "wallets_sell": "crwdns1659:0crwdne1659:0", - "wallets_history": "crwdns1661:0crwdne1661:0", - "settings": "crwdns1663:0crwdne1663:0", - "gas_fee": "crwdns8989:0crwdne8989:0", - "transfer_cost": "crwdns8991:0{{gasFee}}crwdnd8991:0{{symbol}}crwdnd8991:0{{usd}}crwdne8991:0", - "done": "crwdns1665:0crwdne1665:0", - "labs": "crwdns1667:0crwdne1667:0", - "onboarding_wallet": "crwdns22171:0crwdne22171:0", - "wallet_transactions_pending": "crwdns7921:0crwdne7921:0", - "wallet_recovery_title": "crwdns21495:0crwdne21495:0", - "wallet_select_address": "crwdns21497:0crwdne21497:0", - "wallet_derivation_path": "crwdns21499:0{{- path }}crwdne21499:0", - "wallet_recovery_mnemonic_confirm_failed": "crwdns21501:0crwdne21501:0", - "wallet_recovery_description": "crwdns21503:0crwdne21503:0", - "wallet_gas_fee_settings_low": "crwdns9015:0crwdne9015:0", - "wallet_gas_fee_settings_medium": "crwdns9017:0crwdne9017:0", - "wallet_gas_fee_settings_high": "crwdns9019:0crwdne9019:0", - "wallets_startup_create": "crwdns1669:0crwdne1669:0", - "wallets_startup_create_desc": "crwdns1671:0crwdne1671:0", - "wallets_startup_create_action": "crwdns1673:0crwdne1673:0", - "wallets_startup_import": "crwdns1675:0crwdne1675:0", - "wallets_startup_import_desc": "crwdns1677:0crwdne1677:0", - "wallets_startup_import_action": "crwdns1679:0crwdne1679:0", - "wallets_startup_connect": "crwdns1681:0crwdne1681:0", - "wallets_startup_connect_desc": "crwdns1683:0crwdne1683:0", - "wallets_startup_connect_action": "crwdns1685:0crwdne1685:0", - "wallets_connect_wallet_metamask": "crwdns1687:0crwdne1687:0", - "wallets_connect_wallet_connect": "crwdns1689:0crwdne1689:0", - "wallets_connect_wallet_polka": "crwdns1691:0crwdne1691:0", - "wallets_create_wallet_input_placeholder": "crwdns1693:0crwdne1693:0", - "wallets_create_successfully_title": "crwdns1695:0crwdne1695:0", - "wallets_create_successfully_tips": "crwdns1697:0crwdne1697:0", - "wallets_create_successfully_unlock": "crwdns1699:0crwdne1699:0", - "wallets_create_wallet_alert": "crwdns1701:0crwdne1701:0", - "wallets_wallet_connect_title": "crwdns1703:0crwdne1703:0", - "wallets_wallet_mnemonic": "crwdns1705:0crwdne1705:0", - "wallets_wallet_json_file": "crwdns1707:0crwdne1707:0", - "wallets_wallet_private_key": "crwdns1709:0crwdne1709:0", - "wallets_import_wallet_tabs": "crwdns1711:0crwdne1711:0", - "wallets_import_wallet_password_placeholder": "crwdns1713:0crwdne1713:0", - "wallets_import_wallet_cancel": "crwdns1715:0crwdne1715:0", - "wallets_import_wallet_import": "crwdns1717:0crwdne1717:0", - "wallets_create_wallet_tabs": "crwdns1719:0crwdne1719:0", - "wallets_create_wallet_refresh": "crwdns1721:0crwdne1721:0", - "wallets_create_wallet_remember_later": "crwdns1723:0crwdne1723:0", - "wallets_create_wallet_verification": "crwdns1725:0crwdne1725:0", - "wallets_collectible_address": "crwdns1727:0crwdne1727:0", - "wallets_collectible_token_id": "crwdns7951:0crwdne7951:0", - "wallets_collectible_field_contract_require": "crwdns9021:0crwdne9021:0", - "wallets_collectible_field_token_id_require": "crwdns9023:0crwdne9023:0", - "wallets_collectible_load_end": "crwdns9339:0crwdne9339:0", - "wallets_balance": "crwdns1729:0crwdne1729:0", - "wallets_balance_all_chain": "crwdns9341:0crwdne9341:0", - "wallets_balance_Send": "crwdns1731:0crwdne1731:0", - "wallets_balance_Buy": "crwdns1733:0crwdne1733:0", - "wallets_balance_Swap": "crwdns1735:0crwdne1735:0", - "wallets_balance_Receive": "crwdns1737:0crwdne1737:0", - "wallets_assets_token": "crwdns1739:0crwdne1739:0", - "wallets_assets_investment": "crwdns1741:0crwdne1741:0", - "wallets_assets_collectibles": "crwdns8171:0crwdne8171:0", - "wallets_assets_custom_token": "crwdns1745:0crwdne1745:0", - "wallets_assets_custom_collectible": "crwdns1747:0crwdne1747:0", - "wallets_assets_asset": "crwdns1749:0crwdne1749:0", - "wallets_assets_balance": "crwdns1751:0crwdne1751:0", - "wallets_assets_price": "crwdns1753:0crwdne1753:0", - "wallets_assets_value": "crwdns1755:0crwdne1755:0", - "wallets_assets_operation": "crwdns1757:0crwdne1757:0", - "wallets_assets_more$collapsed": "crwdns20011:0{{- symbol }}crwdne20011:0", - "wallets_assets_more$expanded": "crwdns20013:0{{- symbol }}crwdne20013:0", - "wallets_assets_more_show_all": "crwdns18530:0crwdne18530:0", - "wallets_address": "crwdns1759:0crwdne1759:0", - "wallets_receive_tips": "crwdns1761:0{{chainName}}crwdne1761:0", - "wallets_add_collectible": "crwdns1763:0crwdne1763:0", - "wallets_incorrect_address": "crwdns1765:0crwdne1765:0", - "wallets_collectible_been_added": "crwdns1767:0crwdne1767:0", - "wallets_collectible_error_not_exist": "crwdns7953:0crwdne7953:0", - "wallets_collectible_contract_is_empty": "crwdns9347:0crwdne9347:0", - "wallets_collectible_token_id_is_empty": "crwdns9349:0crwdne9349:0", - "wallets_collectible_add": "crwdns1769:0crwdne1769:0", - "wallets_add_token": "crwdns1771:0crwdne1771:0", - "wallets_token_been_added": "crwdns1773:0crwdne1773:0", - "wallets_token_symbol_tips": "crwdns1775:0crwdne1775:0", - "wallets_token_decimals_tips": "crwdns1777:0crwdne1777:0", - "wallets_add_token_contract_address": "crwdns1779:0crwdne1779:0", - "wallets_add_token_symbol": "crwdns1781:0crwdne1781:0", - "wallets_add_token_decimals": "crwdns1783:0crwdne1783:0", - "wallets_add_token_cancel": "crwdns1785:0crwdne1785:0", - "wallets_add_token_next": "crwdns1787:0crwdne1787:0", - "wallets_empty_tokens_tip": "crwdns1789:0crwdne1789:0", - "wallets_empty_collectible_tip": "crwdns1791:0crwdne1791:0", - "wallets_reload": "crwdns17240:0crwdne17240:0", - "wallets_address_copied": "crwdns1793:0crwdne1793:0", - "public_key_copied": "crwdns16574:0crwdne16574:0", - "wallets_address_copy": "crwdns1795:0crwdne1795:0", - "wallets_history_types": "crwdns1797:0crwdne1797:0", - "wallets_history_value": "crwdns1799:0crwdne1799:0", - "wallets_history_time": "crwdns1801:0crwdne1801:0", - "wallets_history_receiver": "crwdns1803:0crwdne1803:0", - "wallets_empty_history_tips": "crwdns1805:0crwdne1805:0", - "wallets_loading_token": "crwdns1807:0crwdne1807:0", - "personas_setup_connect_tips": "crwdns1809:0{{type}}crwdne1809:0", - "personas_setup_tip": "crwdns8091:0crwdne8091:0", - "personas_setup_connect": "crwdns1811:0crwdne1811:0", - "personas_name_maximum_tips": "crwdns1813:0{{length}}crwdne1813:0", - "personas_name_existed": "crwdns7965:0crwdne7965:0", - "personas_rename_placeholder": "crwdns1815:0crwdne1815:0", - "personas_confirm": "crwdns1817:0crwdne1817:0", - "personas_cancel": "crwdns1819:0crwdne1819:0", - "personas_export_persona": "crwdns7939:0crwdne7939:0", - "personas_export_persona_copy": "crwdns7941:0crwdne7941:0", - "personas_export_persona_copy_success": "crwdns7943:0crwdne7943:0", - "personas_export_persona_copy_failed": "crwdns7945:0crwdne7945:0", - "personas_export_persona_confirm_password_tip": "crwdns8177:0crwdne8177:0", - "personas_export_private": "crwdns7947:0crwdne7947:0", - "personas_export_private_key_tip": "crwdns7949:0crwdne7949:0", - "personas_delete_confirm_tips": "crwdns1821:0{{nickname}}crwdne1821:0", - "personas_delete_dialog_title": "crwdns1823:0crwdne1823:0", - "personas_edit_dialog_title": "crwdns1825:0crwdne1825:0", - "personas_edit": "crwdns1827:0crwdne1827:0", - "personas_delete": "crwdns1829:0crwdne1829:0", - "personas_logout": "crwdns8179:0crwdne8179:0", - "personas_logout_confirm_password_tip": "crwdns8181:0crwdne8181:0", - "personas_add_persona": "crwdns1831:0crwdne1831:0", - "personas_back_up": "crwdns1833:0crwdne1833:0", - "personas_connect_to": "crwdns1835:0{{internalName}}crwdne1835:0", - "personas_disconnect": "crwdns1837:0crwdne1837:0", - "personas_disconnect_raw": "crwdns14714:0crwdne14714:0", - "personas_disconnect_warning": "crwdns7997:0crwdne7997:0", - "personas_logout_warning": "crwdns8183:0crwdne8183:0", - "personas_logout_manage_wallet_warning": "crwdns19451:0{{persona}}crwdnd19451:0{{addresses}}crwdne19451:0", - "personas_add": "crwdns7957:0crwdne7957:0", - "personas_upload_avatar": "crwdns7959:0crwdne7959:0", - "personas_rename": "crwdns1839:0crwdne1839:0", - "personas_invite_post": "crwdns7903:0{{identifier}}crwdne7903:0", - "personas_empty_contact_tips": "crwdns7905:0{{name}}crwdne7905:0", - "personas_contacts_name": "crwdns7907:0crwdne7907:0", - "personas_contacts_operation": "crwdns7909:0crwdne7909:0", - "personas_contacts_invite": "crwdns7911:0crwdne7911:0", - "personas_post_is_empty": "crwdns7979:0crwdne7979:0", - "personas_post_create": "crwdns7981:0crwdne7981:0", - "print_tips": "crwdns20471:0crwdne20471:0", - "settings_general": "crwdns1841:0crwdne1841:0", - "settings_backup_recovery": "crwdns7631:0crwdne7631:0", - "settings_local_backup": "crwdns7865:0crwdne7865:0", - "settings_cloud_backup": "crwdns7867:0crwdne7867:0", - "settings_appearance_default": "crwdns7633:0crwdne7633:0", - "settings_appearance_light": "crwdns7635:0crwdne7635:0", - "settings_appearance_dark": "crwdns7637:0crwdne7637:0", - "settings_backup_preview_account": "crwdns7639:0crwdne7639:0", - "settings_backup_preview_personas": "crwdns7641:0crwdne7641:0", - "settings_backup_preview_associated_accounts": "crwdns21655:0crwdne21655:0", - "settings_backup_preview_posts": "crwdns7645:0crwdne7645:0", - "settings_backup_preview_contacts": "crwdns7647:0crwdne7647:0", - "settings_backup_preview_file": "crwdns17256:0crwdne17256:0", - "settings_backup_preview_wallets": "crwdns7651:0crwdne7651:0", - "settings_backup_preview_set_payment_password": "crwdns22161:0crwdne22161:0", - "settings_backup_preview_created_at": "crwdns7653:0crwdne7653:0", - "settings_language_title": "crwdns1847:0crwdne1847:0", - "settings_language_desc": "crwdns1849:0crwdne1849:0", - "settings_language_auto": "crwdns3967:0crwdne3967:0", - "settings_appearance_title": "crwdns1851:0crwdne1851:0", - "settings_appearance_desc": "crwdns1853:0crwdne1853:0", - "settings_data_source_title": "crwdns1855:0crwdne1855:0", - "settings_data_source_desc": "crwdns1857:0crwdne1857:0", - "settings_sync_with_mobile_title": "crwdns1859:0crwdne1859:0", - "settings_sync_with_mobile_desc": "crwdns1861:0crwdne1861:0", - "settings_global_backup_title": "crwdns1863:0crwdne1863:0", - "settings_global_backup_desc": "crwdns1865:0crwdne1865:0", - "settings_global_backup_last": "crwdns7869:0{{backupAt}}crwdnd7869:0{{backupMethod}}crwdne7869:0", - "settings_restore_database_title": "crwdns1867:0crwdne1867:0", - "settings_restore_database_desc": "crwdns1869:0crwdne1869:0", - "settings_email_title": "crwdns7967:0crwdne7967:0", - "settings_email_desc": "crwdns7969:0crwdne7969:0", - "settings_change_password_title": "crwdns7971:0crwdne7971:0", - "settings_change_password_desc": "crwdns7973:0crwdne7973:0", - "settings_change_password_not_set": "crwdns7975:0crwdne7975:0", - "settings_phone_number_title": "crwdns7659:0crwdne7659:0", - "settings_phone_number_desc": "crwdns7661:0crwdne7661:0", - "settings_password_rule": "crwdns7665:0crwdne7665:0", - "settings_button_cancel": "crwdns7977:0crwdne7977:0", - "settings_button_confirm": "crwdns7669:0crwdne7669:0", - "settings_button_sync": "crwdns7671:0crwdne7671:0", - "settings_button_backup": "crwdns7673:0crwdne7673:0", - "settings_button_recovery": "crwdns7675:0crwdne7675:0", - "settings_button_setup": "crwdns9495:0crwdne9495:0", - "settings_button_change": "crwdns7679:0crwdne7679:0", - "settings_dialogs_bind_email_or_phone": "crwdns7871:0crwdne7871:0", - "settings_dialogs_verify_backup_password": "crwdns7683:0crwdne7683:0", - "settings_dialogs_setting_backup_password": "crwdns7685:0crwdne7685:0", - "settings_dialogs_change_backup_password": "crwdns7687:0crwdne7687:0", - "settings_dialogs_setting_email": "crwdns7689:0crwdne7689:0", - "settings_dialogs_change_email": "crwdns7691:0crwdne7691:0", - "settings_dialogs_setting_phone_number": "crwdns7693:0crwdne7693:0", - "settings_dialogs_change_phone_number": "crwdns7695:0crwdne7695:0", - "settings_dialogs_incorrect_code": "crwdns7697:0crwdne7697:0", - "settings_dialogs_incorrect_email": "crwdns7699:0crwdne7699:0", - "settings_dialogs_incorrect_phone": "crwdns7701:0crwdne7701:0", - "settings_dialogs_incorrect_password": "crwdns7703:0crwdne7703:0", - "settings_dialogs_inconsistency_password": "crwdns7705:0crwdne7705:0", - "settings_dialogs_current_email_validation": "crwdns7707:0crwdne7707:0", - "settings_dialogs_change_email_validation": "crwdns8207:0crwdne8207:0", - "settings_dialogs_current_phone_validation": "crwdns7709:0crwdne7709:0", - "settings_dialogs_change_phone_validation": "crwdns8209:0crwdne8209:0", - "settings_dialogs_backup_to_cloud": "crwdns9027:0crwdne9027:0", - "settings_dialogs_merge_to_local_data": "crwdns7875:0crwdne7875:0", - "settings_dialogs_backup_action_desc": "crwdns7879:0crwdne7879:0", - "settings_dialogs_backup_to_cloud_action": "crwdns10601:0crwdne10601:0", - "settings_dialogs_backup_merge_cloud": "crwdns10603:0crwdne10603:0", - "settings_dialogs_backup_merged_tip": "crwdns9029:0crwdne9029:0", - "settings_label_backup_password": "crwdns7711:0crwdne7711:0", - "settings_label_new_backup_password": "crwdns7713:0crwdne7713:0", - "settings_label_backup_password_cloud": "crwdns9025:0crwdne9025:0", - "settings_label_payment_password": "crwdns7881:0crwdne7881:0", - "settings_label_re_enter": "crwdns7715:0crwdne7715:0", - "settings_alert_password_set": "crwdns7883:0crwdne7883:0", - "settings_alert_password_updated": "crwdns7885:0crwdne7885:0", - "settings_alert_email_set": "crwdns7887:0crwdne7887:0", - "settings_alert_email_updated": "crwdns7889:0crwdne7889:0", - "settings_alert_phone_number_set": "crwdns7891:0crwdne7891:0", - "settings_alert_phone_number_updated": "crwdns7893:0crwdne7893:0", - "settings_alert_backup_fail": "crwdns7895:0crwdne7895:0", - "settings_alert_backup_success": "crwdns7897:0crwdne7897:0", - "settings_alert_validation_code_sent": "crwdns7899:0crwdne7899:0", - "settings_alert_merge_success": "crwdns7901:0crwdne7901:0", - "labs_file_service": "crwdns1885:0crwdne1885:0", - "labs_file_service_desc": "crwdns1887:0crwdne1887:0", - "labs_red_packet": "crwdns1893:0crwdne1893:0", - "labs_red_packet_desc": "crwdns1895:0crwdne1895:0", - "labs_swap": "crwdns1897:0crwdne1897:0", - "labs_swap_desc": "crwdns1899:0crwdne1899:0", - "labs_transak": "crwdns1901:0crwdne1901:0", - "labs_transak_desc": "crwdns1903:0crwdne1903:0", - "labs_savings": "crwdns13246:0crwdne13246:0", - "labs_savings_desc": "crwdns13248:0crwdne13248:0", - "labs_snapshot": "crwdns1905:0crwdne1905:0", - "labs_snapshot_desc": "crwdns1907:0crwdne1907:0", - "labs_market_trend": "crwdns1909:0crwdne1909:0", - "labs_market_trend_desc": "crwdns1911:0crwdne1911:0", - "labs_collectibles": "crwdns1913:0crwdne1913:0", - "labs_collectibles_desc": "crwdns1915:0crwdne1915:0", - "labs_gitcoin": "crwdns1917:0crwdne1917:0", - "labs_gitcoin_desc": "crwdns1919:0crwdne1919:0", - "labs_valuables": "crwdns1921:0crwdne1921:0", - "labs_valuables_desc": "crwdns1923:0crwdne1923:0", - "labs_mask_box": "crwdns10323:0crwdne10323:0", - "labs_mask_box_desc": "crwdns10325:0crwdne10325:0", - "labs_loot_man": "crwdns10327:0crwdne10327:0", - "labs_loot_man_desc": "crwdns10329:0crwdne10329:0", - "labs_settings_market_trend": "crwdns1929:0crwdne1929:0", - "labs_settings_market_trend_source": "crwdns1931:0crwdne1931:0", - "labs_settings_swap": "crwdns1933:0crwdne1933:0", - "labs_settings_swap_network": "crwdns8195:0{{network}}crwdne8195:0", - "labs_pets": "crwdns9397:0crwdne9397:0", - "labs_pets_desc": "crwdns9399:0crwdne9399:0", - "labs_cyber_connect": "crwdns13316:0crwdne13316:0", - "labs_cyber_connect_desc": "crwdns13318:0crwdne13318:0", - "labs_setup_tutorial": "crwdns10339:0crwdne10339:0", - "labs_do_not_show_again": "crwdns10341:0crwdne10341:0", - "labs_art_blocks": "crwdns14476:0crwdne14476:0", - "labs_art_blocks_desc": "crwdns14478:0crwdne14478:0", - "dashboard_mobile_test": "crwdns1941:0crwdne1941:0", - "dashboard_source_code": "crwdns1943:0crwdne1943:0", - "privacy_policy": "crwdns1945:0crwdne1945:0", - "version_of_stable": "crwdns1947:0{{version}}crwdne1947:0", - "version_of_unstable": "crwdns1949:0{{version}}crwdnd1949:0{{build}}crwdnd1949:0{{hash}}crwdne1949:0", - "register_restore_backups": "crwdns1951:0crwdne1951:0", - "register_restore_backups_cancel": "crwdns1953:0crwdne1953:0", - "register_restore_backups_confirm": "crwdns1955:0crwdne1955:0", - "register_restore_backups_hint": "crwdns1957:0crwdne1957:0", - "register_restore_backups_file": "crwdns1959:0crwdne1959:0", - "register_restore_backups_text": "crwdns1961:0crwdne1961:0", - "register_restore_backups_tabs": "crwdns1963:0crwdne1963:0", - "create_wallet_mnemonic_tip": "crwdns7725:0crwdne7725:0", - "create_wallet_mnemonic_verification_fail": "crwdns21505:0crwdne21505:0", - "create_wallet_onboarding_got_it": "crwdns21507:0crwdne21507:0", - "create_wallet_onboarding_creating_identity": "crwdns21509:0crwdne21509:0", - "create_wallet_onboarding_ready": "crwdns21511:0crwdne21511:0", - "create_wallet_onboarding_generating_accounts": "crwdns21513:0crwdne21513:0", - "create_wallet_onboarding_encrypting_data": "crwdns21515:0crwdne21515:0", - "create_wallet_mnemonic_keep_safe": "crwdns21517:0crwdne21517:0", - "create_wallet_password_uppercase_tip": "crwdns7727:0crwdne7727:0", - "create_wallet_password_lowercase_tip": "crwdns7729:0crwdne7729:0", - "create_wallet_password_number_tip": "crwdns7731:0crwdne7731:0", - "create_wallet_password_special_tip": "crwdns7733:0crwdne7733:0", - "create_wallet_password_satisfied_requirement": "crwdns9351:0crwdne9351:0", - "create_wallet_password_match_tip": "crwdns7735:0crwdne7735:0", - "create_wallet_password_length_error": "crwdns9353:0crwdne9353:0", - "create_wallet_name_placeholder": "crwdns7737:0crwdne7737:0", - "create_wallet_form_title": "crwdns7739:0crwdne7739:0", - "set_payment_password": "crwdns21519:0crwdne21519:0", - "write_down_recovery_phrase": "crwdns21521:0crwdne21521:0", - "store_recovery_phrase_tip": "crwdns21523:0crwdne21523:0", - "create_wallet_payment_password_place_holder": "crwdns21525:0crwdne21525:0", - "create_wallet_re_enter_payment_password": "crwdns7745:0crwdne7745:0", - "create_wallet_payment_password_tip_1": "crwdns21527:0crwdne21527:0", - "create_wallet_payment_password_tip_2": "crwdns21529:0crwdne21529:0", - "create_wallet_your_wallet_address": "crwdns7749:0crwdne7749:0", - "create_wallet_key_store_not_support": "crwdns21533:0crwdne21533:0", - "create_wallet_key_store_password": "crwdns21535:0crwdne21535:0", - "create_wallet_key_store_incorrect_password": "crwdns21537:0crwdne21537:0", - "create_wallet_done": "crwdns7751:0crwdne7751:0", - "create_wallet_verify_words": "crwdns7753:0crwdne7753:0", - "create_wallet_mnemonic_word_not_match": "crwdns8029:0crwdne8029:0", - "recovery_smart_pay_wallet_title": "crwdns19435:0crwdne19435:0", - "recovery_smart_pay_wallet_description_one": "crwdns19437:0{{count}}crwdne19437:0", - "recovery_smart_pay_wallet_description_other": "crwdns19439:0{{count}}crwdne19439:0", - "welcome_request_to_collect": "crwdns19511:0crwdne19511:0", - "welcome_to_use_mask_network": "crwdns20473:0crwdne20473:0", - "create_step": "crwdns21539:0{{step}}crwdnd21539:0{{totalSteps}}crwdne21539:0", - "persona_create_title": "crwdns20477:0crwdne20477:0", - "persona_create_tips": "crwdns20479:0crwdne20479:0", - "persona_setup_persona_example": "crwdns22217:0crwdne22217:0", - "data_recovery_title": "crwdns20563:0crwdne20563:0", - "data_recovery_description": "crwdns20565:0crwdne20565:0", - "data_recovery_email": "crwdns20567:0crwdne20567:0", - "data_recovery_email_code": "crwdns20569:0crwdne20569:0", - "data_recovery_mobile": "crwdns20571:0crwdne20571:0", - "data_recovery_mobile_code": "crwdns20573:0crwdne20573:0", - "data_recovery_invalid_mobile": "crwdns21657:0crwdne21657:0", - "data_recovery_set_name": "crwdns21789:0crwdne21789:0", - "data_recovery_name_tip": "crwdns21791:0crwdne21791:0", - "data_backup_no_backups_found": "crwdns22363:0crwdne22363:0", - "data_backup_title": "crwdns22365:0crwdne22365:0", - "data_backup_description": "crwdns22367:0crwdne22367:0", - "cloud_backup_title": "crwdns22369:0crwdne22369:0", - "cloud_backup_backup_exists": "crwdns22453:0{{account}}crwdne22453:0", - "cloud_backup_no_exist_tips": "crwdns22375:0crwdne22375:0", - "cloud_backup_email_title": "crwdns22377:0crwdne22377:0", - "cloud_backup_phone_title": "crwdns22379:0crwdne22379:0", - "cloud_backup_incorrect_email_address": "crwdns22381:0crwdne22381:0", - "cloud_backup_incorrect_verified_code": "crwdns22383:0crwdne22383:0", - "cloud_backup_email_verification_code": "crwdns22385:0crwdne22385:0", - "cloud_backup_phone_verification_code": "crwdns22455:0crwdne22455:0", - "cloud_backup_preview_title": "crwdns22387:0crwdne22387:0", - "cloud_backup_preview_description": "crwdns22389:0crwdne22389:0", - "cloud_backup_preview_switch_other_account": "crwdns22391:0crwdne22391:0", - "cloud_backup_merge_local_data": "crwdns22393:0crwdne22393:0", - "cloud_backup_overwrite_backup": "crwdns22395:0crwdne22395:0", - "cloud_backup_overwrite_current_backup": "crwdns22397:0crwdne22397:0", - "cloud_backup_overwrite_current_backup_tips": "crwdns22399:0crwdne22399:0", - "cloud_backup_upload_backup": "crwdns22401:0crwdne22401:0", - "cloud_backup_upload_to_cloud": "crwdns22403:0crwdne22403:0", - "cloud_backup_overwrite_tips": "crwdns22405:0crwdne22405:0", - "cloud_backup_successfully_tips": "crwdns22407:0crwdne22407:0", - "cloud_backup_merge_to_local_database": "crwdns22409:0crwdne22409:0", - "cloud_backup_download_link_expired": "crwdns22411:0crwdne22411:0", - "cloud_backup_enter_backup_password_to_decrypt_file": "crwdns22413:0crwdne22413:0", - "cloud_backup_incorrect_backup_password": "crwdns22415:0crwdne22415:0", - "cloud_backup_merge_to_local": "crwdns22417:0crwdne22417:0", - "cloud_backup_merge_to_local_congratulation_tips": "crwdns22419:0crwdne22419:0", - "cloud_backup_download_backup": "crwdns22421:0crwdne22421:0", - "cloud_backup_merge_to_local_successfully": "crwdns22423:0crwdne22423:0", - "cloud_backup_merge_to_local_failed": "crwdns22425:0crwdne22425:0", - "cloud_backup_backup_to_mask_cloud_service": "crwdns22427:0crwdne22427:0", - "file_unpacking": "crwdns20575:0crwdne20575:0", - "file_unpacking_completed": "crwdns20577:0crwdne20577:0", - "data_decrypting": "crwdns20579:0crwdne20579:0", - "data_downloading": "crwdns20581:0crwdne20581:0", - "switch_other_accounts": "crwdns20583:0crwdne20583:0", - "file_reselect": "crwdns20585:0crwdne20585:0", - "mobile_number": "crwdns20587:0crwdne20587:0", - "persona_phrase_title": "crwdns20481:0crwdne20481:0", - "persona_phrase_tips": "crwdns20483:0crwdne20483:0", - "persona_phrase_copy_description": "crwdns20485:0crwdne20485:0", - "persona_phrase_create_tips": "crwdns20487:0crwdne20487:0", - "persona_phrase_create_check_tips": "crwdns20489:0crwdne20489:0", - "persona_onboarding_to_twitter": "crwdns20491:0crwdne20491:0", - "persona_onboarding_set_payment_password": "crwdns21673:0crwdne21673:0", - "persona_onboarding_pin_tips": "crwdns20493:0crwdne20493:0", - "persona_onboarding_creating_identity": "crwdns20497:0crwdne20497:0", - "persona_onboarding_generating_accounts": "crwdns20499:0crwdne20499:0", - "persona_onboarding_encrypting_data": "crwdns20501:0crwdne20501:0", - "persona_onboarding_ready": "crwdns20503:0crwdne20503:0", - "persona_onboarding_recovery_wallets": "crwdns21675:0crwdne21675:0", - "persona_onboarding_wallets_one": "crwdns21677:0{{count}}crwdne21677:0", - "persona_onboarding_wallets_other": "crwdns21679:0{{count}}crwdne21679:0", - "wallet_history_no_data": "crwdns19885:0crwdne19885:0", - "welcome_new_agreement_policy": "crwdns20685:0crwdne20685:0", - "wallets_history_burn": "crwdns20005:0crwdne20005:0", - "wallet_connect_tips": "crwdns20371:0crwdne20371:0", - "identity_words": "crwdns21661:0crwdne21661:0", - "private_key": "crwdns21663:0crwdne21663:0", - "local_backup": "crwdns21665:0crwdne21665:0", - "incorrect_verification_code": "crwdns21667:0crwdne21667:0", - "wallet_set_payment_password_successfully": "crwdns22115:0crwdne22115:0", - "wallet_open_mask_wallet": "crwdns22117:0crwdne22117:0" -} diff --git a/packages/mask/dashboard/locales/zh-CN.json b/packages/mask/dashboard/locales/zh-CN.json deleted file mode 100644 index e5ba97eea057..000000000000 --- a/packages/mask/dashboard/locales/zh-CN.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "address": "地址", - "about": "关于", - "backup": "备份", - "continue": "继续", - "create": "创建", - "congratulations": "恭喜您!", - "email": "电子邮箱", - "wallet": "钱包", - "wallets": "钱包", - "personas": "身份", - "previous": "上一页", - "persona": "身份", - "persona_name": "身份名称", - "public_key": "公钥", - "refresh": "刷新", - "next": "下一步", - "previous_page": "上一页", - "next_page": "下一页", - "cancel": "取消", - "back": "返回", - "agree": "同意", - "confirm": "确认", - "verify": "验证", - "go_back": "返回", - "connect": "连接", - "searching": "搜索中", - "restore": "恢复", - "save": "保存", - "manage": "管理", - "recovery": "恢复", - "sign_up": "注册", - "skip": "跳过", - "search_area": "探索范围", - "successful": "成功", - "close": "关闭", - "send": "发送", - "resend": "重新发送", - "print": "打印", - "download": "下载", - "identity": "身份", - "accounts": "账户", - "uploading": "正在上传", - "data": "数据", - "ready": "准备🚀", - "print_preview": "打印预览", - "incorrect_password": "密码不正确", - "download_preview": "下载预览", - "download_backup": "下载备份", - "confirm_password": "密码确认", - "about_dialog_license": "开源协议: ", - "footer_bounty_list": "赏金列表", - "about_dialog_source_code": "源代码: ", - "about_dialog_feedback": "反馈: ", - "about_dialog_touch": "联系我们", - "about_dialog_description": "Mask Network 引领您探索更新更开放的互联网。Mask Network 允许您在社交网络上发送加密的贴文。 同时我们提供了更多功能,譬如发送加密红包,购买加密货币,加密文件服务等。", - "setup_page_title": "欢迎来到 Mask Network!", - "setup_page_description": "在社交网络上加密您的贴文和聊天内容,只允许您的朋友进行解密。", - "setup_page_create_account_title": "创建身份", - "setup_page_create_account_subtitle": "构建您的数字身份系统,探索 Web3", - "setup_page_create_account_button": "创建", - "setup_page_create_restore_title": "恢复身份", - "setup_page_create_restore_subtitle": "通过身份助记词或历史备份恢复。", - "setup_page_create_restore_button": "恢复或登录", - "create_account_mask_id": "MASK ID", - "create_account_private_key": "私钥", - "create_account_identity_id": "身份助记词", - "create_account_identity_title": "创建Mask Network身份", - "create_account_sign_in_button": "恢复或登录", - "create_account_persona_exists": "该身份已存在。", - "create_account_mnemonic_download_or_print": "我已妥善保存我的身份信息。", - "create_account_preview_tip": "这个二维码保存您的身份信息,请妥善保存。您可以使用 Mask APP 扫描二维码登录您的身份。", - "create_account_mnemonic_confirm_failed": "助记词不正确", - "create_account_connect_social_media_button": "创建", - "create_account_connect_social_media": "连接到 {{type}}", - "create_account_persona_title": "欢迎来到 Mask Network!", - "create_account_persona_subtitle": "您可以创建个人身份并连接社交账户", - "create_account_persona_successfully": "创建身份成功", - "create_account_connect_social_media_title": "连接到社交媒体", - "create_account_failed": "创建帐户失败", - "follow_us": "关注我们", - "sign_in_account_identity_title": "恢复或登录", - "sign_in_account_tab_identity": "身份", - "sign_in_account_sign_up_button": "注册", - "sign_in_account_identity_warning": "数字身份助记词只能恢复您的数字身份。它可以加密并解密由这个数字身份签名和发送的社交网络信息内容。", - "sign_in_account_private_key_placeholder": "输入您的私钥", - "sign_in_account_private_key_error": "私钥不正确", - "sign_in_account_private_key_persona_not_found": "无法找到身份", - "sign_in_account_private_key_warning": "数字身份助记词只能恢复您的数字身份。它可以加密并解密由这个数字身份签名和发送的社交网络信息内容。", - "sign_in_account_mnemonic_confirm_failed": "助记词不正确", - "sign_in_account_cloud_backup_send_email_success": "验证码已发送到您的 {{type}}。请检查您的 {{type}}。", - "sign_in_account_local_backup_warning": "本地备份可以恢复本地存储的所有数据。", - "sign_in_account_local_backup_payment_password": "支付密码", - "sign_in_account_local_backup_file_drag": "请点击选择或拖动文件到这里", - "sign_in_account_cloud_backup_warning": "云端备份会保存并加密您的数据。", - "sign_in_account_cloud_backup_not_support": "不支持的数据备份格式", - "sign_in_account_cloud_send_verification_code_tip": "发送验证码至", - "sign_in_account_cloud_backup_failed": "恢复备份失败,请再试一次。", - "sign_in_account_cloud_backup_email_or_phone_number": "电子邮箱或电话号码", - "sign_in_account_cloud_backup_password": "备份密码", - "sign_in_account_cloud_restore_failed": "恢复失败", - "sign_in_account_cloud_backup_download_failed": "备份文件下载失败", - "sign_in_account_cloud_backup_decrypt_failed": "备份解密失败,请检查密码", - "incorrect_backup_password": "备份密码错误。", - "incorrect_identity_mnemonic": "助记词不正确。", - "sign_in_account_cloud_backup_email_format_error": "邮箱地址不正确。", - "sign_in_account_cloud_backup_phone_format_error": "此电话号码不正确。", - "sign_in_account_cloud_backup_synchronize_password_tip": "已成功验证您的云端备份密码,备份正在上传。 为了备份密码的一致,请确认您是否愿意将您的云端备份密码设置为本地备份密码。", - "cloud_backup": "云端备份", - "wallets_transfer": "转账", - "wallets_assets": "资产", - "wallets_transfer_memo": "备注", - "wallets_transfer_amount": "金额", - "wallets_transfer_to_address": "发送到地址", - "wallets_print_tips": "这个二维码保存了你的助记词,请安全保存它。", - "wallets_mnemonic_word": "助记词", - "wallets_transfer_error_amount_absence": "输入数额", - "wallets_transfer_error_address_absence": "输入收款人地址", - "wallets_transfer_error_contract": "选择 NFT 合约", - "wallets_transfer_error_nft": "选择一个 NFT", - "wallets_transfer_error_invalid_address": "收款人地址无效", - "wallet_transfer_error_no_address_has_been_set_name": "接收人地址无效。", - "wallet_transfer_error_no_ens_support": "网络不支持 ENS。", - "wallets_transfer_error_insufficient_balance": "{{symbol}} 余额不足", - "wallets_transfer_error_same_address_with_current_account": "此接收地址与发送地址相同,请重新检查。", - "wallets_transfer_error_is_contract_address": "此接收地址为合约地址,请重新检查。", - "wallets_transfer_send": "发送", - "wallets_transfer_memo_placeholder": "可选填信息", - "wallets_transfer_contract": "合约", - "wallets_transfer_contract_placeholder": "选择 NFT 合约", - "wallets_swap": "兑换", - "wallets_red_packet": "红包", - "wallets_sell": "卖出", - "wallets_history": "历史记录", - "settings": "设置", - "gas_fee": "交易手续费", - "transfer_cost": "花费 {{gasFee}} {{symbol}} ≈ ${{usd}}", - "done": "完成", - "labs": "D.Market", - "onboarding_wallet": "钱包", - "wallet_transactions_pending": "待定中", - "wallet_recovery_title": "恢复钱包", - "wallet_select_address": "选择地址", - "wallet_derivation_path": "Ethereum {{- path }}", - "wallet_recovery_mnemonic_confirm_failed": "助记词错误。", - "wallet_recovery_description": "请输入正确的助记词词句,私钥,或上传正确的 keystore 文件。", - "wallet_gas_fee_settings_low": "低", - "wallet_gas_fee_settings_medium": "中", - "wallet_gas_fee_settings_high": "高", - "wallets_startup_create": "创建新钱包", - "wallets_startup_create_desc": "Mask Network支持ETH、BSC 和 Polygon/Matic 等网络。", - "wallets_startup_create_action": "创建", - "wallets_startup_import": "导入钱包", - "wallets_startup_import_desc": "Mask钱包支持私钥、JSON文件和助记词导入。", - "wallets_startup_import_action": "导入", - "wallets_startup_connect": "连接钱包", - "wallets_startup_connect_desc": "支持Mask钱包、MetaMask和WalletConnect。", - "wallets_startup_connect_action": "连接钱包", - "wallets_connect_wallet_metamask": "MetaMask", - "wallets_connect_wallet_connect": "连接钱包", - "wallets_connect_wallet_polka": "Polkadot 钱包", - "wallets_create_wallet_input_placeholder": "钱包名称", - "wallets_create_successfully_title": "成功", - "wallets_create_successfully_tips": "您已成功创建钱包。", - "wallets_create_successfully_unlock": "解锁钱包", - "wallets_create_wallet_alert": "Mask Network是一个免费的开源客户端接口。 Mask Network允许您直接与 区块链进行交互,同时您可以完全控制您的密钥和资金,请仔细考虑这一点。 您是掌控者,Mask Network不是银行或交易所。 我们不保留您的密钥、资金或信息。 这意味着我们无法访问账户、恢复密钥、重置密码或反向交易。", - "wallets_wallet_connect_title": "使用WalletConnect兼容的钱包扫描二维码", - "wallets_wallet_mnemonic": "助记词", - "wallets_wallet_json_file": "本地备份", - "wallets_wallet_private_key": "私钥", - "wallets_import_wallet_tabs": "导入钱包标签", - "wallets_import_wallet_password_placeholder": "钱包密码", - "wallets_import_wallet_cancel": "取消", - "wallets_import_wallet_import": "导入", - "wallets_create_wallet_tabs": "创建钱包标签", - "wallets_create_wallet_refresh": "刷新", - "wallets_create_wallet_remember_later": "请稍后再记住它", - "wallets_create_wallet_verification": "验证", - "wallets_collectible_address": "NFT 地址", - "wallets_collectible_token_id": "代币 ID", - "wallets_collectible_field_contract_require": "NFT 地址为必填项", - "wallets_collectible_field_token_id_require": "代币 ID 为必填项", - "wallets_collectible_load_end": "已加载完毕", - "wallets_balance": "资产在", - "wallets_balance_all_chain": "总览", - "wallets_balance_Send": "发送", - "wallets_balance_Buy": "购买", - "wallets_balance_Swap": "兑换", - "wallets_balance_Receive": "接收", - "wallets_assets_token": "代币", - "wallets_assets_investment": "投资", - "wallets_assets_collectibles": "NFT", - "wallets_assets_custom_token": "自定义代币", - "wallets_assets_custom_collectible": "自定义 NFT", - "wallets_assets_asset": "资产", - "wallets_assets_balance": "余额", - "wallets_assets_price": "价格", - "wallets_assets_value": "价值", - "wallets_assets_operation": "操作", - "wallets_assets_more$collapsed": "值较小的 Token 不会显示({{- symbol }} $1) 。显示全部", - "wallets_assets_more$expanded": "值较小的 Token 会显示({{- symbol }} $1) 。隐藏全部", - "wallets_assets_more_show_all": "全部显示", - "wallets_address": "钱包地址", - "wallets_receive_tips": "扫描二维码并发送{{chainName}} 资产到此钱包", - "wallets_add_collectible": "添加 NFT", - "wallets_incorrect_address": "合约地址不正确", - "wallets_collectible_been_added": "此 NFT 已被添加。", - "wallets_collectible_error_not_exist": "此收 NFT 不存在或不属于您。", - "wallets_collectible_contract_is_empty": "请选择合约", - "wallets_collectible_token_id_is_empty": "请选择代币", - "wallets_collectible_add": "添加", - "wallets_add_token": "添加代币", - "wallets_token_been_added": "代币已被添加。", - "wallets_token_symbol_tips": "代币代号必须少于11或更少的字符。", - "wallets_token_decimals_tips": "十进制必须至少 0,且不得超过 18。", - "wallets_add_token_contract_address": "代币合约地址", - "wallets_add_token_symbol": "代币代号", - "wallets_add_token_decimals": "小数点精度", - "wallets_add_token_cancel": "取消", - "wallets_add_token_next": "下一步", - "wallets_empty_tokens_tip": "没有找到任何资产。请添加代币。", - "wallets_empty_collectible_tip": "没有找到任何收藏品。请添加收藏品。", - "wallets_reload": "重新加载", - "wallets_address_copied": "已成功复制地址", - "public_key_copied": "公钥已成功复制", - "wallets_address_copy": "复制", - "wallets_history_types": "类型", - "wallets_history_value": "价值", - "wallets_history_time": "时间", - "wallets_history_receiver": "交互者", - "wallets_empty_history_tips": "没有任何交易历史", - "wallets_loading_token": "正在加载代币", - "personas_setup_connect_tips": "请连接到您的 {{type}} 账户。", - "personas_setup_tip": "请创建或恢复身份。", - "personas_setup_connect": "连接", - "personas_name_maximum_tips": "最大长度为 {{length}} 个字符。", - "personas_name_existed": "此身份名称已存在", - "personas_rename_placeholder": "身份名称", - "personas_confirm": "确认", - "personas_cancel": "取消", - "personas_export_persona": "导出身份", - "personas_export_persona_copy": "复制", - "personas_export_persona_copy_success": "已复制", - "personas_export_persona_copy_failed": "复制失败", - "personas_export_persona_confirm_password_tip": "您还没有设置您的备份密码。想要导出您的身份私钥,必须先设置备份密码。", - "personas_export_private": "导出身份私钥", - "personas_export_private_key_tip": "此操作仅用于导出私钥。我们不导出任何其他数据。如果您需要导出更多数据,请前往设置页面:", - "personas_delete_confirm_tips": "请输入您的备份密码来确认删除身份 {{nickname}} 。", - "personas_delete_dialog_title": "删除身份", - "personas_edit_dialog_title": "编辑身份", - "personas_edit": "编辑", - "personas_delete": "删除", - "personas_logout": "登出", - "personas_logout_confirm_password_tip": "您还没有设置您的密码。要登出身份,您必须先设置备份密码。", - "personas_add_persona": "添加新身份", - "personas_back_up": "备份", - "personas_connect_to": "连接到 {{internalName}}", - "personas_disconnect": "断开连接", - "personas_disconnect_raw": "断开连接", - "personas_disconnect_warning": "你确定要删除此身份吗? 您的Mask Network朋友将不能再通过此身份向您发送加密的消息,或查看您与此身份相关的 Web3 产品。", - "personas_logout_warning": "身份登出后,您所关联的社交网络账户将不能解密过去的加密消息。 如果您需要重新使用您的身份,您可以使用您的身份私钥进行恢复。", - "personas_logout_manage_wallet_warning": "请注意: \n该 Persona {{persona}} 是您 SmartPay 钱包 {{addresses}} 的管理账户。 如果退出 Persona 账户, 您将无法使用 SmartPay 钱包进行任何交易。", - "personas_add": "添加", - "personas_upload_avatar": "上传头像", - "personas_rename": "重命名", - "personas_invite_post": "@{{identifier}} Hi,请下载Mask Network,以便我们可以用加密的方式分享帖子?#mask_io install http://mask.io", - "personas_empty_contact_tips": "您尚未有安装Mask Network的联系人。请邀请您的朋友下载 {{name}}", - "personas_contacts_name": "名称", - "personas_contacts_operation": "操作", - "personas_contacts_invite": "邀请", - "personas_post_is_empty": "您还没有创建任何加密贴文。", - "personas_post_create": "创建贴文", - "print_tips": "这个二维码保存您的身份码,请妥善保存。 ", - "settings_general": "通用", - "settings_backup_recovery": "备份恢复", - "settings_local_backup": "本地备份", - "settings_cloud_backup": "云端备份", - "settings_appearance_default": "按照系统设置", - "settings_appearance_light": "浅色", - "settings_appearance_dark": "深色", - "settings_backup_preview_account": "账户", - "settings_backup_preview_personas": "身份", - "settings_backup_preview_associated_accounts": "关联账户", - "settings_backup_preview_posts": "加密贴文", - "settings_backup_preview_contacts": "联系人", - "settings_backup_preview_file": "文件", - "settings_backup_preview_wallets": "Mask钱包", - "settings_backup_preview_set_payment_password": "您需要先设置密码才能启用钱包功能完成备份。 转到设置 ", - "settings_backup_preview_created_at": "备份时间", - "settings_language_title": "语言", - "settings_language_desc": "选择您要使用的语言", - "settings_language_auto": "按照系统设置", - "settings_appearance_title": "外观", - "settings_appearance_desc": "选择您要使用的外观主题", - "settings_data_source_title": "数据源", - "settings_data_source_desc": "从不同平台获取趋势数据", - "settings_sync_with_mobile_title": "与移动设备同步", - "settings_sync_with_mobile_desc": "您可以与您的移动设备同步您的帐户和信息。 打开Mask Network移动应用程序,进入设置并点击与插件同步。", - "settings_global_backup_title": "备份数据库", - "settings_global_backup_desc": "提供选择本地备份和云端备份", - "settings_global_backup_last": "最近的备份时间为{{backupAt}},备份方法为{{backupMethod}}。", - "settings_restore_database_title": "恢复数据库", - "settings_restore_database_desc": "从以前的数据库备份恢复", - "settings_email_title": "电子邮箱", - "settings_email_desc": "请绑定您的电子邮箱", - "settings_change_password_title": "备份密码", - "settings_change_password_desc": "修改备份密码", - "settings_change_password_not_set": "您尚未设置备份密码", - "settings_phone_number_title": "手机号码", - "settings_phone_number_desc": "请绑定您的手机号码", - "settings_password_rule": "备份密码必须为 8 到 20 个字符,并且至少包含一个数字、一个大写字母、一个小写字母和一个特殊字符。", - "settings_button_cancel": "取消", - "settings_button_confirm": "确认", - "settings_button_sync": "同步", - "settings_button_backup": "备份", - "settings_button_recovery": "恢复", - "settings_button_setup": "设置", - "settings_button_change": "修改", - "settings_dialogs_bind_email_or_phone": "请绑定您的电子邮箱或手机号码", - "settings_dialogs_verify_backup_password": "验证备份密码", - "settings_dialogs_setting_backup_password": "设置备份密码", - "settings_dialogs_change_backup_password": "修改备份密码", - "settings_dialogs_setting_email": "设置电子邮箱", - "settings_dialogs_change_email": "更改电子邮箱地址", - "settings_dialogs_setting_phone_number": "设置手机号码", - "settings_dialogs_change_phone_number": "修改手机号", - "settings_dialogs_incorrect_code": "验证码不正确.", - "settings_dialogs_incorrect_email": "邮箱地址不正确。", - "settings_dialogs_incorrect_phone": "此手机号码不正确。", - "settings_dialogs_incorrect_password": "密码不正确。", - "settings_dialogs_inconsistency_password": "密码不一致!", - "settings_dialogs_current_email_validation": "当前验证的邮箱地址为", - "settings_dialogs_change_email_validation": "想要更改其他邮箱地址,请验证您当前的邮箱地址。", - "settings_dialogs_current_phone_validation": "当前验证的手机号码为", - "settings_dialogs_change_phone_validation": "想要更改您的手机号码,您需要验证您当前的手机号码。", - "settings_dialogs_backup_to_cloud": "备份至云端", - "settings_dialogs_merge_to_local_data": "将云端备份合并到本地并再次备份到云端", - "settings_dialogs_backup_action_desc": "云端备份已存在, 您可以选择在备份之前先合并此云端备份到您的本地数据,或者直接覆盖重新备份。", - "settings_dialogs_backup_to_cloud_action": "此选项将使用本地数据覆盖现有云备份。", - "settings_dialogs_backup_merge_cloud": "此选项需要您输入现有云备份的密码进行解密。 解密后将会把现有云备份与本地数据合并,然后再次加密上传到云端。", - "settings_dialogs_backup_merged_tip": "您已经合并云端备份到本地. 如果您想要继续完成备份,请点击备份按钮将您的所有数据更新到云端。", - "settings_label_backup_password": "备份密码", - "settings_label_new_backup_password": "新备份密码", - "settings_label_backup_password_cloud": "云端文件的备份密码", - "settings_label_payment_password": "支付密码", - "settings_label_re_enter": "再次输入", - "settings_alert_password_set": "备份密码设置成功。", - "settings_alert_password_updated": "备份密码已更新", - "settings_alert_email_set": "电子邮箱已设置", - "settings_alert_email_updated": "邮箱地址已更新", - "settings_alert_phone_number_set": "手机号码已设置", - "settings_alert_phone_number_updated": "电话号码已更改", - "settings_alert_backup_fail": "备份失败", - "settings_alert_backup_success": "您已成功备份您的数据。", - "settings_alert_validation_code_sent": "验证码已发送", - "settings_alert_merge_success": "您已成功地将云端备份合并到本地数据。", - "labs_file_service": "文件服务", - "labs_file_service_desc": "为用户提供去中心化文档存储功能。", - "labs_red_packet": "红包", - "labs_red_packet_desc": "使用加密红包向您的朋友送上最好的祝福。", - "labs_swap": "兑换", - "labs_swap_desc": "通过Dex购买代币,无需额外费用和限制。", - "labs_transak": "法币入金", - "labs_transak_desc": "法币入金可支持在60多个国家内购买代币。", - "labs_savings": "储蓄", - "labs_savings_desc": "将您的加密资产部署到各种储蓄协议中,并观看您的储蓄增长。", - "labs_snapshot": "Snapshot", - "labs_snapshot_desc": "直接在社交媒体上展示和投票支持提案。", - "labs_market_trend": "市场趋势", - "labs_market_trend_desc": "在社交媒体上直接显示代币信息、价格趋势图表和兑换信息。", - "labs_collectibles": "NFT", - "labs_collectibles_desc": "直接在社交媒体上展示 NFT 在 OpenSea 和 Rarible 的特定信息,和提供发送报价及竞标功能。", - "labs_gitcoin": "Gitcoin", - "labs_gitcoin_desc": "在社交媒体上直接展示Gitcoin项目的具体信息。", - "labs_valuables": "Valuables", - "labs_valuables_desc": "购买和销售由其原创作者发布的推文。", - "labs_mask_box": "Mask盲盒", - "labs_mask_box_desc": "支持多链的去中心化 NFT 盲盒发布平台。", - "labs_loot_man": "LootMan by NonFFriend", - "labs_loot_man_desc": "探索 NFT 无尽的可能性。以革命性的方式在社交媒体上展示您的 NFT。", - "labs_settings_market_trend": "市场趋势设置", - "labs_settings_market_trend_source": "默认趋势来源", - "labs_settings_swap": "交易所设置", - "labs_settings_swap_network": "{{network}} 网络默认交易所", - "labs_pets": "Non-Fungible Friends by Mint Team", - "labs_pets_desc": "探索 NFT 无尽的可能性。", - "labs_cyber_connect": "CyberConnect", - "labs_cyber_connect_desc": "以用户为中心的去中心化的Web3社交图协议", - "labs_setup_tutorial": "教程", - "labs_do_not_show_again": "不再提醒", - "labs_art_blocks": "Artblocks", - "labs_art_blocks_desc": "Artblocks 依托算法生成内容技术,给用户提供随心铸造 NFT 的体验。", - "dashboard_mobile_test": "参加移动端测试", - "dashboard_source_code": "源代码", - "privacy_policy": "隐私政策", - "version_of_stable": "版本 {{version}}", - "version_of_unstable": "版本 {{version}}-{{build}}-{{hash}}", - "register_restore_backups": "恢复备份", - "register_restore_backups_cancel": "取消", - "register_restore_backups_confirm": "恢复", - "register_restore_backups_hint": "请点击选择或拖动文件到这里", - "register_restore_backups_file": "文件", - "register_restore_backups_text": "文本内容", - "register_restore_backups_tabs": "恢复备份", - "create_wallet_mnemonic_tip": "请不要忘记保存您的助记词。您需要这个才能访问您的钱包。", - "create_wallet_mnemonic_verification_fail": "单词选择错误。请重试!", - "create_wallet_onboarding_got_it": "好的", - "create_wallet_onboarding_creating_identity": "创建您的 ", - "create_wallet_onboarding_ready": "您的钱包已开启 ", - "create_wallet_onboarding_generating_accounts": "生成您的 ", - "create_wallet_onboarding_encrypting_data": "加密您的 ", - "create_wallet_mnemonic_keep_safe": "确认保存", - "create_wallet_password_uppercase_tip": "必须包含一个大写字符", - "create_wallet_password_lowercase_tip": "必须包含一个小写字符", - "create_wallet_password_number_tip": "必须包含一个数字", - "create_wallet_password_special_tip": "必须包含一个特殊字符", - "create_wallet_password_satisfied_requirement": "密码格式不符合要求。", - "create_wallet_password_match_tip": "密码不一致。", - "create_wallet_password_length_error": "密码长度不符合要求。", - "create_wallet_name_placeholder": "输入1-12 个字符", - "create_wallet_form_title": "创建一个钱包", - "set_payment_password": "设置支付密码", - "write_down_recovery_phrase": "写下助记词", - "store_recovery_phrase_tip": "请以正确的方式写下或复制这些字词,并将其存储在安全的地方。", - "create_wallet_payment_password_place_holder": "至少六个字符", - "create_wallet_re_enter_payment_password": "再次输入支付密码", - "create_wallet_payment_password_tip_1": "支付密码应该介于 6 到 20 个字符之间。", - "create_wallet_payment_password_tip_2": "您的钱包数据将使用此支付密码加密,这也是交易确认和钱包解锁所必需的。", - "create_wallet_your_wallet_address": "您的钱包地址", - "create_wallet_key_store_not_support": "不支持的密钥存储数据", - "create_wallet_key_store_password": "Keystore密码", - "create_wallet_key_store_incorrect_password": "Keystore 密码不正确。", - "create_wallet_done": "完成", - "create_wallet_verify_words": "验证助记词", - "create_wallet_mnemonic_word_not_match": "助记词不正确", - "recovery_smart_pay_wallet_title": "恢复 SmartPay 钱包", - "recovery_smart_pay_wallet_description_one": "本地检测到 {{count}} 个SmartPay 钱包并且恢复成功。", - "recovery_smart_pay_wallet_description_other": "本地检测到 {{count}} 个 SmartPay 钱包并且恢复成功。", - "welcome_request_to_collect": "允许我们收集您的使用信息以帮助我们改进。", - "welcome_to_use_mask_network": "欢迎来到 Mask Network!", - "create_step": "步骤 {{step}}/{{totalSteps}}", - "persona_create_title": "创建新的 MASK 身份", - "persona_create_tips": "创建您的身份以开始", - "persona_setup_persona_example": "示例:Alice", - "data_recovery_title": "恢复数据", - "data_recovery_description": "请选择合适的方法来恢复个人数据。", - "data_recovery_email": "电子邮箱", - "data_recovery_email_code": "邮箱验证码", - "data_recovery_mobile": "手机", - "data_recovery_mobile_code": "手机验证码", - "data_recovery_invalid_mobile": "无效的电话号码,请检查并重试。", - "data_recovery_set_name": "设置您的角色名称", - "data_recovery_name_tip": "设置您的身份名称,最大长度为24个字符", - "data_backup_no_backups_found": "没有发现备份", - "data_backup_title": "选择备份的内容", - "data_backup_description": "请选择合适的方法来恢复个人数据。", - "cloud_backup_title": "登录到您的 Mask 云", - "cloud_backup_backup_exists": "您上次备份使用了{{account}}。", - "cloud_backup_no_exist_tips": "请使用您经常使用的电子邮件帐户或手机进行备份。", - "cloud_backup_email_title": "电子邮件", - "cloud_backup_phone_title": "手机", - "cloud_backup_incorrect_email_address": "邮箱地址不正确。", - "cloud_backup_incorrect_verified_code": "验证码不正确", - "cloud_backup_email_verification_code": "邮箱验证码", - "cloud_backup_phone_verification_code": "验证码", - "cloud_backup_preview_title": "欢迎使用 Mask 云服务", - "cloud_backup_preview_description": "请选择合适的方法来恢复个人数据。", - "cloud_backup_preview_switch_other_account": "切换帐号", - "cloud_backup_merge_local_data": "合并数据到本地数据库", - "cloud_backup_overwrite_backup": "覆盖备份", - "cloud_backup_overwrite_current_backup": "覆盖当前备份", - "cloud_backup_overwrite_current_backup_tips": "确定要覆盖 Mask 云服务中存储的备份吗?", - "cloud_backup_upload_backup": "上传备份", - "cloud_backup_upload_to_cloud": "备份到云端", - "cloud_backup_overwrite_tips": "此选项将用本地数据覆盖现有的云备份,并且无法恢复。", - "cloud_backup_successfully_tips": "您已成功上传备份到 Mask 云服务。", - "cloud_backup_merge_to_local_database": "合并数据到本地数据库", - "cloud_backup_download_link_expired": "下载链接已过期", - "cloud_backup_enter_backup_password_to_decrypt_file": "请输入云备份密码以下载文件。", - "cloud_backup_incorrect_backup_password": "云备份密码不正确,请重试。", - "cloud_backup_merge_to_local": "合并至本地数据", - "cloud_backup_merge_to_local_congratulation_tips": "数据已成功从 Mask 云服务合并到本地。请重新输入密码以加密并上传备份到 Mask 云服务。", - "cloud_backup_download_backup": "下载备份", - "cloud_backup_merge_to_local_successfully": "备份下载并成功合并到本地。", - "cloud_backup_merge_to_local_failed": "备份下载后合并到本地失败。", - "cloud_backup_backup_to_mask_cloud_service": "备份到 Mask 云服务", - "file_unpacking": "正在解压", - "file_unpacking_completed": "已完成", - "data_decrypting": "正在解密", - "data_downloading": "正在下载", - "switch_other_accounts": "切换帐号", - "file_reselect": "重新选择", - "mobile_number": "手机号码", - "persona_phrase_title": "您的恢复口令", - "persona_phrase_tips": "请选择恢复备份的正确方法。", - "persona_phrase_copy_description": "助记符已被复制,请将其保存在一个安全地方。", - "persona_phrase_create_tips": "永远不要与任何人分享12个单词的恢复短语!", - "persona_phrase_create_check_tips": "我按顺序写下了所有单词", - "persona_onboarding_to_twitter": "Twitter体验", - "persona_onboarding_set_payment_password": "设置支付密码", - "persona_onboarding_pin_tips": "将 Mask Network 固定到工具栏以便更容易访问:", - "persona_onboarding_creating_identity": "创建您的 ", - "persona_onboarding_generating_accounts": "生成您的 ", - "persona_onboarding_encrypting_data": "加密您的 ", - "persona_onboarding_ready": "您的身份已开启 ", - "persona_onboarding_recovery_wallets": "您已经恢复 ", - "persona_onboarding_wallets_one": "{{count}} 个钱包 🚀", - "persona_onboarding_wallets_other": "{{count}} 个钱包 🚀", - "wallet_history_no_data": "没有历史记录。", - "welcome_new_agreement_policy": "我已阅读并同意 服务协议隐私政策,继而可以继续使用应用。", - "wallets_history_burn": "燃烧", - "wallet_connect_tips": "您已连接到一个钱包。请在那个钱包中切换网络,或切换到另一个钱包。", - "identity_words": "身份助记词", - "private_key": "私钥", - "local_backup": "本地备份", - "incorrect_verification_code": "验证码错误。", - "wallet_set_payment_password_successfully": "设置支付密码成功。", - "wallet_open_mask_wallet": "打开一个钱包" -} diff --git a/packages/mask/dashboard/locales/zh-TW.json b/packages/mask/dashboard/locales/zh-TW.json deleted file mode 100644 index 8c3a0f2bd409..000000000000 --- a/packages/mask/dashboard/locales/zh-TW.json +++ /dev/null @@ -1,346 +0,0 @@ -{ - "about": "關於", - "wallets": "錢包", - "personas": "角色", - "persona": "角色", - "refresh": "刷新", - "next": "繼續", - "cancel": "取消", - "back": "返回", - "agree": "同意", - "confirm": "確認", - "verify": "驗證", - "go_back": "返回", - "connect": "連接", - "searching": "搜尋中", - "restore": "恢復", - "save": "存儲", - "manage": "管理", - "recovery": "恢復", - "successful": "成功", - "close": "關閉", - "send": "發送", - "resend": "重新傳送", - "confirm_password": "確認密碼", - "about_dialog_license": "開源協議: ", - "footer_bounty_list": "賞金列表", - "about_dialog_source_code": "原始碼: ", - "about_dialog_feedback": "反饋 ", - "about_dialog_touch": "聯繫我們", - "about_dialog_description": "Mask Network 引領您探索更新更開放的互聯網。Mask Network允許您在社交網路上發送加密的貼文。同時我們提供了更多功能,例如發送加密紅包,購買加密貨幣,加密文件服務等。", - "setup_page_title": "歡迎來到Mask Network", - "setup_page_description": "在社交網路上加密您的推文和聊天訊息,只允許您的朋友進行解密。", - "setup_page_create_account_title": "創建新身份", - "setup_page_create_account_subtitle": "創造你的虛擬身份,探索Web3.0", - "setup_page_create_account_button": "創建", - "setup_page_create_restore_title": "從身份或備份中恢復", - "setup_page_create_restore_subtitle": "從身份和歷史備份中恢復", - "setup_page_create_restore_button": "備份 或 登錄", - "create_account_private_key": "私鑰", - "create_account_identity_title": "在Mask Network創建一個身份", - "create_account_sign_in_button": "恢復", - "create_account_preview_tip": "此二維碼將保存你的身份密碼,請妥善保存。可使用Mask手機端掃描二維碼來登錄。", - "create_account_mnemonic_confirm_failed": "錯誤身份代碼", - "create_account_connect_social_media_button": "創建", - "create_account_connect_social_media": "連接 {{type}}", - "create_account_persona_title": "歡迎來到Mask Network", - "create_account_persona_subtitle": "您可以創建個人身分並連接社交帳戶", - "create_account_persona_successfully": "創建成功", - "create_account_connect_social_media_title": "連接社交平台", - "create_account_failed": "創建帳號失敗", - "sign_in_account_identity_title": "恢復您的身分", - "sign_in_account_tab_identity": "身份", - "sign_in_account_sign_up_button": "註冊", - "sign_in_account_identity_warning": "數字身分助記詞只能恢復您的數字身分。它可以加密並解密由這個數字身分簽名和發送的社交網路信息內容。", - "sign_in_account_private_key_placeholder": "請輸入你的私鑰", - "sign_in_account_private_key_error": "私钥不正确", - "sign_in_account_private_key_persona_not_found": "Persona失蹤", - "sign_in_account_private_key_warning": "數字身分助記詞只能恢復您的數字身分。它可以加密並解密由這個數字身分簽名和發送的社交網路訊息內容。", - "sign_in_account_mnemonic_confirm_failed": "錯誤身份", - "sign_in_account_cloud_backup_send_email_success": "驗證碼已發往 {{type}}. 請查看 {{type}}.", - "sign_in_account_local_backup_warning": "本地備份能恢復之前所有被本地存儲的數據", - "sign_in_account_local_backup_payment_password": "支付密碼", - "sign_in_account_local_backup_file_drag": "請點擊或拖拽文件到此處", - "sign_in_account_cloud_backup_warning": "雲端備份會保存並加密您的數據。", - "sign_in_account_cloud_backup_not_support": "不支持數據備份格式", - "sign_in_account_cloud_send_verification_code_tip": "發送驗證碼至", - "sign_in_account_cloud_backup_failed": "恢復備份失敗,請再試一次。", - "sign_in_account_cloud_backup_email_or_phone_number": "郵箱地址或電話號碼", - "sign_in_account_cloud_backup_password": "備份密碼", - "sign_in_account_cloud_restore_failed": "恢復失敗", - "sign_in_account_cloud_backup_download_failed": "下載備份失敗", - "sign_in_account_cloud_backup_decrypt_failed": "備份解密失敗,請檢查密碼", - "sign_in_account_cloud_backup_email_format_error": "電子郵件地址不正確", - "sign_in_account_cloud_backup_phone_format_error": "此電話號碼不正確。", - "sign_in_account_cloud_backup_synchronize_password_tip": "已成功驗證您的雲端備份密碼,備份正在上傳,為了備份密碼的一致,請確認您是否願意將您的雲端備份密碼設置為本地備份密碼。", - "cloud_backup": "雲端備份", - "wallets_transfer": "轉賬", - "wallets_assets": "資產", - "wallets_transfer_memo": "備註", - "wallets_transfer_amount": "數量", - "wallets_transfer_to_address": "至地址", - "wallets_transfer_error_amount_absence": "輸入數額", - "wallets_transfer_error_address_absence": "輸入接收者地址", - "wallets_transfer_error_contract": "選擇NFT合約", - "wallets_transfer_error_nft": "選擇一個NFT", - "wallets_transfer_error_invalid_address": "無效接收者地址", - "wallet_transfer_error_no_address_has_been_set_name": "接收者地址不存在", - "wallet_transfer_error_no_ens_support": "網路不支持ENS。", - "wallets_transfer_error_insufficient_balance": "{{symbol}} 餘額不足", - "wallets_transfer_send": "傳送", - "wallets_transfer_memo_placeholder": "可選填訊息", - "wallets_transfer_contract": "合約", - "wallets_transfer_contract_placeholder": "選擇一份NFT合約", - "wallets_swap": "兌換", - "wallets_red_packet": "紅包", - "wallets_sell": "賣出", - "wallets_history": "歷史記錄", - "settings": "設定", - "gas_fee": "交易手續費", - "transfer_cost": "花費 {{gasFee}} {{symbol}} ≈ ${{usd}}", - "done": "完成!", - "wallet_transactions_pending": "待定中", - "wallet_gas_fee_settings_low": "低", - "wallet_gas_fee_settings_medium": "中", - "wallet_gas_fee_settings_high": "高", - "wallets_startup_create": "創建一個新錢包", - "wallets_startup_create_desc": "Mask network 支援 Eth、 BSC 和 Polygon 網路。", - "wallets_startup_create_action": "創建", - "wallets_startup_import": "導入錢包", - "wallets_startup_import_desc": "Mask network 支援私鑰、JSON 文件和助記詞", - "wallets_startup_import_action": "導入", - "wallets_startup_connect": "連結其他錢包", - "wallets_startup_connect_desc": "Mask network 支援 Metamask 和 Connect Wallet.", - "wallets_startup_connect_action": "連結", - "wallets_connect_wallet_metamask": "MetaMask", - "wallets_connect_wallet_connect": "連接錢包", - "wallets_connect_wallet_polka": "PolkaDot 錢包", - "wallets_create_wallet_input_placeholder": "錢包名稱", - "wallets_create_successfully_title": "成功", - "wallets_create_successfully_tips": "您已成功創建錢包", - "wallets_create_successfully_unlock": "解鎖錢包", - "wallets_create_wallet_alert": "Mask Network是一個免費的開源客戶端接口。 Mask Network允許您直接與區塊鏈進行交互,同時您可以完全控制自己的密鑰和資金,請仔細考慮。 您是掌控者。 Mask Network不是銀行或交易所。 我們不會保留您的鑰匙,資金或信息。 這意味著我們無法訪問帳戶,恢復密鑰,重設密碼或撤消交易。", - "wallets_wallet_connect_title": "使用兼容WalletConnect的錢包掃描QR碼", - "wallets_wallet_mnemonic": "助記符", - "wallets_wallet_json_file": "本地備份", - "wallets_wallet_private_key": "私鑰", - "wallets_import_wallet_tabs": "導入錢包標籤", - "wallets_import_wallet_password_placeholder": "錢包密碼", - "wallets_import_wallet_cancel": "取消", - "wallets_import_wallet_import": "輸入", - "wallets_create_wallet_tabs": "創建錢包標籤", - "wallets_create_wallet_refresh": "刷新", - "wallets_create_wallet_remember_later": "記得以後", - "wallets_create_wallet_verification": "确认", - "wallets_collectible_address": "收藏品地址", - "wallets_collectible_token_id": "代幣 ID", - "wallets_collectible_field_contract_require": "收藏品地址為必填項", - "wallets_collectible_field_token_id_require": "代幣 ID 為必填項", - "wallets_collectible_load_end": "載入完畢", - "wallets_balance": "結餘", - "wallets_balance_all_chain": "所有鏈", - "wallets_balance_Send": "發送", - "wallets_balance_Buy": "購買", - "wallets_balance_Swap": "交換", - "wallets_balance_Receive": "接收", - "wallets_assets_token": "代幣", - "wallets_assets_investment": "投資", - "wallets_assets_collectibles": "收藏品", - "wallets_assets_custom_token": "自定義代幣", - "wallets_assets_custom_collectible": "自定義收藏品", - "wallets_assets_asset": "資產", - "wallets_assets_balance": "餘額", - "wallets_assets_price": "價格", - "wallets_assets_value": "價值", - "wallets_assets_operation": "操作", - "wallets_address": "錢包地址", - "wallets_receive_tips": "掃瞄二維碼並發送 {{chainName}} 資產到此錢包", - "wallets_add_collectible": "添加收藏品", - "wallets_incorrect_address": "合約地址錯誤", - "wallets_collectible_been_added": "此收藏品已被添加", - "wallets_collectible_error_not_exist": "此藏品不存在或不屬於你", - "wallets_collectible_contract_is_empty": "請選擇合約", - "wallets_collectible_token_id_is_empty": "請選擇代幣", - "wallets_collectible_add": "新增", - "wallets_add_token": "新增代幣", - "wallets_token_been_added": "已添加代幣", - "wallets_token_symbol_tips": "代幣符號必須不超過11個字符。", - "wallets_add_token_contract_address": "代幣合約地址", - "wallets_add_token_symbol": "代幣符號", - "wallets_add_token_decimals": "小數點精度", - "wallets_add_token_cancel": "取消", - "wallets_add_token_next": "下一步", - "wallets_empty_tokens_tip": "沒有找到任何資產。請添加代幣。", - "wallets_empty_collectible_tip": "沒有找到任何收藏品。請添加收藏品。", - "wallets_address_copied": "已複製地址", - "wallets_address_copy": "複製 ", - "wallets_history_types": "類型", - "wallets_history_value": "價值", - "wallets_history_time": "時間", - "wallets_empty_history_tips": "無交易記錄", - "wallets_loading_token": "正在載入代幣", - "personas_setup_connect_tips": "請連接到您的 {{type}} 帳戶。", - "personas_setup_tip": "請創建或恢復身分。", - "personas_setup_connect": "連接", - "personas_name_maximum_tips": "名稱最長長度為 {{length}} 字", - "personas_name_existed": "身份名稱已存在", - "personas_rename_placeholder": "身分名稱", - "personas_confirm": "確認", - "personas_cancel": "取消", - "personas_export_persona": "導出身分", - "personas_export_persona_copy": "複製", - "personas_export_persona_copy_success": "已複製", - "personas_export_persona_copy_failed": "複製失敗", - "personas_export_persona_confirm_password_tip": "你還沒設置密碼,匯出私鑰前必須先設置備份密碼。", - "personas_export_private": "匯出私鑰", - "personas_export_private_key_tip": "此操作僅用於導出私鑰。我們不導出任何其他數據。如果您需要導出更多數據,請前往設置頁面:", - "personas_delete_confirm_tips": "請確認你已刪除身份 {{nickname}} 並已輸入密碼", - "personas_delete_dialog_title": "刪除身份", - "personas_edit_dialog_title": "編輯身分", - "personas_edit": "編輯", - "personas_delete": "刪除", - "personas_logout": "登出", - "personas_logout_confirm_password_tip": "您還沒有設置您的密碼。要登出身分,您必須先設置備份密碼。", - "personas_add_persona": "添加新身分", - "personas_back_up": "備份", - "personas_connect_to": "連接 {{internalName}}", - "personas_disconnect": "斷開連接", - "personas_disconnect_warning": "您確定要斷開{{network}} 帳戶{{userId}} 嗎?斷開連接後,此帳戶將無法解密並使用Mask Network加密任何訊息。", - "personas_logout_warning": "身分登出後,您關聯的社交網路帳戶將不能解密過去的加密訊息。如果您需要重新使用您的身分,您可以使用您的身分私鑰進行恢復。", - "personas_add": "新增", - "personas_upload_avatar": "上傳頭像", - "personas_rename": "重命名", - "personas_invite_post": "@{{identifier}} 您好,請下載Mask,以便我們可以用加密的方式分享貼文 #mask_io install http://mask.io", - "personas_empty_contact_tips": "您尚未有安裝Mask Network的聯繫人。請邀請您的朋友下載{{name}}。", - "personas_contacts_name": "名稱", - "personas_contacts_operation": "操作", - "personas_contacts_invite": "邀請", - "personas_post_is_empty": "您還沒有創建任何貼文。", - "personas_post_create": "創建貼文", - "settings_general": "一般", - "settings_backup_recovery": "備份 & 恢復", - "settings_local_backup": "本地備份", - "settings_cloud_backup": "雲端備份", - "settings_appearance_default": "按照系統設定", - "settings_appearance_light": "淺色", - "settings_appearance_dark": "深色", - "settings_backup_preview_account": "帳戶", - "settings_backup_preview_personas": "身分", - "settings_backup_preview_posts": "加密貼文", - "settings_backup_preview_contacts": "聯繫人", - "settings_backup_preview_wallets": "Mask錢包", - "settings_backup_preview_created_at": "備份時間", - "settings_language_title": "語言", - "settings_language_desc": "選擇您要使用的語言", - "settings_language_auto": "跟隨系統", - "settings_appearance_title": "外觀", - "settings_appearance_desc": "選擇你想使用的外觀", - "settings_data_source_title": "資料源", - "settings_data_source_desc": "從不同平台獲取趨勢資料", - "settings_sync_with_mobile_title": "與手機同步", - "settings_sync_with_mobile_desc": "您可以與您的移動設備同步您的帳戶和資料。打開Mask Network移動應用程式,進入設定頁面並點擊與插件同步。", - "settings_global_backup_desc": "提供本地和雲端兩種備份選項", - "settings_global_backup_last": "最近的備份時間為 {{backupAt}}。備份方法為:{{backupMethod}}。", - "settings_restore_database_title": "復原資料庫", - "settings_restore_database_desc": "從之前的數據庫備份復原", - "settings_email_title": "電子郵箱", - "settings_email_desc": "請綁定郵箱", - "settings_change_password_title": "備份密碼", - "settings_change_password_desc": "變更您的備份密碼", - "settings_change_password_not_set": "你還沒有設定備份密碼", - "settings_phone_number_title": "電話號碼", - "settings_phone_number_desc": "請綁定您的電話號碼", - "settings_password_rule": "備份密碼的長度必須在8到20個字符之間,並且至少包含一個數字,一個大寫字母,一個小寫字母和一個特殊字符。", - "settings_button_cancel": "取消", - "settings_button_confirm": "確認", - "settings_button_sync": "同步", - "settings_button_backup": "備份", - "settings_button_recovery": "恢復", - "settings_button_setup": "設定", - "settings_button_change": "變更", - "settings_dialogs_bind_email_or_phone": "請綁定郵箱或電話號碼", - "settings_dialogs_verify_backup_password": "驗證備份密碼", - "settings_dialogs_setting_backup_password": "設定備份密碼", - "settings_dialogs_change_backup_password": "變更備份密碼", - "settings_dialogs_setting_email": "設定電子郵箱", - "settings_dialogs_change_email": "變更郵箱", - "settings_dialogs_setting_phone_number": "設定電話號碼", - "settings_dialogs_change_phone_number": "變更電話號碼", - "settings_dialogs_incorrect_code": "驗證碼錯誤", - "settings_dialogs_incorrect_email": "郵箱地址不正確", - "settings_dialogs_incorrect_phone": "通訊號碼錯誤", - "settings_dialogs_incorrect_password": "密碼錯誤", - "settings_dialogs_inconsistency_password": "密碼不一致", - "settings_dialogs_current_email_validation": "當前驗證的郵箱地址為", - "settings_dialogs_change_email_validation": "需要驗證現有郵箱地址以更改", - "settings_dialogs_current_phone_validation": "當前驗證的電話號碼為", - "settings_dialogs_change_phone_validation": "想要變更電話號碼,您需要驗證您您當前的電話號碼:", - "settings_dialogs_backup_to_cloud": "備份到雲端", - "settings_dialogs_merge_to_local_data": "將雲端備份合併到本地並再次備份到雲端", - "settings_dialogs_backup_action_desc": "雲端備份已存在,請在備份之前合併雲端備份至本地,或者直接備份。", - "settings_dialogs_backup_to_cloud_action": "此選項將用本地數據覆蓋現有的雲端備份", - "settings_dialogs_backup_merged_tip": "您已把雲端備份合併到本地。如果您想要繼續完成備份,請點擊按鍵將您的所有資料更新到雲端。", - "settings_label_backup_password": "備份密碼", - "settings_label_new_backup_password": "新備份密碼", - "settings_label_backup_password_cloud": "雲端文件的備份密碼", - "settings_label_payment_password": "支付密碼", - "settings_label_re_enter": "重新輸入", - "settings_alert_password_set": "備份密碼設定成功", - "settings_alert_password_updated": "備份密碼已更新", - "settings_alert_email_set": "電子郵箱設定", - "settings_alert_email_updated": "郵箱已更新", - "settings_alert_phone_number_set": "電話號碼設定", - "settings_alert_phone_number_updated": "電話號碼已更新", - "settings_alert_backup_fail": "備份失敗", - "settings_alert_backup_success": "備份數據已成功", - "settings_alert_validation_code_sent": "驗證碼已傳送", - "settings_alert_merge_success": "您已經成功您的雲端備份合併到本地資料。", - "labs_file_service": "文件服務", - "labs_file_service_desc": "上傳及分享文件以享受永久去中心化存儲服務", - "labs_red_packet": "紅包", - "labs_swap": "兌換", - "labs_snapshot": "快照", - "labs_snapshot_desc": "在社交平台上展示和為提案投票", - "labs_market_trend": "市場走勢", - "labs_market_trend_desc": "直接在社交平台中展示代幣信息/走勢圖/換匯信息", - "labs_collectibles": "收藏", - "labs_gitcoin": "Gitcoin", - "labs_gitcoin_desc": "在社交媒體上顯示Gitcoin項目的具體資訊,並且直接對項目進行捐贈。", - "labs_valuables_desc": "從原創者手中購買推特並交易", - "labs_mask_box_desc": "使用專業的多鏈去中心平台以發售NFT盲盒", - "labs_loot_man": "Loot小人 by MintTeam", - "labs_loot_man_desc": "用全新方式在社交平台上連結展示你的NFT收藏並探索NFT的無限可能", - "labs_settings_market_trend_source": "默认信息源", - "labs_settings_swap": "兌換設定", - "labs_settings_swap_network": "{{network}} 鏈上默認交易所", - "labs_pets": "Loot小人 by MintTeam", - "labs_setup_tutorial": "設置教程", - "labs_do_not_show_again": "不再顯示", - "dashboard_mobile_test": "參與手機版本測試", - "dashboard_source_code": "源代碼", - "privacy_policy": "隱私政策", - "version_of_stable": "版本號 {{version}}", - "version_of_unstable": "版本號 {{version}}-{{build}}-{{hash}}", - "register_restore_backups": "恢復備份", - "register_restore_backups_cancel": "取消", - "register_restore_backups_confirm": "恢復", - "register_restore_backups_hint": "請點擊或拖拽文件到此處", - "register_restore_backups_file": "檔案", - "register_restore_backups_text": "文本內容", - "register_restore_backups_tabs": "還原備份", - "create_wallet_mnemonic_tip": "請不要忘記保存助記詞,您將需要這個才能訪問您的錢包。", - "create_wallet_password_uppercase_tip": "必須含有大寫字母", - "create_wallet_password_lowercase_tip": "必須含有小寫字母", - "create_wallet_password_number_tip": "必須包含數字", - "create_wallet_password_special_tip": "必須含有特殊符號", - "create_wallet_password_satisfied_requirement": "該密碼不滿足設置條件", - "create_wallet_password_match_tip": "輸入的密碼不一致", - "create_wallet_password_length_error": "密碼長度不符合規定", - "create_wallet_name_placeholder": "輸入1-12個字元", - "create_wallet_form_title": "創建錢包", - "create_wallet_re_enter_payment_password": "再次輸入支付密碼", - "create_wallet_your_wallet_address": "錢包地址", - "create_wallet_done": "完成", - "create_wallet_verify_words": "驗證助記詞", - "create_wallet_mnemonic_word_not_match": "助記詞錯誤" -} diff --git a/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx b/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx deleted file mode 100644 index 204c60e5e5dc..000000000000 --- a/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import { InjectedDialog, LoadingStatus } from '@masknet/shared' -import { memo, useCallback, useMemo, useRef } from 'react' -import { useDashboardTrans } from '../../locales/i18n_generated.js' -import { Box, DialogActions, DialogContent, Typography } from '@mui/material' -import { useBackupFormState, type BackupFormInputs } from '../../hooks/useBackupFormState.js' -import { ActionButton, makeStyles, useCustomSnackbar } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { useAsyncFn, useUpdateEffect } from 'react-use' -import Services from '#services' -import type { BackupAccountType } from '@masknet/shared-base' -import { fetchDownloadLink, fetchUploadLink, uploadBackupValue } from '../../utils/api.js' -import { encryptBackup } from '@masknet/backup-format' -import { encode } from '@msgpack/msgpack' -import { Controller } from 'react-hook-form' -import { PersonasBackupPreview, WalletsBackupPreview } from '../../components/BackupPreview/index.js' -import PasswordField from '../../components/PasswordField/index.js' -import { useNavigate, useSearchParams } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { format as formatDateTime } from 'date-fns' -import { UserContext } from '../../../shared-ui/index.js' - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - minHeight: 276, - }, - icon: { - '@keyframes spinner': { - to: { - transform: 'rotate(360deg)', - }, - }, - position: 'relative', - width: 40, - height: 40, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - '&:before': { - content: "''", - boxSizing: 'border-box', - position: 'absolute', - top: -11, - left: -15, - width: 68, - height: 68, - borderRadius: '50%', - border: `2px solid ${theme.palette.maskColor.main}`, - borderTopColor: theme.palette.maskColor.second, - animation: 'spinner 2s linear infinite', - }, - }, -})) - -interface BackupPreviewDialogProps { - open: boolean - onClose: () => void - isOverwrite: boolean - code: string - type: BackupAccountType - account: string - abstract?: string -} -export const BackupPreviewDialog = memo(function BackupPreviewDialog({ - open, - onClose, - isOverwrite, - code, - type, - account, - abstract, -}) { - const controllerRef = useRef(null) - const { classes, theme } = useStyles() - const [params, setParams] = useSearchParams() - const navigate = useNavigate() - const t = useDashboardTrans() - const { updateUser } = UserContext.useContainer() - const { - hasPassword, - previewInfo, - loading, - backupWallets, - setBackupWallets, - formState: { - clearErrors, - setError, - control, - handleSubmit, - resetField, - formState: { errors, isDirty, isValid }, - }, - } = useBackupFormState() - const { showSnackbar } = useCustomSnackbar() - - const [{ loading: uploadLoading, value }, handleUploadBackup] = useAsyncFn( - async (data: BackupFormInputs) => { - try { - if (backupWallets && hasPassword) { - const verified = await Services.Wallet.verifyPassword(data.paymentPassword || '') - if (!verified) { - setError('paymentPassword', { type: 'custom', message: t.incorrect_password() }) - return - } - } - - const { file } = await Services.Backup.createBackupFile({ - excludeBase: false, - excludeWallet: !backupWallets, - }) - - const name = `mask-network-keystore-backup-${formatDateTime(new Date(), 'yyyy-MM-dd')}` - const uploadUrl = await fetchUploadLink({ - code, - account, - type, - abstract: name, - }) - const encrypted = await encryptBackup(encode(account + data.backupPassword), encode(file)) - const controller = new AbortController() - controllerRef.current = controller - const response = await uploadBackupValue(uploadUrl, encrypted, controller.signal) - - if (response.ok) { - const now = formatDateTime(new Date(), 'yyyy-MM-dd HH:mm') - const downloadLinkResponse = await fetchDownloadLink({ - account, - type, - code, - }) - showSnackbar(t.settings_alert_backup_success(), { variant: 'success' }) - updateUser({ cloudBackupAt: now, cloudBackupMethod: type }) - setParams((params) => { - params.set('size', downloadLinkResponse.size.toString()) - params.set('abstract', downloadLinkResponse.abstract) - params.set('uploadedAt', downloadLinkResponse.uploadedAt.toString()) - params.set('downloadURL', downloadLinkResponse.downloadURL) - return params.toString() - }) - } - return true - } catch (error) { - showSnackbar(t.settings_alert_backup_fail(), { variant: 'error' }) - onClose() - if ((error as any).status === 400) navigate(DashboardRoutes.CloudBackup, { replace: true }) - return false - } - }, - [code, hasPassword, backupWallets, abstract, code, account, type, t, navigate, updateUser, params], - ) - - const handleClose = useCallback(() => { - // Cancel upload fetch when user close the modal - if (uploadLoading && controllerRef.current) controllerRef.current.abort() - onClose() - }, [uploadLoading, onClose]) - - useUpdateEffect(() => { - resetField('paymentPassword') - }, [backupWallets, resetField]) - - const content = useMemo(() => { - if (value) - return ( - - 🎉 - - {t.congratulations()} - - - {t.cloud_backup_successfully_tips()} - - - ) - if (uploadLoading) - return ( - - - - {t.uploading()} - - - ) - return !loading && previewInfo ? - - - - ( - clearErrors('backupPassword')} - sx={{ mb: 2 }} - placeholder={t.settings_label_backup_password()} - error={!!errors.backupPassword?.message} - helperText={errors.backupPassword?.message} - /> - )} - name="backupPassword" - /> - - - - {backupWallets && hasPassword ? - ( - clearErrors('paymentPassword')} - sx={{ mb: 2 }} - placeholder={t.sign_in_account_local_backup_payment_password()} - error={!!errors.paymentPassword?.message} - helperText={errors.paymentPassword?.message} - /> - )} - name="paymentPassword" - /> - : null} - {isOverwrite ? - - {t.cloud_backup_overwrite_tips()} - - : null} - - : - }, [ - loading, - previewInfo, - control, - t, - JSON.stringify(errors), - backupWallets, - setBackupWallets, - isOverwrite, - theme, - value, - uploadLoading, - classes, - ]) - - const action = useMemo(() => { - if (value) - return ( - - {t.done()} - - ) - if (uploadLoading) - return ( - - {t.cancel()} - - ) - return ( - : } - color={isOverwrite ? 'error' : 'primary'} - disabled={!isDirty || !isValid}> - {isOverwrite ? t.cloud_backup_overwrite_backup() : t.cloud_backup_upload_to_cloud()} - - ) - }, [ - backupWallets, - isOverwrite, - isDirty, - isValid, - hasPassword, - backupWallets, - value, - uploadLoading, - t, - handleClose, - handleSubmit, - handleUploadBackup, - onClose, - ]) - - return ( - - {content} - {action} - - ) -}) diff --git a/packages/mask/dashboard/modals/BackupPreviewModal/index.tsx b/packages/mask/dashboard/modals/BackupPreviewModal/index.tsx deleted file mode 100644 index 2d8cf5f567e7..000000000000 --- a/packages/mask/dashboard/modals/BackupPreviewModal/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { SingletonModalRefCreator, BackupAccountType } from '@masknet/shared-base' -import { useSingletonModal } from '@masknet/shared-base-ui' -import { forwardRef, useState } from 'react' -import { BackupPreviewDialog } from './BackupPreviewDialog.js' - -export interface BackupPreviewModalOpenProps { - isOverwrite?: boolean - code: string - type: BackupAccountType - account: string - abstract?: string -} - -export const BackupPreviewModal = forwardRef>((props, ref) => { - const [isOverwrite, setIsOverwrite] = useState(false) - const [code, setCode] = useState('') - const [type, setType] = useState() - const [account, setAccount] = useState('') - const [abstract, setAbstract] = useState('') - - const [open, dispatch] = useSingletonModal(ref, { - onOpen(props) { - if (props.isOverwrite) setIsOverwrite(props.isOverwrite) - if (props.abstract) setAbstract(props.abstract) - setCode(props.code) - setType(props.type) - setAccount(props.account) - }, - onClose(props) { - setIsOverwrite(false) - setAbstract('') - setCode('') - setType(undefined) - setAccount('') - }, - }) - - if (!open || !type) return null - return ( - dispatch?.close()} - isOverwrite={isOverwrite} - code={code} - type={type} - account={account} - abstract={abstract} - /> - ) -}) diff --git a/packages/mask/dashboard/modals/ConfirmModal/index.tsx b/packages/mask/dashboard/modals/ConfirmModal/index.tsx deleted file mode 100644 index ebf2e8ac4051..000000000000 --- a/packages/mask/dashboard/modals/ConfirmModal/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { InjectedDialog, useSharedTrans } from '@masknet/shared' -import type { SingletonModalRefCreator } from '@masknet/shared-base' -import { useSingletonModal } from '@masknet/shared-base-ui' -import { type ActionButtonProps, makeStyles, ActionButton } from '@masknet/theme' -import { DialogContent, Typography, type DialogProps, Box } from '@mui/material' -import { noop } from 'lodash-es' -import { forwardRef, memo, useState, type ReactNode } from 'react' - -const useStyles = makeStyles()((theme) => ({ - paper: { - minWidth: 320, - width: 320, - minHeight: 280, - }, - title: { - fontSize: 16, - lineHeight: '20px', - fontWeight: 700, - color: theme.palette.maskColor.main, - textAlign: 'center', - }, - message: { - marginTop: theme.spacing(3), - padding: theme.spacing(0), - lineHeight: '20px', - fontSize: 14, - color: theme.palette.maskColor.second, - textAlign: 'center', - }, - buttonGroup: { - width: '100%', - display: 'flex', - gap: theme.spacing(2), - flexDirection: 'column', - marginTop: theme.spacing(4), - }, -})) - -interface ConfirmDialogProps extends Omit { - title?: string - message?: ReactNode | string - cancelLabel?: string - confirmLabel?: string - confirmButtonProps?: ActionButtonProps - cancelButtonProps?: ActionButtonProps - onConfirm(): void - onClose?(): void -} - -const Dialog = memo( - ({ - title, - message, - cancelLabel, - confirmLabel, - confirmButtonProps, - cancelButtonProps, - onConfirm, - onClose, - ...rest - }) => { - const t = useSharedTrans() - const { classes } = useStyles() - return ( - - - - {title} - - - {message} - - - onConfirm()} - {...confirmButtonProps}> - {confirmLabel ?? t.confirm()} - - onClose?.()} - {...cancelButtonProps}> - {cancelLabel ?? t.cancel()} - - - - - ) - }, -) -export type ConfirmDialogOpenProps = Omit -export const ConfirmDialog = forwardRef>((_, ref) => { - const [props, setProps] = useState({ - title: '', - message: '', - onConfirm: noop, - }) - - const [open, dispatch] = useSingletonModal(ref, { - onOpen(p) { - setProps(p) - }, - }) - return dispatch?.close(false)} onConfirm={props.onConfirm} /> -}) diff --git a/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx b/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx deleted file mode 100644 index c01644f4490a..000000000000 --- a/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { InjectedDialog } from '@masknet/shared' -import { memo, useCallback, useMemo, useState } from 'react' -import { useDashboardTrans } from '../../locales/i18n_generated.js' -import { Box, DialogActions, DialogContent, LinearProgress, Typography } from '@mui/material' -import { ActionButton, makeStyles, useCustomSnackbar } from '@masknet/theme' -import { useAsync, useAsyncFn } from 'react-use' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { Icons } from '@masknet/icons' -import { last } from 'lodash-es' -import { formatFileSize } from '@masknet/kit' -import { format as formatDateTime, fromUnixTime } from 'date-fns' -import PasswordField from '../../components/PasswordField/index.js' -import { passwordRegexp } from '../../utils/regexp.js' -import { decryptBackup } from '@masknet/backup-format' -import { decode, encode } from '@msgpack/msgpack' -import Services from '#services' -import { BackupPreviewModal } from '../modals.js' -import type { BackupAccountType } from '@masknet/shared-base' - -const useStyles = makeStyles()((theme) => ({ - account: { - padding: theme.spacing(0.5, 2), - fontSize: 14, - fontWeight: 700, - }, - box: { - background: theme.palette.maskColor.bottom, - borderRadius: 8, - boxShadow: theme.palette.maskColor.bottomBg, - backdropFilter: 'blur(8px)', - padding: theme.spacing(1.5), - display: 'flex', - alignItems: 'center', - margin: theme.spacing(1.5, 0), - columnGap: 8, - }, - fileName: { - fontSize: 14, - lineHeight: '18px', - }, - container: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - minHeight: 276, - }, -})) - -interface MergeBackupDialogProps { - open: boolean - onClose: () => void - downloadLink: string - account: string - uploadedAt: string - size: string - type?: BackupAccountType - abstract?: string - code: string -} - -export const MergeBackupDialog = memo(function MergeBackupDialog({ - open, - onClose, - downloadLink, - account, - uploadedAt, - size, - type, - code, - abstract, -}) { - const t = useDashboardTrans() - const { classes, theme } = useStyles() - const [process, setProcess] = useState(0) - const [backupPassword, setBackupPassword] = useState('') - const [backupPasswordError, setBackupPasswordError] = useState('') - const [showCongratulation, setShowCongratulation] = useState(false) - const navigate = useNavigate() - const { showSnackbar } = useCustomSnackbar() - - const handleClose = useCallback(() => { - setBackupPassword('') - setBackupPasswordError('') - setShowCongratulation(false) - onClose() - }, [onClose]) - - const { value: encrypted } = useAsync(async () => { - if (!downloadLink || !open) return - - const response = await fetch(downloadLink, { method: 'GET', cache: 'no-store' }) - - if (!response.ok || response.status !== 200) { - showSnackbar(t.cloud_backup_download_link_expired(), { variant: 'error' }) - handleClose() - navigate(DashboardRoutes.CloudBackup, { replace: true }) - return - } - const reader = response.body?.getReader() - const contentLength = response.headers.get('Content-Length') - - if (!contentLength || !reader) return - let received = 0 - const chunks: number[] = [] - // eslint-disable-next-line no-constant-condition - while (true) { - const { done, value } = await reader.read() - - if (done || !value) { - setProcess(100) - break - } - chunks.push(...value) - received += value.length - - setProcess((received / Number(contentLength)) * 100) - } - return Uint8Array.from(chunks).buffer - }, [downloadLink, handleClose, open]) - - const fileName = useMemo(() => { - try { - if (!downloadLink) return '' - const url = new URL(downloadLink) - return last(url.pathname.split('/')) - } catch { - return '' - } - }, [downloadLink]) - - const [{ loading }, handleClickMerge] = useAsyncFn(async () => { - try { - if (!encrypted) return - const decrypted = await decryptBackup(encode(account + backupPassword), encrypted) - const backupText = JSON.stringify(decode(decrypted)) - const summary = await Services.Backup.generateBackupSummary(backupText) - if (summary.isErr()) { - setBackupPasswordError(t.cloud_backup_incorrect_backup_password()) - return - } - const backupSummary = summary.unwrapOr(undefined) - if (!backupSummary) return - if (backupSummary.countOfWallets) { - const hasPassword = await Services.Wallet.hasPassword() - if (!hasPassword) await Services.Wallet.setDefaultPassword() - } - await Services.Backup.restoreBackup(backupText) - showSnackbar(t.cloud_backup_download_backup(), { - variant: 'success', - message: t.cloud_backup_merge_to_local_successfully(), - }) - setShowCongratulation(true) - } catch { - showSnackbar(t.cloud_backup_merge_to_local_failed()) - } - }, [encrypted, backupPassword, account]) - - const handleClickBackup = useCallback(async () => { - if (!type) return - BackupPreviewModal.open({ - isOverwrite: true, - code, - abstract, - type, - account, - }) - handleClose() - }, [code, abstract, type, account, handleClose]) - - if (showCongratulation) - return ( - - - - 🎉 - - {t.congratulations()} - - - {t.cloud_backup_merge_to_local_congratulation_tips()} - - - - - - {t.cloud_backup_backup_to_mask_cloud_service()} - - - - ) - - return ( - - - {account} - - - - {fileName} - - - {process !== 100 ? - t.data_downloading() - : <> - - {formatFileSize(Number(size), false)} - - - {formatDateTime(fromUnixTime(Number(uploadedAt)), 'yyyy-MM-dd HH:mm')} - - - } - - - - - { - setBackupPassword(e.target.value) - setBackupPasswordError('') - }} - onBlur={(e) => { - if (!passwordRegexp.test(e.target.value)) { - setBackupPasswordError(t.cloud_backup_incorrect_backup_password()) - } - }} - error={!!backupPasswordError} - helperText={ - backupPasswordError ? backupPasswordError : ( - t.cloud_backup_enter_backup_password_to_decrypt_file() - ) - } - /> - - - - {t.cloud_backup_merge_to_local()} - - - - ) -}) diff --git a/packages/mask/dashboard/modals/MergeBackupModal/index.tsx b/packages/mask/dashboard/modals/MergeBackupModal/index.tsx deleted file mode 100644 index c936aadf6719..000000000000 --- a/packages/mask/dashboard/modals/MergeBackupModal/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { SingletonModalRefCreator, BackupAccountType } from '@masknet/shared-base' -import { useSingletonModal } from '@masknet/shared-base-ui' -import { forwardRef, useState } from 'react' -import { MergeBackupDialog } from './MergeBackupDialog.js' - -export interface MergeBackupModalOpenProps { - downloadLink: string - account: string - uploadedAt: string - size: string - code: string - abstract?: string - type: BackupAccountType -} - -export const MergeBackupModal = forwardRef>((props, ref) => { - const [downloadLink, setDownloadLink] = useState('') - const [code, setCode] = useState('') - const [type, setType] = useState() - const [account, setAccount] = useState('') - const [abstract, setAbstract] = useState('') - const [uploadedAt, setUploadedAt] = useState('') - const [size, setSize] = useState('') - const [open, dispatch] = useSingletonModal(ref, { - onOpen(props) { - if (props.abstract) setAbstract(props.abstract) - setCode(props.code) - setType(props.type) - setDownloadLink(props.downloadLink) - setAccount(props.account) - setUploadedAt(props.uploadedAt) - setSize(props.size) - }, - onClose(props) { - setCode('') - setType(undefined) - setDownloadLink('') - setAccount('') - setSize('') - setUploadedAt('') - }, - }) - - return ( - dispatch?.close()} - account={account} - downloadLink={downloadLink} - size={size} - uploadedAt={uploadedAt} - /> - ) -}) diff --git a/packages/mask/dashboard/modals/index.tsx b/packages/mask/dashboard/modals/index.tsx deleted file mode 100644 index 8b4ebcdc9b60..000000000000 --- a/packages/mask/dashboard/modals/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { memo } from 'react' - -import { ConfirmDialog } from './ConfirmModal/index.js' -import { BackupPreviewModal } from './BackupPreviewModal/index.js' -import { MergeBackupModal } from './MergeBackupModal/index.js' - -import * as modals from './modals.js' - -export const Modals = memo(function Modals() { - return ( - <> - - - - - ) -}) diff --git a/packages/mask/dashboard/modals/modals.ts b/packages/mask/dashboard/modals/modals.ts deleted file mode 100644 index 169a6014e5fc..000000000000 --- a/packages/mask/dashboard/modals/modals.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SingletonModal } from '@masknet/shared-base' -import type { ConfirmDialogOpenProps } from './ConfirmModal/index.js' -import type { BackupPreviewModalOpenProps } from './BackupPreviewModal/index.js' -import type { MergeBackupModalOpenProps } from './MergeBackupModal/index.js' - -export const ConfirmDialog = new SingletonModal() -export const BackupPreviewModal = new SingletonModal() -export const MergeBackupModal = new SingletonModal() diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/AddDeriveWallet/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/AddDeriveWallet/index.tsx deleted file mode 100644 index 87428534ba30..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/AddDeriveWallet/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { first, sortBy, uniq } from 'lodash-es' -import urlcat from 'urlcat' -import { memo, useCallback, useMemo, useState } from 'react' -import { useAsyncFn } from 'react-use' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import * as web3_utils from /* webpackDefer: true */ 'web3-utils' -import { useQueries, useQuery } from '@tanstack/react-query' -import { delay } from '@masknet/kit' -import { DeriveWalletTable } from '@masknet/shared' -import { DashboardRoutes, EMPTY_LIST } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { useWallet, useWallets } from '@masknet/web3-hooks-base' -import { EVMWeb3 } from '@masknet/web3-providers' -import { - HD_PATH_WITHOUT_INDEX_ETHEREUM, - currySameAddress, - generateNewWalletName, - isSameAddress, -} from '@masknet/web3-shared-base' -import { ProviderType } from '@masknet/web3-shared-evm' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import { Typography } from '@mui/material' -import { Box } from '@mui/system' -import { PrimaryButton } from '../../../components/PrimaryButton/index.js' -import { SecondaryButton } from '../../../components/SecondaryButton/index.js' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import { useDashboardTrans } from '../../../locales/i18n_generated.js' -import { ResetWalletContext } from '../context.js' -import Services from '#services' - -const useStyles = makeStyles()((theme) => ({ - header: { - display: 'flex', - justifyContent: 'space-between', - }, - second: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - title: { - fontSize: 36, - lineHeight: 1.2, - fontWeight: 700, - }, - between: { - display: 'flex', - justifyContent: 'space-between', - marginBottom: 12, - }, - pagination: { - display: 'flex', - padding: '8px', - alignItems: 'flex-start', - alignSelf: 'stretch', - marginTop: '12px', - gap: 12, - }, - paginationButton: { - borderRadius: 99, - background: theme.palette.maskColor.thirdMain, - width: '100%', - fontWeight: 700, - }, - bold: { - fontWeight: 700, - }, - create: { - fontSize: 14, - cursor: 'pointer', - lineHeight: '18px', - color: theme.palette.maskColor.main, - }, -})) - -const AddDeriveWallet = memo(function AddDeriveWallet() { - const t = useDashboardTrans() - const { cx, classes } = useStyles() - const navigate = useNavigate() - const state = useLocation().state as { - mnemonic: string - password: string - isReset: boolean - } - const [params] = useSearchParams() - const external_request = params.get('external_request') - - const { mnemonic, password, isReset } = state - // Avoid leaking mnemonic to react-query - const mnemonicHash = web3_utils.sha3(mnemonic) - const [pathIndexes, setPathIndexes] = useState([]) - const { handlePasswordAndWallets } = ResetWalletContext.useContainer() - - useWallet() // Warming up persist caching - const wallets = useWallets() - const existedSiblingQueries = useQueries({ - queries: - isReset ? - wallets.map((wallet) => ({ - queryKey: ['derive-address', mnemonicHash, wallet.derivationPath], - queryFn: async () => { - const derived = await Services.Wallet.generateAddressFromMnemonicWords( - '', - mnemonic, - wallet.derivationPath, - ) - const pathIndex = wallet.derivationPath?.split('/').pop() - if (pathIndex && isSameAddress(derived, wallet.address)) { - return Number.parseInt(pathIndex, 10) - } - return null - }, - })) - : EMPTY_LIST, - }) - const mergedIndexes = useMemo(() => { - if (!isReset || existedSiblingQueries.length === 0) return sortBy(uniq(pathIndexes)) - const existedSiblingsIndexes = existedSiblingQueries - .flatMap((x) => x.data) - .filter((x) => typeof x === 'number') as number[] - return sortBy(uniq([...pathIndexes, ...existedSiblingsIndexes])) - }, [pathIndexes, existedSiblingQueries, isReset]) - - const [page, setPage] = useState(0) - - const { data: walletChunks = EMPTY_LIST, isPending } = useQuery({ - queryKey: ['derived-wallets', mnemonicHash, page], - networkMode: 'always', - queryFn: async () => { - if (!mnemonic) return EMPTY_LIST - return await Services.Wallet.getDerivableAccounts(mnemonic, page) - }, - }) - - const tableData = useMemo(() => { - return walletChunks.map((derivedWallet) => { - const added = !!wallets.find(currySameAddress(derivedWallet.address)) - const pathIndex = derivedWallet.index - const selected = pathIndexes.find((item) => item === pathIndex) !== undefined - return { - added, - selected, - pathIndex, - address: derivedWallet.address, - } - }) - }, [walletChunks, wallets, pathIndexes]) - - const [{ loading: confirmLoading }, onConfirm] = useAsyncFn(async () => { - if (!mnemonic || !mergedIndexes.length) return - - const result = await handlePasswordAndWallets(password, isReset) - if (!result) return - const existedWallets = isReset ? [] : wallets - - const firstIndex = first(mergedIndexes) - const firstWallet = await Services.Wallet.recoverWalletFromMnemonicWords( - generateNewWalletName(existedWallets), - mnemonic, - `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/${firstIndex}`, - ) - - await Promise.all( - mergedIndexes.slice(1).map(async (pathIndex, index) => { - await Services.Wallet.recoverWalletFromMnemonicWords( - generateNewWalletName(existedWallets, index + 1), - mnemonic, - `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/${pathIndex}`, - ) - }), - ) - - await EVMWeb3.connect({ - account: firstWallet, - providerType: ProviderType.MaskWallet, - silent: true, - }) - await Services.Wallet.resolveMaskAccount([{ address: firstWallet }]) - Telemetry.captureEvent(EventType.Access, EventID.EntryPopupWalletImport) - await delay(300) // Wait for warming up above. 300ms is the closed duration after testing. - navigate(urlcat(DashboardRoutes.SignUpMaskWalletOnboarding, { external_request }), { replace: true }) - }, [mnemonic, wallets.length, isReset, password, mergedIndexes, external_request]) - - const onCheck = useCallback(async (checked: boolean, pathIndex: number) => { - setPathIndexes((list) => { - // Will sort and deduplicate in mergedIndexes - return checked ? [...list, pathIndex] : list.filter((x) => x !== pathIndex) - }) - }, []) - - const handleRecovery = useCallback(() => { - navigate(urlcat(DashboardRoutes.CreateMaskWalletMnemonic, { external_request })) - }, [navigate, external_request]) - - const disabled = confirmLoading || isPending || !mergedIndexes.length - - return ( - <> -
- - {t.create_step({ step: '2', totalSteps: '2' })} - - - {t.create()} - -
- - - {t.wallet_select_address()} - - - - - {t.wallet_derivation_path({ path: HD_PATH_WITHOUT_INDEX_ETHEREUM })} - - - - -
- setPage((prev) => prev - 1)}> - {t.previous_page()} - - setPage((prev) => prev + 1)}> - {t.next_page()} - -
- - - - {t.continue()} - - - - ) -}) - -export default AddDeriveWallet diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/ComponentToPrint.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/ComponentToPrint.tsx deleted file mode 100644 index 686e709d4957..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/ComponentToPrint.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { forwardRef, type ForwardedRef, useMemo } from 'react' -import { QRCode } from 'react-qrcode-logo' -import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { NetworkType } from '@masknet/web3-shared-evm' -import { EVMNetworkResolver } from '@masknet/web3-providers' -import { PrintBackground } from '../../../assets/index.js' -import { MnemonicReveal } from '../../../components/Mnemonic/index.js' -import { useDashboardTrans } from '../../../locales/i18n_generated.js' - -interface ComponentToPrintProps { - words: string[] - address: string -} - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - padding: theme.spacing(6), - backgroundColor: theme.palette.maskColor.white, - width: 630, - }, - card: { - width: '100%', - background: `url(${PrintBackground}) no-repeat`, - borderRadius: theme.spacing(1), - padding: theme.spacing(2), - color: theme.palette.maskColor.white, - display: 'flex', - alignItems: 'center', - backgroundSize: 'cover', - }, - publicKeyTitle: { - fontSize: 14, - color: theme.palette.maskColor.white, - lineHeight: '18px', - fontWeight: 700, - }, - publicKey: { - fontSize: 10, - color: theme.palette.maskColor.white, - lineHeight: '10px', - }, - title: { - fontSize: 16, - color: theme.palette.maskColor.publicMain, - lineHeight: '20px', - margin: theme.spacing(2.5, 0), - fontWeight: 700, - }, - tips: { - marginTop: theme.spacing(4.5), - fontSize: 14, - lineHeight: '18px', - fontWeight: 400, - color: theme.palette.maskColor.publicMain, - display: 'flex', - alignItems: 'center', - columnGap: 12, - }, - wordCard: { - backgroundColor: theme.palette.maskColor.publicBg, - color: theme.palette.maskColor.publicThird, - '&::marker': { - backgroundColor: theme.palette.maskColor.publicBg, - color: theme.palette.maskColor.publicThird, - }, - }, - text: { - color: theme.palette.maskColor.publicMain, - }, - qrWrapper: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - overflow: 'hidden', - width: 148, - height: 148, - borderRadius: 6, - }, -})) - -export const ComponentToPrint = forwardRef(function ComponentToPrint( - props: ComponentToPrintProps, - ref: ForwardedRef, -) { - const { words, address } = props - const t = useDashboardTrans() - const { classes } = useStyles() - - const qrValue = useMemo(() => { - return `mask://wallet/mnemonic/${btoa(words.join(' '))}` - }, [words.join(',')]) - - return ( - - - - - {t.address()}:{' '} - - {address} - - - -
- -
-
- {t.wallets_mnemonic_word()} - - - - {t.wallets_print_tips()} - -
- ) -}) diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx deleted file mode 100644 index d02eda675632..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx +++ /dev/null @@ -1,461 +0,0 @@ -import { memo, useCallback, useMemo, useRef, useState } from 'react' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import { useAsync, useAsyncFn } from 'react-use' -import urlcat from 'urlcat' -import { toBlob } from 'html-to-image' -import { Icons } from '@masknet/icons' -import { defer, timeout } from '@masknet/kit' -import { CopyButton } from '@masknet/shared' -import { DashboardRoutes } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { useWallets } from '@masknet/web3-hooks-base' -import { MaskWalletProvider, EVMWeb3 } from '@masknet/web3-providers' -import { generateNewWalletName, isSameAddress } from '@masknet/web3-shared-base' -import { ProviderType } from '@masknet/web3-shared-evm' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import { Alert, Box, Button, Stack, Typography, alpha, useTheme } from '@mui/material' -import Services from '#services' -import { MnemonicReveal } from '../../../components/Mnemonic/index.js' -import { PrimaryButton } from '../../../components/PrimaryButton/index.js' -import { SecondaryButton } from '../../../components/SecondaryButton/index.js' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import { useMnemonicWordsPuzzle, type PuzzleWord } from '../../../hooks/useMnemonicWordsPuzzle.js' -import { useDashboardTrans } from '../../../locales/index.js' -import { ResetWalletContext } from '../context.js' -import { ComponentToPrint } from './ComponentToPrint.js' - -const useStyles = makeStyles()((theme) => ({ - title: { - fontSize: 30, - margin: '12px 0', - lineHeight: '120%', - color: theme.palette.maskColor.main, - }, - tips: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - refresh: { - display: 'flex', - width: 88, - marginTop: 24, - padding: '8px 12px', - float: 'right', - cursor: 'pointer', - fontSize: 12, - color: theme.palette.maskColor.main, - justifyContent: 'center', - alignItems: 'center', - gap: '4px', - }, - words: { - marginTop: 12, - width: '100%', - }, - button: { - whiteSpace: 'nowrap', - }, - import: { - fontSize: 14, - cursor: 'pointer', - lineHeight: '18px', - color: theme.palette.maskColor.main, - }, - second: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - bold: { - fontWeight: 700, - }, - alert: { - marginTop: 24, - padding: 12, - color: theme.palette.maskColor.warn, - background: alpha(theme.palette.maskColor.warn, 0.1), - backdropFilter: 'blur(5px)', - }, - storeWords: { - display: 'flex', - alignItems: 'flex-start', - marginTop: 12, - gap: '12px', - }, - iconBox: { - height: 40, - width: 40, - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '8px', - border: `1px solid ${theme.palette.maskColor.line}`, - }, - buttonGroup: { - display: 'flex', - columnGap: 12, - }, - between: { - display: 'flex', - justifyContent: 'space-between', - }, - puzzleWord: { - display: 'flex', - padding: '12px', - alignItems: 'center', - gap: '12px', - alignSelf: 'stretch', - background: theme.palette.maskColor.bg, - borderRadius: '12px', - }, - puzzleWordList: { - display: 'flex', - padding: 0, - flexDirection: 'column', - alignItems: 'flex-start', - gap: '12px', - alignSelf: 'stretch', - }, - puzzleWordIndex: { - display: 'flex', - fontSize: 14, - color: theme.palette.maskColor.third, - justifyContent: 'center', - alignItems: 'center', - width: 36, - height: 38, - }, - puzzleOption: { - display: 'flex', - width: 180, - padding: '9px 8px', - alignItems: 'center', - marginRight: 24, - cursor: 'pointer', - }, - puzzleWordText: { - fontSize: 14, - fontWeight: 700, - }, - iconWrapper: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - marginRight: 8, - width: 18, - height: 18, - borderRadius: '99px', - overflow: 'hidden', - }, - checkIcon: { - width: 20, - height: 20, - color: theme.palette.maskColor.primary, - }, - emptyCheckbox: { - border: `2px solid ${theme.palette.maskColor.secondaryLine}`, - background: 'transparent', - }, - verificationFail: { - color: theme.palette.maskColor.danger, - fontSize: 14, - fontWeight: 400, - }, -})) - -async function pollResult(address: string) { - const subscription = MaskWalletProvider.subscription.wallets - if (subscription.getCurrentValue().find((x) => isSameAddress(x.address, address))) return - const [promise, resolve] = defer() - const unsubscribe = subscription.subscribe(() => { - if (subscription.getCurrentValue().find((x) => isSameAddress(x.address, address))) resolve(true) - }) - return timeout(promise, 10_000, 'It takes too long to create a wallet. You might try again.').finally(unsubscribe) -} - -const CreateMnemonic = memo(function CreateMnemonic() { - const location = useLocation() - const navigate = useNavigate() - const wallets = useWallets() - const walletName = generateNewWalletName(wallets) - const t = useDashboardTrans() - const { handlePasswordAndWallets } = ResetWalletContext.useContainer() - const [verified, setVerified] = useState(false) - const { classes, cx } = useStyles() - const [params] = useSearchParams() - const external_request = params.get('external_request') - const { words, refreshCallback, puzzleWordList, answerCallback, puzzleAnswer, verifyAnswerCallback, isMatched } = - useMnemonicWordsPuzzle() - - const onVerifyClick = useCallback(() => { - setVerified(true) - }, []) - - const handleRecovery = useCallback(() => { - navigate(urlcat(DashboardRoutes.RecoveryMaskWallet, { external_request }), { - state: { - password: location.state?.password, - isReset: location.state?.isReset, - }, - }) - }, [location.state?.password, location.state?.isReset, external_request]) - - const { value: hasPassword, loading: loadingHasPassword } = useAsync(Services.Wallet.hasPassword, []) - - const { value: address } = useAsync(async () => { - if (!words.length) return - - if (!hasPassword) await Services.Wallet.setDefaultPassword() - - const address = await Services.Wallet.generateAddressFromMnemonicWords(walletName, words.join(' ')) - return address - }, [words.join(' '), walletName, hasPassword]) - - const [{ loading }, onSubmit] = useAsyncFn(async () => { - const result = await handlePasswordAndWallets(location.state?.password, location.state?.isReset) - if (!result) return - const address = await Services.Wallet.createWalletFromMnemonicWords(walletName, words.join(' ')) - await pollResult(address) - await EVMWeb3.connect({ - silent: true, - providerType: ProviderType.MaskWallet, - account: address, - }) - await Services.Wallet.resolveMaskAccount([{ address }]) - Telemetry.captureEvent(EventType.Access, EventID.EntryPopupWalletCreate) - navigate(urlcat(DashboardRoutes.SignUpMaskWalletOnboarding, { external_request }), { replace: true }) - }, [walletName, words, location.state?.isReset, location.state?.password, external_request]) - - const step = useMemo(() => String((verified ? 3 : 2) - (hasPassword ? 1 : 0)), [verified, hasPassword]) - const totalSteps = hasPassword ? '2' : '3' - - return ( - <> -
- - {loadingHasPassword ? '' : t.create_step({ step, totalSteps })} - - - - {t.wallets_import_wallet_import()} - -
- {verified ? - - : - } - - ) -}) - -interface CreateMnemonicUIProps { - words: string[] - address: string | null | undefined - onRefreshWords: () => void - onVerifyClick: () => void -} - -interface VerifyMnemonicUIProps { - words: string[] - answerCallback: (index: number, word: string) => void - verifyAnswerCallback: (callback?: () => void) => void - onRefreshWords: () => void - puzzleAnswer: { - [key: number]: string - } - puzzleWordList: PuzzleWord[] - isReset: boolean - loading: boolean - isMatched: boolean | undefined - setVerified: (verified: boolean) => void - onSubmit: () => void -} - -interface PuzzleOption { - puzzleWord: PuzzleWord - puzzleAnswer: { - [key: number]: string - } - answerCallback: (index: number, word: string) => void -} - -const VerifyMnemonicUI = memo(function VerifyMnemonicUI({ - answerCallback, - setVerified, - onSubmit, - loading, - isReset, - puzzleWordList, - onRefreshWords, - puzzleAnswer, - verifyAnswerCallback, - isMatched, -}) { - const t = useDashboardTrans() - const { classes, cx } = useStyles() - - const handleOnBack = useCallback(() => { - onRefreshWords() - setVerified(false) - }, []) - - return ( - <> - - {t.wallets_create_wallet_verification()} - - {t.create_wallet_verify_words()} - - {puzzleWordList.map((puzzleWord, index) => ( -
- {puzzleWord.index + 1}. - -
- ))} -
- {isMatched === false ? - - {t.create_wallet_mnemonic_verification_fail()} - - : null} - -
- - {t.back()} - - verifyAnswerCallback(onSubmit)}> - {t.verify()} - -
-
- - ) -}) - -const PuzzleOption = memo(function PuzzleOption({ puzzleWord, puzzleAnswer, answerCallback }) { - const { classes, cx } = useStyles() - - return ( - <> - {puzzleWord.options.map((word, index) => ( -
answerCallback(puzzleWord.index, word)}> -
- {word === puzzleAnswer[puzzleWord.index] ? - - : null} -
- - {word} -
- ))} - - ) -}) - -const CreateMnemonicUI = memo(function CreateMnemonicUI({ - words, - onRefreshWords, - onVerifyClick, - address, -}) { - const t = useDashboardTrans() - const ref = useRef(null) - const { classes, cx } = useStyles() - const theme = useTheme() - - const [, handleDownload] = useAsyncFn(async () => { - if (!ref.current) return - const dataUrl = await toBlob(ref.current, { quality: 0.95 }) - if (!dataUrl) return - - const link = document.createElement('a') - link.download = 'mask-wallet-mnemonic.jpeg' - link.href = URL.createObjectURL(dataUrl) - link.click() - }, []) - - return ( - <> - {t.write_down_recovery_phrase()} - {t.store_recovery_phrase_tip()} - theme.spacing(2) }}> - - -
- -
-
-
- -
- -
- } severity="warning" className={classes.alert}> - {t.create_wallet_mnemonic_tip()} - - - - {t.create_wallet_mnemonic_keep_safe()} - - - - - - - - ) -}) - -export default CreateMnemonic diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx deleted file mode 100644 index c951e06a081e..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { memo, useMemo } from 'react' -import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { z as zod } from 'zod' -import { useForm, Controller } from 'react-hook-form' -import { zodResolver } from '@hookform/resolvers/zod' -import { useLocation, useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { useDashboardTrans } from '../../../locales/index.js' -import PasswordField from '../../../components/PasswordField/index.js' -import { PrimaryButton } from '../../../components/PrimaryButton/index.js' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import urlcat from 'urlcat' - -const useStyles = makeStyles()((theme) => ({ - container: { - width: '100%', - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - flex: 1, - }, - title: { - fontSize: 30, - margin: '12px 0', - lineHeight: '120%', - color: theme.palette.maskColor.main, - }, - form: { - marginTop: 24, - width: '90%', - maxWidth: 720, - }, - input: { - width: '100%', - marginTop: 10, - }, - tips: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - tipsBottom: { - fontSize: 14, - lineHeight: '18px', - marginTop: 8, - marginBottom: 24, - color: theme.palette.maskColor.main, - }, - - second: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - bold: { - fontWeight: 700, - }, -})) - -const CreateWalletForm = memo(function CreateWalletForm() { - const t = useDashboardTrans() - const { classes, cx } = useStyles() - const navigate = useNavigate() - const location = useLocation() - const params = new URLSearchParams(location.search) - const isReset = params.get('reset') - const isRecover = params.get('recover') - const external_request = params.get('external_request') - - const schema = useMemo(() => { - const passwordRule = zod - .string() - .min(6, t.create_wallet_password_length_error()) - .max(20, t.create_wallet_password_length_error()) - - return zod - .object({ - password: passwordRule, - confirm: zod.string().optional(), - }) - .refine((data) => data.password === data.confirm, { - message: t.create_wallet_password_match_tip(), - path: ['confirm'], - }) - }, [t]) - - const { - control, - handleSubmit, - formState: { errors, isValid }, - } = useForm<{ password?: string; confirm?: string }>({ - mode: 'onBlur', - resolver: zodResolver(schema), - defaultValues: { - password: '', - confirm: '', - }, - }) - - const onSubmit = handleSubmit((data) => { - navigate( - urlcat(isRecover ? DashboardRoutes.RecoveryMaskWallet : DashboardRoutes.CreateMaskWalletMnemonic, { - external_request, - }), - data.password ? - { - state: { password: data.password, isReset }, - } - : undefined, - ) - }) - - return ( -
- - {!isReset ? t.create_step({ step: '1', totalSteps: '3' }) : null} - - {t.set_payment_password()} - {t.create_wallet_payment_password_tip_1()} -
- - ( - - )} - name="password" - /> - ( - - )} - name="confirm" - control={control} - /> - - - {t.create_wallet_payment_password_tip_2()} -
- - - {t.continue()} - - -
- ) -}) - -export default CreateWalletForm diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/Onboarding/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/Onboarding/index.tsx deleted file mode 100644 index 3f3f23de48b5..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/Onboarding/index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { memo, useCallback, useMemo } from 'react' -import { useDashboardTrans } from '../../../locales/i18n_generated.js' -import { Box, Typography } from '@mui/material' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import { PrimaryButton } from '../../../components/PrimaryButton/index.js' -import { makeStyles } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { Trend } from '../../../assets/index.js' -import { PopupRoutes } from '@masknet/shared-base' - -import Services from '#services' -import { OnboardingWriter } from '../../../components/OnboardingWriter/index.js' -import { useSearchParams } from 'react-router-dom' - -const useStyles = makeStyles()((theme) => ({ - card: { - position: 'fixed', - top: 24, - right: 24, - padding: theme.spacing(2), - border: `1px solid ${theme.palette.maskColor.line}`, - borderRadius: 12, - maxWidth: 360, - }, - pin: { - fontSize: 16, - lineHeight: '20px', - color: theme.palette.maskColor.main, - }, - skeleton: { - background: 'linear-gradient(270deg, #F6F6F6 0%, rgba(217, 217, 217, 0) 94.74%)', - width: 190, - height: 36, - borderRadius: 99, - marginLeft: 42, - }, - plugins: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - width: 36, - height: 36, - borderRadius: 99, - // hard color - background: '#F0F0F4', - marginLeft: 18, - marginRight: 18, - }, - more: { - transform: 'rotate(90deg)', - }, - pinCard: { - marginTop: 18, - borderRadius: 8, - border: `1px solid ${theme.palette.maskColor.line}`, - background: theme.palette.maskColor.bottom, - padding: 16, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - }, - trend: { - position: 'fixed', - top: 206, - right: 408, - }, -})) - -const Onboarding = memo(function Onboarding() { - const t = useDashboardTrans() - const { classes } = useStyles() - const [params] = useSearchParams() - const external_request = params.get('external_request') - - const onOpenPopupWallet = useCallback(async () => { - if (external_request) { - await Services.Helper.openPopupWindow(PopupRoutes.SelectWallet, { external_request }) - } else { - await Services.Helper.openPopupWindow(PopupRoutes.Wallet, {}) - } - window.close() - }, [external_request]) - - const words = useMemo(() => { - return [ - - {t.create_wallet_onboarding_creating_identity()} - {t.onboarding_wallet()} - , - - {t.create_wallet_onboarding_generating_accounts()} - {t.accounts()} - , - - {t.create_wallet_onboarding_encrypting_data()} - {t.data()} - , - - {t.create_wallet_onboarding_ready()} - {t.ready()} - , - ] - }, [t]) - - return ( - <> - - {t.persona_onboarding_pin_tips()} - - - - - - - - - - - {/* There is no need for i18n here. */} - Mask Network - - - - - - - - - - - {t.create_wallet_onboarding_got_it()} - - - - ) -}) - -export default Onboarding diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx deleted file mode 100644 index adf6372232e0..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { DashboardRoutes, NetworkPluginID } from '@masknet/shared-base' -import { MaskTabList, makeStyles, useTabs } from '@masknet/theme' -import { useWallets, useWeb3State } from '@masknet/web3-hooks-base' -import { EVMWeb3 } from '@masknet/web3-providers' -import { generateNewWalletName } from '@masknet/web3-shared-base' -import { ProviderType } from '@masknet/web3-shared-evm' -import { TabContext, TabPanel } from '@mui/lab' -import { Tab, Typography } from '@mui/material' -import { Box } from '@mui/system' -import { memo, useCallback, useMemo, useState } from 'react' -import type { UseFormSetError } from 'react-hook-form' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import { useAsync } from 'react-use' -import { RestoreFromMnemonic } from '../../../components/Restore/RestoreFromMnemonic.js' -import { RestoreFromPrivateKey, type FormInputs } from '../../../components/Restore/RestoreFromPrivateKey.js' -import { RestoreWalletFromLocal } from '../../../components/Restore/RestoreWalletFromLocal.js' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import { RecoveryContext, RecoveryProvider } from '../../../contexts/index.js' -import { useDashboardTrans } from '../../../locales/i18n_generated.js' -import { ResetWalletContext } from '../context.js' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventID, EventType } from '@masknet/web3-telemetry/types' -import Services from '#services' -import urlcat from 'urlcat' - -const useStyles = makeStyles()((theme) => ({ - header: { - display: 'flex', - justifyContent: 'space-between', - }, - second: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, - title: { - fontSize: 36, - lineHeight: 1.2, - fontWeight: 700, - }, - tabContainer: { - border: `1px solid ${theme.palette.maskColor.line}`, - marginTop: theme.spacing(3), - borderRadius: theme.spacing(1), - overflow: 'hidden', - }, - tabList: { - background: theme.palette.maskColor.modalTitleBg, - padding: theme.spacing('14px', 2, 0), - }, - tab: { - fontSize: 16, - fontWeight: 700, - color: theme.palette.maskColor.second, - }, - panels: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: 0, - width: '100%', - }, - panelContainer: { - padding: theme.spacing(2), - }, - buttonGroup: { - display: 'flex', - columnGap: 12, - }, - between: { - display: 'flex', - justifyContent: 'space-between', - marginBottom: 12, - }, - bold: { - fontWeight: 700, - }, - create: { - fontSize: 14, - cursor: 'pointer', - lineHeight: '18px', - color: theme.palette.maskColor.main, - }, -})) - -const Recovery = memo(function Recovery() { - const t = useDashboardTrans() - const location = useLocation() - const { cx, classes } = useStyles() - const tabPanelClasses = useMemo(() => ({ root: classes.panels }), [classes.panels]) - const navigate = useNavigate() - const [error, setError] = useState('') - const { handlePasswordAndWallets } = ResetWalletContext.useContainer() - const [params] = useSearchParams() - const external_request = params.get('external_request') - - const [currentTab, onChange, tabs] = useTabs('mnemonic', 'privateKey', 'local') - - const onTabChange = useCallback((event: object, value: string) => { - onChange(event, value) - setError('') - }, []) - - const wallets = useWallets() - - const newWalletName = useMemo(() => generateNewWalletName(wallets), [wallets]) - - const handleRestoreFromMnemonic = useCallback( - async (values: string[]) => { - try { - const mnemonic = values.join(' ') - await Services.Wallet.getDerivableAccounts(mnemonic, 0, 1) - - navigate(urlcat(DashboardRoutes.AddDeriveWallet, { external_request }), { - replace: false, - state: { - mnemonic, - password: location.state?.password, - isReset: location.state?.isReset, - }, - }) - } catch (error) { - const errorMsg = (error as Error).message - // SDK's error message is not as same as design. - setError( - errorMsg === 'Invalid mnemonic words.' ? t.wallet_recovery_mnemonic_confirm_failed() : errorMsg, - ) - } - }, - [t, navigate, location.state?.isReset, location.state?.password, external_request], - ) - - const { NameService } = useWeb3State(NetworkPluginID.PLUGIN_EVM) - const handleRestoreFromPrivateKey = useCallback( - async (data: FormInputs, onError: UseFormSetError) => { - try { - const result = await handlePasswordAndWallets(location.state?.password, location.state?.isReset) - if (!result) return - const address = await Services.Wallet.generateAddressFromPrivateKey(data.privateKey) - const ens = await NameService?.reverse?.(address) - const walletName = ens || newWalletName - const account = await Services.Wallet.recoverWalletFromPrivateKey(walletName, data.privateKey) - await EVMWeb3.connect({ - account, - providerType: ProviderType.MaskWallet, - silent: true, - }) - Telemetry.captureEvent(EventType.Access, EventID.EntryPopupWalletImport) - navigate(urlcat(DashboardRoutes.SignUpMaskWalletOnboarding, { external_request }), { replace: true }) - } catch (error) { - const errorMsg = (error as Error).message - onError('privateKey', { - type: 'value', - message: errorMsg === 'Invalid private key.' ? t.sign_in_account_private_key_error() : errorMsg, - }) - } - }, - [t, navigate, location.state?.isReset, location.state?.password, newWalletName, external_request], - ) - - const onRestore = useCallback( - async (keyStoreContent: string, keyStorePassword: string) => { - try { - const result = await handlePasswordAndWallets(location.state?.password, location.state?.isReset) - if (!result) return - const jsonAddress = await Services.Wallet.generateAddressFromKeyStoreJSON( - keyStoreContent, - keyStorePassword, - ) - const ens = await NameService?.reverse?.(jsonAddress) - const walletName = ens || newWalletName - - const address = await Services.Wallet.recoverWalletFromKeyStoreJSON( - walletName, - keyStoreContent, - keyStorePassword, - ) - await EVMWeb3.connect({ - account: address, - providerType: ProviderType.MaskWallet, - silent: true, - }) - await Services.Wallet.resolveMaskAccount([{ address }]) - Telemetry.captureEvent(EventType.Access, EventID.EntryPopupWalletImport) - navigate(urlcat(DashboardRoutes.SignUpMaskWalletOnboarding, { external_request }), { replace: true }) - } catch (error) { - const errorMsg = (error as Error).message - // Todo: SDK should return 'Incorrect Keystore Password.' when keystore pwd is wrong. - setError( - errorMsg === 'Incorrect payment password.' ? - t.create_wallet_key_store_incorrect_password() - : errorMsg, - ) - } - }, - [t, navigate, location.state?.isReset, location.state?.password, newWalletName, external_request], - ) - - const handleRecovery = useCallback(() => { - navigate(urlcat(DashboardRoutes.CreateMaskWalletMnemonic, { external_request }), { - state: { - password: location.state?.password, - isReset: location.state?.isReset, - }, - replace: true, - }) - }, [location.state?.password, location.state?.isReset, external_request]) - - const { value: hasPassword, loading: loadingHasPassword } = useAsync(Services.Wallet.hasPassword, []) - - const step = hasPassword ? '1' : '2' - - return ( - <> -
- - {loadingHasPassword ? '' : t.create_step({ step, totalSteps: step })} - - - {t.create()} - -
- - - {t.wallet_recovery_title()} - - - - - {t.wallet_recovery_description()} - - -
- -
- - - - - -
-
- - - - - - - - - -
-
-
- - {({ SubmitOutlet }) => { - return ( - -
{SubmitOutlet}
-
- ) - }} -
-
- - ) -}) - -export default Recovery diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/context.ts b/packages/mask/dashboard/pages/CreateMaskWallet/context.ts deleted file mode 100644 index 003be98bd9ed..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/context.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useCallback } from 'react' -import { createContainer } from 'unstated-next' -import { useWallets } from '@masknet/web3-hooks-base' -import { getDefaultWalletPassword, CrossIsolationMessages, PopupRoutes } from '@masknet/shared-base' -import { EVMWeb3 } from '@masknet/web3-providers' -import { ProviderType } from '@masknet/web3-shared-evm' -import Services from '#services' -import { useQueryClient } from '@tanstack/react-query' - -function useContext() { - const wallets = useWallets() - const queryClient = useQueryClient() - const resetWallets = useCallback( - async (password: string | undefined, isReset: boolean | undefined) => { - if (!isReset || !wallets.length || !password) return - await Services.Wallet.resetPassword(password) - await EVMWeb3.resetAllWallets?.({ - providerType: ProviderType.MaskWallet, - }) - CrossIsolationMessages.events.walletsUpdated.sendToAll(undefined) - }, - [wallets.length], - ) - - const handlePasswordAndWallets = useCallback( - async (password: string | undefined, isReset: boolean | undefined) => { - const hasPassword = await Services.Wallet.hasPassword() - - if (!hasPassword) await Services.Wallet.setDefaultPassword() - const isLocked = await Services.Wallet.isLocked() - - queryClient.removeQueries({ queryKey: ['@@has-password'] }) - - if (isReset) { - await resetWallets(password, isReset) - if (isLocked && password) await Services.Wallet.unlockWallet(password) - } else if (hasPassword && isLocked) { - await Services.Helper.openPopupWindow(PopupRoutes.Wallet, {}) - return false - } else if (password && !hasPassword) { - await Services.Wallet.changePassword(getDefaultWalletPassword(), password) - } - return true - }, - [resetWallets, queryClient], - ) - - return { - resetWallets, - handlePasswordAndWallets, - } -} - -export const ResetWalletContext = createContainer(useContext) diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/index.tsx deleted file mode 100644 index 36bd06c30485..000000000000 --- a/packages/mask/dashboard/pages/CreateMaskWallet/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Routes, Route, useMatch } from 'react-router-dom' -import { lazy } from 'react' -import { DashboardRoutes, relativeRouteOf } from '@masknet/shared-base' -import { ResetWalletContext } from './context.js' -import { SetupFrame } from '../../components/SetupFrame/index.js' - -const CreateWalletForm = lazy(() => import(/* webpackMode: 'eager' */ './CreateWalletForm/index.js')) -const CreateMnemonic = lazy(() => import(/* webpackMode: 'eager' */ './CreateMnemonic/index.js')) -const OnBoarding = lazy(() => import(/* webpackMode: 'eager' */ './Onboarding/index.js')) -const OnRecovery = lazy(() => import(/* webpackMode: 'eager' */ './Recovery/index.js')) -const AddDeriveWallet = lazy(() => import(/* webpackMode: 'eager' */ './AddDeriveWallet/index.js')) - -const r = relativeRouteOf(DashboardRoutes.CreateMaskWallet) -export default function CreateWallet() { - return ( - - - - } /> - } /> - } /> - } /> - } /> - - - - ) -} diff --git a/packages/mask/dashboard/pages/PrivacyPolicy/en.html b/packages/mask/dashboard/pages/PrivacyPolicy/en.html deleted file mode 100644 index 937430388d41..000000000000 --- a/packages/mask/dashboard/pages/PrivacyPolicy/en.html +++ /dev/null @@ -1,872 +0,0 @@ - - - - - - - - -
-
-

Mask Network Service Agreement beta

-
-
Dear user,
-

- Thank you for choosing the service of Mask Network. The "Mask Network Service Agreement" (hereinafter - referred to as the "Agreement") is signed by - http://mask.io (hereinafter referred to as "Mask Network" or "we") and the user - (hereinafter referred to as "you" or "user"). The agreement establishes the legal effect of contract - between you and Mask Network. -

-

- Mask Network hereby reminds you that before using Mask Network (hereinafter referred to as "Mask - Network" or "the software"), please carefully read the "Mask Network Service Agreement" and the relevant - terms mentioned later especially those in "Disclaimer and Limitation of Liability", to ensure that you - fully understand the terms of this agreement and take risks into consideration autonomously. -

- -
1. Regarding the Confirmation and Acceptance of this Agreement
-
    -
  1. -

    - You understand that this agreement and related agreements apply to Mask Network and the - decentralized applications autonomously developed and owned by Mask Network on Mask Network - (referred to as "DApps") (excluding DApps developed by third parties). -

    -
  2. -
  3. -

    - If you download the Mask Network software and create or import an account, you shall be deemed - to have fully read and accepted all the terms of this agreement. This agreement shall take - effect immediately and be binding on both parties. -

    -
  4. -
  5. -

    - This agreement can be updated by Mask Network at any time. Once the modified agreement is - published on Mask Network, it will take effect immediately without further notice. After Mask - Network announces the revised agreement terms, if you do not accept the revised terms, please - stop using Mask Network immediately. Your continued use of Mask Network will mean that you - accept and agree to the revised terms. -

    -
  6. -
  7. -

    - If you are under the age of 18, or if you have no civil capacity or limited capacity of civil - conduct, please use Mask Network under the guidance of your parents or guardians. -

    -
  8. -
- -
2. Definition
-
    -
  1. -

    - Mask Network: refers to the multi-chain based encrypted social assistance tool developed by Mask - Network. The tool is compatible with some decentralized networks and is able to run some DApps. -

    -
  2. -
  3. -

    The user:

    -
      -
    1. -

      The user must be a natural person with full civil capacity;

      -
    2. -
    3. -

      - If you are a minor under the age of 18, you need to use Mask Network under the guidance - of your parents or guardians. If a person with no civil capacity engages in transactions - using Mask Network, or a person with limited civil capacity engages in transactions - beyond the scope of their civil rights or capacity, Mask Network has the right to hold - the person and the person’s parents or guardians responsible for all consequences. -

      -
    4. -
    -
  4. -
  5. -

    - Creating or importing a wallet: refers to the process of using Mask Network to create or import - a wallet in compliance with this agreement. -

    -
  6. -
  7. -

    - Identity code: Mask Network provides identity code for users, which is composed of 12 ordered - mnemonic words randomly generated by algorithms. It is the key to create and restore your - digital identity. Please store it carefully. -

    -
  8. -
  9. -

    - Persona: user identity with public and private key pair, derived from the identity code in the - system. Each Persona will have its corresponding public and private keys, which are used to post - encrypted information and decrypt information shared by others. -

    -
  10. -
  11. -

    - Wallet account: refers to the process of creating a wallet on Mask Network. As a decentralized - application, a set of mnemonic words will be provided during the initial setup of wallet. The - wallet can be re-imported through plaintext private key, mnemonic words or encrypted - keystore/json file. -

    -
  12. -
  13. -

    - Wallet payment password: refers to the password determined by you during the process of creating - the Mask Wallet, which will be used to encrypt and protect your wallet. As Mask Network is a - decentralized application, we will not store any of your wallet data. Once you lose or forget - the wallet password, you need to reset the wallet password with the help of a private key or - mnemonic phrase. -

    -
  14. -
  15. -

    - Backup password: the backup password is used to encrypt files when you export data. Any exported - data is protected by your backup password. Mask Network does not store user passwords, nor do we - provide a centralized function of password recovery. Please protect your encrypted backup and - backup password. -

    -
  16. -
  17. -

    - Information prompt: It is recommended that the user follow the relevant steps of the information - prompt provided by the user interface of Mask Network software. -

    -
  18. -
  19. -

    - Private key: It is composed of 256-bit random characters and is the core of user’s ownership and - management of digital assets. -

    -
  20. -
  21. -

    - Public key: It is a digital wallet address generated by one-way derivation from the private key - under the principles of cryptography. It is the public address for receiving digital assets. -

    -
  22. -
  23. -

    - Keystore: It is a file where the private key or mnemonic phrase is stored and encrypted by the - wallet password set up by the user. It is only stored in your local device and will not be - synchronized to the Mask Network server. -

    -
  24. -
  25. -

    - Mnemonic phrase: It is another form of plaintext private key. Mask Network uses 12 words for - encryption. The generation sequence is based on certain algorithm. Please back up your mnemonic - phrase in time with physical medium, such as writing the mnemonic down on paper with a pen. -

    -
  26. -
  27. -

    - Digital assets: refers to the types of digital assets currently supported by Mask Network, - including but not limited to ETH, DAI, etc. -

    -
  28. -
  29. -

    - Third-party services: refer to products and services provided by third-party DApps, third-party - smart contracts, third-party open source agreements, third-party products and wallets, etc. -

    -
  30. -
  31. -

    - Personal information: refers to various information of a natural person recorded electronically - or in other ways that can identify the user's personal identity alone or in combination with - other information, including but not limited to the name, date of birth, ID number, personal - biometric information, address, phone number, bank card number, email address, wallet address, - mobile device information, operation records, transaction records, etc., but does not include - the user’s wallet password, private key, mnemonic phrase and keystore. -

    -
  32. -
  33. -

    - Risk warning: refers to the risk warning that needs to be confirmed when using any - wallet-related plug-ins in Mask Network. -

    -
  34. -
-
3. Service Content
-
    -
  1. -

    - Create identity code: The identity code consists of 12 random words and supports the encryption - and decryption of encrypted social information signed and posted by the digital identity. - Through "create an identity" provided by Mask Network, you can create or restore your identity - (login). You can manage multiple personas within the identity at the same time. -

    -
  2. -
  3. -

    - Create or import a wallet. For digital assets supported by Mask Network, you can use Mask - Network to generate a new wallet or import compatible wallets generated by other wallet tools in - relevant blockchain networks. Mask wallet currently supports three chains: ETH, BSC, Polygon - (Matic network). In the future, we may integrate other chains -

    -
  4. -
  5. -

    - Backup and recovery: You can use the global backup function to back up data locally or via the - cloud, including identity, Persona, encrypted social information, wallet (optional), etc. The - user needs to set up a backup password to encrypt the selected data. If the user chooses to back - up the Mask wallet, the user needs to enter the payment password to verify the wallet. When the - user chooses to back up via cloud provided by Mask Network, the user needs to set up a mobile - phone number or email address for sending and receiving verification codes so that the encrypted - backup data can be fetched from cloud. -

    -
  6. -
  7. -

    - Blockchain contract interactions such as transfer, receiving, lucky drops, Swap, etc. You can - use Mask Network's services such as transfer, receiving, lucky drops and Swap to manage digital - assets, that is, use private keys for electronic signatures and to modify related blockchain - ledger. Transfer means that the payer uses the payee's blockchain address to conduct transfer - operations. The actual transfer and receiving operations occur in the relevant blockchain - systems (not Mask Network). -

    -
  8. -
  9. -

    - Post encrypted information. You can install the Mask Network plug-in in your browser, and use - the Mask Network plug-in on supported social media platforms to post encrypted information. -

    -
  10. -
  11. -

    - Browse through DApps. Mask Network Dashboard - Mask Labs provides self-owned DApps and DApps - provided by third-party platforms. For DApps provided by third-party platforms, Mask Network - only provides users with access to the DApp and provides a blockchain browser, and users can - click it to jump to the third-party DApp and use the services provided by the DApp. As Mask - Network provides non-commercial integration services, it will integrate plug-ins provided by - community members and excellent teams of third-party DApps. Before using any wallet-related - plug-ins in Mask Network (including Mask Network’s own DApps and third-party DApps), the users - need to confirm the risk warning. -

    -
  12. -
  13. -

    - Transaction Records. We will display transaction records through blockchain systems. The - transaction records are subject to the records of the blockchain systems. -

    -
  14. -
  15. -

    - The file storage system currently used by Mask Network is Arweave's permanent storage system. - Currently, Mask Network is the official payer of storage service fees. As long as the storage - funds of Mask Network are sufficient, Mask Network will continue to pay for users. We do not - rule out the possibility that with the rapid expansion of Mask Network users in the future, - users may need to pay for file storage by themselves. Arweave's file storage can only be - encrypted and stored by user's own account. Mask Network cannot parse any user’s encrypted - files. The files can only be decrypted by users of Mask Network themselves. As the file storage - service is provided by Arweave, Arweave is responsible for any file problems that might occur - and Mask Network shall not be held accountable. -

    -
  16. -
  17. -

    - Service of suspension. You understand that based on the "irrevocable" nature of blockchain - system transactions, we cannot suspend or cancel transactions for you. -

    -
  18. -
  19. Other services that Mask Network deems necessary to provide.

  20. -
  21. -

    - Users should learn about the following common problems when accepting the above services - provided by Mask Network: -

    -
      -
    1. -

      - a). Adhering to the decentralized characteristics of blockchain, Mask Network provides - decentralized services which are essentially different from banking financial - institutions in order to protect the security of users' digital assets. Users should - understand that Mask Network does not provide the following services: -

      -
        -
      1. -

        - i. Store the Identity code, backup password, wallet payment password, private - key, mnemonic phrase, keystore of non-hosted users. -

        -
      2. -
      3. -

        ii. Freeze a wallet;

        -
      4. -
      5. -

        iii. Report a lost wallet;

        -
      6. -
      7. -

        iv. Restore a wallet;

        -
      8. -
      9. -

        v. Transaction rollback;

        -
      10. -
      -
    2. -
    3. -
    -
  22. -
    -

    - b). Since Mask Network does not provide the above services, users should keep the mobile devices - with installation of Mask Network safe, back up the identity code of Mask Network, back up - wallet payment password, mnemonic phrase, private key and Keystore by themselves. If the user - should lose the mobile device, or delete the identity code of Mask Network without backup, or - delete the wallet without backup, or forget the wallet password, private key, mnemonic phrase, - Keystore, or if the wallet is stolen, Mask Network cannot restore the wallet or retrieve the - wallet password, private key, Mnemonic phrase or Keystore; If the user should make a mistake - during a transaction (such as entering the wrong transfer address), Mask Network cannot cancel - the transaction. -

    -
    -
    -

    - c). Mask Network and the digital asset management services provided by Mask Network do not - include all existing digital assets. Please do not operate any digital assets that Mask Network - does not support through Mask Network. -

    -
    -
    -

    - d). Mask Network is only a digital asset management tool for users, not an exchange or trading - platform. Although this agreement will refer to "transactions" many times, it generally means - the transfer and receiving operations conducted by users using Mask Network, which is - essentially different from "transactions" conducted on exchanges or trading platforms. -

    -
    -
    -

    - e). The integrated DApps on Mask Network include DApps owned by Mask Network and DApps that are - provided by third-party platforms. For DApps provided by third-party platforms, Mask Network - only provides blockchain explorer for users to enter the DApps. Before accepting the service or - conducting transactions, users must judge and evaluate by themselves whether there is risk in - the service or transaction provided by the third-party DApps. -

    -
    -
- -
4. Your Rights and Obligations
-

1). Create or import a wallet

-
-

- a). Create or import a wallet: You have the right to create and/or import a wallet through Mask - Network on your mobile device, and you have the right to use your own wallet to conduct transactions - such as transfer and receiving on the blockchain through the Mask Network application. Mask Network - may develop different software versions for different terminal devices. You should choose to - download and install the appropriate version according to your actual needs. If you obtain a - software or an installation program with the same name as the Mask Network software from an - unauthorized third party, Mask Network cannot guarantee the normal use of the software, nor its - security, and you shall be responsible for any losses caused by the software. -

-
-
-

- b). After the new version of this software is released, the old version of the software may not be - available to use. Mask Network does not guarantee the security, continued availability and - corresponding customer service of the old version of the software. Please back up your existing data - at any time, check and download the latest version. -

-
-

2). Use

-
-

- a). Users should properly keep the mobile device, wallet password, private key, mnemonic phrase, - Keystore and other information by themselves. Without authorization, Mask Network is not responsible - for keeping the above information for users. All risks, liabilities, losses, and expenses caused by - your loss of mobile device, active or passive disclosure of wallet password, forgetting wallet - password, private key, mnemonic phrase and Keystore, or attack and fraud by others shall be borne by - you. -

-
-
-

- b). Follow the information prompts. You understand and agree to follow the information prompts - provided by Mask Network and operate in accordance with the content of the information prompts. - Otherwise, all risks, responsibilities, losses and expenses shall be borne by you. -

-
-
-

- c). You know and understand that Mask Network is not obliged to perform due diligence on the linked - third-party DApp services or transactions, and you should make rational investment decisions and - bear the corresponding investment risks independently. -

-
-
-

- d). Actively complete identity verification. When Mask Network reasonably believes that your - transaction behavior or transaction condition is abnormal, or that your identity information is - suspicious, or that your ID or other necessary documents should be verified, please actively - cooperate with Mask Network to verify your valid ID or other necessary documents and complete the - relevant identity verification in time. -

-
-

3). Transfer.

-
-

- a). You understand that because of the "irrevocable" attribute of blockchain-based operations, when - you use Mask Network’s transfer function, you should bear the consequences caused by your mistakes - by yourself (including but not limited to wrong transfer address, your own choice of the node - server). -

-
-
-

- b). You understand that when using the Mask Network service, the following situations may lead to - "transaction failure" or "packing timeout" in transfers: -

-
-
-

i. Insufficient wallet balance;

-
-
-

ii. Insufficient transaction miner fees;

-
-
-

iii. The block chain failed to execute the contract code;

-
-
-

iv. Technical failures with network, equipment, etc.;

-
-
-

v. Blockchain network congestion, failure and other reasons cause transactions to be abandoned;

-
-
-

- vi. Your address or the address of the counterparty is identified as a special address, such as a - high-risk address, an exchange address, an ICO address, a Token address, etc. -

-
-

- 4). You know that Mask Network only provides you with transfer tools. After you use Mask Network to - complete the transfer, Mask Network has completed all the obligations of the current service. Mask - Network will not bear any obligations for other disputes. -

-

- 5). Legal compliance. You understand that you should follow the requirements of relevant laws, - regulations and national policies when operating on Mask Network or using DApps on Mask Network to - conduct transactions. -

-

6). Service fees and tax liability:

-
-

- (a) Mask Network does not charge you any form of service fee or handling fee for the time being, and - specific terms will be further agreed or announced when certain services need to be charged in the - future; -

-
-
-

- (b) When you use Mask Network to transfer funds, you should pay miner fees, and the amount is - determined by you. Miner fees are collected by the relevant blockchain system; -

-
-
-

- (c) You know that under certain circumstances, if your transfer operation is not completed because - of your environment and unstable network status, the relevant blockchain system will still charge - miner fees; -

-
-
-

- (d) You are responsible for paying all taxable expenses and other expenses incurred by you for - trading on Mask Network. -

-
- -
5. Risk Warning
-

- 1). You understand that due to the unsound laws, regulations and policies in the field of digital - assets, there might be major risks such as inability to cash out digital assets and technical - instability. What’s more, the price volatility of digital assets is much higher than other financial - assets. We cautiously remind you that you should rationally choose to hold or sell any digital asset - based on your financial situation and risk appetite. Mask Network provides the service of viewing the - third-party market trend, which displays only search results from capturing the exchange rate of digital - assets on some exchanges, and does not represent the latest market trend or the best offer. -

-

- 2). When using the Mask Network service, if you or your counterparty fail to comply with the agreement, - or with the operation prompts and rules on related pages such as website instructions, transactions, - payments, sending and receiving lucky drops, Mask Network does not guarantee that the transaction will - be completed smoothly, and Mask Network is not liable for losses. If the foregoing situation occurs and - the money has been credited to your or your counterparty’s Mask Network wallet or third-party wallet, - you understand that due to the "irreversible" attribute of blockchain operations and the "irrevocable" - characteristic of related transactions, you and your counterparty shall bear the corresponding risks and - consequences. -

-

- 3). When you use the third-party DApp services integrated by Mask Network or conduct transactions, for - your benefit, Mask Network recommends that you carefully read this agreement and Mask Network prompts, - understand the transaction party and product information, and carefully assess the risks before taking - action. All your transactions in third-party DApps are your personal behaviors. A binding contractual - relationship is established between you and your counterparty, not with Mask Network. Mask Network - assumes no responsibility for all risks, liabilities, losses, and expenses caused by your trading - operations. -

-

- 4). During the transaction, you should judge by yourself whether the counterparty is a person with full - civil capacity and decide whether to trade with the counterparty or transfer funds to the counterparty, - and you shall bear all risks related to this. -

-

- 5). During the transfer process, if there are abnormal information prompts such as "transaction failed" - and "package timeout", you should reconfirm through the official channel of the relevant blockchain - system or other blockchain query tools to avoid duplicate transfer; otherwise, all losses and expenses - caused by this shall be borne by you. -

-

- 6). You understand that after you create or import a wallet on Mask Network, your keystore, private key, - mnemonic phrase and other information are only stored in your current mobile device, not on the Mask - Network or Mask Network servers. You can change your mobile device by synchronizing your wallet and by - other ways according to the operation guide provided by Mask Network. However, if you do not save or - back up your wallet password, private key, mnemonic phrase, keystore and other information, when your - device is lost, your digital assets will be lost consequentially. Mask Network cannot retrieve it for - you and you shall bear the corresponding loss by yourself. When you export, save or back up wallet - passwords, private keys, mnemonics, keystore and other information, if the information is leaked, or if - the device or server that has stored or backed up the above information is hacked or controlled, your - digital assets will be lost consequentially. Mask Network cannot retrieve it for you and you shall bear - the corresponding loss by yourself. -

-

- 7). We recommend that you back up your wallet password, private key, mnemonic phrase, and keystore - securely when creating or importing a wallet. Please do not use the following backup methods: - screenshots, emails, notepads, text messages, WeChat, QQ and other electronic backup methods. We - recommend that you write down information such as mnemonics and keystores on a paper notebook, and you - can also store your electronic data in a password manager. -

-

- 8). We recommend that you use Mask Network in a secure network environment and ensure that your mobile - device is not jailbroken or rooted to avoid possible security risks. -

-

- 9). Please be alert to non-official Mask Network frauds when using our services. Once you discover such - behavior, we encourage you to inform us as soon as possible. -

- -
6. Service Change, Interruption, and Termination
-

- 1). You agree that Mask Network can temporarily provide some service functions, or suspend some service - functions or provide new service functions in the future in order to guarantee the right of independent - business operation. When any function is reduced, increased or changed, as long as you still use the - services provided by Mask Network, it means that you still agree to this agreement or the revised terms - of this agreement. -

-

- 2). In order to protect the security of your digital assets, please try to avoid the misoperation or - risks caused by using Mask Network without basic knowledge of blockchain. Otherwise, Mask Network - reserves the right to restrict or refuse to provide some or all of the service functions. -

-

3). You understand that Mask Network will suspend services under the following circumstances:

-
-

- a). Due to technical reasons such as your equipment failure, blockchain system maintenance, upgrade, - failure and communication interruption; -

-
-
-

- b). Due to force majeure factors such as typhoons, earthquakes, tsunamis, floods, power outages, - wars or terrorist attacks, viruses, Trojan horses, hacker attacks, system instability, or government - actions. -

-
-
-

c). Other situations that Mask Network cannot control or reasonably foresee.

-
-

- 4). When you change, suspend, or terminate Mask Network Service, it will not affect the migration and - storage of user's local data. -

- -
7. Your Promise to Legally use Mask Network Services
-

- 1). You should abide by the laws and regulations of the country or region of residence and must not use - Mask Network for any illegal purpose or use Mask Network services in any illegal way. -

-

- 2). You may not use Mask Network to engage in illegal or criminal activities, including but not limited - to: -

-
-

- a. Oppose the basic principles established by the Constitution, endanger national security, leak - state secrets, subvert state power, or undermine national unity; -

-
-
-

- b. Engage in any illegal and criminal acts, including but not limited to money laundering, illegal - fund-raising, etc.; -

-
-
-

- c. Use any automated programs, software, engines, web crawlers, web analysis tools, data mining - tools or similar tools to have access to Mask Network services, collect or process the content - provided by Mask Network, interfere or attempt to interfere with users, or gain access to Mask - Network services in any other ways. -

-
-
-

d. Provide gambling information or induce others to participate in gambling in any way;

-
-
-

e. Invade others' Mask Network wallet to steal digital assets;

-
-
-

- f. Conduct transactions that are inconsistent with the transaction content declared by the - counterparty, or untrue transactions; -

-
-
-

g. Engage in any act that infringes or may infringe the Mask Network service system and data;

-
-
-

- h. Other acts that violate laws or acts that Mask Network deem inappropriate with legitimate - reasons. -

-
-

- 3). You understand and agree that if you violate the relevant laws (including but not limited to customs - and taxation regulations) or the provisions of this agreement and cause Mask Network to suffer any loss - or to be subject to any third-party claims or any administrative penalties, you should compensate Mask - Network including reasonable attorney's fees. -

-

- 4). You promise to pay Mask Network's service fees (if any) on time, otherwise Mask Network has the - right to suspend or terminate the service provided to you. -

- -
8. Privacy Policy
-

- Mask Network attaches great importance to the protection of user privacy. For related privacy protection - policies, please refer to the "Mask Network Privacy Policy" published and updated by Mask Network. -

- -
9. Disclaimer and Limitation of Liability
-

1). Mask Network is only responsible for the obligations listed in this agreement.

-

- 2). You understand and agree that, within the scope permitted by law, Mask Network can only provide - services in accordance with the current technical level and conditions. Mask Network is not responsible - for failing to provide services due to the following reasons: -

-
-

a). Force majeure such as typhoons, earthquakes, floods, lightning or terrorist attacks;

-
-
-

- b). Your mobile device's software and hardware, communication lines, and power supply lines are - faulty; -

-
-
-

- c). Viruses, Trojan horses, malicious program attacks, network congestion, system instability, - system or equipment failures, communication failures, power failures, banks, or government actions; -

-
-
-

d). Any other reasons not caused by the Mask Network.

-
-

3). Mask Network is not responsible for the following situations:

-
-

- a). Loss of digital assets caused by the user's loss of mobile device, deletion of Mask Network - without backup, deletion of wallet without backup, stolen wallet, forgetting of wallet password, - private key, mnemonic phrase and Keystore; -

-
-
-

- b).The user discloses the wallet password, private key, mnemonic phrase and keystore, or lends, - transfers or authorizes others to use his/her own mobile device or Mask Network wallet, or does not - download the Mask Network application through the official channels of Mask Network, or use Mask - Network application in other unsafe ways, and causes the loss of digital assets. -

-
-
-

- c). Loss of digital assets due to user misoperation (including but not limited to inputting the - wrong transfer address); -

-
-
-

- d). Loss of digital assets caused by misoperation because users do not understand the nature of - blockchain technology; -

-
-
-

- e). Users' transaction records copied by Mask Network derivative from those on the blockchain due to - time lag, the instability of the blockchain system, etc. -

-
-
-

f). The risks and consequences caused by users' operations on third-party DApps.

-
-

- 4). You understand that Mask Network is only used as a tool for your digital asset management. Mask - Network cannot control the quality, safety or legality of products and services, and the authenticity or - accuracy of information provided by third-party DApps, nor can we control the ability of the - counterparty to fulfill its obligations under the agreement signed with you. All your transactions in - third-party DApps are your personal behaviors, and a binding contractual relationship is established - between you and your counterparty, not with Mask Network. Mask Network reminds you that you should use - your own prudent judgment to determine the authenticity, legality and validity of the DApp and its - related information when login. You should also bear the risks arising from your transaction with any - third party. -

-

- 5). Mask Network may provide services to you and your counterparty at the same time. You agree to - explicitly exempt any actual or potential conflicts of interest from such actions that might exist, and - you are not allowed to claim that Mask Network has legal flaws when providing services, or aggravate - Mask Network's responsibility or duty of care. -

-

6). Mask Network does not guarantee:

-
-

a). The Mask Network service will meet all your needs;

-
-
-

- b). Any technology, products, services, and information you obtain through the Mask Network service - will meet your expectations; -

-
-
-

- c). The timeliness, accuracy, completeness and reliability of market transaction information of - digital assets captured from third-party exchanges by Mask Network; -

-
-
-

- d). The transaction parties on Mask Network will promptly fulfill their obligations in compliance - with the agreement with you. -

-
-

7). In any case, Mask Network’s total liability for breach of contract shall not exceed 10 USD.

-

- 8). You understand that Mask Network is only used as a tool for users to manage digital assets and - display transaction information. Mask Network does not provide services such as legal, tax or investment - advice. You should seek advice from professionals in the field of law, taxation, and investment. Mask - Network is not responsible for any investment loss and data loss that incurs during your use of our - services. -

- -
10. Entire Agreement
-

1). This agreement consists of "Mask Network Service Agreement" and "Mask Network Privacy Policy".

-

- 2). If part of the content of this agreement is deemed to be violating or invalid by the court with - jurisdiction, the validity of other content will not be affected. -

-

- 3). Any translated version of this agreement is provided for the convenience of users only, and there is - no intention to modify the terms of this agreement. If there is a conflict between the English version - of this agreement and the non-English version, the English version shall prevail. -

- -
11. Intellectual Property Protection
-

- 1). Mask Network is an application developed and owned by Mask Network. If you need to use it, please - refer to the Mask Network source code authorization agreement. -

- -
12. Application of Law and Dispute Resolution
-

- 1). The validity, interpretation, modification, execution and dispute resolution of this agreement and - its revised version shall be governed by the laws of the People's Republic of China. If there are no - relevant legal provisions, international business practices and/or industry practices shall apply. -

-

- 2). If there is any dispute or dispute between you and Mask Network, you should first settle it through - friendly negotiation. If the negotiation fails, either party can submit it to the People's Court with - jurisdiction where Mask Network is located. -

- -
13. Others
-

- 1). If you are a user outside the People's Republic of China, you need to fully understand and abide by - all relevant laws, regulations and rules regarding the use of Mask Network services in your - jurisdiction. -

-

- 2). If you encounter any problems while using Mask Network services, you can contact us by submitting - your feedback on Mask Network. -

-

- 3). You can view this agreement in Mask Network. Mask Network encourages you to check Mask Network's - service agreement every time you visit Mask Network. -

-

- 4). This agreement shall be effective from August 24, 2021. For matters not covered in this agreement, - you need to comply with Mask Network's updated service agreement terms. -

-
- - diff --git a/packages/mask/dashboard/pages/PrivacyPolicy/index.tsx b/packages/mask/dashboard/pages/PrivacyPolicy/index.tsx deleted file mode 100644 index 3042ec30f33c..000000000000 --- a/packages/mask/dashboard/pages/PrivacyPolicy/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ColumnLayout } from '../../components/RegisterFrame/ColumnLayout.js' -import { useLanguage } from '../../../shared-ui/index.js' -import { styled } from '@mui/material/styles' - -const IFrame = styled('iframe')` - border: none; - width: 100%; - min-height: 520px; -` - -const PRIVACY_POLICY_PAGE_MAPPING: Record = { - 'zh-TW': new URL('./zh.html', import.meta.url).toString(), - en: new URL('./en.html', import.meta.url).toString(), -} - -function PrivacyPolicy() { - // todo: fix language is auto - const lang = useLanguage() - const privacyPolicyURL = PRIVACY_POLICY_PAGE_MAPPING[lang] ?? PRIVACY_POLICY_PAGE_MAPPING.en - - return ( - -

QGbv;) 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