diff --git a/.i18n-codegen.json b/.i18n-codegen.json index a9395a1f305a..07d4db197bd2 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", @@ -196,284 +40,6 @@ "emitTS": true, "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", - "parser": { "type": "i18next", "contextSeparator": "$", "pluralSeparator": "_" }, - "generator": { - "type": "i18next/react-hooks", - "hooks": "useCalendarTrans", - "namespace": "io.mask.calendar", - "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/cspell.json b/cspell.json index a344509ed9ea..eb4b15b20e5c 100644 --- a/cspell.json +++ b/cspell.json @@ -109,9 +109,7 @@ "fileservice", "finalised", "flashloan", - "flowns", "forcaster", - "fortmatic", "funder", "futuna", "gapcheck", @@ -161,9 +159,6 @@ "lockdown", "logsearch", "looksrare", - "maarten", - "macbinary", - "magiceden", "maskbook", "maskbox", "masknet", @@ -354,7 +349,6 @@ "apng", "arbid", "armv", - "blocto", "bscscan", "bsct", "btcb", diff --git a/eslint.config.js b/eslint.config.js index 199be62b58d6..e7ca9a0d3d21 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,15 +22,12 @@ if (pathToFileURL(process.argv[1]).toString().includes('eslint/bin/eslint.js')) const deferPackages = [ 'wallet.ts', 'anchorme', - '@blocto/fcl', '@metamask/eth-sig-util', - '@masknet/gun-utils', 'web3-eth', 'web3-eth-accounts', 'twitter-text', 'web3-utils', 'web3-eth-abi', - '@solana/web3.js', '@project-serum/sol-wallet-adapter', // add package names here. ] @@ -465,24 +462,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 +549,6 @@ export default tseslint.config( '**/languages.ts', 'packages/contracts', 'packages/scripts', - 'packages/mask/.webpack', ], }, { @@ -596,13 +574,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 24ecefb11c95..c5fc675893d0 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,12 @@ { "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.25.0", + "version": "2.26.2", "private": true, "license": "AGPL-3.0-or-later", "scripts": { @@ -32,18 +30,14 @@ "@dimensiondev/holoflows-kit": "0.9.0-20240322092738-f9180f3", "@emotion/cache": "11.11.0", "@emotion/react": "11.11.1", - "@emotion/serialize": "1.1.2", "@emotion/styled": "11.11.0", "@masknet/kit": "0.3.0", - "@mui/base": "5.0.0-beta.38", "@mui/icons-material": "5.15.12", "@mui/lab": "5.0.0-alpha.167", "@mui/material": "5.15.12", "@mui/system": "5.15.12", "@tanstack/react-query": "^5.29.2", "@types/masknet__global-types": "workspace:^", - "@types/react": "18.2.48", - "@types/react-dom": "^18.2.18", "@typescript/lib-dom": "npm:@types/web@^0.0.143", "i18next": "^23.2.11", "knip": "^5.11.0", @@ -67,6 +61,8 @@ "@swc/core": "1.3.106", "@tanstack/eslint-plugin-query": "^5.28.11", "@types/lodash-es": "^4.17.9", + "@types/react": "18.2.48", + "@types/react-dom": "^18.2.18", "@vitest/ui": "^0.34.3", "cross-fetch": "^4.0.0", "cspell": "^7.2.0", @@ -96,11 +92,9 @@ "cross-blob": "3.0.1", "i18next-translation-parser>html-parse-stringify2": "github:locize/html-parse-stringify2#d463109433b2c49c74a081044f54b2a6a1ccad7c", "web3@0.20.7>bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", - "@protobufjs/inquire": "1.1.0", "reflect-metadata": "0.1.13", "webpack@5": "Jack-Works/webpack#528c91e564d5756e21c9c462b607d913452af770", - "@tanstack/react-query": "^5.29.2", - "@lifi/widget": "^2.10.1" + "@tanstack/react-query": "^5.29.2" }, "override comments": { "i18next-translation-parser": "see https://github.com/i18next/i18next-translation-parser/issues/11 we manually pin the commit" @@ -125,30 +119,13 @@ } }, "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" + "react-use@17.5.1": "patches/react-use@17.5.1.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/base/package.json b/packages/base/package.json index cf387360d5ba..6dd9ad3fb349 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -13,7 +13,7 @@ "types": "./dist/index.d.ts", "dependencies": { "@masknet/kit": "0.3.0", - "anchorme": "^2.1.2", + "anchorme": "^3.0.8", "pvtsutils": "^1.3.5", "tiny-secp256k1": "^2.2.3", "ts-results-es": "^4.0.0" diff --git a/packages/base/src/Identifier/identifier.ts b/packages/base/src/Identifier/identifier.ts index d05a5ad4bd5c..5a6c4e423d68 100644 --- a/packages/base/src/Identifier/identifier.ts +++ b/packages/base/src/Identifier/identifier.ts @@ -137,7 +137,7 @@ export class ECKeyIdentifier extends Identifier { ) } declare [Symbol.toStringTag]: string - static [Symbol.hasInstance](x: any): boolean { + static override [Symbol.hasInstance](x: any): boolean { return toText(x)?.startsWith('ec_key:') ?? false } static { @@ -183,7 +183,7 @@ export class PostIVIdentifier extends Identifier { return new Uint8Array(decodeArrayBuffer(x)) } declare [Symbol.toStringTag]: string - static [Symbol.hasInstance](x: any): boolean { + static override [Symbol.hasInstance](x: any): boolean { return toText(x)?.startsWith('post_iv:') ?? false } static { @@ -232,7 +232,7 @@ export class PostIdentifier extends Identifier { return this.postID } declare [Symbol.toStringTag]: string - static [Symbol.hasInstance](x: any): boolean { + static override [Symbol.hasInstance](x: any): boolean { return toText(x)?.startsWith('post:') ?? false } static { @@ -290,7 +290,7 @@ export class ProfileIdentifier extends Identifier { return `person:${this.network}/${this.userId}` } declare [Symbol.toStringTag]: string - static [Symbol.hasInstance](x: any): boolean { + static override [Symbol.hasInstance](x: any): boolean { return toText(x)?.startsWith('person:') ?? false } static { diff --git a/packages/base/src/utils/parseURLs.ts b/packages/base/src/utils/parseURLs.ts index ca566b691486..cabb78300a7d 100644 --- a/packages/base/src/utils/parseURLs.ts +++ b/packages/base/src/utils/parseURLs.ts @@ -1,4 +1,5 @@ import * as parser from /* webpackDefer: true */ 'anchorme' + export function parseURLs(text: string, requireProtocol = true) { // CJS-ESM compatibility const lib = parser.default.default || parser.default diff --git a/packages/encryption/src/encryption/Encryption.ts b/packages/encryption/src/encryption/Encryption.ts index 5ca39b42d09b..bbbc6b9f24e8 100644 --- a/packages/encryption/src/encryption/Encryption.ts +++ b/packages/encryption/src/encryption/Encryption.ts @@ -56,7 +56,7 @@ export async function encrypt(options: EncryptOptions, io: EncryptIO): Promise - authorPublic: Option> + authorPublic?: Option> } /** @internal */ @@ -93,7 +93,7 @@ async function e2e_v37( io: EncryptIO, ): Promise<[PayloadWellFormed.EndToEndEncryption, EncryptResult['e2e']]> { const { authorPublic, postIV, postKeyEncoded } = context - if (!authorPublic.isSome()) throw new Error(EncryptErrorReasons.PublicKeyNotFound) + if (!authorPublic?.isSome()) throw new Error(EncryptErrorReasons.PublicKeyNotFound) const { ephemeralKeys, getEphemeralKey } = createEphemeralKeysMap(io) const ecdhResult = v37_addReceiver(true, { ...context, getEphemeralKey }, target, io) diff --git a/packages/encryption/src/encryption/EncryptionTypes.ts b/packages/encryption/src/encryption/EncryptionTypes.ts index 195997cb021c..45a5b80262e7 100644 --- a/packages/encryption/src/encryption/EncryptionTypes.ts +++ b/packages/encryption/src/encryption/EncryptionTypes.ts @@ -15,7 +15,7 @@ export interface EncryptOptions { /** Current author who started the encryption. */ author: Option /** Public key of the current author. */ - authorPublicKey: Option> + authorPublicKey?: Option> /** Network of the encryption */ network: string /** The message to be encrypted. */ diff --git a/packages/gun-utils/.gitignore b/packages/gun-utils/.gitignore deleted file mode 100644 index 54bb81a2508f..000000000000 --- a/packages/gun-utils/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gun.js diff --git a/packages/gun-utils/README.md b/packages/gun-utils/README.md deleted file mode 100644 index 64852208c5e2..000000000000 --- a/packages/gun-utils/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @masknet/gun-utils - -This packages wrap Gun with a developer-friendly API. Before using, please make sure you have understand: - -- Gun is a decentralized graph database. -- There're many other applications are using Gun. -- Every change is visible to others (may not be correct, but in our use cases it is). -- There is no "private server", you cannot create a fresh "gun" world that only contains our data. -- DO NOT publish sensitive information on Gun. - -For more information, please access [Gun.js](https://gun.eco/). - -It is especially important to understand how Gun is modeling "Array of data". diff --git a/packages/gun-utils/builder.mjs b/packages/gun-utils/builder.mjs deleted file mode 100644 index a14f6e9563d2..000000000000 --- a/packages/gun-utils/builder.mjs +++ /dev/null @@ -1,78 +0,0 @@ -import { createRequire } from 'module' -import { readFile, writeFile } from 'fs/promises' -const require = createRequire(import.meta.url) -const files = await Promise.all( - [ - require.resolve('gun/gun.js'), - require.resolve('gun/sea'), - require.resolve('gun/lib/radix'), - require.resolve('gun/lib/radisk'), - require.resolve('gun/lib/store'), - require.resolve('gun/lib/rindexed'), - ].map((path) => readFile(path, 'utf8')), -) -function init() { - 'use strict' - // This log is required by Gun's license. - globalThis.console.log( - 'Hello wonderful person! :) Thanks for using GUN, please ask for help on http://chat.gun.eco if anything takes you longer than 5min to figure out!', - ) - function setTimeout() { - return globalThis.setTimeout.apply(globalThis, arguments) - } - const location = new URL('https://example.com') - const Object = { - prototype: globalThis.Object.prototype, - keys: globalThis.Object.keys, - create: globalThis.Object.create, - assign: globalThis.Object.assign, - } - const console = { log() {} } - const JSON = { parse: globalThis.JSON.parse, stringify: globalThis.JSON.stringify } - function String() { - return globalThis.String.apply(this, arguments) - } - String.fromCharCode = globalThis.String.fromCharCode - String.fromCodePoint = globalThis.String.fromCodePoint - const window = { - setTimeout, - location, - Object, - console, - String, - JSON, - TextEncoder: globalThis.TextEncoder, - TextDecoder: globalThis.TextDecoder, - crypto: globalThis.crypto, - get localStorage() { - return globalThis.localStorage - }, - get sessionStorage() { - return globalThis.sessionStorage - }, - indexedDB: globalThis.indexedDB, - } - try { - // Source Code Here - } finally { - return window - } -} - -const patchedSource = files - .join('\n;') - // patch .constructor != Object to .constructor == globalThis.Object - .replace(/!==?\s+Object/g, '!== globalThis.Object') - .replace(/===?\s+Object/g, '=== globalThis.Object') - // patch instanceof Object to instanceof globalThis.Object - .replace(/instanceof\s+Object/g, 'instanceof globalThis.Object') - -const result = `(() => { -${init.toString().replace('// Source Code Here', patchedSource)}; -if (!globalThis.Gun) { - globalThis.Gun = ${init.name}().Gun; -} -})(); -undefined; -` -writeFile(new URL('./gun.js', import.meta.url), result) diff --git a/packages/gun-utils/package.json b/packages/gun-utils/package.json deleted file mode 100644 index 9e1ca3157e44..000000000000 --- a/packages/gun-utils/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@masknet/gun-utils", - "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", - "scripts": { - "build": "node ./builder.mjs" - }, - "dependencies": { - "event-iterator": "^2.0.0", - "gun": "0.2020.1234" - } -} diff --git a/packages/gun-utils/src/index.ts b/packages/gun-utils/src/index.ts deleted file mode 100644 index 5bff871ccdd3..000000000000 --- a/packages/gun-utils/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { deleteGunData, getGunData, pushToGunDataArray, setGunData, subscribeGunMapData } from './utils.js' diff --git a/packages/gun-utils/src/instance.ts b/packages/gun-utils/src/instance.ts deleted file mode 100644 index 557147a4f820..000000000000 --- a/packages/gun-utils/src/instance.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { gunServers } from './server.js' -declare const Gun: typeof import('gun') - -type GunRoot = ReturnType -let gun: GunRoot | undefined -export function getGunInstance(): GunRoot { - if (gun) return gun - return (gun = createGun()) -} -export const OnCloseEvent = new Set<() => void>() - -function createGun() { - class WebSocket extends globalThis.WebSocket { - constructor(url: string | URL) { - super(url) - const abort = (this.abort = () => { - gun?.off() - gun = undefined - this.close() - for (const each of OnCloseEvent) each() - console.log('[Network/gun] WebSocket of the Gun instance is killed due to inactive.') - }) - const keepAlive = (this.keepAlive = () => { - if (this.timer) clearTimeout(this.timer) - this.timer = setTimeout(abort, 3 * 60 * 1000) - }) - this.addEventListener( - 'message', - (e) => { - // if there is no meaningful data exchange, then do not keep the connection alive. - if (typeof e.data === 'string' && e.data.length < 3) return - keepAlive() - }, - {}, - ) - } - private declare abort: () => void - private declare keepAlive: () => void - declare timer: ReturnType | undefined - override send(data: any) { - this.keepAlive() - super.send(data) - } - override get onclose() { - return null - } - override set onclose(f) {} - } - - const _ = new Gun({ - peers: [...gunServers], - localStorage: false, - radisk: true, - WebSocket, - }) - _.opt({ retry: Number.POSITIVE_INFINITY }) - return _ -} diff --git a/packages/gun-utils/src/server.ts b/packages/gun-utils/src/server.ts deleted file mode 100644 index ef8972151a52..000000000000 --- a/packages/gun-utils/src/server.ts +++ /dev/null @@ -1 +0,0 @@ -export const gunServers = ['https://gun.r2d2.to/gun'] as const diff --git a/packages/gun-utils/src/utils.ts b/packages/gun-utils/src/utils.ts deleted file mode 100644 index b3db03be0e94..000000000000 --- a/packages/gun-utils/src/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { EventIterator } from 'event-iterator' -import { getGunInstance, OnCloseEvent } from './instance.js' - -function getGunNodeFromPath(path: string[]) { - const resultNode = path.reduce((gun, path) => gun.get(path as never), getGunInstance()) - return resultNode -} -/** - * Get data from Gun. Equivalent as the following code: - * - * ```ts - * gun.get(path[0]).get(path[1])....get(path[n]).once() - * ``` - */ -export function getGunData(...path: string[]) { - return new Promise<{ [x: string]: unknown } | string | number | undefined>((resolve) => { - getGunNodeFromPath(path).once(resolve) - }) -} - -/** - * Set data on Gun. Equivalent as the following code: - * - * ```ts - * gun.get(path[0]).get(path[1])....get(path[n]).put(data) - * ``` - * @param path graph path on Gun - * @param data data to be stored - */ -export function setGunData(path: string[], data: any) { - getGunNodeFromPath(path).put(data) -} - -/** - * Delete data on Gun. Equivalent as the following code: - * - * ```ts - * gun.get(path[0]).get(path[1])....get(path[n]).put(null!) - * ``` - * @param path graph path on Gun - */ -export function deleteGunData(path: string[]) { - getGunNodeFromPath(path).put(null!) -} - -/** - * Push data to the Gun data Set (Mathematical Set) - * @param path graph path on Gun - * @param value the object to be stored - */ -export function pushToGunDataArray(path: string[], value: object) { - getGunNodeFromPath(path).set(value) -} - -/** - * Subscribe future data on Gun. - * When subscribing a Gun data Set (Mathematical Set), you will not get the immediate result back. - * - * @param path graph path on Gun - * @param isT is the data type T - * @param abortSignal the signal to stop subscribing - */ -export async function* subscribeGunMapData(path: string[], isT: (x: unknown) => x is T, abortSignal: AbortSignal) { - yield* new EventIterator((queue) => { - // gun.off() will remove ALL listener on it - let listenerClosed = false - - function stop() { - queue.stop() - listenerClosed = true - OnCloseEvent.delete(stop) - } - abortSignal.addEventListener('abort', stop) - OnCloseEvent.add(stop) - - const resultNode = getGunNodeFromPath(path) - - resultNode.map().on((data) => { - if (listenerClosed) return - if (isT(data)) queue.push(data) - }) - }) -} diff --git a/packages/gun-utils/tsconfig.json b/packages/gun-utils/tsconfig.json deleted file mode 100644 index 8bcad252c078..000000000000 --- a/packages/gun-utils/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src/", - "outDir": "./dist/", - "tsBuildInfoFile": "./dist/.tsbuildinfo" - }, - "include": ["./src"], - "references": [] -} diff --git a/packages/icons/brands/Aave.svg b/packages/icons/brands/Aave.svg index 20713454d23f..d96e513ce2e3 100644 --- a/packages/icons/brands/Aave.svg +++ b/packages/icons/brands/Aave.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Arweave.png b/packages/icons/brands/Arweave.png index 08e093a4914d..3ab9dee526a7 100644 Binary files a/packages/icons/brands/Arweave.png and b/packages/icons/brands/Arweave.png differ diff --git a/packages/icons/brands/Avalanche.svg b/packages/icons/brands/Avalanche.svg index fba39b88907d..cbf3e6e0671c 100644 --- a/packages/icons/brands/Avalanche.svg +++ b/packages/icons/brands/Avalanche.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/BSC.svg b/packages/icons/brands/BSC.svg index c85e43ca0a54..2f65a0d1eae5 100644 --- a/packages/icons/brands/BSC.svg +++ b/packages/icons/brands/BSC.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Celo.svg b/packages/icons/brands/Celo.svg index 392cf7561ad9..2ca4389234bc 100644 --- a/packages/icons/brands/Celo.svg +++ b/packages/icons/brands/Celo.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/CoinGecko.svg b/packages/icons/brands/CoinGecko.svg index ddf2f761b978..e3158fc6b511 100644 --- a/packages/icons/brands/CoinGecko.svg +++ b/packages/icons/brands/CoinGecko.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Cosmos.svg b/packages/icons/brands/Cosmos.svg index 3ea4339733e8..f202b8c2ea16 100644 --- a/packages/icons/brands/Cosmos.svg +++ b/packages/icons/brands/Cosmos.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Discord.svg b/packages/icons/brands/Discord.svg index a6ff7719a188..9a885c2c1380 100644 --- a/packages/icons/brands/Discord.svg +++ b/packages/icons/brands/Discord.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/icons/brands/EIP1577.svg b/packages/icons/brands/EIP1577.svg index 34f74b0928d7..2e0e52fd525b 100644 --- a/packages/icons/brands/EIP1577.svg +++ b/packages/icons/brands/EIP1577.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/ETH.svg b/packages/icons/brands/ETH.svg index af32e202ba5d..3c281645278b 100644 --- a/packages/icons/brands/ETH.svg +++ b/packages/icons/brands/ETH.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/EtherScan.svg b/packages/icons/brands/EtherScan.svg index bf69027a3251..9a36608e9a5d 100644 --- a/packages/icons/brands/EtherScan.svg +++ b/packages/icons/brands/EtherScan.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/FacebookColored.svg b/packages/icons/brands/FacebookColored.svg index 9ea903e4edda..ed4702876da1 100644 --- a/packages/icons/brands/FacebookColored.svg +++ b/packages/icons/brands/FacebookColored.svg @@ -1,6 +1 @@ - - - - , - - + \ No newline at end of file diff --git a/packages/icons/brands/FacebookRound.svg b/packages/icons/brands/FacebookRound.svg index 585ef7e0c693..947a4f0b6eca 100644 --- a/packages/icons/brands/FacebookRound.svg +++ b/packages/icons/brands/FacebookRound.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/FacebookRoundGray.svg b/packages/icons/brands/FacebookRoundGray.svg index 1376c76240a1..5d2fb8d680ed 100644 --- a/packages/icons/brands/FacebookRoundGray.svg +++ b/packages/icons/brands/FacebookRoundGray.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Flow.svg b/packages/icons/brands/Flow.svg deleted file mode 100644 index 0c2136f1c72b..000000000000 --- a/packages/icons/brands/Flow.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/icons/brands/Gem.svg b/packages/icons/brands/Gem.svg index cb9bd7eedecd..c5d8aa90ab6d 100644 --- a/packages/icons/brands/Gem.svg +++ b/packages/icons/brands/Gem.svg @@ -1,27 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/IPFS.svg b/packages/icons/brands/IPFS.svg index 4ba053d88367..1edcc5e29a81 100644 --- a/packages/icons/brands/IPFS.svg +++ b/packages/icons/brands/IPFS.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/InstagramRoundColored.svg b/packages/icons/brands/InstagramRoundColored.svg index c386b9c5c0cd..846cfa17febd 100644 --- a/packages/icons/brands/InstagramRoundColored.svg +++ b/packages/icons/brands/InstagramRoundColored.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Keybase.svg b/packages/icons/brands/Keybase.svg index 5027c0ffcafa..b7e294bad7e3 100644 --- a/packages/icons/brands/Keybase.svg +++ b/packages/icons/brands/Keybase.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Kusama.svg b/packages/icons/brands/Kusama.svg index 0b6caccdd506..6be096b36bb8 100644 --- a/packages/icons/brands/Kusama.svg +++ b/packages/icons/brands/Kusama.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Leaderboard.png b/packages/icons/brands/Leaderboard.png index d48aac15ada8..74c19dc1814b 100644 Binary files a/packages/icons/brands/Leaderboard.png and b/packages/icons/brands/Leaderboard.png differ diff --git a/packages/icons/brands/Lens.svg b/packages/icons/brands/Lens.svg index 51e84c8627df..520e2ff719b5 100644 --- a/packages/icons/brands/Lens.svg +++ b/packages/icons/brands/Lens.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Link3.svg b/packages/icons/brands/Link3.svg index 576391c23cfd..51ccecfb5c97 100644 --- a/packages/icons/brands/Link3.svg +++ b/packages/icons/brands/Link3.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/LooksRare.svg b/packages/icons/brands/LooksRare.svg deleted file mode 100644 index 7fb50516a6c0..000000000000 --- a/packages/icons/brands/LooksRare.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/brands/Mask.dark.svg b/packages/icons/brands/Mask.dark.svg index 8f533829836c..e2596c353f1b 100644 --- a/packages/icons/brands/Mask.dark.svg +++ b/packages/icons/brands/Mask.dark.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Mask.light.svg b/packages/icons/brands/Mask.light.svg index 66b177cb9b84..9b673710c07c 100644 --- a/packages/icons/brands/Mask.light.svg +++ b/packages/icons/brands/Mask.light.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/MaskPlaceholder.dark.svg b/packages/icons/brands/MaskPlaceholder.dark.svg deleted file mode 100644 index 15673da129df..000000000000 --- a/packages/icons/brands/MaskPlaceholder.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/brands/MaskPlaceholder.dim.svg b/packages/icons/brands/MaskPlaceholder.dim.svg deleted file mode 100644 index 0ec5ab97513d..000000000000 --- a/packages/icons/brands/MaskPlaceholder.dim.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/brands/MaskPlaceholder.light.svg b/packages/icons/brands/MaskPlaceholder.light.svg deleted file mode 100644 index 4a3dfab1113a..000000000000 --- a/packages/icons/brands/MaskPlaceholder.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/brands/MaskSquare.dark.svg b/packages/icons/brands/MaskSquare.dark.svg deleted file mode 100644 index 1a942b5acc61..000000000000 --- a/packages/icons/brands/MaskSquare.dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/icons/brands/MaskSquare.light.svg b/packages/icons/brands/MaskSquare.light.svg deleted file mode 100644 index 8fb929f12aa5..000000000000 --- a/packages/icons/brands/MaskSquare.light.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/icons/brands/MaskWallet.png b/packages/icons/brands/MaskWallet.png index 113f15a34345..df9f716c5e4a 100644 Binary files a/packages/icons/brands/MaskWallet.png and b/packages/icons/brands/MaskWallet.png differ diff --git a/packages/icons/brands/MetaMask.svg b/packages/icons/brands/MetaMask.svg index c78868613507..ceaa91db8c21 100644 --- a/packages/icons/brands/MetaMask.svg +++ b/packages/icons/brands/MetaMask.svg @@ -1,17 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Mirror.svg b/packages/icons/brands/Mirror.svg index 640472f58804..7fd57e731026 100644 --- a/packages/icons/brands/Mirror.svg +++ b/packages/icons/brands/Mirror.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/NextIDMini.dark.svg b/packages/icons/brands/NextIDMini.dark.svg index b857d60ca9ac..37ccdfb99ac1 100644 --- a/packages/icons/brands/NextIDMini.dark.svg +++ b/packages/icons/brands/NextIDMini.dark.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/NextIDMini.light.svg b/packages/icons/brands/NextIDMini.light.svg index 56984f6ec0a2..04762e27f709 100644 --- a/packages/icons/brands/NextIDMini.light.svg +++ b/packages/icons/brands/NextIDMini.light.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/PancakeSwap.png b/packages/icons/brands/PancakeSwap.png index eff8a147bd1d..376c1d357896 100644 Binary files a/packages/icons/brands/PancakeSwap.png and b/packages/icons/brands/PancakeSwap.png differ diff --git a/packages/icons/brands/PolkaDot.svg b/packages/icons/brands/PolkaDot.svg index f4ce0f701ac5..ee774a2087c8 100644 --- a/packages/icons/brands/PolkaDot.svg +++ b/packages/icons/brands/PolkaDot.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Polygon.svg b/packages/icons/brands/Polygon.svg index 3c2d6d7fabf2..9f0d32927190 100644 --- a/packages/icons/brands/Polygon.svg +++ b/packages/icons/brands/Polygon.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/PolygonScan.svg b/packages/icons/brands/PolygonScan.svg index 638e51098af7..c2ceb3b98497 100644 --- a/packages/icons/brands/PolygonScan.svg +++ b/packages/icons/brands/PolygonScan.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/RedditRound.svg b/packages/icons/brands/RedditRound.svg index 407e06599810..0a922a8555e6 100644 --- a/packages/icons/brands/RedditRound.svg +++ b/packages/icons/brands/RedditRound.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/RedditRoundGray.svg b/packages/icons/brands/RedditRoundGray.svg index ed158718f4e2..957e7d3ddf88 100644 --- a/packages/icons/brands/RedditRoundGray.svg +++ b/packages/icons/brands/RedditRoundGray.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/SimpleHash.png b/packages/icons/brands/SimpleHash.png index 42f8429bb12c..d0198fd9a1c0 100644 Binary files a/packages/icons/brands/SimpleHash.png and b/packages/icons/brands/SimpleHash.png differ diff --git a/packages/icons/brands/Solana.svg b/packages/icons/brands/Solana.svg index b74680b8659b..c613cd2a86a4 100644 --- a/packages/icons/brands/Solana.svg +++ b/packages/icons/brands/Solana.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/SushiSwap.png b/packages/icons/brands/SushiSwap.png index 0317f2a34262..ceea314cf142 100644 Binary files a/packages/icons/brands/SushiSwap.png and b/packages/icons/brands/SushiSwap.png differ diff --git a/packages/icons/brands/Sybil.png b/packages/icons/brands/Sybil.png index 7e40ac3acf47..1143e6555435 100644 Binary files a/packages/icons/brands/Sybil.png and b/packages/icons/brands/Sybil.png differ diff --git a/packages/icons/brands/TelegramRound.svg b/packages/icons/brands/TelegramRound.svg index 847fb3c20f4f..a0f51948c727 100644 --- a/packages/icons/brands/TelegramRound.svg +++ b/packages/icons/brands/TelegramRound.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/TwitterXRound.dark.svg b/packages/icons/brands/TwitterXRound.dark.svg index e6d5f12e9fc4..8f25f8a69330 100644 --- a/packages/icons/brands/TwitterXRound.dark.svg +++ b/packages/icons/brands/TwitterXRound.dark.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/TwitterXRound.light.svg b/packages/icons/brands/TwitterXRound.light.svg index 56132ceb8514..a7abe54a3edd 100644 --- a/packages/icons/brands/TwitterXRound.light.svg +++ b/packages/icons/brands/TwitterXRound.light.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/brands/Uniswap.svg b/packages/icons/brands/Uniswap.svg index db142adb265f..a513aabe8bb3 100644 --- a/packages/icons/brands/Uniswap.svg +++ b/packages/icons/brands/Uniswap.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/WETH.svg b/packages/icons/brands/WETH.svg index 33620842c5bc..8f9a69dffd73 100644 --- a/packages/icons/brands/WETH.svg +++ b/packages/icons/brands/WETH.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/X2Y2.svg b/packages/icons/brands/X2Y2.svg index d8a947df1c55..67c65b4abce2 100644 --- a/packages/icons/brands/X2Y2.svg +++ b/packages/icons/brands/X2Y2.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/YouTube.svg b/packages/icons/brands/YouTube.svg index e1c985b2378e..b3d8051cf21f 100644 --- a/packages/icons/brands/YouTube.svg +++ b/packages/icons/brands/YouTube.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/icons/brands/YouTubeGray.svg b/packages/icons/brands/YouTubeGray.svg deleted file mode 100644 index 6b4bed6a4757..000000000000 --- a/packages/icons/brands/YouTubeGray.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/brands/Zora.svg b/packages/icons/brands/Zora.svg index 5cec98a25e53..120ccf518aad 100644 --- a/packages/icons/brands/Zora.svg +++ b/packages/icons/brands/Zora.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/brands/zkScan.svg b/packages/icons/brands/zkScan.svg index 2dbc76b2eb08..8c44dab7249f 100644 --- a/packages/icons/brands/zkScan.svg +++ b/packages/icons/brands/zkScan.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/Add.svg b/packages/icons/general/Add.svg deleted file mode 100644 index 39edaa45fb1c..000000000000 --- a/packages/icons/general/Add.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Appearance.svg b/packages/icons/general/Appearance.svg deleted file mode 100644 index 6964cb63355c..000000000000 --- a/packages/icons/general/Appearance.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Appendices.svg b/packages/icons/general/Appendices.svg deleted file mode 100644 index 900919cfab40..000000000000 --- a/packages/icons/general/Appendices.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ApplicationNFT.svg b/packages/icons/general/ApplicationNFT.svg deleted file mode 100644 index 276f26ffe273..000000000000 --- a/packages/icons/general/ApplicationNFT.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/ArrowBack.svg b/packages/icons/general/ArrowBack.svg deleted file mode 100644 index f1896544b4f0..000000000000 --- a/packages/icons/general/ArrowBack.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ArrowDownRound.svg b/packages/icons/general/ArrowDownRound.svg deleted file mode 100644 index c7b882590cb0..000000000000 --- a/packages/icons/general/ArrowDownRound.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ArrowDownward.svg b/packages/icons/general/ArrowDownward.svg deleted file mode 100644 index afb1b6a5a3a0..000000000000 --- a/packages/icons/general/ArrowDownward.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/ArrowRight.svg b/packages/icons/general/ArrowRight.svg deleted file mode 100644 index d283e05d35ea..000000000000 --- a/packages/icons/general/ArrowRight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ArrowUp.svg b/packages/icons/general/ArrowUp.svg deleted file mode 100644 index 30b53d85d897..000000000000 --- a/packages/icons/general/ArrowUp.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ArrowUpRound.svg b/packages/icons/general/ArrowUpRound.svg deleted file mode 100644 index cc968dddc6a3..000000000000 --- a/packages/icons/general/ArrowUpRound.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/BackUp.svg b/packages/icons/general/BackUp.svg deleted file mode 100644 index 564271781f03..000000000000 --- a/packages/icons/general/BackUp.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/icons/general/BaseClose.dark.svg b/packages/icons/general/BaseClose.dark.svg deleted file mode 100644 index 4054ec4ccf8a..000000000000 --- a/packages/icons/general/BaseClose.dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/BaseClose.light.svg b/packages/icons/general/BaseClose.light.svg deleted file mode 100644 index 493473da9ada..000000000000 --- a/packages/icons/general/BaseClose.light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/BaseContacts.svg b/packages/icons/general/BaseContacts.svg deleted file mode 100644 index 0eb65670c374..000000000000 --- a/packages/icons/general/BaseContacts.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/BaseUpload.svg b/packages/icons/general/BaseUpload.svg deleted file mode 100644 index 18f08e5a4a83..000000000000 --- a/packages/icons/general/BaseUpload.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/BaseUser.svg b/packages/icons/general/BaseUser.svg deleted file mode 100644 index 1c8a0724b3fa..000000000000 --- a/packages/icons/general/BaseUser.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/BestTrade.svg b/packages/icons/general/BestTrade.svg deleted file mode 100644 index dabd7832d682..000000000000 --- a/packages/icons/general/BestTrade.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/BluePin.svg b/packages/icons/general/BluePin.svg deleted file mode 100644 index 81a5033d3847..000000000000 --- a/packages/icons/general/BluePin.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/BorderedSuccess.svg b/packages/icons/general/BorderedSuccess.svg index b89c4a1fc2c0..443db578c3f0 100644 --- a/packages/icons/general/BorderedSuccess.svg +++ b/packages/icons/general/BorderedSuccess.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/BusyWalletNav.svg b/packages/icons/general/BusyWalletNav.svg deleted file mode 100644 index b3cceb356789..000000000000 --- a/packages/icons/general/BusyWalletNav.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Buy.svg b/packages/icons/general/Buy.svg deleted file mode 100644 index b7523834b845..000000000000 --- a/packages/icons/general/Buy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/general/CNY.svg b/packages/icons/general/CNY.svg deleted file mode 100644 index 149f65bdf9b9..000000000000 --- a/packages/icons/general/CNY.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Cached.svg b/packages/icons/general/Cached.svg deleted file mode 100644 index 52c02247d83e..000000000000 --- a/packages/icons/general/Cached.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Card.svg b/packages/icons/general/Card.svg deleted file mode 100644 index 348083cdd951..000000000000 --- a/packages/icons/general/Card.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/general/CheckboxBorder.svg b/packages/icons/general/CheckboxBorder.svg deleted file mode 100644 index 2fa52af18add..000000000000 --- a/packages/icons/general/CheckboxBorder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ChevronUp.svg b/packages/icons/general/ChevronUp.svg deleted file mode 100644 index 4987b6b489cc..000000000000 --- a/packages/icons/general/ChevronUp.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Chrome.svg b/packages/icons/general/Chrome.svg deleted file mode 100644 index bed209ec6e0e..000000000000 --- a/packages/icons/general/Chrome.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/CircleClose.svg b/packages/icons/general/CircleClose.svg deleted file mode 100644 index 3616090bcd09..000000000000 --- a/packages/icons/general/CircleClose.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/CircleWarning.svg b/packages/icons/general/CircleWarning.svg index b19d6bf6bdf8..906d0445f011 100644 --- a/packages/icons/general/CircleWarning.svg +++ b/packages/icons/general/CircleWarning.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Clear.dark.svg b/packages/icons/general/Clear.dark.svg deleted file mode 100644 index 584fc7df9e93..000000000000 --- a/packages/icons/general/Clear.dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Clear.light.svg b/packages/icons/general/Clear.light.svg deleted file mode 100644 index 029122701635..000000000000 --- a/packages/icons/general/Clear.light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Cloud.svg b/packages/icons/general/Cloud.svg deleted file mode 100644 index 2228db77bb57..000000000000 --- a/packages/icons/general/Cloud.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/icons/general/CloudBackup.png b/packages/icons/general/CloudBackup.png deleted file mode 100644 index 25eb9a4afd1c..000000000000 Binary files a/packages/icons/general/CloudBackup.png and /dev/null differ diff --git a/packages/icons/general/CloudBackup2.svg b/packages/icons/general/CloudBackup2.svg deleted file mode 100644 index f59b4958e711..000000000000 --- a/packages/icons/general/CloudBackup2.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/icons/general/CloudLink.png b/packages/icons/general/CloudLink.png deleted file mode 100644 index 57758111cc9b..000000000000 Binary files a/packages/icons/general/CloudLink.png and /dev/null differ diff --git a/packages/icons/general/Collectible.svg b/packages/icons/general/Collectible.svg deleted file mode 100644 index 8a9baa1f7c67..000000000000 --- a/packages/icons/general/Collectible.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Comeback.svg b/packages/icons/general/Comeback.svg deleted file mode 100644 index 190cf65a1216..000000000000 --- a/packages/icons/general/Comeback.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Connect.svg b/packages/icons/general/Connect.svg deleted file mode 100644 index 09c89adcc857..000000000000 --- a/packages/icons/general/Connect.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Contacts.svg b/packages/icons/general/Contacts.svg deleted file mode 100644 index 44a908cb8652..000000000000 --- a/packages/icons/general/Contacts.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Currency.svg b/packages/icons/general/Currency.svg deleted file mode 100644 index fcf20ed764f7..000000000000 --- a/packages/icons/general/Currency.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Dark.svg b/packages/icons/general/Dark.svg deleted file mode 100644 index ad0c37114a4e..000000000000 --- a/packages/icons/general/Dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/DefaultToken.dark.svg b/packages/icons/general/DefaultToken.dark.svg deleted file mode 100644 index fcdd69bb2660..000000000000 --- a/packages/icons/general/DefaultToken.dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/DefaultToken.dim.svg b/packages/icons/general/DefaultToken.dim.svg deleted file mode 100644 index 728f8adb1ba9..000000000000 --- a/packages/icons/general/DefaultToken.dim.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/DefaultToken.light.svg b/packages/icons/general/DefaultToken.light.svg deleted file mode 100644 index 86a6e3c39cb1..000000000000 --- a/packages/icons/general/DefaultToken.light.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/icons/general/Delete.svg b/packages/icons/general/Delete.svg deleted file mode 100644 index b37067027897..000000000000 --- a/packages/icons/general/Delete.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Disconnect.svg b/packages/icons/general/Disconnect.svg deleted file mode 100644 index 314d0cfef300..000000000000 --- a/packages/icons/general/Disconnect.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Document.svg b/packages/icons/general/Document.svg deleted file mode 100644 index efafc241510f..000000000000 --- a/packages/icons/general/Document.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Documents.svg b/packages/icons/general/Documents.svg deleted file mode 100644 index 72cf845b1010..000000000000 --- a/packages/icons/general/Documents.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Download.svg b/packages/icons/general/Download.svg deleted file mode 100644 index 0d8ccdb3d1e6..000000000000 --- a/packages/icons/general/Download.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Download2.svg b/packages/icons/general/Download2.svg deleted file mode 100644 index 50d80d1f7c45..000000000000 --- a/packages/icons/general/Download2.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Drop.svg b/packages/icons/general/Drop.svg deleted file mode 100644 index 1d2cb3f91930..000000000000 --- a/packages/icons/general/Drop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Dump.svg b/packages/icons/general/Dump.svg deleted file mode 100644 index 7105471ab9d3..000000000000 --- a/packages/icons/general/Dump.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/ETHSymbol.svg b/packages/icons/general/ETHSymbol.svg deleted file mode 100644 index 95ffa477ece0..000000000000 --- a/packages/icons/general/ETHSymbol.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Edit.svg b/packages/icons/general/Edit.svg deleted file mode 100644 index 9542464b7ca4..000000000000 --- a/packages/icons/general/Edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Empty.png b/packages/icons/general/Empty.png deleted file mode 100644 index c8fc0976b8ea..000000000000 Binary files a/packages/icons/general/Empty.png and /dev/null differ diff --git a/packages/icons/general/EmptySimple.dark.svg b/packages/icons/general/EmptySimple.dark.svg index 12bffbd9e998..f8bb9d004df0 100644 --- a/packages/icons/general/EmptySimple.dark.svg +++ b/packages/icons/general/EmptySimple.dark.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/EmptySimple.light.svg b/packages/icons/general/EmptySimple.light.svg index 54335adb4eb2..d0dec141e0cb 100644 --- a/packages/icons/general/EmptySimple.light.svg +++ b/packages/icons/general/EmptySimple.light.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/EncryptedFiles.svg b/packages/icons/general/EncryptedFiles.svg deleted file mode 100644 index 72d56559ade0..000000000000 --- a/packages/icons/general/EncryptedFiles.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/general/Europe.svg b/packages/icons/general/Europe.svg index 79a92601911a..cda591860b4d 100644 --- a/packages/icons/general/Europe.svg +++ b/packages/icons/general/Europe.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Eye.dark.svg b/packages/icons/general/Eye.dark.svg deleted file mode 100644 index 673094806982..000000000000 --- a/packages/icons/general/Eye.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Eye.light.svg b/packages/icons/general/Eye.light.svg deleted file mode 100644 index 461d07861959..000000000000 --- a/packages/icons/general/Eye.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/EyeColor.svg b/packages/icons/general/EyeColor.svg deleted file mode 100644 index 2c4525ead559..000000000000 --- a/packages/icons/general/EyeColor.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/EyeOff.dark.svg b/packages/icons/general/EyeOff.dark.svg deleted file mode 100644 index b58cf24118b1..000000000000 --- a/packages/icons/general/EyeOff.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/EyeOff.light.svg b/packages/icons/general/EyeOff.light.svg deleted file mode 100644 index 710fbe41056e..000000000000 --- a/packages/icons/general/EyeOff.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Facebook.svg b/packages/icons/general/Facebook.svg deleted file mode 100644 index c58fa82ab144..000000000000 --- a/packages/icons/general/Facebook.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/File.svg b/packages/icons/general/File.svg deleted file mode 100644 index fcebf64d6441..000000000000 --- a/packages/icons/general/File.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/general/FileMessage.svg b/packages/icons/general/FileMessage.svg deleted file mode 100644 index 35446725a8af..000000000000 --- a/packages/icons/general/FileMessage.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/FillSuccess.svg b/packages/icons/general/FillSuccess.svg index 7c06d9a73c90..8f0579da80c8 100644 --- a/packages/icons/general/FillSuccess.svg +++ b/packages/icons/general/FillSuccess.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Filter.svg b/packages/icons/general/Filter.svg deleted file mode 100644 index fa0f02391b50..000000000000 --- a/packages/icons/general/Filter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Folder.svg b/packages/icons/general/Folder.svg deleted file mode 100644 index 1f8e11d6e65d..000000000000 --- a/packages/icons/general/Folder.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Gas.svg b/packages/icons/general/Gas.svg deleted file mode 100644 index d3cc424e1739..000000000000 --- a/packages/icons/general/Gas.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/GasStation.svg b/packages/icons/general/GasStation.svg deleted file mode 100644 index 8bc87f78b4ca..000000000000 --- a/packages/icons/general/GasStation.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Gear.dark.svg b/packages/icons/general/Gear.dark.svg deleted file mode 100644 index a73f6765d861..000000000000 --- a/packages/icons/general/Gear.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Gear.light.svg b/packages/icons/general/Gear.light.svg deleted file mode 100644 index a6627c216e9a..000000000000 --- a/packages/icons/general/Gear.light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/GearSettings.svg b/packages/icons/general/GearSettings.svg deleted file mode 100644 index 055c8587464c..000000000000 --- a/packages/icons/general/GearSettings.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Globe.svg b/packages/icons/general/Globe.svg deleted file mode 100644 index c7397223c4d7..000000000000 --- a/packages/icons/general/Globe.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/GrayMasks.svg b/packages/icons/general/GrayMasks.svg deleted file mode 100644 index 76517e5fdd40..000000000000 --- a/packages/icons/general/GrayMasks.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/HKD.svg b/packages/icons/general/HKD.svg deleted file mode 100644 index aab365eac4a9..000000000000 --- a/packages/icons/general/HKD.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/HamburgerMenu.svg b/packages/icons/general/HamburgerMenu.svg deleted file mode 100644 index fa7c61d61cd0..000000000000 --- a/packages/icons/general/HamburgerMenu.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Heart.svg b/packages/icons/general/Heart.svg index 7750794c3a3f..785f4d563520 100644 --- a/packages/icons/general/Heart.svg +++ b/packages/icons/general/Heart.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/icons/general/HongKong.svg b/packages/icons/general/HongKong.svg index 0c737cf1d459..486f2829eb37 100644 --- a/packages/icons/general/HongKong.svg +++ b/packages/icons/general/HongKong.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Identity.dark.svg b/packages/icons/general/Identity.dark.svg deleted file mode 100644 index 650d850c0f09..000000000000 --- a/packages/icons/general/Identity.dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Identity.light.svg b/packages/icons/general/Identity.light.svg deleted file mode 100644 index c0104dd31303..000000000000 --- a/packages/icons/general/Identity.light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Info.dark.svg b/packages/icons/general/Info.dark.svg index 7ee511f575cb..05c053f0dd64 100644 --- a/packages/icons/general/Info.dark.svg +++ b/packages/icons/general/Info.dark.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/Interaction.svg b/packages/icons/general/Interaction.svg deleted file mode 100644 index 91a4e93fe189..000000000000 --- a/packages/icons/general/Interaction.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/icons/general/InteractionCircle.svg b/packages/icons/general/InteractionCircle.svg deleted file mode 100644 index 76621d075985..000000000000 --- a/packages/icons/general/InteractionCircle.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/general/JPY.svg b/packages/icons/general/JPY.svg deleted file mode 100644 index abb690dc0f16..000000000000 --- a/packages/icons/general/JPY.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Japan.svg b/packages/icons/general/Japan.svg index 8bc5e38204f2..d9c0d8672c61 100644 --- a/packages/icons/general/Japan.svg +++ b/packages/icons/general/Japan.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/KeySquare.svg b/packages/icons/general/KeySquare.svg deleted file mode 100644 index 582e189d9104..000000000000 --- a/packages/icons/general/KeySquare.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/LinearCalendar.dark.svg b/packages/icons/general/LinearCalendar.dark.svg index b4fec0e7ebcd..1e3b95a3365d 100644 --- a/packages/icons/general/LinearCalendar.dark.svg +++ b/packages/icons/general/LinearCalendar.dark.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/LinearCalendar.light.svg b/packages/icons/general/LinearCalendar.light.svg index 47ec02115621..252d227942cc 100644 --- a/packages/icons/general/LinearCalendar.light.svg +++ b/packages/icons/general/LinearCalendar.light.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Link.svg b/packages/icons/general/Link.svg deleted file mode 100644 index df39698a2062..000000000000 --- a/packages/icons/general/Link.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Loader.svg b/packages/icons/general/Loader.svg deleted file mode 100644 index 3211fa9c4d69..000000000000 --- a/packages/icons/general/Loader.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/LoadingBase.dark.svg b/packages/icons/general/LoadingBase.dark.svg deleted file mode 100644 index cc32d990ecda..000000000000 --- a/packages/icons/general/LoadingBase.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/LoadingBase.light.svg b/packages/icons/general/LoadingBase.light.svg deleted file mode 100644 index 1c954b717c30..000000000000 --- a/packages/icons/general/LoadingBase.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/LocalBackup.png b/packages/icons/general/LocalBackup.png deleted file mode 100644 index 0e85735e9ac3..000000000000 Binary files a/packages/icons/general/LocalBackup.png and /dev/null differ diff --git a/packages/icons/general/Lock.svg b/packages/icons/general/Lock.svg deleted file mode 100644 index 95c3b16efb9c..000000000000 --- a/packages/icons/general/Lock.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/MaskAvatar.light.svg b/packages/icons/general/MaskAvatar.light.svg index 20b43a695d2d..77fd6186119b 100644 --- a/packages/icons/general/MaskAvatar.light.svg +++ b/packages/icons/general/MaskAvatar.light.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/icons/general/MaskInMinds.svg b/packages/icons/general/MaskInMinds.svg deleted file mode 100644 index 781f73555721..000000000000 --- a/packages/icons/general/MaskInMinds.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/MaskMe.svg b/packages/icons/general/MaskMe.svg deleted file mode 100644 index 2b2ed2eb0937..000000000000 --- a/packages/icons/general/MaskMe.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Medal.svg b/packages/icons/general/Medal.svg deleted file mode 100644 index 29895dbec58e..000000000000 --- a/packages/icons/general/Medal.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Message.svg b/packages/icons/general/Message.svg deleted file mode 100644 index 086e99b5f84d..000000000000 --- a/packages/icons/general/Message.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Messages.svg b/packages/icons/general/Messages.svg deleted file mode 100644 index f560ecc9258b..000000000000 --- a/packages/icons/general/Messages.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Minus.svg b/packages/icons/general/Minus.svg deleted file mode 100644 index 134d2d17d5fe..000000000000 --- a/packages/icons/general/Minus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Mnemonic.svg b/packages/icons/general/Mnemonic.svg deleted file mode 100644 index b9662ac73aa6..000000000000 --- a/packages/icons/general/Mnemonic.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/More.svg b/packages/icons/general/More.svg deleted file mode 100644 index 66cb67a57aec..000000000000 --- a/packages/icons/general/More.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/NFTRedPacket.svg b/packages/icons/general/NFTRedPacket.svg deleted file mode 100644 index 7336ada6b113..000000000000 --- a/packages/icons/general/NFTRedPacket.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/NextIdAvatar.dark.svg b/packages/icons/general/NextIdAvatar.dark.svg deleted file mode 100644 index 865fe2ff54f2..000000000000 --- a/packages/icons/general/NextIdAvatar.dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/NextIdAvatar.light.svg b/packages/icons/general/NextIdAvatar.light.svg deleted file mode 100644 index 4169b70fc1cf..000000000000 --- a/packages/icons/general/NextIdAvatar.light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/NextIdPersonaWarning.svg b/packages/icons/general/NextIdPersonaWarning.svg deleted file mode 100644 index ee377d5f6ad5..000000000000 --- a/packages/icons/general/NextIdPersonaWarning.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/OutlinedMask.svg b/packages/icons/general/OutlinedMask.svg deleted file mode 100644 index c9679fda8208..000000000000 --- a/packages/icons/general/OutlinedMask.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/PersonasOutline.svg b/packages/icons/general/PersonasOutline.svg deleted file mode 100644 index b7b8ff0ecca5..000000000000 --- a/packages/icons/general/PersonasOutline.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/Pin.dark.svg b/packages/icons/general/Pin.dark.svg deleted file mode 100644 index f448a68a60a4..000000000000 --- a/packages/icons/general/Pin.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Pin.light.svg b/packages/icons/general/Pin.light.svg deleted file mode 100644 index ff76fc48eea5..000000000000 --- a/packages/icons/general/Pin.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Play.dark.svg b/packages/icons/general/Play.dark.svg deleted file mode 100644 index 8a38d26bcaa9..000000000000 --- a/packages/icons/general/Play.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Play.svg b/packages/icons/general/Play.svg deleted file mode 100644 index 8aa9f1936ebb..000000000000 --- a/packages/icons/general/Play.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Plugins.svg b/packages/icons/general/Plugins.svg deleted file mode 100644 index 4c1705296b46..000000000000 --- a/packages/icons/general/Plugins.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Plus.svg b/packages/icons/general/Plus.svg deleted file mode 100644 index 58e72344e2e6..000000000000 --- a/packages/icons/general/Plus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/PopupClose.svg b/packages/icons/general/PopupClose.svg deleted file mode 100644 index 0632addbda28..000000000000 --- a/packages/icons/general/PopupClose.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/PopupRestore.svg b/packages/icons/general/PopupRestore.svg deleted file mode 100644 index a88bade20c35..000000000000 --- a/packages/icons/general/PopupRestore.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/PopupTrash.svg b/packages/icons/general/PopupTrash.svg deleted file mode 100644 index 77314e2ef230..000000000000 --- a/packages/icons/general/PopupTrash.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/PrimaryInfo.svg b/packages/icons/general/PrimaryInfo.svg index 2550fd70022f..123644db76a6 100644 --- a/packages/icons/general/PrimaryInfo.svg +++ b/packages/icons/general/PrimaryInfo.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/PublicKey.dark.svg b/packages/icons/general/PublicKey.dark.svg deleted file mode 100644 index 22dae03c98fa..000000000000 --- a/packages/icons/general/PublicKey.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/PublicKey.light.svg b/packages/icons/general/PublicKey.light.svg deleted file mode 100644 index 93640fd7aee3..000000000000 --- a/packages/icons/general/PublicKey.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/PublicKey2.svg b/packages/icons/general/PublicKey2.svg deleted file mode 100644 index bb770cd720f5..000000000000 --- a/packages/icons/general/PublicKey2.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/RadioButtonChecked.svg b/packages/icons/general/RadioButtonChecked.svg index c9ad5db75c3b..aa4781e50a4a 100644 --- a/packages/icons/general/RadioButtonChecked.svg +++ b/packages/icons/general/RadioButtonChecked.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/RadioNo.dark.svg b/packages/icons/general/RadioNo.dark.svg deleted file mode 100644 index 92248f5f93fa..000000000000 --- a/packages/icons/general/RadioNo.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/RadioNo.light.svg b/packages/icons/general/RadioNo.light.svg deleted file mode 100644 index 86f771c15ba8..000000000000 --- a/packages/icons/general/RadioNo.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ReceiveColorful.svg b/packages/icons/general/ReceiveColorful.svg deleted file mode 100644 index e2ddd17c4076..000000000000 --- a/packages/icons/general/ReceiveColorful.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/icons/general/RedPacket.svg b/packages/icons/general/RedPacket.svg index d3fb8eab4cfd..399c93097d0f 100644 --- a/packages/icons/general/RedPacket.svg +++ b/packages/icons/general/RedPacket.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/Refresh.svg b/packages/icons/general/Refresh.svg deleted file mode 100644 index e409aa88bea0..000000000000 --- a/packages/icons/general/Refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Restore.svg b/packages/icons/general/Restore.svg deleted file mode 100644 index b8e5829c60ab..000000000000 --- a/packages/icons/general/Restore.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/RestoreColorful.svg b/packages/icons/general/RestoreColorful.svg deleted file mode 100644 index df7ad7930b85..000000000000 --- a/packages/icons/general/RestoreColorful.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/general/ResultNo.svg b/packages/icons/general/ResultNo.svg index 49f529061cba..9f4d83b02e63 100644 --- a/packages/icons/general/ResultNo.svg +++ b/packages/icons/general/ResultNo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/icons/general/ResultYes.svg b/packages/icons/general/ResultYes.svg index 32b83adecd11..fc495d92560b 100644 --- a/packages/icons/general/ResultYes.svg +++ b/packages/icons/general/ResultYes.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Retweet.dark.svg b/packages/icons/general/Retweet.dark.svg deleted file mode 100644 index fdf2151c5356..000000000000 --- a/packages/icons/general/Retweet.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Retweet.light.svg b/packages/icons/general/Retweet.light.svg deleted file mode 100644 index ccb2bfee711e..000000000000 --- a/packages/icons/general/Retweet.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Right.svg b/packages/icons/general/Right.svg deleted file mode 100644 index e01dd2f7a4ad..000000000000 --- a/packages/icons/general/Right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Risk.svg b/packages/icons/general/Risk.svg deleted file mode 100644 index e7d755adabdb..000000000000 --- a/packages/icons/general/Risk.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/SecurityRisk.svg b/packages/icons/general/SecurityRisk.svg deleted file mode 100644 index 958f90bc3091..000000000000 --- a/packages/icons/general/SecurityRisk.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/SecurityWarning.svg b/packages/icons/general/SecurityWarning.svg deleted file mode 100644 index 8170ac6bc138..000000000000 --- a/packages/icons/general/SecurityWarning.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Self.svg b/packages/icons/general/Self.svg deleted file mode 100644 index 2b2ed2eb0937..000000000000 --- a/packages/icons/general/Self.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Send.svg b/packages/icons/general/Send.svg deleted file mode 100644 index 67a648246b5f..000000000000 --- a/packages/icons/general/Send.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/SendColorful.svg b/packages/icons/general/SendColorful.svg deleted file mode 100644 index 8686e1e4660a..000000000000 --- a/packages/icons/general/SendColorful.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/general/Setting.svg b/packages/icons/general/Setting.svg deleted file mode 100644 index f3498712e5ed..000000000000 --- a/packages/icons/general/Setting.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Settings2.svg b/packages/icons/general/Settings2.svg deleted file mode 100644 index 95cc2b51bc45..000000000000 --- a/packages/icons/general/Settings2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/SharpMask.svg b/packages/icons/general/SharpMask.svg deleted file mode 100644 index 6803f7eb4714..000000000000 --- a/packages/icons/general/SharpMask.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/ShutDown.svg b/packages/icons/general/ShutDown.svg deleted file mode 100644 index e7486b36a9b4..000000000000 --- a/packages/icons/general/ShutDown.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/SignUpAccount.png b/packages/icons/general/SignUpAccount.png deleted file mode 100644 index d239e15d9631..000000000000 Binary files a/packages/icons/general/SignUpAccount.png and /dev/null differ diff --git a/packages/icons/general/SmartPay.svg b/packages/icons/general/SmartPay.svg index ecb26fc455b9..1baf6af0129a 100644 --- a/packages/icons/general/SmartPay.svg +++ b/packages/icons/general/SmartPay.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/Star.svg b/packages/icons/general/Star.svg deleted file mode 100644 index 3f3f3255fbf5..000000000000 --- a/packages/icons/general/Star.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Success.svg b/packages/icons/general/Success.svg deleted file mode 100644 index 3a7a24144262..000000000000 --- a/packages/icons/general/Success.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/SuccessForSnackBar.svg b/packages/icons/general/SuccessForSnackBar.svg index 5d3e3799c55f..e2b2d5115b42 100644 --- a/packages/icons/general/SuccessForSnackBar.svg +++ b/packages/icons/general/SuccessForSnackBar.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Sun.svg b/packages/icons/general/Sun.svg deleted file mode 100644 index 71f008985319..000000000000 --- a/packages/icons/general/Sun.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Swap.svg b/packages/icons/general/Swap.svg deleted file mode 100644 index 1fe2e3b2daff..000000000000 --- a/packages/icons/general/Swap.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/SwapColorful.svg b/packages/icons/general/SwapColorful.svg deleted file mode 100644 index f8917b64ca8f..000000000000 --- a/packages/icons/general/SwapColorful.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/SwitchLogo.dark.svg b/packages/icons/general/SwitchLogo.dark.svg deleted file mode 100644 index ba9e0080b051..000000000000 --- a/packages/icons/general/SwitchLogo.dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/SwitchLogo.light.svg b/packages/icons/general/SwitchLogo.light.svg deleted file mode 100644 index 45304a6a753f..000000000000 --- a/packages/icons/general/SwitchLogo.light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/TelegramRoundGray.svg b/packages/icons/general/TelegramRoundGray.svg deleted file mode 100644 index 0ab98d707faf..000000000000 --- a/packages/icons/general/TelegramRoundGray.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Tick.svg b/packages/icons/general/Tick.svg deleted file mode 100644 index aea497e2c170..000000000000 --- a/packages/icons/general/Tick.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Time.svg b/packages/icons/general/Time.svg deleted file mode 100644 index 7870d04ee69c..000000000000 --- a/packages/icons/general/Time.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/icons/general/Tips.svg b/packages/icons/general/Tips.svg deleted file mode 100644 index 24cbaee9d301..000000000000 --- a/packages/icons/general/Tips.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/general/TransactionFailed.svg b/packages/icons/general/TransactionFailed.svg index d91f0f3e7d95..c7449917bbad 100644 --- a/packages/icons/general/TransactionFailed.svg +++ b/packages/icons/general/TransactionFailed.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Trash.svg b/packages/icons/general/Trash.svg deleted file mode 100644 index eda7e67b7091..000000000000 --- a/packages/icons/general/Trash.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/TrashLine.svg b/packages/icons/general/TrashLine.svg index 86dab6d6b78a..127647c6912f 100644 --- a/packages/icons/general/TrashLine.svg +++ b/packages/icons/general/TrashLine.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/Tutorial.svg b/packages/icons/general/Tutorial.svg deleted file mode 100644 index 9e432a3381f1..000000000000 --- a/packages/icons/general/Tutorial.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/Twitter.svg b/packages/icons/general/Twitter.svg deleted file mode 100644 index 033971e5fcda..000000000000 --- a/packages/icons/general/Twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/TwitterStroke.svg b/packages/icons/general/TwitterStroke.svg deleted file mode 100644 index 0f96fce5bc24..000000000000 --- a/packages/icons/general/TwitterStroke.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/general/TxIn.svg b/packages/icons/general/TxIn.svg deleted file mode 100644 index 0d8ccdb3d1e6..000000000000 --- a/packages/icons/general/TxIn.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/TxOut.svg b/packages/icons/general/TxOut.svg deleted file mode 100644 index 08be6e57cab0..000000000000 --- a/packages/icons/general/TxOut.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/USD.svg b/packages/icons/general/USD.svg deleted file mode 100644 index b0d084b2e19e..000000000000 --- a/packages/icons/general/USD.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/general/Upload.svg b/packages/icons/general/Upload.svg deleted file mode 100644 index 9e6bd8d77210..000000000000 --- a/packages/icons/general/Upload.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/general/User.svg b/packages/icons/general/User.svg deleted file mode 100644 index 7c73447d78f9..000000000000 --- a/packages/icons/general/User.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/general/WalletNav.svg b/packages/icons/general/WalletNav.svg deleted file mode 100644 index f3ec903661a7..000000000000 --- a/packages/icons/general/WalletNav.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/WalletSetting.svg b/packages/icons/general/WalletSetting.svg deleted file mode 100644 index 2ccc84e9be34..000000000000 --- a/packages/icons/general/WalletSetting.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/general/Warning.svg b/packages/icons/general/Warning.svg index b6afdadb01a2..578af89d9431 100644 --- a/packages/icons/general/Warning.svg +++ b/packages/icons/general/Warning.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/WarningTriangle.svg b/packages/icons/general/WarningTriangle.svg index 8d7a38640ef9..c2f7fccbe29d 100644 --- a/packages/icons/general/WarningTriangle.svg +++ b/packages/icons/general/WarningTriangle.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/icons/general/Web.svg b/packages/icons/general/Web.svg deleted file mode 100644 index 5b0b621e1eeb..000000000000 --- a/packages/icons/general/Web.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/general/WebBlack.dark.svg b/packages/icons/general/WebBlack.dark.svg index 8842eae50b96..54d18239394a 100644 --- a/packages/icons/general/WebBlack.dark.svg +++ b/packages/icons/general/WebBlack.dark.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/WebBlack.light.svg b/packages/icons/general/WebBlack.light.svg index 2c3235b7ed41..4a8dec1b1523 100644 --- a/packages/icons/general/WebBlack.light.svg +++ b/packages/icons/general/WebBlack.light.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/candle.svg b/packages/icons/general/candle.svg index 5e5f8e5d7e2a..195b406ebd9b 100644 --- a/packages/icons/general/candle.svg +++ b/packages/icons/general/candle.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/general/decrease.svg b/packages/icons/general/decrease.svg deleted file mode 100644 index 8404723bd632..000000000000 --- a/packages/icons/general/decrease.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/icon-generated-as-jsx.js b/packages/icons/icon-generated-as-jsx.js index 01ea2c11035f..423af7007367 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', [ { @@ -199,11 +191,6 @@ export const Firefly = /*#__PURE__*/ __createIcon('Firefly', [ u: () => new URL('./brands/Firefly.light.svg', import.meta.url).href, }, ]) -export const Flow = /*#__PURE__*/ __createIcon('Flow', [ - { - u: () => new URL('./brands/Flow.svg', import.meta.url).href, - }, -]) export const Gem = /*#__PURE__*/ __createIcon('Gem', [ { u: () => new URL('./brands/Gem.svg', import.meta.url).href, @@ -274,11 +261,6 @@ export const Link3 = /*#__PURE__*/ __createIcon('Link3', [ u: () => new URL('./brands/Link3.svg', import.meta.url).href, }, ]) -export const LooksRare = /*#__PURE__*/ __createIcon('LooksRare', [ - { - u: () => new URL('./brands/LooksRare.svg', import.meta.url).href, - }, -]) export const Mask = /*#__PURE__*/ __createIcon('Mask', [ { c: ['dark'], @@ -309,30 +291,6 @@ export const MaskGrey = /*#__PURE__*/ __createIcon('MaskGrey', [ u: () => new URL('./brands/MaskGrey.light.svg', import.meta.url).href, }, ]) -export const MaskPlaceholder = /*#__PURE__*/ __createIcon('MaskPlaceholder', [ - { - c: ['dark'], - u: () => new URL('./brands/MaskPlaceholder.dark.svg', import.meta.url).href, - }, - { - c: ['dim'], - u: () => new URL('./brands/MaskPlaceholder.dim.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./brands/MaskPlaceholder.light.svg', import.meta.url).href, - }, -]) -export const MaskSquare = /*#__PURE__*/ __createIcon('MaskSquare', [ - { - c: ['dark'], - u: () => new URL('./brands/MaskSquare.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./brands/MaskSquare.light.svg', import.meta.url).href, - }, -]) export const MaskText = /*#__PURE__*/ __createIcon('MaskText', [ { u: () => new URL('./brands/MaskText.svg', import.meta.url).href, @@ -582,11 +540,6 @@ export const YouTube = /*#__PURE__*/ __createIcon('YouTube', [ u: () => new URL('./brands/YouTube.svg', import.meta.url).href, }, ]) -export const YouTubeGray = /*#__PURE__*/ __createIcon('YouTubeGray', [ - { - u: () => new URL('./brands/YouTubeGray.svg', import.meta.url).href, - }, -]) export const Zerion = /*#__PURE__*/ __createIcon('Zerion', [ { u: () => new URL('./brands/Zerion.svg', import.meta.url).href, @@ -616,37 +569,6 @@ export const Zora = /*#__PURE__*/ __createIcon('Zora', [ u: () => new URL('./brands/Zora.svg', import.meta.url).href, }, ]) -export const Add = /*#__PURE__*/ __createIcon('Add', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12 4.8a7.2 7.2 0 1 0 0 14.4 7.2 7.2 0 0 0 0-14.4ZM3.2 12a8.8 8.8 0 1 1 17.6 0 8.8 8.8 0 0 1-17.6 0Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12 8a.8.8 0 0 1 .8.8v6.4a.8.8 0 0 1-1.6 0V8.8A.8.8 0 0 1 12 8Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M8 12a.8.8 0 0 1 .8-.8h6.4a.8.8 0 0 1 0 1.6H8.8A.8.8 0 0 1 8 12Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) export const AddUser = /*#__PURE__*/ __createIcon('AddUser', [ { j: () => @@ -684,43 +606,6 @@ export const America = /*#__PURE__*/ __createIcon('America', [ u: () => new URL('./general/America.svg', import.meta.url).href, }, ]) -export const Appearance = /*#__PURE__*/ __createIcon('Appearance', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm10.09 13.908-4.7-2.286 3.707-3.503L9.692 3l4.576 2.538 5.072-.878-.879 5.071L21 14.307l-5.118.598-3.504 3.705-2.286-4.702Zm-2.262.658 1.608 1.609-4.825 4.826-1.609-1.609 4.826-4.826Z', - }), - }), - s: true, - }, -]) -export const Appendices = /*#__PURE__*/ __createIcon('Appendices', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12.537 4.226c-.486 0-.953.193-1.297.537l-5.744 5.744A3.085 3.085 0 1 0 9.86 14.87l5.744-5.744a.667.667 0 0 1 .942.943l-5.743 5.744a4.42 4.42 0 0 1-6.25-6.25l5.744-5.743a3.168 3.168 0 0 1 4.48 4.48l-5.75 5.744a1.918 1.918 0 0 1-2.711-2.711l5.307-5.3a.667.667 0 0 1 .942.943l-5.306 5.3a.584.584 0 1 0 .826.825l5.75-5.743a1.835 1.835 0 0 0-1.298-3.132Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const ApplicationNFT = /*#__PURE__*/ __createIcon('ApplicationNFT', [ - { - u: () => new URL('./general/ApplicationNFT.svg', import.meta.url).href, - }, -]) export const Approve = /*#__PURE__*/ __createIcon('Approve', [ { j: () => @@ -746,11 +631,6 @@ export const Approve = /*#__PURE__*/ __createIcon('Approve', [ s: true, }, ]) -export const ArrowBack = /*#__PURE__*/ __createIcon('ArrowBack', [ - { - u: () => new URL('./general/ArrowBack.svg', import.meta.url).href, - }, -]) export const ArrowCircle = /*#__PURE__*/ __createIcon( 'ArrowCircle', [ @@ -783,148 +663,133 @@ export const ArrowCircle = /*#__PURE__*/ __createIcon( ], [24, 25], ) -export const ArrowDownRound = /*#__PURE__*/ __createIcon('ArrowDownRound', [ +export const ArrowDrop = /*#__PURE__*/ __createIcon('ArrowDrop', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', + viewBox: '0 0 24 24', children: /*#__PURE__*/ _jsx('path', { - fill: 'none', - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'm4 5.6 4 4 4-4', + fill: 'currentColor', + d: 'm11.434 15.434-5.068-5.068A.8.8 0 0 1 6.93 9h10.14a.8.8 0 0 1 .565 1.366l-5.068 5.068a.8.8 0 0 1-1.132 0Z', }), }), s: true, }, ]) -export const ArrowDownward = /*#__PURE__*/ __createIcon('ArrowDownward', [ +export const BorderedSuccess = /*#__PURE__*/ __createIcon('BorderedSuccess', [ + { + u: () => new URL('./general/BorderedSuccess.svg', import.meta.url).href, + }, +]) +export const Candle = /*#__PURE__*/ __createIcon('candle', [ + { + u: () => new URL('./general/candle.svg', import.meta.url).href, + }, +]) +export const Check = /*#__PURE__*/ __createIcon('Check', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { + children: /*#__PURE__*/ _jsx('path', { fill: 'currentColor', fillRule: 'evenodd', + d: 'M21.066 6.434a.8.8 0 0 1 0 1.13l-10 10a.8.8 0 0 1-1.131 0l-5-5a.8.8 0 0 1 1.13-1.13l4.435 4.434 9.434-9.434a.8.8 0 0 1 1.132 0Z', clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M12 4.135a1 1 0 0 1 1 1v14a1 1 0 1 1-2 0v-14a1 1 0 0 1 1-1Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M4.293 11.428a1 1 0 0 1 1.414 0L12 17.72l6.293-6.293a1 1 0 1 1 1.414 1.414l-7 7a1 1 0 0 1-1.414 0l-7-7a1 1 0 0 1 0-1.414Z', - }), - ], }), }), s: true, }, ]) -export const ArrowDrop = /*#__PURE__*/ __createIcon('ArrowDrop', [ +export const Checkbox = /*#__PURE__*/ __createIcon('Checkbox', [ { j: () => - /*#__PURE__*/ _jsx('svg', { + /*#__PURE__*/ _jsxs('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm11.434 15.434-5.068-5.068A.8.8 0 0 1 6.93 9h10.14a.8.8 0 0 1 .565 1.366l-5.068 5.068a.8.8 0 0 1-1.132 0Z', - }), + viewBox: '0 0 18 18', + children: [ + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + d: 'M0 4a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4Z', + }), + /*#__PURE__*/ _jsx('path', { + fill: '#fff', + fillRule: 'evenodd', + d: 'M14.03 5.47a.75.75 0 0 1 0 1.06l-6 6a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 0 1 1.06-1.06l2.47 2.47 5.47-5.47a.75.75 0 0 1 1.06 0Z', + clipRule: 'evenodd', + }), + ], }), s: true, }, ]) -export const ArrowRight = /*#__PURE__*/ __createIcon('ArrowRight', [ +export const CheckboxBlank = /*#__PURE__*/ __createIcon('CheckboxBlank', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', fill: 'none', viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { + children: /*#__PURE__*/ _jsx('rect', { + width: '18', + height: '18', + x: '1', + y: '1', stroke: 'currentColor', - strokeMiterlimit: '10', - d: 'm7.708 4.792 5.209 5.416-5.209 5.417', - }), - }), - s: true, - }, -]) -export const ArrowUp = /*#__PURE__*/ __createIcon('ArrowUp', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm12.566 9.066 5.068 5.068a.8.8 0 0 1-.566 1.366H6.931a.8.8 0 0 1-.565-1.366l5.068-5.068a.8.8 0 0 1 1.132 0Z', + strokeWidth: '2', + rx: '3', }), }), s: true, }, ]) -export const ArrowUpRound = /*#__PURE__*/ __createIcon('ArrowUpRound', [ +export const CheckboxNo = /*#__PURE__*/ __createIcon('CheckboxNo', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 28 28', - children: /*#__PURE__*/ _jsx('path', { - fill: 'none', + fill: 'none', + viewBox: '0 0 16 16', + children: /*#__PURE__*/ _jsx('rect', { + width: '14', + height: '14', + x: '1', + y: '1', stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'm18 15.6-4-4-4 4', + strokeWidth: '2', + rx: '3', }), }), s: true, }, ]) -export const BackUp = /*#__PURE__*/ __createIcon('BackUp', [ +export const CheckCircle = /*#__PURE__*/ __createIcon('CheckCircle', [ { - u: () => new URL('./general/BackUp.svg', import.meta.url).href, + u: () => new URL('./general/CheckCircle.svg', import.meta.url).href, }, ]) -export const BaseClose = /*#__PURE__*/ __createIcon('BaseClose', [ - { - c: ['dark'], - u: () => new URL('./general/BaseClose.dark.svg', import.meta.url).href, - }, +export const China = /*#__PURE__*/ __createIcon('China', [ { - c: ['light'], - u: () => new URL('./general/BaseClose.light.svg', import.meta.url).href, + u: () => new URL('./general/China.svg', import.meta.url).href, }, ]) -export const BaseContacts = /*#__PURE__*/ __createIcon('BaseContacts', [ +export const CircleLoading = /*#__PURE__*/ __createIcon('CircleLoading', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', + viewBox: '0 0 36 36', children: /*#__PURE__*/ _jsxs('g', { - id: 'BaseContacts_svg__base/Contacts-1', fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', children: [ /*#__PURE__*/ _jsx('path', { - id: 'BaseContacts_svg__Vector (Stroke)', - d: 'M6 2.809c-1.225 0-2.2.979-2.2 2.17v10.91c0 1.191.975 2.17 2.2 2.17h.75c.996 0 1.97.39 2.684 1.102l.002.002 1.706 1.687a1.222 1.222 0 0 0 1.706 0l1.707-1.687.001-.002a3.788 3.788 0 0 1 2.684-1.102H18c1.224 0 2.2-.987 2.2-2.17V4.979c0-1.191-.975-2.17-2.2-2.17H6Zm-3.8 2.17c0-2.089 1.705-3.77 3.8-3.77h12c2.095 0 3.8 1.681 3.8 3.77v10.91c0 2.077-1.703 3.77-3.8 3.77h-.76c-.592 0-1.147.229-1.554.636l-.004.003-1.71 1.69a2.823 2.823 0 0 1-3.954 0l-1.71-1.69-.003-.003a2.205 2.205 0 0 0-1.555-.636H6c-2.095 0-3.8-1.681-3.8-3.77V4.979Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'BaseContacts_svg__Vector (Stroke)_2', - d: 'M12 6.14a1.53 1.53 0 1 0 0 3.059 1.53 1.53 0 0 0 0-3.06ZM8.87 7.668a3.13 3.13 0 1 1 6.26 0 3.13 3.13 0 0 1-6.26 0Z', + d: 'M34.5 18c0 9.113-7.387 16.5-16.5 16.5S1.5 27.113 1.5 18 8.887 1.5 18 1.5 34.5 8.887 34.5 18ZM4.8 18c0 7.29 5.91 13.2 13.2 13.2 7.29 0 13.2-5.91 13.2-13.2 0-7.29-5.91-13.2-13.2-13.2-7.29 0-13.2 5.91-13.2 13.2Z', + opacity: '.5', }), /*#__PURE__*/ _jsx('path', { - id: 'BaseContacts_svg__Vector (Stroke)_3', - d: 'M7.2 15.66c0-2.387 2.309-4.06 4.8-4.06 2.492 0 4.8 1.673 4.8 4.06a.8.8 0 0 1-1.6 0c0-1.214-1.271-2.46-3.2-2.46-1.928 0-3.2 1.246-3.2 2.46a.8.8 0 0 1-1.6 0Z', + d: 'M18 32.85c0 .911-.74 1.658-1.647 1.568A16.5 16.5 0 1 1 31.595 8.65c.517.751.218 1.76-.58 2.199-.799.439-1.794.14-2.33-.598a13.2 13.2 0 1 0-12.33 20.846c.903.114 1.645.842 1.645 1.753Z', }), ], }), @@ -932,34 +797,29 @@ export const BaseContacts = /*#__PURE__*/ __createIcon('BaseContacts', [ s: true, }, ]) -export const BaseUpload = /*#__PURE__*/ __createIcon( - 'BaseUpload', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 25', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'm12 5.12 4.566 4.565a.8.8 0 0 1-1.131 1.131L12.8 8.182v7.569a.8.8 0 0 1-1.6 0V8.18l-2.634 2.635a.8.8 0 0 1-1.131-1.131L12 5.119Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M5.8 15.45v3h12.4v-3h1.6v3.6a1 1 0 0 1-1 1H5.2a1 1 0 0 1-1-1v-3.6h1.6Z', - }), - ], - }), +export const CircleWarning = /*#__PURE__*/ __createIcon('CircleWarning', [ + { + u: () => new URL('./general/CircleWarning.svg', import.meta.url).href, + }, +]) +export const Close = /*#__PURE__*/ __createIcon('Close', [ + { + j: () => + /*#__PURE__*/ _jsx('svg', { + xmlns: 'http://www.w3.org/2000/svg', + viewBox: '0 0 20 20', + children: /*#__PURE__*/ _jsx('path', { + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeWidth: '1.25', + d: 'M15 5 5 15M5 5l10 10', }), - s: true, - }, - ], - [24, 25], -) -export const BaseUser = /*#__PURE__*/ __createIcon('BaseUser', [ + }), + s: true, + }, +]) +export const ColorfulClose = /*#__PURE__*/ __createIcon('ColorfulClose', [ { j: () => /*#__PURE__*/ _jsxs('svg', { @@ -969,20 +829,18 @@ export const BaseUser = /*#__PURE__*/ __createIcon('BaseUser', [ children: [ /*#__PURE__*/ _jsx('path', { fill: 'currentColor', - fillRule: 'evenodd', - d: 'M6.786 21.02c.637.12 1.37.18 2.215.18h6c.844 0 1.577-.06 2.214-.18-.536-1.776-2.565-3.25-5.214-3.25-2.65 0-4.679 1.474-5.215 3.25Zm-1.723.532c.266-3.141 3.413-5.382 6.938-5.382 3.524 0 6.67 2.241 6.937 5.382a.8.8 0 0 1-.57.835c-.978.289-2.1.413-3.367.413H9c-1.268 0-2.39-.124-3.367-.413a.8.8 0 0 1-.57-.835Z', - clipRule: 'evenodd', + d: 'M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Z', }), /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', + fill: '#fff', fillRule: 'evenodd', - d: 'M3.06 3.059C4.408 1.71 6.42 1.199 9 1.199h6c2.58 0 4.592.511 5.94 1.86 1.35 1.348 1.86 3.361 1.86 5.94v6c0 1.955-.292 3.578-1.016 4.838-.744 1.292-1.895 2.119-3.426 2.552a.8.8 0 0 1-1.015-.702c-.174-2.059-2.367-3.918-5.343-3.918-2.975 0-5.168 1.86-5.343 3.918a.8.8 0 0 1-1.015.702c-1.53-.433-2.682-1.26-3.425-2.552-.725-1.26-1.017-2.883-1.017-4.838V9c0-2.579.511-4.592 1.86-5.94Zm1.13 1.13C3.29 5.092 2.8 6.58 2.8 9v6c0 1.825.278 3.127.804 4.04a3.61 3.61 0 0 0 1.65 1.506C6.029 17.943 8.865 16.17 12 16.17c3.135 0 5.971 1.773 6.747 4.376a3.615 3.615 0 0 0 1.65-1.506c.525-.913.803-2.215.803-4.04V9c0-2.42-.489-3.908-1.39-4.81-.902-.9-2.389-1.39-4.81-1.39H9c-2.42 0-3.908.49-4.81 1.39ZM12 7.8a2.776 2.776 0 0 0-2.78 2.78A2.785 2.785 0 0 0 12 13.37a2.785 2.785 0 0 0 2.78-2.79A2.776 2.776 0 0 0 12 7.8Zm-4.38 2.78A4.376 4.376 0 0 1 12 6.2a4.376 4.376 0 0 1 4.38 4.38A4.385 4.385 0 0 1 12 14.97a4.385 4.385 0 0 1-4.38-4.39Z', + d: 'M7.757 7.757c.352-.351.985-.287 1.416.143l6.927 6.927c.43.43.494 1.064.142 1.416-.351.351-.985.287-1.415-.143L7.9 9.173c-.43-.43-.494-1.064-.143-1.416Z', clipRule: 'evenodd', }), /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', + fill: '#fff', fillRule: 'evenodd', - d: 'M12 7.8a2.776 2.776 0 0 0-2.78 2.78A2.785 2.785 0 0 0 12 13.37a2.785 2.785 0 0 0 2.78-2.79A2.776 2.776 0 0 0 12 7.8Zm-4.38 2.78A4.376 4.376 0 0 1 12 6.2a4.376 4.376 0 0 1 4.38 4.38A4.385 4.385 0 0 1 12 14.97a4.385 4.385 0 0 1-4.38-4.39Z', + d: 'M16.242 7.757c.352.352.288.985-.142 1.416L9.173 16.1c-.43.43-1.064.494-1.416.142-.351-.351-.287-.985.143-1.415L14.827 7.9c.43-.43 1.064-.494 1.415-.143Z', clipRule: 'evenodd', }), ], @@ -990,239 +848,228 @@ export const BaseUser = /*#__PURE__*/ __createIcon('BaseUser', [ s: true, }, ]) -export const BestTrade = /*#__PURE__*/ __createIcon('BestTrade', [ - { - u: () => new URL('./general/BestTrade.svg', import.meta.url).href, - }, -]) -export const BluePin = /*#__PURE__*/ __createIcon('BluePin', [ - { - u: () => new URL('./general/BluePin.svg', import.meta.url).href, - }, -]) -export const BorderedSuccess = /*#__PURE__*/ __createIcon('BorderedSuccess', [ - { - u: () => new URL('./general/BorderedSuccess.svg', import.meta.url).href, - }, -]) -export const BusyWalletNav = /*#__PURE__*/ __createIcon('BusyWalletNav', [ +export const Comment = /*#__PURE__*/ __createIcon('Comment', [ { j: () => /*#__PURE__*/ _jsxs('svg', { xmlns: 'http://www.w3.org/2000/svg', fill: 'none', - viewBox: '0 0 24 24', + viewBox: '0 0 20 20', children: [ /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M17.77 9.873h3.484c.412 0 .746.326.746.728v2.53a.746.746 0 0 1-.746.729h-3.405c-.994.013-1.863-.651-2.089-1.595a1.982 1.982 0 0 1 .433-1.652 2.092 2.092 0 0 1 1.576-.74Zm.15 2.66h.33a.755.755 0 0 0 .764-.745.755.755 0 0 0-.764-.745h-.33a.766.766 0 0 0-.54.213.728.728 0 0 0-.224.524c0 .413.341.749.765.754Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M22 8.382h-4.231v.034c-1.964 0-3.556 1.552-3.556 3.467s1.592 3.467 3.556 3.467H22v.312C22 19.015 19.964 21 16.516 21H7.484C4.036 21 2 19.015 2 15.662V8.338C2 4.985 4.036 3 7.484 3h9.032C19.964 3 22 4.985 22 8.382Zm-15.262 0h5.644a.755.755 0 0 0 .765-.746.755.755 0 0 0-.764-.745H6.738a.755.755 0 0 0-.764.736c0 .413.34.75.764.754Z', - clipRule: 'evenodd', + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: '10', + d: 'M14.984 8.992v3.333c0 .217-.009.425-.034.625-.191 2.25-1.516 3.367-3.958 3.367h-.333c-.209 0-.409.1-.534.266l-1 1.334c-.441.591-1.158.591-1.6 0l-1-1.334a.77.77 0 0 0-.533-.266h-.333c-2.659 0-3.992-.659-3.992-3.992V8.992c0-2.442 1.125-3.767 3.367-3.959.2-.025.408-.033.625-.033h5.333c2.658 0 3.992 1.333 3.992 3.992Z', }), /*#__PURE__*/ _jsx('path', { - fill: '#FF3545', - d: 'M23 3.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z', + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: '10', + d: 'M18.317 5.658v3.334c0 2.45-1.125 3.766-3.367 3.958.025-.2.033-.408.033-.625V8.992C14.983 6.333 13.65 5 10.991 5H5.659c-.216 0-.425.008-.625.033.192-2.241 1.517-3.366 3.959-3.366h5.333c2.658 0 3.992 1.333 3.992 3.991Z', }), /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M24 3.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0ZM20.5 6a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z', - clipRule: 'evenodd', + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + d: 'M11.246 11.042h.007m-2.923 0h.007m-2.924 0h.007', }), ], }), s: true, }, ]) -export const Buy = /*#__PURE__*/ __createIcon('Buy', [ - { - u: () => new URL('./general/Buy.svg', import.meta.url).href, - }, -]) -export const Cached = /*#__PURE__*/ __createIcon('Cached', [ +export const Copy = /*#__PURE__*/ __createIcon( + 'Copy', + [ + { + j: () => + /*#__PURE__*/ _jsxs('svg', { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + viewBox: '0 0 16 17', + children: [ + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M7.736 12.923c-.898 0-1.68-.268-2.244-.794-.566-.53-.858-1.272-.858-2.117V5.267c0-.84.29-1.58.851-2.11.559-.525 1.336-.794 2.227-.8h4.386c.898 0 1.68.267 2.243.793.567.53.859 1.272.859 2.117v4.745c0 .84-.29 1.58-.852 2.109-.558.525-1.335.795-2.226.8h-.003l-4.383.002v-.534.534Zm0-1.067c-.689 0-1.19-.203-1.516-.506-.32-.3-.52-.746-.52-1.338V5.267c0-.589.198-1.033.516-1.332.322-.303.82-.507 1.5-.51h.002l4.38-.002c.689 0 1.19.203 1.515.506.321.3.52.746.52 1.338v4.745c0 .589-.197 1.032-.516 1.332-.322.303-.818.506-1.5.51l-4.381.002Z', + clipRule: 'evenodd', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M5.41 15c-.898 0-1.68-.268-2.243-.794-.567-.53-.86-1.272-.86-2.117V7.344c0-1.084.484-1.987 1.37-2.49a.533.533 0 0 1 .526.928c-.514.292-.828.817-.828 1.562v4.745c0 .592.199 1.037.52 1.338.325.303.826.506 1.515.506l4.382-.002c.763-.005 1.295-.258 1.612-.627a.533.533 0 0 1 .81.694c-.563.654-1.418.994-2.418 1h-.003L5.41 15v-.534V15Z', + clipRule: 'evenodd', + }), + ], + }), + s: true, + }, + ], + [16, 17], +) +export const Edit2 = /*#__PURE__*/ __createIcon('Edit2', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 25 25', + viewBox: '0 0 24 24', children: /*#__PURE__*/ _jsx('path', { - d: 'm19.617 8.902-4 4h3c0 3.31-2.69 6-6 6a5.87 5.87 0 0 1-2.8-.7l-1.46 1.46a7.93 7.93 0 0 0 4.26 1.24c4.42 0 8-3.58 8-8h3l-4-4Zm-13 4c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46a7.93 7.93 0 0 0-4.26-1.24c-4.42 0-8 3.58-8 8h-3l4 4 4-4h-3Z', - style: { - '--default-color': '#1C68F3', - fill: 'var(--icon-color, var(--default-color, currentColor))', - }, + fill: 'currentColor', + d: 'M20 19.25a.75.75 0 1 1 0 1.5H4a.75.75 0 1 1 0-1.5h16ZM16.76 2.81l2.304 2.303a1.2 1.2 0 0 1 0 1.697l-9.8 9.8a1.2 1.2 0 0 1-.849.352H5.512a.6.6 0 0 1-.6-.6V13.46a1.2 1.2 0 0 1 .352-.849l9.8-9.8a1.2 1.2 0 0 1 1.697 0l-.001.001Zm-.848 1.273-9.5 9.5v1.88h1.879l9.5-9.5-1.879-1.88Z', }), }), s: true, }, ]) -export const Candle = /*#__PURE__*/ __createIcon('candle', [ +export const EmptySimple = /*#__PURE__*/ __createIcon('EmptySimple', [ { - u: () => new URL('./general/candle.svg', import.meta.url).href, + c: ['dark'], + u: () => new URL('./general/EmptySimple.dark.svg', import.meta.url).href, }, -]) -export const Card = /*#__PURE__*/ __createIcon('Card', [ { - u: () => new URL('./general/Card.svg', import.meta.url).href, + c: ['light'], + u: () => new URL('./general/EmptySimple.light.svg', import.meta.url).href, }, ]) -export const Check = /*#__PURE__*/ __createIcon('Check', [ +export const Europe = /*#__PURE__*/ __createIcon('Europe', [ { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M21.066 6.434a.8.8 0 0 1 0 1.13l-10 10a.8.8 0 0 1-1.131 0l-5-5a.8.8 0 0 1 1.13-1.13l4.435 4.434 9.434-9.434a.8.8 0 0 1 1.132 0Z', - clipRule: 'evenodd', - }), - }), - s: true, + u: () => new URL('./general/Europe.svg', import.meta.url).href, }, ]) -export const Checkbox = /*#__PURE__*/ __createIcon('Checkbox', [ +export const FillSuccess = /*#__PURE__*/ __createIcon('FillSuccess', [ { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 18 18', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M0 4a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M14.03 5.47a.75.75 0 0 1 0 1.06l-6 6a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 0 1 1.06-1.06l2.47 2.47 5.47-5.47a.75.75 0 0 1 1.06 0Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, + u: () => new URL('./general/FillSuccess.svg', import.meta.url).href, }, ]) -export const CheckboxBlank = /*#__PURE__*/ __createIcon('CheckboxBlank', [ +export const FireflyNFT = /*#__PURE__*/ __createIcon('FireflyNFT', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('rect', { - width: '18', - height: '18', - x: '1', - y: '1', - stroke: 'currentColor', - strokeWidth: '2', - rx: '3', + viewBox: '0 0 16 16', + children: /*#__PURE__*/ _jsxs('g', { + fill: 'currentColor', + fillRule: 'evenodd', + clipRule: 'evenodd', + children: [ + /*#__PURE__*/ _jsx('path', { + id: 'FireflyNFT_svg__Union', + d: 'M6.722 10.808h-.935v.463h-.468v.926h.468v.463h.935v-.463h.467v-.926h-.467v-.463Z', + }), + /*#__PURE__*/ _jsx('path', { + id: 'FireflyNFT_svg__Subtract', + d: 'M4.274.826a.458.458 0 0 1 .325-.564c.246-.065.5.082.567.328l.12.441L9.199 0l.122.447.604-.16.736 2.704-.604.16.123.448-3.918 1.033 1.995 7.36h-.205v1.544h.468v.463h.28l.37 1.363a.458.458 0 0 1-.326.563.465.465 0 0 1-.567-.327l-.838-3.092H7.5v-1.544h-.467V10.5h-.138L5.369 4.867l-3.907 1.03-.122-.447-.604.16L0 2.905l.603-.16-.122-.448 3.912-1.031-.12-.441Zm5.492 12.863-.932-.001v-.463H8.59v.002h-.227v-.84l.001.004v-.09h.466v-.463l.676.002v-.002h.26v.386h-.003v.079h.392v-.002h.078v.566h-.002l.001.36h-.467v.462Zm.78-.822h3.506v-.643h.65v-.644h.649v-.643H16V7.72h-.65V6.434h-.649V5.147h-.649v-.643h.65V3.86h-.65v.644h-.65V3.86h-.649v-.643h-1.298v-.643h-.65V3.86h.65v.644h-.65V5.79h-.649v-.643h-.65v-.643h.65V3.86h-.65v.644h-.649v2.573h-.649v3.217h.65v1.235h1.22v.463h.468v.875ZM7.19 11.27v.312l-.085-.312h.085Zm.527-9.917-1.384.365.14.518.347-.091.328 1.208.692-.183-.328-1.208.346-.09-.141-.519Zm-3.361.887 1.73-.456.14.517-.864.228.094.346.536-.142.094.345-.536.142.14.517-.864.229-.47-1.726Zm-.939.248.692-.183.47 1.726-.692.182-.094-.345-.173.046-.094-.345-.173.045.188.69-.692.183-.47-1.726.692-.182.094.345.173-.046.094.345.173-.045-.188-.69Zm8.687 4.588h.65v.643h-.65v-.643Zm-1.299 1.93v-.643h2.598v.643h.649v2.573h-.65v.644h-2.597v-.644h-.649V9.007h.65Zm-.026.746h.51v.404h.408v.505h-.408v.404h-.51v-.505h.408v-.303h-.408v-.505Zm2.17 0h.51v.505h-.408v.303h.408v.505h-.51v-.404h-.407v-.505h.407v-.404Z', + }), + ], }), }), s: true, }, ]) -export const CheckboxBorder = /*#__PURE__*/ __createIcon('CheckboxBorder', [ +export const Flag = /*#__PURE__*/ __createIcon('Flag', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 18 18', - children: /*#__PURE__*/ _jsx('rect', { - width: '17', - height: '17', - x: '.5', - y: '.5', - fill: '#fff', - stroke: 'currentColor', - rx: '3.5', + fill: 'none', + viewBox: '0 0 24 24', + children: /*#__PURE__*/ _jsxs('g', { + fill: 'currentColor', + fillRule: 'evenodd', + clipRule: 'evenodd', + children: [ + /*#__PURE__*/ _jsx('path', { + d: 'M4.89 1.2a.8.8 0 0 1 .8.8v20a.8.8 0 0 1-1.6 0V2a.8.8 0 0 1 .8-.8Z', + }), + /*#__PURE__*/ _jsx('path', { + d: 'M4.09 4a.8.8 0 0 1 .8-.8h11.2c.74 0 1.398.102 1.934.326.542.228 1.005.603 1.232 1.16.226.554.16 1.146-.064 1.687-.222.537-.616 1.072-1.137 1.593l-1.2 1.2c-.51.51-.452 1.27-.038 1.632a.79.79 0 0 1 .038.037l1.2 1.2c.99.99 1.63 2.191 1.158 3.293-.467 1.09-1.766 1.472-3.123 1.472H4.89a.8.8 0 0 1 0-1.6h11.2c1.243 0 1.594-.367 1.652-.502.053-.124.092-.622-.818-1.532l-1.183-1.183c-1.164-1.042-1.1-2.865-.017-3.948l1.2-1.2c.43-.43.674-.794.79-1.074.113-.274.084-.413.06-.471-.023-.057-.098-.175-.369-.288-.276-.116-.705-.202-1.315-.202H4.89a.8.8 0 0 1-.8-.8Z', + }), + ], }), }), s: true, }, ]) -export const CheckboxNo = /*#__PURE__*/ __createIcon('CheckboxNo', [ +export const Gift = /*#__PURE__*/ __createIcon('Gift', [ { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsx('rect', { - width: '14', - height: '14', - x: '1', - y: '1', - stroke: 'currentColor', - strokeWidth: '2', - rx: '3', - }), - }), - s: true, + u: () => new URL('./general/Gift.svg', import.meta.url).href, }, ]) -export const CheckCircle = /*#__PURE__*/ __createIcon('CheckCircle', [ +export const Heart = /*#__PURE__*/ __createIcon('Heart', [ { - u: () => new URL('./general/CheckCircle.svg', import.meta.url).href, + u: () => new URL('./general/Heart.svg', import.meta.url).href, }, ]) -export const ChevronUp = /*#__PURE__*/ __createIcon('ChevronUp', [ +export const History = /*#__PURE__*/ __createIcon('History', [ { j: () => - /*#__PURE__*/ _jsx('svg', { + /*#__PURE__*/ _jsxs('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'none', - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M5.833 10.833 10 15l4.167-4.167M5.833 5 10 9.167 14.167 5', - }), + fill: 'none', + viewBox: '0 0 24 24', + children: [ + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M3.743 3.919c-.63.677-.974 1.703-.974 3.08v10c0 1.378.344 2.403.974 3.08.619.668 1.628 1.12 3.226 1.12h7.375a.8.8 0 1 1 0 1.6H6.969c-1.903 0-3.393-.547-4.399-1.63-.996-1.073-1.401-2.547-1.401-4.17V7c0-1.622.405-3.097 1.401-4.17C3.576 1.748 5.066 1.2 6.97 1.2h8c1.902 0 3.393.548 4.399 1.63.995 1.073 1.4 2.548 1.4 4.17v4.205a.8.8 0 1 1-1.6 0V6.999c0-1.377-.343-2.403-.973-3.08-.62-.667-1.628-1.12-3.226-1.12h-8c-1.598 0-2.607.453-3.226 1.12Z', + clipRule: 'evenodd', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M18.95 14.418a3.442 3.442 0 1 0 0 6.884 3.442 3.442 0 0 0 0-6.884Zm-4.842 3.442a4.842 4.842 0 1 1 9.684 0 4.842 4.842 0 0 1-9.684 0Z', + clipRule: 'evenodd', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M18.949 14.895a.7.7 0 0 1 .7.7v2.278a.7.7 0 1 1-1.4 0v-2.279a.7.7 0 0 1 .7-.7Z', + clipRule: 'evenodd', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M18.343 17.522a.7.7 0 0 1 .957-.256l1.668.963a.7.7 0 1 1-.7 1.213l-1.668-.963a.7.7 0 0 1-.257-.957ZM14.95 2a.8.8 0 0 1 .8.8v2c0 .658.542 1.2 1.2 1.2h2a.8.8 0 0 1 0 1.6h-2a2.806 2.806 0 0 1-2.8-2.8v-2a.8.8 0 0 1 .8-.8Zm-8.783 8.388a.8.8 0 0 1 .8-.8h6a.8.8 0 0 1 0 1.6h-6a.8.8 0 0 1-.8-.8Zm0 4.85a.8.8 0 0 1 .8-.8h4.814a.8.8 0 0 1 0 1.6H6.967a.8.8 0 0 1-.8-.8Z', + clipRule: 'evenodd', + }), + ], }), s: true, }, ]) -export const China = /*#__PURE__*/ __createIcon('China', [ - { - u: () => new URL('./general/China.svg', import.meta.url).href, - }, -]) -export const Chrome = /*#__PURE__*/ __createIcon('Chrome', [ +export const HongKong = /*#__PURE__*/ __createIcon('HongKong', [ { - u: () => new URL('./general/Chrome.svg', import.meta.url).href, + u: () => new URL('./general/HongKong.svg', import.meta.url).href, }, ]) -export const CircleClose = /*#__PURE__*/ __createIcon('CircleClose', [ +export const Info = /*#__PURE__*/ __createIcon('Info', [ { - u: () => new URL('./general/CircleClose.svg', import.meta.url).href, + c: ['dark'], + u: () => new URL('./general/Info.dark.svg', import.meta.url).href, }, -]) -export const CircleLoading = /*#__PURE__*/ __createIcon('CircleLoading', [ { + c: ['light'], + u: () => new URL('./general/Info.light.svg', import.meta.url).href, j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 36 36', + viewBox: '0 0 20 20', children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', + fill: 'none', children: [ /*#__PURE__*/ _jsx('path', { - d: 'M34.5 18c0 9.113-7.387 16.5-16.5 16.5S1.5 27.113 1.5 18 8.887 1.5 18 1.5 34.5 8.887 34.5 18ZM4.8 18c0 7.29 5.91 13.2 13.2 13.2 7.29 0 13.2-5.91 13.2-13.2 0-7.29-5.91-13.2-13.2-13.2-7.29 0-13.2 5.91-13.2 13.2Z', - opacity: '.5', + fill: 'currentColor', + d: 'M17.5 10a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z', }), /*#__PURE__*/ _jsx('path', { - d: 'M18 32.85c0 .911-.74 1.658-1.647 1.568A16.5 16.5 0 1 1 31.595 8.65c.517.751.218 1.76-.58 2.199-.799.439-1.794.14-2.33-.598a13.2 13.2 0 1 0-12.33 20.846c.903.114 1.645.842 1.645 1.753Z', + fill: '#fff', + fillRule: 'evenodd', + d: 'M10 8.61a.75.75 0 0 1 .75.75v4.516a.75.75 0 1 1-1.5 0V9.359a.75.75 0 0 1 .75-.75Zm0-2.5a.75.75 0 0 1 .75.75v.019a.75.75 0 0 1-1.5 0v-.02a.75.75 0 0 1 .75-.75Z', + clipRule: 'evenodd', }), ], }), @@ -1230,155 +1077,142 @@ export const CircleLoading = /*#__PURE__*/ __createIcon('CircleLoading', [ s: true, }, ]) -export const CircleWarning = /*#__PURE__*/ __createIcon('CircleWarning', [ +export const Japan = /*#__PURE__*/ __createIcon('Japan', [ { - u: () => new URL('./general/CircleWarning.svg', import.meta.url).href, + u: () => new URL('./general/Japan.svg', import.meta.url).href, }, ]) -export const Clear = /*#__PURE__*/ __createIcon('Clear', [ - { - c: ['dark'], - u: () => new URL('./general/Clear.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Clear.light.svg', import.meta.url).href, - }, -]) -export const Close = /*#__PURE__*/ __createIcon('Close', [ +export const LeftArrow = /*#__PURE__*/ __createIcon('LeftArrow', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', + viewBox: '0 0 25 25', children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M15 5 5 15M5 5l10 10', + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M16.566 4.937a.8.8 0 0 1 0 1.132l-6.435 6.434 6.434 6.434a.8.8 0 0 1-1.13 1.132l-7-7a.8.8 0 0 1 0-1.132l7-7a.8.8 0 0 1 1.13 0Z', + clipRule: 'evenodd', }), }), s: true, }, ]) -export const Cloud = /*#__PURE__*/ __createIcon('Cloud', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 18 18', - children: [ - /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipPath: "url('#Cloud_svg__cloud_a')", - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M4.924 7.633a.6.6 0 0 1-.536.658 2.576 2.576 0 1 0 .726 5.097.6.6 0 0 1 .217 1.18 3.776 3.776 0 1 1-1.065-7.471.6.6 0 0 1 .658.536Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M13.515 7.313a.6.6 0 0 1-.737-.422 4.253 4.253 0 1 0-8.107 2.55.6.6 0 0 1-1.129.406 5.453 5.453 0 1 1 10.395-3.27.6.6 0 0 1-.422.736Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M12.574 13.412a2.889 2.889 0 1 0-1.734-4.951.6.6 0 0 1-.835-.862 4.088 4.088 0 1 1 2.455 7.008.6.6 0 0 1 .114-1.195Zm-3.948-2.038a.6.6 0 0 1 .6.6v4.01l1.282-1.28a.6.6 0 1 1 .848.848L9.05 17.858a.6.6 0 0 1-.848 0l-2.306-2.306a.6.6 0 1 1 .848-.849l1.282 1.282v-4.011a.6.6 0 0 1 .6-.6Z', - }), - ], - }), - /*#__PURE__*/ _jsx('defs', { - children: /*#__PURE__*/ _jsx('clipPath', { - id: 'Cloud_svg__cloud_a', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M.043.933h18v18h-18z', - }), - }), +export const Like = /*#__PURE__*/ __createIcon( + 'Like', + [ + { + j: () => + /*#__PURE__*/ _jsx('svg', { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + viewBox: '0 0 22 20', + children: /*#__PURE__*/ _jsx('path', { + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + d: 'M11.62 18.662c-.34.117-.9.117-1.24 0C7.48 17.688 1 13.628 1 6.745 1 3.708 3.49 1.25 6.56 1.25c1.82 0 3.43.865 4.44 2.202a5.56 5.56 0 0 1 4.44-2.202c3.07 0 5.56 2.458 5.56 5.496 0 6.882-6.48 10.942-9.38 11.916Z', }), - ], - }), - s: true, + }), + s: true, + }, + ], + [22, 20], +) +export const LinearCalendar = /*#__PURE__*/ __createIcon('LinearCalendar', [ + { + c: ['dark'], + u: () => new URL('./general/LinearCalendar.dark.svg', import.meta.url).href, }, -]) -export const CloudBackup = /*#__PURE__*/ __createIcon('CloudBackup', [ { - u: () => new URL('./general/CloudBackup.png', import.meta.url).href, + c: ['light'], + u: () => new URL('./general/LinearCalendar.light.svg', import.meta.url).href, }, ]) -export const CloudBackup2 = /*#__PURE__*/ __createIcon('CloudBackup2', [ +export const LinkOut = /*#__PURE__*/ __createIcon('LinkOut', [ { j: () => - /*#__PURE__*/ _jsxs('svg', { + /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 19 19', - children: [ - /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipPath: "url('#CloudBackup2_svg__cloud_backup_a')", - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M4.924 7.633a.6.6 0 0 1-.536.658 2.576 2.576 0 1 0 .726 5.097.6.6 0 0 1 .217 1.18 3.776 3.776 0 1 1-1.065-7.471.6.6 0 0 1 .658.536Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M13.515 7.313a.6.6 0 0 1-.737-.422 4.253 4.253 0 1 0-8.107 2.55.6.6 0 0 1-1.129.406 5.453 5.453 0 1 1 10.395-3.27.6.6 0 0 1-.422.736Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M12.574 13.412a2.889 2.889 0 1 0-1.734-4.951.6.6 0 0 1-.835-.862 4.088 4.088 0 1 1 2.455 7.008.6.6 0 0 1 .114-1.195Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M8.626 16.535a.6.6 0 0 1-.6-.6v-4.011l-1.282 1.282a.6.6 0 0 1-.849-.849l2.306-2.306a.6.6 0 0 1 .849 0l2.306 2.306a.6.6 0 0 1-.848.849l-1.282-1.282v4.01a.6.6 0 0 1-.6.6Z', - }), - ], - }), - /*#__PURE__*/ _jsx('defs', { - children: /*#__PURE__*/ _jsx('clipPath', { - id: 'CloudBackup2_svg__cloud_backup_a', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M.043.933h18v18h-18z', - }), - }), - }), - ], + viewBox: '0 0 12 12', + children: /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M1.6 2.1a.5.5 0 0 1 .5-.5h3.529v.8H2.4v7.2h7.2V6.235h.8V9.9a.5.5 0 0 1-.5.5H2.1a.5.5 0 0 1-.5-.5V2.1ZM6.832 2c0-.22.18-.4.4-.4H10c.22 0 .4.18.4.4v2.747a.4.4 0 0 1-.8 0v-1.77l-4.935 5.02a.4.4 0 0 1-.57-.56L9.045 2.4H7.233a.4.4 0 0 1-.4-.4Z', + clipRule: 'evenodd', + }), }), s: true, }, ]) -export const CloudLink = /*#__PURE__*/ __createIcon('CloudLink', [ +export const MaskAvatar = /*#__PURE__*/ __createIcon('MaskAvatar', [ + { + c: ['dark'], + u: () => new URL('./general/MaskAvatar.dark.svg', import.meta.url).href, + }, { - u: () => new URL('./general/CloudLink.png', import.meta.url).href, + c: ['light'], + u: () => new URL('./general/MaskAvatar.light.svg', import.meta.url).href, }, ]) -export const CNY = /*#__PURE__*/ __createIcon('CNY', [ +export const Masks = /*#__PURE__*/ __createIcon('Masks', [ { - u: () => new URL('./general/CNY.svg', import.meta.url).href, + u: () => new URL('./general/Masks.svg', import.meta.url).href, }, ]) -export const Collectible = /*#__PURE__*/ __createIcon( - 'Collectible', +export const NFTHolder = /*#__PURE__*/ __createIcon( + 'NFTHolder', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 13 12', + fill: 'none', + viewBox: '0 0 21 20', children: /*#__PURE__*/ _jsx('path', { fill: 'currentColor', fillRule: 'evenodd', - d: 'M6.5 0c-3.307 0-6 2.69-6 6s2.693 6 6 6 6-2.693 6-6-2.69-6-6-6Zm5.223 6a5.19 5.19 0 0 1-.92 2.96 2.705 2.705 0 0 0-.026-.067V8.89c-.156-.384-.468-.844-.905-1.367v-.001c-.09-.106-.175-.205-.252-.291a.617.617 0 0 0 .11-.352V3.975A3.255 3.255 0 0 0 7.299.838 5.233 5.233 0 0 1 11.723 6ZM1.277 6A5.233 5.233 0 0 1 5.805.823a2.85 2.85 0 0 0-1.287.739 2.972 2.972 0 0 0-.883 2.098v3.4c-.39.423-.843 1.015-1.117 1.61a6.888 6.888 0 0 0-.192.458A5.184 5.184 0 0 1 1.277 6Zm1.637 3.793c.062-.197.135-.397.224-.605.017.112.04.226.07.337l.001.002c.102.356.306.66.481.874a5.335 5.335 0 0 1-.776-.608Zm1.554-2.47c.023-.021.047-.042.073-.063a3.02 3.02 0 0 0-.009.233c0 .28.045.589.2.841a.97.97 0 0 0 .715.47c.09.016.173.02.249.02.45 0 .852-.183 1.167-.393.23-.154.423-.327.567-.474a.636.636 0 0 0 .486.02l.682-.245a2.694 2.694 0 0 0-.824.774c-.281.406-.443 1.019-.54 1.56-.077.432-.116.843-.136 1.116-.194.025-.389.04-.585.041-.063-.3-.236-.745-.726-1.013a1.97 1.97 0 0 0-1.406-.204c-.143-.154-.342-.41-.422-.691-.152-.533-.057-1.184-.013-1.423a6.11 6.11 0 0 1 .522-.569Zm1.016 1.254-.035.228h-.004l.04-.228Zm2.35-4.3h-.002V3.054c0-.825-.672-1.493-1.493-1.493h-.126a1.5 1.5 0 0 0-.23.018c.173-.046.354-.069.54-.067a2.47 2.47 0 0 1 2.43 2.463v2.8l-1.118.405V4.277ZM4.417 3.66c0-.545.215-1.085.594-1.487-.182.247-.29.552-.29.882v1.454c0 .443.196.836.498 1.108a.714.714 0 0 1-.128.247c-.107.14-.314.28-.566.45a6.006 6.006 0 0 0-.108.072V3.66Zm1.92 2.348c.26 0 .504-.068.715-.186v1.393a3.274 3.274 0 0 1-.454.444c-.31.247-.678.433-1.034.379h-.001a.22.22 0 0 1-.172-.108c-.081-.136-.104-.384-.074-.681.025-.248.08-.49.126-.644a1.513 1.513 0 0 0 .469-.63c.095.02.195.033.299.033h.126Zm-.839-2.953c0-.396.322-.716.716-.716h.126c.396 0 .716.322.716.716v1.454a.716.716 0 0 1-.716.716h-.126a.716.716 0 0 1-.716-.716V3.054Zm2.915 5.894a1.923 1.923 0 0 1 1.011-.741c.318.399.533.734.633.978.052.127.1.294.139.499a5.244 5.244 0 0 1-2.305 1.35c.022-.248.059-.56.117-.878.088-.484.22-.94.405-1.208Zm-3.95 1.857a.188.188 0 0 1 .01-.018c.12-.041.495-.137.94.106a.638.638 0 0 1 .258.262 5.25 5.25 0 0 1-1.208-.35Z', + d: 'M5.362 1.037A.575.575 0 0 1 5.77.33a.583.583 0 0 1 .711.41l.15.554L11.542 0l.153.56.758-.2.923 3.393-.757.2.154.563L7.856 5.81l2.503 9.234h-.258v1.937h.587v.581h.354l.464 1.71a.575.575 0 0 1-.409.708.583.583 0 0 1-.71-.411l-1.053-3.88h.076v-1.936h-.586v-.582h-.172L6.736 6.106 1.834 7.4l-.153-.561-.758.2L0 3.646l.757-.2-.153-.563 4.908-1.294-.15-.552Zm6.89 16.138-1.17-.002v-.58h-.303v.002h-.287v-1.06l.002.005v-.108h.202l.383.001v-.58l.847.002v-.004h.326v.486h-.003v.098l.492.001v-.004h.098v.712h-.003l.001.45h-.585v.581Zm.978-1.03h4.398v-.808h.815v-.807h.815v-.807h.815V9.687h-.815V8.073h-.815V6.459h-.815V5.65h.815v-.807h-.815v.807h-.814v-.807h-.815v-.807h-1.63V3.23h-.814v1.614h.815v.807h-.815v1.615h-.814v-.807h-.815V5.65h.815v-.807h-.815v.807h-.815v3.23h-.815v4.036h.815v1.548h1.532v.58h.587v1.1ZM9.02 14.14v.386l-.106-.386h.105ZM9.681 1.7l-1.737.458.177.65.434-.115.413 1.516.868-.23-.412-1.515.434-.115-.177-.65ZM5.465 2.812l2.17-.572.178.65-1.086.285.118.433.673-.177.118.433-.673.178.177.65-1.086.285-.589-2.165Zm-1.178.31.869-.228.589 2.165-.868.229-.118-.433-.217.057-.118-.433-.217.057.236.867-.869.229-.59-2.166.87-.229.117.433.217-.057.118.433.217-.057-.236-.866ZM15.184 8.88H16v.807h-.815V8.88Zm-1.63 2.422v-.808h3.26v.808h.814v3.228h-.814v.807h-3.26v-.807h-.814v-3.228h.815Zm-.032.936h.64v.507h.51v.633h-.51v.506h-.64v-.633h.511v-.38h-.51v-.633Zm2.723 0h.64v.633h-.512v.38h.511v.633h-.639v-.505h-.511v-.634h.511v-.507Z', clipRule: 'evenodd', }), }), s: true, }, ], - [13, 12], + [21, 20], ) -export const ColorfulClose = /*#__PURE__*/ __createIcon('ColorfulClose', [ +export const Plugin = /*#__PURE__*/ __createIcon('Plugin', [ + { + u: () => new URL('./general/Plugin.svg', import.meta.url).href, + }, +]) +export const PopupLink = /*#__PURE__*/ __createIcon('PopupLink', [ + { + j: () => + /*#__PURE__*/ _jsx('svg', { + xmlns: 'http://www.w3.org/2000/svg', + viewBox: '0 0 12 12', + children: /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + fillRule: 'evenodd', + d: 'M1.6 2.1a.5.5 0 0 1 .5-.5h3.529v.8H2.4v7.2h7.2V6.235h.8V9.9a.5.5 0 0 1-.5.5H2.1a.5.5 0 0 1-.5-.5V2.1ZM6.832 2c0-.22.18-.4.4-.4H10c.22 0 .4.18.4.4v2.747a.4.4 0 0 1-.8 0v-1.77l-4.935 5.02a.4.4 0 0 1-.57-.56L9.045 2.4H7.233a.4.4 0 0 1-.4-.4Z', + clipRule: 'evenodd', + }), + }), + s: true, + }, +]) +export const PrimaryInfo = /*#__PURE__*/ __createIcon('PrimaryInfo', [ + { + u: () => new URL('./general/PrimaryInfo.svg', import.meta.url).href, + }, +]) +export const Provider = /*#__PURE__*/ __createIcon('Provider', [ + { + u: () => new URL('./general/Provider.svg', import.meta.url).href, + }, +]) +export const Questions = /*#__PURE__*/ __createIcon('Questions', [ { j: () => /*#__PURE__*/ _jsxs('svg', { @@ -1388,18 +1222,18 @@ export const ColorfulClose = /*#__PURE__*/ __createIcon('ColorfulClose', [ children: [ /*#__PURE__*/ _jsx('path', { fill: 'currentColor', - d: 'M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', fillRule: 'evenodd', - d: 'M7.757 7.757c.352-.351.985-.287 1.416.143l6.927 6.927c.43.43.494 1.064.142 1.416-.351.351-.985.287-1.415-.143L7.9 9.173c-.43-.43-.494-1.064-.143-1.416Z', + d: 'M12 4.8a7.2 7.2 0 1 0 0 14.4 7.2 7.2 0 0 0 0-14.4ZM3.2 12a8.8 8.8 0 1 1 17.6 0 8.8 8.8 0 0 1-17.6 0Z', clipRule: 'evenodd', }), /*#__PURE__*/ _jsx('path', { - fill: '#fff', + fill: 'currentColor', + d: 'M11.9 16.787a.69.69 0 1 1-.057-1.38.69.69 0 0 1 .056 1.38Zm2.956-6.713c-.04.738-.267 1.317-1.397 2.448-.571.573-.932 1.004-.97 1.38a.574.574 0 0 1-1.14-.112c.076-.795.66-1.437 1.3-2.08 1.027-1.024 1.046-1.35 1.063-1.697a1.475 1.475 0 0 0-.421-1.1 1.79 1.79 0 0 0-1.293-.55h-.003a1.714 1.714 0 0 0-1.707 1.711.574.574 0 0 1-1.147 0c0-.762.295-1.478.833-2.019a2.838 2.838 0 0 1 2.018-.838 2.947 2.947 0 0 1 2.13.906c.513.537.772 1.23.734 1.951Z', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', fillRule: 'evenodd', - d: 'M16.242 7.757c.352.352.288.985-.142 1.416L9.173 16.1c-.43.43-1.064.494-1.416.142-.351-.351-.287-.985.143-1.415L14.827 7.9c.43-.43 1.064-.494 1.415-.143Z', + d: 'M14.052 8.19a2.85 2.85 0 0 0-2.06-.876 2.74 2.74 0 0 0-1.948.81 2.744 2.744 0 0 0-.806 1.95.476.476 0 1 0 .952 0 1.811 1.811 0 0 1 1.804-1.809h.004a1.887 1.887 0 0 1 1.363.58 1.572 1.572 0 0 1 .449 1.172c-.01.18-.02.365-.165.636-.142.266-.411.612-.927 1.127-.644.646-1.2 1.264-1.272 2.02a.475.475 0 1 0 .947.092c.02-.213.132-.432.302-.664a7.27 7.27 0 0 1 .694-.775c.563-.563.895-.982 1.09-1.348a2.31 2.31 0 0 0 .28-1.036 2.507 2.507 0 0 0-.707-1.879Zm-2.06-1.07a3.044 3.044 0 0 1 2.2.935 2.7 2.7 0 0 1 .761 2.025c-.02.38-.09.723-.301 1.117-.21.39-.556.826-1.124 1.394a7.08 7.08 0 0 0-.675.753c-.163.22-.25.405-.266.568a.67.67 0 0 1-1.31.13m-.026-.261c.081-.835.692-1.5 1.329-2.14.512-.51.765-.84.893-1.08.125-.233.133-.385.142-.553a1.376 1.376 0 0 0-.394-1.027V8.98a1.692 1.692 0 0 0-1.223-.52h-.003a1.616 1.616 0 0 0-1.61 1.614.67.67 0 1 1-1.342 0c0-.788.306-1.529.862-2.088a2.936 2.936 0 0 1 2.087-.867v.098-.098m-.74 6.662.096.01-.097-.01m.644 1.723a.594.594 0 0 0-.234 1.147.592.592 0 1 0 .234-1.147Zm-.303-.145a.787.787 0 1 1 .558 1.473.787.787 0 0 1-.558-1.473Z', clipRule: 'evenodd', }), ], @@ -1407,22 +1241,35 @@ export const ColorfulClose = /*#__PURE__*/ __createIcon('ColorfulClose', [ s: true, }, ]) -export const Comeback = /*#__PURE__*/ __createIcon('Comeback', [ +export const RadioButtonChecked = /*#__PURE__*/ __createIcon('RadioButtonChecked', [ + { + u: () => new URL('./general/RadioButtonChecked.svg', import.meta.url).href, + }, +]) +export const RadioButtonUnChecked = /*#__PURE__*/ __createIcon('RadioButtonUnChecked', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M20 11H7.414l4.293-4.293a1 1 0 0 0-1.414-1.414l-6 6a1 1 0 0 0 0 1.414l6 6a.996.996 0 0 0 1.415 0 1 1 0 0 0 0-1.414L7.413 13H20a1 1 0 1 0 0-2Z', + viewBox: '0 0 21 21', + children: /*#__PURE__*/ _jsx('circle', { + cx: '10.166', + cy: '10.131', + r: '9', + stroke: 'currentColor', + strokeWidth: '2', }), }), s: true, }, ]) -export const Comment = /*#__PURE__*/ __createIcon('Comment', [ +export const RedPacket = /*#__PURE__*/ __createIcon('RedPacket', [ + { + u: () => new URL('./general/RedPacket.svg', import.meta.url).href, + }, +]) +export const Repost = /*#__PURE__*/ __createIcon('Repost', [ { j: () => /*#__PURE__*/ _jsxs('svg', { @@ -1435,158 +1282,125 @@ export const Comment = /*#__PURE__*/ __createIcon('Comment', [ strokeLinecap: 'round', strokeLinejoin: 'round', strokeMiterlimit: '10', - d: 'M14.984 8.992v3.333c0 .217-.009.425-.034.625-.191 2.25-1.516 3.367-3.958 3.367h-.333c-.209 0-.409.1-.534.266l-1 1.334c-.441.591-1.158.591-1.6 0l-1-1.334a.77.77 0 0 0-.533-.266h-.333c-2.659 0-3.992-.659-3.992-3.992V8.992c0-2.442 1.125-3.767 3.367-3.959.2-.025.408-.033.625-.033h5.333c2.658 0 3.992 1.333 3.992 3.992Z', + strokeWidth: '1.5', + d: 'M2.983 4.3h11.534c1.383 0 2.5 1.117 2.5 2.5v2.767', }), /*#__PURE__*/ _jsx('path', { stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round', strokeMiterlimit: '10', - d: 'M18.317 5.658v3.334c0 2.45-1.125 3.766-3.367 3.958.025-.2.033-.408.033-.625V8.992C14.983 6.333 13.65 5 10.991 5H5.659c-.216 0-.425.008-.625.033.192-2.241 1.517-3.366 3.959-3.366h5.333c2.658 0 3.992 1.333 3.992 3.991Z', + strokeWidth: '1.5', + d: 'M5.617 1.667 2.983 4.3l2.634 2.633m11.4 8.767H5.483a2.497 2.497 0 0 1-2.5-2.5v-2.767', }), /*#__PURE__*/ _jsx('path', { stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round', - d: 'M11.246 11.042h.007m-2.923 0h.007m-2.924 0h.007', + strokeMiterlimit: '10', + strokeWidth: '1.5', + d: 'm14.384 18.333 2.633-2.633-2.633-2.633', }), ], }), s: true, }, ]) -export const Connect = /*#__PURE__*/ __createIcon('Connect', [ +export const ResultNo = /*#__PURE__*/ __createIcon('ResultNo', [ + { + u: () => new URL('./general/ResultNo.svg', import.meta.url).href, + }, +]) +export const ResultYes = /*#__PURE__*/ __createIcon('ResultYes', [ + { + u: () => new URL('./general/ResultYes.svg', import.meta.url).href, + }, +]) +export const RightArrow = /*#__PURE__*/ __createIcon('RightArrow', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { + viewBox: '0 0 25 25', + children: /*#__PURE__*/ _jsx('path', { fill: 'currentColor', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M6.39 8.988a.6.6 0 0 1 .6-.6h4.02a.6.6 0 1 1 0 1.2H6.99a.6.6 0 0 1-.6-.6Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M1.986 9a3.961 3.961 0 0 1 3.96-3.96h2.015v1.2H5.947a2.761 2.761 0 1 0 0 5.521H7.96v1.2H5.947a3.961 3.961 0 0 1-3.961-3.96Zm14.028 0a3.961 3.961 0 0 0-3.96-3.96H9.288v1.2h2.764a2.761 2.761 0 0 1 0 5.521H9.29v1.2h2.764a3.961 3.961 0 0 0 3.961-3.96Z', - }), - ], + fillRule: 'evenodd', + d: 'M8.935 20.063a.8.8 0 0 1 0-1.132l6.434-6.434-6.434-6.434a.8.8 0 1 1 1.13-1.132l7 7a.8.8 0 0 1 0 1.132l-7 7a.8.8 0 0 1-1.13 0Z', + clipRule: 'evenodd', }), }), s: true, }, ]) -export const Contacts = /*#__PURE__*/ __createIcon('Contacts', [ +export const Search = /*#__PURE__*/ __createIcon('Search', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', + viewBox: '0 0 20 20', children: /*#__PURE__*/ _jsx('path', { fill: 'currentColor', fillRule: 'evenodd', - d: 'M17.294 7.291A5.274 5.274 0 0 1 12 12.583a5.275 5.275 0 0 1-5.294-5.292A5.274 5.274 0 0 1 12 2a5.273 5.273 0 0 1 5.294 5.291ZM12 22c-4.338 0-8-.705-8-3.425 0-2.721 3.685-3.401 8-3.401 4.339 0 8 .705 8 3.425 0 2.72-3.685 3.4-8 3.4Zm3.644-8.014a.8.8 0 0 1 .8-.8h5.112a.8.8 0 0 1 0 1.6h-5.112a.8.8 0 0 1-.8-.8Zm1.289-2.45a.8.8 0 0 1 .8-.8h3.823a.8.8 0 0 1 0 1.6h-3.823a.8.8 0 0 1-.8-.8Zm1.203-2.449a.8.8 0 0 1 .8-.8h2.62a.8.8 0 0 1 0 1.6h-2.62a.8.8 0 0 1-.8-.8Z', + d: 'M4.271 9.41a4.93 4.93 0 1 1 8.478 3.424.617.617 0 0 0-.122.123A4.93 4.93 0 0 1 4.272 9.41zm8.837 4.79a6.18 6.18 0 1 1 .884-.884l2.596 2.596a.625.625 0 1 1-.884.884L13.108 14.2z', clipRule: 'evenodd', }), }), s: true, }, ]) -export const Copy = /*#__PURE__*/ __createIcon( - 'Copy', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', +export const Selected = /*#__PURE__*/ __createIcon('Selected', [ + { + c: ['dark'], + u: () => new URL('./general/Selected.dark.svg', import.meta.url).href, + j: () => + /*#__PURE__*/ _jsx('svg', { + xmlns: 'http://www.w3.org/2000/svg', + viewBox: '0 0 16 16', + children: /*#__PURE__*/ _jsxs('g', { fill: 'none', - viewBox: '0 0 16 17', children: [ /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M7.736 12.923c-.898 0-1.68-.268-2.244-.794-.566-.53-.858-1.272-.858-2.117V5.267c0-.84.29-1.58.851-2.11.559-.525 1.336-.794 2.227-.8h4.386c.898 0 1.68.267 2.243.793.567.53.859 1.272.859 2.117v4.745c0 .84-.29 1.58-.852 2.109-.558.525-1.335.795-2.226.8h-.003l-4.383.002v-.534.534Zm0-1.067c-.689 0-1.19-.203-1.516-.506-.32-.3-.52-.746-.52-1.338V5.267c0-.589.198-1.033.516-1.332.322-.303.82-.507 1.5-.51h.002l4.38-.002c.689 0 1.19.203 1.515.506.321.3.52.746.52 1.338v4.745c0 .589-.197 1.032-.516 1.332-.322.303-.818.506-1.5.51l-4.381.002Z', - clipRule: 'evenodd', + fill: '#101010', + d: 'M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Z', }), /*#__PURE__*/ _jsx('path', { fill: 'currentColor', + d: 'M8 14.667A6.667 6.667 0 1 0 8 1.333a6.667 6.667 0 0 0 0 13.334Z', + }), + /*#__PURE__*/ _jsx('path', { + fill: '#fff', fillRule: 'evenodd', - d: 'M5.41 15c-.898 0-1.68-.268-2.243-.794-.567-.53-.86-1.272-.86-2.117V7.344c0-1.084.484-1.987 1.37-2.49a.533.533 0 0 1 .526.928c-.514.292-.828.817-.828 1.562v4.745c0 .592.199 1.037.52 1.338.325.303.826.506 1.515.506l4.382-.002c.763-.005 1.295-.258 1.612-.627a.533.533 0 0 1 .81.694c-.563.654-1.418.994-2.418 1h-.003L5.41 15v-.534V15Z', + d: 'M11.115 5.507c.272.247.293.67.045.941l-3.333 3.667a.667.667 0 0 1-.928.058l-2.333-2a.667.667 0 1 1 .868-1.012l1.842 1.578 2.897-3.187a.667.667 0 0 1 .942-.045Z', clipRule: 'evenodd', }), ], }), - s: true, - }, - ], - [16, 17], -) -export const Currency = /*#__PURE__*/ __createIcon('Currency', [ + }), + s: true, + }, { + c: ['light'], + u: () => new URL('./general/Selected.light.svg', import.meta.url).href, j: () => - /*#__PURE__*/ _jsxs('svg', { + /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 25 25', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M3.36 8.49v9h15v-9h-15Zm14.25 2.128a2.265 2.265 0 0 1-1.377-1.378h1.377v1.378Zm-2.133 6.122H6.245a2.975 2.975 0 0 0-2.135-2.133v-3.23A2.978 2.978 0 0 0 6.244 9.24h9.232a2.979 2.979 0 0 0 2.134 2.138v3.227a2.978 2.978 0 0 0-2.133 2.135Zm-9.99-7.5a2.258 2.258 0 0 1-1.377 1.371v-1.37h1.377ZM4.11 15.373a2.255 2.255 0 0 1 1.372 1.367H4.11v-1.367Zm12.132 1.367a2.26 2.26 0 0 1 1.368-1.376v1.376h-1.368Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M14.985 11.865a1.125 1.125 0 1 0 0 2.25 1.125 1.125 0 0 0 0-2.25Zm0 1.5a.376.376 0 0 1 0-.75.376.376 0 0 1 0 .75ZM10.86 9.99c-1.242 0-2.25 1.344-2.25 3 0 1.657 1.008 3 2.25 3 1.243 0 2.25-1.343 2.25-3 0-1.656-1.007-3-2.25-3Zm0 5.25c-.813 0-1.5-1.03-1.5-2.25s.687-2.25 1.5-2.25 1.5 1.03 1.5 2.25-.687 2.25-1.5 2.25Zm-4.125-3.375a1.125 1.125 0 1 0 0 2.25 1.125 1.125 0 0 0 0-2.25Zm0 1.5a.375.375 0 0 1 0-.75.376.376 0 0 1 0 .75ZM5.28 6.48H6v1.56h-.72zm14.76 0v.72H6v-.72z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#111418', - d: 'M20.04 14.76v.72h-.96v-.72z', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - stroke: 'currentColor', - strokeWidth: '.36', - d: 'M20.22 6.66h.36v8.64h-.36z', - }), - ], - }), - s: true, - }, -]) -export const Dark = /*#__PURE__*/ __createIcon('Dark', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M9.266 1.878a3.979 3.979 0 0 0 1.007 7.827 3.979 3.979 0 0 0 3.849-2.971 6.25 6.25 0 1 1-4.855-4.855Z', - }), - }), - s: true, - }, -]) -export const Decrease = /*#__PURE__*/ __createIcon('decrease', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', children: /*#__PURE__*/ _jsxs('g', { - id: 'decrease_svg__base/decrease', - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', + fill: 'none', children: [ /*#__PURE__*/ _jsx('path', { - id: 'decrease_svg__Vector (Stroke)', - d: 'M10 4a6 6 0 1 0 0 12 6 6 0 0 0 0-12Zm-7.333 6a7.333 7.333 0 1 1 14.666 0 7.333 7.333 0 0 1-14.666 0Z', + fill: '#F5F5F5', + d: 'M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Z', + }), + /*#__PURE__*/ _jsx('path', { + fill: 'currentColor', + d: 'M8 14.667A6.667 6.667 0 1 0 8 1.333a6.667 6.667 0 0 0 0 13.334Z', }), /*#__PURE__*/ _jsx('path', { - id: 'decrease_svg__Vector (Stroke)_2', - d: 'M6.667 10c0-.369.298-.667.666-.667h5.334a.667.667 0 1 1 0 1.333H7.333a.667.667 0 0 1-.666-.667Z', + fill: '#fff', + fillRule: 'evenodd', + d: 'M11.115 5.507c.272.247.293.67.045.941l-3.333 3.667a.667.667 0 0 1-.928.058l-2.333-2a.667.667 0 1 1 .868-1.012l1.842 1.578 2.897-3.187a.667.667 0 0 1 .942-.045Z', + clipRule: 'evenodd', }), ], }), @@ -1594,112 +1408,87 @@ export const Decrease = /*#__PURE__*/ __createIcon('decrease', [ s: true, }, ]) -export const DefaultToken = /*#__PURE__*/ __createIcon('DefaultToken', [ - { - c: ['dark'], - u: () => new URL('./general/DefaultToken.dark.svg', import.meta.url).href, - }, - { - c: ['dim'], - u: () => new URL('./general/DefaultToken.dim.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/DefaultToken.light.svg', import.meta.url).href, - }, -]) -export const Delete = /*#__PURE__*/ __createIcon('Delete', [ +export const Settings = /*#__PURE__*/ __createIcon('Settings', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 16 16', + viewBox: '0 0 24 24', children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M2 4h12M5.333 4V2.667a1.333 1.333 0 0 1 1.334-1.334h2.666a1.334 1.334 0 0 1 1.334 1.334V4m2 0v9.333a1.334 1.334 0 0 1-1.334 1.334H4.667a1.333 1.333 0 0 1-1.334-1.334V4h9.334zm-6 3.333v4m2.666-4v4', + fill: 'currentColor', + d: 'm12 23-9.5-5.5v-11L12 1l9.5 5.5v11L12 23zm0-19.688L4.5 7.653v8.694l7.5 4.342 7.5-4.342V7.653L12 3.311v.001zM12 16a4 4 0 1 1 2.828-1.172A4.027 4.027 0 0 1 12 16zm0-6a2 2 0 1 0-.001 4A2 2 0 0 0 12 10z', }), }), s: true, }, ]) -export const Disconnect = /*#__PURE__*/ __createIcon('Disconnect', [ +export const SmartPay = /*#__PURE__*/ __createIcon('SmartPay', [ { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M16.348 4.455a.8.8 0 0 1 .293 1.093L8.746 19.223a.8.8 0 0 1-1.386-.8l7.895-13.675a.8.8 0 0 1 1.093-.293Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M2.647 12A5.281 5.281 0 0 1 7.93 6.72h3.316v1.6H7.929a3.681 3.681 0 1 0 0 7.362h1.163v1.6H7.93a5.281 5.281 0 0 1-5.282-5.28Zm18.705 0a5.281 5.281 0 0 0-5.28-5.281h-1.66v1.6h1.66a3.681 3.681 0 0 1 0 7.362H12.35v1.6h3.72a5.281 5.281 0 0 0 5.281-5.28Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, + u: () => new URL('./general/SmartPay.svg', import.meta.url).href, + }, +]) +export const SuccessForSnackBar = /*#__PURE__*/ __createIcon('SuccessForSnackBar', [ + { + u: () => new URL('./general/SuccessForSnackBar.svg', import.meta.url).href, + }, +]) +export const TransactionFailed = /*#__PURE__*/ __createIcon('TransactionFailed', [ + { + u: () => new URL('./general/TransactionFailed.svg', import.meta.url).href, }, ]) -export const Document = /*#__PURE__*/ __createIcon('Document', [ +export const TrashLine = /*#__PURE__*/ __createIcon('TrashLine', [ { - u: () => new URL('./general/Document.svg', import.meta.url).href, + u: () => new URL('./general/TrashLine.svg', import.meta.url).href, }, ]) -export const Documents = /*#__PURE__*/ __createIcon('Documents', [ +export const Undo = /*#__PURE__*/ __createIcon('Undo', [ { j: () => /*#__PURE__*/ _jsxs('svg', { xmlns: 'http://www.w3.org/2000/svg', fill: 'none', - viewBox: '0 0 16 16', + viewBox: '0 0 24 24', children: [ /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M3.98 2.61c-.42.45-.65 1.14-.65 2.06v6.66c0 .92.23 1.6.65 2.06.42.44 1.09.74 2.15.74h5.34c1.06 0 1.74-.3 2.15-.74.42-.46.65-1.14.65-2.06V4.67c0-.92-.23-1.6-.65-2.06-.41-.44-1.09-.74-2.15-.74H6.13c-1.06 0-1.73.3-2.15.74ZM3.2 1.9C3.87 1.17 4.87.8 6.13.8h5.34c1.27 0 2.26.37 2.93 1.1.66.71.93 1.7.93 2.78v6.66c0 1.08-.27 2.07-.93 2.78-.67.72-1.66 1.09-2.93 1.09H6.13c-1.26 0-2.26-.37-2.93-1.09a3.98 3.98 0 0 1-.93-2.78V4.67c0-1.09.27-2.07.93-2.78Z', - clipRule: 'evenodd', + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: '10', + strokeWidth: '1.6', + d: 'M7.13 18.31h8c2.76 0 5-2.24 5-5s-2.24-5-5-5h-11', }), /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M11.46 1.33c.29 0 .53.24.53.54V3.2c0 .44.36.8.8.8h1.33a.53.53 0 0 1 0 1.07H12.8a1.87 1.87 0 0 1-1.87-1.87V1.87c0-.3.24-.54.54-.54ZM5.6 8c0-.3.24-.53.53-.53h4a.53.53 0 1 1 0 1.06h-4A.53.53 0 0 1 5.6 8Zm0 2.14c0-.3.24-.54.53-.54h5.33a.53.53 0 0 1 0 1.07H6.13a.53.53 0 0 1-.53-.53Z', - clipRule: 'evenodd', + stroke: 'currentColor', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeWidth: '1.6', + d: 'M6.43 10.81 3.87 8.25l2.56-2.56', }), ], }), s: true, }, ]) -export const Download = /*#__PURE__*/ __createIcon('Download', [ +export const UserPlus = /*#__PURE__*/ __createIcon('UserPlus', [ { j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', + fill: 'none', + viewBox: '0 0 24 24', children: /*#__PURE__*/ _jsx('path', { - fill: 'none', stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M14 10v2.667A1.334 1.334 0 0 1 12.667 14H3.333A1.334 1.334 0 0 1 2 12.667V10m2.667-3.333L8 10l3.333-3.333M8 10V2', + strokeWidth: '2', + d: 'M8 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0Zm8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4', }), }), s: true, }, ]) -export const Download2 = /*#__PURE__*/ __createIcon('Download2', [ +export const Verification = /*#__PURE__*/ __createIcon('Verification', [ { j: () => /*#__PURE__*/ _jsxs('svg', { @@ -1708,21 +1497,16 @@ export const Download2 = /*#__PURE__*/ __createIcon('Download2', [ viewBox: '0 0 24 24', children: [ /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M11.694 9.779a.8.8 0 0 1-.494-.74v-6a.8.8 0 0 1 1.6 0v4.07l.634-.635a.8.8 0 0 1 1.132 1.132l-2 2a.8.8 0 0 1-.872.173Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12.566 9.606a.8.8 0 0 1-1.132 0l-2-2a.8.8 0 1 1 1.132-1.132l2 2a.8.8 0 0 1 0 1.132Zm-9.724 3.11c.731-1.058 2.083-1.517 4.158-1.517.552 0 .982.057 1.37.209.364.141.642.35.88.529l.03.022a.8.8 0 0 1 .104.094l1.01 1.078a2.198 2.198 0 0 0 3.202.001l.003-.002 1.02-1.08a.803.803 0 0 1 .101-.09l.03-.023c.239-.18.516-.388.88-.53.388-.15.818-.208 1.37-.208 2.076 0 3.427.459 4.158 1.517.35.505.502 1.075.573 1.622.07.53.07 1.101.07 1.633v1.028c0 1.451-.259 2.928-1.185 4.046-.951 1.148-2.468 1.754-4.616 1.754H8c-2.576 0-4.177-.57-5.033-1.81-.412-.596-.595-1.274-.682-1.94-.085-.65-.085-1.352-.085-2.02V15.97c0-.532 0-1.102.07-1.633.07-.547.223-1.117.572-1.622Zm1.014 1.83c-.055.417-.056.888-.056 1.453v1c0 .703.001 1.306.072 1.842.069.53.198.93.411 1.239.395.57 1.293 1.12 3.717 1.12h8c1.852 0 2.836-.514 3.384-1.176.574-.693.816-1.716.816-3.025v-1c0-.565-.001-1.036-.056-1.454-.053-.41-.151-.702-.302-.92-.269-.39-.917-.826-2.842-.826-.448 0-.658.048-.79.1-.131.05-.24.124-.475.3l-.97 1.027-.002.001a3.799 3.799 0 0 1-5.534.002l-.003-.003-.962-1.029c-.235-.175-.343-.247-.474-.299-.132-.05-.342-.099-.79-.099-1.924 0-2.573.437-2.842.826-.15.218-.249.51-.302.92Z', - clipRule: 'evenodd', + d: 'M10.705 1.603A1.699 1.699 0 0 1 12 1a1.691 1.691 0 0 1 1.295.603l.816.968a1.695 1.695 0 0 0 1.698.555l1.229-.302a1.7 1.7 0 0 1 2.095 1.53l.094 1.265a1.705 1.705 0 0 0 1.05 1.45l1.168.48c.97.4 1.353 1.58.8 2.476l-.665 1.079a1.71 1.71 0 0 0 0 1.793l.666 1.08c.552.895.17 2.074-.8 2.472l-1.17.483a1.703 1.703 0 0 0-1.05 1.45l-.093 1.265a1.712 1.712 0 0 1-.695 1.252 1.695 1.695 0 0 1-1.4.277l-1.229-.302a1.69 1.69 0 0 0-1.697.555l-.817.968A1.699 1.699 0 0 1 12 23a1.691 1.691 0 0 1-1.295-.603l-.816-.968a1.698 1.698 0 0 0-1.698-.555l-1.229.302a1.7 1.7 0 0 1-2.095-1.53l-.094-1.265a1.71 1.71 0 0 0-1.05-1.45l-1.168-.482a1.706 1.706 0 0 1-.8-2.472l.665-1.08a1.71 1.71 0 0 0 0-1.793l-.665-1.08a1.71 1.71 0 0 1 .8-2.473l1.17-.481a1.707 1.707 0 0 0 1.05-1.451l.092-1.266a1.71 1.71 0 0 1 .695-1.252 1.694 1.694 0 0 1 1.4-.277l1.229.302a1.694 1.694 0 0 0 1.698-.555l.816-.968Z', + style: { + '--default-color': '#1C68F3', + fill: 'var(--icon-color, var(--default-color, currentColor))', + }, }), /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', + fill: '#fff', fillRule: 'evenodd', - d: 'M8.797 5.961a.8.8 0 0 1-.72.874c-1.355.13-1.796.54-1.996.915-.261.49-.28 1.179-.28 2.248v2a.8.8 0 1 1-1.6 0V9.894c-.001-.92-.002-2.015.469-2.897.55-1.03 1.609-1.596 3.253-1.755a.8.8 0 0 1 .874.72Zm6.407 0a.8.8 0 0 1 .873-.719c1.645.159 2.704.725 3.254 1.755.47.882.47 1.977.47 2.897v2.104a.8.8 0 1 1-1.6 0v-2c0-1.07-.02-1.759-.282-2.248-.2-.375-.64-.784-1.996-.915a.8.8 0 0 1-.719-.874Z', + d: 'M17.698 8.776a.75.75 0 0 1 0 1.06l-6.867 6.866a.75.75 0 0 1-1.06 0l-3.21-3.209a.75.75 0 1 1 1.061-1.06l2.679 2.678 6.336-6.335a.75.75 0 0 1 1.06 0Z', clipRule: 'evenodd', }), ], @@ -1730,56 +1514,53 @@ export const Download2 = /*#__PURE__*/ __createIcon('Download2', [ s: true, }, ]) -export const Drop = /*#__PURE__*/ __createIcon( - 'Drop', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M9.46 14.845a.7.7 0 0 0 1.08 0l2.845-3.45a.7.7 0 0 0-.54-1.145h-5.69a.7.7 0 0 0-.54 1.145l2.845 3.45Z', - }), - }), - s: true, - }, - ], - [20, 24], -) -export const Dump = /*#__PURE__*/ __createIcon('Dump', [ +export const Wallet = /*#__PURE__*/ __createIcon('Wallet', [ { j: () => - /*#__PURE__*/ _jsxs('svg', { + /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M3.566 4.565C3.07 5.06 2.8 5.993 2.8 7.999v8c0 2.006.27 2.939.766 3.434.495.496 1.428.766 3.434.766h10c2.006 0 2.939-.27 3.434-.766.496-.495.766-1.428.766-3.434v-4c0-2.006-.27-2.939-.765-3.434-.496-.496-1.43-.766-3.435-.766h-3c-.572 0-1.043-.084-1.446-.328-.359-.216-.592-.525-.759-.746l-.032-.042-.003-.004-1.5-2c-.304-.405-.44-.569-.623-.679-.165-.099-.456-.2-1.137-.2H7c-2.006 0-2.939.27-3.434.765ZM2.435 3.434C3.439 2.429 5.005 2.199 7 2.199h1.5c.82 0 1.443.118 1.96.43.478.286.787.699 1.046 1.045l.034.045 1.499 1.998c.21.277.27.341.343.385.051.031.19.097.618.097h3c1.994 0 3.561.23 4.566 1.235 1.004 1.004 1.234 2.57 1.234 4.565v4c0 1.995-.23 3.562-1.234 4.566-1.005 1.004-2.572 1.234-4.566 1.234H7c-1.994 0-3.561-.23-4.565-1.234C1.43 19.56 1.2 17.993 1.2 15.999V8c0-1.994.23-3.561 1.235-4.565Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M7.2 3a.8.8 0 0 1 .8-.8h9c1.123 0 2.113.28 2.816.984.703.702.984 1.692.984 2.815v1.38a.8.8 0 0 1-1.6 0V6c0-.876-.218-1.387-.515-1.684-.297-.297-.808-.516-1.685-.516H8a.8.8 0 0 1-.8-.8Zm8.339 11.106a.8.8 0 0 1-.74.494h-6a.8.8 0 0 1 0-1.6h4.07l-.635-.634a.8.8 0 0 1 1.131-1.132l2 2a.8.8 0 0 1 .174.872Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M15.365 13.234a.8.8 0 0 1 0 1.132l-2 2a.8.8 0 0 1-1.131-1.132l2-2a.8.8 0 0 1 1.131 0Z', - clipRule: 'evenodd', - }), - ], + children: /*#__PURE__*/ _jsxs('g', { + fill: 'currentColor', + fillRule: 'evenodd', + clipRule: 'evenodd', + children: [ + /*#__PURE__*/ _jsx('path', { + d: 'M6.2 11.15a.8.8 0 0 1 .8-.8h6a.8.8 0 0 1 0 1.6H7a.8.8 0 0 1-.8-.8Zm-.51-7.51A2.887 2.887 0 0 0 2.8 6.53v4.62a.8.8 0 0 1-1.6 0V6.53a4.487 4.487 0 0 1 4.49-4.49h5.62c1.182 0 2.298.367 3.131 1.082.845.724 1.36 1.774 1.36 3.027a.8.8 0 0 1-1.6 0c0-.787-.311-1.392-.802-1.813-.501-.43-1.23-.697-2.089-.697H5.69Z', + }), + /*#__PURE__*/ _jsx('path', { + d: 'M6 6.95a3.199 3.199 0 0 0-3.2 3.2v7a3.2 3.2 0 0 0 3.2 3.2h10a3.2 3.2 0 0 0 3.2-3.2v-.65h-.13c-1.254 0-2.542-.772-2.902-2.113l-.002-.004a2.813 2.813 0 0 1 .75-2.751 2.793 2.793 0 0 1 2.004-.832h.28v-.65c0-1.759-1.442-3.2-3.2-3.2H6Zm-4.8 3.2c0-2.652 2.148-4.8 4.8-4.8h10c2.642 0 4.8 2.158 4.8 4.8v1.45a.8.8 0 0 1-.8.8h-1.08a1.193 1.193 0 0 0-.886.377 1.214 1.214 0 0 0-.32 1.197c.14.518.691.926 1.356.926H20a.8.8 0 0 1 .8.8v1.45a4.8 4.8 0 0 1-4.8 4.8H6a4.799 4.799 0 0 1-4.8-4.8v-7Z', + }), + /*#__PURE__*/ _jsx('path', { + d: 'M18.92 12.399c-.345 0-.648.134-.866.358l-.015.014c-.256.25-.398.605-.362.982v.01c.051.605.636 1.136 1.363 1.136h1.93c.134 0 .23-.107.23-.22v-2.06a.227.227 0 0 0-.23-.22h-2.05Zm-2.006-.765a2.793 2.793 0 0 1 2.006-.835h2.05c1.007 0 1.83.813 1.83 1.82v2.06c0 1.007-.823 1.82-1.83 1.82h-1.93c-1.431 0-2.824-1.047-2.956-2.599a2.808 2.808 0 0 1 .83-2.267Z', + }), + ], + }), }), s: true, }, ]) -export const Edit = /*#__PURE__*/ __createIcon('Edit', [ +export const Warning = /*#__PURE__*/ __createIcon('Warning', [ + { + u: () => new URL('./general/Warning.svg', import.meta.url).href, + }, +]) +export const WarningTriangle = /*#__PURE__*/ __createIcon('WarningTriangle', [ + { + u: () => new URL('./general/WarningTriangle.svg', import.meta.url).href, + }, +]) +export const WebBlack = /*#__PURE__*/ __createIcon('WebBlack', [ + { + c: ['dark'], + u: () => new URL('./general/WebBlack.dark.svg', import.meta.url).href, + }, + { + c: ['light'], + u: () => new URL('./general/WebBlack.light.svg', import.meta.url).href, + }, +]) +export const Avatar = /*#__PURE__*/ __createIcon('Avatar', [ { j: () => /*#__PURE__*/ _jsx('svg', { @@ -1787,2122 +1568,36 @@ export const Edit = /*#__PURE__*/ __createIcon('Edit', [ fill: 'none', viewBox: '0 0 20 20', children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M10 17.596h7.5m-3.75-13.75a1.768 1.768 0 0 1 2.5 2.5L5.833 16.763l-3.333.833.833-3.333L13.75 3.846z', + fill: 'currentColor', + d: 'M7.413 2.721c1.191-.96 2.814-1.23 4.293-.95 1.346.322 2.551 1.228 3.204 2.453.453.805.593 1.738.599 2.65l-.001 7.089c-.04.308.204.528.359.764.735 1.054 1.078 2.332 1.139 3.605-.442.002-.884.001-1.326 0-.067-.822-.212-1.644-.597-2.382-1.206-.006-2.41.02-3.617.008-.007-.895-.003-1.79-.005-2.686-1.028-.24-2.115-.061-3.081.338-1.846.778-3.096 2.727-3.089 4.723H4c-.061-1.578.593-3.132 1.626-4.307.145-.137.093-.352.108-.53-.013-1.811.008-3.624-.01-5.436-.045-.902-.017-1.806.024-2.708.108-1.08.77-2.041 1.666-2.63Zm4.23.365c.148.42.554.684.715 1.102.504.938.361 2.023.378 3.044.008 2.481.006 4.962.007 7.443.495.001.99.004 1.486-.006-.004-2.598-.003-5.198 0-7.796-.006-.627-.069-1.273-.349-1.845-.414-.933-1.265-1.646-2.238-1.942Zm-3.712.848c-.653.427-.977 1.227-.952 1.99 1.498.015 2.994.005 4.492.006.053-.995-.59-2.006-1.576-2.261-.656-.233-1.369-.046-1.964.265Zm-.913 3.268c-.007.533.012 1.093.293 1.564.418.793 1.285 1.28 2.172 1.306v1.27a3.986 3.986 0 0 1-2.493-.973c.013.825 0 1.651.01 2.478a6.488 6.488 0 0 1 4.465-.941c-.008-1.568-.001-3.136-.005-4.704H7.018Z', }), }), s: true, }, ]) -export const Edit2 = /*#__PURE__*/ __createIcon('Edit2', [ +export const Collectibles = /*#__PURE__*/ __createIcon('Collectibles', [ { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M20 19.25a.75.75 0 1 1 0 1.5H4a.75.75 0 1 1 0-1.5h16ZM16.76 2.81l2.304 2.303a1.2 1.2 0 0 1 0 1.697l-9.8 9.8a1.2 1.2 0 0 1-.849.352H5.512a.6.6 0 0 1-.6-.6V13.46a1.2 1.2 0 0 1 .352-.849l9.8-9.8a1.2 1.2 0 0 1 1.697 0l-.001.001Zm-.848 1.273-9.5 9.5v1.88h1.879l9.5-9.5-1.879-1.88Z', - }), - }), - s: true, + u: () => new URL('./plugins/Collectibles.svg', import.meta.url).href, }, ]) -export const Empty = /*#__PURE__*/ __createIcon('Empty', [ +export const ENS = /*#__PURE__*/ __createIcon('ENS', [ { - u: () => new URL('./general/Empty.png', import.meta.url).href, + u: () => new URL('./plugins/ENS.png', import.meta.url).href, }, ]) -export const EmptySimple = /*#__PURE__*/ __createIcon('EmptySimple', [ +export const SettingInfo = /*#__PURE__*/ __createIcon('SettingInfo', [ { c: ['dark'], - u: () => new URL('./general/EmptySimple.dark.svg', import.meta.url).href, + u: () => new URL('./plugins/SettingInfo.dark.svg', import.meta.url).href, }, { c: ['light'], - u: () => new URL('./general/EmptySimple.light.svg', import.meta.url).href, + u: () => new URL('./plugins/SettingInfo.light.svg', import.meta.url).href, }, ]) -export const EncryptedFiles = /*#__PURE__*/ __createIcon('EncryptedFiles', [ +export const Shared = /*#__PURE__*/ __createIcon('shared', [ { - u: () => new URL('./general/EncryptedFiles.svg', import.meta.url).href, - }, -]) -export const ETHSymbol = /*#__PURE__*/ __createIcon( - 'ETHSymbol', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 25', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm12 2.563-6.25 10.5 6.25 3.75 6.25-3.75L12 2.563Zm-6.25 11.75 6.25 8.75 6.25-8.75-6.25 3.75-6.25-3.75Z', - }), - }), - s: true, - }, - ], - [24, 25], -) -export const Europe = /*#__PURE__*/ __createIcon('Europe', [ - { - u: () => new URL('./general/Europe.svg', import.meta.url).href, - }, -]) -export const Eye = /*#__PURE__*/ __createIcon('Eye', [ - { - c: ['dark'], - u: () => new URL('./general/Eye.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Eye.light.svg', import.meta.url).href, - }, -]) -export const EyeColor = /*#__PURE__*/ __createIcon('EyeColor', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 21 21', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M10.58 8.1a2.31 2.31 0 1 0 0 4.63 2.31 2.31 0 0 0 0-4.62Zm-3.65 2.32a3.65 3.65 0 1 1 7.3 0 3.65 3.65 0 0 1-7.3 0Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M2.43 7.9c2-3.14 4.93-5.04 8.15-5.04 3.22 0 6.16 1.9 8.15 5.04.46.72.67 1.64.67 2.52 0 .88-.2 1.8-.67 2.52-2 3.14-4.93 5.04-8.15 5.04-3.22 0-6.16-1.9-8.15-5.04a4.76 4.76 0 0 1-.67-2.52c0-.88.2-1.8.67-2.52Zm8.15-3.71c-2.66 0-5.21 1.56-7.03 4.42-.29.46-.46 1.11-.46 1.8 0 .7.17 1.36.46 1.81 1.82 2.86 4.37 4.43 7.03 4.43s5.21-1.57 7.03-4.43c.29-.45.46-1.1.46-1.8s-.17-1.35-.46-1.8c-1.82-2.87-4.37-4.43-7.03-4.43Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const EyeOff = /*#__PURE__*/ __createIcon('EyeOff', [ - { - c: ['dark'], - u: () => new URL('./general/EyeOff.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/EyeOff.light.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 18 18', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm16.393 5.708.015.01-.335.452c-.693.937-1.416 1.723-2.168 2.356l1.501 2.6a1.125 1.125 0 0 1-1.537-.411L13 9.207a7.798 7.798 0 0 1-2.063.982l.426 2.417a1.125 1.125 0 0 1-1.303-.912l-.224-1.27a7.027 7.027 0 0 1-2.201-.06l-.43 2.443-.02-.003a1.125 1.125 0 0 1-.896-1.282l.003-.018.253-1.438a8.129 8.129 0 0 1-1.973-1.053l-.083.147-1.46 2.529-.016-.01c-.52-.311-.7-.98-.405-1.51l.009-.017.894-1.548.173-.307a14.136 14.136 0 0 1-1.842-2.041l-.064-.086-.335-.451a1.14 1.14 0 0 1 1.58.216l.068.087.048.061.044.056.04.05.035.043.023.028.02.025.009.009C5.052 8.35 6.92 9.358 8.925 9.358c1.99 0 3.846-.992 5.578-3.018l.059-.07.018-.022.025-.03.023-.029.03-.038.046-.057.065-.083.059-.075a1.141 1.141 0 0 1 1.55-.239z', - }), - }), - s: true, - }, -]) -export const Facebook = /*#__PURE__*/ __createIcon('Facebook', [ - { - u: () => new URL('./general/Facebook.svg', import.meta.url).href, - }, -]) -export const File = /*#__PURE__*/ __createIcon('File', [ - { - u: () => new URL('./general/File.svg', import.meta.url).href, - }, -]) -export const FileMessage = /*#__PURE__*/ __createIcon('FileMessage', [ - { - u: () => new URL('./general/FileMessage.svg', import.meta.url).href, - }, -]) -export const FillSuccess = /*#__PURE__*/ __createIcon('FillSuccess', [ - { - u: () => new URL('./general/FillSuccess.svg', import.meta.url).href, - }, -]) -export const Filter = /*#__PURE__*/ __createIcon('Filter', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M2.535 16.26a.8.8 0 0 1 .8-.8h7.234a.8.8 0 0 1 0 1.6H3.335a.8.8 0 0 1-.8-.8Zm15.249-2.08a2.08 2.08 0 1 0-.002 4.159 2.08 2.08 0 0 0 .002-4.159Zm-3.68 2.08a3.68 3.68 0 1 1 7.36 0 3.68 3.68 0 0 1-7.36 0Zm-1.475-8.523a.8.8 0 0 1 .8-.8h7.235a.8.8 0 0 1 0 1.6H13.43a.8.8 0 0 1-.8-.8Zm-10.094 0a3.68 3.68 0 1 1 7.36 0 3.68 3.68 0 0 1-7.36 0Zm3.68-2.08a2.08 2.08 0 1 0 0 4.16 2.08 2.08 0 0 0 0-4.16Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const FireflyNFT = /*#__PURE__*/ __createIcon('FireflyNFT', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - id: 'FireflyNFT_svg__Union', - d: 'M6.722 10.808h-.935v.463h-.468v.926h.468v.463h.935v-.463h.467v-.926h-.467v-.463Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'FireflyNFT_svg__Subtract', - d: 'M4.274.826a.458.458 0 0 1 .325-.564c.246-.065.5.082.567.328l.12.441L9.199 0l.122.447.604-.16.736 2.704-.604.16.123.448-3.918 1.033 1.995 7.36h-.205v1.544h.468v.463h.28l.37 1.363a.458.458 0 0 1-.326.563.465.465 0 0 1-.567-.327l-.838-3.092H7.5v-1.544h-.467V10.5h-.138L5.369 4.867l-3.907 1.03-.122-.447-.604.16L0 2.905l.603-.16-.122-.448 3.912-1.031-.12-.441Zm5.492 12.863-.932-.001v-.463H8.59v.002h-.227v-.84l.001.004v-.09h.466v-.463l.676.002v-.002h.26v.386h-.003v.079h.392v-.002h.078v.566h-.002l.001.36h-.467v.462Zm.78-.822h3.506v-.643h.65v-.644h.649v-.643H16V7.72h-.65V6.434h-.649V5.147h-.649v-.643h.65V3.86h-.65v.644h-.65V3.86h-.649v-.643h-1.298v-.643h-.65V3.86h.65v.644h-.65V5.79h-.649v-.643h-.65v-.643h.65V3.86h-.65v.644h-.649v2.573h-.649v3.217h.65v1.235h1.22v.463h.468v.875ZM7.19 11.27v.312l-.085-.312h.085Zm.527-9.917-1.384.365.14.518.347-.091.328 1.208.692-.183-.328-1.208.346-.09-.141-.519Zm-3.361.887 1.73-.456.14.517-.864.228.094.346.536-.142.094.345-.536.142.14.517-.864.229-.47-1.726Zm-.939.248.692-.183.47 1.726-.692.182-.094-.345-.173.046-.094-.345-.173.045.188.69-.692.183-.47-1.726.692-.182.094.345.173-.046.094.345.173-.045-.188-.69Zm8.687 4.588h.65v.643h-.65v-.643Zm-1.299 1.93v-.643h2.598v.643h.649v2.573h-.65v.644h-2.597v-.644h-.649V9.007h.65Zm-.026.746h.51v.404h.408v.505h-.408v.404h-.51v-.505h.408v-.303h-.408v-.505Zm2.17 0h.51v.505h-.408v.303h.408v.505h-.51v-.404h-.407v-.505h.407v-.404Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Flag = /*#__PURE__*/ __createIcon('Flag', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M4.89 1.2a.8.8 0 0 1 .8.8v20a.8.8 0 0 1-1.6 0V2a.8.8 0 0 1 .8-.8Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M4.09 4a.8.8 0 0 1 .8-.8h11.2c.74 0 1.398.102 1.934.326.542.228 1.005.603 1.232 1.16.226.554.16 1.146-.064 1.687-.222.537-.616 1.072-1.137 1.593l-1.2 1.2c-.51.51-.452 1.27-.038 1.632a.79.79 0 0 1 .038.037l1.2 1.2c.99.99 1.63 2.191 1.158 3.293-.467 1.09-1.766 1.472-3.123 1.472H4.89a.8.8 0 0 1 0-1.6h11.2c1.243 0 1.594-.367 1.652-.502.053-.124.092-.622-.818-1.532l-1.183-1.183c-1.164-1.042-1.1-2.865-.017-3.948l1.2-1.2c.43-.43.674-.794.79-1.074.113-.274.084-.413.06-.471-.023-.057-.098-.175-.369-.288-.276-.116-.705-.202-1.315-.202H4.89a.8.8 0 0 1-.8-.8Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Folder = /*#__PURE__*/ __createIcon('Folder', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M3.566 4.565C3.07 5.06 2.8 5.993 2.8 7.999v8c0 2.006.27 2.939.766 3.434.495.496 1.428.766 3.434.766h10c2.006 0 2.939-.27 3.434-.766.496-.495.766-1.428.766-3.434v-4c0-2.006-.27-2.939-.765-3.434-.496-.496-1.43-.766-3.435-.766h-3c-.572 0-1.043-.084-1.446-.328-.359-.216-.592-.525-.759-.746l-.032-.042-.003-.004-1.5-2c-.304-.405-.44-.569-.623-.679-.165-.099-.456-.2-1.137-.2H7c-2.006 0-2.939.27-3.434.765ZM2.435 3.434C3.439 2.429 5.005 2.199 7 2.199h1.5c.82 0 1.443.118 1.96.43.478.286.787.699 1.046 1.045l.034.045 1.499 1.998c.21.277.27.341.343.385.051.031.19.097.618.097h3c1.994 0 3.561.23 4.566 1.235 1.004 1.004 1.234 2.57 1.234 4.565v4c0 1.995-.23 3.562-1.234 4.566-1.005 1.004-2.572 1.234-4.566 1.234H7c-1.994 0-3.561-.23-4.565-1.234C1.43 19.56 1.2 17.993 1.2 15.999V8c0-1.994.23-3.561 1.235-4.565Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M7.2 3a.8.8 0 0 1 .8-.8h9c1.123 0 2.113.28 2.816.984.703.702.984 1.692.984 2.815v1.38a.8.8 0 0 1-1.6 0V6c0-.876-.218-1.387-.515-1.684-.297-.297-.808-.516-1.685-.516H8a.8.8 0 0 1-.8-.8Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const Gas = /*#__PURE__*/ __createIcon('Gas', [ - { - u: () => new URL('./general/Gas.svg', import.meta.url).href, - }, -]) -export const GasStation = /*#__PURE__*/ __createIcon('GasStation', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M4.92 3.338c-.354.327-.62.85-.62 1.662v16.2h12.4V5c0-.811-.265-1.335-.62-1.662-.364-.336-.906-.538-1.58-.538h-8c-.674 0-1.215.202-1.58.538ZM3.836 2.162C4.556 1.498 5.514 1.2 6.5 1.2h8c.986 0 1.945.298 2.665.962.73.674 1.135 1.65 1.135 2.838v17a.8.8 0 0 1-.8.8h-14a.8.8 0 0 1-.8-.8V5c0-1.188.405-2.164 1.136-2.838Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M1.2 22a.8.8 0 0 1 .8-.8h17a.8.8 0 0 1 0 1.6H2a.8.8 0 0 1-.8-.8ZM6.53 4.811c.538-.47 1.226-.62 1.86-.62h4.23c.635 0 1.323.15 1.861.62.558.487.83 1.205.83 2.07v1.23c0 .864-.272 1.582-.83 2.069-.538.47-1.226.62-1.86.62H8.39c-.635 0-1.323-.15-1.862-.62-.558-.487-.829-1.205-.829-2.07V6.88c0-.864.271-1.582.83-2.069Zm1.05 1.206c-.127.11-.28.338-.28.863v1.23c0 .526.153.753.28.864.147.128.404.226.81.226h4.23c.405 0 .662-.098.808-.226.127-.11.281-.338.281-.864V6.88c0-.525-.154-.752-.28-.863-.147-.128-.404-.227-.81-.227H8.39c-.405 0-.662.1-.809.227Zm4.258 6.187a.8.8 0 0 1 0 1.131l-1.741 1.741h2.738a.8.8 0 0 1 .584 1.346l-3.134 3.355a.8.8 0 1 1-1.169-1.092l1.876-2.009H8.165A.8.8 0 0 1 7.6 15.31l3.107-3.107a.8.8 0 0 1 1.131 0Zm7.447-3.561a.8.8 0 0 1 1.073-.358l2 1A.8.8 0 0 1 22.8 10v6a.8.8 0 0 1-.798.8l-4.5.01a.8.8 0 0 1-.004-1.6l3.702-.008v-4.707l-1.558-.78a.8.8 0 0 1-.357-1.072Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const Gear = /*#__PURE__*/ __createIcon('Gear', [ - { - c: ['dark'], - u: () => new URL('./general/Gear.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Gear.light.svg', import.meta.url).href, - }, -]) -export const GearSettings = /*#__PURE__*/ __createIcon('GearSettings', [ - { - u: () => new URL('./general/GearSettings.svg', import.meta.url).href, - }, -]) -export const Gift = /*#__PURE__*/ __createIcon('Gift', [ - { - u: () => new URL('./general/Gift.svg', import.meta.url).href, - }, -]) -export const Globe = /*#__PURE__*/ __createIcon('Globe', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M12 1.5C6.2 1.5 1.5 6.2 1.5 12S6.2 22.5 12 22.5 22.5 17.8 22.5 12 17.8 1.5 12 1.5ZM9.047 5.9c-.878.484-1.22.574-1.486.858-.263.284-.663 1.597-.84 1.712-.177.115-1.462.154-1.462.154s2.148 1.674 2.853 1.832c.706.158 2.43-.21 2.77-.142.342.07 2.116 1.67 2.324 2.074.208.404.166 1.748-.038 1.944-.204.196-1.183 1.09-1.393 1.39-.21.3-1.894 4.078-2.094 4.08-.2 0-.62-.564-.73-.848-.11-.284-.427-4.012-.59-4.263-.163-.25-1.126-.82-1.276-1.026-.15-.207-.552-1.387-.527-1.617.024-.23.492-1.007.374-1.214-.117-.207-2.207-1.033-2.61-1.18a38.524 38.524 0 0 0-.983-.332 9.077 9.077 0 0 1 8.52-6.38s.125-.018.186.14c.11.286.256 1.078.092 1.345-.143.23-2.21.99-3.088 1.474L9.047 5.9Zm11.144 8.24c-.21-.383-1.222-2.35-1.593-2.684-.23-.208-2.2-.912-2.55-1.09-.352-.177-1.258-.997-1.267-1.213-.01-.216 1.115-1.204 1.15-1.524.056-.49-1.882-1.835-1.897-2.054-.015-.22.147-.66.31-.81.403-.36 3.19.04 3.556.36 2 1.757 3.168 4.126 3.168 6.873 0 .776-.18 1.912-.282 2.18-.08.21-.443.232-.595-.04v.002Z', - }), - }), - s: true, - }, -]) -export const GrayMasks = /*#__PURE__*/ __createIcon('GrayMasks', [ - { - u: () => new URL('./general/GrayMasks.svg', import.meta.url).href, - }, -]) -export const HamburgerMenu = /*#__PURE__*/ __createIcon('HamburgerMenu', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - children: [ - /*#__PURE__*/ _jsx('rect', { - width: '15.383', - height: '2', - x: '4.309', - y: '6.539', - rx: '1', - }), - /*#__PURE__*/ _jsx('rect', { - width: '15.383', - height: '2', - x: '4.309', - y: '11', - rx: '1', - }), - /*#__PURE__*/ _jsx('rect', { - width: '15.383', - height: '2', - x: '4.309', - y: '15.461', - rx: '1', - }), - ], - }), - }), - s: true, - }, -]) -export const Heart = /*#__PURE__*/ __createIcon('Heart', [ - { - u: () => new URL('./general/Heart.svg', import.meta.url).href, - }, -]) -export const History = /*#__PURE__*/ __createIcon('History', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M3.743 3.919c-.63.677-.974 1.703-.974 3.08v10c0 1.378.344 2.403.974 3.08.619.668 1.628 1.12 3.226 1.12h7.375a.8.8 0 1 1 0 1.6H6.969c-1.903 0-3.393-.547-4.399-1.63-.996-1.073-1.401-2.547-1.401-4.17V7c0-1.622.405-3.097 1.401-4.17C3.576 1.748 5.066 1.2 6.97 1.2h8c1.902 0 3.393.548 4.399 1.63.995 1.073 1.4 2.548 1.4 4.17v4.205a.8.8 0 1 1-1.6 0V6.999c0-1.377-.343-2.403-.973-3.08-.62-.667-1.628-1.12-3.226-1.12h-8c-1.598 0-2.607.453-3.226 1.12Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M18.95 14.418a3.442 3.442 0 1 0 0 6.884 3.442 3.442 0 0 0 0-6.884Zm-4.842 3.442a4.842 4.842 0 1 1 9.684 0 4.842 4.842 0 0 1-9.684 0Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M18.949 14.895a.7.7 0 0 1 .7.7v2.278a.7.7 0 1 1-1.4 0v-2.279a.7.7 0 0 1 .7-.7Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M18.343 17.522a.7.7 0 0 1 .957-.256l1.668.963a.7.7 0 1 1-.7 1.213l-1.668-.963a.7.7 0 0 1-.257-.957ZM14.95 2a.8.8 0 0 1 .8.8v2c0 .658.542 1.2 1.2 1.2h2a.8.8 0 0 1 0 1.6h-2a2.806 2.806 0 0 1-2.8-2.8v-2a.8.8 0 0 1 .8-.8Zm-8.783 8.388a.8.8 0 0 1 .8-.8h6a.8.8 0 0 1 0 1.6h-6a.8.8 0 0 1-.8-.8Zm0 4.85a.8.8 0 0 1 .8-.8h4.814a.8.8 0 0 1 0 1.6H6.967a.8.8 0 0 1-.8-.8Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const HKD = /*#__PURE__*/ __createIcon('HKD', [ - { - u: () => new URL('./general/HKD.svg', import.meta.url).href, - }, -]) -export const HongKong = /*#__PURE__*/ __createIcon('HongKong', [ - { - u: () => new URL('./general/HongKong.svg', import.meta.url).href, - }, -]) -export const Identity = /*#__PURE__*/ __createIcon('Identity', [ - { - c: ['dark'], - u: () => new URL('./general/Identity.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Identity.light.svg', import.meta.url).href, - }, -]) -export const Info = /*#__PURE__*/ __createIcon('Info', [ - { - c: ['dark'], - u: () => new URL('./general/Info.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Info.light.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'none', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M17.5 10a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M10 8.61a.75.75 0 0 1 .75.75v4.516a.75.75 0 1 1-1.5 0V9.359a.75.75 0 0 1 .75-.75Zm0-2.5a.75.75 0 0 1 .75.75v.019a.75.75 0 0 1-1.5 0v-.02a.75.75 0 0 1 .75-.75Z', - clipRule: 'evenodd', - }), - ], - }), - }), - s: true, - }, -]) -export const Interaction = /*#__PURE__*/ __createIcon('Interaction', [ - { - u: () => new URL('./general/Interaction.svg', import.meta.url).href, - }, -]) -export const InteractionCircle = /*#__PURE__*/ __createIcon('InteractionCircle', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M10.833 18.667a8.333 8.333 0 1 0 0-16.667 8.333 8.333 0 0 0 0 16.667z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'm12.75 6 1.5 1.5-1.5 1.5', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M7.5 9.75V9A1.5 1.5 0 0 1 9 7.5h5.25M9 14.25l-1.5-1.5 1.5-1.5', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M14.25 10.5v.75a1.5 1.5 0 0 1-1.5 1.5H7.5', - }), - ], - }), - }), - s: true, - }, -]) -export const Japan = /*#__PURE__*/ __createIcon('Japan', [ - { - u: () => new URL('./general/Japan.svg', import.meta.url).href, - }, -]) -export const JPY = /*#__PURE__*/ __createIcon('JPY', [ - { - u: () => new URL('./general/JPY.svg', import.meta.url).href, - }, -]) -export const KeySquare = /*#__PURE__*/ __createIcon( - 'KeySquare', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 25 24', - children: [ - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.5', - d: 'M9.8 22h6c5 0 7-2 7-7V9c0-5-2-7-7-7h-6c-5 0-7 2-7 7v6c0 5 2 7 7 7Z', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'M17.08 13.61a4.147 4.147 0 0 1-4.18 1.03l-2.59 2.58c-.18.19-.55.31-.82.27l-1.2-.16c-.4-.05-.76-.43-.82-.82l-.16-1.2c-.04-.26.09-.63.27-.82l2.58-2.58c-.44-1.43-.1-3.05 1.03-4.18 1.62-1.62 4.26-1.62 5.89 0 1.62 1.61 1.62 4.25 0 5.88Zm-5.83 2.67-.85-.86', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '2', - d: 'M14.195 10.7h.009', - }), - ], - }), - s: true, - }, - ], - [25, 24], -) -export const LeftArrow = /*#__PURE__*/ __createIcon('LeftArrow', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 25 25', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M16.566 4.937a.8.8 0 0 1 0 1.132l-6.435 6.434 6.434 6.434a.8.8 0 0 1-1.13 1.132l-7-7a.8.8 0 0 1 0-1.132l7-7a.8.8 0 0 1 1.13 0Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Like = /*#__PURE__*/ __createIcon( - 'Like', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 22 20', - children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - d: 'M11.62 18.662c-.34.117-.9.117-1.24 0C7.48 17.688 1 13.628 1 6.745 1 3.708 3.49 1.25 6.56 1.25c1.82 0 3.43.865 4.44 2.202a5.56 5.56 0 0 1 4.44-2.202c3.07 0 5.56 2.458 5.56 5.496 0 6.882-6.48 10.942-9.38 11.916Z', - }), - }), - s: true, - }, - ], - [22, 20], -) -export const LinearCalendar = /*#__PURE__*/ __createIcon('LinearCalendar', [ - { - c: ['dark'], - u: () => new URL('./general/LinearCalendar.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/LinearCalendar.light.svg', import.meta.url).href, - }, -]) -export const Link = /*#__PURE__*/ __createIcon('Link', [ - { - u: () => new URL('./general/Link.svg', import.meta.url).href, - }, -]) -export const LinkOut = /*#__PURE__*/ __createIcon('LinkOut', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 12 12', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M1.6 2.1a.5.5 0 0 1 .5-.5h3.529v.8H2.4v7.2h7.2V6.235h.8V9.9a.5.5 0 0 1-.5.5H2.1a.5.5 0 0 1-.5-.5V2.1ZM6.832 2c0-.22.18-.4.4-.4H10c.22 0 .4.18.4.4v2.747a.4.4 0 0 1-.8 0v-1.77l-4.935 5.02a.4.4 0 0 1-.57-.56L9.045 2.4H7.233a.4.4 0 0 1-.4-.4Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Loader = /*#__PURE__*/ __createIcon('Loader', [ - { - u: () => new URL('./general/Loader.svg', import.meta.url).href, - }, -]) -export const LoadingBase = /*#__PURE__*/ __createIcon('LoadingBase', [ - { - c: ['dark'], - u: () => new URL('./general/LoadingBase.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/LoadingBase.light.svg', import.meta.url).href, - }, -]) -export const LocalBackup = /*#__PURE__*/ __createIcon('LocalBackup', [ - { - u: () => new URL('./general/LocalBackup.png', import.meta.url).href, - }, -]) -export const Lock = /*#__PURE__*/ __createIcon( - 'Lock', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 25', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M7.748 5.01c-.697.866-.948 2.132-.948 3.74v2a.8.8 0 0 1-1.6 0v-2c0-1.704.25-3.438 1.302-4.744C7.585 2.662 9.363 1.95 12 1.95s4.416.713 5.498 2.057C18.55 5.312 18.8 7.046 18.8 8.75v2a.8.8 0 0 1-1.6 0v-2c0-1.607-.25-2.873-.948-3.74-.667-.828-1.889-1.46-4.252-1.46s-3.584.632-4.252 1.46zM12 15.05a1.7 1.7 0 1 0 0 3.4 1.7 1.7 0 0 0 0-3.4zm-3.3 1.7a3.3 3.3 0 1 1 6.6 0 3.3 3.3 0 0 1-6.6 0z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M3.566 12.315c-.496.495-.766 1.428-.766 3.434v2c0 2.006.27 2.939.766 3.434.495.496 1.428.766 3.434.766h10c2.006 0 2.939-.27 3.434-.766.496-.495.766-1.428.766-3.434v-2c0-2.006-.27-2.939-.765-3.434-.496-.496-1.43-.766-3.435-.766H7c-2.006 0-2.939.27-3.434.766zm-1.131-1.131C3.439 10.178 5.005 9.949 7 9.949h10c1.994 0 3.561.23 4.566 1.235 1.004 1.004 1.234 2.57 1.234 4.565v2c0 1.995-.23 3.562-1.234 4.566-1.005 1.004-2.572 1.234-4.566 1.234H7c-1.994 0-3.561-.23-4.565-1.234C1.43 21.31 1.2 19.743 1.2 17.749v-2c0-1.994.23-3.561 1.235-4.566z', - }), - ], - }), - }), - s: true, - }, - ], - [24, 25], -) -export const MaskAvatar = /*#__PURE__*/ __createIcon('MaskAvatar', [ - { - c: ['dark'], - u: () => new URL('./general/MaskAvatar.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/MaskAvatar.light.svg', import.meta.url).href, - }, -]) -export const MaskInMinds = /*#__PURE__*/ __createIcon('MaskInMinds', [ - { - u: () => new URL('./general/MaskInMinds.svg', import.meta.url).href, - }, -]) -export const MaskMe = /*#__PURE__*/ __createIcon('MaskMe', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 25 25', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M17.794 7.397a5.274 5.274 0 0 1-5.294 5.292 5.275 5.275 0 0 1-5.294-5.292A5.274 5.274 0 0 1 12.5 2.105a5.273 5.273 0 0 1 5.294 5.292ZM12.5 22.105c-4.338 0-8-.705-8-3.425 0-2.72 3.685-3.4 8-3.4 4.339 0 8 .704 8 3.424 0 2.721-3.685 3.401-8 3.401Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Masks = /*#__PURE__*/ __createIcon('Masks', [ - { - u: () => new URL('./general/Masks.svg', import.meta.url).href, - }, -]) -export const Medal = /*#__PURE__*/ __createIcon('Medal', [ - { - u: () => new URL('./general/Medal.svg', import.meta.url).href, - }, -]) -export const Message = /*#__PURE__*/ __createIcon('Message', [ - { - u: () => new URL('./general/Message.svg', import.meta.url).href, - }, -]) -export const Messages = /*#__PURE__*/ __createIcon('Messages', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'M17.98 10.79v4c0 .26-.01.51-.04.75-.23 2.7-1.82 4.04-4.75 4.04h-.4c-.25 0-.49.12-.64.32l-1.2 1.6c-.53.71-1.39.71-1.92 0l-1.2-1.6a.924.924 0 0 0-.64-.32h-.4C3.6 19.58 2 18.79 2 14.79v-4c0-2.93 1.35-4.52 4.04-4.75.24-.03.49-.04.75-.04h6.4c3.19 0 4.79 1.6 4.79 4.79Z', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'M21.98 6.79v4c0 2.94-1.35 4.52-4.04 4.75.03-.24.04-.49.04-.75v-4c0-3.19-1.6-4.79-4.79-4.79h-6.4c-.26 0-.51.01-.75.04C6.27 3.35 7.86 2 10.79 2h6.4c3.19 0 4.79 1.6 4.79 4.79Z', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '2', - d: 'M13.495 13.25h.01m-3.51 0h.01m-3.51 0h.01', - }), - ], - }), - s: true, - }, -]) -export const Minus = /*#__PURE__*/ __createIcon('Minus', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M1.833 10c0-.369.299-.667.667-.667h15a.667.667 0 0 1 0 1.333h-15a.667.667 0 0 1-.667-.667Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Mnemonic = /*#__PURE__*/ __createIcon( - 'Mnemonic', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 25', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M2.865 5.878a2.8 2.8 0 0 1 2.8-2.8h13.69a.8.8 0 0 1 0 1.6H5.665a1.2 1.2 0 0 0-1.2 1.2v.803h13.87a2.8 2.8 0 0 1 2.8 2.8v10.138a2.8 2.8 0 0 1-2.8 2.8H5.665a2.8 2.8 0 0 1-2.8-2.8V5.88Zm1.6 2.403v11.338a1.2 1.2 0 0 0 1.2 1.2h12.67a1.2 1.2 0 0 0 1.2-1.2V9.481a1.2 1.2 0 0 0-1.2-1.2H4.465Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12.285 9.23a.8.8 0 0 1 .463 1.032l-3.484 9.155a.8.8 0 1 1-1.496-.569l3.484-9.155a.8.8 0 0 1 1.033-.463Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M15.769 19.881a.8.8 0 0 0 .463-1.032l-3.484-9.155a.8.8 0 0 0-1.495.569l3.484 9.155a.8.8 0 0 0 1.032.463Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M9.458 15.546a.8.8 0 0 1 .8-.8H13.9a.8.8 0 0 1 0 1.6h-3.642a.8.8 0 0 1-.8-.8Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, - ], - [24, 25], -) -export const More = /*#__PURE__*/ __createIcon('More', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2Zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2Zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2Z', - }), - }), - s: true, - }, -]) -export const NextIdAvatar = /*#__PURE__*/ __createIcon('NextIdAvatar', [ - { - c: ['dark'], - u: () => new URL('./general/NextIdAvatar.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/NextIdAvatar.light.svg', import.meta.url).href, - }, -]) -export const NextIdPersonaWarning = /*#__PURE__*/ __createIcon('NextIdPersonaWarning', [ - { - u: () => new URL('./general/NextIdPersonaWarning.svg', import.meta.url).href, - }, -]) -export const NFTHolder = /*#__PURE__*/ __createIcon( - 'NFTHolder', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 21 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M5.362 1.037A.575.575 0 0 1 5.77.33a.583.583 0 0 1 .711.41l.15.554L11.542 0l.153.56.758-.2.923 3.393-.757.2.154.563L7.856 5.81l2.503 9.234h-.258v1.937h.587v.581h.354l.464 1.71a.575.575 0 0 1-.409.708.583.583 0 0 1-.71-.411l-1.053-3.88h.076v-1.936h-.586v-.582h-.172L6.736 6.106 1.834 7.4l-.153-.561-.758.2L0 3.646l.757-.2-.153-.563 4.908-1.294-.15-.552Zm6.89 16.138-1.17-.002v-.58h-.303v.002h-.287v-1.06l.002.005v-.108h.202l.383.001v-.58l.847.002v-.004h.326v.486h-.003v.098l.492.001v-.004h.098v.712h-.003l.001.45h-.585v.581Zm.978-1.03h4.398v-.808h.815v-.807h.815v-.807h.815V9.687h-.815V8.073h-.815V6.459h-.815V5.65h.815v-.807h-.815v.807h-.814v-.807h-.815v-.807h-1.63V3.23h-.814v1.614h.815v.807h-.815v1.615h-.814v-.807h-.815V5.65h.815v-.807h-.815v.807h-.815v3.23h-.815v4.036h.815v1.548h1.532v.58h.587v1.1ZM9.02 14.14v.386l-.106-.386h.105ZM9.681 1.7l-1.737.458.177.65.434-.115.413 1.516.868-.23-.412-1.515.434-.115-.177-.65ZM5.465 2.812l2.17-.572.178.65-1.086.285.118.433.673-.177.118.433-.673.178.177.65-1.086.285-.589-2.165Zm-1.178.31.869-.228.589 2.165-.868.229-.118-.433-.217.057-.118-.433-.217.057.236.867-.869.229-.59-2.166.87-.229.117.433.217-.057.118.433.217-.057-.236-.866ZM15.184 8.88H16v.807h-.815V8.88Zm-1.63 2.422v-.808h3.26v.808h.814v3.228h-.814v.807h-3.26v-.807h-.814v-3.228h.815Zm-.032.936h.64v.507h.51v.633h-.51v.506h-.64v-.633h.511v-.38h-.51v-.633Zm2.723 0h.64v.633h-.512v.38h.511v.633h-.639v-.505h-.511v-.634h.511v-.507Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, - ], - [21, 20], -) -export const NFTRedPacket = /*#__PURE__*/ __createIcon('NFTRedPacket', [ - { - u: () => new URL('./general/NFTRedPacket.svg', import.meta.url).href, - }, -]) -export const OutlinedMask = /*#__PURE__*/ __createIcon('OutlinedMask', [ - { - u: () => new URL('./general/OutlinedMask.svg', import.meta.url).href, - }, -]) -export const PersonasOutline = /*#__PURE__*/ __createIcon( - 'PersonasOutline', - [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 21', - children: /*#__PURE__*/ _jsx('g', { - id: 'PersonasOutline_svg__base/me', - children: /*#__PURE__*/ _jsx('path', { - id: 'PersonasOutline_svg__Profile', - fillRule: 'evenodd', - stroke: 'currentColor', - strokeOpacity: '.78', - strokeWidth: '1.6', - d: 'M14.412 6.577A4.395 4.395 0 0 1 10 10.987a4.396 4.396 0 0 1-4.412-4.41A4.395 4.395 0 0 1 10 2.167a4.394 4.394 0 0 1 4.412 4.41ZM10 18.834c-3.615 0-6.667-.587-6.667-2.854 0-2.268 3.071-2.834 6.667-2.834 3.616 0 6.667.587 6.667 2.854 0 2.267-3.072 2.834-6.667 2.834Z', - clipRule: 'evenodd', - }), - }), - }), - s: true, - }, - ], - [20, 21], -) -export const Pin = /*#__PURE__*/ __createIcon('Pin', [ - { - c: ['dark'], - u: () => new URL('./general/Pin.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Pin.light.svg', import.meta.url).href, - }, -]) -export const Play = /*#__PURE__*/ __createIcon('Play', [ - { - u: () => new URL('./general/Play.svg', import.meta.url).href, - }, - { - c: ['dark'], - u: () => new URL('./general/Play.dark.svg', import.meta.url).href, - }, -]) -export const Plugin = /*#__PURE__*/ __createIcon('Plugin', [ - { - u: () => new URL('./general/Plugin.svg', import.meta.url).href, - }, -]) -export const Plugins = /*#__PURE__*/ __createIcon('Plugins', [ - { - u: () => new URL('./general/Plugins.svg', import.meta.url).href, - }, -]) -export const Plus = /*#__PURE__*/ __createIcon( - 'Plus', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 13 12', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M1.6 6a.4.4 0 0 1 .4-.4h9a.4.4 0 0 1 0 .8H2a.4.4 0 0 1-.4-.4Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M6.5 1.1a.4.4 0 0 1 .4.4v9a.4.4 0 0 1-.8 0v-9a.4.4 0 0 1 .4-.4Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, - ], - [13, 12], -) -export const PopupClose = /*#__PURE__*/ __createIcon('PopupClose', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'm6 6 12 12M6 18 18 6', - }), - }), - s: true, - }, -]) -export const PopupLink = /*#__PURE__*/ __createIcon('PopupLink', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 12 12', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M1.6 2.1a.5.5 0 0 1 .5-.5h3.529v.8H2.4v7.2h7.2V6.235h.8V9.9a.5.5 0 0 1-.5.5H2.1a.5.5 0 0 1-.5-.5V2.1ZM6.832 2c0-.22.18-.4.4-.4H10c.22 0 .4.18.4.4v2.747a.4.4 0 0 1-.8 0v-1.77l-4.935 5.02a.4.4 0 0 1-.57-.56L9.045 2.4H7.233a.4.4 0 0 1-.4-.4Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const PopupRestore = /*#__PURE__*/ __createIcon('PopupRestore', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 18 18', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'm13.777 8.974-3.636 1.02a.323.323 0 0 0-.213.458.38.38 0 0 0 .023.042l1.942 3.17a.46.46 0 0 0 .554.18.33.33 0 0 0 .177-.173l.501-.743c.863.446 1.46.842 1.802 1.673l.527 1.28a.457.457 0 0 0 .516.27.33.33 0 0 0 .27-.29l.115-1c.208-1.655-.805-3.351-2.512-4.207l.474-1.174c.074-.182-.037-.4-.249-.485a.452.452 0 0 0-.29-.021Zm-2.892 1.6 2.37-.667-.303.752c-.074.183.037.4.248.485 1.376.556 2.287 1.74 2.372 3.016-.514-1.052-1.464-1.911-2.629-2.38-.21-.085-.442-.006-.516.177l-.277.686-1.265-2.07Z', - }), - /*#__PURE__*/ _jsx('path', { - fillRule: 'evenodd', - d: 'M5.998 3.895c-.907.417-1.19.873-1.19 1.165 0 .292.283.747 1.19 1.165.856.394 2.08.654 3.465.654 1.384 0 2.609-.26 3.464-.654.908-.418 1.19-.873 1.19-1.165 0-.292-.283-.748-1.19-1.165-.855-.394-2.08-.654-3.464-.654-1.385 0-2.61.26-3.465.654Zm-.502-1.09c1.046-.482 2.45-.764 3.967-.764s2.92.282 3.966.764c.995.457 1.888 1.211 1.888 2.255 0 1.044-.893 1.797-1.888 2.255-1.046.482-2.449.764-3.966.764-1.518 0-2.92-.282-3.967-.764-.994-.458-1.888-1.211-1.888-2.255 0-1.044.894-1.798 1.888-2.255Zm.279 6.487c.708.375 1.738.655 2.942.738l-.082 1.197c-1.325-.091-2.529-.401-3.422-.875-.859-.455-1.605-1.165-1.605-2.115h1.2c0 .262.224.661.967 1.055Zm0 2.948c.708.374 1.738.654 2.942.737l-.082 1.197c-1.325-.091-2.529-.401-3.422-.875-.859-.455-1.605-1.165-1.605-2.115h1.2c0 .263.224.661.967 1.055Zm-.097 2.7c.755.4 2.1.713 4.374.713v1.2c-2.347 0-3.924-.318-4.935-.852-1.053-.557-1.509-1.365-1.509-2.223h1.2c0 .355.156.784.87 1.162Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fillRule: 'evenodd', - d: 'M14.717 4.358a.6.6 0 0 1 .6.6v2.548a.6.6 0 1 1-1.2 0V4.958a.6.6 0 0 1 .6-.6Zm-10.509 0a.6.6 0 0 1 .6.6v8.82a.6.6 0 0 1-1.2 0v-8.82a.6.6 0 0 1 .6-.6Z', - clipRule: 'evenodd', - }), - ], - }), - }), - s: true, - }, -]) -export const PopupTrash = /*#__PURE__*/ __createIcon('PopupTrash', [ - { - u: () => new URL('./general/PopupTrash.svg', import.meta.url).href, - }, -]) -export const PrimaryInfo = /*#__PURE__*/ __createIcon('PrimaryInfo', [ - { - u: () => new URL('./general/PrimaryInfo.svg', import.meta.url).href, - }, -]) -export const Provider = /*#__PURE__*/ __createIcon('Provider', [ - { - u: () => new URL('./general/Provider.svg', import.meta.url).href, - }, -]) -export const PublicKey = /*#__PURE__*/ __createIcon('PublicKey', [ - { - c: ['dark'], - u: () => new URL('./general/PublicKey.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/PublicKey.light.svg', import.meta.url).href, - }, -]) -export const PublicKey2 = /*#__PURE__*/ __createIcon('PublicKey2', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { - id: 'PublicKey2_svg__base/key', - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - id: 'PublicKey2_svg__Ellipse 85 (Stroke)', - d: 'M11.856 4.691a3.244 3.244 0 1 0 3.996 5.111 3.244 3.244 0 0 0-3.996-5.11Zm-1.608 5.375a4.577 4.577 0 1 1 7.212-5.638 4.577 4.577 0 0 1-7.212 5.638Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'PublicKey2_svg__Ellipse 86 (Stroke)', - d: 'M13.433 6.71a.684.684 0 1 0 .842 1.076.684.684 0 0 0-.842-1.077Zm-1.168 1.78a2.017 2.017 0 1 1 3.178-2.484 2.017 2.017 0 0 1-3.178 2.484Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'PublicKey2_svg__Vector 11 (Stroke)', - d: 'M1.924 16.573a.667.667 0 0 1 .114-.935l3.776-2.953 4.494-3.494a.667.667 0 0 1 .819 1.053L7.16 13.328l.95 1.215a.667.667 0 0 1-1.05.821l-.952-1.216-1.043.815 1.556 1.99a.667.667 0 1 1-1.05.82l-1.556-1.99-1.157.905a.667.667 0 0 1-.935-.115Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Questions = /*#__PURE__*/ __createIcon('Questions', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12 4.8a7.2 7.2 0 1 0 0 14.4 7.2 7.2 0 0 0 0-14.4ZM3.2 12a8.8 8.8 0 1 1 17.6 0 8.8 8.8 0 0 1-17.6 0Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M11.9 16.787a.69.69 0 1 1-.057-1.38.69.69 0 0 1 .056 1.38Zm2.956-6.713c-.04.738-.267 1.317-1.397 2.448-.571.573-.932 1.004-.97 1.38a.574.574 0 0 1-1.14-.112c.076-.795.66-1.437 1.3-2.08 1.027-1.024 1.046-1.35 1.063-1.697a1.475 1.475 0 0 0-.421-1.1 1.79 1.79 0 0 0-1.293-.55h-.003a1.714 1.714 0 0 0-1.707 1.711.574.574 0 0 1-1.147 0c0-.762.295-1.478.833-2.019a2.838 2.838 0 0 1 2.018-.838 2.947 2.947 0 0 1 2.13.906c.513.537.772 1.23.734 1.951Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M14.052 8.19a2.85 2.85 0 0 0-2.06-.876 2.74 2.74 0 0 0-1.948.81 2.744 2.744 0 0 0-.806 1.95.476.476 0 1 0 .952 0 1.811 1.811 0 0 1 1.804-1.809h.004a1.887 1.887 0 0 1 1.363.58 1.572 1.572 0 0 1 .449 1.172c-.01.18-.02.365-.165.636-.142.266-.411.612-.927 1.127-.644.646-1.2 1.264-1.272 2.02a.475.475 0 1 0 .947.092c.02-.213.132-.432.302-.664a7.27 7.27 0 0 1 .694-.775c.563-.563.895-.982 1.09-1.348a2.31 2.31 0 0 0 .28-1.036 2.507 2.507 0 0 0-.707-1.879Zm-2.06-1.07a3.044 3.044 0 0 1 2.2.935 2.7 2.7 0 0 1 .761 2.025c-.02.38-.09.723-.301 1.117-.21.39-.556.826-1.124 1.394a7.08 7.08 0 0 0-.675.753c-.163.22-.25.405-.266.568a.67.67 0 0 1-1.31.13m-.026-.261c.081-.835.692-1.5 1.329-2.14.512-.51.765-.84.893-1.08.125-.233.133-.385.142-.553a1.376 1.376 0 0 0-.394-1.027V8.98a1.692 1.692 0 0 0-1.223-.52h-.003a1.616 1.616 0 0 0-1.61 1.614.67.67 0 1 1-1.342 0c0-.788.306-1.529.862-2.088a2.936 2.936 0 0 1 2.087-.867v.098-.098m-.74 6.662.096.01-.097-.01m.644 1.723a.594.594 0 0 0-.234 1.147.592.592 0 1 0 .234-1.147Zm-.303-.145a.787.787 0 1 1 .558 1.473.787.787 0 0 1-.558-1.473Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const RadioButtonChecked = /*#__PURE__*/ __createIcon('RadioButtonChecked', [ - { - u: () => new URL('./general/RadioButtonChecked.svg', import.meta.url).href, - }, -]) -export const RadioButtonUnChecked = /*#__PURE__*/ __createIcon('RadioButtonUnChecked', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 21 21', - children: /*#__PURE__*/ _jsx('circle', { - cx: '10.166', - cy: '10.131', - r: '9', - stroke: 'currentColor', - strokeWidth: '2', - }), - }), - s: true, - }, -]) -export const RadioNo = /*#__PURE__*/ __createIcon('RadioNo', [ - { - c: ['dark'], - u: () => new URL('./general/RadioNo.dark.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - stroke: 'currentColor', - viewBox: '0 0 21 21', - children: /*#__PURE__*/ _jsx('circle', { - cx: '10.166', - cy: '10.437', - r: '9', - stroke: '#6F6F6F', - strokeWidth: '2', - }), - }), - s: true, - }, - { - c: ['light'], - u: () => new URL('./general/RadioNo.light.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - stroke: 'currentColor', - viewBox: '0 0 21 21', - children: /*#__PURE__*/ _jsx('circle', { - cx: '10.166', - cy: '10.911', - r: '9', - stroke: '#E6E7E8', - strokeWidth: '2', - }), - }), - s: true, - }, -]) -export const ReceiveColorful = /*#__PURE__*/ __createIcon('ReceiveColorful', [ - { - u: () => new URL('./general/ReceiveColorful.svg', import.meta.url).href, - }, -]) -export const RedPacket = /*#__PURE__*/ __createIcon('RedPacket', [ - { - u: () => new URL('./general/RedPacket.svg', import.meta.url).href, - }, -]) -export const Refresh = /*#__PURE__*/ __createIcon('Refresh', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M12 4.934a7.198 7.198 0 0 0-6.283 3.68.8.8 0 1 1-1.395-.783A8.8 8.8 0 1 1 3.2 12.134a.8.8 0 0 1 1.6 0 7.2 7.2 0 1 0 7.2-7.2Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M4.797 3.47a.8.8 0 0 1 .8.8v3.2h3.2a.8.8 0 0 1 0 1.6h-4a.8.8 0 0 1-.8-.8v-4a.8.8 0 0 1 .8-.8Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Repost = /*#__PURE__*/ __createIcon('Repost', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: [ - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'M2.983 4.3h11.534c1.383 0 2.5 1.117 2.5 2.5v2.767', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'M5.617 1.667 2.983 4.3l2.634 2.633m11.4 8.767H5.483a2.497 2.497 0 0 1-2.5-2.5v-2.767', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.5', - d: 'm14.384 18.333 2.633-2.633-2.633-2.633', - }), - ], - }), - s: true, - }, -]) -export const Restore = /*#__PURE__*/ __createIcon('Restore', [ - { - u: () => new URL('./general/Restore.svg', import.meta.url).href, - }, -]) -export const RestoreColorful = /*#__PURE__*/ __createIcon('RestoreColorful', [ - { - u: () => new URL('./general/RestoreColorful.svg', import.meta.url).href, - }, -]) -export const ResultNo = /*#__PURE__*/ __createIcon('ResultNo', [ - { - u: () => new URL('./general/ResultNo.svg', import.meta.url).href, - }, -]) -export const ResultYes = /*#__PURE__*/ __createIcon('ResultYes', [ - { - u: () => new URL('./general/ResultYes.svg', import.meta.url).href, - }, -]) -export const Retweet = /*#__PURE__*/ __createIcon('Retweet', [ - { - c: ['dark'], - u: () => new URL('./general/Retweet.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/Retweet.light.svg', import.meta.url).href, - }, -]) -export const Right = /*#__PURE__*/ __createIcon('Right', [ - { - u: () => new URL('./general/Right.svg', import.meta.url).href, - }, -]) -export const RightArrow = /*#__PURE__*/ __createIcon('RightArrow', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 25 25', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M8.935 20.063a.8.8 0 0 1 0-1.132l6.434-6.434-6.434-6.434a.8.8 0 1 1 1.13-1.132l7 7a.8.8 0 0 1 0 1.132l-7 7a.8.8 0 0 1-1.13 0Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Risk = /*#__PURE__*/ __createIcon('Risk', [ - { - u: () => new URL('./general/Risk.svg', import.meta.url).href, - }, -]) -export const Search = /*#__PURE__*/ __createIcon('Search', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M4.271 9.41a4.93 4.93 0 1 1 8.478 3.424.617.617 0 0 0-.122.123A4.93 4.93 0 0 1 4.272 9.41zm8.837 4.79a6.18 6.18 0 1 1 .884-.884l2.596 2.596a.625.625 0 1 1-.884.884L13.108 14.2z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const SecurityRisk = /*#__PURE__*/ __createIcon('SecurityRisk', [ - { - u: () => new URL('./general/SecurityRisk.svg', import.meta.url).href, - }, -]) -export const SecurityWarning = /*#__PURE__*/ __createIcon('SecurityWarning', [ - { - u: () => new URL('./general/SecurityWarning.svg', import.meta.url).href, - }, -]) -export const Selected = /*#__PURE__*/ __createIcon('Selected', [ - { - c: ['dark'], - u: () => new URL('./general/Selected.dark.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'none', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: '#101010', - d: 'M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M8 14.667A6.667 6.667 0 1 0 8 1.333a6.667 6.667 0 0 0 0 13.334Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M11.115 5.507c.272.247.293.67.045.941l-3.333 3.667a.667.667 0 0 1-.928.058l-2.333-2a.667.667 0 1 1 .868-1.012l1.842 1.578 2.897-3.187a.667.667 0 0 1 .942-.045Z', - clipRule: 'evenodd', - }), - ], - }), - }), - s: true, - }, - { - c: ['light'], - u: () => new URL('./general/Selected.light.svg', import.meta.url).href, - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'none', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: '#F5F5F5', - d: 'M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M8 14.667A6.667 6.667 0 1 0 8 1.333a6.667 6.667 0 0 0 0 13.334Z', - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M11.115 5.507c.272.247.293.67.045.941l-3.333 3.667a.667.667 0 0 1-.928.058l-2.333-2a.667.667 0 1 1 .868-1.012l1.842 1.578 2.897-3.187a.667.667 0 0 1 .942-.045Z', - clipRule: 'evenodd', - }), - ], - }), - }), - s: true, - }, -]) -export const Self = /*#__PURE__*/ __createIcon('Self', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 25 25', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M17.794 7.397a5.274 5.274 0 0 1-5.294 5.292 5.275 5.275 0 0 1-5.294-5.292A5.274 5.274 0 0 1 12.5 2.105a5.273 5.273 0 0 1 5.294 5.292ZM12.5 22.105c-4.338 0-8-.705-8-3.425 0-2.72 3.685-3.4 8-3.4 4.339 0 8 .704 8 3.424 0 2.721-3.685 3.401-8 3.401Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const Send = /*#__PURE__*/ __createIcon('Send', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 18 18', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M16.345 3.382 2.425 7.926l3.397 2.032a.75.75 0 0 1 .365.643v2.277l2.3-.933a.75.75 0 0 1 .648.041l5.208 2.92 2.002-11.524Zm.339-1.372a.75.75 0 0 1 .971.84l-2.239 12.89a.75.75 0 0 1-1.106.525L8.737 13.14l-2.718 1.1a.75.75 0 0 1-1.032-.695v-2.69L.96 8.45a.75.75 0 0 1 .152-1.357l15.57-5.082ZM14.18 5.656a.6.6 0 0 1-.06.846l-4.234 3.679a.6.6 0 0 1-.787-.906l4.234-3.679a.6.6 0 0 1 .847.06Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const SendColorful = /*#__PURE__*/ __createIcon('SendColorful', [ - { - u: () => new URL('./general/SendColorful.svg', import.meta.url).href, - }, -]) -export const Setting = /*#__PURE__*/ __createIcon('Setting', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 18 18', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'none', - stroke: 'currentColor', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M9 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M14.55 11.25a1.238 1.238 0 0 0 .247 1.365l.045.045a1.5 1.5 0 0 1-1.06 2.562 1.5 1.5 0 0 1-1.062-.44l-.045-.044a1.238 1.238 0 0 0-1.365-.248 1.238 1.238 0 0 0-.75 1.133v.127a1.5 1.5 0 1 1-3 0v-.068a1.238 1.238 0 0 0-.81-1.132 1.238 1.238 0 0 0-1.365.247l-.045.045a1.5 1.5 0 1 1-2.123-2.122l.046-.045a1.238 1.238 0 0 0 .247-1.365 1.238 1.238 0 0 0-1.132-.75H2.25a1.5 1.5 0 1 1 0-3h.067a1.237 1.237 0 0 0 1.133-.81 1.237 1.237 0 0 0-.248-1.365l-.044-.045A1.5 1.5 0 1 1 5.28 3.217l.045.046a1.237 1.237 0 0 0 1.365.247h.06a1.237 1.237 0 0 0 .75-1.132V2.25a1.5 1.5 0 0 1 3 0v.067a1.238 1.238 0 0 0 .75 1.133 1.237 1.237 0 0 0 1.365-.248l.045-.044a1.5 1.5 0 1 1 2.123 2.122l-.045.045a1.237 1.237 0 0 0-.248 1.365v.06a1.237 1.237 0 0 0 1.133.75h.127a1.5 1.5 0 0 1 0 3h-.068a1.238 1.238 0 0 0-1.132.75z', - }), - ], - }), - }), - s: true, - }, -]) -export const Settings = /*#__PURE__*/ __createIcon('Settings', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'm12 23-9.5-5.5v-11L12 1l9.5 5.5v11L12 23zm0-19.688L4.5 7.653v8.694l7.5 4.342 7.5-4.342V7.653L12 3.311v.001zM12 16a4 4 0 1 1 2.828-1.172A4.027 4.027 0 0 1 12 16zm0-6a2 2 0 1 0-.001 4A2 2 0 0 0 12 10z', - }), - }), - s: true, - }, -]) -export const Settings2 = /*#__PURE__*/ __createIcon('Settings2', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'm17.808 3.606 4.345 7.528c.31.537.31 1.205 0 1.744l-4.345 7.526c-.312.537-.89.872-1.512.872h-8.69c-.624 0-1.2-.333-1.512-.872l-4.346-7.526a1.743 1.743 0 0 1 0-1.744l4.299-7.441c.34-.59.977-.959 1.66-.959h8.59c.62 0 1.2.333 1.51.872Zm-9.415 8.4a3.562 3.562 0 0 1 3.557-3.559 3.564 3.564 0 0 1 3.558 3.56 3.56 3.56 0 0 1-3.558 3.556 3.562 3.562 0 0 1-3.557-3.558Zm1.64.001a1.92 1.92 0 0 0 1.917 1.918 1.92 1.92 0 0 0 1.918-1.918 1.92 1.92 0 0 0-1.918-1.917 1.92 1.92 0 0 0-1.917 1.917Z', - clipRule: 'evenodd', - }), - }), - s: true, - }, -]) -export const SharpMask = /*#__PURE__*/ __createIcon('SharpMask', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M3.2 1.5h13.6a1.7 1.7 0 0 1 1.7 1.7v13.6a1.7 1.7 0 0 1-1.7 1.7H3.2a1.7 1.7 0 0 1-1.7-1.7V3.2a1.7 1.7 0 0 1 1.7-1.7Zm14 9.7v-4H2.8v8.56a1.44 1.44 0 0 0 1.44 1.44h11.52a1.44 1.44 0 0 0 1.44-1.44V12.4h-2.326a5.44 5.44 0 0 1-4.883 3.04 5.442 5.442 0 0 1-5.308-4.24H17.2Zm-7.21 3.05a4.235 4.235 0 0 0 3.502-1.85H6.488a4.236 4.236 0 0 0 3.503 1.85Zm-5.288-4.17a2.24 2.24 0 0 1 4.434 0H7.91a1.04 1.04 0 0 0-1.98 0H4.703Zm6.16 0a2.24 2.24 0 0 1 4.434 0H14.07a1.04 1.04 0 0 0-1.98 0h-1.227ZM17.2 4.24a1.438 1.438 0 0 0-1.44-1.44H4.24A1.44 1.44 0 0 0 2.8 4.24V6h14.4V4.24Z', - }), - }), - s: true, - }, -]) -export const ShutDown = /*#__PURE__*/ __createIcon( - 'ShutDown', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 17 16', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M5.585 3.951a.6.6 0 0 1-.08.845A5.222 5.222 0 0 0 3.6 8.833a5.235 5.235 0 0 0 5.233 5.234 5.235 5.235 0 0 0 5.234-5.234c0-1.624-.74-3.076-1.905-4.037a.6.6 0 0 1 .764-.925 6.422 6.422 0 0 1 2.34 4.962 6.436 6.436 0 0 1-6.433 6.434A6.435 6.435 0 0 1 2.4 8.833c0-1.997.912-3.783 2.34-4.962a.6.6 0 0 1 .845.08Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M8.833 1.4a.6.6 0 0 1 .6.6v5.334a.6.6 0 0 1-1.2 0V2a.6.6 0 0 1 .6-.6Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, - ], - [17, 16], -) -export const SignUpAccount = /*#__PURE__*/ __createIcon('SignUpAccount', [ - { - u: () => new URL('./general/SignUpAccount.png', import.meta.url).href, - }, -]) -export const SmartPay = /*#__PURE__*/ __createIcon('SmartPay', [ - { - u: () => new URL('./general/SmartPay.svg', import.meta.url).href, - }, -]) -export const Star = /*#__PURE__*/ __createIcon('Star', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M11.742 17.426a.5.5 0 0 1 .516 0l4.908 2.962a.5.5 0 0 0 .745-.542l-1.302-5.582a.5.5 0 0 1 .16-.492l4.337-3.758a.5.5 0 0 0-.284-.876l-5.712-.492a.5.5 0 0 1-.418-.303L12.46 3.084a.5.5 0 0 0-.92 0l-2.232 5.26a.5.5 0 0 1-.418.302l-5.713.493a.5.5 0 0 0-.285.875l4.33 3.759a.5.5 0 0 1 .16.49l-1.295 5.585a.5.5 0 0 0 .746.54l4.909-2.962z', - }), - }), - s: true, - }, -]) -export const Success = /*#__PURE__*/ __createIcon('Success', [ - { - u: () => new URL('./general/Success.svg', import.meta.url).href, - }, -]) -export const SuccessForSnackBar = /*#__PURE__*/ __createIcon('SuccessForSnackBar', [ - { - u: () => new URL('./general/SuccessForSnackBar.svg', import.meta.url).href, - }, -]) -export const Sun = /*#__PURE__*/ __createIcon('Sun', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M8 12.992c.254 0 .466.183.51.425l.008.093v.972a.518.518 0 0 1-1.027.093l-.009-.093v-.972c0-.286.232-.518.518-.518Zm4.181-1.528.081.066.688.688a.518.518 0 0 1-.651.798l-.081-.066-.688-.688a.518.518 0 0 1 .651-.798Zm-7.711.066c.177.177.199.45.066.651l-.066.081-.688.688a.518.518 0 0 1-.798-.651l.066-.081.688-.688a.518.518 0 0 1 .732 0ZM8 4.296a3.704 3.704 0 1 1 0 7.408 3.704 3.704 0 0 1 0-7.408Zm6.482 3.186a.518.518 0 0 1 .093 1.027l-.093.009h-.972a.518.518 0 0 1-.093-1.027l.093-.009h.972Zm-11.992 0a.518.518 0 0 1 .093 1.027l-.093.009h-.972a.518.518 0 0 1-.093-1.027l.093-.009h.972ZM12.95 3.05c.177.177.199.45.066.651l-.066.081-.688.688a.518.518 0 0 1-.798-.651l.066-.081.688-.688a.518.518 0 0 1 .732 0ZM3.7 2.984l.081.066.688.688a.518.518 0 0 1-.651.798l-.081-.066-.688-.688a.518.518 0 0 1 .651-.798ZM8 1c.254 0 .466.183.51.425l.008.093v.972a.518.518 0 0 1-1.027.093l-.009-.093v-.972C7.482 1.232 7.714 1 8 1Z', - }), - }), - s: true, - }, -]) -export const Swap = /*#__PURE__*/ __createIcon('Swap', [ - { - u: () => new URL('./general/Swap.svg', import.meta.url).href, - }, -]) -export const SwapColorful = /*#__PURE__*/ __createIcon('SwapColorful', [ - { - u: () => new URL('./general/SwapColorful.svg', import.meta.url).href, - }, -]) -export const SwitchLogo = /*#__PURE__*/ __createIcon('SwitchLogo', [ - { - c: ['dark'], - u: () => new URL('./general/SwitchLogo.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/SwitchLogo.light.svg', import.meta.url).href, - }, -]) -export const TelegramRoundGray = /*#__PURE__*/ __createIcon('TelegramRoundGray', [ - { - u: () => new URL('./general/TelegramRoundGray.svg', import.meta.url).href, - }, -]) -export const Tick = /*#__PURE__*/ __createIcon('Tick', [ - { - u: () => new URL('./general/Tick.svg', import.meta.url).href, - }, -]) -export const Time = /*#__PURE__*/ __createIcon('Time', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { - id: 'Time_svg__base/time', - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - id: 'Time_svg__Vector (Stroke)', - d: 'M10 3.166a6.833 6.833 0 1 0 0 13.667 6.833 6.833 0 0 0 0-13.667ZM1.834 9.999a8.167 8.167 0 1 1 16.333 0 8.167 8.167 0 0 1-16.334 0Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'Time_svg__Vector (Stroke)_2', - d: 'M10 5.233c.368 0 .667.298.667.666v4.125a.667.667 0 1 1-1.333 0V5.9c0-.368.298-.666.666-.666Z', - }), - /*#__PURE__*/ _jsx('path', { - id: 'Time_svg__Vector (Stroke)_3', - d: 'M9.423 9.69a.667.667 0 0 1 .91-.244l3.021 1.745a.667.667 0 0 1-.666 1.154l-3.021-1.744a.667.667 0 0 1-.244-.91Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Tips = /*#__PURE__*/ __createIcon('Tips', [ - { - u: () => new URL('./general/Tips.svg', import.meta.url).href, - }, -]) -export const TransactionFailed = /*#__PURE__*/ __createIcon('TransactionFailed', [ - { - u: () => new URL('./general/TransactionFailed.svg', import.meta.url).href, - }, -]) -export const Trash = /*#__PURE__*/ __createIcon( - 'Trash', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 25 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M2.5 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2h-18a1 1 0 0 1-1-1Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M10.5 3a1 1 0 0 0-1 1v1h6V4a1 1 0 0 0-1-1h-4Zm7 2V4a3 3 0 0 0-3-3h-4a3 3 0 0 0-3 3v1h-2a1 1 0 0 0-1 1v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V6a1 1 0 0 0-1-1h-2Zm-11 2v13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7h-12Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M10.5 10a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0v-6a1 1 0 0 1 1-1Zm4 0a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0v-6a1 1 0 0 1 1-1Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, - ], - [25, 24], -) -export const TrashLine = /*#__PURE__*/ __createIcon('TrashLine', [ - { - u: () => new URL('./general/TrashLine.svg', import.meta.url).href, - }, -]) -export const Tutorial = /*#__PURE__*/ __createIcon('Tutorial', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M18.227 10.117v-2.44c.27-.285.284-.57.27-.732-.067-.637-.772-.895-1.002-.99-1.681-.636-4.799-1.897-6.52-2.602-1.044-.515-1.382-.42-2.399-.013-.271.108-4.758 1.775-6.492 2.494-.95.393-1.085.976-1.085 1.3 0 .543.38 1.03 1.071 1.316l2.074.84v5.449l.176.176c.081.081 1.938 1.952 5.34 1.952 3.186 0 5.002-1.884 5.083-1.966l.163-.176v-5.38l.772-.326-.027 1.111-.623 1.044 1.884 1.79 2.087-1.817-.772-1.03Zm-4.541 4.12A6.337 6.337 0 0 1 9.66 15.66c-2.358 0-3.808-1.03-4.296-1.423V9.791l3.063 1.247h.013c.976.366 1.722.34 2.657-.067l2.589-1.098v4.364Zm3.267-2.928-.38-.352.285-.474.054-2.169a.615.615 0 0 0-.176-.678.632.632 0 0 0-.773-.054L10.61 9.846c-.637.284-1.057.298-1.735.054l-6.33-2.575c-.162-.068-.257-.136-.298-.19.04-.04.136-.109.312-.19 1.735-.718 6.411-2.467 6.465-2.48h.014c.813-.326.813-.326 1.423-.014l.04.014c1.709.69 4.799 1.951 6.507 2.602v3.456l.339.448-.393.338Z', - }), - }), - s: true, - }, -]) -export const Twitter = /*#__PURE__*/ __createIcon('Twitter', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M13.33 4.459a2.867 2.867 0 0 0 1.258-1.583 5.728 5.728 0 0 1-1.816.694 2.867 2.867 0 0 0-4.878 2.61 8.123 8.123 0 0 1-5.896-2.99 2.867 2.867 0 0 0 .886 3.82 2.85 2.85 0 0 1-1.296-.358v.036c0 1.362.96 2.535 2.296 2.805a2.867 2.867 0 0 1-1.292.05 2.867 2.867 0 0 0 2.672 1.986 5.74 5.74 0 0 1-3.553 1.225c-.228 0-.457-.013-.683-.04A8.099 8.099 0 0 0 5.414 14a8.085 8.085 0 0 0 8.13-8.51 5.812 5.812 0 0 0 1.429-1.482 5.72 5.72 0 0 1-1.643.45Z', - }), - }), - s: true, - }, -]) -export const TwitterStroke = /*#__PURE__*/ __createIcon( - 'TwitterStroke', - [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 21 20', - children: [ - /*#__PURE__*/ _jsx('g', { - clipPath: "url('#TwitterStroke_svg__twitter-stroke')", - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12.664 2.113a4.4 4.4 0 0 1 4.575.898 8.416 8.416 0 0 0 2.043-1.057.667.667 0 0 1 1.033.703 7.101 7.101 0 0 1-1.674 3.127c.017.154.025.308.026.463v.002c0 5.025-2.416 8.696-5.875 10.537-3.441 1.832-7.848 1.815-11.782-.37a.667.667 0 0 1 .35-1.25 9.034 9.034 0 0 0 4.42-.957c-1.371-.793-2.336-1.75-2.992-2.785-.817-1.291-1.127-2.66-1.183-3.894-.056-1.232.14-2.342.346-3.14a10.736 10.736 0 0 1 .396-1.223l.03-.074.01-.02.002-.007.015-.032.596.299.546-.384a8.217 8.217 0 0 0 6.287 3.483V6.28a4.4 4.4 0 0 1 2.831-4.166ZM3.248 4.7l-.006.023a9.42 9.42 0 0 0-.305 2.746c.048 1.058.311 2.188.978 3.241.664 1.049 1.754 2.06 3.523 2.847a.667.667 0 0 1 .103 1.16 10.365 10.365 0 0 1-3.547 1.537c2.875.896 5.804.615 8.172-.645 3-1.597 5.167-4.8 5.168-9.358a3.178 3.178 0 0 0-.055-.568.666.666 0 0 1 .186-.6c.326-.321.61-.678.85-1.063a9.753 9.753 0 0 1-1.073.393.667.667 0 0 1-.695-.201 3.066 3.066 0 0 0-5.38 2.053v.843a.667.667 0 0 1-.65.666A9.55 9.55 0 0 1 3.248 4.7Z', - clipRule: 'evenodd', - }), - }), - /*#__PURE__*/ _jsx('defs', { - children: /*#__PURE__*/ _jsx('clipPath', { - id: 'TwitterStroke_svg__twitter-stroke', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M.5 0h20v20H.5z', - }), - }), - }), - ], - }), - s: true, - }, - ], - [21, 20], -) -export const TxIn = /*#__PURE__*/ __createIcon('TxIn', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 16 16', - children: /*#__PURE__*/ _jsx('path', { - fill: 'none', - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.25', - d: 'M14 10v2.667A1.334 1.334 0 0 1 12.667 14H3.333A1.334 1.334 0 0 1 2 12.667V10m2.667-3.333L8 10l3.333-3.333M8 10V2', - }), - }), - s: true, - }, -]) -export const TxOut = /*#__PURE__*/ __createIcon('TxOut', [ - { - u: () => new URL('./general/TxOut.svg', import.meta.url).href, - }, -]) -export const Undo = /*#__PURE__*/ __createIcon('Undo', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeMiterlimit: '10', - strokeWidth: '1.6', - d: 'M7.13 18.31h8c2.76 0 5-2.24 5-5s-2.24-5-5-5h-11', - }), - /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '1.6', - d: 'M6.43 10.81 3.87 8.25l2.56-2.56', - }), - ], - }), - s: true, - }, -]) -export const Upload = /*#__PURE__*/ __createIcon('Upload', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M12.306 1.26A.8.8 0 0 1 12.8 2v6a.8.8 0 0 1-1.6 0V3.93l-.634.635a.8.8 0 0 1-1.131-1.131l2-2a.8.8 0 0 1 .871-.174Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M11.434 1.434a.8.8 0 0 1 1.132 0l2 2a.8.8 0 0 1-1.132 1.13l-2-2a.8.8 0 0 1 0-1.13ZM2.842 12.716c.731-1.058 2.083-1.517 4.158-1.517.552 0 .982.057 1.37.209.364.141.642.35.88.529l.03.022a.8.8 0 0 1 .104.094l1.01 1.078a2.198 2.198 0 0 0 3.202.001l.003-.002 1.02-1.08a.803.803 0 0 1 .101-.09l.03-.023c.239-.18.516-.388.88-.53.388-.15.818-.208 1.37-.208 2.076 0 3.427.459 4.158 1.517.35.505.502 1.075.573 1.622.07.53.07 1.101.07 1.633v1.028c0 1.451-.259 2.928-1.185 4.046-.951 1.148-2.468 1.754-4.616 1.754H8c-2.576 0-4.177-.57-5.033-1.81-.412-.596-.595-1.274-.682-1.94-.085-.65-.085-1.352-.085-2.02V15.97c0-.532 0-1.102.07-1.633.07-.547.223-1.117.572-1.622Zm1.014 1.83c-.055.417-.056.888-.056 1.453v1c0 .703.001 1.306.072 1.842.069.53.198.93.411 1.239.395.57 1.293 1.12 3.717 1.12h8c1.852 0 2.836-.514 3.384-1.176.574-.693.816-1.716.816-3.025v-1c0-.565-.001-1.036-.056-1.454-.053-.41-.151-.702-.302-.92-.269-.39-.917-.826-2.842-.826-.448 0-.658.048-.79.1-.131.05-.24.124-.475.3l-.97 1.027-.002.001a3.799 3.799 0 0 1-5.534.002l-.003-.003-.962-1.029c-.235-.175-.343-.247-.474-.299-.132-.05-.342-.099-.79-.099-1.924 0-2.573.437-2.842.826-.15.218-.249.51-.302.92Z', - clipRule: 'evenodd', - }), - /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - fillRule: 'evenodd', - d: 'M8.797 5.961a.8.8 0 0 1-.72.874c-1.355.13-1.796.54-1.996.915-.261.49-.28 1.179-.28 2.248v2a.8.8 0 1 1-1.6 0V9.894c-.001-.92-.002-2.015.469-2.897.55-1.03 1.609-1.596 3.253-1.755a.8.8 0 0 1 .874.72Zm6.407 0a.8.8 0 0 1 .873-.719c1.645.159 2.704.725 3.254 1.755.47.882.47 1.977.47 2.897v2.104a.8.8 0 1 1-1.6 0v-2c0-1.07-.02-1.759-.282-2.248-.2-.375-.64-.784-1.996-.915a.8.8 0 0 1-.719-.874Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const USD = /*#__PURE__*/ __createIcon('USD', [ - { - u: () => new URL('./general/USD.svg', import.meta.url).href, - }, -]) -export const User = /*#__PURE__*/ __createIcon('User', [ - { - u: () => new URL('./general/User.svg', import.meta.url).href, - }, -]) -export const UserPlus = /*#__PURE__*/ __createIcon('UserPlus', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - stroke: 'currentColor', - strokeLinecap: 'round', - strokeLinejoin: 'round', - strokeWidth: '2', - d: 'M8 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0Zm8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4', - }), - }), - s: true, - }, -]) -export const Verification = /*#__PURE__*/ __createIcon('Verification', [ - { - j: () => - /*#__PURE__*/ _jsxs('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M10.705 1.603A1.699 1.699 0 0 1 12 1a1.691 1.691 0 0 1 1.295.603l.816.968a1.695 1.695 0 0 0 1.698.555l1.229-.302a1.7 1.7 0 0 1 2.095 1.53l.094 1.265a1.705 1.705 0 0 0 1.05 1.45l1.168.48c.97.4 1.353 1.58.8 2.476l-.665 1.079a1.71 1.71 0 0 0 0 1.793l.666 1.08c.552.895.17 2.074-.8 2.472l-1.17.483a1.703 1.703 0 0 0-1.05 1.45l-.093 1.265a1.712 1.712 0 0 1-.695 1.252 1.695 1.695 0 0 1-1.4.277l-1.229-.302a1.69 1.69 0 0 0-1.697.555l-.817.968A1.699 1.699 0 0 1 12 23a1.691 1.691 0 0 1-1.295-.603l-.816-.968a1.698 1.698 0 0 0-1.698-.555l-1.229.302a1.7 1.7 0 0 1-2.095-1.53l-.094-1.265a1.71 1.71 0 0 0-1.05-1.45l-1.168-.482a1.706 1.706 0 0 1-.8-2.472l.665-1.08a1.71 1.71 0 0 0 0-1.793l-.665-1.08a1.71 1.71 0 0 1 .8-2.473l1.17-.481a1.707 1.707 0 0 0 1.05-1.451l.092-1.266a1.71 1.71 0 0 1 .695-1.252 1.694 1.694 0 0 1 1.4-.277l1.229.302a1.694 1.694 0 0 0 1.698-.555l.816-.968Z', - style: { - '--default-color': '#1C68F3', - fill: 'var(--icon-color, var(--default-color, currentColor))', - }, - }), - /*#__PURE__*/ _jsx('path', { - fill: '#fff', - fillRule: 'evenodd', - d: 'M17.698 8.776a.75.75 0 0 1 0 1.06l-6.867 6.866a.75.75 0 0 1-1.06 0l-3.21-3.209a.75.75 0 1 1 1.061-1.06l2.679 2.678 6.336-6.335a.75.75 0 0 1 1.06 0Z', - clipRule: 'evenodd', - }), - ], - }), - s: true, - }, -]) -export const Wallet = /*#__PURE__*/ __createIcon('Wallet', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M6.2 11.15a.8.8 0 0 1 .8-.8h6a.8.8 0 0 1 0 1.6H7a.8.8 0 0 1-.8-.8Zm-.51-7.51A2.887 2.887 0 0 0 2.8 6.53v4.62a.8.8 0 0 1-1.6 0V6.53a4.487 4.487 0 0 1 4.49-4.49h5.62c1.182 0 2.298.367 3.131 1.082.845.724 1.36 1.774 1.36 3.027a.8.8 0 0 1-1.6 0c0-.787-.311-1.392-.802-1.813-.501-.43-1.23-.697-2.089-.697H5.69Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M6 6.95a3.199 3.199 0 0 0-3.2 3.2v7a3.2 3.2 0 0 0 3.2 3.2h10a3.2 3.2 0 0 0 3.2-3.2v-.65h-.13c-1.254 0-2.542-.772-2.902-2.113l-.002-.004a2.813 2.813 0 0 1 .75-2.751 2.793 2.793 0 0 1 2.004-.832h.28v-.65c0-1.759-1.442-3.2-3.2-3.2H6Zm-4.8 3.2c0-2.652 2.148-4.8 4.8-4.8h10c2.642 0 4.8 2.158 4.8 4.8v1.45a.8.8 0 0 1-.8.8h-1.08a1.193 1.193 0 0 0-.886.377 1.214 1.214 0 0 0-.32 1.197c.14.518.691.926 1.356.926H20a.8.8 0 0 1 .8.8v1.45a4.8 4.8 0 0 1-4.8 4.8H6a4.799 4.799 0 0 1-4.8-4.8v-7Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M18.92 12.399c-.345 0-.648.134-.866.358l-.015.014c-.256.25-.398.605-.362.982v.01c.051.605.636 1.136 1.363 1.136h1.93c.134 0 .23-.107.23-.22v-2.06a.227.227 0 0 0-.23-.22h-2.05Zm-2.006-.765a2.793 2.793 0 0 1 2.006-.835h2.05c1.007 0 1.83.813 1.83 1.82v2.06c0 1.007-.823 1.82-1.83 1.82h-1.93c-1.431 0-2.824-1.047-2.956-2.599a2.808 2.808 0 0 1 .83-2.267Z', - }), - ], - }), - }), - s: true, - }, -]) -export const WalletNav = /*#__PURE__*/ __createIcon('WalletNav', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - fillRule: 'evenodd', - clipRule: 'evenodd', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'M17.77 9.873h3.484c.412 0 .746.326.746.728v2.53a.746.746 0 0 1-.746.729h-3.405c-.994.013-1.863-.651-2.089-1.595a1.982 1.982 0 0 1 .433-1.652 2.092 2.092 0 0 1 1.576-.74Zm.15 2.66h.33a.755.755 0 0 0 .764-.745.755.755 0 0 0-.764-.745h-.33a.766.766 0 0 0-.54.213.728.728 0 0 0-.224.524c0 .413.341.749.765.754Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M22 8.382h-4.231v.034c-1.964 0-3.556 1.552-3.556 3.467s1.592 3.467 3.556 3.467H22v.312C22 19.015 19.964 21 16.516 21H7.484C4.036 21 2 19.015 2 15.662V8.338C2 4.985 4.036 3 7.484 3h9.032C19.964 3 22 4.985 22 8.382Zm-15.262 0h5.644a.755.755 0 0 0 .765-.746.755.755 0 0 0-.764-.745H6.738a.755.755 0 0 0-.764.736c0 .413.34.75.764.754Z', - }), - ], - }), - }), - s: true, - }, -]) -export const WalletSetting = /*#__PURE__*/ __createIcon('WalletSetting', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsxs('g', { - fill: 'currentColor', - children: [ - /*#__PURE__*/ _jsx('path', { - d: 'm18.46 9.279-3.62-6.274a1.458 1.458 0 0 0-1.26-.726H6.42c-.568 0-1.1.306-1.382.799l-3.582 6.2c-.26.45-.26 1.006 0 1.454l3.62 6.271c.26.45.74.727 1.26.727h7.243c.517 0 1-.28 1.26-.727l3.62-6.271a1.461 1.461 0 0 0 0-1.453Zm-1.185.77-3.621 6.27a.086.086 0 0 1-.074.044H6.337a.084.084 0 0 1-.074-.043l-3.62-6.272a.085.085 0 0 1 0-.086l3.58-6.199a.232.232 0 0 1 .198-.115h7.159c.03 0 .058.015.074.043l3.62 6.271a.085.085 0 0 1 0 .086Z', - }), - /*#__PURE__*/ _jsx('path', { - d: 'M9.959 7.04a2.968 2.968 0 0 0-2.965 2.964A2.968 2.968 0 0 0 9.96 12.97a2.967 2.967 0 0 0 2.965-2.963A2.97 2.97 0 0 0 9.959 7.04Zm0 4.564a1.6 1.6 0 0 1-1.598-1.598A1.6 1.6 0 0 1 9.96 8.41a1.6 1.6 0 0 1 1.598 1.597 1.6 1.6 0 0 1-1.598 1.598Z', - }), - ], - }), - }), - s: true, - }, -]) -export const Warning = /*#__PURE__*/ __createIcon('Warning', [ - { - u: () => new URL('./general/Warning.svg', import.meta.url).href, - }, -]) -export const WarningTriangle = /*#__PURE__*/ __createIcon('WarningTriangle', [ - { - u: () => new URL('./general/WarningTriangle.svg', import.meta.url).href, - }, -]) -export const Web = /*#__PURE__*/ __createIcon('Web', [ - { - u: () => new URL('./general/Web.svg', import.meta.url).href, - }, -]) -export const WebBlack = /*#__PURE__*/ __createIcon('WebBlack', [ - { - c: ['dark'], - u: () => new URL('./general/WebBlack.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./general/WebBlack.light.svg', import.meta.url).href, - }, -]) -export const MenuPersonas = /*#__PURE__*/ __createIcon('MenuPersonas', [ - { - u: () => new URL('./menus/MenuPersonas.png', import.meta.url).href, - }, -]) -export const MenuPersonasActive = /*#__PURE__*/ __createIcon('MenuPersonasActive', [ - { - u: () => new URL('./menus/MenuPersonasActive.png', import.meta.url).href, - }, -]) -export const MenuSettings = /*#__PURE__*/ __createIcon('MenuSettings', [ - { - u: () => new URL('./menus/MenuSettings.png', import.meta.url).href, - }, -]) -export const MenuSettingsActive = /*#__PURE__*/ __createIcon('MenuSettingsActive', [ - { - u: () => new URL('./menus/MenuSettingsActive.png', import.meta.url).href, - }, -]) -export const MenuWallets = /*#__PURE__*/ __createIcon('MenuWallets', [ - { - u: () => new URL('./menus/MenuWallets.png', import.meta.url).href, - }, -]) -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: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 20 20', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M7.413 2.721c1.191-.96 2.814-1.23 4.293-.95 1.346.322 2.551 1.228 3.204 2.453.453.805.593 1.738.599 2.65l-.001 7.089c-.04.308.204.528.359.764.735 1.054 1.078 2.332 1.139 3.605-.442.002-.884.001-1.326 0-.067-.822-.212-1.644-.597-2.382-1.206-.006-2.41.02-3.617.008-.007-.895-.003-1.79-.005-2.686-1.028-.24-2.115-.061-3.081.338-1.846.778-3.096 2.727-3.089 4.723H4c-.061-1.578.593-3.132 1.626-4.307.145-.137.093-.352.108-.53-.013-1.811.008-3.624-.01-5.436-.045-.902-.017-1.806.024-2.708.108-1.08.77-2.041 1.666-2.63Zm4.23.365c.148.42.554.684.715 1.102.504.938.361 2.023.378 3.044.008 2.481.006 4.962.007 7.443.495.001.99.004 1.486-.006-.004-2.598-.003-5.198 0-7.796-.006-.627-.069-1.273-.349-1.845-.414-.933-1.265-1.646-2.238-1.942Zm-3.712.848c-.653.427-.977 1.227-.952 1.99 1.498.015 2.994.005 4.492.006.053-.995-.59-2.006-1.576-2.261-.656-.233-1.369-.046-1.964.265Zm-.913 3.268c-.007.533.012 1.093.293 1.564.418.793 1.285 1.28 2.172 1.306v1.27a3.986 3.986 0 0 1-2.493-.973c.013.825 0 1.651.01 2.478a6.488 6.488 0 0 1 4.465-.941c-.008-1.568-.001-3.136-.005-4.704H7.018Z', - }), - }), - s: true, - }, -]) -export const Bit = /*#__PURE__*/ __createIcon('Bit', [ - { - u: () => new URL('./plugins/Bit.svg', import.meta.url).href, - }, -]) -export const Calendar = /*#__PURE__*/ __createIcon('Calendar', [ - { - u: () => new URL('./plugins/Calendar.svg', import.meta.url).href, - }, -]) -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, - }, -]) -export const ENS = /*#__PURE__*/ __createIcon('ENS', [ - { - u: () => new URL('./plugins/ENS.png', import.meta.url).href, - }, -]) -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, - }, -]) -export const MaskBox = /*#__PURE__*/ __createIcon('MaskBox', [ - { - u: () => new URL('./plugins/MaskBox.svg', import.meta.url).href, - }, -]) -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, - }, -]) -export const SecurityChecker = /*#__PURE__*/ __createIcon('SecurityChecker', [ - { - u: () => new URL('./plugins/SecurityChecker.svg', import.meta.url).href, - }, -]) -export const SettingInfo = /*#__PURE__*/ __createIcon('SettingInfo', [ - { - c: ['dark'], - u: () => new URL('./plugins/SettingInfo.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./plugins/SettingInfo.light.svg', import.meta.url).href, - }, -]) -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, + u: () => new URL('./plugins/shared.svg', import.meta.url).href, }, ]) export const SpaceId = /*#__PURE__*/ __createIcon('SpaceId', [ @@ -3910,357 +1605,3 @@ 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'], - u: () => new URL('./plugins/Valuables.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./plugins/Valuables.light.svg', import.meta.url).href, - }, -]) -export const Web3Profile = /*#__PURE__*/ __createIcon('Web3Profile', [ - { - u: () => new URL('./plugins/Web3Profile.svg', import.meta.url).href, - }, -]) -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, - }, -]) -export const Email = /*#__PURE__*/ __createIcon('Email', [ - { - j: () => - /*#__PURE__*/ _jsx('svg', { - xmlns: 'http://www.w3.org/2000/svg', - fill: 'none', - viewBox: '0 0 24 24', - children: /*#__PURE__*/ _jsx('path', { - fill: 'currentColor', - d: 'M16.192 14.725c1.238.944 2.07 1.14 2.597.905.346-.156.595-.521.74-.925a8.002 8.002 0 0 0-3.53-9.633A8 8 0 1 0 12.015 20l.006 2A9.957 9.957 0 0 1 7 20.66C2.22 17.897.58 11.781 3.341 7a10 10 0 0 1 18.07 8.383 4.033 4.033 0 0 1-.471.925c-.337.494-.775.895-1.332 1.145-1.327.595-2.945.19-4.83-1.297a5 5 0 1 1 1.414-1.432ZM12.001 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z', - }), - }), - s: true, - }, -]) -export const JP = /*#__PURE__*/ __createIcon('JP', [ - { - u: () => new URL('./settings/JP.svg', import.meta.url).href, - }, -]) -export const KR = /*#__PURE__*/ __createIcon('KR', [ - { - u: () => new URL('./settings/KR.svg', import.meta.url).href, - }, -]) -export const SettingsAppearance = /*#__PURE__*/ __createIcon('SettingsAppearance', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsAppearance.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsAppearance.light.svg', import.meta.url).href, - }, -]) -export const SettingsBackup = /*#__PURE__*/ __createIcon('SettingsBackup', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsBackup.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsBackup.light.svg', import.meta.url).href, - }, -]) -export const SettingsEmail = /*#__PURE__*/ __createIcon('SettingsEmail', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsEmail.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsEmail.light.svg', import.meta.url).href, - }, -]) -export const SettingsLanguage = /*#__PURE__*/ __createIcon('SettingsLanguage', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsLanguage.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsLanguage.light.svg', import.meta.url).href, - }, -]) -export const SettingsPassword = /*#__PURE__*/ __createIcon('SettingsPassword', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsPassword.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsPassword.light.svg', import.meta.url).href, - }, -]) -export const SettingsPhone = /*#__PURE__*/ __createIcon('SettingsPhone', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsPhone.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsPhone.light.svg', import.meta.url).href, - }, -]) -export const SettingsRestore = /*#__PURE__*/ __createIcon('SettingsRestore', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsRestore.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsRestore.light.svg', import.meta.url).href, - }, -]) -export const SettingsSync = /*#__PURE__*/ __createIcon('SettingsSync', [ - { - c: ['dark'], - u: () => new URL('./settings/SettingsSync.dark.svg', import.meta.url).href, - }, - { - c: ['light'], - u: () => new URL('./settings/SettingsSync.light.svg', import.meta.url).href, - }, -]) -export const UK = /*#__PURE__*/ __createIcon('UK', [ - { - u: () => new URL('./settings/UK.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..8b852c1648a9 100644 --- a/packages/icons/icon-generated-as-url.js +++ b/packages/icons/icon-generated-as-url.js @@ -31,7 +31,6 @@ export function fantom_url() { return new URL("./brands/Fantom.svg", import.meta export function farcaster_url() { return new URL("./brands/Farcaster.svg", import.meta.url).href } export function firefly_dark_url() { return new URL("./brands/Firefly.dark.svg", import.meta.url).href } export function firefly_light_url() { return new URL("./brands/Firefly.light.svg", import.meta.url).href } -export function flow_url() { return new URL("./brands/Flow.svg", import.meta.url).href } export function gem_url() { return new URL("./brands/Gem.svg", import.meta.url).href } export function git_hub_url() { return new URL("./brands/GitHub.svg", import.meta.url).href } export function git_hub_gray_url() { return new URL("./brands/GitHubGray.svg", import.meta.url).href } @@ -46,18 +45,12 @@ export function kusama_url() { return new URL("./brands/Kusama.svg", import.meta export function leaderboard_url() { return new URL("./brands/Leaderboard.png", import.meta.url).href } export function lens_url() { return new URL("./brands/Lens.svg", import.meta.url).href } export function link_3_url() { return new URL("./brands/Link3.svg", import.meta.url).href } -export function looks_rare_url() { return new URL("./brands/LooksRare.svg", import.meta.url).href } export function mask_dark_url() { return new URL("./brands/Mask.dark.svg", import.meta.url).href } export function mask_light_url() { return new URL("./brands/Mask.light.svg", import.meta.url).href } export function mask_banner_url() { return new URL("./brands/MaskBanner.svg", import.meta.url).href } export function mask_blue_url() { return new URL("./brands/MaskBlue.svg", import.meta.url).href } export function mask_grey_dark_url() { return new URL("./brands/MaskGrey.dark.svg", import.meta.url).href } export function mask_grey_light_url() { return new URL("./brands/MaskGrey.light.svg", import.meta.url).href } -export function mask_placeholder_dark_url() { return new URL("./brands/MaskPlaceholder.dark.svg", import.meta.url).href } -export function mask_placeholder_dim_url() { return new URL("./brands/MaskPlaceholder.dim.svg", import.meta.url).href } -export function mask_placeholder_light_url() { return new URL("./brands/MaskPlaceholder.light.svg", import.meta.url).href } -export function mask_square_dark_url() { return new URL("./brands/MaskSquare.dark.svg", import.meta.url).href } -export function mask_square_light_url() { return new URL("./brands/MaskSquare.light.svg", import.meta.url).href } export function mask_text_url() { return new URL("./brands/MaskText.svg", import.meta.url).href } export function mask_text_nightly_url() { return new URL("./brands/MaskTextNightly.svg", import.meta.url).href } export function mask_wallet_url() { return new URL("./brands/MaskWallet.png", import.meta.url).href } @@ -108,339 +101,86 @@ export function x_2_y_2_url() { return new URL("./brands/X2Y2.svg", import.meta. export function x_log_dark_url() { return new URL("./brands/XLog.dark.svg", import.meta.url).href } export function x_log_url() { return new URL("./brands/XLog.svg", import.meta.url).href } export function you_tube_url() { return new URL("./brands/YouTube.svg", import.meta.url).href } -export function you_tube_gray_url() { return new URL("./brands/YouTubeGray.svg", import.meta.url).href } export function zerion_url() { return new URL("./brands/Zerion.svg", import.meta.url).href } export function zero_x_dark_url() { return new URL("./brands/ZeroX.dark.svg", import.meta.url).href } export function zero_x_url() { return new URL("./brands/ZeroX.svg", import.meta.url).href } export function zilliqa_url() { return new URL("./brands/Zilliqa.svg", import.meta.url).href } export function zk_scan_url() { return new URL("./brands/zkScan.svg", import.meta.url).href } export function zora_url() { return new URL("./brands/Zora.svg", import.meta.url).href } -export function add_url() { return new URL("./general/Add.svg", import.meta.url).href } export function add_user_url() { return new URL("./general/AddUser.svg", import.meta.url).href } export function america_url() { return new URL("./general/America.svg", import.meta.url).href } -export function appearance_url() { return new URL("./general/Appearance.svg", import.meta.url).href } -export function appendices_url() { return new URL("./general/Appendices.svg", import.meta.url).href } -export function application_nft_url() { return new URL("./general/ApplicationNFT.svg", import.meta.url).href } export function approve_url() { return new URL("./general/Approve.svg", import.meta.url).href } -export function arrow_back_url() { return new URL("./general/ArrowBack.svg", import.meta.url).href } export function arrow_circle_url() { return new URL("./general/ArrowCircle.svg", import.meta.url).href } -export function arrow_down_round_url() { return new URL("./general/ArrowDownRound.svg", import.meta.url).href } -export function arrow_downward_url() { return new URL("./general/ArrowDownward.svg", import.meta.url).href } export function arrow_drop_url() { return new URL("./general/ArrowDrop.svg", import.meta.url).href } -export function arrow_right_url() { return new URL("./general/ArrowRight.svg", import.meta.url).href } -export function arrow_up_url() { return new URL("./general/ArrowUp.svg", import.meta.url).href } -export function arrow_up_round_url() { return new URL("./general/ArrowUpRound.svg", import.meta.url).href } -export function back_up_url() { return new URL("./general/BackUp.svg", import.meta.url).href } -export function base_close_dark_url() { return new URL("./general/BaseClose.dark.svg", import.meta.url).href } -export function base_close_light_url() { return new URL("./general/BaseClose.light.svg", import.meta.url).href } -export function base_contacts_url() { return new URL("./general/BaseContacts.svg", import.meta.url).href } -export function base_upload_url() { return new URL("./general/BaseUpload.svg", import.meta.url).href } -export function base_user_url() { return new URL("./general/BaseUser.svg", import.meta.url).href } -export function best_trade_url() { return new URL("./general/BestTrade.svg", import.meta.url).href } -export function blue_pin_url() { return new URL("./general/BluePin.svg", import.meta.url).href } export function bordered_success_url() { return new URL("./general/BorderedSuccess.svg", import.meta.url).href } -export function busy_wallet_nav_url() { return new URL("./general/BusyWalletNav.svg", import.meta.url).href } -export function buy_url() { return new URL("./general/Buy.svg", import.meta.url).href } -export function cached_url() { return new URL("./general/Cached.svg", import.meta.url).href } export function candle_url() { return new URL("./general/candle.svg", import.meta.url).href } -export function card_url() { return new URL("./general/Card.svg", import.meta.url).href } export function check_url() { return new URL("./general/Check.svg", import.meta.url).href } export function checkbox_url() { return new URL("./general/Checkbox.svg", import.meta.url).href } export function checkbox_blank_url() { return new URL("./general/CheckboxBlank.svg", import.meta.url).href } -export function checkbox_border_url() { return new URL("./general/CheckboxBorder.svg", import.meta.url).href } export function checkbox_no_url() { return new URL("./general/CheckboxNo.svg", import.meta.url).href } export function check_circle_url() { return new URL("./general/CheckCircle.svg", import.meta.url).href } -export function chevron_up_url() { return new URL("./general/ChevronUp.svg", import.meta.url).href } export function china_url() { return new URL("./general/China.svg", import.meta.url).href } -export function chrome_url() { return new URL("./general/Chrome.svg", import.meta.url).href } -export function circle_close_url() { return new URL("./general/CircleClose.svg", import.meta.url).href } export function circle_loading_url() { return new URL("./general/CircleLoading.svg", import.meta.url).href } export function circle_warning_url() { return new URL("./general/CircleWarning.svg", import.meta.url).href } -export function clear_dark_url() { return new URL("./general/Clear.dark.svg", import.meta.url).href } -export function clear_light_url() { return new URL("./general/Clear.light.svg", import.meta.url).href } export function close_url() { return new URL("./general/Close.svg", import.meta.url).href } -export function cloud_url() { return new URL("./general/Cloud.svg", import.meta.url).href } -export function cloud_backup_url() { return new URL("./general/CloudBackup.png", import.meta.url).href } -export function cloud_backup_2_url() { return new URL("./general/CloudBackup2.svg", import.meta.url).href } -export function cloud_link_url() { return new URL("./general/CloudLink.png", import.meta.url).href } -export function cny_url() { return new URL("./general/CNY.svg", import.meta.url).href } -export function collectible_url() { return new URL("./general/Collectible.svg", import.meta.url).href } export function colorful_close_url() { return new URL("./general/ColorfulClose.svg", import.meta.url).href } -export function comeback_url() { return new URL("./general/Comeback.svg", import.meta.url).href } export function comment_url() { return new URL("./general/Comment.svg", import.meta.url).href } -export function connect_url() { return new URL("./general/Connect.svg", import.meta.url).href } -export function contacts_url() { return new URL("./general/Contacts.svg", import.meta.url).href } export function copy_url() { return new URL("./general/Copy.svg", import.meta.url).href } -export function currency_url() { return new URL("./general/Currency.svg", import.meta.url).href } -export function dark_url() { return new URL("./general/Dark.svg", import.meta.url).href } -export function decrease_url() { return new URL("./general/decrease.svg", import.meta.url).href } -export function default_token_dark_url() { return new URL("./general/DefaultToken.dark.svg", import.meta.url).href } -export function default_token_dim_url() { return new URL("./general/DefaultToken.dim.svg", import.meta.url).href } -export function default_token_light_url() { return new URL("./general/DefaultToken.light.svg", import.meta.url).href } -export function delete_url() { return new URL("./general/Delete.svg", import.meta.url).href } -export function disconnect_url() { return new URL("./general/Disconnect.svg", import.meta.url).href } -export function document_url() { return new URL("./general/Document.svg", import.meta.url).href } -export function documents_url() { return new URL("./general/Documents.svg", import.meta.url).href } -export function download_url() { return new URL("./general/Download.svg", import.meta.url).href } -export function download_2_url() { return new URL("./general/Download2.svg", import.meta.url).href } -export function drop_url() { return new URL("./general/Drop.svg", import.meta.url).href } -export function dump_url() { return new URL("./general/Dump.svg", import.meta.url).href } -export function edit_url() { return new URL("./general/Edit.svg", import.meta.url).href } export function edit_2_url() { return new URL("./general/Edit2.svg", import.meta.url).href } -export function empty_url() { return new URL("./general/Empty.png", import.meta.url).href } export function empty_simple_dark_url() { return new URL("./general/EmptySimple.dark.svg", import.meta.url).href } export function empty_simple_light_url() { return new URL("./general/EmptySimple.light.svg", import.meta.url).href } -export function encrypted_files_url() { return new URL("./general/EncryptedFiles.svg", import.meta.url).href } -export function eth_symbol_url() { return new URL("./general/ETHSymbol.svg", import.meta.url).href } export function europe_url() { return new URL("./general/Europe.svg", import.meta.url).href } -export function eye_dark_url() { return new URL("./general/Eye.dark.svg", import.meta.url).href } -export function eye_light_url() { return new URL("./general/Eye.light.svg", import.meta.url).href } -export function eye_color_url() { return new URL("./general/EyeColor.svg", import.meta.url).href } -export function eye_off_dark_url() { return new URL("./general/EyeOff.dark.svg", import.meta.url).href } -export function eye_off_light_url() { return new URL("./general/EyeOff.light.svg", import.meta.url).href } -export function facebook_url() { return new URL("./general/Facebook.svg", import.meta.url).href } -export function file_url() { return new URL("./general/File.svg", import.meta.url).href } -export function file_message_url() { return new URL("./general/FileMessage.svg", import.meta.url).href } export function fill_success_url() { return new URL("./general/FillSuccess.svg", import.meta.url).href } -export function filter_url() { return new URL("./general/Filter.svg", import.meta.url).href } export function firefly_nft_url() { return new URL("./general/FireflyNFT.svg", import.meta.url).href } export function flag_url() { return new URL("./general/Flag.svg", import.meta.url).href } -export function folder_url() { return new URL("./general/Folder.svg", import.meta.url).href } -export function gas_url() { return new URL("./general/Gas.svg", import.meta.url).href } -export function gas_station_url() { return new URL("./general/GasStation.svg", import.meta.url).href } -export function gear_dark_url() { return new URL("./general/Gear.dark.svg", import.meta.url).href } -export function gear_light_url() { return new URL("./general/Gear.light.svg", import.meta.url).href } -export function gear_settings_url() { return new URL("./general/GearSettings.svg", import.meta.url).href } export function gift_url() { return new URL("./general/Gift.svg", import.meta.url).href } -export function globe_url() { return new URL("./general/Globe.svg", import.meta.url).href } -export function gray_masks_url() { return new URL("./general/GrayMasks.svg", import.meta.url).href } -export function hamburger_menu_url() { return new URL("./general/HamburgerMenu.svg", import.meta.url).href } export function heart_url() { return new URL("./general/Heart.svg", import.meta.url).href } export function history_url() { return new URL("./general/History.svg", import.meta.url).href } -export function hkd_url() { return new URL("./general/HKD.svg", import.meta.url).href } export function hong_kong_url() { return new URL("./general/HongKong.svg", import.meta.url).href } -export function identity_dark_url() { return new URL("./general/Identity.dark.svg", import.meta.url).href } -export function identity_light_url() { return new URL("./general/Identity.light.svg", import.meta.url).href } export function info_dark_url() { return new URL("./general/Info.dark.svg", import.meta.url).href } export function info_light_url() { return new URL("./general/Info.light.svg", import.meta.url).href } -export function interaction_url() { return new URL("./general/Interaction.svg", import.meta.url).href } -export function interaction_circle_url() { return new URL("./general/InteractionCircle.svg", import.meta.url).href } export function japan_url() { return new URL("./general/Japan.svg", import.meta.url).href } -export function jpy_url() { return new URL("./general/JPY.svg", import.meta.url).href } -export function key_square_url() { return new URL("./general/KeySquare.svg", import.meta.url).href } export function left_arrow_url() { return new URL("./general/LeftArrow.svg", import.meta.url).href } export function like_url() { return new URL("./general/Like.svg", import.meta.url).href } export function linear_calendar_dark_url() { return new URL("./general/LinearCalendar.dark.svg", import.meta.url).href } export function linear_calendar_light_url() { return new URL("./general/LinearCalendar.light.svg", import.meta.url).href } -export function link_url() { return new URL("./general/Link.svg", import.meta.url).href } export function link_out_url() { return new URL("./general/LinkOut.svg", import.meta.url).href } -export function loader_url() { return new URL("./general/Loader.svg", import.meta.url).href } -export function loading_base_dark_url() { return new URL("./general/LoadingBase.dark.svg", import.meta.url).href } -export function loading_base_light_url() { return new URL("./general/LoadingBase.light.svg", import.meta.url).href } -export function local_backup_url() { return new URL("./general/LocalBackup.png", import.meta.url).href } -export function lock_url() { return new URL("./general/Lock.svg", import.meta.url).href } export function mask_avatar_dark_url() { return new URL("./general/MaskAvatar.dark.svg", import.meta.url).href } export function mask_avatar_light_url() { return new URL("./general/MaskAvatar.light.svg", import.meta.url).href } -export function mask_in_minds_url() { return new URL("./general/MaskInMinds.svg", import.meta.url).href } -export function mask_me_url() { return new URL("./general/MaskMe.svg", import.meta.url).href } export function masks_url() { return new URL("./general/Masks.svg", import.meta.url).href } -export function medal_url() { return new URL("./general/Medal.svg", import.meta.url).href } -export function message_url() { return new URL("./general/Message.svg", import.meta.url).href } -export function messages_url() { return new URL("./general/Messages.svg", import.meta.url).href } -export function minus_url() { return new URL("./general/Minus.svg", import.meta.url).href } -export function mnemonic_url() { return new URL("./general/Mnemonic.svg", import.meta.url).href } -export function more_url() { return new URL("./general/More.svg", import.meta.url).href } -export function next_id_avatar_dark_url() { return new URL("./general/NextIdAvatar.dark.svg", import.meta.url).href } -export function next_id_avatar_light_url() { return new URL("./general/NextIdAvatar.light.svg", import.meta.url).href } -export function next_id_persona_warning_url() { return new URL("./general/NextIdPersonaWarning.svg", import.meta.url).href } export function nft_holder_url() { return new URL("./general/NFTHolder.svg", import.meta.url).href } -export function nft_red_packet_url() { return new URL("./general/NFTRedPacket.svg", import.meta.url).href } -export function outlined_mask_url() { return new URL("./general/OutlinedMask.svg", import.meta.url).href } -export function personas_outline_url() { return new URL("./general/PersonasOutline.svg", import.meta.url).href } -export function pin_dark_url() { return new URL("./general/Pin.dark.svg", import.meta.url).href } -export function pin_light_url() { return new URL("./general/Pin.light.svg", import.meta.url).href } -export function play_dark_url() { return new URL("./general/Play.dark.svg", import.meta.url).href } -export function play_url() { return new URL("./general/Play.svg", import.meta.url).href } export function plugin_url() { return new URL("./general/Plugin.svg", import.meta.url).href } -export function plugins_url() { return new URL("./general/Plugins.svg", import.meta.url).href } -export function plus_url() { return new URL("./general/Plus.svg", import.meta.url).href } -export function popup_close_url() { return new URL("./general/PopupClose.svg", import.meta.url).href } export function popup_link_url() { return new URL("./general/PopupLink.svg", import.meta.url).href } -export function popup_restore_url() { return new URL("./general/PopupRestore.svg", import.meta.url).href } -export function popup_trash_url() { return new URL("./general/PopupTrash.svg", import.meta.url).href } export function primary_info_url() { return new URL("./general/PrimaryInfo.svg", import.meta.url).href } export function provider_url() { return new URL("./general/Provider.svg", import.meta.url).href } -export function public_key_dark_url() { return new URL("./general/PublicKey.dark.svg", import.meta.url).href } -export function public_key_light_url() { return new URL("./general/PublicKey.light.svg", import.meta.url).href } -export function public_key_2_url() { return new URL("./general/PublicKey2.svg", import.meta.url).href } export function questions_url() { return new URL("./general/Questions.svg", import.meta.url).href } export function radio_button_checked_url() { return new URL("./general/RadioButtonChecked.svg", import.meta.url).href } export function radio_button_un_checked_url() { return new URL("./general/RadioButtonUnChecked.svg", import.meta.url).href } -export function radio_no_dark_url() { return new URL("./general/RadioNo.dark.svg", import.meta.url).href } -export function radio_no_light_url() { return new URL("./general/RadioNo.light.svg", import.meta.url).href } -export function receive_colorful_url() { return new URL("./general/ReceiveColorful.svg", import.meta.url).href } export function red_packet_url() { return new URL("./general/RedPacket.svg", import.meta.url).href } -export function refresh_url() { return new URL("./general/Refresh.svg", import.meta.url).href } export function repost_url() { return new URL("./general/Repost.svg", import.meta.url).href } -export function restore_url() { return new URL("./general/Restore.svg", import.meta.url).href } -export function restore_colorful_url() { return new URL("./general/RestoreColorful.svg", import.meta.url).href } export function result_no_url() { return new URL("./general/ResultNo.svg", import.meta.url).href } export function result_yes_url() { return new URL("./general/ResultYes.svg", import.meta.url).href } -export function retweet_dark_url() { return new URL("./general/Retweet.dark.svg", import.meta.url).href } -export function retweet_light_url() { return new URL("./general/Retweet.light.svg", import.meta.url).href } -export function right_url() { return new URL("./general/Right.svg", import.meta.url).href } export function right_arrow_url() { return new URL("./general/RightArrow.svg", import.meta.url).href } -export function risk_url() { return new URL("./general/Risk.svg", import.meta.url).href } export function search_url() { return new URL("./general/Search.svg", import.meta.url).href } -export function security_risk_url() { return new URL("./general/SecurityRisk.svg", import.meta.url).href } -export function security_warning_url() { return new URL("./general/SecurityWarning.svg", import.meta.url).href } export function selected_dark_url() { return new URL("./general/Selected.dark.svg", import.meta.url).href } export function selected_light_url() { return new URL("./general/Selected.light.svg", import.meta.url).href } -export function self_url() { return new URL("./general/Self.svg", import.meta.url).href } -export function send_url() { return new URL("./general/Send.svg", import.meta.url).href } -export function send_colorful_url() { return new URL("./general/SendColorful.svg", import.meta.url).href } -export function setting_url() { return new URL("./general/Setting.svg", import.meta.url).href } export function settings_url() { return new URL("./general/Settings.svg", import.meta.url).href } -export function settings_2_url() { return new URL("./general/Settings2.svg", import.meta.url).href } -export function sharp_mask_url() { return new URL("./general/SharpMask.svg", import.meta.url).href } -export function shut_down_url() { return new URL("./general/ShutDown.svg", import.meta.url).href } -export function sign_up_account_url() { return new URL("./general/SignUpAccount.png", import.meta.url).href } export function smart_pay_url() { return new URL("./general/SmartPay.svg", import.meta.url).href } -export function star_url() { return new URL("./general/Star.svg", import.meta.url).href } -export function success_url() { return new URL("./general/Success.svg", import.meta.url).href } export function success_for_snack_bar_url() { return new URL("./general/SuccessForSnackBar.svg", import.meta.url).href } -export function sun_url() { return new URL("./general/Sun.svg", import.meta.url).href } -export function swap_url() { return new URL("./general/Swap.svg", import.meta.url).href } -export function swap_colorful_url() { return new URL("./general/SwapColorful.svg", import.meta.url).href } -export function switch_logo_dark_url() { return new URL("./general/SwitchLogo.dark.svg", import.meta.url).href } -export function switch_logo_light_url() { return new URL("./general/SwitchLogo.light.svg", import.meta.url).href } -export function telegram_round_gray_url() { return new URL("./general/TelegramRoundGray.svg", import.meta.url).href } -export function tick_url() { return new URL("./general/Tick.svg", import.meta.url).href } -export function time_url() { return new URL("./general/Time.svg", import.meta.url).href } -export function tips_url() { return new URL("./general/Tips.svg", import.meta.url).href } export function transaction_failed_url() { return new URL("./general/TransactionFailed.svg", import.meta.url).href } -export function trash_url() { return new URL("./general/Trash.svg", import.meta.url).href } export function trash_line_url() { return new URL("./general/TrashLine.svg", import.meta.url).href } -export function tutorial_url() { return new URL("./general/Tutorial.svg", import.meta.url).href } -export function twitter_url() { return new URL("./general/Twitter.svg", import.meta.url).href } -export function twitter_stroke_url() { return new URL("./general/TwitterStroke.svg", import.meta.url).href } -export function tx_in_url() { return new URL("./general/TxIn.svg", import.meta.url).href } -export function tx_out_url() { return new URL("./general/TxOut.svg", import.meta.url).href } export function undo_url() { return new URL("./general/Undo.svg", import.meta.url).href } -export function upload_url() { return new URL("./general/Upload.svg", import.meta.url).href } -export function usd_url() { return new URL("./general/USD.svg", import.meta.url).href } -export function user_url() { return new URL("./general/User.svg", import.meta.url).href } export function user_plus_url() { return new URL("./general/UserPlus.svg", import.meta.url).href } export function verification_url() { return new URL("./general/Verification.svg", import.meta.url).href } export function wallet_url() { return new URL("./general/Wallet.svg", import.meta.url).href } -export function wallet_nav_url() { return new URL("./general/WalletNav.svg", import.meta.url).href } -export function wallet_setting_url() { return new URL("./general/WalletSetting.svg", import.meta.url).href } export function warning_url() { return new URL("./general/Warning.svg", import.meta.url).href } export function warning_triangle_url() { return new URL("./general/WarningTriangle.svg", import.meta.url).href } -export function web_url() { return new URL("./general/Web.svg", import.meta.url).href } export function web_black_dark_url() { return new URL("./general/WebBlack.dark.svg", import.meta.url).href } export function web_black_light_url() { return new URL("./general/WebBlack.light.svg", import.meta.url).href } -export function menu_personas_url() { return new URL("./menus/MenuPersonas.png", import.meta.url).href } -export function menu_personas_active_url() { return new URL("./menus/MenuPersonasActive.png", import.meta.url).href } -export function menu_settings_url() { return new URL("./menus/MenuSettings.png", import.meta.url).href } -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 } -export function kr_url() { return new URL("./settings/KR.svg", import.meta.url).href } -export function settings_appearance_dark_url() { return new URL("./settings/SettingsAppearance.dark.svg", import.meta.url).href } -export function settings_appearance_light_url() { return new URL("./settings/SettingsAppearance.light.svg", import.meta.url).href } -export function settings_backup_dark_url() { return new URL("./settings/SettingsBackup.dark.svg", import.meta.url).href } -export function settings_backup_light_url() { return new URL("./settings/SettingsBackup.light.svg", import.meta.url).href } -export function settings_email_dark_url() { return new URL("./settings/SettingsEmail.dark.svg", import.meta.url).href } -export function settings_email_light_url() { return new URL("./settings/SettingsEmail.light.svg", import.meta.url).href } -export function settings_language_dark_url() { return new URL("./settings/SettingsLanguage.dark.svg", import.meta.url).href } -export function settings_language_light_url() { return new URL("./settings/SettingsLanguage.light.svg", import.meta.url).href } -export function settings_password_dark_url() { return new URL("./settings/SettingsPassword.dark.svg", import.meta.url).href } -export function settings_password_light_url() { return new URL("./settings/SettingsPassword.light.svg", import.meta.url).href } -export function settings_phone_dark_url() { return new URL("./settings/SettingsPhone.dark.svg", import.meta.url).href } -export function settings_phone_light_url() { return new URL("./settings/SettingsPhone.light.svg", import.meta.url).href } -export function settings_restore_dark_url() { return new URL("./settings/SettingsRestore.dark.svg", import.meta.url).href } -export function settings_restore_light_url() { return new URL("./settings/SettingsRestore.light.svg", import.meta.url).href } -export function settings_sync_dark_url() { return new URL("./settings/SettingsSync.dark.svg", import.meta.url).href } -export function settings_sync_light_url() { return new URL("./settings/SettingsSync.light.svg", import.meta.url).href } -export function uk_url() { return new URL("./settings/UK.svg", import.meta.url).href } \ No newline at end of file +export function space_id_url() { return new URL("./plugins/SpaceId.svg", import.meta.url).href } \ No newline at end of file diff --git a/packages/icons/index.d.ts b/packages/icons/index.d.ts index ce009ac36409..66e1681f62b1 100644 --- a/packages/icons/index.d.ts +++ b/packages/icons/index.d.ts @@ -5,5 +5,4 @@ export * from './icon-generated-as-url.js' export type { GeneratedIconProps, GeneratedIconNonSquareProps, GeneratedIcon } from './utils/internal.js' export * as Icons from './icon-generated-as-jsx.js' -export * as MaskIcons from './icon-generated-as-jsx.js' export * as MaskIconURLs from './icon-generated-as-url.js' diff --git a/packages/icons/index.js b/packages/icons/index.js index 0857dbc2adfb..c39bdb79722f 100644 --- a/packages/icons/index.js +++ b/packages/icons/index.js @@ -2,5 +2,4 @@ export * from './utils/index.js' export * from './utils/MaskIconPaletteContext.js' export * as Icons from './icon-generated-as-jsx.js' -export * as MaskIcons from './icon-generated-as-jsx.js' export * as MaskIconURLs from './icon-generated-as-url.js' diff --git a/packages/icons/menus/MenuPersonas.png b/packages/icons/menus/MenuPersonas.png deleted file mode 100644 index 456216d50f7d..000000000000 Binary files a/packages/icons/menus/MenuPersonas.png and /dev/null differ diff --git a/packages/icons/menus/MenuPersonasActive.png b/packages/icons/menus/MenuPersonasActive.png deleted file mode 100644 index d239e15d9631..000000000000 Binary files a/packages/icons/menus/MenuPersonasActive.png and /dev/null differ diff --git a/packages/icons/menus/MenuSettings.png b/packages/icons/menus/MenuSettings.png deleted file mode 100644 index 28963de1b754..000000000000 Binary files a/packages/icons/menus/MenuSettings.png and /dev/null differ diff --git a/packages/icons/menus/MenuSettingsActive.png b/packages/icons/menus/MenuSettingsActive.png deleted file mode 100644 index 26dc68321402..000000000000 Binary files a/packages/icons/menus/MenuSettingsActive.png and /dev/null differ diff --git a/packages/icons/menus/MenuWallets.png b/packages/icons/menus/MenuWallets.png deleted file mode 100644 index f7c1348c79d3..000000000000 Binary files a/packages/icons/menus/MenuWallets.png and /dev/null differ diff --git a/packages/icons/menus/MenuWalletsActive.png b/packages/icons/menus/MenuWalletsActive.png deleted file mode 100644 index e8bdf4c41e5d..000000000000 Binary files a/packages/icons/menus/MenuWalletsActive.png and /dev/null differ 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 bd3eb544a8cc..000000000000 Binary files a/packages/icons/plugins/ArtBlocks.png and /dev/null differ diff --git a/packages/icons/plugins/Bit.svg b/packages/icons/plugins/Bit.svg deleted file mode 100644 index 725db7524d6c..000000000000 --- a/packages/icons/plugins/Bit.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/icons/plugins/Calendar.svg b/packages/icons/plugins/Calendar.svg deleted file mode 100644 index 1d591a55d604..000000000000 --- a/packages/icons/plugins/Calendar.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/icons/plugins/Collectibles.svg b/packages/icons/plugins/Collectibles.svg index 71f7b7a17824..d210449865f9 100644 --- a/packages/icons/plugins/Collectibles.svg +++ b/packages/icons/plugins/Collectibles.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/icons/plugins/CrossBridge.png b/packages/icons/plugins/CrossBridge.png deleted file mode 100644 index 5834a1348926..000000000000 Binary files a/packages/icons/plugins/CrossBridge.png and /dev/null differ diff --git a/packages/icons/plugins/CyberConnect.dark.svg b/packages/icons/plugins/CyberConnect.dark.svg deleted file mode 100644 index 4f70f4192f43..000000000000 --- a/packages/icons/plugins/CyberConnect.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - 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/DecentralizedSearch.svg b/packages/icons/plugins/DecentralizedSearch.svg deleted file mode 100644 index a612b629b3b4..000000000000 --- a/packages/icons/plugins/DecentralizedSearch.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/packages/icons/plugins/ENS.png b/packages/icons/plugins/ENS.png index 371cc1fbb6b0..1e5fd6d7e87a 100644 Binary files a/packages/icons/plugins/ENS.png and b/packages/icons/plugins/ENS.png differ diff --git a/packages/icons/plugins/ENSCover.svg b/packages/icons/plugins/ENSCover.svg deleted file mode 100644 index 974116d2d234..000000000000 --- a/packages/icons/plugins/ENSCover.svg +++ /dev/null @@ -1,15 +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 9abb4c30f594..000000000000 Binary files a/packages/icons/plugins/FindTruman.png and /dev/null differ diff --git a/packages/icons/plugins/FriendTech.svg b/packages/icons/plugins/FriendTech.svg deleted file mode 100644 index 10c845f77b27..000000000000 --- a/packages/icons/plugins/FriendTech.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - 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 d7f87ec35106..000000000000 Binary files a/packages/icons/plugins/Markets.png and /dev/null differ diff --git a/packages/icons/plugins/MarketsClaim.svg b/packages/icons/plugins/MarketsClaim.svg deleted file mode 100644 index 66096cde9492..000000000000 --- a/packages/icons/plugins/MarketsClaim.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/icons/plugins/MaskBox.svg b/packages/icons/plugins/MaskBox.svg deleted file mode 100644 index 9d3ebae0b222..000000000000 --- a/packages/icons/plugins/MaskBox.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/icons/plugins/NFTAvatar.svg b/packages/icons/plugins/NFTAvatar.svg deleted file mode 100644 index ad01f25e5a60..000000000000 --- a/packages/icons/plugins/NFTAvatar.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/plugins/PoolTogether.png b/packages/icons/plugins/PoolTogether.png deleted file mode 100644 index 3b23abf50b60..000000000000 Binary files a/packages/icons/plugins/PoolTogether.png and /dev/null differ diff --git a/packages/icons/plugins/Savings.svg b/packages/icons/plugins/Savings.svg deleted file mode 100644 index 1cd2c7253063..000000000000 --- a/packages/icons/plugins/Savings.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/plugins/ScamSniffer.svg b/packages/icons/plugins/ScamSniffer.svg deleted file mode 100644 index 222c20dc2937..000000000000 --- a/packages/icons/plugins/ScamSniffer.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/plugins/SecurityChecker.svg b/packages/icons/plugins/SecurityChecker.svg deleted file mode 100644 index 82db45087afb..000000000000 --- a/packages/icons/plugins/SecurityChecker.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/plugins/SettingInfo.dark.svg b/packages/icons/plugins/SettingInfo.dark.svg index a73c0b15ede9..60551768b01f 100644 --- a/packages/icons/plugins/SettingInfo.dark.svg +++ b/packages/icons/plugins/SettingInfo.dark.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/icons/plugins/SettingInfo.light.svg b/packages/icons/plugins/SettingInfo.light.svg index 76c885bd327f..56f8e1e223e7 100644 --- a/packages/icons/plugins/SettingInfo.light.svg +++ b/packages/icons/plugins/SettingInfo.light.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file 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 b35227258284..000000000000 Binary files a/packages/icons/plugins/Transak.png and /dev/null differ 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/plugins/Valuables.dark.svg b/packages/icons/plugins/Valuables.dark.svg deleted file mode 100644 index 47b84502e043..000000000000 --- a/packages/icons/plugins/Valuables.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/Valuables.light.svg b/packages/icons/plugins/Valuables.light.svg deleted file mode 100644 index 6e9e5f97dad7..000000000000 --- a/packages/icons/plugins/Valuables.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/plugins/Web3Profile.svg b/packages/icons/plugins/Web3Profile.svg deleted file mode 100644 index f1c5f4667e1f..000000000000 --- a/packages/icons/plugins/Web3Profile.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/icons/plugins/Web3ProfileCard.svg b/packages/icons/plugins/Web3ProfileCard.svg deleted file mode 100644 index 390e26cb53ee..000000000000 --- a/packages/icons/plugins/Web3ProfileCard.svg +++ /dev/null @@ -1,26 +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/icons/settings/CN.svg b/packages/icons/settings/CN.svg deleted file mode 100644 index a6e641fb4fbe..000000000000 --- a/packages/icons/settings/CN.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/settings/Email.svg b/packages/icons/settings/Email.svg deleted file mode 100644 index b1d470fbb1b1..000000000000 --- a/packages/icons/settings/Email.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/JP.svg b/packages/icons/settings/JP.svg deleted file mode 100644 index cfb724dc88c6..000000000000 --- a/packages/icons/settings/JP.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/icons/settings/KR.svg b/packages/icons/settings/KR.svg deleted file mode 100644 index 7913d6081280..000000000000 --- a/packages/icons/settings/KR.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/packages/icons/settings/SettingsAppearance.dark.svg b/packages/icons/settings/SettingsAppearance.dark.svg deleted file mode 100644 index 678e0d534794..000000000000 --- a/packages/icons/settings/SettingsAppearance.dark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/settings/SettingsAppearance.light.svg b/packages/icons/settings/SettingsAppearance.light.svg deleted file mode 100644 index d98268a31090..000000000000 --- a/packages/icons/settings/SettingsAppearance.light.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/icons/settings/SettingsBackup.dark.svg b/packages/icons/settings/SettingsBackup.dark.svg deleted file mode 100644 index 664e92044f63..000000000000 --- a/packages/icons/settings/SettingsBackup.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/SettingsBackup.light.svg b/packages/icons/settings/SettingsBackup.light.svg deleted file mode 100644 index 64e66f701498..000000000000 --- a/packages/icons/settings/SettingsBackup.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/SettingsEmail.dark.svg b/packages/icons/settings/SettingsEmail.dark.svg deleted file mode 100644 index cccd86637998..000000000000 --- a/packages/icons/settings/SettingsEmail.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/SettingsEmail.light.svg b/packages/icons/settings/SettingsEmail.light.svg deleted file mode 100644 index a3ec60305f22..000000000000 --- a/packages/icons/settings/SettingsEmail.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/SettingsLanguage.dark.svg b/packages/icons/settings/SettingsLanguage.dark.svg deleted file mode 100644 index 45cc57de6f88..000000000000 --- a/packages/icons/settings/SettingsLanguage.dark.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/icons/settings/SettingsLanguage.light.svg b/packages/icons/settings/SettingsLanguage.light.svg deleted file mode 100644 index e2ab7db97779..000000000000 --- a/packages/icons/settings/SettingsLanguage.light.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/icons/settings/SettingsPassword.dark.svg b/packages/icons/settings/SettingsPassword.dark.svg deleted file mode 100644 index 54c3e3841241..000000000000 --- a/packages/icons/settings/SettingsPassword.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsPassword.light.svg b/packages/icons/settings/SettingsPassword.light.svg deleted file mode 100644 index b2bc4896ae2b..000000000000 --- a/packages/icons/settings/SettingsPassword.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsPhone.dark.svg b/packages/icons/settings/SettingsPhone.dark.svg deleted file mode 100644 index 45c97e548a9a..000000000000 --- a/packages/icons/settings/SettingsPhone.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsPhone.light.svg b/packages/icons/settings/SettingsPhone.light.svg deleted file mode 100644 index a6ac53408036..000000000000 --- a/packages/icons/settings/SettingsPhone.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsRestore.dark.svg b/packages/icons/settings/SettingsRestore.dark.svg deleted file mode 100644 index 0ce2bb4300d2..000000000000 --- a/packages/icons/settings/SettingsRestore.dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsRestore.light.svg b/packages/icons/settings/SettingsRestore.light.svg deleted file mode 100644 index 31764cf2e87d..000000000000 --- a/packages/icons/settings/SettingsRestore.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/icons/settings/SettingsSync.dark.svg b/packages/icons/settings/SettingsSync.dark.svg deleted file mode 100644 index 0b6128ac03d4..000000000000 --- a/packages/icons/settings/SettingsSync.dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/SettingsSync.light.svg b/packages/icons/settings/SettingsSync.light.svg deleted file mode 100644 index 26bfcdb92cb4..000000000000 --- a/packages/icons/settings/SettingsSync.light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/icons/settings/UK.svg b/packages/icons/settings/UK.svg deleted file mode 100644 index abf196e174fa..000000000000 --- a/packages/icons/settings/UK.svg +++ /dev/null @@ -1,7 +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 b2dcc25bdac2..000000000000 --- a/packages/mask/.webpack/config.ts +++ /dev/null @@ -1,423 +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))) - - 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 - 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, - /** JSON.stringify twice */ - WEB3_CONSTANTS_RPC: WEB3_CONSTANTS_RPC, - MASK_SENTRY_DSN: process.env.MASK_SENTRY_DSN || '', - MASK_SENTRY: process.env.MASK_SENTRY || JSON.stringify('disabled'), - MASK_MIXPANEL: process.env.MASK_MIXPANEL || JSON.stringify('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 4b797dfa5eb0..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/dividerActive.png and /dev/null differ 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 e660effcb97a..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/dividerDisable.png and /dev/null differ diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/dividerDone.png b/packages/mask/content-script/components/shared/assets/stepAssets/dividerDone.png deleted file mode 100644 index 758979c3c91f..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/dividerDone.png and /dev/null differ 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 31b0ceef7577..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/step1Active.png and /dev/null differ 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 ba9f93b3f7c7..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/step2Active.png and /dev/null differ 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 0e7f76cf8448..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/step2Disable.png and /dev/null differ diff --git a/packages/mask/content-script/components/shared/assets/stepAssets/stepSuccess.png b/packages/mask/content-script/components/shared/assets/stepAssets/stepSuccess.png deleted file mode 100644 index 5df1c5bfc0da..000000000000 Binary files a/packages/mask/content-script/components/shared/assets/stepAssets/stepSuccess.png and /dev/null differ diff --git a/packages/mask/content-script/components/shared/openApplicationBoardDialog.tsx b/packages/mask/content-script/components/shared/openApplicationBoardDialog.tsx deleted file mode 100644 index 74e8640df779..000000000000 --- a/packages/mask/content-script/components/shared/openApplicationBoardDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useCallback } from 'react' -import type { PluginID } from '@masknet/shared-base' -import { ApplicationBoardModal } from '@masknet/shared' -import { useLastRecognizedIdentity } from '../DataSource/useActivatedUI.js' -import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/ui.js' -import { usePersonasFromDB } from '../../../shared-ui/hooks/usePersonasFromDB.js' -import { usePersonaPerSiteConnectStatus } from '../DataSource/usePersonaPerSiteConnectStatus.js' -import Services from '#services' - -export function useOpenApplicationBoardDialog(quickMode?: boolean, focusPluginID?: PluginID) { - const lastRecognized = useLastRecognizedIdentity() - const allPersonas = usePersonasFromDB() - const { value: applicationCurrentStatus, loading: personaPerSiteConnectStatusLoading } = - usePersonaPerSiteConnectStatus() - - return useCallback( - () => - 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 0351680cac1d..000000000000 Binary files a/packages/mask/content-script/resources/extensionPinned.png and /dev/null differ 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 f40c8af00605..000000000000 Binary files a/packages/mask/content-script/resources/image-payload/normal/payload-2021.png and /dev/null differ 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 d82d945d6658..000000000000 Binary files a/packages/mask/content-script/resources/image-payload/normal/payload-2022.png and /dev/null differ diff --git a/packages/mask/content-script/resources/image-payload/normal/payload-2023.png b/packages/mask/content-script/resources/image-payload/normal/payload-2023.png deleted file mode 100644 index 4fb1973f0e86..000000000000 Binary files a/packages/mask/content-script/resources/image-payload/normal/payload-2023.png and /dev/null differ diff --git a/packages/mask/content-script/resources/maskFilledIcon.png b/packages/mask/content-script/resources/maskFilledIcon.png deleted file mode 100644 index a2f665862309..000000000000 Binary files a/packages/mask/content-script/resources/maskFilledIcon.png and /dev/null differ diff --git a/packages/mask/content-script/resources/maskFilledIconDark.png b/packages/mask/content-script/resources/maskFilledIconDark.png deleted file mode 100644 index 1e33cd431f0c..000000000000 Binary files a/packages/mask/content-script/resources/maskFilledIconDark.png and /dev/null differ diff --git a/packages/mask/content-script/resources/tool-icon/airdrop.png b/packages/mask/content-script/resources/tool-icon/airdrop.png deleted file mode 100644 index 856b7f5645ab..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/airdrop.png and /dev/null differ diff --git a/packages/mask/content-script/resources/tool-icon/claim.png b/packages/mask/content-script/resources/tool-icon/claim.png deleted file mode 100644 index 8974924f0bbc..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/claim.png and /dev/null differ 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 2d0432199d1e..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/encryptedmsg.png and /dev/null differ 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 229024ab5747..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/markets.png and /dev/null differ diff --git a/packages/mask/content-script/resources/tool-icon/redpacket.png b/packages/mask/content-script/resources/tool-icon/redpacket.png deleted file mode 100644 index 85322b94ccf5..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/redpacket.png and /dev/null differ 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 3ce3ba44ed26..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/swap.png and /dev/null differ 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 c7f6d38f8a66..000000000000 Binary files a/packages/mask/content-script/resources/tool-icon/token.png and /dev/null differ diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/automation/AttachImageToComposition.ts b/packages/mask/content-script/site-adaptor-infra/defaults/automation/AttachImageToComposition.ts deleted file mode 100644 index a88643ebac22..000000000000 --- a/packages/mask/content-script/site-adaptor-infra/defaults/automation/AttachImageToComposition.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { waitDocumentReadyState } from '@masknet/kit' -import type { SiteAdaptorUI } from '@masknet/types' -import { MaskMessages } from '@masknet/shared-base' -import { activatedSiteAdaptorUI } from '../../ui.js' -import { downloadUrl, pasteImageToActiveElements } from '../../../utils/index.js' - -export function pasteImageToCompositionDefault(hasSucceed: () => 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 ca0b1bf0884e..000000000000 Binary files a/packages/mask/dashboard/assets/Welcome.splinecode.png and /dev/null differ diff --git a/packages/mask/dashboard/assets/images/AboutDialogBackground.png b/packages/mask/dashboard/assets/images/AboutDialogBackground.png deleted file mode 100644 index 08c5b534ef07..000000000000 Binary files a/packages/mask/dashboard/assets/images/AboutDialogBackground.png and /dev/null differ diff --git a/packages/mask/dashboard/assets/images/MaskWallet.png b/packages/mask/dashboard/assets/images/MaskWallet.png deleted file mode 100644 index c83560067eee..000000000000 Binary files a/packages/mask/dashboard/assets/images/MaskWallet.png and /dev/null differ diff --git a/packages/mask/dashboard/assets/images/MaskWatermark.png b/packages/mask/dashboard/assets/images/MaskWatermark.png deleted file mode 100644 index d657316e3289..000000000000 Binary files a/packages/mask/dashboard/assets/images/MaskWatermark.png and /dev/null differ diff --git a/packages/mask/dashboard/assets/images/PrintBackground.png b/packages/mask/dashboard/assets/images/PrintBackground.png deleted file mode 100644 index 7b5d9d252040..000000000000 Binary files a/packages/mask/dashboard/assets/images/PrintBackground.png and /dev/null differ diff --git a/packages/mask/dashboard/assets/images/Trend.png b/packages/mask/dashboard/assets/images/Trend.png deleted file mode 100644 index af6c1bce400f..000000000000 Binary files a/packages/mask/dashboard/assets/images/Trend.png and /dev/null differ 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 ( - - - -
- } - 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 ( - -