diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000000..35962618ff27 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,17 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) + +## Released packages + +Currently, we release the following packages in this monorepo to npm. + +- [@masknet/base](../packages/base) +- [@masknet/typed-message](../packages/typed-message/base) +- [@masknet/typed-message-react](../packages/typed-message/react) +- [@masknet/encryption](../packages/encryption) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000000..a0f5e30d11a0 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "develop", + "updateInternalDependencies": "patch", + "ignore": [ + "!(@masknet/base|@masknet/typed-message|@masknet/typed-message-react|@masknet/encryption|@masknet/config)" + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 61463ef69800..000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,117 +0,0 @@ -# Javascript Node CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-javascript/ for more details -# -version: 2.1 -executors: - mask_node: - docker: - - image: cimg/node:17.5 - working_directory: ~/repo -commands: - restore_workspace: - description: 'Restore the workspace' - steps: - - attach_workspace: - at: ~/ -jobs: - install_dependencies: - executor: mask_node - steps: - - checkout - - restore_cache: - name: Restore PNPM Package Cache - keys: - - v4-mask-cache-pnpm - - run: - name: Enable corepack - command: sudo corepack enable - - run: - name: Install Dependencies - command: pnpm install --frozen-lockfile --store-dir ~/.pnpm/ - - save_cache: - name: Save NPM Package Cache - key: v4-mask-cache-pnpm - paths: - - ~/.pnpm/ - - persist_to_workspace: - root: ~/ - paths: - - repo - build: - executor: mask_node - steps: - - restore_workspace - - run: - name: Enable corepack - command: sudo corepack enable - - run: - name: Build Mask Network - command: npx gulp build-ci - - store_artifacts: - path: MaskNetwork.base.zip - destination: /MaskNetwork.base.zip - - store_artifacts: - path: MaskNetwork.iOS.zip - destination: /MaskNetwork.iOS.zip - - store_artifacts: - path: MaskNetwork.chromium.zip - destination: /MaskNetwork.chromium.zip - - store_artifacts: - path: MaskNetwork.firefox.zip - destination: /MaskNetwork.firefox.zip - - store_artifacts: - path: MaskNetwork.gecko.zip - destination: /MaskNetwork.gecko.zip - - store_artifacts: - path: MaskNetwork.chromium-beta.zip - destination: /MaskNetwork.chromium-beta.zip - - persist_to_workspace: - root: ~/repo/ - paths: - - MaskNetwork.base.zip - - MaskNetwork.iOS.zip - - MaskNetwork.chromium.zip - - MaskNetwork.chromium-beta.zip - - MaskNetwork.firefox.zip - - MaskNetwork.gecko.zip - publish-github-release: - docker: - - image: cibuilds/github:0.10 - steps: - - restore_workspace - - run: - name: 'Publish Release on GitHub' - command: | - set -o nounset - mkdir ~/repo/release - mv ~/*.zip ~/repo/release/ - cd ~/repo/release - ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -b "✔ No breaking changes. / ⚠ Has breaking changes! - - 🆕 New Feature - - 🔐 Security - - 🎨 UI Improvements - - 🐛 Bug Fixes - - 👩‍💻 Miscellaneous" -replace -draft $(git describe HEAD) ~/repo/release - # -b BODY \ # Set text describing the contents of the release - # -delete \ # Delete release and its git tag in advance if it exists (same as -recreate) - # -n TITLE \ # Set release title -workflows: - version: 2 - main: - jobs: - - install_dependencies - - build: - requires: - - install_dependencies - - publish-github-release: - requires: - - build - filters: - branches: - only: released diff --git a/.ecrc b/.ecrc deleted file mode 100644 index 279fdae23de2..000000000000 --- a/.ecrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "Disable": { - "Indentation": true - } -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 40bdbc3261ed..000000000000 --- a/.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -root = true - -[*] -indent_size = 4 -charset = utf-8 -end_of_line = lf -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space - -[{.*rc,ts*.json,package.json}] -indent_size = 2 - -[*.{yaml,yml,md}] -indent_size = 2 diff --git a/.env/dev-preference.example b/.env/dev-preference.example new file mode 100644 index 000000000000..4c1fa0e8e462 --- /dev/null +++ b/.env/dev-preference.example @@ -0,0 +1,106 @@ +# Copy and rename this file to dev-preference +# This dot file will NOT be used in CI or production build. + +### Experimental CSP + +; csp=false +# (default) Not enabling strict CSP. + +; csp=true +# Enable strict CSP. + +### SourceMap + +; sourceMap=true +# (default) Uses suggested sourceMap in the current build set. + +; sourceMap=false +# Disables the sourceMap. + +; sourceMap=eval +; sourceMap=eval-cheap-source-map +; sourceMap=eval-cheap-module-source-map +; sourceMap=eval-source-map +; sourceMap=cheap-source-map +; sourceMap=cheap-module-source-map +; sourceMap=source-map +; sourceMap=inline-cheap-source-map +; sourceMap=inline-cheap-module-source-map +; sourceMap=inline-source-map +# sourceMap=... +# Any valid value listed in https://webpack.js.org/configuration/devtool/, but it might not work in our environment. + +### SourceMap hide frameworks + +; sourceMapHideFrameworks=true +# (default) Hide frameworks in sourceMap. + +; sourceMapHideFrameworks=false +# Show frameworks in sourceMap. + +### Preferred Manifest Version + +; manifest=3 +# (default) Use Chromium Manifest V3 as default development target. + +; manifest=2 +# Use Chromium Manifest V2 as default development target. + +; manifest=chromium-mv2 +; manifest=chromium-beta-mv3 +; manifest=chromium-mv3 +; manifest=firefox-mv2 +; manifest=firefox-mv3 +; manifest=safari-mv3 + +### Hot Module Reload + +; hmr=true +# (default) Enable Hot Module Reload in development mode. + +; hmr=false +# Disable Hot Module Reload in development mode. + +### Devtools integration + +; devtools=true +# (default) Enable Devtools integration in development mode. + +; devtools=false +# Disable Devtools integration in development mode. + +### React devtools default editor + +; devtoolsEditorURI=vscode://file/{path}:{line} +; devtoolsEditorURI=mvim://open?url=file://{path}&line={line} +; devtoolsEditorURI=raycast://script-commands/your-custom-script?arguments={path}&arguments={line} +# (default) VSCode + +# devtoolsEditorURI=... +# Any URI that can open the editor you used. + +### Experimental React compiler + +; reactCompiler=false +# (default) Not enabling react compiler. + +; reactCompiler=true +# Enable react compiler. + +; reactCompiler=infer +; reactCompiler=annotation +; reactCompiler=all +# Enable react compiler with specific mode. + +### Experimental LavaMoat + +; lavamoat=false +# (default) Not enabling LavaMoat. + +; lavamoat=true +# Enable LavaMoat. + +### Build Environment Variables + +; FIREFLY_X_CLIENT_ID= +; FIREFLY_X_CLIENT_SECRET= diff --git a/.env/e2e-template b/.env/e2e-template deleted file mode 100644 index 94356e0475d8..000000000000 --- a/.env/e2e-template +++ /dev/null @@ -1,58 +0,0 @@ -# THIS FILE ONLY FOR LISTING ALL AVAILABLE E2E CONFIGURATIONS -# PLEASE CREATE CORRESPONDING e2e-[ENV] -# UNDER THE CURRENT DIR TO CUSTOMIZE E2E BEHAVIOR FOR DIFFERENT ENV -# DO NOT RUN ANY E2E TEST WITHOUT THESE VARIABLES SET PROPERLY - -# Specify network (twitter.com or facebook.com) -E2E_NETWORK_ID= - -# Specify the user agent of the puppteer browser -E2E_USER_AGENT= - -# Specify the extension id -E2E_EXT_ID=jkoeaghipilijlahjplgbfiocjhldnap - -# Specify the extension dir which will be tested -# dist/ if test against development build -# build/ if test against production build -E2E_EXT_DIR= - -# Specify the user data dir which helps to prevent frequently -# login thus ban by SNS provider -# -# more about --user-data-dir: -# - https://peter.sh/experiments/chromium-command-line-switches/#user-data-dir -E2E_ALICE_USER_DATA_DIR=./pptr-alice -E2E_BOB_USER_DATA_DIR=./pptr-bob - -# Specify alice's twitter account -E2E_ALICE_TWITTER_ID= -E2E_ALICE_TWITTER_USERNAME= -E2E_ALICE_TWITTER_PASSWORD= -E2E_ALICE_TWITTER_EMAIL= -E2E_ALICE_TWITTER_EMAIL_PASSWORD= -E2E_ALICE_TWITTER_PHONE= - -# Specify bob's twitter account (optional) -E2E_BOB_TWITTER_ID= -E2E_BOB_TWITTER_USERNAME= -E2E_BOB_TWITTER_PASSWORD= -E2E_BOB_TWITTER_EMAIL= -E2E_BOB_TWITTER_EMAIL_PASSWORD= -E2E_BOB_TWITTER_PHONE= - -# Specify primary facebook account -E2E_ALICE_FACEBOOK_ID= -E2E_ALICE_FACEBOOK_USERNAME= -E2E_ALICE_FACEBOOK_PASSWORD= -E2E_ALICE_FACEBOOK_EMAIL= -E2E_ALICE_FACEBOOK_EMAIL_PASSWORD= -E2E_ALICE_FACEBOOK_PHONE= - -# Specify vice facebook account (optional) -E2E_BOB_FACEBOOK_ID= -E2E_BOB_FACEBOOK_USERNAME= -E2E_BOB_FACEBOOK_PASSWORD= -E2E_BOB_FACEBOOK_EMAIL= -E2E_BOB_FACEBOOK_EMAIL_PASSWORD= -E2E_BOB_FACEBOOK_PHONE= diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index dcb2b1518d05..000000000000 --- a/.eslintignore +++ /dev/null @@ -1,15 +0,0 @@ -*.d.ts -*.test.tsx -build/ -dist -gun.js -e2e -package.json -packages/contracts -packages/storybook-shared -packages/netlify/storybook-static -packages/netlify/sites -public/ -scripts/ -packages/dashboard/storybook-static/ -packages/theme/storybook-static/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 38b157bf9e1e..000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module" - }, - "env": { - "node": true - }, - "plugins": ["unused-imports"], - "rules": { - "constructor-super": "error", - "dot-notation": "error", - "eqeqeq": "error", - "object-shorthand": "warn", - "no-var": "error", - "no-bitwise": "error", - "no-debugger": "error", - "no-eval": "error", - "no-extra-bind": "error", - "no-fallthrough": "error", - "no-new-wrappers": "error", - "no-plusplus": "error", - "valid-typeof": "error", - "no-loss-of-precision": "error", - "no-unsafe-optional-chaining": "error", - "no-unsafe-negation": "error", - "no-unsafe-finally": "error", - "no-irregular-whitespace": "error", - "no-restricted-globals": ["error", "event", "name", "length", "closed"], - "no-restricted-imports": [ - "error", - { - "paths": [ - { "name": "lodash", "message": "Please use lodash-unified instead." }, - { "name": "lodash-es", "message": "Please use lodash-unified instead." }, - { "name": "date-fns", "message": "Please use date-fns/{submodule} instead." }, - { "name": "date-fns/esm", "message": "Please use date-fns/{submodule} instead." }, - { "name": "@masknet/typed-message/base", "message": "Please use @masknet/typed-message instead." }, - { - "name": "@dimensiondev/holoflows-kit/es", - "message": "Please use @dimensiondev/holoflows-kit instead." - } - ] - } - ], - "no-return-await": "error", - "no-sparse-arrays": "error", - "no-template-curly-in-string": "error", - "prefer-const": "warn", - "use-isnan": "error", - "unused-imports/no-unused-imports-ts": "error" - } -} diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..11a74a748b18 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# Prettier +17a48c22dace1efc4bb21189f3eeda8363314b31 +b515a343aa66943b4736ea197f8cb7152a0cc126 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..ed3eb4dd9302 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +packages/icons/icon-generated-as-jsx.js linguist-generated=true +packages/icons/icon-generated-as-url.js linguist-generated=true +*.po linguist-generated=true +en-US.json linguist-generated=true +ja-JP.json linguist-generated=true +zh-CN.json linguist-generated=true +zh-TW.json linguist-generated=true +ko-KR.json linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 81e4acd26aad..d7882a058d6e 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -41,5 +41,3 @@ assignees: Jack-Works, jk234ert - [ ] Latest Chrome (stable channel) - [ ] Latest Firefox (stable channel) -- [ ] Android 11 -- [ ] iOS 14 diff --git a/.github/ISSUE_TEMPLATE/evm.md b/.github/ISSUE_TEMPLATE/evm.md new file mode 100644 index 000000000000..22abde3efdf9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/evm.md @@ -0,0 +1,31 @@ +--- +name: Deploy Contract Request +about: Request to deploy contracts on a new EVM-compatible chain. +title: '[EVM Chain] Request to deploy contracts on ' +labels: '' +assignees: DimensionDev/dev-smartcontract +--- + +Request to deploy contracts on . + + + +## Chain Info + +| Name | Content | +| ------------ | ------- | +| Name | \- | +| ChainId | \- | +| Website | \- | +| RPC Endpoint | \- | + +## Checklist + +The following contracts are requested. + ++ [RedPacket](https://github.com/DimensionDev/RedPacket) ++ [Initial Twitter Offer](https://github.com/DimensionDev/InitialTwitterOffering) ++ Dummy Qualification ++ BalanceChecker ++ Multicall ++ UniswapInterfaceMulticall diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 9190ab74395c..98a719a3aedf 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -11,7 +11,7 @@ When reporting an issue, where possible, please provide at least: - The project and commit version the issue was identified at - A proof of concept (plain-text; no binaries) - Steps to reproduce -- Your recommended remediation(s), if any. +- Your recommended remediations, if any. ## `security.txt` diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml new file mode 100644 index 000000000000..9628232e0799 --- /dev/null +++ b/.github/workflows/changeset.yml @@ -0,0 +1,41 @@ +name: Changeset + +on: + push: + branches: + - develop + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Changeset + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + packages: write + pull-requests: write + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + filter: blob:none + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: ".node-version" + cache: "pnpm" + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest + with: + registry: true + - run: git config --global url."https://github.com/".insteadOf git@github.com + - run: pnpm install + - uses: changesets/action@v1 + with: + publish: pnpm exec gulp changeset-release + commit: "chore: changeset release" + title: "chore: changeset release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index b1ab3043611c..c716202fc6ce 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -2,74 +2,99 @@ name: Compile on: push: - branches: [master, develop*, released] + tags: ["v*"] + branches: [master, develop, released, hotfix/*, release/*] pull_request: - branches: [master, develop*, released] + branches: [master, develop, released, hotfix/*, release/*] + merge_group: + types: [checks_requested] + +permissions: + contents: write + pull-requests: read + packages: read -# Cancels all previous workflow runs for pull requests that have not completed. concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest env: WEB3_CONSTANTS_RPC: ${{ secrets.WEB3_CONSTANTS_RPC }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + MASK_SENTRY_DSN: ${{ secrets.MASK_SENTRY_DSN }} + MASK_SENTRY: ${{ secrets.MASK_SENTRY }} + MASK_MIXPANEL: ${{ secrets.MASK_MIXPANEL }} + PRIVY_APP_ID: ${{ secrets.PRIVY_APP_ID }} + FIREFLY_X_CLIENT_ID: ${{ secrets.FIREFLY_X_CLIENT_ID }} + FIREFLY_X_CLIENT_SECRET: ${{ secrets.FIREFLY_X_CLIENT_SECRET }} steps: - name: Get cache date id: get-date - run: echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" + run: echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_STATE shell: bash - - uses: actions/checkout@v2 + # note: required by sentry + - uses: actions/checkout@v7 with: - ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 + filter: blob:none + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 with: - node-version: '17' + node-version-file: ".node-version" cache: 'pnpm' - - name: Restore Webpack cache - uses: actions/cache@v2 + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest with: - path: packages/mask/node_modules/.cache/ - key: ${{ runner.os }}-extension-${{ hashFiles('pnpm-lock.yaml') }}-${{ steps.get-date.outputs.date }} - # Not fallback to different dependencies. Webpack seems like have bug. - # An example caused by the webpack cache bug: https://github.com/facebook/react/issues/21587 - restore-keys: ${{ runner.os }}-extension-${{ hashFiles('pnpm-lock.yaml') }} + registry: true + - run: git config --global url."https://github.com/".insteadOf git@github.com - run: pnpm install - - run: npx gulp build-ci - - name: Upload `MaskNetwork.base.zip` - uses: actions/upload-artifact@v2 - with: - name: MaskNetwork.base.zip - path: MaskNetwork.base.zip - - name: Upload `MaskNetwork.iOS.zip` - uses: actions/upload-artifact@v2 - with: - name: MaskNetwork.iOS.zip - path: MaskNetwork.iOS.zip - - name: Upload `MaskNetwork.chromium.zip` - uses: actions/upload-artifact@v2 + - name: Build CI + env: + NODE_OPTIONS: --max-old-space-size=8192 + run: pnpm exec gulp build-ci + # See https://github.com/getsentry/action-release/issues/187 + - name: Change git configuration + shell: bash + run: | + if [ -n "$(git config --get extensions.worktreeconfig)" ]; then + git config --unset-all extensions.worktreeconfig + fi + - name: Create Sentry release + uses: getsentry/action-release@v1 + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/released' + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: mask-network + SENTRY_PROJECT: mask-network with: - name: MaskNetwork.chromium.zip - path: MaskNetwork.chromium.zip + environment: production + sourcemaps: ./build + version_prefix: mask- + ignore_missing: true - name: Upload `MaskNetwork.chromium-beta.zip` - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v7 with: name: MaskNetwork.chromium-beta.zip - path: MaskNetwork.chromium-beta.zip - - name: Upload `MaskNetwork.firefox.zip` - uses: actions/upload-artifact@v2 + path: masknetwork.chromium-beta.zip + if-no-files-found: error + - name: Upload `MaskNetwork.chromium-mv3.zip` + uses: actions/upload-artifact@v7 + with: + name: MaskNetwork.chromium-mv3.zip + path: masknetwork.chromium-mv3.zip + if-no-files-found: error + - name: Upload `MaskNetwork.firefox-mv3.zip` + uses: actions/upload-artifact@v7 with: - name: MaskNetwork.firefox.zip - path: MaskNetwork.firefox.zip - - name: Upload `MaskNetwork.gecko.zip` - uses: actions/upload-artifact@v2 + name: MaskNetwork.firefox-mv3.zip + path: masknetwork.firefox-mv3.zip + if-no-files-found: error + - uses: "marvinpinto/action-automatic-releases@latest" + if: startsWith(github.ref, 'refs/tags/v') with: - name: MaskNetwork.gecko.zip - path: MaskNetwork.gecko.zip + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + files: | + *.zip diff --git a/.github/workflows/deploy-proxy.yml b/.github/workflows/deploy-proxy.yml deleted file mode 100644 index 9bd3176fa268..000000000000 --- a/.github/workflows/deploy-proxy.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Publish Proxy Package - -on: - workflow_dispatch: -jobs: - build: - runs-on: ubuntu-20.04 - permissions: - packages: write - contents: read - steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 - with: - cache: pnpm - - uses: DimensionDev/github-token-action@latest - with: - registry: true - - run: pnpm install - - run: pnpm build - name: Build solution - - run: pnpm build - name: Build provider-proxy Package - working-directory: packages/provider-proxy - - run: npm publish - working-directory: packages/provider-proxy/dist diff --git a/.github/workflows/jira-issue-key-checking.yml b/.github/workflows/jira-issue-key-checking.yml new file mode 100644 index 000000000000..ad21db8f9435 --- /dev/null +++ b/.github/workflows/jira-issue-key-checking.yml @@ -0,0 +1,23 @@ +name: "Check Jira Issue Key" + +on: + pull_request: + types: + - opened + - edited + - synchronize + merge_group: + types: [checks_requested] + +permissions: + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: DimensionDev/jira-issue-key-checker@master + if: ${{ github.event.pull_request }} + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + jira-prefix: 'MF|FW' diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index bff90d350dca..599cc53c0cab 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -2,101 +2,128 @@ name: Linters and tests on: push: - branches: [master, develop*, released] + branches: [master, develop, released, hotfix/*, release/*] pull_request: - branches: [master, develop*, released] + branches: [master, develop, released, hotfix/*, release/*] + merge_group: + types: [checks_requested] + +permissions: + contents: read + packages: read -# Cancels all previous workflow runs for pull requests that have not completed. concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: - prettier: - runs-on: ubuntu-20.04 + linter: + runs-on: ubuntu-latest + permissions: + packages: read + contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v2 - - run: npx prettier@2.6.2 --check . - markdownlint: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 + ref: ${{ github.event.pull_request.head.ref }} + token: ${{ secrets.GGITHUB_TOKEN }} + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v2 - - run: npx markdownlint-cli . - editorconfig: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 + node-version-file: ".node-version" + cache: "pnpm" + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: DimensionDev/setup-editorconfig-action@latest - - run: editorconfig-checker - anti-trojan-source: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 + registry: true + - run: pnpm install + - run: pnpm exec gulp codegen + - run: pnpm run lingui:extract + - run: pnpm run lingui:compile + - run: pnpm exec gulp lint-package-json + - run: pnpm exec gulp fix-plugins-tsconfig + - run: pnpx prettier@3.6.2 --write . + - uses: EndBug/add-and-commit@v9 with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v2 - - run: npx anti-trojan-source --files='packages/**/src/**/*.{ts,tsx,js}' - locale-kit: - runs-on: ubuntu-20.04 + message: 'fix: linter' + type-check: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 with: - node-version: "17" + node-version-file: ".node-version" cache: "pnpm" + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest + with: + registry: true + - run: git config --global url."https://github.com/".insteadOf git@github.com - run: pnpm install - - run: npx gulp locale-kit - type-check-and-jest: - runs-on: ubuntu-20.04 + - run: pnpm exec gulp codegen + - run: pnpm exec gulp typescript + test: + runs-on: ubuntu-latest steps: - - name: Get cache date - id: get-date - run: echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" - shell: bash - - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 with: - node-version: "17" + node-version-file: ".node-version" cache: "pnpm" + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest + with: + registry: true + - run: git config --global url."https://github.com/".insteadOf git@github.com - run: pnpm install - - run: npx gulp codegen - - run: npx gulp polyfill - - run: node --experimental-vm-modules ./node_modules/jest/bin/jest.js + - run: pnpm exec gulp codegen + - run: pnpm exec gulp polyfill + - run: pnpm run test eslint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + permissions: + packages: read + contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 + ref: ${{ github.event.pull_request.head.ref }} + token: ${{ secrets.GGITHUB_TOKEN }} + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 with: - node-version: "17" + node-version-file: ".node-version" cache: "pnpm" + - name: Authenticate NPM + uses: DimensionDev/github-token-action@latest + with: + registry: true + - name: Get cache date + id: get-date + run: echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_STATE + shell: bash + - name: Restore ESLint cache + uses: actions/cache@v5 + with: + path: .eslintcache + key: linter-v2-${{ steps.get-date.outputs.date }} + restore-keys: linter-v2- + - run: git config --global url."https://github.com/".insteadOf git@github.com - run: pnpm install + - run: pnpm exec gulp codegen - name: Run Eslint run: | - # report to stdio and to file, it's cached, the second run should be fast + export NODE_OPTIONS="--max-old-space-size=8192" + # report to stdio and to a file, it's cached so the second run should be fast pnpm run lint:ci pnpm run lint:ci-report + - uses: EndBug/add-and-commit@v9 + with: + message: 'fix: eslint' - name: Upload eslint report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v7 with: name: eslint path: reports/junit diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml new file mode 100644 index 000000000000..1451c4ae7971 --- /dev/null +++ b/.github/workflows/milestone.yml @@ -0,0 +1,62 @@ +name: "Check Milestone" + +on: + pull_request: + types: + - opened + - edited + - synchronize + - milestoned + - demilestoned + branches: + - 'develop' + - 'release/*' + - 'hotfix/*' + merge_group: + types: [checks_requested] + +permissions: + contents: read + +jobs: + check: + # disable + if: ${{ false }} + runs-on: ubuntu-latest + steps: + - name: Check Milestone + if: "(github.event.pull_request && github.event.pull_request.milestone == null) && github.event.pull_request.title != 'docs(i18n): sync i18n files from Crowdin'" + run: | + echo "Please set a milestone." + exit 1 + + - name: Check target branch of develop + if: ${{ github.event.pull_request && github.base_ref == 'develop' && github.event.pull_request.milestone != null }} # merge to develop + run: | + if [[ ${{ github.event.pull_request.milestone.title }} =~ ^[0-9]+.[0-9]+.0$ ]] + then + echo "Well Done" + else + echo "Don't patch on the develop branch." + exit 1 + fi + + - name: Check target branch of release/* + if: ${{ github.event.pull_request && startsWith(github.base_ref, 'release/') }} # merge to release/* + run: | + if [[ ${{ github.event.pull_request.milestone.title }} =~ ^[0-9]+.[0-9]+.0$ ]] + then + echo "Well Done" + else + echo "Don't patch on a pre-release branch." + fi + + - name: Check target branch of hotfix/* + if: ${{ github.event.pull_request && startsWith(github.base_ref, 'hotfix/') }} # merge to hotfix/* + run: | + if [[ ${{ github.event.pull_request.milestone.title }} =~ ^[0-9]+.[0-9]+.0$ ]] + then + echo "A hotfix branch only allows patch requests." + else + echo "Well Done" + fi diff --git a/.github/workflows/netlify.yml b/.github/workflows/netlify.yml deleted file mode 100644 index f044a9dbd179..000000000000 --- a/.github/workflows/netlify.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build - -on: - push: - branches: [master, develop*, released] - pull_request: - branches: [master, develop*, released] - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -jobs: - netlify: - runs-on: ubuntu-20.04 - env: - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - steps: - - name: Get cache date - id: get-date - run: echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" - shell: bash - - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - uses: pnpm/action-setup@v2.2.1 - - uses: actions/setup-node@v2 - with: - node-version: '16' - cache: 'pnpm' - - name: Restore Webpack cache - uses: actions/cache@v2 - with: - path: | - packages/dashboard/node_modules/.cache/ - packages/theme/node_modules/.cache/ - key: ${{ runner.os }}-netlify-${{ hashFiles('pnpm-lock.yaml') }}-${{ steps.get-date.outputs.date }} - # Not fallback to different dependencies. Webpack seems like have bug. - # An example caused by the webpack cache bug: https://github.com/facebook/react/issues/21587 - restore-keys: ${{ runner.os }}-netlify-${{ hashFiles('pnpm-lock.yaml') }} - - run: pnpm install - - run: npx gulp build-ci-netlify - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v1.2 - with: - publish-dir: './packages/netlify/' - production-branch: develop - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "Deploy from GitHub Actions" - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - timeout-minutes: 1 diff --git a/.github/workflows/spell-checking.yml b/.github/workflows/spell-checking.yml index de83baf8ff9c..500b44023b8d 100644 --- a/.github/workflows/spell-checking.yml +++ b/.github/workflows/spell-checking.yml @@ -2,22 +2,27 @@ name: Check Spell on: pull_request: - branches: [develop*] + branches: [master, develop, released, hotfix/*, release/*] + merge_group: + types: [checks_requested] + +permissions: + contents: read -# Cancels all previous workflow runs for pull requests that have not completed. concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. group: ${{ github.workflow }}-${{ github.head_ref }} cancel-in-progress: true jobs: check-spell: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version-file: ".node-version" - name: Check Spell + if: ${{ github.event.pull_request }} run: | git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1 - git diff --name-only origin/${{ github.event.pull_request.base.ref }} | xargs npx cspell lint --no-must-find-files + git diff --name-only origin/${{ github.event.pull_request.base.ref }} | xargs npx cspell lint --no-must-find-files --no-progress diff --git a/.gitignore b/.gitignore index ec3f16f1daf2..d1dc1a473550 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # generated files -i18n_generated.js -i18n_generated.d.ts -i18n_generated.d.ts.map +i18n_generated* +.swc +.env/dev-preference # VSCode personal settings .vscode/launch.json @@ -10,6 +10,8 @@ i18n_generated.d.ts.map # JetBrain personal settings .idea +.vim + # testing /reports /junit.xml @@ -23,36 +25,13 @@ i18n_generated.d.ts.map dist build /build-* -storybook-static -packages/netlify/sites/ /MaskNetwork.*.zip compilation-stats.json -# Environment files -.env.local -.env.development.local -.env.test.local -.env.production.local - # Block-chain contract files /contracts -# E2E tests -/.env/e2e-test -/.env/e2e-development -/.env/e2e-production -/.pptr-alice -/.pptr-bob -/screenshots -/.env -/.pptr-* - -# Temp profiles -.firefox -.chrome - # Following content is copied from https://github.com/github/gitignore/blob/master/Node.gitignore -# Logs logs *.log npm-debug.log* @@ -95,9 +74,6 @@ build/Release node_modules/ jspm_packages/ -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - # TypeScript cache *.tsbuildinfo @@ -119,17 +95,6 @@ web_modules/ # Output of 'npm pack' *.tgz -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - # Next.js build output .next out @@ -144,31 +109,6 @@ dist # https://nextjs.org/blog/next-9-1#public-directory-support # public -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - # Rush temporary files common/deploy/ common/temp/ @@ -181,3 +121,9 @@ common/autoinstallers/*/.npmrc # Disallow other package manager lock-file /package-lock.json /yarn.lock +quick-lint-js.config +.aider* +*.har + +.claude +.pnpm-store/ diff --git a/.husky/commit-msg b/.husky/commit-msg index 4f15872e3341..ef9f0fa958be 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname $0)/_/husky.sh" -npx --no-install commitlint --edit $1 +pnpm exec --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit index 8c404449e3ae..5ee7abd87c6f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname $0)/_/husky.sh" - -npx lint-staged +pnpm exec lint-staged diff --git a/.i18n-codegen.json b/.i18n-codegen.json deleted file mode 100644 index ded70c3006bd..000000000000 --- a/.i18n-codegen.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "$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": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useMaskI18N", - "namespace": "mask", - "trans": "MaskTrans", - "sourceMap": "inline" - } - }, - { - "input": "./packages/mask/src/social-network-adaptor/twitter.com/locales/en-US.json", - "output": "./packages/mask/src/social-network-adaptor/twitter.com/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useDO_NOT_USE", - "namespace": "DO_NOT_USE", - "trans": "DO_NOT_USE_TRANSLATOR", - "sourceMap": false - } - }, - { - "input": "./packages/shared/src/locales/en-US.json", - "output": "./packages/shared/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSharedI18N", - "namespace": "shared", - "trans": "SharedTrans", - "sourceMap": "inline" - } - }, - { - "input": "./packages/shared-base-ui/src/locales/en-US.json", - "output": "./packages/shared-base-ui/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useSharedBaseI18N", - "namespace": "shareBase", - "trans": "SharedBaseTrans", - "sourceMap": "inline" - } - }, - { - "input": "./packages/dashboard/src/locales/en-US.json", - "output": "./packages/dashboard/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useDashboardI18N", - "namespace": "dashboard", - "trans": "DashboardTrans", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/example/src/locales/en-US.json", - "output": "./packages/plugins/example/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "io.mask.example", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/Debugger/src/locales/en-US.json", - "output": "./packages/plugins/Debugger/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "io.mask.debugger", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/Wallet/src/locales/en-US.json", - "output": "./packages/plugins/Wallet/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.maskbook.wallet", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/Flow/src/locales/en-US.json", - "output": "./packages/plugins/Flow/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.mask.flow", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/FileService/src/locales/en-US.json", - "output": "./packages/plugins/FileService/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.maskbook.fileservice", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/CyberConnect/src/locales/en-US.json", - "output": "./packages/plugins/CyberConnect/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.maskbook.cyberconnect", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/RSS3/src/locales/en-US.json", - "output": "./packages/plugins/RSS3/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "bio.rss3", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/DAO/src/locales/en-US.json", - "output": "./packages/plugins/DAO/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "money.juicebox", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/Solana/src/locales/en-US.json", - "output": "./packages/plugins/Solana/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.maskbook.solana", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/mask/src/plugins/NextID/locales/en-US.json", - "output": "./packages/mask/src/plugins/NextID/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.mask.next_id", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/template/src/locales/en-US.json", - "output": "./packages/plugins/template/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "__template__", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/GoPlusSecurity/src/locales/en-US.json", - "output": "./packages/plugins/GoPlusSecurity/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "io.gopluslabs.security", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/plugins/CrossChainBridge/src/locales/en-US.json", - "output": "./packages/plugins/CrossChainBridge/src/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "io.mask.cross-chain-bridge", - "trans": "Translate", - "sourceMap": "inline" - } - }, - { - "input": "./packages/mask/src/plugins/Tips/locales/en-US.json", - "output": "./packages/mask/src/plugins/Tips/locales/i18n_generated", - "parser": "i18next", - "generator": { - "type": "i18next/react-hooks", - "hooks": "useI18N", - "namespace": "com.maskbook.tip", - "trans": "Translate", - "sourceMap": "inline" - } - } - ] -} diff --git a/.ignore b/.ignore new file mode 100644 index 000000000000..c98d244df8cb --- /dev/null +++ b/.ignore @@ -0,0 +1,3 @@ +# Ignores for rg or ag +patches/ +**/locale/*.{po,json} diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs deleted file mode 100644 index df61d12fe526..000000000000 --- a/.lintstagedrc.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export default { - '*': 'prettier --write --ignore-unknown', - 'packages/**/*.{ts,tsx,js,jsx}': 'eslint -c packages/.eslintrc.json --fix', - 'packages/mask/**/*': () => 'gulp locale-kit --sync-keys --remove-unused-keys', - 'packages/web3-constants/**/*': () => 'pnpm --filter ./packages/web3-constants start', - 'cspell.json': () => 'gulp reorder-spellcheck', -} diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index feec355226e4..000000000000 --- a/.markdownlint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "line-length": false -} diff --git a/.markdownlintignore b/.markdownlintignore deleted file mode 100644 index 3c3629e647f5..000000000000 --- a/.markdownlintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.node-version b/.node-version index a8ec175ad839..4c8f24d74e13 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v16.3.0 +v24.7.0 diff --git a/.npmrc b/.npmrc index 7ff61b493520..4dab09130554 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ -@dimensiondev:registry=https://npm.dimension.im/ -strict-peer-dependencies=false +@dimensiondev:registry=https://npm.pkg.github.com/ diff --git a/.npmrc.pages b/.npmrc.pages new file mode 100644 index 000000000000..16182d43237d --- /dev/null +++ b/.npmrc.pages @@ -0,0 +1,3 @@ +@dimensiondev:registry=https://npm.pkg.github.com/ +resolution-mode=time-based +//npm.pkg.github.com/:_authToken=${NPM_TOKEN} diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index fe4337139e1a..ece6548aa0f0 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -1,37 +1,20 @@ -// !!! There is some relative path installation source in the dependency tree, -// !!! but if we do not allow those packages to run install scripts anyway, it might be safe. -// !!! If we can resolve 'link:../empty' to something like 'workspaceRoot:/projects/empty', it will be safe to install. -/** @type {Map} */ -// @ts-expect-error -const approvedList = new Map([ - // opensea-js https://github.com/ProjectOpenSea/opensea-js/issues/407 - [ - 'ethereumjs-abi', - [ - 'git+https://github.com/ProjectWyvern/ethereumjs-abi.git', - 'git+https://github.com/ethereumjs/ethereumjs-abi.git', - 'https://github.com/ProjectWyvern/ethereumjs-abi.git', - ], - ], - [ - 'wyvern-js', - ['git+https://github.com/ProjectOpenSea/wyvern-js.git#v3.2.1', 'github:ProjectOpenSea/wyvern-js#semver:^3.2.1'], - ], - ['wyvern-schemas', 'git+https://github.com/ProjectOpenSea/wyvern-schemas.git#v0.13.1'], - /* cspell:disable-next-line */ - ['async-eventemitter', 'github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c'], - // opensea-js (v1), (and more, run `pnpm -r why web3@0.20.7`) -> web3 - ['bignumber.js', 'git+https://github.com/frozeman/bignumber.js-nolookahead.git'], +// !!! 1. Be responsible. +// !!! 2. Open a new issue to the library author. +// !!! 3. Add a pnpm/resolution in package.json if the package specifier does not include git hash to pin the version. - // https://github.com/i18next/i18next-translation-parser/issues/11 - // @magic-works/i18n-codegen -> i18next-translation-parser - /* cspell:disable-next-line */ - ['html-parse-stringify2', 'github:locize/html-parse-stringify2'], +/* cspell:disable */ +/** @type {Map} */ +const approvedList = new Map() - // ipfs https://github.com/ipfs/js-ipfs-utils/issues/158 - ['node-fetch', 'https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz'], -]) +approvedList.set('@types/react', ['npm:types-react@beta']) +approvedList.set('@types/react-dom', ['npm:types-react-dom@beta']) +// glob -> jackspeak -> @isaacs/cliui -> ... +approvedList.set('string-width-cjs', 'npm:string-width@^4.2.0') +approvedList.set('strip-ansi-cjs', 'npm:strip-ansi@^6.0.1') +approvedList.set('wrap-ansi-cjs', ['npm:wrap-ansi@^6.0.1', 'npm:wrap-ansi@^7.0.0']) + +approvedList.set('cbw-sdk', ['npm:@coinbase/wallet-sdk@3.9.3']) /** * @param {string} parentPackage The current resolving parentPackage * @param {string} dependedPackage The package it depends on @@ -44,39 +27,39 @@ function assertInstallationSourceValid(parentPackage, dependedPackage, installat if (Array.isArray(source) && source.indexOf(installationSource) !== -1) return } - if (dependedPackage === 'lodash-es' && installationSource.startsWith('npm:lodash@^4')) return - - // !!! Relative path - if (installationSource === 'link:../empty' || installationSource === 'link:..\\empty') return - if (installationSource === '../empty' || installationSource === '..\\empty') return + if (dependedPackage === '@typescript/lib-dom' && installationSource.startsWith('npm:@types/web@^')) return throw new Error( `Unapproved dependency source: Package: ${dependedPackage} Source: ${installationSource} - Declared by: ${parentPackage}`, + Declared by: ${parentPackage} + + If you want to approve this new unusual dependency, please edit .pnpmfile.cjs.`, ) } -function validatePackage({ dependencies, devDependencies, optionalDependencies, peerDependencies, name }) { +/* cspell:enable */ + +function validatePackage({ dependencies, optionalDependencies, peerDependencies, name, exports }) { + if ( + exports && + !name.startsWith('@dimensiondev') && + !name.startsWith('@masknet') && + JSON.stringify(exports).includes('mask-src') + ) { + throw new Error( + 'A package ' + name + ' out of @dimensiondev or @masknet scope using mask-src in exports field.', + ) + } for (const [k, v] of notNormativeInstall(dependencies)) assertInstallationSourceValid(name, k, v) // devDependencies won't be installed for intermediate dependencies - // for (const [k, v] of notNormativeInstall(devDependencies)) assertInstallationSourceValid(name, k, v) for (const [k, v] of notNormativeInstall(optionalDependencies)) assertInstallationSourceValid(name, k, v) for (const [k, v] of notNormativeInstall(peerDependencies)) assertInstallationSourceValid(name, k, v) } -function lockPackage(pkg) { - if (pkg.name === 'opensea-js') { - const prefix = 'git+https://github.com/ProjectOpenSea/wyvern-' - pkg.dependencies['wyvern-js'] = `${prefix}js.git#fabb7660f23f2252c141077e32193d281036299e` - pkg.dependencies['wyvern-schemas'] = `${prefix}schemas.git#e1a08fcf8ce2b11a0fe9cbdc7c9f77c59fadef26` - } -} - function readPackage(pkg, context) { validatePackage(pkg) - lockPackage(pkg) return pkg } diff --git a/.prettierignore b/.prettierignore index b3703149f7e7..d9fa784fa8fc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,14 +1,16 @@ +# https://github.com/prettier/prettier/issues/17784 +packages/theme/src/UIHelper/makeStyles.ts + +**/locale/*.json + .github/ .husky/ package.json package-lock.json pnpm-lock.yaml +**/CHANGELOG.md *generated* -storybook-static - -packages/storybook-shared/*.d.ts -packages/storybook-shared/*.js # VSCode personal settings .vscode/launch.json @@ -24,7 +26,6 @@ packages/storybook-shared/*.js # Build out dist/* /build -/storybook-static /MaskNetwork.*.zip # Environment files @@ -163,8 +164,3 @@ common/deploy/ common/temp/ common/autoinstallers/*/.npmrc **/.rush/temp/ - -# storybook-shared -packages/storybook-shared/*.js -packages/storybook-shared/*.map -packages/storybook-shared/*.d.ts diff --git a/.prettierrc b/.prettierrc index e52bf866b09a..7276bcf78c23 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,17 @@ "printWidth": 120, "semi": false, "singleQuote": true, - "bracketSameLine": true + "bracketSameLine": true, + "tabWidth": 4, + "experimentalTernaries": true, + "overrides": [ + { + "files": ["*rc", "package.json", "tsconfig*", "*.md", "*.yaml", "*.yml"], + "options": { "tabWidth": 2 } + }, + { + "files": ["*.ts", "*.tsx"], + "options": { "parser": "babel-ts" } + } + ] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f18d3a92cd50..ba84b843a107 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,11 +6,9 @@ "recommendations": [ "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", - "editorconfig.editorconfig", "aaron-bond.better-comments", - "davidanson.vscode-markdownlint", - "diegolincoln.vscode-styled-components", - "christian-kohler.path-intellisense" + "christian-kohler.path-intellisense", + "TypeScriptTeam.native-preview" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] diff --git a/.vscode/schema/constants.json b/.vscode/schema/constants.json index 4bff76476e14..d7c5c8ed9f08 100644 --- a/.vscode/schema/constants.json +++ b/.vscode/schema/constants.json @@ -30,7 +30,10 @@ "Matic", "Mumbai", "Arbitrum", - "Arbitrum_Rinkeby" + "Arbitrum_Rinkeby", + "Optimism", + "Optimism_Kovan", + "Optimism_Goerli" ] }, "primitive": { diff --git a/.vscode/settings.json b/.vscode/settings.json index cad096e7b384..a1ce6d4fdee8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,17 @@ { - "typescript.tsdk": "node_modules/typescript/lib", + "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.preferences.importModuleSpecifierEnding": "js", + "js/ts.experimental.useTsgo": true, + "js/ts.implicitProjectConfig.module": "NodeNext", + "js/ts.implicitProjectConfig.target": "ESNext", "editor.formatOnSave": true, "editor.formatOnSaveMode": "file", + "explorer.fileNesting.patterns": { + "README.md": ".commitlintrc*, .ecrc, .node-version, .npm*, .prettier*, .unlock*, .ignore, CNAME, gulp*, LICENSE, lint-staged*, cspell*, svgo*, *.ts, crowdin*", + "package.json": "pnpm-*.yaml, .pnpmfile.cjs" + }, "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, "editor.tabSize": 4, "json.schemas": [ @@ -12,11 +20,11 @@ "url": "http://json.schemastore.org/tsconfig" }, { - "fileMatch": ["/packages/mask/src/manifest.json"], + "fileMatch": ["/packages/mask/.webpack/manifest/*.json"], "url": "http://json.schemastore.org/chrome-manifest" }, { - "fileMatch": ["/packages/mask/src/plugins/*/schema.json"], + "fileMatch": ["/packages/plugins/*/schema.json"], "url": "http://json-schema.org/draft-07/schema" }, { @@ -24,7 +32,6 @@ "url": "/.vscode/schema/constants.json" } ], - "cSpell.words": ["Reificated"], "files.associations": { "**/.env/*": "ini" }, @@ -52,6 +59,10 @@ "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.wordWrap": "on", - "editor.quickSuggestions": true + "editor.quickSuggestions": { + "comments": "on", + "strings": "on", + "other": "on" + } } } diff --git a/.vscode/tasks.recommended.json b/.vscode/tasks.recommended.json index 8aed36ee58d1..5457056e6353 100644 --- a/.vscode/tasks.recommended.json +++ b/.vscode/tasks.recommended.json @@ -15,15 +15,6 @@ }, "runOptions": { "instanceLimit": 1, "runOn": "folderOpen" } }, - { - "type": "npm", - "script": "start", - "isBackground": true, - "group": "build", - "path": "packages/dashboard/", - "label": "Develop dashboard", - "problemMatcher": [] - }, { "type": "npm", "script": "start", @@ -35,7 +26,7 @@ }, { "type": "npm", - "script": "start", + "script": "start:mv3", "isBackground": true, "group": "build", "label": "Develop main extension", diff --git a/CNAME b/CNAME new file mode 100644 index 000000000000..dd1dee217194 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +code.mask.r2d2.to \ No newline at end of file diff --git a/README.md b/README.md index 86d10875614d..0699582e4df3 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ - ([I:b]) - # Mask Network [![GitHub license][badge-license]][license] -[![Circle CI][badge-ci]][circle-ci] [![Join the chat at https://gitter.im/Maskbook/community][badge-gitter]][gitter] [![Chrome Web Store][badge-chrome]][client-chrome] [![Mozilla Add-on][badge-firefox]][client-firefox] +[![Opera][badge-opera]][client-opera] [![FOSSA Status][badge-fossa]][fossa] [![Crowdin][badge-crowdin]][crowdin] Mask Network is a portal to the new, open internet, that bridge from Web2.0 to Web3.0 . -With Mask Network, you can send encrypted posts to your friends, trade your best token on Twitter, enjoy NFT pfps, +With Mask Network, you can send encrypted posts to your friends, trade your best token on X (formerly Twitter), enjoy NFT pfps, participate in crypto lucky-draws, and share encrypted files on the platforms you are already using. For general introductions, see [Mask.io](https://mask.io). @@ -27,24 +25,22 @@ For general introductions, see [Mask.io](https://mask.io). ## Downloads - [Mask Network on Chrome Web Store][client-chrome] -- [Mask Network on Firefox Addon Store][client-firefox] (Works on Firefox for Android too!) - -- [Mask Network for iOS][client-ios] -- (Testflight!) [Mask Network for iOS Testflight][client-ios-testflight] -- (Beta!) [Mask Network for Android][client-android] +- [Mask Network on Firefox Addon Store][client-firefox] +- [Mask Network on Opera Addon Store][client-opera] ## Useful Links - [User Forum](https://github.com/DimensionDev/Maskbook/discussions) - [Report Bug][report-bug] -- [Twitter](https://twitter.com/realMaskNetwork) +- [X (formerly Twitter)](https://x.com/masknetwork) - [Facebook](https://www.facebook.com/masknetwork) - [Telegram](https://t.me/maskbook_group) - [Discord](https://discord.gg/4SVXvj7) +- [YouTube](https://www.youtube.com/c/MaskNetwork) ## Developer documentation -Please check out [docs](docs) +Please check out [docs](https://docs.mask.io/) ## Reporting a Vulnerability @@ -57,19 +53,15 @@ see [SECURITY.md](.github/SECURITY.md) Please submit your **resume** to `jobs (at) mask.io` [badge-chrome]: https://img.shields.io/chrome-web-store/v/jkoeaghipilijlahjplgbfiocjhldnap.svg?logo=Maskbook&logoColor=%231c68f3&style=flat-square&label=Chrome%20store -[badge-ci]: https://img.shields.io/circleci/project/github/DimensionDev/Maskbook.svg?style=flat-square&logo=circleci [badge-crowdin]: https://badges.crowdin.net/mask-network/localized.svg [badge-firefox]: https://img.shields.io/amo/v/maskbook?label=Firefox%20store&style=flat-square +[badge-opera]: https://img.shields.io/badge/Opera%20store-v2.32.0-blue.svg [badge-fossa]: https://app.fossa.io/api/projects/git%2Bgithub.com%2FDimensionDev%2FMaskbook.svg?type=shield [badge-gitter]: https://badges.gitter.im/Maskbook/community.svg [badge-license]: https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square -[client-android]: https://play.google.com/store/apps/details?id=com.dimension.maskbook [client-chrome]: https://chrome.google.com/webstore/detail/maskbook/jkoeaghipilijlahjplgbfiocjhldnap/ [client-firefox]: https://addons.mozilla.org/en-US/firefox/addon/maskbook/ -[client-ios-testflight]: https://testflight.apple.com/join/PYomz4pJ -[client-ios]: https://apps.apple.com/app/id1478382964 -[bounty]: https://github.com/DimensionDev/Maskbook/issues?q=is%3Aopen+is%3Aissue+label%3A%22Bounty%3A+Open%22 -[circle-ci]: https://circleci.com/gh/DimensionDev/Maskbook +[client-opera]: https://addons.opera.com/en/extensions/details/maskbook/ [crowdin]: https://crowdin.com/project/mask-network [fossa-status]: https://app.fossa.io/api/projects/git%2Bgithub.com%2FDimensionDev%2FMaskbook.svg?type=large [fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2FDimensionDev%2FMaskbook diff --git a/crowdin.yml b/crowdin.yml index eb5e1ac758d4..2cde1f846c34 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -2,9 +2,7 @@ preserve_hierarchy: true pull_request_title: 'docs(i18n): sync i18n files from Crowdin' commit_message: 'docs(i18n): sync translation %original_file_name% for %language%' files: - - source: /packages/**/locales/en-US.json - translation: /packages/**/locales/%locale%.json + - source: /packages/shared-base-ui/src/locale/en-US.json + translation: /packages/shared-base-ui/src/locale/%locale%.json update_option: 'update_as_unapproved' skip_untranslated_strings: true - ignore: - - /packages/plugins/example diff --git a/cspell.json b/cspell.json index 9e1391ac7183..86ff077b59c0 100644 --- a/cspell.json +++ b/cspell.json @@ -1,346 +1,278 @@ { + "version": "0.2", "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", - "dictionaries": ["typescript", "node", "html", "css", "fonts"], + "dictionaries": ["typescript", "node", "npm", "html", "css", "fonts"], "ignorePaths": [ + "./packages/gun-utils/gun.js", + "./packages/icons/**", + "./packages/mask/swap/webgl/**", + "./packages/web3-contracts/types/**", + "./patches/**", ".github", + ".gitignore", + ".patch", ".prettierignore", + "*.css", + "*.json", + "*.po", "*.snap", - "**/*.json", - "**/dist/**", - "**/node_modules/**", + "*.svg", + "*.tsbuildinfo", "*generated*", - "patches/**", - "pnpm-lock.yaml" + "build", + "dist", + "node_modules", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + "qya-aa.json", + ".env/dev-preference.example" ], - "words": [ - "aave", - "adai", - "addrs", + "TODO: fix those words": ["bridgable", "clonable", "sniffings"], + "ignoreWords": [ "aeth", - "amountstate", - "ampl", - "ampleforth", + "algr", "anchorme", - "apikey", - "arbitrum", - "areth", + "apng", + "arbid", "artblocks", "arweave", - "avax", - "bgcolor", + "astar", "bignumber", - "bips", - "birthdate", "bitcointalk", + "bitget", + "bitkeep", + "blockhash", "blockie", "blockies", - "bluebar", - "boba", + "blocto", + "blurhash", "bonfida", - "caip", - "canonify", + "bridgable", + "bridgers", + "bscscan", + "bsct", "cashtag", "cbridge", - "checksummed", - "choudhary", - "cipherparams", - "ciphertext", + "celer", + "chainbase", + "chainid", "clonable", "codegen", - "commentid", - "composertitlebar", + "coincarp", "contribs", - "cooldown", - "corepack", + "cronos", + "crossbell", + "cryptopunks", + "cusd", "cyberconnect", - "cyberlab", - "datas", + "darkweb", + "dataitem", "debank", - "defikingdoms", - "denorm", - "dfyn", - "dhedge", + "deficonnect", + "devnet", "dimensiondev", - "dklen", "dompurify", - "ecsign", - "endregion", + "dotbit", + "dsearch", + "efprpc", "enhanceable", - "ethersphere", - "ethersproject", - "everytime", - "fbid", + "ethfollow", + "evmos", + "farcaster", + "favourites", "filelist", "fileservice", - "flashloan", - "formating", - "fullfilled", - "furucombo", - "gltf", - "goodghosting", - "gundb", - "hashmasks", + "funder", + "fusd", + "gopluslabs", + "gorli", + "healthz", + "heco", "holoflows", - "iconbar", + "hookz", + "hovercard", + "husd", "idbp", - "ifclaimed", + "ifrandom", "imagebitmap", + "imagedelivery", "immer", - "imtokenv", - "investables", + "inpage", "ipfs", "ipfshttp", - "ittr", - "jsbi", - "juicebox", - "kdfparams", + "iqwiki", + "jkoeaghipilijlahjplgbfiocjhldnap", "keccak", "keybase", - "kred", + "keypair", + "kiwistand", + "klaytn", + "lamport", "lamports", "languagedetector", - "lendingpool", - "linkswap", + "lavamoat", + "leaderboard", + "lensprotocol", + "lenster", + "linea", + "lingui", + "linkify", + "lngs", "locationchange", "lockdown", - "macbinary", + "looksrare", + "magiceden", "maskbook", "masknet", "masknetwork", + "maskwallet", "mastdon", - "mdex", - "merkel", "merkle", + "metaswap", "metaverse", - "millify", + "milkomeda", "mintable", - "monofont", - "mooniswap", + "momoka", + "moonriver", + "msgid", "msgpack", + "msgstr", "multicall", - "multihop", - "newsfeed", - "nftrss", + "multisigs", + "nftgo", + "nftredpackets", "nftscan", - "nums", - "openswap", + "nftx", + "nonfungiblefriends", + "notistack", + "nstructions", + "oembed", + "okex", + "okxwallet", + "onekey", + "opengraph", + "openocean", "openx", - "overridable", "overscan", + "ownerblock", "pageable", - "pagelet", - "pancakeswap", - "pangolindex", - "pathnames", - "perma", - "pids", - "plusplus", - "pooltogether", - "porportion", - "prefetch", - "presale", - "pressable", - "promi", - "proxied", - "pushstate", + "pausable", + "paytoken", + "poap", + "polygonscan", + "postop", + "prefund", + "profilecard", "pvtsutils", "qrcode", "quickswap", - "radisk", + "rabby", + "rari", "rarible", - "realise", - "rebalance", + "raycast", + "raydium", + "rdns", + "realmasknetwork", + "recasted", "redpacket", - "repayer", - "replacestate", + "redpackets", + "rehype", "repost", - "resizer", - "rhonin", - "rindexed", + "robinhood", "rpid", - "sablier", - "sashimiswap", - "scroller", + "rspack", + "rweb", + "savm", + "scamsniffer", + "scenechange", + "schnorr", "secp", - "serializers", + "servie", + "shiden", + "signless", + "simplehash", + "splinetool", + "sniffings", "solana", + "solanaart", + "solanart", + "solflare", "sollet", - "steganographic", + "solsea", + "spaceid", + "stargate", "steganography", "stego", - "subrepo", - "sushiswap", + "Binaryen", + "suji", + "Emscripten", + "suspendable", "swappable", + "sysvar", + "telos", "testid", - "thegraph", - "timelock", - "timelocked", - "titlebar", - "tokenid", - "topbar", - "traderjoe", + "tinycolor", + "tokenpocket", "transak", - "trisolaris", + "trustwallet", + "tsgo", "twimg", - "txid", + "twitterblue", + "txes", "typechain", - "typehash", "typeson", - "unencrypted", - "unreviewed", + "unfollow", + "unfollower", + "unitoken", + "unpadded", "unstake", - "unstyled", + "unstoppabledomains", + "upvoted", + "upvotes", "urlcat", - "usermenu", "vcent", - "venomswap", - "viewbox", - "viperswap", "visualcompletion", + "vitalik", "walletconnect", - "wannaswap", - "wault", - "wavax", + "wanchain", "webextension", - "webm", - "withdrawed", - "wmatic", + "winternitz", + "wnative", "xdai", "xlarge", + "xlayer", + "yumi", "zerion", - "zora", - "zubin" + "zkscan", + "zksync", + "zora" ], - "ignoreWords": [ - "aicanft", - "algr", - "anft", - "avator", - "blocto", - "bsct", - "btcb", - "canft", + "ignoreRegExpList": ["/[A-Za-z0-9]{44}/", "/[A-Za-z0-9]{46}/", "/[A-Za-z0-9]{59}/"], + "overrides": [], + "words": [ + "abitype", + "arbitrum", + "boba", + "Brise", + "caip", + "cashtags", "celo", - "ceur", - "chainid", - "circleci", - "clueid", - "consts", - "cotm", - "cryptoart", - "cryptoartai", - "csrftoken", - "cusd", - "defi", - "deletable", - "devnet", - "dodoex", - "drawed", - "energi", - "enjin", - "ethjs", - "fbclid", - "findtruman", - "frax", - "ftgs", - "ftpoap", - "fusd", - "gamification", - "gopluslabs", - "gorli", - "gusd", - "hookform", - "horiz", - "huobi", - "husd", - "ifadmin", - "ifrandom", - "iife", - "inpage", - "koda", - "kodav", - "lngs", - "maskface", - "maskwallet", - "mathwallet", - "metadate", - "metaplex", - "metaswap", - "nftred", - "nftx", - "nonfungiblefriends", - "northcutt", - "notistack", - "nrge", - "nrgt", - "nyfi", - "openocean", - "pausable", - "pkts", - "pnpm", - "poap", - "poaps", - "powah", - "powahs", - "rawdata", - "redpackets", - "renfil", - "shapeclip", - "steth", - "swaptoken", - "testweb", - "tinycolor", - "tipable", - "traderjoexyz", - "treeshake", - "txes", - "txreceipt", - "uaddr", - "uipd", - "unauthenticate", - "unitoken", - "unlocklock", - "unlockprotocol", - "usdp", - "usei", - "vitalik", - "walletlink", - "wnative", - "xsushi" - ], - "ignoreRegExpList": ["/@servie/"], - "overrides": [ - { - "filename": "**/*.md", - "ignoreWords": ["guanbinrui", "jkoeaghipilijlahjplgbfiocjhldnap", "pnpx", "yisiliu", "zhouhancheng"] - }, - { - "filename": "packages/web3-contracts/types/GoodGhostingIncentives.d.ts", - "ignoreWords": ["adming", "ditribution"] - }, - { - "filename": "packages/web3-shared/evm/assets/**/*.json", - "ignoreWords": ["görli"] - }, - { - "filename": "packages/mask/src/plugins/ITO/assets/regions.json", - "enabled": false - }, - { - "filename": "**/qya-AA.json", - "enabled": false - }, - { - "filename": "**/pnpm-lock.yaml", - "enabled": false - }, - { - "filename": "**/*.html", - "enabled": false - }, - { - "filename": ".circleci/config.yml", - "ignoreWords": ["cimg"] - } + "endregion", + "frameable", + "Hrefs", + "Idempotently", + "linkedin", + "luma", + "muln", + "reposted", + "reposts", + "sepolia", + "tanstack", + "tiktok", + "tweetnacl", + "txid", + "viem", + "waitlist", + "youtube" ] } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index e9c4c414a9c4..000000000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -author: Jack-Works -maintainer: - - Jack-Works - - Septs ---- - -# Mask Network Architecture Overview - -![WIP](https://img.shields.io/badge/status-wip-orange.svg?style=flat-square) - -> This document describes the Mask Network protocols, the subsystems, -> the interfaces, and how it all fits together. -> It delegates non-interface details to other specs as much as possible. -> This is meant as a top-level view of the protocol and how the system fits together. - -## Subsystems - -### Background service - -The entry point is `packages/mask/src/background-service.ts` - -Background service is like a "backend" or "server" in a normal web app. -It is running on a web page that not visible to the user ([background page in Web Extensions][background-page]). -The background page hosts mosts of our non-UI-related work in the Mask Network. If you see code like - -[background-page]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#background_scripts - -```js -Services.Crypto.encrypt(...) -``` - -It is sending the request to the background service. - -### Content scripts - -The entry point is `packages/mask/src/content-script.ts`. - -All UI on the Twitter/Facebook are rendered by the content scripts. - -### Options page (or Dashboard) - -The entry point is `packages/mask/src/extension/options-page/index.tsx`. - -This is a normal web app that interacts with the background page. - -### Injected scripts - -The entry point is `packages/mask/src/extension/injected-script/index.ts`. - -Generally, you don't need to modify this. It provides the ability to change the main Realm of the web page. (Thus we can emulate some DOM events). - -![Architecture overview](https://user-images.githubusercontent.com/5390719/109270562-28f4a700-7849-11eb-9a7a-b364318bdeec.png) diff --git a/docs/FAQ.md b/docs/FAQ.md deleted file mode 100644 index 543bef8d0847..000000000000 --- a/docs/FAQ.md +++ /dev/null @@ -1,103 +0,0 @@ -# FAQ - -## How to resolve merge conflicts in `pnpm-lock.yaml`? - -Merge the target branch into yours and never mind those conflicts in `pnpm-lock.yaml`. And checkout the file to be the one on the target branch to revert changes your branch took in. Then run `pnpm install` to up the lockfile to date. - -E.g., your `feat/fantasy` branch conflicts with `develop` branch. - -```bash -> git branch --show-current -feat/fantasy - -# merge the develop branch and never manually handle the conflicts in lock file -> git merge develop - -# check out the lock file from the base branch -> git checkout develop -- pnpm-lock.yaml - -# up the lockfile to date -> pnpm install -``` - -## Why my Git hooks don't work? - -```bash -npx husky install # on project root directory -``` - -## How to fix cspell errors in CI? - -This project uses [cspell](https://github.com/streetsidesoftware/cspell) for checking typos. You can add unlisted words into `cspell.json` to bypass cspell checking. After you update the configuration file, you could run checking locally before pushing it to make sure your patch is working. - -```bash -npx cspell lint pattern_that_match_your_files - -# e.g. check spell of the RSS3 plugin -npx cspell lint ./packages/plugins/RSS3/**/* -``` - -Learn more: [`cspell.json`](https://cspell.org/configuration/#cspelljson) - -## Why were my components rendered many times? - -All components should working in [Strict Mode](https://reactjs.org/docs/strict-mode.html) and React 18 new [Strict Effects](https://github.com/reactwg/react-18/discussions/19). - -If you found your code not working correctly, please read the documentation above. In addition, you can comment out `` temporarily to verify if it is a problem with your component not supporting Strict Mode. - -DO NOT remove ``. - -## How to download CI builds? - -| Name | Description | -| ----------------------------- | ----------------------------------------------------------------------- | -| MaskNetwork.base.zip | The default build, currently is the same as the Chromium build. | -| MaskNetwork.chromium-beta.zip | Build for Chromium based browsers with some insider features turned on. | -| MaskNetwork.chromium.zip | Build for Chromium based browsers | -| MaskNetwork.firefox.zip | Build for Firefox | -| MaskNetwork.gecko.zip | Build for Android native Mask app | -| MaskNetwork.iOS.zip | Build for iOS native Mask app | - -You can download these builds in two places. - -- Github: Open the pull request page, and click the **Actions** tab. Then on the opened page, click the **build** sub-item on the **Compile** item. On the action detailed page, click the **Summary** tab. Now you can download builds on the **Artifacts** section. - - E.g., - -- CircleCI: Open the pull request page, and scroll down to the review status card. Click **Show all checks** to find the ** - ci/circleci: build** item, and click the **details** link. On the opened CircleCI page, click the **ARTIFACTS** tab. - - E.g., - -## How to resolve "No CORS Headers" problem - -Please contact the service maintainer to add CORS headers, the extension will send requests in following origins: - -| Browser | Origin | -| -------- | --------------------------------------------------- | -| Chromium | chrome-extension://jkoeaghipilijlahjplgbfiocjhldnap | -| Firefox | moz-extension://id | - -The Chromium extension has a fixed id, but only on production mode. And the Firefox browser will set a new id each time it boots an extension. So, in summary, it's better to allow all origins which match the regexp below. - -```txt -/.*-extension:\/\/[^\S]+/ -``` - -If you cannot reach the service maintainer another workaround is to use a proxy server to add CORS headers. To enable it try to change the request URL into `https://cors.r2d2.to/?=[url]`. - -```ts -// before -fetch('https://api.com') - -// after -fetch('https://cors.r2d2.to/?=https://api.com') -``` - -## How to clear the local settings? - -Open the background.html of the extension and execute the following scripts in the console. - -```js -browser.storage.local.clear() -``` diff --git a/docs/blockchain-integration.md b/docs/blockchain-integration.md deleted file mode 100644 index 7b625e76c890..000000000000 --- a/docs/blockchain-integration.md +++ /dev/null @@ -1 +0,0 @@ -# Blockchain Integration diff --git a/docs/bounty-development-guide.md b/docs/bounty-development-guide.md deleted file mode 100644 index 2e3f8887d93e..000000000000 --- a/docs/bounty-development-guide.md +++ /dev/null @@ -1,74 +0,0 @@ -# Bounty Development Guide - -Hi, Awesome people! Welcome to start a bounty task on Mask Network. - -## Tech Requirements - -Mask Network extension is written in TypeScript. The UI is written by React and [@mui](https://mui.com/) framework. We write CSS in [CSS-in-JS](css-in-js.md) style. - -We prefer widely adopting tech solutions that include: - -- [Web3.js](https://web3js.readthedocs.io/) Ethereum JavaScript API -- [react-use](https://streamich.github.io/react-use/) React Hooks — 👍 -- [bignumber.js](https://mikemcl.github.io/bignumber.js/) A JavaScript library for arbitrary-precision arithmetic. -- [lodash](https://lodash.com/docs/) A modern JavaScript utility library delivering modularity, performance & extras. -- [urlcat](https://urlcat.dev/) A URL builder library for JavaScript. - -> If your bounty task is related to another project, it could have extra requirements that need to consider. - -If you are familiar with these libraries mentioned above, it will take less effort for you to get started. -The codebase is open-sourced under the AGPLv3 license. - -## Packages - -After cloning the repository and [set up the development environment](setup.md). The codebase is constructed as a monorepo with many internal packages. Each package serves a specific purpose. Let's take a quick tour. - -### Core Packages - -- `packages/mask` The main extension which has multiple websites supports, keeps the user's data safe and hosts a plugin system. -- `packages/encryption` The encryption & decryption of mask network. -- `packages/plugin-infra` The definition of the plugin system, with a bunch of APIs to expose the core abilities to plugins. - -### Plugin Packages - -- `packages/plugins/*` All of integrated plugins. - -### Shared Packages - -- `packages/shared` Shared UI components and utilities. -- `packages/shared-base` Shared types, constants, and atomic units. Must be as pure as possible and testable. - -### Web3 Packages - -- `packages/web3-constants` Each Web3 constant must set up for all known chain IDs. -- `packages/web3-contracts` EVM contract ABIs and compiled TypeScript definitions. -- `packages/web3-provider` A hub of APIs for external data source. -- `packages/web3-shared-*` Shared hooks, utilities, types for each network. - -## Learn Through Examples - -Almost all bounty tasks for the Mask Network plugin relate to a plugin. After learning the basics, checkout those pull requests or plugins to learn quick from examples. - -### Dapp Plugins - -| Plugin | Pull Request Links | -| ----------- | ----------------------------------------------------------------------------------------------- | -| Collectible | | -| Trader | | -| Savings | | - -### Network Plugins - -| Plugin | Pull Request Links | -| ---------- | -------------------------------------------------------------------------------------- | -| EVM Chains | | - -## Pull Request Conversions - -After bounty hacker opening a pull request. Reviewer will label it with `Type: Bounty`, and update a status tag while the progress on-going. - -| Status | Description | -| ------------------- | ---------------------------------------------------------------------------- | -| `Bounty: Started` | The DEV team noticed your request. You will receive comments from reviewers. | -| `Bounty: Reviewed` | The QA team noticed your request. You will receive bugs from reviewers. | -| `Bounty: Qualified` | Your request is qualifed. It will ship soon. | diff --git a/docs/concepts.md b/docs/concepts.md deleted file mode 100644 index 8f2f9615e826..000000000000 --- a/docs/concepts.md +++ /dev/null @@ -1,21 +0,0 @@ -# Concepts - -## Persona - -## Post Payload - -## Wallet - -## Dashboard - -## Popup - -## SNS Provider - -## Wallet Provider - -## Plugin - -## Network - -## ChainId diff --git a/docs/css-in-js.md b/docs/css-in-js.md deleted file mode 100644 index d92374225867..000000000000 --- a/docs/css-in-js.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -author: Jack-Works -maintainer: - - Jack-Works - - Septs ---- - -# CSS in JS guide in Mask Network - -## General guides - -- ✅ For recommendations -- ⚠ For warnings -- 🚫 For forbidden -- ✅ Use [the Box component provided by the library](https://next.material-ui.com/components/box/#main-content) - when the CSS is simple and only used once. -- ✅ CSS custom variables is OK but do not abuse it. - Get the variable from the theme if it is possible. -- ✅ CSS grid is OK but you may want to use the `Grid` component. - Choose the fit one based on your need. -- 🚫 DO NOT use mystery abbreviations in the `sx` properties, e.g. `` - (DON'T) but `` (DO, easier to read). - -## Guides on the `makeStyles` style - -```ts -const useStyle = makeStyles()((theme) => ({ - root: { margin: theme.spacing(4) }, -})) -``` - -### Change style of MUI components - -✅ DO - -```js - -} -``` - -First of all, it creates a [Web3](https://web3js.readthedocs.io/) instance which redirects all JSON-RPC requests to the [request](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/request.ts) service on the background page. If you'd like to read the source code, you will realise that there is a [Koa.js](https://koajs.com/) styled [composer](https://github.com/koajs/compose) built-in. A list of middleware is used and serve different purposes: a middleware stores transactions into DB, a middleware watches transaction status, a middleware notifies transaction progress, and so on. - -At the current stage, there are two kinds of wallets: Mask Wallet and other wallets. - -Mask Wallet sends requests to the network directly on the background page. If the request takes the response, then the user will get notified. - -But it's not that simple for other wallets. They are supported only on the front end. E.g., the [Fortmatic](https://docs.fortmatic.com/) SDK injects an iframe on the currently visiting page. Mask Network cannot invoke those SDKs on the background page as an extension. Because of that, they should take their requests to the front end and handle them there. Many mounted components, so-called `ProviderBridge`, listen to the `PROVIDER_RPC_REQUEST` event and call the corresponding SDK once they receive any request from the background. After the SKD finishes the work, they return the result to the bridged provider on the background page with the `PROVIDER_RPC_RESPONSE` event. - -It takes a quite long detour, but the benefit is all requests can leverage Mask Wallet abilities. - -## A Wallet on a bridged provider - -If the wallet that only works on the front end. It needs to use the bridged provider way. - -On the front end: - -- create a bridged provider by implementing the [`EIP1193Provider`](https://github.com/DimensionDev/Maskbook/blob/develop/packages/web3-shared/evm/types/index.ts) interface. -- instantiate the bridged provider in [useBridgedProvider](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/plugins/EVM/hooks/useBridgedProvider.ts) which was used by [``](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/plugins/EVM/UI/components/ProviderBridge.tsx). -- add a new `` in the [EVM](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx) plugin to receive events from the background page. - -On the background page: - -- instantiate a [BridgedProvider](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/providers/Bridged.ts) and add it into the supported list in [getProvider](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/provider.ts). - -## A wallet without any UI - -If the wallet is totally UI free and can connect/disconnect by calling some APIs. It can send requests to those APIs directly. - -On the background page: - -- create a provider to extend from the [BaseProvider](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/providers/MaskWallet.ts) interface and add it into the supported list in [getProvider](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/provider.ts). - -## Interceptor - -The implementation of Ethereum JSON-RPC may very different between wallets. Those JSON-RPC requests will need some preprocessing before sending to the real wallet. Nevertheless, the Mask Network flavors a bunch of self-known RPC methods that were unknown to other wallets. Bypassing a such request will hit an unimplemented error. - - - -For this sake, the [`composer`](https://github.com/DimensionDev/Maskbook/blob/develop/packages/mask/src/extension/background-script/EthereumServices/composer.ts) creates a middleware for intercepting JSON-RPC requests. Here is a quick example that converts the Mask Network flavored [`mask_requestAccounts`](./mask-flavored-**jsonrpc**-api.md#mask_requestaccounts) into an Ethereum styled [`eth_accounts`](https://eth.wiki/json-rpc/API#eth_accounts). - -```ts -export class Example implements Middleware { - async fn(context: Context, next: () => Promise) { - switch (context.method) { - case EthereumMethodType.MASK_REQUEST_ACCOUNTS: - context.requestArguments = { - ...context.requestArguments, - method: EthereumMethodType.ETH_ACCOUNTS, - } - break - default: - break - } - await next() - } -} -``` - -## Examples - -| Wallet | Implementation | -| ------------- | -------------- | -| MetaMask | \- | -| WalletConnect | \- | -| Fortmatic | \- | diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000000..7f3c27094b15 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,593 @@ +// cSpell:disable +// @ts-check +import eslint from '@eslint/js' +import { defineConfig } from 'eslint/config' +import tseslint from 'typescript-eslint' +import unicorn from 'eslint-plugin-unicorn' +import UnusedImportsPlugin from 'eslint-plugin-unused-imports' +// @ts-expect-error +import UnusedClassesPlugin from 'eslint-plugin-tss-unused-classes' +import ReactCompilerPlugin from 'eslint-plugin-react-compiler' +import ImportPlugin from 'eslint-plugin-import-x' +import eslintReact from '@eslint-react/eslint-plugin' +import MasknetPlugin from '@masknet/eslint-plugin' +import tanstackReactQuery from '@tanstack/eslint-plugin-query' +import lingui from 'eslint-plugin-lingui' +import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript' + +const deferPackages = [ + 'wallet.ts', + 'anchorme', + '@blocto/fcl', + '@metamask/eth-sig-util', + '@masknet/gun-utils', + 'twitter-text', + '@solana/web3.js', + // add package names here. +] + +// Prefer rules from @typescript-eslint > unicorn > other plugins +// Level: if the rule is fixable and can be tolerate during dev, use 'warn' is better. +// if the fix needs big rewrite (e.g. XHR => fetch), use 'error' to notice the developer early. +// for RegEx rules, always uses 'error'. + +/** @type {Partial} */ +const disabledRules = { + // Not compatible + // with TypeScript + 'unicorn/no-optional-chaining-on-undeclared-variable': 'off', + // TypeScript can do the check + 'unicorn/no-array-callback-reference': 'off', + 'unicorn/no-invalid-argument-count': 'off', + // false positives + 'unicorn/no-useless-coercion': 'off', + 'unicorn/prefer-spread': 'off', // TypedArrays + 'unicorn/require-number-to-fixed-digits-argument': 'off', // BigNumber + // WebExtension: in Firefox content scripts, window !== globalThis (instanceof SandBox). + 'unicorn/no-unnecessary-global-this': 'off', + 'unicorn/prefer-global-this': 'off', + // with Prettier + 'unicorn/number-literal-case': 'off', + + // bad practice + 'unicorn/prefer-top-level-await': 'off', // top-level await is bad for applications. scripts are ok. + + // too strict + '@eslint-react/jsx-no-leaked-dollar': 'off', // all use cases in our codebase are not a bug, but a currency amount. + '@eslint-react/no-array-index-key': 'off', + '@typescript-eslint/no-deprecated': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'off', // require promise to be then or catch + '@typescript-eslint/no-misused-promises': 'off', // require promise to be then or catch + '@typescript-eslint/no-namespace': 'off', // namespace T {}, they won't support type only namespace + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unnecessary-condition': 'off', // not sure if we can trust our typing + '@typescript-eslint/no-unnecessary-type-conversion': 'off', // we have some defensive code for untrusted input, not sure if it is safe to remove those + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', // we use a lot of || to check falsy string "" in web3 related code. nullish coalescing will break those code. + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/triple-slash-reference': 'off', // we need it, maybe + 'lingui/no-expression-in-message': 'off', + 'unicorn/no-array-reduce': 'off', + 'unicorn/no-await-expression-member': 'off', + 'unicorn/no-break-in-nested-loop': 'off', + 'unicorn/no-computed-property-existence-check': 'off', + 'unicorn/no-top-level-assignment-in-function': 'off', + 'unicorn/no-top-level-side-effects': 'off', + + // too strict, this require elimination of any + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + + // style, readibility and convention + 'unicorn/consistent-class-member-order': 'off', + 'unicorn/explicit-length-check': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/import-style': 'off', + 'unicorn/max-nested-calls': 'off', + 'unicorn/no-for-each': 'off', + 'unicorn/no-nested-ternary': 'off', + 'unicorn/no-non-function-verb-prefix': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-unreadable-for-of-expression': 'off', + 'unicorn/no-useless-else': 'off', // useless else sometime is more readable than early return. + 'unicorn/prefer-await': 'off', // sometimes a .then/catch more readable + 'unicorn/prefer-includes-over-repeated-comparisons': 'off', // not worth + 'unicorn/prefer-switch': 'off', // switch is bad + 'unicorn/switch-case-braces': 'off', // we have a rule that warns for decelaration in switch case + + // TODO: add back + '@eslint-react/exhaustive-deps': 'off', + '@eslint-react/use-state': [ + 'warn', + { + enforceSetterName: false, + // enforceAssignment: false, + }, + ], + '@tanstack/query/prefer-query-options': 'off', + '@typescript-eslint/no-confusing-void-expression': 'off', // reasonable, but too much work + '@typescript-eslint/no-invalid-void-type': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/require-await': 'off', // most cases, parameter signature required to + '@typescript-eslint/restrict-template-expressions': 'off', // recheck with number allowed in template string + '@typescript-eslint/unbound-method': 'off', + 'unicorn/consistent-boolean-name': 'off', + 'unicorn/consistent-compound-words': 'off', + 'unicorn/name-replacements': 'off', + 'unicorn/prefer-number-coercion': 'off', +} + +/** @type {Partial} */ +const avoidMistakeRules = { + // Libraries + '@tanstack/query/no-rest-destructuring': 'error', + 'lingui/no-plural-inside-trans': 'error', + + // Code quality + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-expect-error': 'allow-with-description', + 'ts-ignore': true, + 'ts-nocheck': true, + 'ts-check': false, + minimumDescriptionLength: 5, + }, + ], // disable a rule requires a reason + /// React bad practice + 'react-compiler/react-compiler': 'error', + /// TypeScript bad practice + '@typescript-eslint/no-restricted-types': [ + 'error', + { + types: { + FC: { + message: + "To declare a component, you don't have to use FC to annotate it. To type something that accepts/is a React Component, use ComponentType.", + fixWith: 'ComponentType', + }, + ReactElement: { + message: + 'In most cases, you want ReactNode. Only ignore this rule when you want to use cloneElement.', + fixWith: 'ReactNode', + }, + 'React.FC': { + message: + "To declare a component, you don't have to use React.FC to annotate it. To type something that accepts/is a React Component, use React.ComponentType.", + fixWith: 'React.ComponentType', + }, + 'React.ReactElement': { + message: + 'In most cases, you want React.ReactNode. Only ignore this rule when you want to use cloneElement.', + fixWith: 'React.ReactNode', + }, + }, + }, + ], + '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }], + /// Unicode support + 'require-unicode-regexp': 'error', // RegEx modern RegEx with Unicode support + // '@masknet/no-builtin-base64': 'warn', // Note: it fixes to Node's Buffer + /// type safety + // '@typescript-eslint/method-signature-style': 'warn', // method signature is bivariant + // '@typescript-eslint/strict-boolean-expressions': 'error', // stronger check for nullable string/number/boolean + // '@typescript-eslint/switch-exhaustiveness-check': 'error', // switch should be exhaustive + // '@masknet/type-no-force-cast-via-top-type': 'error', // expr as any as T + + // Security + '@masknet/browser-no-set-html': 'error', // .innerHTML = + '@masknet/unicode-no-bidi': 'error', + '@masknet/unicode-no-invisible': 'error', + '@masknet/unicode-specific-set': 'off', + 'no-script-url': 'error', // javascript: + 'unicorn/no-unsafe-dom-html': 'error', + // '@masknet/string-no-data-url': 'error', // data:... + // 'unicorn/require-post-message-target-origin': 'warn', // postMessage(data, 'origin') + + // Confusing code + 'no-bitwise': 'error', // need mark out + 'no-div-regex': 'error', // RegEx + 'no-label-var': 'warn', // name collision + 'no-plusplus': 'warn', // ++i? i++? + 'no-sequences': 'warn', // (a, b) + + // Problematic language features + /// API with trap + radix: 'warn', // parseInt('1', _required_) + // This rule breaks BigNumber class which has different .toFixed() default value. + // 'unicorn/require-number-to-fixed-digits-argument': 'warn', // Number#toFixed(_required_) + '@typescript-eslint/require-array-sort-compare': 'error', // Array#sort(_required_) + /// Footgun language features + '@typescript-eslint/prefer-enum-initializers': 'warn', // add a new item in the middle is an API breaking change. + 'no-new-wrappers': 'error', // wrapper objects are bad + /// Little-known language features + 'no-constructor-return': 'error', // constructor() { return expr } + + // Prevent bugs + '@eslint-react/no-leaked-conditional-rendering': 'error', //
{0 && }
will render "0"! + '@masknet/string-no-locale-case': 'error', // in non-i18n cases use locale-aware string methods are wrong + '@typescript-eslint/no-loop-func': 'warn', // capture a loop variable might be a bug + 'default-case-last': 'error', // default: should be the last + 'no-duplicate-case': 'error', // switch + 'no-self-compare': 'error', // a === a + 'no-template-curly-in-string': 'error', // "${expr}" looks like a bug + 'no-unmodified-loop-condition': 'error', // loop bug + 'no-unreachable-loop': 'error', // loop bug + // 'array-callback-return': 'error', // .map .some ... calls should have a return value + eqeqeq: 'error', // === + 'no-restricted-globals': [ + 'error', + // source of bug (those names are too common) + 'error', + 'event', + 'name', + 'length', + 'closed', + // no localStorage & sessionStorage in a web extension + { + name: 'localStorage', + message: + "If you're in the background script, localStorage is banned. It will cause Manifest V3 to crash. If you're in the chrome-extension:// pages, localStorage is discouraged. If you're in the content scripts, we can only use localStorage to read websites' data and MUST NOT store our own data.", + }, + { + name: 'sessionStorage', + message: + "If you're in the background script, sessionStorage is banned. It will cause Manifest V3 to crash. If you're in the chrome-extension:// pages, sessionStorage is discouraged. If you're in the content scripts, we can only use sessionStorage to read websites' data and MUST NOT store our own data.", + }, + ], + // 'require-atomic-updates': 'error', // await/yield race condition +} + +/** @type {Partial} */ +const codeStyleRules = { + // Deprecated + 'no-alert': 'warn', // alert() + 'no-proto': 'error', // __proto__ accessor + + // Useless code + '@masknet/string-no-simple-template-literal': 'warn', // prefer simple string + '@masknet/string-no-unneeded-to-string': 'warn', // useless .toString() + '@typescript-eslint/no-unnecessary-qualifier': 'warn', // no extra qualifier in enum/namespace + 'no-unused-private-class-members': 'off', + '@typescript-eslint/no-unused-private-class-members': 'warn', + 'no-extra-bind': 'warn', // unused bind on a function that does not uses this + 'no-extra-label': 'warn', // break/continue is ok without label + 'no-unneeded-ternary': 'warn', // expr ? true : false + 'no-useless-call': 'warn', // expr.call(undefined, ...) + 'no-useless-concat': 'warn', // "a" + "b" + // '@eslint-react/no-unused-props': 'warn', + // '@masknet/array-no-unneeded-flat-map': 'warn', // bans Array#flatMap((x) => x) + '@typescript-eslint/no-useless-empty-export': 'warn', // export {} + // 'no-lone-blocks': 'warn', // no block that not introducing a new scope + // Note: this rule seems like does not have the correct type checking behavior. before typescript-eslint has project reference support, don't use it. + + // Prefer modern things + 'prefer-object-has-own': 'warn', + 'unicorn/prefer-dispose': 'warn', + 'unicorn/prefer-error-is-error': 'warn', + 'unicorn/prefer-import-meta-properties': 'warn', + 'unicorn/prefer-uint8array-base64': 'warn', + 'prefer-exponentiation-operator': 'warn', // ** + // 'unicorn/prefer-iterator-concat': 'warn', // no TypeScript .d yet + // 'prefer-named-capture-group': 'warn', // RegEx + // 'prefer-object-spread': 'warn', // { ... } than Object.assign + + // Prefer modern things (web) + '@masknet/browser-prefer-location-assign': 'warn', + '@masknet/jsx-no-class-component': 'error', + '@masknet/prefer-fetch': 'error', + // '@masknet/array-prefer-from': 'warn', + // '@masknet/no-unsafe-date': 'error', // use date-fns or Temporal instead + // '@masknet/type-no-number-constructor': 'warn', + + // Better debug + 'symbol-description': 'warn', // Symbol(desc) + 'unicorn/catch-error-name': ['warn', { ignore: ['^err$'] }], // catch (err) + '@eslint-react/no-missing-context-display-name': 'warn', + + // More readable code + // '@typescript-eslint/consistent-indexed-object-style': ['warn', 'index-signature'], // index signature includes key's name, e.g. { [what_it_should_be: string]: T } than Record + '@typescript-eslint/consistent-indexed-object-style': 'off', // index signature includes key's name, e.g. { [what_it_should_be: string]: T } than Record + 'object-shorthand': 'warn', + 'prefer-numeric-literals': 'warn', // 0b111110111 === 503 + 'prefer-regex-literals': 'warn', // RegEx + 'spaced-comment': ['warn', 'always', { line: { markers: ['/'] } }], + // 'no-else-return': 'warn', + // 'unicorn/comment-content': 'warn', // comment content should use corret spelling + + // Consistency + '@masknet/prefer-early-return': 'warn', + '@typescript-eslint/array-type': ['warn', { default: 'array-simple' }], // prefer T[] than Array + '@typescript-eslint/consistent-type-assertions': [ + 'warn', + { assertionStyle: 'as' /* objectLiteralTypeAssertions: 'never' */ }, + ], // prefer a as T than a, and bans it on object literal + 'unicorn/relative-url-style': ['warn', 'always'], // prefer relative url starts with ./ + // '@masknet/jsx-no-template-literal': 'warn', + // '@masknet/no-redundant-variable': 'warn', + // '@masknet/no-single-return': 'warn', + yoda: 'warn', + + // Naming convention + // 'func-name-matching': 'warn', + // 'new-cap': 'warn', + + // Bad practice + '@typescript-eslint/default-param-last': 'warn', // (a, b = 1, c) + 'no-multi-assign': 'warn', // a = b = c + 'no-promise-executor-return': 'error', // new Promise(() => result) + 'no-return-assign': 'warn', // return x = expr + // 'no-param-reassign': 'warn', + /// Async functions / Promise bad practice + + // No unused + 'tss-unused-classes/unused-classes': 'warn', + // 'unicorn/no-unused-properties': 'warn', +} + +/** @type {Partial} */ +const moduleSystemRules = { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { name: 'uuid', message: 'Use crypto.randomUUID() instead.' }, + { name: '@sentry/browser', message: 'Use Sentry.* global object instead.', allowTypeImports: true }, + { name: 'async-call-rpc', message: 'Please use async-call-rpc/full instead.', allowTypeImports: true }, + { name: '@masknet/typed-message/base', message: 'Please use @masknet/typed-message instead.' }, + { + name: '@dimensiondev/holoflows-kit/es', + message: 'Please use @dimensiondev/holoflows-kit instead.', + }, + { + name: 'lodash-es', + message: 'Avoid using type unsafe methods.', + importNames: ['get'], + }, + { + name: 'viem', + message: + 'Use toHex from @masknet/shared-base. They have different behaviors on "0x-" strings. If you want to use the original toHex, import it like import { toHex as viem_toHex } from "viem".', + importNames: ['toHex'], + }, + { + name: 'react-use', + importNames: ['useLocalStorage'], + message: + "If you're in the chrome-extension:// pages, localStorage is discouraged. If you're in the content scripts, we can only use localStorage to read websites' data and MUST NOT store our own data.", + }, + { + name: '@masknet/kit', + importNames: ['formatFileSize'], + message: 'Please use formatFileSize in @masknet/shared instead.', + }, + ], + }, + ], + 'import/no-restricted-paths': [ + 'error', + { + zones: [ + { + target: './packages/mask/background/**', + from: './packages/mask/shared-ui/', + message: 'Background cannot import Ui specific code.', + }, + { + target: './packages/mask/shared/**', + from: './packages/mask/shared-ui/', + message: 'packages/mask/shared cannot import services. Move it to packages/mask/shared-ui instead.', + }, + { + 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. + { + target: './packages/!(plugins|plugin-infra|shared)/**', + from: [ + './packages/plugin-infra/src/dom/context.ts', + './packages/plugin-infra/src/site-adaptor/context.ts', + ], + message: 'Only plugins can import plugin context.', + }, + ], + }, + ], + + // Style + 'import/no-named-default': 'warn', // bans import { default as T } + 'import/no-useless-path-segments': 'warn', + 'import/no-webpack-loader-syntax': 'error', + // 'import/no-anonymous-default-export': 'error', + 'import/no-duplicates': 'warn', // duplicate specifiers + 'import/no-empty-named-blocks': 'warn', // bans import T, {} + 'unused-imports/no-unused-imports': 'warn', + '@typescript-eslint/consistent-type-exports': ['warn', { fixMixedExportsWithInlineTypeSpecifier: true }], + '@typescript-eslint/consistent-type-imports': [ + 'warn', + { + prefer: 'type-imports', + disallowTypeAnnotations: false, + fixStyle: 'inline-type-imports', + }, + ], + 'no-useless-rename': 'error', + + // Avoid mistake + // 'import/first': 'warn', // ES import always runs first even if you inserted some statements inside. + // TypeError: context.getDeclaredVariables is not a function + 'import/no-absolute-path': 'error', + // 'import/no-cycle': 'warn', + // 'import/no-extraneous-dependencies': 'error', // import from devDependencies might be a mistake + // 'import/no-nodejs-modules': 'error', + // 'import/no-relative-packages': 'error', // bans import '../../another-package', should import the workspace package instead + 'import/no-self-import': 'error', + // 'import/no-unassigned-import': 'error', // bans `import 'x'`. side-effect only imports should be explicitly marked. + '@typescript-eslint/no-import-type-side-effects': 'warn', + + // performance + '@masknet/prefer-defer-import': [ + 'warn', + { + deferPackages, + }, + ], +} + +/** @type {any} */ +const plugins = { + js: eslint, + 'tss-unused-classes': UnusedClassesPlugin, + import: ImportPlugin, + unicorn: unicorn, + '@typescript-eslint': tseslint.plugin, + '@masknet': MasknetPlugin, + 'unused-imports': UnusedImportsPlugin, + 'react-compiler': ReactCompilerPlugin, + '@tanstack/query': tanstackReactQuery, + lingui: lingui, +} +export default defineConfig( + { + name: 'maskbook/settings', + settings: { + react: { version: '18.3' }, + 'import-x/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import-x/resolver-next': [createTypeScriptImportResolver({})], + }, + }, + { + name: 'maskbook/ignores', + ignores: [ + '**/*.d.ts', + '**/public', + '**/build', + '**/dist', + '**/i18n_generated.ts', + '**/languages.ts', + 'packages/contracts', + 'packages/mask/.webpack', + ], + }, + { + name: 'maskbook/typescript', + files: ['packages/**/*.ts', 'packages/**/*.tsx'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: 'latest', + projectService: true, + tsconfigRootDir: import.meta.dirname, + warnOnUnsupportedTypeScriptVersion: false, + allowAutomaticSingleRunInference: true, + }, + }, + plugins, + linterOptions: { + reportUnusedDisableDirectives: true, + }, + extends: [ + eslint.configs.recommended, + unicorn.configs.recommended, + lingui.configs['flat/recommended'], + ...tanstackReactQuery.configs['flat/recommended-strict'], + tseslint.configs.strictTypeChecked, + tseslint.configs.stylisticTypeChecked, + eslintReact.configs['strict-type-checked'], + eslintReact.configs['disable-rsc'], + ], + rules: { + ...disabledRules, + ...avoidMistakeRules, + ...codeStyleRules, + ...moduleSystemRules, + }, + }, + { + name: 'maskbook/background', + files: ['packages/mask/background/**/*.ts'], + plugins, + rules: { + 'no-restricted-globals': ['error', 'setTimeout', 'setInterval'], + }, + }, + { + name: 'maskbook/tests', + files: ['packages/**/tests/**/*.ts'], + rules: { + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/template-indent': 'off', + }, + }, + { + name: 'scripts', + files: ['packages/**/scripts/**/*.ts'], + rules: { + 'unicorn/no-process-exit': 'off', + }, + }, + { + name: 'maskbook/shared', + files: ['packages/mask/shared/**/*.ts', 'packages/mask/shared/**/*.tsx'], + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { + name: '#services', + message: + 'packages/mask/shared cannot import services. Move it to packages/mask/shared-ui instead.', + }, + ], + }, + ], + }, + }, + { + name: 'maskbook/non-ui-lingui', + files: ['packages/**/*.ts', 'packages/**/*.tsx'], + ignores: [ + 'packages/shared/**/*', + 'packages/shared-base-ui/**/*', + 'packages/mask/content-script/**/*', + 'packages/mask/dashboard/**/*', + 'packages/mask/popups/**/*', + 'packages/mask/shared/**/*', + 'packages/mask/shared-ui/**/*', + 'packages/mask/swap/**/*', + 'packages/theme/**/*', + 'packages/plugins/**/*', + ], + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { name: '@lingui/react', message: 'Non-UI packages must not reference @lingui/react.' }, + { name: '@lingui/marco', message: 'Non-UI packages must not reference @lingui/marco.' }, + ], + }, + ], + }, + }, +) diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000000..f6f22de9a9c5 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1 @@ +export * from './packages/scripts/src/index.ts' diff --git a/gulpfile.ts b/gulpfile.ts deleted file mode 100644 index 8193975aa970..000000000000 --- a/gulpfile.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './packages/scripts/src' diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index f3c63bc5235d..000000000000 --- a/jest.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * For a detailed explanation regarding each configuration property and type check, visit: - * https://jestjs.io/docs/configuration - */ -import type { InitialOptionsTsJest } from 'ts-jest/dist/types' -import { defaultsESM as tsjPreset } from 'ts-jest/presets' - -Error.stackTraceLimit = Infinity -const config: InitialOptionsTsJest = { - transform: { - ...tsjPreset.transform, - }, - globals: { - 'ts-jest': { - useESM: true, - isolatedModules: true, - }, - }, - cacheDirectory: './node_modules/.cache/jest/', - clearMocks: true, - coverageProvider: 'v8', - testMatch: ['**/tests/**/*.[jt]s?(x)'], - modulePathIgnorePatterns: ['dist'], - extensionsToTreatAsEsm: ['.ts', '.tsx'], - moduleNameMapper: { - '@masknet/shared-base': '/packages/shared-base/src/index.ts', - '@masknet/public-api': '/packages/public-api/src/index.ts', - 'jest-websocket-mock': '/packages/web3-shared/base/node_modules/jest-websocket-mock', - 'reconnecting-websocket': '/packages/web3-shared/base/node_modules/reconnecting-websocket', - 'date-fns/(.*)': '/packages/web3-shared/base/node_modules/date-fns/$1', - }, - snapshotSerializers: ['@masknet/serializer'], -} - -export default config diff --git a/knip.ts b/knip.ts new file mode 100644 index 000000000000..c3859190676f --- /dev/null +++ b/knip.ts @@ -0,0 +1,47 @@ +// cspell:ignore knip +import type { KnipConfig } from 'knip' + +// https://github.com/webpro/knip +const config: KnipConfig = { + ignore: ['**/*.d.ts', '**/locales/index.ts'], + vite: false, + webpack: false, + workspaces: { + '.': { + entry: ['*.js', '*.cjs'], + ignoreDependencies: ['@typescript/lib-dom', 'ses', 'eslint-import-resolver-typescript', 'vite'], + }, + 'packages/mask': { + ignore: ['public'], + entry: [ + '.webpack/webpack.config.ts', + 'background/initialization/mv3-entry.ts', + 'dashboard/initialization/index.ts', + 'popups/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'], + }, + 'packages/injected-script': { + 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'], + ignoreDependencies: ['buffer', 'https-browserify', 'punycode'], +} + +export default config diff --git a/lingui.config.ts b/lingui.config.ts new file mode 100644 index 000000000000..43f58bccfaea --- /dev/null +++ b/lingui.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from '@lingui/cli' +import { formatter } from '@lingui/format-po' + +export default defineConfig({ + compileNamespace: 'json', + format: formatter({ + origins: true, + lineNumbers: false, + }), + locales: ['en-US', 'ja-JP', 'ko-KR', 'zh-CN', 'zh-TW'], + fallbackLocales: { + 'zh-CN': 'zh-TW', + 'zh-TW': 'zh-CN', + default: 'en-US', + }, + catalogs: [ + { + path: './packages/shared-base-ui/src/locale/{locale}', + include: [''], + exclude: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/build/**'], + }, + ], +}) diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 000000000000..dc18d0fa10e3 --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,6 @@ +export default { + '*': 'prettier --write --ignore-unknown', + 'packages/**/*.{ts,tsx,js,jsx}': 'eslint --fix', + 'cspell.json': () => 'gulp reorder-spellcheck', + 'packages/**/*.svg': 'pnpm svgo', +} diff --git a/package.json b/package.json index 2c521581f49c..e73af76ca3a8 100644 --- a/package.json +++ b/package.json @@ -1,113 +1,98 @@ { "name": "mask-network", - "packageManager": "pnpm@7.0.0", + "type": "module", + "packageManager": "pnpm@11.0.8", "engines": { - "node": ">=17.0.0", - "pnpm": ">=7.0.0", + "node": ">=24", "yarn": ">=999.0.0", "npm": ">=999.0.0" }, - "version": "2.7.0", + "version": "2.35.0", "private": true, "license": "AGPL-3.0-or-later", "scripts": { "start": "dev", - "start:mv3": "dev --mv3", - "start:fx": "dev --preset firefox", + "start-rspack": "dev-rspack", + "start:fx": "dev --manifest firefox-mv3", "codegen": "gulp codegen-watch", "build": "build", - "build-ios": "build --preset iOS", - "lint": "eslint -c packages/.eslintrc.json packages --ext .ts,.tsx,.js --cache --fix", - "lint:ci": "eslint -c packages/.eslintrc.json packages --ext .ts,.tsx,.js --cache", + "build-rspack": "build-rspack", + "build:mv3": "build --manifest 3", + "lint": "eslint packages --cache --fix", + "lint:ci": "eslint packages --cache --cache-strategy content --fix", "lint:ci-report": "pnpm run lint:ci --format=junit --output-file=reports/junit/eslint-results.xml --max-warnings=0", - "postinstall": "patch-package && gulp fix-lockfile", - "prepare": "husky install", - "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", - "test:update": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js -u", - "spellcheck": "cspell lint --no-must-find-files" + "prepare": "husky", + "svgo": "svgo packages/icons/*/*.svg", + "test": "vitest", + "spellcheck": "cspell lint --no-must-find-files", + "clean": "gulp clean", + "lingui:compile": "lingui compile", + "lingui:extract": "lingui extract", + "lingui": "pnpm run lingui:extract && pnpm run lingui:compile" }, "dependencies": { - "@dimensiondev/kit": "0.0.0-20220228054820-f2378be", - "@emotion/cache": "^11.7.1", - "@emotion/react": "^11.9.0", - "@emotion/serialize": "^1.0.3", - "@emotion/styled": "^11.8.1", - "@emotion/utils": "^1.1.0", - "@mui/icons-material": "5.6.2", - "@mui/lab": "5.0.0-alpha.80", - "@mui/material": "5.6.4", - "@mui/system": "5.6.4", - "@solana/web3.js": "1.39.1", - "@types/masknet__global-types": "workspace:*", - "@types/react": "18.0.8", - "@types/react-dom": "^18.0.3", - "@types/web": "^0.0.64", - "i18next": "^21.7.1", - "immer": "9.0.12", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "lodash-unified": "1.0.2", - "react": "18.1.0", - "react-dom": "18.1.0", - "react-i18next": "^11.16.9", - "ses": "0.15.15", - "ts-results": "3.3.0", - "web3-core": "1.7.3", - "web3-core-method": "1.7.3" + "@dimensiondev/holoflows-kit": "0.9.0-20240322092738-f9180f3", + "@emotion/cache": "11.11.0", + "@emotion/react": "11.11.4", + "@emotion/serialize": "1.1.4", + "@emotion/styled": "11.11.5", + "@lingui/core": "^6.5.0", + "@lingui/react": "^6.5.0", + "@masknet/kit": "0.4.1", + "@mui/icons-material": "5.15.21", + "@mui/lab": "5.0.0-alpha.170", + "@mui/material": "5.15.20", + "@mui/system": "5.15.20", + "@tanstack/react-query": "^5.49.2", + "@types/masknet__global-types": "workspace:^", + "@types/react": "^19.2.10", + "@types/react-dom": "^19.2.3", + "@typescript/lib-dom": "npm:@types/web@^0.0.323", + "buffer": "^6.0.3", + "lodash-es": "^4.17.23", + "react": "0.0.0-experimental-58af67a8f8-20240628", + "react-dom": "0.0.0-experimental-58af67a8f8-20240628", + "ses": "1.14.0", + "ts-results-es": "^4.2.0" }, "devDependencies": { - "@commitlint/cli": "^16.2.4", - "@commitlint/config-conventional": "^16.2.4", - "@dimensiondev/eslint-plugin": "^0.0.2-20220414093950-7d54c58", - "@dimensiondev/patch-package": "^6.5.0", - "@jest/globals": "^28.1.0", - "@magic-works/i18n-codegen": "^0.1.0", - "@masknet/cli": "workspace:*", - "@masknet/serializer": "workspace:*", - "@nice-labs/git-rev": "^3.5.0", - "@swc/core": "^1.2.177", - "@types/lodash-es": "^4.17.6", - "@typescript-eslint/eslint-plugin": "^5.22.0", - "@typescript-eslint/experimental-utils": "^5.22.0", - "@typescript-eslint/parser": "^5.22.0", - "cspell": "^5.20.0", - "eslint": "8.15.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-react": "^7.29.4", - "eslint-plugin-react-hooks": "^4.5.0", - "eslint-plugin-unicorn": "^42.0.0", - "eslint-plugin-unused-imports": "^2.0.0", - "gulp": "^4.0.2", - "husky": "^7.0.4", - "jest": "^28.1.0", - "lint-staged": "^12.4.1", - "prettier": "^2.6.2", - "ts-jest": "^28.0.1", - "ts-node": "^10.7.0", - "typescript": "4.7.0-beta" - }, - "pnpm": { - "overrides": { - "@types/node": "^17.0.31", - "@types/react": "18.0.9", - "@solana/web3.js": "1.39.1" - }, - "onlyBuiltDependencies": [], - "peerDependencyRules": { - "ignoreMissing": [ - "react", - "react-dom", - "@types/node", - "@babel/core", - "webextension-polyfill", - "prop-types", - "@mui/material", - "typescript" - ], - "allowedVersions": { - "react": "18", - "react-dom": "18" - } - } + "@changesets/cli": "^2.31.0", + "@commitlint/cli": "^19.7.1", + "@commitlint/config-conventional": "^19.7.1", + "@eslint-react/eslint-plugin": "^5.14.1", + "@eslint/js": "^10.0.1", + "@lingui/cli": "^6.5.0", + "@lingui/format-po": "^6.5.0", + "@lingui/swc-plugin": "^6.5.1", + "@masknet/cli": "workspace:^", + "@masknet/config": "workspace:^", + "@masknet/eslint-plugin": "^0.4.1", + "@masknet/typescript-plugin": "workspace:^", + "@nice-labs/git-rev": "^3.5.1", + "@swc/core": "1.15.43", + "@tanstack/eslint-plugin-query": "^5.101.2", + "@types/lodash-es": "^4.17.12", + "@typescript/native-preview": "7.0.0-dev.20260128.1", + "@vitest/ui": "^4.1.10", + "cspell": "^8.17.5", + "eslint": "10.7.0", + "eslint-formatter-junit": "^9.0.1", + "eslint-import-resolver-typescript": "^4.4.5", + "eslint-plugin-import-x": "^4.17.1", + "eslint-plugin-lingui": "^0.14.0", + "eslint-plugin-react-compiler": "19.1.0-rc.2", + "eslint-plugin-tss-unused-classes": "^1.0.4", + "eslint-plugin-unicorn": "^71.1.0", + "eslint-plugin-unused-imports": "^4.4.1", + "gulp": "^5.0.0", + "husky": "^9.1.7", + "knip": "^5.45.0", + "lint-staged": "^15.4.3", + "prettier": "^3.6.2", + "svgo": "^3.3.2", + "typescript": "5.9.2", + "typescript-eslint": "^8.63.0", + "vite": "^6.2.0", + "vitest": "^4.1.10" } } diff --git a/packages/.eslintrc.json b/packages/.eslintrc.json deleted file mode 100644 index 329c14763de5..000000000000 --- a/packages/.eslintrc.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 7, - "sourceType": "module", - "project": "./tsconfig.eslint.json", - "warnOnUnsupportedTypeScriptVersion": false - }, - "extends": ["../.eslintrc.json", "plugin:import/typescript"], - "plugins": ["@typescript-eslint", "@dimensiondev", "unused-imports", "import", "unicorn", "react", "react-hooks"], - "rules": { - "no-restricted-imports": [ - "error", - { - "paths": [ - { "name": "lodash", "message": "Please use lodash-unified instead." }, - { "name": "lodash-es", "message": "Please use lodash-unified instead." }, - { "name": "date-fns", "message": "Please use date-fns/{submodule} instead." }, - { "name": "date-fns/esm", "message": "Please use date-fns/{submodule} instead." }, - { "name": "idb", "message": "Please use idb/with-async-ittr instead." }, - { "name": "idb/with-async-ittr-cjs", "message": "Please use idb/with-async-ittr instead." }, - { "name": "@masknet/typed-message/base", "message": "Please use @masknet/typed-message instead." }, - { - "name": "@dimensiondev/holoflows-kit/es", - "message": "Please use @dimensiondev/holoflows-kit instead." - } - ] - } - ], - "yoda": "error", - "radix": "error", - "eqeqeq": ["error", "always"], - "spaced-comment": ["error", "always", { "line": { "markers": ["/"] } }], - "no-cond-assign": "error", - "no-constant-condition": "error", - "no-extra-boolean-cast": "error", - "no-script-url": "error", - "no-throw-literal": "error", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-useless-concat": "error", - "no-loss-of-precision": "error", - "prefer-regex-literals": "error", - "react/jsx-boolean-value": "error", - "react/jsx-curly-brace-presence": ["warn", { "props": "never", "children": "never" }], - "react/jsx-key": "error", - "react/no-invalid-html-attribute": "error", - "react/no-unknown-property": "error", - "react/self-closing-comp": ["warn", { "component": true, "html": true }], - "react-hooks/exhaustive-deps": "off", - "react-hooks/rules-of-hooks": "error", - "import/no-deprecated": "off", - "import/no-duplicates": "error", - "unicorn/better-regex": "error", - "unicorn/catch-error-name": ["error", { "ignore": ["^err$"] }], - "unicorn/no-instanceof-array": "error", - "unicorn/no-new-array": "error", - "unicorn/no-new-buffer": "error", - "unicorn/no-thenable": "error", - "unicorn/no-useless-promise-resolve-reject": "error", - "unicorn/prefer-add-event-listener": "error", - "unicorn/prefer-date-now": "error", - "unicorn/prefer-dom-node-dataset": "error", - "unicorn/prefer-number-properties": "error", - "unicorn/relative-url-style": ["error", "always"], - "unicorn/throw-new-error": "error", - "unicorn/prefer-string-slice": "error", - "@dimensiondev/array/no-implicit-sort": "error", - "@dimensiondev/browser/prefer-location-assign": "error", - "@dimensiondev/jsx/no-class-component": "error", - "@dimensiondev/jsx/no-template-literal": "error", - "@dimensiondev/no-number-constructor": "off", - "@dimensiondev/prefer-early-return": "error", - "@dimensiondev/string/no-interpolation": "off", - "@dimensiondev/string/no-locale-case": "error", - "@dimensiondev/string/no-simple-template-literal": "error", - "@dimensiondev/string/no-unneeded-to-string": "error", - "@dimensiondev/type/no-instanceof-wrapper": "error", - "@dimensiondev/type/no-wrapper-type-reference": "error", - "@dimensiondev/unicode/specific-set": "error", - "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/no-base-to-string": "off", - "@typescript-eslint/no-for-in-array": "error", - "@typescript-eslint/no-implied-eval": "error", - "@typescript-eslint/no-inferrable-types": "error", - "@typescript-eslint/no-invalid-this": "error", - "@typescript-eslint/no-loop-func": "error", - "@typescript-eslint/no-this-alias": "error", - "@typescript-eslint/prefer-enum-initializers": "error", - "@typescript-eslint/prefer-for-of": "error", - "@typescript-eslint/prefer-includes": "error", - "@typescript-eslint/prefer-literal-enum-member": "error", - "@typescript-eslint/prefer-nullish-coalescing": "error", - "@typescript-eslint/prefer-optional-chain": "error", - "@typescript-eslint/prefer-reduce-type-parameter": "error", - "@typescript-eslint/prefer-regexp-exec": "off", - "@typescript-eslint/prefer-string-starts-ends-with": "error", - "@typescript-eslint/restrict-plus-operands": "off", - "@typescript-eslint/restrict-template-expressions": "off" - }, - "overrides": [ - { - "files": ["./plugins/**/*"], - "rules": { - "@typescript-eslint/no-explicit-any": "error", - "no-nested-ternary": "error" - } - } - ] -} diff --git a/packages/README.md b/packages/README.md index a1370dcac0ad..5fb8605b510e 100644 --- a/packages/README.md +++ b/packages/README.md @@ -26,14 +26,6 @@ This package provides two command to wrap the other commands. -- [netlify](netlify) - - Tools of building netlify previews - -- [storybook-shared](storybook-shared) - - Same, but for storybook - ## Resources - [contracts](contracts) diff --git a/packages/backup-format/package.json b/packages/backup-format/package.json index 5c97085569ec..d93a6c368d9e 100644 --- a/packages/backup-format/package.json +++ b/packages/backup-format/package.json @@ -1,21 +1,23 @@ { "name": "@masknet/backup-format", "private": true, + "sideEffects": false, + "type": "module", "exports": { ".": { - "webpack": "./src/index.ts", + "types": "./dist/index.d.ts", + "mask-src": "./src/index.ts", "default": "./dist/index.js" } }, "types": "./dist/index.d.ts", - "type": "module", "dependencies": { - "@masknet/shared-base": "workspace:*", - "@msgpack/msgpack": "^2.7.2", - "elliptic": "^6.5.3", - "pvtsutils": "^1.3.2" + "@masknet/shared-base": "workspace:^", + "@msgpack/msgpack": "^3.0.0-beta2", + "elliptic": "6.6.1", + "pvtsutils": "^1.3.5" }, "devDependencies": { - "@types/elliptic": "^6.4.14" + "@types/elliptic": "^6.4.18" } } diff --git a/packages/backup-format/src/container/index.ts b/packages/backup-format/src/container/index.ts index 2a148185e4ae..ed8a47036a7e 100644 --- a/packages/backup-format/src/container/index.ts +++ b/packages/backup-format/src/container/index.ts @@ -1,5 +1,5 @@ -import { unreachable, concatArrayBuffer } from '@dimensiondev/kit' -import { BackupErrors } from '../BackupErrors' +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 @@ -14,13 +14,16 @@ function getMagicHeader(version: SupportedVersions) { } /** @internal */ -export async function createContainer(version: SupportedVersions, data: ArrayBuffer) { +export async function createContainer(version: SupportedVersions, data: ArrayBuffer | Uint8Array) { 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) { +export async function parseEncryptedJSONContainer( + version: SupportedVersions, + _container: ArrayBuffer | ArrayLike, +) { const container = new Uint8Array(_container) for (const [index, value] of getMagicHeader(version).entries()) { diff --git a/packages/backup-format/src/index.ts b/packages/backup-format/src/index.ts index 65876c2f581d..9e4909af9ca0 100644 --- a/packages/backup-format/src/index.ts +++ b/packages/backup-format/src/index.ts @@ -1,4 +1,9 @@ -export { encryptBackup, decryptBackup } from './version-3' -export { BackupErrors } from './BackupErrors' -export { normalizeBackup, type NormalizedBackup, createEmptyNormalizedBackup, generateBackupRAW } from './normalize' -export { getBackupPreviewInfo, type BackupPreview } from './utils/backupPreview' +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 index 677e4ced8822..2ce4a60b5850 100644 --- a/packages/backup-format/src/normalize/index.ts +++ b/packages/backup-format/src/normalize/index.ts @@ -1,24 +1,24 @@ -import { None } from 'ts-results' -import { BackupErrors } from '../BackupErrors' -import { isBackupVersion0, normalizeBackupVersion0 } from '../version-0' -import { isBackupVersion1, normalizeBackupVersion1 } from '../version-1' -import { generateBackupVersion2, isBackupVersion2, normalizeBackupVersion2 } from '../version-2' -import type { NormalizedBackup } from './type' +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' -function __normalizeBackup(data: unknown): NormalizedBackup.Data { +export type * 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 function normalizeBackup(data: unknown): NormalizedBackup.Data { - const normalized = __normalizeBackup(data) +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), + /^(http|)/u.test(url), ) return normalized } diff --git a/packages/backup-format/src/normalize/type.ts b/packages/backup-format/src/normalize/type.ts index 76752069a046..55c4f7c4a450 100644 --- a/packages/backup-format/src/normalize/type.ts +++ b/packages/backup-format/src/normalize/type.ts @@ -6,8 +6,9 @@ import type { ProfileIdentifier, RelationFavor, PostIVIdentifier, + ECKeyIdentifier, } from '@masknet/shared-base' -import type { Option } from 'ts-results' +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 { @@ -20,7 +21,7 @@ export namespace NormalizedBackup { posts: Map wallets: WalletBackup[] settings: SettingsBackup - plugins: Record + plugins: { [pluginId: string]: unknown } } export interface Meta { /** Backup file version */ @@ -39,6 +40,7 @@ export namespace NormalizedBackup { nickname: Option createdAt: Option updatedAt: Option + address: Option } export interface Mnemonic { words: string @@ -54,13 +56,13 @@ export namespace NormalizedBackup { updatedAt: Option } export interface RelationBackup { - profile: ProfileIdentifier + profile: ProfileIdentifier | ECKeyIdentifier persona: PersonaIdentifier favor: RelationFavor } export interface PostBackup { identifier: PostIVIdentifier - postBy?: ProfileIdentifier + postBy: Option postCryptoKey: Option recipients: Option foundAt: Date @@ -85,6 +87,8 @@ export namespace NormalizedBackup { export interface WalletBackup { address: string name: string + mnemonicId: Option + derivationPath: Option passphrase: Option publicKey: Option privateKey: Option diff --git a/packages/backup-format/src/utils/backupPreview.ts b/packages/backup-format/src/utils/backupPreview.ts index eb1be97c3095..f0b71827e494 100644 --- a/packages/backup-format/src/utils/backupPreview.ts +++ b/packages/backup-format/src/utils/backupPreview.ts @@ -1,31 +1,56 @@ import type { NormalizedBackup } from '@masknet/backup-format' +import { compact, flatten, sumBy } from 'lodash-es' -export interface BackupPreview { - personas: number +export interface BackupSummary { + personas: string[] accounts: number posts: number contacts: number relations: number files: number - wallets: number + wallets: string[] createdAt: number + countOfWallets: number } -export function getBackupPreviewInfo(json: NormalizedBackup.Data): BackupPreview { +export function getBackupSummary(json: NormalizedBackup.Data): BackupSummary { let files = 0 try { - files = Number((json.plugins?.['com.maskbook.fileservice'] as any)?.length || 0) - } catch {} + files = Number((json.plugins['com.maskbook.fileservice'] as any)?.length || 0) + } catch { + // ignore + } + + const ownerPersonas = json.personas + .values() + .filter((persona) => !persona.privateKey.isNone()) + .toArray() + const ownerProfiles = new Set( + flatten(ownerPersonas.map((persona) => persona.linkedProfiles.keys().toArray())).map((item) => item.toText()), + ) + const personas = compact( + ownerPersonas + .toSorted((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.has(profile.identifier.toText()) && profile.linkedPersona.isSome() + }) + .toArray() return { - personas: json.personas.size, - accounts: [...json.personas.values()].reduce((a, b) => a + b.linkedProfiles.size, 0), + // Names or publicKeys */ + personas, + accounts: sumBy(ownerPersonas, (persona) => persona.linkedProfiles.size), posts: json.posts.size, - contacts: json.profiles.size, + contacts: contacts.length, relations: json.relations.length, files, - wallets: json.wallets.length, - createdAt: Number(json.meta.createdAt), + 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 index c56fcf6172ba..00a9a35df1a8 100644 --- a/packages/backup-format/src/utils/hex2buffer.ts +++ b/packages/backup-format/src/utils/hex2buffer.ts @@ -1,19 +1,24 @@ +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) - // eslint-disable-next-line no-plusplus - for (let i = 0; i < hexString.length; i++) { - // eslint-disable-next-line no-plusplus - const c = hexString.slice(i, ++i + 1) + 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 + len = + len > 32 ? + len > 48 ? + 66 + : 48 + : 32 if (res.length < len) { res = concat(new Uint8Array(len - res.length), res) } @@ -22,13 +27,12 @@ export function hex2buffer(hexString: string, padded?: boolean) { } /** @internal */ -function concat(...buf: (Uint8Array | number[])[]) { - const res = new Uint8Array(buf.map((item) => item.length).reduce((prev, cur) => prev + cur)) +function concat(...buf: Array) { + const res = new Uint8Array(sum(buf.map((item) => item.length))) let offset = 0 - buf.forEach((item, index) => { - // eslint-disable-next-line no-plusplus - for (let i = 0; i < item.length; i++) { - res[offset + i] = item[i] + buf.forEach((item) => { + for (const [i, element] of item.entries()) { + res[offset + i] = element } offset += item.length }) diff --git a/packages/backup-format/src/version-0/index.ts b/packages/backup-format/src/version-0/index.ts index fb50e2335871..4335acb8e17e 100644 --- a/packages/backup-format/src/version-0/index.ts +++ b/packages/backup-format/src/version-0/index.ts @@ -1,30 +1,28 @@ import { - AESJsonWebKey, - ECKeyIdentifierFromJsonWebKey, - EC_Private_JsonWebKey, - EC_Public_JsonWebKey, + type AESJsonWebKey, + type EC_Private_JsonWebKey, + type EC_Public_JsonWebKey, isAESJsonWebKey, isEC_Private_JsonWebKey, - isEC_Public_JsonWebKey, + isEC_JsonWebKey, ProfileIdentifier, + ECKeyIdentifier, } from '@masknet/shared-base' -import { isObjectLike } from 'lodash-unified' -import { None, Some } from 'ts-results' -import { createEmptyNormalizedBackup } from '../normalize' -import type { NormalizedBackup } from '../normalize/type' +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 || !data.key.key || !data.key.key.privateKey || !data.key.key.publicKey) - return false - return true + return !(!data.local || !data.key?.key?.privateKey || !data.key.key.publicKey) } catch { return false } } -export function normalizeBackupVersion0(file: BackupJSONFileVersion0): NormalizedBackup.Data { +export async function normalizeBackupVersion0(file: BackupJSONFileVersion0): Promise { const backup = createEmptyNormalizedBackup() backup.meta.version = 0 backup.meta.maskVersion = Some('<=1.3.2') @@ -33,10 +31,10 @@ export function normalizeBackupVersion0(file: BackupJSONFileVersion0): Normalize const { username, key } = file.key const { publicKey, privateKey } = key - if (!isEC_Public_JsonWebKey(publicKey)) return backup + if (!isEC_JsonWebKey(publicKey)) return backup const persona: NormalizedBackup.PersonaBackup = { - identifier: ECKeyIdentifierFromJsonWebKey(publicKey), + identifier: (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap(), publicKey, linkedProfiles: new Map(), localKey: isAESJsonWebKey(local) ? Some(local) : None, @@ -45,12 +43,14 @@ export function normalizeBackupVersion0(file: BackupJSONFileVersion0): Normalize nickname: None, createdAt: None, updatedAt: None, + address: None, } backup.personas.set(persona.identifier, persona) - if (username && username !== '$unknown' && username !== '$local') { + const identifier = ProfileIdentifier.of('facebook.com', username) + if (identifier.isSome()) { const profile: NormalizedBackup.ProfileBackup = { - identifier: ProfileIdentifier.of('facebook.com', username).unwrap(), + identifier: identifier.value, linkedPersona: Some(persona.identifier), createdAt: None, updatedAt: None, diff --git a/packages/backup-format/src/version-1/index.ts b/packages/backup-format/src/version-1/index.ts index 09006f754713..4475db020acf 100644 --- a/packages/backup-format/src/version-1/index.ts +++ b/packages/backup-format/src/version-1/index.ts @@ -1,17 +1,17 @@ import { - AESJsonWebKey, - ECKeyIdentifierFromJsonWebKey, - EC_Private_JsonWebKey, - EC_Public_JsonWebKey, + type AESJsonWebKey, + type EC_Private_JsonWebKey, + type EC_Public_JsonWebKey, isEC_Private_JsonWebKey, - isEC_Public_JsonWebKey, + isEC_JsonWebKey, isAESJsonWebKey, ProfileIdentifier, + ECKeyIdentifier, } from '@masknet/shared-base' -import { isObjectLike } from 'lodash-unified' -import { None, Some } from 'ts-results' -import { createEmptyNormalizedBackup } from '../normalize' -import type { NormalizedBackup } from '../normalize/type' +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 @@ -25,7 +25,7 @@ export function isBackupVersion1(obj: unknown): obj is BackupJSONFileVersion1 { return false } } -export function normalizeBackupVersion1(file: BackupJSONFileVersion1): NormalizedBackup.Data { +export async function normalizeBackupVersion1(file: BackupJSONFileVersion1): Promise { const backup = createEmptyNormalizedBackup() backup.meta.version = 1 @@ -38,7 +38,9 @@ export function normalizeBackupVersion1(file: BackupJSONFileVersion1): Normalize const { whoami, people } = file for (const { network, publicKey, userId, nickname, localKey, privateKey } of [...whoami, ...(people || [])]) { - const identifier = ProfileIdentifier.of(network, userId).unwrap() + 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, @@ -48,8 +50,8 @@ export function normalizeBackupVersion1(file: BackupJSONFileVersion1): Normalize linkedPersona: None, } - if (isEC_Public_JsonWebKey(publicKey)) { - const personaID = ECKeyIdentifierFromJsonWebKey(publicKey) + if (isEC_JsonWebKey(publicKey)) { + const personaID = (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap() const persona: NormalizedBackup.PersonaBackup = backup.personas.get(personaID) || { identifier: personaID, nickname: None, @@ -60,6 +62,7 @@ export function normalizeBackupVersion1(file: BackupJSONFileVersion1): Normalize mnemonic: None, createdAt: None, updatedAt: None, + address: None, } profile.linkedPersona = Some(personaID) @@ -71,8 +74,8 @@ export function normalizeBackupVersion1(file: BackupJSONFileVersion1): Normalize } if (isAESJsonWebKey(localKey)) { profile.localKey = Some(localKey) - if (profile.linkedPersona.some && backup.personas.has(profile.linkedPersona.val)) { - backup.personas.get(profile.linkedPersona.val)!.localKey = Some(localKey) + if (profile.linkedPersona.isSome() && backup.personas.has(profile.linkedPersona.value)) { + backup.personas.get(profile.linkedPersona.value)!.localKey = Some(localKey) } } } @@ -89,16 +92,16 @@ interface BackupJSONFileVersion1 { publicKey: EC_Public_JsonWebKey privateKey: EC_Private_JsonWebKey localKey: AESJsonWebKey - previousIdentifiers?: { network: string; userId: string }[] + previousIdentifiers?: Array<{ network: string; userId: string }> nickname?: string }> people?: Array<{ network: string userId: string publicKey: EC_Public_JsonWebKey - previousIdentifiers?: { network: string; userId: string }[] + previousIdentifiers?: Array<{ network: string; userId: string }> nickname?: string - groups?: { network: string; groupID: string; virtualGroupOwner: string | null }[] + 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 diff --git a/packages/backup-format/src/version-2/index.ts b/packages/backup-format/src/version-2/index.ts index ec5400e4ef55..c4a30fdc2324 100644 --- a/packages/backup-format/src/version-2/index.ts +++ b/packages/backup-format/src/version-2/index.ts @@ -1,31 +1,32 @@ -import { decodeArrayBuffer, encodeArrayBuffer, safeUnreachable } from '@dimensiondev/kit' +import { decodeArrayBuffer, encodeArrayBuffer, safeUnreachable } from '@masknet/kit' import { ECKeyIdentifier, - ECKeyIdentifierFromJsonWebKey, isAESJsonWebKey, isEC_Private_JsonWebKey, isEC_Public_JsonWebKey, PostIVIdentifier, ProfileIdentifier, - RelationFavor, + 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' -import { createEmptyNormalizedBackup } from '../normalize' -import type { NormalizedBackup } from '../normalize/type' -import { hex2buffer } from '../utils/hex2buffer' +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 {} + } catch { + // ignore + } return false } -export function normalizeBackupVersion2(item: BackupJSONFileVersion2): NormalizedBackup.Data { +export async function normalizeBackupVersion2(item: BackupJSONFileVersion2): Promise { const backup = createEmptyNormalizedBackup() backup.meta.version = 2 @@ -38,7 +39,7 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize for (const persona of personas) { const { publicKey } = persona if (!isEC_Public_JsonWebKey(publicKey)) continue - const identifier = ECKeyIdentifierFromJsonWebKey(publicKey) + const identifier = (await ECKeyIdentifier.fromJsonWebKey(publicKey)).unwrap() const normalizedPersona: NormalizedBackup.PersonaBackup = { identifier, linkedProfiles: new Map(), @@ -49,11 +50,12 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize 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.none) continue - normalizedPersona.linkedProfiles.set(id.val, null) + if (id.isNone()) continue + normalizedPersona.linkedProfiles.set(id.value, null) } if (persona.mnemonic) { const { words, parameter } = persona.mnemonic @@ -65,22 +67,22 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize for (const profile of profiles) { const identifier = ProfileIdentifier.from(profile.identifier) - if (identifier.none) continue + if (identifier.isNone()) continue const normalizedProfile: NormalizedBackup.ProfileBackup = { - identifier: identifier.val, + 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.val, normalizedProfile) + 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) { + if (backup.profiles.get(profile)?.linkedPersona.unwrapOr(undefined) === persona.identifier) { // do nothing } else toRemove.push(profile) } @@ -89,15 +91,15 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize for (const post of posts) { const identifier = PostIVIdentifier.from(post.identifier) + if (identifier.isNone()) continue + const postBy = ProfileIdentifier.from(post.postBy) const encryptBy = ECKeyIdentifier.from(post.encryptBy) - - if (identifier.none) continue const interestedMeta = new Map() const normalizedPost: NormalizedBackup.PostBackup = { - identifier: identifier.val, + identifier: identifier.value, foundAt: new Date(post.foundAt), - postBy: postBy.unwrapOr(undefined), + postBy, interestedMeta, encryptBy, summary: post.summary ? Some(post.summary) : None, @@ -113,9 +115,9 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize const map = new Map() for (const [recipient, { reason }] of post.recipients) { const id = ProfileIdentifier.from(recipient) - if (id.none) continue + if (id.isNone()) continue const reasons: NormalizedBackup.RecipientReason[] = [] - map.set(id.val, reasons) + 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) }) @@ -126,17 +128,17 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize } if (post.interestedMeta) normalizedPost.interestedMeta = MetaFromJson(post.interestedMeta) - backup.posts.set(identifier.val, normalizedPost) + 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.some && b.some) { + if (a.isSome() && b.isSome()) { backup.relations.push({ - profile: a.val, - persona: b.val, + profile: a.value, + persona: b.value, favor, }) } @@ -144,12 +146,12 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize for (const wallet of wallets || []) { if (wallet.privateKey?.d && !wallet.publicKey) { - // @ts-ignore - const ec = new (__.ec || __.default.ec)('secp256k1') as __.ec + // @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) + const hexY = hexPub.slice(hexPub.length / 2) wallet.privateKey.x = Convert.ToBase64Url(hex2buffer(hexX)) wallet.privateKey.y = Convert.ToBase64Url(hex2buffer(hexY)) } @@ -157,15 +159,18 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize 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, + 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), } @@ -178,10 +183,11 @@ export function normalizeBackupVersion2(item: BackupJSONFileVersion2): Normalize } 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), + createdAt: Number(item.meta.createdAt.unwrapOr(now)), type: 'maskbook-backup', version: 2, }, @@ -197,13 +203,13 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF for (const [id, data] of item.personas) { result.personas.push({ identifier: id.toText(), - createdAt: Number(data.createdAt), - updatedAt: Number(data.updatedAt), + 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, - ]), + linkedProfiles: data.linkedProfiles + .keys() + .map((id) => [id.toText(), { connectionConfirmState: 'confirmed' }] as [string, LinkedProfileDetails]) + .toArray(), publicKey: data.publicKey, privateKey: data.privateKey.unwrapOr(undefined), mnemonic: data.mnemonic @@ -219,8 +225,8 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF for (const [id, data] of item.profiles) { result.profiles.push({ identifier: id.toText(), - createdAt: Number(data.createdAt), - updatedAt: Number(data.updatedAt), + 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), @@ -231,7 +237,7 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF const item: BackupJSONFileVersion2['posts'][0] = { identifier: id.toText(), foundAt: Number(data.foundAt), - postBy: data.postBy?.toText() || 'person:localhost/$unknown', + 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), @@ -241,11 +247,11 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF recipients: [], } result.posts.push(item) - if (data.recipients.some) { - if (data.recipients.val.type === 'public') item.recipients = 'everyone' - else if (data.recipients.val.type === 'e2e') { + 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.val.receivers) { + for (const [recipient, reasons] of data.recipients.value.receivers) { if (!reasons.length) continue item.recipients.push([ recipient.toText(), @@ -259,7 +265,7 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF }, ]) } - } else safeUnreachable(data.recipients.val) + } else safeUnreachable(data.recipients.value) } } @@ -286,6 +292,8 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF .unwrapOr(undefined), createdAt: Number(data.createdAt), updatedAt: Number(data.updatedAt), + derivationPath: data.derivationPath.unwrapOr(undefined), + mnemonicId: data.mnemonicId.unwrapOr(undefined), }) } return result @@ -298,7 +306,7 @@ function MetaFromJson(meta: string | undefined): Map { return new Map(Object.entries(raw)) } function MetaToJson(meta: ReadonlyMap) { - return encodeArrayBuffer(encode(Object.fromEntries(meta.entries()))) + return encodeArrayBuffer(encode(Object.fromEntries(meta))) } /** @@ -322,9 +330,10 @@ interface BackupJSONFileVersion2 { privateKey?: JsonWebKey localKey?: JsonWebKey nickname?: string - linkedProfiles: [/** ProfileIdentifier.toText() */ string, LinkedProfileDetails][] + linkedProfiles: Array<[/** ProfileIdentifier.toText() */ string, LinkedProfileDetails]> createdAt: number // Unix timestamp updatedAt: number // Unix timestamp + address?: string }> profiles: Array<{ identifier: string // ProfileIdentifier.toText() @@ -345,7 +354,7 @@ interface BackupJSONFileVersion2 { postBy: string // ProfileIdentifier.toText() identifier: string // PostIVIdentifier.toText() postCryptoKey?: JsonWebKey - recipients: 'everyone' | [/** ProfileIdentifier.toText() */ string, { reason: RecipientReasonJSON[] }][] + recipients: 'everyone' | Array<[/** ProfileIdentifier.toText() */ string, { reason: RecipientReasonJSON[] }]> /** @deprecated */ recipientGroups: never[] foundAt: number // Unix timestamp @@ -366,9 +375,11 @@ interface BackupJSONFileVersion2 { } createdAt: number // Unix timestamp updatedAt: number // Unix timestamp + mnemonicId?: string + derivationPath?: string }> grantedHostPermissions: string[] - plugin?: Record + plugin?: { [propertyName: string]: unknown } } interface LinkedProfileDetails { diff --git a/packages/backup-format/src/version-3/index.ts b/packages/backup-format/src/version-3/index.ts index d60da9e33072..71026bf83e22 100644 --- a/packages/backup-format/src/version-3/index.ts +++ b/packages/backup-format/src/version-3/index.ts @@ -1,23 +1,24 @@ import { decode, encode } from '@msgpack/msgpack' -import { createContainer, parseEncryptedJSONContainer, SupportedVersions } from '../container' -import { BackupErrors } from '../BackupErrors' +import { createContainer, parseEncryptedJSONContainer, SupportedVersions } from '../container/index.js' +import { BackupErrors } from '../BackupErrors.js' -export async function encryptBackup(password: BufferSource, binaryBackup: Uint8Array) { +export async function encryptBackup(password: Uint8Array, binaryBackup: Uint8Array) { 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]) + const container = encode([pbkdf2IV, AESParam.iv, encrypted]) as Uint8Array return createContainer(SupportedVersions.Version0, container) } -export async function decryptBackup(password: BufferSource, data: ArrayBuffer) { +export async function decryptBackup(password: Uint8Array, data: ArrayBuffer | ArrayLike) { 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 payloadTuple = decode(container) + if (!Array.isArray(payloadTuple) || payloadTuple.length !== 3) throw new TypeError(BackupErrors.UnknownFormat) + if (!payloadTuple.every((x): x is Uint8Array => x instanceof Uint8Array)) + throw new TypeError(BackupErrors.UnknownFormat) + const [pbkdf2IV, encryptIV, encrypted] = payloadTuple as Array> const aes = await getAESFromPassword(password, pbkdf2IV) @@ -26,11 +27,11 @@ export async function decryptBackup(password: BufferSource, data: ArrayBuffer) { return decryptedBackup } -async function createAESFromPassword(password: BufferSource) { +async function createAESFromPassword(password: Uint8Array) { 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' }, + { name: 'PBKDF2', salt: iv, iterations: 10_000, hash: 'SHA-256' }, pbkdf, { name: 'AES-GCM', length: 256 }, true, @@ -39,10 +40,10 @@ async function createAESFromPassword(password: BufferSource) { return [iv, aes] as const } -async function getAESFromPassword(password: BufferSource, iv: Uint8Array) { +async function getAESFromPassword(password: Uint8Array, 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' }, + { name: 'PBKDF2', salt: iv, iterations: 10_000, hash: 'SHA-256' }, pbkdf, { name: 'AES-GCM', length: 256 }, true, diff --git a/packages/backup-format/tests/encryption.ts b/packages/backup-format/tests/encryption.ts index a109b84cb6df..7ae85789a804 100644 --- a/packages/backup-format/tests/encryption.ts +++ b/packages/backup-format/tests/encryption.ts @@ -1,6 +1,8 @@ -import { test, expect, beforeAll } from '@jest/globals' -import { webcrypto } from 'crypto' -import { encryptBackup, decryptBackup } from '../src/index' +import { test, expect, beforeAll } from 'vitest' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore not want to install @types/node for this +import { webcrypto } from 'node:crypto' +import { encryptBackup, decryptBackup } from '../src/index.js' beforeAll(() => { Reflect.set(globalThis, 'crypto', webcrypto) @@ -16,13 +18,13 @@ const testData = new Uint8Array([ ]) test('Old data can be still decrypted', async () => { - const password = Uint8Array.from('password'.split('').map((x) => x.charCodeAt(0))) + const password = Uint8Array.from('password'.split('').map((x) => x.codePointAt(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 password = Uint8Array.from('password'.split('').map((x) => x.codePointAt(0))) const data = new Uint8Array([4, 5, 6]) const result = await encryptBackup(password, data) diff --git a/packages/backup-format/tests/tsconfig.json b/packages/backup-format/tests/tsconfig.json new file mode 100644 index 000000000000..27e9c94d27ed --- /dev/null +++ b/packages/backup-format/tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.leaf.json", + "compilerOptions": { + "rootDir": "./", + "tsBuildInfoFile": "../dist/tests.tsbuildinfo" + }, + "include": ["./"], + "references": [{ "path": "../tsconfig.json" }] +} diff --git a/packages/backup-format/tsconfig.json b/packages/backup-format/tsconfig.json index ecb7b18837fa..1b10e8f1de10 100644 --- a/packages/backup-format/tsconfig.json +++ b/packages/backup-format/tsconfig.json @@ -6,5 +6,5 @@ "tsBuildInfoFile": "./dist/.tsbuildinfo" }, "include": ["./src", "./src/**/*.json"], - "references": [{ "path": "../shared-base" }] + "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 cfe390996f8e..000000000000 --- a/packages/backup-format/tsconfig.tests.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "noEmit": true, - "tsBuildInfoFile": "./dist/tests.tsbuildinfo" - }, - "include": ["./tests"], - "references": [{ "path": "./tsconfig.json" }] -} diff --git a/packages/base/CHANGELOG.md b/packages/base/CHANGELOG.md new file mode 100644 index 000000000000..adca72cb19b7 --- /dev/null +++ b/packages/base/CHANGELOG.md @@ -0,0 +1,33 @@ +# @masknet/base + +## 0.3.2 + +### Patch Changes + +- fee2714: Update TypeScript definition to only accept Uint8Array + +## 0.3.1 + +### Patch Changes + +- 4837cb476: fix: Identifier class has circular reference and from method does not work + +## 0.3.0 + +### Minor Changes + +- 95ed3afc1: refactor!: upgrade to ts-results-es 4.0.0 + +## 0.2.0 + +### Minor Changes + +- d53e1858f: remove ECKeyIdentifierFromJsonWebKey. Use ECKeyIdentifier.fromJsonWebKey instead +- d53e1858f: add to/from JWK/CryptoKey methods on ECKeyIdentifier +- d53e1858f: add isEC_JsonWebKey + +## 0.1.0 + +### Minor Changes + +- 15bbd2649: initial release diff --git a/packages/base/README.md b/packages/base/README.md new file mode 100644 index 000000000000..d0dd6a7ab0a0 --- /dev/null +++ b/packages/base/README.md @@ -0,0 +1 @@ +# @masknet/base diff --git a/packages/base/package.json b/packages/base/package.json new file mode 100644 index 000000000000..610d05b6ab9f --- /dev/null +++ b/packages/base/package.json @@ -0,0 +1,27 @@ +{ + "name": "@masknet/base", + "version": "0.3.2", + "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/kit": "0.4.1", + "anchorme": "^2.1.2", + "pvtsutils": "^1.3.5", + "tiny-secp256k1": "^2.2.3", + "ts-results-es": "^4.2.0" + }, + "files": [ + "src", + "dist/**/*.js", + "dist/**/*.d.ts", + "dist/**/*.map" + ] +} diff --git a/packages/base/src/Identifier/identifier.ts b/packages/base/src/Identifier/identifier.ts new file mode 100644 index 000000000000..02cbaf0dd83d --- /dev/null +++ b/packages/base/src/Identifier/identifier.ts @@ -0,0 +1,329 @@ +import { type Option, None, Some, Result, Err } from 'ts-results-es' +import { decodeArrayBuffer, encodeArrayBuffer } from '@masknet/kit' +import { Convert } from 'pvtsutils' +import { isEC_JsonWebKey, type EC_JsonWebKey, type EC_Public_JsonWebKey } from '../WebCrypto/JsonWebKey.js' +import { decompressK256Key, type EC_CryptoKey, type EC_Public_CryptoKey } from '../index.js' +import { compressK256Key } from '../WebCrypto/k256.js' + +export abstract class Identifier { + abstract toText(): string + abstract [Symbol.toStringTag]: string + /** @internal */ + toString() { + return this.toText() + } + /** + * This type only refers to the stringified Identifier. ANY suffix CANNOT be renamed. + * person:... + * post:... + * post_iv:... + * ec_key:... + * + * Note: + * group: has removed, if you want to add a new identifier for grouping, please choose another prefix. + */ + static from(input: string | undefined | null): Option { + if (!input) return None + input = String(input) + if (input.startsWith('person:')) { + const [network, userID] = input.slice('person:'.length).split('/') + if (!network || !userID) return None + return ProfileIdentifier.of(network, userID) + } else if (input.startsWith('post:')) { + const [postID, ...rest] = input.slice('post:'.length).split('/') + const inner = this.from(rest.join('/')) + if (inner.isNone()) return None + if (inner.value instanceof ProfileIdentifier) return Some(new PostIdentifier(inner.value, postID)) + return None + } else if (input.startsWith('post_iv:')) { + const [network, postIV] = input.slice('post_iv:'.length).split('/') + if (!network || !postIV) return None + return Some(new PostIVIdentifier(network, postIV.replaceAll('|', '/'))) + } else if (input.startsWith('ec_key:')) { + const [curve, compressedPoint] = input.slice('ec_key:'.length).split('/') + if (curve !== 'secp256k1') return None + if (!compressedPoint) return None + return Some(new ECKeyIdentifier(curve, compressedPoint)) + } + return None + } + static [Symbol.hasInstance](x: unknown): boolean { + return ( + x instanceof ProfileIdentifier || + x instanceof ECKeyIdentifier || + x instanceof PostIVIdentifier || + x instanceof PostIdentifier + ) + } + static { + Object.freeze(Identifier.prototype) + Object.freeze(Identifier) + } +} + +/** + * This class identify the point on an EC curve. + * ec_key:secp256k1/CompressedPoint + */ +export class ECKeyIdentifier extends Identifier { + static #k256: { [k256: string]: ECKeyIdentifier } = Object.create(null) + static #keyAsHex: { [hex: string]: string } = Object.create(null) + static override from(input: string | null | undefined): Option { + if (!input) return None + input = String(input) + if (input.startsWith('ec_key:')) return super.from(input) as Option + return None + } + static fromHexPublicKeyK256(hex: string | null | undefined): Option { + if (!hex) return None + hex = String(hex) + if (hex.startsWith('0x')) hex = hex.slice(2) + const publicKey = encodeArrayBuffer(new Uint8Array(Convert.FromHex(hex))) + return Some(new ECKeyIdentifier('secp256k1', publicKey)) + } + static async fromJsonWebKey(key: EC_JsonWebKey): Promise> { + if (!isEC_JsonWebKey(key)) return Err(new Error('key is not a EC_JsonWebKey')) + if (key.crv !== 'K-256') return Err(new Error('curve is not K-256')) + const result = await Result.wrapAsync(() => compressK256Key(key)) + return result.map((key) => new ECKeyIdentifier('secp256k1', key)) + } + static async fromCryptoKey(key: EC_CryptoKey): Promise> { + if (!key.extractable) return Err('key is not extractable') + if ((key.algorithm as EcKeyAlgorithm).namedCurve !== 'K-256') return Err('curve is not K-256') + const jwk = await Result.wrapAsync(() => crypto.subtle.exportKey('jwk', key)) + if (jwk.isErr()) return jwk + return this.fromJsonWebKey(jwk.value as EC_JsonWebKey) + } + async toJsonWebKey(usage: 'sign_and_verify' | 'derive'): Promise { + const key = await decompressK256Key(this.rawPublicKey) + if (usage === 'sign_and_verify') key.key_ops = ['sign', 'verify'] + return key + } + async toCryptoKey(usage: 'sign_and_verify' | 'derive'): Promise { + const key = await this.toJsonWebKey(usage) + return crypto.subtle.importKey( + 'jwk', + key as JsonWebKey, + { name: usage === 'sign_and_verify' ? 'ECDSA' : 'ECDH', namedCurve: 'K-256' }, + true, + key.key_ops as readonly KeyUsage[], + ) as Promise + } + + declare readonly curve: 'secp256k1' + declare readonly rawPublicKey: string + constructor(curve: 'secp256k1', publicKey: string) { + publicKey = String(publicKey).replaceAll('|', '/') + if (curve !== 'secp256k1') throw new Error('Only secp256k1 is supported') + + // return the cache to keep the object identity + // eslint-disable-next-line no-constructor-return + if (ECKeyIdentifier.#k256[publicKey]) return ECKeyIdentifier.#k256[publicKey] + + super() + this.curve = 'secp256k1' + this.rawPublicKey = publicKey + Object.freeze(this) + ECKeyIdentifier.#k256[publicKey] = this + } + toText() { + const normalized = this.rawPublicKey.replaceAll('/', '|') + return `ec_key:${this.curve}/${normalized}` + } + get publicKeyAsHex() { + return ( + '0x' + + (ECKeyIdentifier.#keyAsHex[this.rawPublicKey] ??= Convert.ToHex(decodeArrayBuffer(this.rawPublicKey))) + ) + } + declare [Symbol.toStringTag]: string + #ec: undefined + static override [Symbol.hasInstance](x: any): boolean { + return typeof x === 'object' && x !== null && #ec in x + } + static { + ECKeyIdentifier.prototype[Symbol.toStringTag] = 'ECKeyIdentifier' + Object.freeze(ECKeyIdentifier.prototype) + Object.freeze(ECKeyIdentifier) + } +} +export type PersonaIdentifier = ECKeyIdentifier +export const PersonaIdentifier = [ECKeyIdentifier] + +export class PostIVIdentifier extends Identifier { + static #cache: { [network: string]: { [postIV: string]: PostIVIdentifier } } = Object.create(null) + static override from(input: string | null | undefined): Option { + if (!input) return None + input = String(input) + if (input.startsWith('post_iv:')) return super.from(input) as Option + return None + } + declare readonly network: string + declare readonly postIV: string + constructor(network: string, postIV: string) { + network = String(network) + postIV = String(postIV) + + if (network === 'x.com') { + network = 'twitter.com' + console.warn('[@masknet/shared-base] For compatibility, x.com is converted to twitter.com') + } + + PostIVIdentifier.#cache[network] ??= {} + const networkCache = PostIVIdentifier.#cache[network] + // return the cache to keep the object identity + // eslint-disable-next-line no-constructor-return + if (networkCache[postIV]) return networkCache[postIV] + + banSlash(network, 'PostIVIdentifier.network') + super() + this.network = network + this.postIV = postIV + Object.freeze(this) + networkCache[postIV] = this + } + toText() { + return `post_iv:${this.network}/${this.postIV.replaceAll('/', '|')}` + } + toIV() { + const x = this.postIV.replaceAll('|', '/') + return new Uint8Array(decodeArrayBuffer(x)) + } + declare [Symbol.toStringTag]: string + #post_iv: undefined + static override [Symbol.hasInstance](x: any): boolean { + return typeof x === 'object' && x !== null && #post_iv in x + } + static { + PostIVIdentifier.prototype[Symbol.toStringTag] = 'PostIVIdentifier' + Object.freeze(PostIVIdentifier.prototype) + Object.freeze(PostIVIdentifier) + } +} + +/** + * If identifier is a PostIdentifier, that means this post is bound with other post in some kind + * e.g. a comment. + */ +export class PostIdentifier extends Identifier { + static #cache = new WeakMap() + static override from(input: string | null | undefined): Option { + if (!input) return None + input = String(input) + if (input.startsWith('post:')) return super.from(input) as Option + return None + } + declare readonly identifier: ProfileIdentifier + declare readonly postID: string + constructor(identifier: ProfileIdentifier, postID: string) { + if (!(identifier instanceof ProfileIdentifier)) + throw new TypeError('[@masknet/shared-base] PostIdentifier.identifier is not a ProfileIdentifier') + + if (!PostIdentifier.#cache.has(identifier)) PostIdentifier.#cache.set(identifier, Object.create(null)) + const idCache = PostIdentifier.#cache.get(identifier)! + // return the cache to keep the object identity + // eslint-disable-next-line no-constructor-return + if (idCache[postID]) return idCache[postID] + + banSlash(postID, 'PostIdentifier.postID') + super() + this.identifier = identifier + this.postID = postID + Object.freeze(this) + idCache[postID] = this + } + toText() { + return `post:${this.postID}/${this.identifier.toText()}` + } + /** @deprecated */ + get postId() { + return this.postID + } + declare [Symbol.toStringTag]: string + #post: undefined + static override [Symbol.hasInstance](x: any): boolean { + return typeof x === 'object' && x !== null && #post in x + } + static { + PostIdentifier.prototype[Symbol.toStringTag] = 'PostIdentifier' + Object.freeze(PostIdentifier.prototype) + Object.freeze(PostIdentifier) + } +} + +/** + * Refers to a profile on a network. + */ +export class ProfileIdentifier extends Identifier { + static #cache: { [network: string]: { [userID: string]: ProfileIdentifier } } = Object.create(null) + static override from(input: string | null | undefined): Option { + input = String(input) + if (input === 'person:localhost/$unknown') return None + if (input.startsWith('person:')) return super.from(input) as Option + return None + } + static of(network: string | undefined | null, userID: string | undefined | null): Option { + if (!userID || !network) return None + if (network === 'localhost' && userID === '$unknown') return None + if (network.includes('/') || userID.includes('/')) return None + if (network.includes('\n') || userID.includes('\n')) return None + return Some(new ProfileIdentifier(network, userID)) + } + + // ! "network" and "userId" cannot be renamed because they're stored in the database in it's object form. + declare readonly network: string + declare readonly userId: string + private constructor(network: string, userID: string) { + network = String(network).toLowerCase() + userID = String(userID) + + if (network === 'x.com') { + network = 'twitter.com' + console.warn('[@masknet/shared-base] For compatibility, x.com is converted to twitter.com') + } + + if (network === 'localhost' && userID === '$unknown') { + throw new TypeError('[@masknet/base] Use null instead.') + } + if (!userID) throw new TypeError('[@masknet/base] userID cannot be empty.') + + ProfileIdentifier.#cache[network] ??= {} + const networkCache = ProfileIdentifier.#cache[network] + // return the cache to keep the object identity + // eslint-disable-next-line no-constructor-return + if (networkCache[userID]) return networkCache[userID] + + banSlash(network, 'ProfileIdentifier.network') + banSlash(userID, 'ProfileIdentifier.userID') + super() + this.network = network + this.userId = userID + Object.freeze(this) + networkCache[userID] = this + } + toText() { + return `person:${this.network}/${this.userId}` + } + declare [Symbol.toStringTag]: string + #profile: undefined + static override [Symbol.hasInstance](x: any): boolean { + return typeof x === 'object' && x !== null && #profile in x + } + static { + ProfileIdentifier.prototype[Symbol.toStringTag] = 'ProfileIdentifier' + Object.freeze(ProfileIdentifier.prototype) + Object.freeze(ProfileIdentifier) + } +} + +/** + * Because "/" is used to split parts in identifier + * we should reject the "/" + * + * If you want to use it, you must first convert to something else + * @internal + */ +function banSlash(input: string | undefined | null, source?: string) { + if (!input) return + if (input.includes('/')) throw new TypeError(`Cannot contain / in a part of identifier (${source}): ${input}`) +} diff --git a/packages/base/src/Identifier/index.ts b/packages/base/src/Identifier/index.ts new file mode 100644 index 000000000000..a6fb17f2dee8 --- /dev/null +++ b/packages/base/src/Identifier/index.ts @@ -0,0 +1,2 @@ +export * from './identifier.js' +export * from './map.js' diff --git a/packages/base/src/Identifier/map.ts b/packages/base/src/Identifier/map.ts new file mode 100644 index 000000000000..32f3969668bb --- /dev/null +++ b/packages/base/src/Identifier/map.ts @@ -0,0 +1,59 @@ +import { + Identifier, + ProfileIdentifier as A, + ECKeyIdentifier as B, + PostIdentifier as C, + PostIVIdentifier as D, +} from './identifier.js' + +type I = Iterable + +// you're free to add a new overload... +export function convertRawMapToIdentifierMap(it: I, ...of: Array): Map +export function convertRawMapToIdentifierMap(it: I, ...of: Array): Map +export function convertRawMapToIdentifierMap(it: I, ...of: Array): Map +export function convertRawMapToIdentifierMap(it: I, ...of: Array): Map +export function convertRawMapToIdentifierMap(it: I, ...of: unknown[]): Map { + const hasProfileIdentifier = of.includes(A) + const hasECKeyIdentifier = of.includes(B) + const hasPostIdentifier = of.includes(C) + const hasPostIVIdentifier = of.includes(D) + + const result = new Map() + const droppedValues = new Map() + for (const [key, value] of it) { + const id = Identifier.from(key) + if (id.isNone()) { + droppedValues.set(key, value) + continue + } + + if ( + (hasProfileIdentifier && id.value instanceof A) || + (hasECKeyIdentifier && id.value instanceof B) || + (hasPostIdentifier && id.value instanceof C) || + (hasPostIVIdentifier && id.value instanceof D) + ) + result.set(id.value, value) + else droppedValues.set(key, value) + } + + if (droppedValues.size) { + console.warn( + '[@masknet/shared-base] identifierRawMapToMap: Some value violates the constraint. Dropped values:', + droppedValues, + 'constraints:', + of, + ) + } + + return result +} + +export function convertIdentifierMapToRawMap(map: Iterable<[Identifier, T]>): Map { + const result = new Map() + for (const [key, value] of map) { + result.set(key.toText(), value) + } + return result +} diff --git a/packages/base/src/WebCrypto/CryptoKey.ts b/packages/base/src/WebCrypto/CryptoKey.ts new file mode 100644 index 000000000000..64f2a4b250aa --- /dev/null +++ b/packages/base/src/WebCrypto/CryptoKey.ts @@ -0,0 +1,13 @@ +// Create nominal typing interfaces for different CryptoKey type +// So they will no longer assignable to each other + +export type EC_CryptoKey = EC_Private_CryptoKey | EC_Public_CryptoKey +export interface EC_Public_CryptoKey extends CryptoKey, Nominal<'EC public'> {} +export interface EC_Private_CryptoKey extends CryptoKey, Nominal<'EC private'> {} +export interface AESCryptoKey extends CryptoKey, Nominal<'AES'> {} +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters +declare class Nominal { + /** Ghost property, don't use it! */ + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members + #__brand: T +} diff --git a/packages/base/src/WebCrypto/JsonWebKey.ts b/packages/base/src/WebCrypto/JsonWebKey.ts new file mode 100644 index 000000000000..524745409418 --- /dev/null +++ b/packages/base/src/WebCrypto/JsonWebKey.ts @@ -0,0 +1,36 @@ +export interface JsonWebKeyPair, Priv extends JsonWebKey & Nominal> { + publicKey: Pub + privateKey: Priv +} +// Create nominal typing interfaces for different JsonWebKey type +// So they will no longer assignable to each other + +export type EC_JsonWebKey = EC_Private_JsonWebKey | EC_Public_JsonWebKey +export interface EC_Public_JsonWebKey extends JsonWebKey, Nominal<'EC public'> {} +export interface EC_Private_JsonWebKey extends JsonWebKey, Nominal<'EC private'> {} +export interface AESJsonWebKey extends JsonWebKey, Nominal<'AES'> {} + +export function isAESJsonWebKey(x: unknown): x is AESJsonWebKey { + if (typeof x !== 'object' || x === null) return false + const { alg, k, key_ops, kty } = x as JsonWebKey + return !(!alg || !k) && Array.isArray(key_ops) && kty === 'oct' +} +export function isEC_JsonWebKey(o: unknown): o is EC_JsonWebKey { + if (typeof o !== 'object' || o === null) return false + const { crv, key_ops, kty, x, y } = o as JsonWebKey + return !(!crv || !Array.isArray(key_ops) || !kty || !x || !y) +} +export function isEC_Public_JsonWebKey(o: unknown): o is EC_Public_JsonWebKey { + if (!isEC_JsonWebKey(o)) return false + return !o.d +} +export function isEC_Private_JsonWebKey(o: unknown): o is EC_Private_JsonWebKey { + if (!isEC_JsonWebKey(o)) return false + return !!o.d +} +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters +declare class Nominal { + /** Ghost property, don't use it! */ + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members + #__brand: T +} diff --git a/packages/base/src/WebCrypto/index.ts b/packages/base/src/WebCrypto/index.ts new file mode 100644 index 000000000000..f2475d184544 --- /dev/null +++ b/packages/base/src/WebCrypto/index.ts @@ -0,0 +1,3 @@ +export type * from './CryptoKey.js' +export * from './JsonWebKey.js' +export * from './k256.js' diff --git a/packages/base/src/WebCrypto/k256.ts b/packages/base/src/WebCrypto/k256.ts new file mode 100644 index 000000000000..04944edfbbb3 --- /dev/null +++ b/packages/base/src/WebCrypto/k256.ts @@ -0,0 +1,71 @@ +import { encodeArrayBuffer, decodeArrayBuffer, concatArrayBuffer } from '@masknet/kit' +import type { EC_JsonWebKey, EC_Public_JsonWebKey } from './JsonWebKey.js' +import { Convert } from 'pvtsutils' + +/** + * Compress x & y into a single x + */ +export async function compressK256Point(x: string, y: string): Promise { + const { isPoint, pointCompress } = await import('tiny-secp256k1') + const xb = new Uint8Array(Convert.FromBase64Url(x)) + const yb = new Uint8Array(Convert.FromBase64Url(y)) + const point = new Uint8Array(concatArrayBuffer(new Uint8Array([0x04]), xb, yb)) + if (isPoint(point)) { + return pointCompress(point, true) + } else { + throw new TypeError('Not a point on secp256k1.') + } +} +/** + * Decompress point into x & y + */ +export async function decompressK256Point(point: Uint8Array): Promise<{ x: string; y: string }> { + const { isPoint, isPointCompressed, pointCompress } = await import('tiny-secp256k1') + if (!isPoint(point)) throw new TypeError('Not a point on secp256k1.') + const uncompressed = isPointCompressed(point) ? pointCompress(point, false) : point + const len = (uncompressed.length - 1) / 2 + const x = uncompressed.slice(1, len + 1) + const y = uncompressed.slice(len + 1) + return { x: Convert.ToBase64Url(x), y: Convert.ToBase64Url(y) } +} + +export async function compressK256KeyRaw(point: Uint8Array) { + const { isPoint, isPointCompressed, pointCompress } = await import('tiny-secp256k1') + if (!isPoint(point)) throw new TypeError('Not a point on secp256k1.') + if (isPointCompressed(point)) return point + return pointCompress(point, true) as Uint8Array +} +export async function decompressK256Raw(point: Uint8Array) { + const { isPoint, isPointCompressed, pointCompress } = await import('tiny-secp256k1') + if (!isPoint(point)) throw new TypeError('Not a point on secp256k1.') + if (!isPointCompressed(point)) return point + return pointCompress(point, false) as Uint8Array +} + +export async function compressK256Key(key: EC_JsonWebKey): Promise { + const arr = await compressK256Point(key.x!, key.y!) + return encodeArrayBuffer(arr) +} +export async function decompressK256Key(compressedPublic: string): Promise { + const arr = decodeArrayBuffer(compressedPublic) + const key = await decompressK256Point(new Uint8Array(arr)) + const jwk: JsonWebKey = { + crv: 'K-256', + ext: true, + x: key.x, + y: key.y, + key_ops: ['deriveKey', 'deriveBits'], + kty: 'EC', + } + return jwk as EC_Public_JsonWebKey +} + +export async function isK256Point(x: Uint8Array) { + const { isPoint } = await import('tiny-secp256k1') + return isPoint(x) +} + +export async function isK256PrivateKey(d: Uint8Array) { + const { isPrivate } = await import('tiny-secp256k1') + return isPrivate(d) +} diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts new file mode 100644 index 000000000000..5e1e557fb55f --- /dev/null +++ b/packages/base/src/index.ts @@ -0,0 +1,34 @@ +export { + Identifier, + ECKeyIdentifier, + PersonaIdentifier, + PostIVIdentifier, + PostIdentifier, + ProfileIdentifier, + convertIdentifierMapToRawMap, + convertRawMapToIdentifierMap, +} from './Identifier/index.js' +export { + type AESCryptoKey, + type AESJsonWebKey, + type EC_CryptoKey, + type EC_JsonWebKey, + type EC_Private_CryptoKey, + type EC_Private_JsonWebKey, + type EC_Public_CryptoKey, + type EC_Public_JsonWebKey, + type JsonWebKeyPair, + isAESJsonWebKey, + isEC_JsonWebKey, + isEC_Private_JsonWebKey, + isEC_Public_JsonWebKey, + compressK256KeyRaw, + compressK256Point, + decompressK256Key, + decompressK256Point, + decompressK256Raw, + isK256Point, + isK256PrivateKey, +} from './WebCrypto/index.js' +export { CheckedError, OptionalResult, andThenAsync } from './ts-results/index.js' +export { parseURLs } from './utils/index.js' diff --git a/packages/shared-base/src/Results/CheckedError.ts b/packages/base/src/ts-results/CheckedError.ts similarity index 79% rename from packages/shared-base/src/Results/CheckedError.ts rename to packages/base/src/ts-results/CheckedError.ts index a5d59de8e3d2..7ba9d9144c9e 100644 --- a/packages/shared-base/src/Results/CheckedError.ts +++ b/packages/base/src/ts-results/CheckedError.ts @@ -1,7 +1,10 @@ -import { Err, Result } from 'ts-results' +import { Err, type Result } from 'ts-results-es' export class CheckedError extends Error { - constructor(private kind: T, private reason: any) { + constructor( + private kind: T, + private reason: any, + ) { super(kind + '', { cause: reason }) } override toString() { @@ -25,8 +28,8 @@ export class CheckedError extends Error { ): (...args: P) => Result> | Promise>> { return (...args: P) => { const r = f(...args) - if ('then' in r) return r.then((r) => r.mapErr(CheckedError.mapErr(o))) - return r.mapErr(CheckedError.mapErr(o)) + if ('then' in r) return r.then((r) => r.mapErr(this.mapErr(o))) + return r.mapErr(this.mapErr(o)) } } toErr() { diff --git a/packages/base/src/ts-results/OptionalResult.ts b/packages/base/src/ts-results/OptionalResult.ts new file mode 100644 index 000000000000..969ab71375b1 --- /dev/null +++ b/packages/base/src/ts-results/OptionalResult.ts @@ -0,0 +1,13 @@ +import { type Result, type Option, Ok, None, Some } from 'ts-results-es' +import type { CheckedError } from './CheckedError.js' +export type OptionalResult = Result, CheckedError> +export const OptionalResult = { + Some(x: T): OptionalResult { + return Ok(Some(x)) + }, + None: Ok(None) as OptionalResult, + fromResult(x: Result>): OptionalResult { + if (x.isErr()) return x + return Ok(Some(x.value)) + }, +} diff --git a/packages/base/src/ts-results/andThenAsync.ts b/packages/base/src/ts-results/andThenAsync.ts new file mode 100644 index 000000000000..42b9effd12f5 --- /dev/null +++ b/packages/base/src/ts-results/andThenAsync.ts @@ -0,0 +1,10 @@ +import type { Result } from 'ts-results-es' + +export async function andThenAsync( + op: Result | Promise>, + mapper: (t: T) => Result | Promise>, +): Promise> { + op = await op + if (op.isErr()) return op + return mapper(op.value) +} diff --git a/packages/base/src/ts-results/index.ts b/packages/base/src/ts-results/index.ts new file mode 100644 index 000000000000..f5838d40b1c3 --- /dev/null +++ b/packages/base/src/ts-results/index.ts @@ -0,0 +1,3 @@ +export * from './OptionalResult.js' +export * from './andThenAsync.js' +export * from './CheckedError.js' diff --git a/packages/base/src/utils/index.ts b/packages/base/src/utils/index.ts new file mode 100644 index 000000000000..b7b67b4df43e --- /dev/null +++ b/packages/base/src/utils/index.ts @@ -0,0 +1 @@ +export * from './parseURLs.js' diff --git a/packages/base/src/utils/parseURLs.ts b/packages/base/src/utils/parseURLs.ts new file mode 100644 index 000000000000..a463c2b3ade1 --- /dev/null +++ b/packages/base/src/utils/parseURLs.ts @@ -0,0 +1,15 @@ +// Note: this package is published as is, therefore we cannot ship the syntax nor change the module target. +// eslint-disable-next-line @masknet/prefer-defer-import +import * as parser from /* webpackDefer: true */ 'anchorme' +export function parseURLs(text: string, requireProtocol = true): string[] { + // CJS-ESM compatibility + const lib = parser.default.default || parser.default + return lib + .list(text) + .map((x) => x.string) + .filter((y) => { + if (!requireProtocol) return true + // See https://github.com/alexcorvi/anchorme.js/issues/109 + return URL.canParse(y) + }) +} diff --git a/packages/base/tests/identifier.ts b/packages/base/tests/identifier.ts new file mode 100644 index 000000000000..8c04083e8ac2 --- /dev/null +++ b/packages/base/tests/identifier.ts @@ -0,0 +1,80 @@ +/* cspell:disable */ +import { ECKeyIdentifier, ProfileIdentifier } from '../src/index.js' +import { describe, expect, test } from 'vitest' + +describe('ECKeyIdentifier', () => { + const identifierTextMock = 'Avy0TP3pwfgzipD7h7+eynhAgfa8PLnD9yNR2Q7cUonv' + const identifierTextMock1 = 'AxxjarSyam+UQa5L0Qo0mibBXtUzwBTo5AbbKV7vhXRM' + const identifierECKeyTextMock = 'ec_key:secp256k1/Avy0TP3pwfgzipD7h7+eynhAgfa8PLnD9yNR2Q7cUonv' + + const a = new ECKeyIdentifier('secp256k1', identifierTextMock) + expect(a).instanceOf(ECKeyIdentifier) + test('should have same identity for the same ECKeyIdentifier', () => { + const a2 = new ECKeyIdentifier('secp256k1', identifierTextMock) + const a3 = ECKeyIdentifier.from(identifierECKeyTextMock).unwrap() + const a4 = ECKeyIdentifier.from(identifierECKeyTextMock).unwrap() + expect(new Set([a, a2, a3, a4]).size).toBe(1) + + const b = new ECKeyIdentifier('secp256k1', identifierTextMock1) + expect(a).not.toBe(b) + + expect(a.curve).toMatchInlineSnapshot('"secp256k1"') + expect(a.publicKeyAsHex).toMatchInlineSnapshot( + '"0x02fcb44cfde9c1f8338a90fb87bf9eca784081f6bc3cb9c3f72351d90edc5289ef"', + ) + expect(a.rawPublicKey).toMatchInlineSnapshot('"Avy0TP3pwfgzipD7h7+eynhAgfa8PLnD9yNR2Q7cUonv"') + expect(a.toText()).toMatchInlineSnapshot('"ec_key:secp256k1/Avy0TP3pwfgzipD7h7+eynhAgfa8PLnD9yNR2Q7cUonv"') + }) + + test('should be immutable', () => { + expect(() => Object.assign(a, { curve: 'secp256p1' })).throws() + expect(() => Object.assign(a, { publicKeyAsHex: 'a' })).throws() + expect(() => Object.assign(a, { rawPublicKey: 'b' })).throws() + expect(() => Object.assign(a, { extraKey: true })).throws() + }) +}) + +describe('ProfileIdentifier', () => { + const identifierTextMock = 'person:twitter.com/test_twitter_1' + const identifierTextMock1 = 'person:twitter.com/test_twitter_2' + + const a = ProfileIdentifier.from(identifierTextMock).unwrap() + expect(a).instanceOf(ProfileIdentifier) + test('should have same identity for the same ProfileIdentifier', () => { + const a2 = ProfileIdentifier.from(identifierTextMock).unwrap() + const a3 = ProfileIdentifier.of('twitter.com', 'test_twitter_1').unwrap() + const a4 = ProfileIdentifier.of('twitter.com', 'test_twitter_1').unwrap() + expect(new Set([a, a2, a3, a4]).size).toBe(1) + + const b = ProfileIdentifier.from(identifierTextMock1) + expect(a).not.toBe(b) + + expect(a.network).toMatchInlineSnapshot('"twitter.com"') + expect(a.userId).toMatchInlineSnapshot('"test_twitter_1"') + expect(a.toText()).toMatchInlineSnapshot('"person:twitter.com/test_twitter_1"') + }) + // Note: ProfileIdentifier MUST NOT be case insensitive. It's toText() result will be used as password of stego. + // change it to case insensitive is a breaking change. + test('should NOT be case insensitive', () => { + const a2 = ProfileIdentifier.from('person:twitter.com/Test_twitter_1').unwrap() + expect(a).not.toBe(a2) + }) + test('should be immutable', () => { + expect(() => Object.assign(a, { network: 'twitter.com' })).throws() + expect(() => Object.assign(a, { userId: 'test_twitter_1' })).throws() + expect(() => Object.assign(a, { extraKey: true })).throws() + }) + + test('.from()', () => { + expect(ProfileIdentifier.from(undefined).isNone()).toBe(true) + expect(ProfileIdentifier.from(null).isNone()).toBe(true) + expect(ProfileIdentifier.from('other:a.com/test').isNone()).toBe(true) + expect(ProfileIdentifier.from('person:localhost/$unknown').isNone()).toBe(true) + }) + + test('.of()', () => { + expect(ProfileIdentifier.of(null, 'test_twitter_1').isNone()).toBe(true) + expect(ProfileIdentifier.of('twitter.com', null).isNone()).toBe(true) + expect(ProfileIdentifier.of('localhost', '$unknown').isNone()).toBe(true) + }) +}) diff --git a/packages/base/tests/tsconfig.json b/packages/base/tests/tsconfig.json new file mode 100644 index 000000000000..27e9c94d27ed --- /dev/null +++ b/packages/base/tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.leaf.json", + "compilerOptions": { + "rootDir": "./", + "tsBuildInfoFile": "../dist/tests.tsbuildinfo" + }, + "include": ["./"], + "references": [{ "path": "../tsconfig.json" }] +} diff --git a/packages/base/tsconfig.json b/packages/base/tsconfig.json new file mode 100644 index 000000000000..42ec59613f06 --- /dev/null +++ b/packages/base/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.npm.json", + "compilerOptions": { + "rootDir": "./src/", + "outDir": "./dist/", + "tsBuildInfoFile": "./dist/.tsbuildinfo" + }, + "include": ["./src", "./src/**/*.json"], + "references": [] +} diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md new file mode 100644 index 000000000000..acea36ae97b5 --- /dev/null +++ b/packages/config/CHANGELOG.md @@ -0,0 +1,20 @@ +# @masknet/config + +## 0.2.1 + +### Patch Changes + +- 5db69be39: add sideEffects: false field in package.json + +## 0.2.0 + +### Minor Changes + +- a4dd2e585: upgrade to ts 5.0 +- 034df655c: remove isolatedModules + +## 0.1.0 + +### Minor Changes + +- 3b9a37468: add shared tsconfig diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 000000000000..cb5f172fc375 --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,6 @@ +{ + "name": "@masknet/config", + "version": "0.2.1", + "type": "module", + "sideEffects": false +} diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json new file mode 100644 index 000000000000..ae50bd65bc7e --- /dev/null +++ b/packages/config/tsconfig.json @@ -0,0 +1,51 @@ +{ + "compilerOptions": { + "verbatimModuleSyntax": true, + // Classification follows https://www.typescriptlang.org/tsconfig + + // Type Checking + "strict": true, + // exactOptionalPropertyTypes: false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + // noPropertyAccessFromIndexSignature: false, + // noUncheckedIndexedAccess: false, + "noUncheckedSideEffectImports": true, + // noUnusedLocals: false, + // noUnusedParameters: false, + "useUnknownInCatchVariables": true, + + // Modules + "module": "NodeNext", + "moduleResolution": "NodeNext", + + // Emit + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "stripInternal": true, // skip type emit for @internal types + + // JavaScript Support + "allowJs": false, + "checkJs": false, + + // Interop Constraints + "forceConsistentCasingInFileNames": true, + + // Language and Environment + "jsx": "react-jsx", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "target": "ES2023", + "useDefineForClassFields": true, + + // Projects + "composite": true, + "incremental": true, + + // Completeness + "skipLibCheck": true // skip all type checks for .d.ts files + } +} diff --git a/packages/configuration/README.md b/packages/configuration/README.md deleted file mode 100644 index 73779adc31c4..000000000000 --- a/packages/configuration/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Configuration - -This package is an client of [Mask Configuration](https://github.com/DimensionDev/Mask-Configuration). It fetches configuration from a remote server. In brief, it returns the stale data while emitting an updating request to keep data fresh finally. - -## Usage - -```ts -const WHITELIST = create('whitelist', [ - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000002', - '0x0000000000000000000000000000000000000003', -]) - -// at first, it returns the initial whitelist -// at the same time, it emits a revalidate request -const whitelist = WHITELIST.get() - -// a few moments later, the server returns the fresh data -const whitelistSynced = WHITELIST.get() -``` diff --git a/packages/configuration/package.json b/packages/configuration/package.json deleted file mode 100644 index eb1e66be47c3..000000000000 --- a/packages/configuration/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@masknet/configuration", - "private": true, - "types": "./dist/index.d.ts", - "exports": { - ".": { - "webpack": "./src/index.ts", - "default": "./dist/index.js" - } - }, - "type": "module", - "dependencies": { - "urlcat": "^2.0.4" - } -} diff --git a/packages/configuration/src/constants.ts b/packages/configuration/src/constants.ts deleted file mode 100644 index 849c20ba1aa4..000000000000 --- a/packages/configuration/src/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DEFAULT_HOST = 'https://configuration.r2d2.to' -export const DEFAULT_PREFIX = 'com.maskbook' diff --git a/packages/configuration/src/index.ts b/packages/configuration/src/index.ts deleted file mode 100644 index 69c3e9e18294..000000000000 --- a/packages/configuration/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import urlcat from 'urlcat' -import { DEFAULT_HOST, DEFAULT_PREFIX } from './constants' - -class Configuration { - private ac: AbortController | undefined - - constructor(private url: string, private data: T | undefined) { - this.revalidate() - } - - private async revalidate() { - if (this.ac) this.ac.abort() - this.ac = new AbortController() - - const response = await fetch(this.url, { - signal: this.ac.signal, - }) - this.data = (await response.json()) as T - } - - get() { - this.revalidate() - return this.data - } -} - -const cache = new Map>() - -export function create(name: string, prefix?: string, initialData?: T) { - const url = urlcat(DEFAULT_HOST, prefix ? ':prefix.:name.json' : ':name.json', { - name, - prefix: prefix ?? DEFAULT_PREFIX, - }) - if (!cache.has(url)) cache.set(url, new Configuration(url, initialData)) - return cache.get(url) as Configuration -} diff --git a/packages/dashboard/.storybook/main.js b/packages/dashboard/.storybook/main.js deleted file mode 100644 index 3830c85d8961..000000000000 --- a/packages/dashboard/.storybook/main.js +++ /dev/null @@ -1,23 +0,0 @@ -const patchWebpackConfig = require('@masknet/storybook-shared/patch-webpack') - -module.exports = { - stories: ['../stories/**/*.mdx', '../stories/**/*.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - { - name: '@storybook/addon-essentials', - options: { - // https://github.com/storybookjs/storybook/issues/15901 - // docs not compatible with webpack 5. - docs: false, - }, - }, - ], - reactOptions: { - fastRefresh: true, - }, - webpackFinal: patchWebpackConfig, - core: { - builder: 'webpack5', - }, -} diff --git a/packages/dashboard/.storybook/manager.js b/packages/dashboard/.storybook/manager.js deleted file mode 100644 index c50f84cc035e..000000000000 --- a/packages/dashboard/.storybook/manager.js +++ /dev/null @@ -1,10 +0,0 @@ -import { addons } from '@storybook/addons' -import { themes } from '@storybook/theming' - -addons.setConfig({ - panelPosition: 'right', - theme: themes.light, - selectedPanel: undefined, - initialActive: 'sidebar', - showRoots: true, -}) diff --git a/packages/dashboard/.storybook/preview.js b/packages/dashboard/.storybook/preview.js deleted file mode 100644 index 3084c436862f..000000000000 --- a/packages/dashboard/.storybook/preview.js +++ /dev/null @@ -1,35 +0,0 @@ -// @ts-check -import React from 'react' -import { ThemeProvider, StyledEngineProvider } from '@mui/material' -import { applyMaskColorVars, CustomSnackbarProvider, MaskDarkTheme } from '@masknet/theme' -import { addSharedI18N, I18NextProviderHMR } from '@masknet/shared' -// import { withMatrix } from 'storybook-addon-matrix' -import { addDashboardI18N } from '../src/locales/languages' -import { i18NextInstance } from '@masknet/shared-base' -import { DisableShadowRootContext } from '@masknet/theme' - -addDashboardI18N(i18NextInstance) -addSharedI18N(i18NextInstance) - -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, -} -export const decorators = [ - // withMatrix, - (Story) => ( - - - - - - - - - - - - - - ), -] -applyMaskColorVars(document.body, 'light') diff --git a/packages/dashboard/README.md b/packages/dashboard/README.md deleted file mode 100644 index 6233319fca9e..000000000000 --- a/packages/dashboard/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Dashboard - -The developing new dashboard. - -Expected to working in three mode: - -## Storybook - -Most of the components should be expected to work in the Storybook. -This means you should split logic and UI components. - -## Isolated website - -This project should be able to run under http or https instead of chrome-extension. - -Currently, you must also install the development mode (`./dist/`) Mask Network extension where the extension ID must be `jkoeaghipilijlahjplgbfiocjhldnap`. -Otherwise you will encountered error "Error: Attempting to use a disconnected port object". - -It is not required to open the webpack process of the main Mask Network to develop this project (this is what "isolated" means). - -Currently the isolated mode only works on Chrome. - -## Embedded mode - -This project should be able to run in the chrome-extension protocol (embedded in the extension). diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json deleted file mode 100644 index 794d858b72de..000000000000 --- a/packages/dashboard/package.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "@masknet/dashboard", - "private": true, - "description": "New dashboard of Mask Network", - "types": "./dist/src/entry.d.ts", - "exports": { - ".": { - "webpack": "./src/entry.tsx", - "default": "./dist/src/entry.js" - } - }, - "scripts": { - "storybook": "dev -- start-storybook -p 6006", - "build-storybook": "build -- build-storybook" - }, - "dependencies": { - "@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0", - "@hookform/resolvers": "^2.8.8", - "@masknet/backup-format": "workspace:*", - "@masknet/icons": "workspace:*", - "@masknet/plugin-example": "workspace:*", - "@masknet/plugin-flow": "workspace:*", - "@masknet/plugin-infra": "workspace:*", - "@masknet/plugin-solana": "workspace:*", - "@masknet/plugin-wallet": "workspace:*", - "@masknet/public-api": "workspace:*", - "@masknet/shared": "workspace:*", - "@masknet/shared-base": "workspace:*", - "@masknet/shared-base-ui": "workspace:*", - "@masknet/storybook-shared": "workspace:*", - "@masknet/theme": "workspace:*", - "@masknet/web3-providers": "workspace:*", - "@masknet/web3-shared-base": "workspace:*", - "@masknet/web3-shared-evm": "workspace:*", - "@msgpack/msgpack": "^2.7.2", - "@servie/events": "^3.0.0", - "@types/color": "^3.0.3", - "@types/react-avatar-editor": "^12.0.0", - "async-call-rpc": "^6.0.2", - "bignumber.js": "^9.0.2", - "classnames": "^2.3.1", - "color": "^4.2.3", - "date-fns": "2.28.0", - "html-to-image": "^1.9.0", - "json-stable-stringify": "^1.0.1", - "react-avatar-editor": "^12.0.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-hook-form": "^7.30.0", - "react-qrcode-logo": "^2.7.0", - "react-router-dom": "^6.3.0", - "react-to-print": "^2.14.6", - "react-use": "^17.3.2", - "unstated-next": "^1.1.0", - "urlcat": "^2.0.4", - "use-subscription": "1.6.0", - "uuid": "^8.3.2", - "wallet.ts": "^1.0.1", - "web3-core-helpers": "1.7.3", - "web3-utils": "1.7.3", - "zod": "^3.14.4" - }, - "devDependencies": { - "@babel/core": "^7.17.10", - "@storybook/addon-actions": "^6.4.22", - "@storybook/addon-essentials": "^6.4.22", - "@storybook/addon-links": "^6.4.22", - "@storybook/builder-webpack5": "^6.4.22", - "@storybook/manager-webpack5": "^6.4.22", - "@storybook/react": "^6.4.22", - "@types/use-subscription": "^1.0.0", - "@types/uuid": "^8.3.4", - "babel-loader": "^8.2.5", - "webpack": "^5.72.0" - } -} diff --git a/packages/dashboard/src/API.tsx b/packages/dashboard/src/API.tsx deleted file mode 100644 index 53593349d6f1..000000000000 --- a/packages/dashboard/src/API.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// This file includes the API bridge to the Mask Network extension -// In isolated mode, set up at ./initialization/isolated_bridge -// In integrated mode, set up at /packages/mask/src/extension/dashboard/index - -import type { DashboardPluginMessages, DashboardPluginServices } from '@masknet/shared' -import type { Services as ServiceType } from '../../mask/dist/src/extension/service' -import type { MaskEvents } from '@masknet/shared-base' -import type { WebExtensionMessage } from '@dimensiondev/holoflows-kit' -import type { WalletMessages } from '@masknet/plugin-wallet' - -export let Services: typeof ServiceType = null! -export let Messages: WebExtensionMessage = null! -export let PluginServices: PluginServices = null! -export let PluginMessages: PluginMessages = null! -export interface PluginServices extends DashboardPluginServices { - Wallet: typeof import('../../mask/dist/src/plugins/Wallet/messages').WalletRPC - Swap: typeof import('../../mask/dist/src/plugins/Trader/messages').PluginTraderRPC -} -export interface PluginMessages extends DashboardPluginMessages { - Wallet: typeof WalletMessages - Transak: typeof import('../../mask/dist/src/plugins/Transak/messages').PluginTransakMessages - Swap: typeof import('../../mask/dist/src/plugins/Trader/messages').PluginTraderMessages - Pets: typeof import('../../mask/dist/src/plugins/Pets/messages').PluginPetMessages -} -export function setService(rpc: any) { - Services = rpc - Object.assign(globalThis, { Services: rpc }) -} -export function setMessages(MaskMessage: any) { - Messages = MaskMessage - Object.assign(globalThis, { Messages: MaskMessage }) -} -export function setPluginServices(rpc: DashboardPluginServices) { - PluginServices = rpc as any - Object.assign(globalThis, { PluginServices: rpc }) -} - -export function setPluginMessages(message: DashboardPluginMessages) { - PluginMessages = message as any - Object.assign(globalThis, { PluginMessages: message }) -} diff --git a/packages/dashboard/src/Dashboard.tsx b/packages/dashboard/src/Dashboard.tsx deleted file mode 100644 index dee4be073eca..000000000000 --- a/packages/dashboard/src/Dashboard.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { lazy, Suspense } from 'react' - -const Dashboard = lazy(() => import(/* webpackPreload: true */ './initialization/Dashboard')) -export function IntegratedDashboard() { - return ( - - - - ) -} diff --git a/packages/dashboard/src/GuardRoute.tsx b/packages/dashboard/src/GuardRoute.tsx deleted file mode 100644 index 07643d109b6e..000000000000 --- a/packages/dashboard/src/GuardRoute.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PersonaContext } from './pages/Personas/hooks/usePersonaContext' -import { Navigate } from 'react-router-dom' - -interface GuardRouteProps { - redirectTo: string -} - -export default function NoPersonaGuardRoute(props: React.PropsWithChildren) { - const { redirectTo } = props - const { currentPersona } = PersonaContext.useContainer() - return currentPersona ? : <>{props.children} -} diff --git a/packages/dashboard/src/assets/images/AboutDialogBackground.png b/packages/dashboard/src/assets/images/AboutDialogBackground.png deleted file mode 100644 index 08c5b534ef07..000000000000 Binary files a/packages/dashboard/src/assets/images/AboutDialogBackground.png and /dev/null differ diff --git a/packages/dashboard/src/assets/images/MaskBlue.png b/packages/dashboard/src/assets/images/MaskBlue.png deleted file mode 100644 index 6ce0ae44f589..000000000000 Binary files a/packages/dashboard/src/assets/images/MaskBlue.png and /dev/null differ diff --git a/packages/dashboard/src/assets/images/MaskWatermark.png b/packages/dashboard/src/assets/images/MaskWatermark.png deleted file mode 100644 index d657316e3289..000000000000 Binary files a/packages/dashboard/src/assets/images/MaskWatermark.png and /dev/null differ diff --git a/packages/dashboard/src/assets/images/PoolTogether.png b/packages/dashboard/src/assets/images/PoolTogether.png deleted file mode 100644 index 5a6b57723d6f..000000000000 Binary files a/packages/dashboard/src/assets/images/PoolTogether.png and /dev/null differ diff --git a/packages/dashboard/src/assets/images/SetupTutorial.svg b/packages/dashboard/src/assets/images/SetupTutorial.svg deleted file mode 100644 index 781d8ed25516..000000000000 --- a/packages/dashboard/src/assets/images/SetupTutorial.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/dashboard/src/assets/index.ts b/packages/dashboard/src/assets/index.ts deleted file mode 100644 index a6bba9242542..000000000000 --- a/packages/dashboard/src/assets/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Manage local static resource - */ -export const ABOUT_DIALOG_BACKGROUND = new URL('./images/AboutDialogBackground.png', import.meta.url) -export const PoolTogetherURL = new URL('./images/PoolTogether.png', import.meta.url) -export const SetupTutorialURL = new URL('./images/SetupTutorial.svg', import.meta.url) -export const WatermarkURL = new URL('./images/MaskWatermark.png', import.meta.url) -export const MaskBlueURL = new URL('./images/MaskBlue.png', import.meta.url) diff --git a/packages/dashboard/src/components/ActionCard/index.tsx b/packages/dashboard/src/components/ActionCard/index.tsx deleted file mode 100644 index ced92461fb3d..000000000000 --- a/packages/dashboard/src/components/ActionCard/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Box, Button, Card, Stack, Typography } from '@mui/material' -import type { ReactNode } from 'react' -import { experimentalStyled as styled } from '@mui/material/styles' - -const ActionCardIcon = styled('div')( - ({ theme }) => ` - width: 36px; - height: 36px; - - & > svg { - width: 100%; - height: 100%; - } -`, -) - -const ActionCardButton = styled('div')( - ({ theme }) => ` - font-size: 14px; - - & > button { - width: 164px; - border-radius: ${theme.spacing(3)}; - } -`, -) - -export interface ISetupActionCardProps { - icon: ReactNode - title: string - subtitle?: string - action: { - type: 'secondary' | 'primary' - text: string - handler: () => void - } -} - -export const 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/dashboard/src/components/BackupPasswordConfirmDialog/index.tsx b/packages/dashboard/src/components/BackupPasswordConfirmDialog/index.tsx deleted file mode 100644 index e1d25f13e29a..000000000000 --- a/packages/dashboard/src/components/BackupPasswordConfirmDialog/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { memo, useContext, useMemo, useState } from 'react' -import { Button, DialogActions, DialogContent, Typography } from '@mui/material' -import { ConfirmPasswordOption, UserContext } from '../../pages/Settings/hooks/UserContext' -import { MaskDialog } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import PasswordField from '../PasswordField' - -interface DialogProps { - open: boolean - option?: ConfirmPasswordOption - onClose(): void - onConfirmed(): void -} - -export const BackupPasswordConfirmDialog = memo(({ onConfirmed, onClose, open, option }) => { - const t = useDashboardI18N() - const navigate = useNavigate() - const { user } = useContext(UserContext) - const [password, setPassword] = useState('') - const [error, setError] = useState('') - - const onSubmitPassword = () => { - if (user.backupPassword === password) { - onConfirmed() - } else { - setError(t.settings_dialogs_incorrect_password()) - } - } - - const title = useMemo(() => { - return (user.backupPassword ? option?.confirmTitle : option?.tipTitle) ?? t.confirm_password() - }, [option?.tipTitle, option?.confirmTitle]) - - return ( - - {!user.backupPassword && ( - <> - - - {option?.tipContent} - - - - - - - - )} - {user.backupPassword && ( - <> - - { - setPassword(e.currentTarget.value) - setError('') - }} - placeholder={t.settings_label_backup_password()} - error={!!error} - helperText={error} - /> - - - - - - - )} - - ) -}) diff --git a/packages/dashboard/src/components/ConfirmDialog/index.tsx b/packages/dashboard/src/components/ConfirmDialog/index.tsx deleted file mode 100644 index 11212cc4bcf0..000000000000 --- a/packages/dashboard/src/components/ConfirmDialog/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { MaskDialog } from '@masknet/theme' -import type { PropsWithChildren, ReactNode } from 'react' -import { DialogContent, DialogActions, Button, styled, buttonClasses } from '@mui/material' -import { useDashboardI18N } from '../../locales' - -const StyledButton: typeof Button = styled(Button)(() => ({ - [`&.${buttonClasses.root}`]: { - minWidth: 100, - }, -})) as any - -export interface ConfirmDialogProps extends PropsWithChildren<{}> { - title: string - open: boolean - cancelText?: ReactNode | string - confirmText?: ReactNode | string - confirmDisabled?: boolean - maxWidth?: false | 'sm' | 'xs' | 'md' | 'lg' | 'xl' - onClose(): void - onConfirm?(): void -} - -export default function ConfirmDialog(props: ConfirmDialogProps) { - const t = useDashboardI18N() - const { - title, - open, - onClose, - onConfirm, - children, - maxWidth = 'sm', - cancelText = t.settings_button_cancel(), - confirmText = t.settings_button_confirm(), - confirmDisabled = false, - } = props - return ( - - {children} - - - {cancelText} - - - {confirmText} - - - - ) -} diff --git a/packages/dashboard/src/components/ConnectActionList/ConnectActionListItem.tsx b/packages/dashboard/src/components/ConnectActionList/ConnectActionListItem.tsx deleted file mode 100644 index 50e99f462353..000000000000 --- a/packages/dashboard/src/components/ConnectActionList/ConnectActionListItem.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ListItemIcon, ListItemText, styled } from '@mui/material' -import ListItemButton from '@mui/material/ListItemButton' -import { MaskColorVar } from '@masknet/theme' - -export interface ConnectActionListItemProps { - title: string - icon: React.ReactNode - onClick(): void -} - -export function ConnectActionListItem(props: ConnectActionListItemProps) { - const { title, icon, onClick } = props - return ( - - - - {icon} - - ) -} - -const Icon = styled(ListItemIcon)` - font-size: 48px; -` - -const Container = styled(ListItemButton)` - border: 1px solid ${MaskColorVar.border}; - border-radius: 8px; -` - -const Dot = styled('div')( - ({ theme }) => ` - width: 10px; - height: 10px; - border-radius: 50%; - background-color: ${MaskColorVar.iconLight}; - margin-right: ${theme.spacing(2)}; -`, -) diff --git a/packages/dashboard/src/components/ConnectActionList/index.tsx b/packages/dashboard/src/components/ConnectActionList/index.tsx deleted file mode 100644 index 85b8d4025ca2..000000000000 --- a/packages/dashboard/src/components/ConnectActionList/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { StyledComponent } from '@emotion/styled' -import { styled, listItemClasses, Theme } from '@mui/material' -import type { MUIStyledCommonProps } from '@mui/system' -import type { DetailedHTMLProps, HTMLAttributes } from 'react' - -export const ConnectActionList: StyledComponent< - MUIStyledCommonProps, - DetailedHTMLProps, HTMLUListElement>, - {} -> = styled('ul')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - listStyle: 'none', - padding: 0, - // TODO: mobile - width: 520, - // TODO: deep style - [`& .${listItemClasses.root}`]: { - marginBottom: theme.spacing(1.5), - }, -})) - -export * from './ConnectActionListItem' diff --git a/packages/dashboard/src/components/ConnectWalletDialog/index.tsx b/packages/dashboard/src/components/ConnectWalletDialog/index.tsx deleted file mode 100644 index 1e724f25bb15..000000000000 --- a/packages/dashboard/src/components/ConnectWalletDialog/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { memo } from 'react' -import { PolkaDotIcon, WalletConnectIcon, MetaMaskIcon } from '@masknet/icons' -import { ConnectActionList, ConnectActionListItem } from '../ConnectActionList' -import { useDashboardI18N } from '../../locales' - -export enum ConnectWalletTargets { - MetaMask = 0, - Connect = 1, - PolkaDot = 2, -} -export interface ConnectWalletListProps { - onConnect(target: ConnectWalletTargets): void -} -export const ConnectWalletList = memo(({ onConnect }: ConnectWalletListProps) => { - const t = useDashboardI18N() - - return ( - - } - onClick={() => onConnect(ConnectWalletTargets.MetaMask)} - /> - } - onClick={() => onConnect(ConnectWalletTargets.Connect)} - /> - } - onClick={() => onConnect(ConnectWalletTargets.PolkaDot)} - /> - - ) -}) diff --git a/packages/dashboard/src/components/ContentContainer/index.tsx b/packages/dashboard/src/components/ContentContainer/index.tsx deleted file mode 100644 index e4c1a2e4c491..000000000000 --- a/packages/dashboard/src/components/ContentContainer/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { styled } from '@mui/material/styles' -import { MaskColorVar } from '@masknet/theme' - -export const ContentContainer = styled('div')(({ theme }) => ({ - flex: 1, - borderRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: MaskColorVar.primaryBackground, -})) diff --git a/packages/dashboard/src/components/CreatSuccessfully/index.tsx b/packages/dashboard/src/components/CreatSuccessfully/index.tsx deleted file mode 100644 index 08edf447ba51..000000000000 --- a/packages/dashboard/src/components/CreatSuccessfully/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { memo } from 'react' -import { Button, styled, Typography } from '@mui/material' -import { SuccessIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' -export interface CreateSuccessfully { - onUnlock(): void -} - -export const CreateSuccessfully = memo((props: CreateSuccessfully) => { - const { onUnlock } = props - - const t = useDashboardI18N() - return ( - - - - - {t.wallets_create_successfully_title()} - {t.wallets_create_successfully_tips()} - {t.wallets_create_successfully_unlock()} - - ) -}) - -const Icon = styled('div')` - font-size: 64px; -` - -const Container = styled('div')` - display: flex; - flex-direction: column; - align-items: center; -` - -const SuccessTitle = styled(Typography)(({ theme }) => ({ - fontSize: theme.typography.h5.fontSize, - color: theme.palette.success.main, - fontWeight: theme.typography.fontWeightMedium as any, - margin: theme.spacing(2, 0), -})) - -const SuccessTips = styled(Typography)(({ theme }) => ({ - fontSize: theme.typography.fontSize, - color: MaskColorVar.normalText, -})) - -const UnlockButton = styled(Button)( - ({ theme }) => ` - width: 124px; - height: 32px; - border-radius: 6px; - margin-top: ${theme.spacing(7)} -`, -) diff --git a/packages/dashboard/src/components/CreateWalletForm/index.tsx b/packages/dashboard/src/components/CreateWalletForm/index.tsx deleted file mode 100644 index ddb77638de16..000000000000 --- a/packages/dashboard/src/components/CreateWalletForm/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { - FormControl, - ListItemIcon, - MenuItem, - Select, - styled, - Typography, - FilledInput, - SelectChangeEvent, -} from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { useCallback, useState } from 'react' -import { useDashboardI18N } from '../../locales' - -const useStyles = makeStyles()((theme) => ({ - root: { - // TODO: mobile - width: 380, - marginTop: theme.spacing(1.5), - }, - input: { - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - }, - filled: { - display: 'flex', - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - }, -})) - -// TODO: actions, and icon may be an img url -export interface CreateWalletFormProps { - options: { - label: string - icon: React.ReactNode - value: number - }[] -} - -export function CreateWalletForm(props: CreateWalletFormProps) { - const { options } = props - const { classes } = useStyles() - const [selected, setSelected] = useState() - - const t = useDashboardI18N() - - return ( - - - - - - - - ) -} - -const Container = styled('div')` - display: flex; - flex-direction: column; -` - -const FormContainer = styled(FormControl)` - // TODO: mobile - width: 380px; -` diff --git a/packages/dashboard/src/components/CreateWalletFrame/index.tsx b/packages/dashboard/src/components/CreateWalletFrame/index.tsx deleted file mode 100644 index 7d686c863ae9..000000000000 --- a/packages/dashboard/src/components/CreateWalletFrame/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { memo } from 'react' -import { makeStyles, MaskColorVar } from '@masknet/theme' - -const useStyles = makeStyles()({ - root: { - padding: '3vw', - minHeight: '100vh', - backgroundColor: MaskColorVar.bottom, - display: 'flex', - flexDirection: 'column', - }, - container: { - backgroundColor: MaskColorVar.background, - borderRadius: 24, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - flex: 1, - }, -}) - -export interface CreateMaskWalletFrameProps extends React.PropsWithChildren<{}> {} - -export const CreateMaskWalletFrame = memo((props) => { - const { classes } = useStyles() - return ( -
-
{props.children}
-
- ) -}) diff --git a/packages/dashboard/src/components/DashboardFrame/Navigation.tsx b/packages/dashboard/src/components/DashboardFrame/Navigation.tsx deleted file mode 100644 index 1a0191d1a202..000000000000 --- a/packages/dashboard/src/components/DashboardFrame/Navigation.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { - List, - ListItem as MuiListItem, - ListItemText, - ListItemIcon, - Collapse, - Theme, - useMediaQuery, - styled, - listItemClasses, - listItemIconClasses, - ListItemProps, - listItemTextClasses, - useTheme, -} from '@mui/material' -import { ExpandLess, ExpandMore } from '@mui/icons-material' -import { useContext } from 'react' -import { useMatch, useNavigate } from 'react-router-dom' -import { DashboardContext } from './context' -import { - MaskBannerIcon, - MaskNotSquareIcon, - MenuPersonasActiveIcon, - MenuPersonasIcon, - MenuSettingsActiveIcon, - MenuSettingsIcon, - MenuWalletsActiveIcon, - MenuWalletsIcon, -} from '@masknet/icons' -import { useDashboardI18N } from '../../locales' -import { MaskColorVar } from '@masknet/theme' -import { DashboardRoutes } from '@masknet/shared-base' -import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra/web3' - -const ListItemLinkUnStyled = ({ to, ...props }: ListItemProps & { to: string }) => { - const navigate = useNavigate() - - return ( - { - navigate(to) - props.onClick?.(event) - }} - /> - ) -} - -const ListItemLink = styled(ListItemLinkUnStyled)(({ theme }) => { - return { - [`&.${listItemClasses.root}`]: { - color: theme.palette.mode === 'light' ? '' : 'rgba(255,255,255,.8)', - paddingLeft: theme.spacing(2), - cursor: 'pointer', - '&:hover': { - background: theme.palette.background.default, - }, - }, - [`&.${listItemClasses.selected}`]: { - color: MaskColorVar.textLink, - backgroundColor: theme.palette.background.default, - position: 'relative', - [listItemIconClasses.root]: { - color: MaskColorVar.textLink, - }, - '&:after': { - content: '""', - display: 'inline-block', - width: 5, - height: 40, - boxShadow: '-2px 0px 10px 2px rgba(0, 56, 255, 0.15)', - borderRadius: 50, - background: MaskColorVar.textLink, - position: 'absolute', - right: 0, - }, - }, - } -}) - -const LogoItem = styled(MuiListItem)(({ theme }) => ({ - [`&.${listItemClasses.root}`]: { - justifyContent: 'start', - marginBottom: theme.spacing(3.5), - }, -})) as any as typeof MuiListItem - -const ItemIcon = styled(ListItemIcon)(({ theme }) => ({ - '& svg': { - fontSize: 36, - }, -})) - -const ListSubTextItem = styled(ListItemText)(({ theme }) => ({ - [`&.${listItemTextClasses.inset}`]: { - marginLeft: theme.spacing(2), - '&:before': { - content: '""', - display: 'inline-block', - width: 4, - height: 4, - borderRadius: 2, - background: 'currentColor', - position: 'absolute', - left: theme.spacing(9), - top: 22, - }, - }, -})) - -export interface NavigationProps { - onClose?: () => void -} -export function Navigation({ onClose }: NavigationProps) { - const { expanded, toggleNavigationExpand } = useContext(DashboardContext) - const isWalletPath = useMatch(DashboardRoutes.Wallets) - const isWalletTransferPath = useMatch(DashboardRoutes.WalletsTransfer) - const isWalletHistoryPath = useMatch(DashboardRoutes.WalletsHistory) - - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const t = useDashboardI18N() - const mode = useTheme().palette.mode - const currentPluginId = useCurrentWeb3NetworkPluginID() - - const onExpand = (e: React.MouseEvent) => { - e.stopPropagation() - toggleNavigationExpand() - } - - return ( - onClose?.()}> - {isLargeScreen && {mode === 'dark' ? : }} - - - {useMatch(DashboardRoutes.Personas) ? : } - - - - - - {isWalletPath || isWalletHistoryPath || isWalletTransferPath ? ( - - ) : ( - - )} - - {t.wallets()} - {expanded ? : } - - - - - - - {currentPluginId === NetworkPluginID.PLUGIN_EVM && ( - - - - )} - {currentPluginId === NetworkPluginID.PLUGIN_EVM && ( - - - - )} - - - - - {useMatch(DashboardRoutes.Settings) ? : } - - - - - ) -} diff --git a/packages/dashboard/src/components/DashboardFrame/context.ts b/packages/dashboard/src/components/DashboardFrame/context.ts deleted file mode 100644 index d80d0534a187..000000000000 --- a/packages/dashboard/src/components/DashboardFrame/context.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createContext } from 'react' - -export interface DashboardState { - expanded: boolean - drawerOpen: boolean - toggleNavigationExpand: () => void - toggleDrawer: () => void -} -export const DashboardContextDefault = { - expanded: true, - drawerOpen: false, - toggleDrawer: () => {}, - toggleNavigationExpand: () => {}, -} - -export const DashboardContext = createContext(DashboardContextDefault) diff --git a/packages/dashboard/src/components/DashboardFrame/index.tsx b/packages/dashboard/src/components/DashboardFrame/index.tsx deleted file mode 100644 index fd897707952f..000000000000 --- a/packages/dashboard/src/components/DashboardFrame/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { memo, Suspense, useMemo, useState } from 'react' -import { Grid, styled, Theme, useMediaQuery } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { DashboardContext } from './context' -import { Navigation } from './Navigation' -import { NavigationVersionFooter } from '../NavigationVersionFooter' - -const Root = styled(Grid)(({ theme }) => ({ - backgroundColor: MaskColorVar.primaryBackground, -})) - -const LeftContainer = styled(Grid)(({ theme }) => ({ - height: '100vh', - [theme.breakpoints.up('lg')]: { - // Just meet the design size - minWidth: 232, - }, - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - paddingBottom: '22px', -})) - -export interface DashboardFrameProps extends React.PropsWithChildren<{}> {} - -export const DashboardFrame = memo((props: DashboardFrameProps) => { - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const [navigationExpanded, setNavigationExpanded] = useState(true) - const [drawerOpen, setDrawerOpen] = useState(false) - - const context = useMemo( - () => ({ - drawerOpen, - expanded: navigationExpanded, - toggleNavigationExpand: () => setNavigationExpanded((e) => !e), - toggleDrawer: () => setDrawerOpen((e) => !e), - }), - [drawerOpen, navigationExpanded], - ) - - return ( - - - {isLargeScreen && ( - - - - - )} - - - {props.children} - - - - - ) -}) diff --git a/packages/dashboard/src/components/FileUpload/index.tsx b/packages/dashboard/src/components/FileUpload/index.tsx deleted file mode 100644 index 0c1117013e65..000000000000 --- a/packages/dashboard/src/components/FileUpload/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { MaskColorVar, makeStyles } from '@masknet/theme' -import { Card, Typography } from '@mui/material' -import { ReactNode, useEffect, useState } from 'react' -import { File as FileIcon } from '@masknet/icons' -import { blobToText } from '@dimensiondev/kit' - -const useStyles = makeStyles()({ - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - position: 'relative', - height: '100%', - borderRadius: 8, - }, - container: { - textAlign: 'center', - }, - file: { - position: 'absolute', - width: '100%', - height: '100%', - top: 0, - left: 0, - opacity: 0, - cursor: 'pointer', - }, - text: { - color: MaskColorVar.textSecondary, - fontSize: 13, - }, -}) - -export interface FileUploadProps { - width?: number - height?: number - readAsText?: boolean - onChange: (file: File, content?: string) => void - accept?: string - icon?: ReactNode -} - -export default function FileUpload({ width, height, readAsText, onChange, accept, icon }: FileUploadProps) { - const { classes } = useStyles() - const [file, setFile] = useState() - - const handleChange = ({ target }: React.ChangeEvent) => { - if (target.files) { - setFile(target.files[0]) - } - } - - useEffect(() => { - if (file) { - if (readAsText) { - blobToText(file).then((result) => onChange(file, result)) - } else { - onChange(file) - } - } - }, [file, readAsText, onChange]) - return ( - -
- {icon ?? } - Please click or drag the file to here -
- -
- ) -} diff --git a/packages/dashboard/src/components/FooterLine/About.tsx b/packages/dashboard/src/components/FooterLine/About.tsx deleted file mode 100644 index 4a4bb92a05e6..000000000000 --- a/packages/dashboard/src/components/FooterLine/About.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { DiscordIcon, MaskBlueIcon, MaskGreyIcon, MaskTextIcon, MaskTextNightlyIcon } from '@masknet/icons' -import { Avatar, IconButton, Link, Typography } from '@mui/material' -import { makeStyles, getMaskColor } from '@masknet/theme' -import FacebookIcon from '@mui/icons-material/Facebook' -import GitHubIcon from '@mui/icons-material/GitHub' -import TelegramIcon from '@mui/icons-material/Telegram' -import TwitterIcon from '@mui/icons-material/Twitter' -import { useDashboardI18N } from '../../locales' -import { styled } from '@mui/material/styles' -import { Version } from './Version' -import links from './links.json' -import { ABOUT_DIALOG_BACKGROUND } from '../../assets' - -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`, - }, - maskface: { - width: 120, - height: 120, - marginTop: 75, - }, - version: { - color: '#FFF', - fontSize: 12, - marginBottom: 20, - }, - main: { - fontSize: 16, - textAlign: 'center', - margin: '24px 68px', - - '& > p': { - marginBottom: '36px', - }, - }, - getInTouch: { - fontSize: 16, - fontWeight: 'bold', - marginTop: 20, - marginBottom: 28, - }, - icon: { - color: theme.palette.text.primary, - }, - close: { - color: '#FFF', - }, - 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 IconContainer = styled('div')(` - width: 100%; - height: 100%; - - & > svg { - width: 100%; - height: 100%; - } -`) - -const TextIconContainer = styled('div')( - ({ theme }) => ` - padding: ${theme.spacing(1)} 0; - width: 100px; - - & > svg { - width: 100%; - height: 100%; - } -`, -) - -const brands: Record = { - 'https://www.facebook.com/masknetwork': , - 'https://twitter.com/realMaskNetwork': , - 'https://github.com/DimensionDev/Maskbook': , - 'https://t.me/maskbook_group': , - 'https://discord.gg/4SVXvj7': , -} - -const MaskIcon = () => (process.env.NODE_ENV === 'production' ? : ) -const MaskTitleIcon = () => (process.env.NODE_ENV === 'production' ? : ) - -export function About() { - const { classes } = useStyles() - const t = useDashboardI18N() - 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/dashboard/src/components/FooterLine/Version.tsx b/packages/dashboard/src/components/FooterLine/Version.tsx deleted file mode 100644 index ef1ae999ca47..000000000000 --- a/packages/dashboard/src/components/FooterLine/Version.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useDashboardI18N } from '../../locales' -import { Typography } from '@mui/material' - -export const Version = ({ className }: { className?: string }) => { - const t = useDashboardI18N() - const version = globalThis.browser?.runtime.getManifest()?.version ?? process.env.TAG_NAME.slice(1) - - return ( - - {process.env.channel === 'stable' - ? t.version_of_stable({ version }) - : t.version_of_unstable({ - version, - build: process.env.channel ?? '', - hash: process.env.COMMIT_HASH ?? '', - })} - - ) -} diff --git a/packages/dashboard/src/components/FooterLine/index.tsx b/packages/dashboard/src/components/FooterLine/index.tsx deleted file mode 100644 index 2dcf9bde0f83..000000000000 --- a/packages/dashboard/src/components/FooterLine/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { styled, Breadcrumbs, Dialog, IconButton, Link, Typography } from '@mui/material' -import { useHref, useNavigate } from 'react-router-dom' -import { makeStyles, getMaskColor } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' -import { memo, useState } from 'react' -import { About } from './About' -import { Close } from '@mui/icons-material' -import { Version } from './Version' -import links from './links.json' -import { openWindow } from '@masknet/shared-base-ui' - -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 } - -function FooterLinkExternal(props: FooterLinkAnchorProps) { - const { classes } = useStyles() - return ( - - - {props.children} - - - ) -} - -type FooterLinkLinkProps = FooterLinkBaseProps & { to: string } -function FooterLinkTo(props: FooterLinkLinkProps) { - const { classes } = useStyles() - const href = useHref(props.to) - const navigate = useNavigate() - - return ( - navigate(href)} - href={href} - color="textPrimary" - className={classes.footerLink}> - - {props.children} - - - ) -} - -type FooterLinkAnchorButtonProps = FooterLinkBaseProps & { onClick(e: React.MouseEvent): void } -function FooterLinkButton(props: FooterLinkAnchorButtonProps) { - const { classes } = useStyles() - return ( - - - {props.children} - - - ) -} - -export const FooterLine = memo(() => { - const t = useDashboardI18N() - const { classes } = useStyles() - const [isOpen, setOpen] = useState(false) - const version = globalThis.browser?.runtime.getManifest()?.version ?? process.env.TAG_NAME.slice(1) - - const openVersionLink = (event: React.MouseEvent) => { - // `MouseEvent.prototype.metaKey` on macOS (`Command` key), Windows (`Windows` key), Linux (`Super` key) - if (process.env.channel === 'stable' && !event.metaKey) { - openWindow(`${links.DOWNLOAD_LINK_STABLE_PREFIX}/v${version}`) - } else { - openWindow(`${links.DOWNLOAD_LINK_UNSTABLE_PREFIX}/${process.env.COMMIT_HASH}`) - } - } - return ( - <> - - Mask.io - setOpen(true)}>{t.about()} - - - - {t.dashboard_mobile_test()} - {t.dashboard_source_code()} - {t.footer_bounty_list()} - {t.privacy_policy()} - - setOpen(false)}> - - setOpen(false)} - edge="end" - color="inherit"> - - - - - ) -}) diff --git a/packages/dashboard/src/components/FooterLine/links.json b/packages/dashboard/src/components/FooterLine/links.json deleted file mode 100644 index 374d0b1d292c..000000000000 --- a/packages/dashboard/src/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/dashboard/src/components/LoadingButton/index.tsx b/packages/dashboard/src/components/LoadingButton/index.tsx deleted file mode 100644 index 01882990b791..000000000000 --- a/packages/dashboard/src/components/LoadingButton/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { getMaskColor, makeStyles, MaskLoadingButton } from '@masknet/theme' -import { memo } from 'react' -import { LoadingAnimation } from '@masknet/shared' -import classNames from 'classnames' -import type { LoadingButtonProps } from '@mui/lab' - -interface DashboardLoadingButtonProps extends LoadingButtonProps { - onClick(event: React.MouseEvent): Promise -} -const useStyles = makeStyles()((theme) => ({ - icon: { - color: getMaskColor(theme).white, - width: '100%', - }, - loadingButtonOverride: { - opacity: '1 !important', - }, -})) - -export const LoadingButton = memo((props) => { - const { onClick, children, ...rest } = props - const { classes } = useStyles() - return ( - } - onClick={onClick} - {...rest}> - {children} - - ) -}) diff --git a/packages/dashboard/src/components/LoadingPlaceholder/index.tsx b/packages/dashboard/src/components/LoadingPlaceholder/index.tsx deleted file mode 100644 index 035ad4cac8be..000000000000 --- a/packages/dashboard/src/components/LoadingPlaceholder/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { memo } from 'react' -import { Box, Typography } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { LoadingAnimation } from '@masknet/shared' - -const useStyles = makeStyles()((theme) => ({ - container: { - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - prompt: { - color: MaskColorVar.textLight, - fontSize: theme.typography.pxToRem(12), - lineHeight: theme.typography.pxToRem(16), - marginTop: theme.spacing(2.5), - }, - icon: { - width: 30, - height: 30, - fill: MaskColorVar.primary, - }, -})) - -export const LoadingPlaceholder = memo(() => { - const { classes } = useStyles() - return ( - - - loading... - - ) -}) diff --git a/packages/dashboard/src/components/MaskAlert/index.tsx b/packages/dashboard/src/components/MaskAlert/index.tsx deleted file mode 100644 index ea0bd1aeec73..000000000000 --- a/packages/dashboard/src/components/MaskAlert/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { memo, useCallback, useState } from 'react' -import { Alert, alertClasses, Collapse, styled, IconButton } from '@mui/material' -import { Close as CloseIcon } from '@mui/icons-material' -import { getMaskColor, MaskColorVar } from '@masknet/theme' -import { InfoIcon, RiskIcon, SuccessIcon } from '@masknet/icons' - -const InfoAlert = styled(Alert)(({ theme }) => ({ - [`& > .${alertClasses.message}`]: { - display: 'flex', - alignItems: 'center', - lineHeight: '16px', - color: MaskColorVar.secondaryInfoText, - fontSize: theme.typography.caption.fontSize, - }, - [`& > .${alertClasses.icon}`]: { - alignItems: 'center', - }, - [`& > .${alertClasses.action}`]: { - alignItems: 'center', - }, - // standard - [`&.${alertClasses.standardInfo}`]: { - backgroundColor: getMaskColor(theme).infoBackground, - }, - [`&.${alertClasses.standardInfo} .${alertClasses.icon}`]: { - color: getMaskColor(theme).secondaryInfoText, - }, - [`&.${alertClasses.standardInfo} .${alertClasses.action}`]: { - color: getMaskColor(theme).secondaryInfoText, - }, - // error - [`&.${alertClasses.standardError}`]: { - backgroundColor: MaskColorVar.redMain.alpha(0.1), - }, - [`&.${alertClasses.standardError} .${alertClasses.icon}`]: { - color: getMaskColor(theme).redMain, - 'path:first-child': { - fill: MaskColorVar.redMain.alpha(0.5), - }, - path: { - fill: getMaskColor(theme).redMain, - }, - }, - [`&.${alertClasses.standardError} .${alertClasses.action}`]: { - color: getMaskColor(theme).redMain, - }, - [`&.${alertClasses.standardError} .${alertClasses.message}`]: { - color: getMaskColor(theme).redMain, - }, - // success - [`&.${alertClasses.standardSuccess}`]: { - backgroundColor: MaskColorVar.greenMain.alpha(0.1), - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.icon}`]: { - color: getMaskColor(theme).greenMain, - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.action}`]: { - color: getMaskColor(theme).greenMain, - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.message}`]: { - color: getMaskColor(theme).greenMain, - }, -})) - -export interface MaskAlertProps { - description: string - type?: 'error' | 'info' | 'success' | 'warning' -} - -const AlertIconMapping = { - error: , - info: , - success: , - warning: , -} - -export const MaskAlert = memo(({ description, type = 'info' }: MaskAlertProps) => { - const [openAlert, setOpenAlert] = useState(true) - - return ( - - setOpenAlert(false), [])}> - - - }> - {description} - - - ) -}) diff --git a/packages/dashboard/src/components/MaskAvatar/index.tsx b/packages/dashboard/src/components/MaskAvatar/index.tsx deleted file mode 100644 index 68d4bfc623bc..000000000000 --- a/packages/dashboard/src/components/MaskAvatar/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Avatar, Stack } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { MenuPersonasActiveIcon } from '@masknet/icons' -import { memo } from 'react' -import { usePersonaAvatar } from '../../pages/Personas/api' - -const useStyles = makeStyles()((theme) => ({ - author: { - fill: MaskColorVar.secondaryBackground, - cursor: 'pointer', - }, -})) - -interface MaskAvatarProps { - size?: number - onClick?(): void -} - -export const MaskAvatar = memo(({ size = 36, onClick }) => { - const { classes } = useStyles() - const avatar = usePersonaAvatar() - const commonProps = { - sx: { - width: size, - height: size, - display: 'inline-block', - background: MaskColorVar.lightBackground, - borderRadius: '50%', - }, - onClick, - className: classes.author, - } - - if (!avatar) { - return ( - - - - ) - } - - return -}) diff --git a/packages/dashboard/src/components/Mnemonic/DesktopMnemonicConfirm.tsx b/packages/dashboard/src/components/Mnemonic/DesktopMnemonicConfirm.tsx deleted file mode 100644 index b5a2a2982a80..000000000000 --- a/packages/dashboard/src/components/Mnemonic/DesktopMnemonicConfirm.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { MaskTextField } from '@masknet/theme' -import { Grid } from '@mui/material' -import { memo, useCallback } from 'react' -import { useDrop } from 'react-use' - -export interface DesktopMnemonicConfirmProps { - puzzleWords: string[] - indexes?: number[] - onChange(word: string, index: number): void - setAll?(words: string[]): void -} - -const parserPastingAllMnemonic = (text: string) => { - const result = [...text.matchAll(/([a-z])+/g)] - return result.length === 12 ? result : null -} - -export const DesktopMnemonicConfirm = memo((props: DesktopMnemonicConfirmProps) => { - 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 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/dashboard/src/components/Mnemonic/MnemonicReveal.tsx b/packages/dashboard/src/components/Mnemonic/MnemonicReveal.tsx deleted file mode 100644 index f105282e0ca3..000000000000 --- a/packages/dashboard/src/components/Mnemonic/MnemonicReveal.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { styled, Grid, Typography } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' - -const WordCard = styled(Typography)( - ({ theme }) => ` - padding: ${theme.spacing(1)}; - border-radius: 6px; - color: ${theme.palette.mode === 'dark' ? MaskColorVar.textPrimary : MaskColorVar.textLink}; - font-size: 14; - background-color: ${MaskColorVar.blue.alpha(0.1)}; - display: flex; - justify-content: center; - align-items: center; -`, -) - -export interface MnemonicRevealProps { - words: string[] - indexed?: boolean - wordClass?: string -} - -export function MnemonicReveal(props: MnemonicRevealProps) { - const { words, indexed, wordClass } = props - return ( - - {words.map((item, index) => ( - - - {indexed ? `${index + 1}. ` : ''} - {item} - - - ))} - - ) -} diff --git a/packages/dashboard/src/components/Mnemonic/index.tsx b/packages/dashboard/src/components/Mnemonic/index.tsx deleted file mode 100644 index 81171ecff851..000000000000 --- a/packages/dashboard/src/components/Mnemonic/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DesktopMnemonicConfirm' -export * from './MnemonicReveal' diff --git a/packages/dashboard/src/components/NavigationVersionFooter/index.tsx b/packages/dashboard/src/components/NavigationVersionFooter/index.tsx deleted file mode 100644 index 298af0747b8d..000000000000 --- a/packages/dashboard/src/components/NavigationVersionFooter/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { MaskColorVar } from '@masknet/theme' -import { styled } from '@mui/system' -import { memo } from 'react' -import { useDashboardI18N } from '../../locales' - -const VersionContainer = styled('div')(() => ({ - color: MaskColorVar.textSecondary, - textAlign: 'center', - fontSize: '12px', -})) - -export const NavigationVersionFooter = memo(() => { - const t = useDashboardI18N() - const version = globalThis.browser?.runtime.getManifest()?.version ?? process.env.TAG_NAME.slice(1) - return {version && t.version_of_stable({ version })} -}) diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/BuyETH.png b/packages/dashboard/src/components/PageFrame/FeaturePromotions/BuyETH.png deleted file mode 100644 index da8588d7cab9..000000000000 Binary files a/packages/dashboard/src/components/PageFrame/FeaturePromotions/BuyETH.png and /dev/null differ diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/FollowUs.png b/packages/dashboard/src/components/PageFrame/FeaturePromotions/FollowUs.png deleted file mode 100644 index 8d9ec75d17cf..000000000000 Binary files a/packages/dashboard/src/components/PageFrame/FeaturePromotions/FollowUs.png and /dev/null differ diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/ITO.png b/packages/dashboard/src/components/PageFrame/FeaturePromotions/ITO.png deleted file mode 100644 index 7bb878950af1..000000000000 Binary files a/packages/dashboard/src/components/PageFrame/FeaturePromotions/ITO.png and /dev/null differ diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/SendLuckyDrop.png b/packages/dashboard/src/components/PageFrame/FeaturePromotions/SendLuckyDrop.png deleted file mode 100644 index acca764d34f0..000000000000 Binary files a/packages/dashboard/src/components/PageFrame/FeaturePromotions/SendLuckyDrop.png and /dev/null differ diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx b/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx deleted file mode 100644 index 1dd049428263..000000000000 --- a/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { memo, useCallback, useMemo } from 'react' -import { useNavigate } from 'react-router-dom' -import { makeStyles } from '@masknet/theme' -import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { useAccount } from '@masknet/web3-shared-evm' -import { PluginMessages, Services } from '../../../API' -import { PersonaContext } from '../../../pages/Personas/hooks/usePersonaContext' -import { DashboardRoutes, EnhanceableSite } from '@masknet/shared-base' -import { PluginId } from '@masknet/plugin-infra' - -const useStyles = makeStyles()((theme) => ({ - container: { - padding: theme.spacing(3.125), - display: 'flex', - flexDirection: 'column', - '& > *': { - marginBottom: theme.spacing(2), - }, - [theme.breakpoints.down('md')]: { - display: 'none', - }, - }, - img: { - fill: 'none', - width: 250, - height: 140, - cursor: 'pointer', - }, -})) - -export const FeaturePromotions = memo(() => { - const { classes } = useStyles() - const navigate = useNavigate() - const account = useAccount() - - const { currentPersona, connectPersona } = PersonaContext.useContainer() - const { setDialog: setBuyDialog } = useRemoteControlledDialog(PluginMessages.Transak.buyTokenDialogUpdated) - - const isConnectedTwitter = useMemo(() => { - if (!currentPersona) return false - - const { linkedProfiles } = currentPersona - if (linkedProfiles.length === 0) return false - - return !!linkedProfiles.find((profile) => profile.identifier.network === EnhanceableSite.Twitter) - }, [currentPersona]) - - const openTransakDialog = useCallback(() => { - setBuyDialog({ - open: true, - address: account ?? '', - }) - }, []) - - const openTwitter = (pluginId: string) => async () => { - if (!currentPersona) { - navigate(DashboardRoutes.SignUp) - return - } - if (isConnectedTwitter) { - await Services.SocialNetwork.openSNSAndActivatePlugin('https://twitter.com/home', pluginId) - return - } - connectPersona(currentPersona.identifier, EnhanceableSite.Twitter) - } - - const openMaskNetwork = () => openWindow('https://twitter.com/realMaskNetwork') - - return ( -
- - - -
- ) -}) diff --git a/packages/dashboard/src/components/PageFrame/index.tsx b/packages/dashboard/src/components/PageFrame/index.tsx deleted file mode 100644 index ea442b3b07fe..000000000000 --- a/packages/dashboard/src/components/PageFrame/index.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { memo, useContext } from 'react' -import { useLocation } from 'react-router-dom' -import { - AppBar, - Box, - Drawer, - Grid, - IconButton, - paperClasses, - styled, - Theme, - Toolbar, - Typography, - useMediaQuery, - useTheme, -} from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { Close as CloseIcon, Menu as MenuIcon } from '@mui/icons-material' -import Color from 'color' -import { DashboardContext } from '../DashboardFrame/context' -import { Navigation } from '../DashboardFrame/Navigation' -import { MaskBannerIcon, MaskNotSquareIcon } from '@masknet/icons' -import { FeaturePromotions } from './FeaturePromotions' -import { DashboardRoutes } from '@masknet/shared-base' -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { NavigationVersionFooter } from '../NavigationVersionFooter' - -const featurePromotionsEnabled = [ - DashboardRoutes.Wallets, - DashboardRoutes.WalletsTransfer, - DashboardRoutes.WalletsHistory, -] - -const MaskLogo = styled(Grid)` - flex-basis: 212px; - max-width: 212px; - & > svg { - flex: 1; - } -` - -const MenuButton = styled(IconButton)(({ theme }) => ({ - paddingLeft: theme.spacing(1.5), - paddingRight: theme.spacing(1.5), -})) - -const PageTitle = styled(Grid)(({ theme }) => ({ - minHeight: 40, - alignItems: 'center', - paddingLeft: theme.spacing(4.25), - '& > h6': { - fontSize: '1.5rem', - }, - [theme.breakpoints.down('lg')]: { - flex: 1, - }, -})) - -const Containment = styled(Grid)(({ theme }) => ({ - maxWidth: '100%', - display: 'flex', - height: 'calc(100vh - 64px)', - overflow: 'hidden', -})) - -const NavigationDrawer = styled(Drawer)(({ theme }) => ({ - top: `${theme.mixins.toolbar.minHeight}px !important`, - // https://github.com/mui-org/material-ui/issues/20012#issuecomment-770654893 - [`& > .${paperClasses.root}`]: { - width: 232, - top: theme.mixins.toolbar.minHeight, - paddingTop: '28px', - background: new Color(theme.palette.background.paper).alpha(0.8).toString(), - backdropFilter: 'blur(4px)', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - paddingBottom: `calc( 22px + ${theme.mixins.toolbar.minHeight}px)`, - }, -})) - -const ShapeHelper = styled('div')(({ theme }) => ({ - padding: theme.spacing(3), - paddingBottom: 0, - borderTopLeftRadius: Number(theme.shape.borderRadius) * 5, - borderTopRightRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: theme.palette.mode === 'dark' ? '#1B1E38' : MaskColorVar.secondaryBackground, - display: 'flex', - flexDirection: 'column', - flex: 1, - overflow: 'auto', -})) - -const ContentContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - borderRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: 'transparent', - minHeight: '100%', - position: 'relative', - '&:after': { - content: '""', - display: 'block', - paddingTop: theme.spacing(3), - }, -})) - -const useStyle = makeStyles()((theme) => ({ - toolbarGutters: { - backgroundColor: MaskColorVar.primaryBackground, - [theme.breakpoints.up('lg')]: { - paddingLeft: theme.spacing(0), - }, - [theme.breakpoints.down('lg')]: { - paddingLeft: theme.spacing(1), - }, - }, - shapeContainerWithBackground: { - backgroundColor: theme.palette.background.paper, - }, -})) - -export interface PageFrameProps extends React.PropsWithChildren<{}> { - title: React.ReactNode | string - primaryAction?: React.ReactNode - noBackgroundFill?: boolean -} - -export const PageFrame = memo((props: PageFrameProps) => { - const location = useLocation() - const left = typeof props.title === 'string' ? {props.title} : props.title - const right = props.primaryAction - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const { drawerOpen, toggleDrawer } = useContext(DashboardContext) - const showFeaturePromotions = featurePromotionsEnabled.some((path: string) => path === location.pathname) - const mode = useTheme().palette.mode - const { classes } = useStyle() - - return ( - <> - - - {!isLargeScreen && ( - - - {drawerOpen ? : } - - {mode === 'dark' ? : } - - )} - - {left} - - {right} - - - - - {!isLargeScreen && ( - - - - - )} - - - {props.children} - - - {showFeaturePromotions ? : null} - - - ) -}) diff --git a/packages/dashboard/src/components/PasswordField/index.tsx b/packages/dashboard/src/components/PasswordField/index.tsx deleted file mode 100644 index 9a91330537b0..000000000000 --- a/packages/dashboard/src/components/PasswordField/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { MaskTextField, MaskTextFieldProps } from '@masknet/theme' -import { IconButton, InputAdornment } from '@mui/material' -import { ForwardedRef, useState, forwardRef } from 'react' -import { EyeIcon, EyeOffIcon } from '@masknet/icons' - -export type PasswordFieldProps = Exclude - -const PasswordField = forwardRef((props: PasswordFieldProps, ref: ForwardedRef) => { - const [showPassword, setShowPassword] = useState(false) - return ( - - setShowPassword(!showPassword)} - onMouseDown={(event) => event.preventDefault()} - edge="end" - size="small"> - {showPassword ? : } - - - ), - }} - /> - ) -}) - -export default PasswordField diff --git a/packages/dashboard/src/components/RegisterFrame/ButtonContainer.tsx b/packages/dashboard/src/components/RegisterFrame/ButtonContainer.tsx deleted file mode 100644 index 3a1132544ff2..000000000000 --- a/packages/dashboard/src/components/RegisterFrame/ButtonContainer.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { styled } from '@mui/material/styles' -import { buttonClasses, Stack } from '@mui/material' -import type { PropsWithChildren } from 'react' - -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 const ButtonContainer = ({ children }: ButtonGroupProps) => { - return ( - - {children} - - ) -} diff --git a/packages/dashboard/src/components/RegisterFrame/ColumnContentHeader.tsx b/packages/dashboard/src/components/RegisterFrame/ColumnContentHeader.tsx deleted file mode 100644 index 309452c9f9a7..000000000000 --- a/packages/dashboard/src/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')( - ({ theme }) => ` - 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', - }, -})) - -export 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}} - - ) -}) diff --git a/packages/dashboard/src/components/RegisterFrame/ColumnContentLayout.tsx b/packages/dashboard/src/components/RegisterFrame/ColumnContentLayout.tsx deleted file mode 100644 index ec79b6c20424..000000000000 --- a/packages/dashboard/src/components/RegisterFrame/ColumnContentLayout.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { RestoreBlueIcon, SignUpAccountIcon } from '@masknet/icons' -import { Box, Typography } from '@mui/material' -import { styled } from '@mui/material/styles' -import { memo } from 'react' -import { useDashboardI18N } from '../../locales' - -export const ColumnContentLayout = styled('div')` - display: flex; - flex-direction: column; - flex: 1; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; -` - -export const Body = styled('main')(({ theme }) => ({ - flex: '1 5', - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -export const Footer = styled('footer')(({ theme }) => ({ - flex: 1, - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -export const LogoBoxStyled = styled(Box)(({ theme }) => ({ - marginBottom: theme.spacing(10), - [theme.breakpoints.down('md')]: { - marginBottom: theme.spacing(2), - }, -})) - -export const SignUpAccountLogo = styled(SignUpAccountIcon)(({ theme }) => ({ - width: '100%', - height: '96px', -})) as any as typeof SignUpAccountIcon - -export const RestoreBlueLogo = styled(RestoreBlueIcon)(({ theme }) => ({ - width: '100%', - height: '96px', -})) as any as typeof RestoreBlueIcon - -interface PersonaLogoBoxProps {} - -export const PersonaLogoBox = memo>(({ children }) => { - const t = useDashboardI18N() - return ( - - {children} - - {t.persona()} - - - ) -}) diff --git a/packages/dashboard/src/components/RegisterFrame/ColumnLayout.tsx b/packages/dashboard/src/components/RegisterFrame/ColumnLayout.tsx deleted file mode 100644 index 776f1f67b42a..000000000000 --- a/packages/dashboard/src/components/RegisterFrame/ColumnLayout.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { styled, useTheme } from '@mui/material/styles' -import { FooterLine } from '../FooterLine' -import { Paper, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MaskBannerIcon, MaskNotSquareIcon } from '@masknet/icons' - -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 const ColumnLayout = ({ haveFooter = true, children }: ColumnLayoutProps) => { - const { classes } = useStyles() - const mode = useTheme().palette.mode - - return ( - - - - {mode === 'dark' ? : } - {children} - - {haveFooter && } - - - ) -} diff --git a/packages/dashboard/src/components/RegisterFrame/RowLayout.tsx b/packages/dashboard/src/components/RegisterFrame/RowLayout.tsx deleted file mode 100644 index 5a87d896992b..000000000000 --- a/packages/dashboard/src/components/RegisterFrame/RowLayout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { MaskBannerIcon } from '@masknet/icons' -import { styled } from '@mui/material/styles' -import { memo } from 'react' -import { Container } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' - -const LayoutContainer = styled('div')( - ({ theme }) => ` - display: flex; - position: absolute; - height: 100%; - width: 100%; - background: ${MaskColorVar.primaryBackground} -`, -) - -const LeftSide = styled('div')(({ theme }) => ({ - padding: theme.spacing(5), - width: '30%', - maxWidth: '400px', - background: theme.palette.primary.main, - [theme.breakpoints.down('md')]: { - width: '25%', - padding: theme.spacing(3), - }, - [theme.breakpoints.down('sm')]: { - display: 'none', - }, -})) - -const RightContent = styled('div')( - ({ theme }) => ` - flex: 1; - display: flex; - justify-content: center; - max-height: 100%; - overflow: auto; - background: transparent; -`, -) - -interface RowLayoutProps extends React.PropsWithChildren<{}> {} - -export const RowLayout = memo(({ children }: RowLayoutProps) => { - return ( - - - - - - {children} - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/BackupInfoCard.tsx b/packages/dashboard/src/components/Restore/BackupInfoCard.tsx deleted file mode 100644 index 285909a04036..000000000000 --- a/packages/dashboard/src/components/Restore/BackupInfoCard.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { memo } from 'react' -import { Box, Card, Grid, Stack, Typography } from '@mui/material' -import formatDateTime from 'date-fns/format' -import fromUnixTime from 'date-fns/fromUnixTime' -import type { BackupFileInfo } from '../../pages/Settings/type' -import Tooltip from '@mui/material/Tooltip' -import { formatFileSize } from '@dimensiondev/kit' - -interface BackupInfoProps { - info: BackupFileInfo -} - -export const BackupInfoCard = memo(({ info }: BackupInfoProps) => { - return ( - - - - - {economizeAbstract(info.abstract)} - - {formatDateTime(fromUnixTime(info.uploadedAt), 'yyyy-MM-dd HH:mm')} - - - - - - - {formatFileSize(info.size, false)} - - - - - - ) -}) - -function economizeAbstract(input: string) { - if (!input.length) return
error
- if (input.length < 30) return
{input}
- return ( - -
- {input.slice(0, 30)}...({input.split(',').length}) -
-
- ) -} diff --git a/packages/dashboard/src/components/Restore/CodeValidation.tsx b/packages/dashboard/src/components/Restore/CodeValidation.tsx deleted file mode 100644 index 1c919c0c2387..000000000000 --- a/packages/dashboard/src/components/Restore/CodeValidation.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { memo } from 'react' -import { fetchDownloadLink } from '../../pages/Settings/api' -import { useAsyncFn } from 'react-use' -import { Step, Stepper } from '../Stepper' -import { ConfirmBackupInfo } from './steps/ConfirmBackupInfo' -import { EmailField } from './steps/EmailField' -import { PhoneField } from './steps/PhoneField' -import { LoadingCard } from './steps/LoadingCard' -import { ValidationAccount } from './steps/ValidationAccount' -import { ValidationCodeStep } from './steps/common' -import type { AccountType } from '../../pages/Settings/type' - -interface CodeValidationProps { - onValidated(downloadLink: string, account: string, password: string, type: AccountType): Promise -} - -export const CodeValidation = memo(({ onValidated }: CodeValidationProps) => { - const [{ loading: fetchingBackupInfo }, fetchDownloadLinkFn] = useAsyncFn( - async (account: string, type: AccountType, code: string) => { - return fetchDownloadLink({ code, account, type }) - }, - [], - ) - - return ( - , trigger: fetchingBackupInfo }}> - {(toStep) => } - {(toStep) => } - - {(toStep, { account, type }) => ( - - )} - - - {(toStep, { backupInfo, account, type }) => ( - onValidated(backupInfo.downloadURL, account, password, type)} - /> - )} - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/ConfirmSynchronizePasswordDialog.tsx b/packages/dashboard/src/components/Restore/ConfirmSynchronizePasswordDialog.tsx deleted file mode 100644 index eb48e176277f..000000000000 --- a/packages/dashboard/src/components/Restore/ConfirmSynchronizePasswordDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useDashboardI18N } from '../../locales' -import { memo } from 'react' -import { getMaskColor, MaskDialog } from '@masknet/theme' -import { Button, DialogActions, DialogContent, Stack, Typography } from '@mui/material' -import { SuccessIcon } from '@masknet/icons' - -export interface ConfirmSynchronizePasswordDialogProps { - open: boolean - onClose(): void - onConform(): void -} - -export const ConfirmSynchronizePasswordDialog = memo( - ({ open, onClose, onConform }) => { - const t = useDashboardI18N() - - return ( - - - - - getMaskColor(t).greenMain }} fontSize={24}> - {t.successful()} - - - - {t.sign_in_account_cloud_backup_synchronize_password_tip()} - - - - - - - - ) - }, -) diff --git a/packages/dashboard/src/components/Restore/RestoreFromCloud.tsx b/packages/dashboard/src/components/Restore/RestoreFromCloud.tsx deleted file mode 100644 index f810606257b4..000000000000 --- a/packages/dashboard/src/components/Restore/RestoreFromCloud.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { useDashboardI18N } from '../../locales' -import { Box } from '@mui/material' -import { MaskAlert } from '../MaskAlert' -import { CodeValidation } from './CodeValidation' -import { fetchBackupValue } from '../../pages/Settings/api' -import { Messages, Services } from '../../API' -import BackupPreviewCard from '../../pages/Settings/components/BackupPreviewCard' -import { ButtonContainer } from '../RegisterFrame/ButtonContainer' -import { useCustomSnackbar } from '@masknet/theme' -import { useAsyncFn } from 'react-use' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { Step, Stepper } from '../Stepper' -import { LoadingCard } from './steps/LoadingCard' -import { BackupPreview, decryptBackup } from '@masknet/backup-format' -import { decode, encode } from '@msgpack/msgpack' -import { PersonaContext } from '../../pages/Personas/hooks/usePersonaContext' -import { AccountType } from '../../pages/Settings/type' -import { UserContext } from '../../pages/Settings/hooks/UserContext' -import { ConfirmSynchronizePasswordDialog } from './ConfirmSynchronizePasswordDialog' -import { LoadingButton } from '../LoadingButton' - -export const RestoreFromCloud = memo(() => { - const t = useDashboardI18N() - const navigate = useNavigate() - const { showSnackbar } = useCustomSnackbar() - const { user, updateUser } = useContext(UserContext) - const { currentPersona, changeCurrentPersona } = PersonaContext.useContainer() - - const [account, setAccount] = useState(null) - const [backupId, setBackupId] = useState('') - const [openSynchronizePasswordDialog, toggleSynchronizePasswordDialog] = useState(false) - const [step, setStep] = useState<{ name: string; params: any }>({ name: 'validate', params: null }) - - const [{ loading: fetchingBackupValue, error: fetchBackupValueError }, fetchBackupValueFn] = useAsyncFn( - async (downloadLink) => fetchBackupValue(downloadLink), - [], - ) - - const [{ loading: decryptingBackup }, decryptBackupFn] = useAsyncFn( - async (account: string, password: string, encryptedValue: ArrayBuffer) => { - try { - const decrypted = await decryptBackup(encode(account + password), encryptedValue) - return JSON.stringify(decode(decrypted)) - } catch { - return null - } - }, - [], - ) - - useEffect(() => { - if (!fetchBackupValueError) return - showSnackbar(t.sign_in_account_cloud_backup_download_failed(), { variant: 'error' }) - }, [fetchBackupValueError]) - - const onValidated = useCallback( - async (downloadLink: string, accountValue: string, password: string, type: AccountType) => { - const backupEncrypted = await fetchBackupValueFn(downloadLink) - const backupDecrypted = await decryptBackupFn(accountValue, password, backupEncrypted) - - if (!backupDecrypted) { - return t.sign_in_account_cloud_backup_decrypt_failed() - } - - const backupNormalized = await Services.Backup.addUnconfirmedBackup(backupDecrypted) - if (backupNormalized.err) return t.sign_in_account_cloud_backup_decrypt_failed() - - const { id, info } = backupNormalized.val - setBackupId(id) - setAccount({ type, value: accountValue, password }) - setStep({ - name: 'restore', - params: { - backupJson: info, - handleRestore: () => onRestore(backupNormalized.val), - }, - }) - return null - }, - [], - ) - - const restoreCallback = useCallback(async () => { - if (!currentPersona) { - const lastedPersona = await Services.Identity.queryLastPersonaCreated() - if (lastedPersona) { - await changeCurrentPersona(lastedPersona) - } - } - if (account) { - if (!user.email && account.type === AccountType.email) { - updateUser({ email: account.value }) - } - if (!user.phone && account.type === AccountType.phone) { - updateUser({ phone: account.value }) - } - } - toggleSynchronizePasswordDialog(true) - }, [currentPersona, account, user, toggleSynchronizePasswordDialog]) - - const onRestore = useCallback( - async (backupInfo: { info: BackupPreview; id: string }) => { - try { - if (backupInfo.info?.wallets) { - await Services.Backup.restoreUnconfirmedBackup({ id: backupInfo.id, action: 'wallet' }) - return - } else { - await Services.Backup.restoreUnconfirmedBackup({ id: backupInfo.id, action: 'confirm' }) - await restoreCallback() - } - } catch { - showSnackbar(t.sign_in_account_cloud_restore_failed(), { variant: 'error' }) - } - }, - [user], - ) - - const getTransition = useMemo(() => { - if (decryptingBackup) { - return { - render: , - trigger: decryptingBackup, - } - } - if (fetchingBackupValue) { - return { - render: , - trigger: true, - } - } - return undefined - }, [fetchingBackupValue, decryptingBackup]) - - const synchronizePassword = () => { - if (!account) return - updateUser({ backupPassword: account.password }) - onCloseSynchronizePassword() - } - - const onCloseSynchronizePassword = () => { - toggleSynchronizePasswordDialog(false) - navigate(DashboardRoutes.Personas, { replace: true }) - } - - useEffect(() => { - return Messages.events.restoreSuccess.on(restoreCallback) - }, [restoreCallback]) - - return ( - <> - - - {() => ( - - - - )} - - - {(_, { backupJson: backupBasicInfoJson, handleRestore: handleRestore }) => ( - <> - - - - - - {t.restore()} - - - - )} - - - {openSynchronizePasswordDialog && ( - onCloseSynchronizePassword()} - onConform={synchronizePassword} - /> - )} - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/RestoreFromLocal.tsx b/packages/dashboard/src/components/Restore/RestoreFromLocal.tsx deleted file mode 100644 index 6c5faf077f40..000000000000 --- a/packages/dashboard/src/components/Restore/RestoreFromLocal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { memo, useCallback, useEffect, useState } from 'react' -import { useAsync } from 'react-use' -import { Box, Card } from '@mui/material' -import type { BackupPreview } from '@masknet/backup-format' -import { useDashboardI18N } from '../../locales' -import { Messages, Services } from '../../API' -import BackupPreviewCard from '../../pages/Settings/components/BackupPreviewCard' -import { MaskAlert } from '../MaskAlert' -import FileUpload from '../FileUpload' -import { ButtonContainer } from '../RegisterFrame/ButtonContainer' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { blobToText } from '@dimensiondev/kit' -import { LoadingCard } from './steps/LoadingCard' -import { decryptBackup } from '@masknet/backup-format' -import { decode, encode } from '@msgpack/msgpack' -import { PersonaContext } from '../../pages/Personas/hooks/usePersonaContext' -import { LoadingButton } from '../LoadingButton' -import PasswordField from '../PasswordField' -import { useCustomSnackbar } from '@masknet/theme' - -enum RestoreStatus { - WaitingInput = 0, - Verifying = 1, - Verified = 2, - Decrypting = 3, -} - -const supportedFileType = { - json: 'application/json', - octetStream: 'application/octet-stream', - macBinary: 'application/macbinary', -} - -export const RestoreFromLocal = memo(() => { - const t = useDashboardI18N() - const navigate = useNavigate() - const { showSnackbar } = useCustomSnackbar() - const { currentPersona, changeCurrentPersona } = PersonaContext.useContainer() - - const [file, setFile] = useState(null) - const [json, setJSON] = useState(null) - const [backupValue, setBackupValue] = useState('') - const [backupId, setBackupId] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState('') - const [restoreStatus, setRestoreStatus] = useState(RestoreStatus.WaitingInput) - - const handleSetFile = useCallback(async (file: File) => { - setFile(file) - if (file.type === supportedFileType.json) { - const content = await blobToText(file) - setBackupValue(content) - } else if ([supportedFileType.octetStream, supportedFileType.macBinary].includes(file.type)) { - setRestoreStatus(RestoreStatus.Decrypting) - } else { - showSnackbar(t.sign_in_account_cloud_backup_not_support(), { variant: 'error' }) - } - }, []) - - useAsync(async () => { - if (!backupValue) return - - setRestoreStatus(RestoreStatus.Verifying) - const backupInfo = await Services.Backup.addUnconfirmedBackup(backupValue) - if (backupInfo.ok) { - setJSON(backupInfo.val.info) - setBackupId(backupInfo.val.id) - 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 - - try { - const decrypted = await decryptBackup(encode(password), await file.arrayBuffer()) - setBackupValue(JSON.stringify(decode(decrypted))) - } catch (error_) { - setError(t.sign_in_account_cloud_backup_decrypt_failed()) - } - }, [file, password]) - - const restoreCallback = useCallback(async () => { - if (!currentPersona) { - const lastedPersona = await Services.Identity.queryLastPersonaCreated() - if (lastedPersona) { - await changeCurrentPersona(lastedPersona) - } - } - navigate(DashboardRoutes.Personas, { replace: true }) - }, [currentPersona, changeCurrentPersona]) - - const restoreDB = useCallback(async () => { - try { - // If json has wallets, restore in popup. - if (json?.wallets) { - await Services.Backup.restoreUnconfirmedBackup({ id: backupId, action: 'wallet' }) - return - } else { - await Services.Backup.restoreUnconfirmedBackup({ id: backupId, action: 'confirm' }) - - await restoreCallback() - } - } catch { - showSnackbar(t.sign_in_account_cloud_backup_failed(), { variant: 'error' }) - } - }, [backupId, json]) - - useEffect(() => { - return Messages.events.restoreSuccess.on(restoreCallback) - }, [restoreCallback]) - - return ( - <> - - {restoreStatus === RestoreStatus.Verifying && } - {restoreStatus === RestoreStatus.WaitingInput && ( - - - - )} - {restoreStatus === RestoreStatus.Verified && json && } - {restoreStatus === RestoreStatus.Decrypting && ( - - setPassword(e.currentTarget.value)} - error={!!error} - helperText={error} - /> - - )} - - - - {restoreStatus !== RestoreStatus.Verified ? t.next() : t.restore()} - - - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/RestoreFromMnemonic.tsx b/packages/dashboard/src/components/Restore/RestoreFromMnemonic.tsx deleted file mode 100644 index 7359f08b3f70..000000000000 --- a/packages/dashboard/src/components/Restore/RestoreFromMnemonic.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { DesktopMnemonicConfirm } from '../Mnemonic' -import { useList } from 'react-use' -import { Box, Typography } from '@mui/material' -import { getMaskColor, makeStyles } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' -import { some } from 'lodash-unified' -import { MaskAlert } from '../MaskAlert' -import { ButtonContainer } from '../RegisterFrame/ButtonContainer' -import { Services } from '../../API' -import { PersonaContext } from '../../pages/Personas/hooks/usePersonaContext' -import { DashboardRoutes } from '@masknet/shared-base' -import { useNavigate } from 'react-router-dom' -import { useState } from 'react' -import { SignUpRoutePath } from '../../pages/SignUp/routePath' -import { LoadingButton } from '../LoadingButton' -import { delay } from '@dimensiondev/kit' - -const useStyles = makeStyles()((theme) => ({ - error: { - marginTop: theme.spacing(1), - paddingLeft: theme.spacing(1), - color: getMaskColor(theme).redMain, - borderLeft: `2px solid ${getMaskColor(theme).redMain}`, - }, -})) - -export const RestoreFromMnemonic = () => { - const navigate = useNavigate() - const { classes } = useStyles() - const [error, setError] = useState('') - const { changeCurrentPersona } = PersonaContext.useContainer() - const t = useDashboardI18N() - const [values, { updateAt, set: setMnemonic }] = useList(Array.from({ length: 12 }).fill('')) - - const handleImport = async () => { - try { - const persona = await Services.Identity.queryPersonaByMnemonic(values.join(' '), '') - if (persona) { - await changeCurrentPersona(persona) - // Waiting persona changed event notify - await delay(100) - navigate(DashboardRoutes.Personas, { replace: true }) - } else { - navigate(`${DashboardRoutes.SignUp}/${SignUpRoutePath.PersonaRecovery}`, { - replace: false, - state: { mnemonic: values }, - }) - } - } catch { - setError(t.sign_in_account_mnemonic_confirm_failed()) - } - } - - return ( - <> - - { - updateAt(index, word) - setError('') - }} - puzzleWords={values} - setAll={setMnemonic} - /> - {error && ( - - {error} - - )} - - - !value)}> - {t.confirm()} - - - - - - - ) -} diff --git a/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx b/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx deleted file mode 100644 index c66743e742f3..000000000000 --- a/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { memo } from 'react' -import { MaskTextField } from '@masknet/theme' -import { Box, Button } from '@mui/material' -import { useDashboardI18N } from '../../locales' -import { MaskAlert } from '../MaskAlert' -import { ButtonContainer } from '../RegisterFrame/ButtonContainer' -import { SubmitHandler, useForm, Controller } from 'react-hook-form' -import { Services } from '../../API' -import { PersonaContext } from '../../pages/Personas/hooks/usePersonaContext' -import { zodResolver } from '@hookform/resolvers/zod' -import { z } from 'zod' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { SignUpRoutePath } from '../../pages/SignUp/routePath' -import { delay } from '@dimensiondev/kit' -type FormInputs = { - privateKey: string -} - -export const RestoreFromPrivateKey = memo(() => { - const navigate = useNavigate() - const t = useDashboardI18N() - const { changeCurrentPersona } = PersonaContext.useContainer() - - const schema = z.object({ - privateKey: z.string(), - }) - - const { - control, - handleSubmit, - setError, - formState: { errors, isSubmitting, isDirty }, - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { - privateKey: '', - }, - }) - - const onSubmit: SubmitHandler = async (data) => { - try { - const persona = await Services.Identity.loginExistPersonaByPrivateKey(data.privateKey) - if (persona) { - await changeCurrentPersona(persona) - // Waiting persona changed event notify - await delay(100) - navigate(DashboardRoutes.Personas) - } else { - navigate(`${DashboardRoutes.SignUp}/${SignUpRoutePath.PersonaRecovery}`, { - replace: false, - state: { privateKey: data.privateKey }, - }) - } - } catch { - setError('privateKey', { type: 'value', message: t.sign_in_account_private_key_error() }) - } - } - - return ( - <> - -
- - ( - - )} - name="privateKey" - /> - - - - -
-
- - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/index.tsx b/packages/dashboard/src/components/Restore/index.tsx deleted file mode 100644 index b43e44a4c05a..000000000000 --- a/packages/dashboard/src/components/Restore/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { memo } from 'react' -import { Tab } from '@mui/material' -import { makeStyles, ButtonGroupTabList, useTabs } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' -import { RestoreFromLocal } from './RestoreFromLocal' -import { RestoreFromMnemonic } from './RestoreFromMnemonic' -import { RestoreFromCloud } from './RestoreFromCloud' -import { RestoreFromPrivateKey } from './RestoreFromPrivateKey' -import { PersonaLogoBox, RestoreBlueLogo, SignUpAccountLogo } from '../RegisterFrame/ColumnContentLayout' -import { TabContext, TabPanel } from '@mui/lab' - -const useStyles = makeStyles()((theme) => ({ - tabs: { - width: '100%', - display: 'flex', - marginBottom: theme.spacing(4), - }, - panels: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: 0, - width: '100%', - }, -})) - -export const Restore = memo(() => { - const t = useDashboardI18N() - const { classes } = useStyles() - const [currentTab, onChange, tabs] = useTabs('mnemonic', 'privateKey', 'local', 'cloud') - const tabPanelClasses = { root: classes.panels } - - return ( - <> - - {['mnemonic', 'privateKey'].includes(currentTab) ? : } - - - - - - - - - - - - - - - - - - - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/steps/ConfirmBackupInfo.tsx b/packages/dashboard/src/components/Restore/steps/ConfirmBackupInfo.tsx deleted file mode 100644 index e6c9cea50b83..000000000000 --- a/packages/dashboard/src/components/Restore/steps/ConfirmBackupInfo.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useDashboardI18N } from '../../../locales' -import { memo, useState } from 'react' -import { Box, Button } from '@mui/material' -import { BackupInfoCard } from '../BackupInfoCard' -import { ButtonContainer } from '../../RegisterFrame/ButtonContainer' -import type { StepCommonProps } from '../../Stepper' -import type { BackupFileInfo } from '../../../pages/Settings/type' -import PasswordField from '../../PasswordField' - -interface ConfirmBackupInfoProps extends StepCommonProps { - backupInfo: BackupFileInfo - onNext(password: string): Promise -} - -export const ConfirmBackupInfo = memo(({ backupInfo, onNext }: ConfirmBackupInfoProps) => { - const t = useDashboardI18N() - const [password, setPassword] = useState('') - const [errorMessage, setErrorMessage] = useState('') - - const handleNext = async () => { - const result = await onNext(password) - if (result) { - setErrorMessage(result) - } - } - - if (!backupInfo) return null - - return ( - <> - - - - setPassword(e.currentTarget.value)} - error={!!errorMessage} - helperText={errorMessage} - /> - - - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/steps/EmailField.tsx b/packages/dashboard/src/components/Restore/steps/EmailField.tsx deleted file mode 100644 index d64a37ce4f34..000000000000 --- a/packages/dashboard/src/components/Restore/steps/EmailField.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useDashboardI18N } from '../../../locales' -import { memo, useState } from 'react' -import { MaskTextField } from '@masknet/theme' -import { ButtonContainer } from '../../RegisterFrame/ButtonContainer' -import { Button } from '@mui/material' -import { Label, ValidationCodeStep } from './common' -import type { StepCommonProps } from '../../Stepper' -import { AccountType } from '../../../pages/Settings/type' -import { emailRegexp } from '../../../pages/Settings/regexp' - -export const EmailField = memo(({ toStep }: StepCommonProps) => { - const t = useDashboardI18N() - const [account, setAccount] = useState('') - const [invalidEmail, setInvalidEmail] = useState(false) - - const validCheck = () => { - if (!account) return - - const isValid = emailRegexp.test(account) - setInvalidEmail(!isValid) - } - - return ( - <> - toStep(ValidationCodeStep.PhoneInput)} mode={AccountType.email} />} - fullWidth - value={account} - onBlur={validCheck} - onChange={(event) => setAccount(event.target.value)} - error={invalidEmail} - helperText={invalidEmail ? t.sign_in_account_cloud_backup_email_format_error() : ''} - type="email" - /> - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/steps/LoadingCard.tsx b/packages/dashboard/src/components/Restore/steps/LoadingCard.tsx deleted file mode 100644 index f5574685d08e..000000000000 --- a/packages/dashboard/src/components/Restore/steps/LoadingCard.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { LoadingIcon } from '@masknet/icons' -import { Card, Stack, Typography } from '@mui/material' -import { memo } from 'react' - -interface LoadingProps { - text?: string -} - -export const LoadingCard = memo(({ text = 'Loading' }: LoadingProps) => { - return ( - - - - - {text} - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/steps/PhoneField.tsx b/packages/dashboard/src/components/Restore/steps/PhoneField.tsx deleted file mode 100644 index a4c0db21f6ad..000000000000 --- a/packages/dashboard/src/components/Restore/steps/PhoneField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useDashboardI18N } from '../../../locales' -import { memo, useCallback, useState } from 'react' -import { PhoneNumberField } from '@masknet/theme' -import { ButtonContainer } from '../../RegisterFrame/ButtonContainer' -import { Button } from '@mui/material' -import { Label, ValidationCodeStep } from './common' -import type { StepCommonProps } from '../../Stepper' -import { AccountType } from '../../../pages/Settings/type' -import { phoneRegexp } from '../../../pages/Settings/regexp' - -export const PhoneField = memo(({ toStep }: StepCommonProps) => { - const t = useDashboardI18N() - const [account, setAccount] = useState('') - const [invalidPhone, setInvalidPhone] = useState(false) - - const validCheck = () => { - if (!account) return - - const isValid = phoneRegexp.test(account) - setInvalidPhone(!isValid) - } - - const handleClick = useCallback(() => { - if (!phoneRegexp.test(account)) return - toStep(ValidationCodeStep.AccountValidation, { account, type: AccountType.phone }) - }, [account]) - - return ( - <> - toStep(ValidationCodeStep.EmailInput)} mode={AccountType.phone} />} - onChange={({ country, phone }) => setAccount(country + ' ' + phone)} - error={invalidPhone ? t.sign_in_account_cloud_backup_phone_format_error() : ''} - value={{ - country: '+1', - phone: '', - }} - /> - - - - - ) -}) diff --git a/packages/dashboard/src/components/Restore/steps/ValidationAccount.tsx b/packages/dashboard/src/components/Restore/steps/ValidationAccount.tsx deleted file mode 100644 index fd804a4f495f..000000000000 --- a/packages/dashboard/src/components/Restore/steps/ValidationAccount.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useDashboardI18N } from '../../../locales' -import { useState } from 'react' -import { useAsyncFn } from 'react-use' -import { sendCode, useLanguage } from '../../../pages/Settings/api' -import { SendingCodeField, useCustomSnackbar } from '@masknet/theme' -import { Button, Typography } from '@mui/material' -import { ButtonContainer } from '../../RegisterFrame/ButtonContainer' -import type { StepCommonProps } from '../../Stepper' -import { ValidationCodeStep } from './common' -import { AccountType, BackupFileInfo, Locale, Scenario } from '../../../pages/Settings/type' - -interface ValidationAccountProps extends StepCommonProps { - account: string - type: AccountType - onNext(account: string, type: AccountType, code: string): Promise -} - -export const ValidationAccount = ({ account, toStep, type, onNext }: ValidationAccountProps) => { - const language = useLanguage() - const t = useDashboardI18N() - const { showSnackbar } = useCustomSnackbar() - - const [code, setCode] = useState('') - const [error, setError] = useState('') - - const [{ error: sendCodeError }, handleSendCodeFn] = useAsyncFn(async () => { - 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, type]) - - const handleNext = async () => { - const backupInfo = await onNext(account, type, code) - - if ((backupInfo as BackupFileInfo).downloadURL) { - setError('') - toStep(ValidationCodeStep.ConfirmBackupInfo, { backupInfo, account, type }) - } else { - setError((backupInfo as { message: string }).message) - } - } - - return ( - <> - - {t.sign_in_account_cloud_send_verification_code_tip()} {account} - - } - autoSend - onChange={(c) => setCode(c)} - errorMessage={sendCodeError?.message || error} - onSend={handleSendCodeFn} - /> - - - - - ) -} diff --git a/packages/dashboard/src/components/Restore/steps/common.tsx b/packages/dashboard/src/components/Restore/steps/common.tsx deleted file mode 100644 index 7b94d69bea06..000000000000 --- a/packages/dashboard/src/components/Restore/steps/common.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button, Stack, Typography } from '@mui/material' -import { memo } from 'react' -import { AccountType } from '../../../pages/Settings/type' - -interface LabelProps { - onModeChange(mode: AccountType): void - mode: AccountType -} - -export enum ValidationCodeStep { - EmailInput = 'EmailInput', - PhoneInput = 'PhoneInput', - AccountValidation = 'AccountValidation', - ConfirmBackupInfo = 'ConfirmBackupInfo', -} - -export const Label = memo(({ mode, onModeChange }: LabelProps) => { - return ( - - - {mode === 'email' ? 'Email' : 'Phone Number'} - - {mode === 'email' ? ( - - ) : ( - - )} - - ) -}) diff --git a/packages/dashboard/src/components/Stepper/index.tsx b/packages/dashboard/src/components/Stepper/index.tsx deleted file mode 100644 index 4d641d7370c4..000000000000 --- a/packages/dashboard/src/components/Stepper/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import type { ReactElement, ReactNode } from 'react' -import React, { Children, cloneElement, isValidElement, useEffect, useState } from 'react' -import { useMap } from 'react-use' -import { Box } from '@mui/material' -import { makeStyles } from '@masknet/theme' - -const useStyles = makeStyles()({ - hidden: { - position: 'absolute', - left: 10000, - }, -}) - -export interface StepCommonProps { - toStep: (stepName: string, params?: any) => void -} - -interface StepProps { - name: string - params?: any - toStep?: (stepName: string, callbackParams?: any) => void - children: (toNext: (stepName: string, callbackParams?: any) => void, params: any) => ReactNode -} - -export const Step = ({ children, toStep, params }: StepProps) => { - return <>{children(toStep!, params)} -} - -interface StepperProps { - defaultStep: string - step?: { - name: string - params?: any - } - transition?: { - render: ReactNode - trigger: boolean - } - children: ReactElement[] -} -export const Stepper = (props: StepperProps) => { - const { defaultStep, transition, step } = props - const { classes } = useStyles() - const [currentStep, setCurrentStep] = useState(defaultStep) - const [currentTransition, setCurrentTransition] = useState(transition?.render) - - const [steps, { set: setSteps }] = useMap<{ [key: string]: ReactElement }>() - const [stepParams, { set: setParam }] = useMap<{ [key: string]: any }>() - - const toStep = (stepName: string, params: any) => { - setCurrentStep(stepName) - setParam(stepName, params) - } - - useEffect(() => { - Children.forEach(props.children, (child: ReactElement) => { - if (!isValidElement(child)) return - const name = child.props.name - setSteps(name, child) - }) - }, []) - - useEffect(() => { - if (!transition) { - setCurrentTransition(null) - return - } - if (transition.trigger) { - setCurrentTransition(transition.render) - } else { - setCurrentTransition(null) - } - }, [transition]) - - useEffect(() => { - if (!step) return - toStep(step.name, step.params) - }, [step]) - - return ( - <> - <>{currentTransition} - <> - {steps[currentStep] ? ( - - {cloneElement(steps[currentStep], { toStep, params: stepParams[currentStep] })} - - ) : null} - - - ) -} diff --git a/packages/dashboard/src/components/WalletConnect/index.tsx b/packages/dashboard/src/components/WalletConnect/index.tsx deleted file mode 100644 index 944a3480fd12..000000000000 --- a/packages/dashboard/src/components/WalletConnect/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { memo } from 'react' -import { styled, Typography } from '@mui/material' -import { WalletQRCodeContainer } from '../WalletQRCodeContainer' -import { MaskColorVar } from '@masknet/theme' -import { useDashboardI18N } from '../../locales' - -const Container = styled('div')` - display: flex; - flex-direction: column; - align-items: center; -` - -const Tip = styled(Typography)(({ theme }) => ({ - textAlign: 'center', - color: MaskColorVar.textSecondary, - fontSize: theme.typography.body1.fontSize, - marginBottom: theme.spacing(5), -})) - -export const WalletConnect = memo(() => { - const t = useDashboardI18N() - return ( - - {t.wallets_wallet_connect_title()} - - - ) -}) diff --git a/packages/dashboard/src/components/WalletQRCodeContainer/index.tsx b/packages/dashboard/src/components/WalletQRCodeContainer/index.tsx deleted file mode 100644 index f6ba94217902..000000000000 --- a/packages/dashboard/src/components/WalletQRCodeContainer/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo } from 'react' -import { styled } from '@mui/material' - -const QRCodeContainer = styled('div')( - ({ width, height, border: { borderWidth, borderHeight } }: WalletQRCodeProps) => ` - width: ${width}px; - height: ${height}px; - background: linear-gradient(to right, black ${borderHeight}px, transparent ${borderHeight}px) 0 0, - linear-gradient(to right, black ${borderHeight}px, transparent ${borderHeight}px) 0 100%, - linear-gradient(to left, black ${borderHeight}px, transparent ${borderHeight}px) 100% 0, - linear-gradient(to left, black ${borderHeight}px, transparent ${borderHeight}px) 100% 100%, - linear-gradient(to bottom, black ${borderHeight}px, transparent ${borderHeight}px) 0 0, - linear-gradient(to bottom, black ${borderHeight}px, transparent ${borderHeight}px) 100% 0, - linear-gradient(to top, black ${borderHeight}px, transparent ${borderHeight}px) 0 100%, - linear-gradient(to top, black ${borderHeight}px, transparent ${borderHeight}px) 100% 100%; - - background-repeat: no-repeat; - background-size: ${borderWidth}px ${borderWidth}px; - padding: ${borderHeight}px; -`, -) - -export interface WalletQRCodeProps extends React.PropsWithChildren<{}> { - width: number - height: number - border: { - borderWidth: number - borderHeight: number - } -} - -export const WalletQRCodeContainer = memo((props: WalletQRCodeProps) => { - return {props.children} -}) diff --git a/packages/dashboard/src/entry.tsx b/packages/dashboard/src/entry.tsx deleted file mode 100644 index aa3250325223..000000000000 --- a/packages/dashboard/src/entry.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// This entry is for dashboard used as a package (required by the main extension). -// Should only expose necessary API for setup the Dashboard API correctly. - -export { IntegratedDashboard } from './Dashboard' -export { setService, setPluginMessages, setMessages, setPluginServices } from './API' -export { addDashboardI18N } from './locales/languages' diff --git a/packages/dashboard/src/env.d.ts b/packages/dashboard/src/env.d.ts deleted file mode 100644 index 7764462aeed7..000000000000 --- a/packages/dashboard/src/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// -/// -/// -/// - -// TODO: remove dependency on the following types. it blocks the publish of this package on it's own. -/// -/// diff --git a/packages/dashboard/src/hooks/useCreatePersonaV2.ts b/packages/dashboard/src/hooks/useCreatePersonaV2.ts deleted file mode 100644 index 0c5fe164e417..000000000000 --- a/packages/dashboard/src/hooks/useCreatePersonaV2.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { delay } from '@dimensiondev/kit' -import { Messages, Services } from '../API' - -export function useCreatePersonaV2() { - return async (mnemonicWord: string, nickName: string) => { - const identifier = await Services.Identity.createPersonaByMnemonicV2(mnemonicWord, nickName, '') - await delay(300) - Messages.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) - Messages.events.ownPersonaChanged.sendToAll(undefined) - - return identifier - } -} diff --git a/packages/dashboard/src/hooks/useGasOptions.ts b/packages/dashboard/src/hooks/useGasOptions.ts deleted file mode 100644 index c9f42b64bb11..000000000000 --- a/packages/dashboard/src/hooks/useGasOptions.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { GasOption, isEIP1559Supported, useChainId } from '@masknet/web3-shared-evm' -import { useMemo } from 'react' -import { useAsync } from 'react-use' -import { useDashboardI18N } from '../locales' -import { PluginServices } from '../API' - -const { Wallet: WalletRPC } = PluginServices -export function useGasOptions() { - const t = useDashboardI18N() - const chainId = useChainId() - const is1559Supported = useMemo(() => isEIP1559Supported(chainId), [chainId]) - const { value: gasFromMetaMask, loading: getFromMetaMaskLoading } = useAsync(async () => { - if (!is1559Supported) return - - return WalletRPC.getEstimateGasFees(chainId) - }, [is1559Supported, chainId]) - - // #region Get gas options from debank - const { value: gasFromDebank, loading: getFromDebankLoading } = useAsync(async () => { - if (is1559Supported) return - const response = await WalletRPC.getGasPriceDictFromDeBank(chainId) - if (!response) return null - return { - low: response.data.slow.price, - medium: response.data.normal.price, - high: response.data.fast.price, - } - }, [is1559Supported, chainId]) - // #endregion - - const gasOptions = is1559Supported ? gasFromMetaMask : gasFromDebank - - const options = useMemo(() => { - return [ - { - title: t.wallet_gas_fee_settings_low(), - gasOption: GasOption.Low, - gasPrice: gasOptions?.low ?? 0, - }, - { - title: t.wallet_gas_fee_settings_medium(), - gasOption: GasOption.Medium, - gasPrice: gasOptions?.medium ?? 0, - }, - { - title: t.wallet_gas_fee_settings_high(), - gasOption: GasOption.High, - gasPrice: gasOptions?.high ?? 0, - }, - ] - }, [is1559Supported, gasOptions]) - - return { - value: options, - loading: is1559Supported ? getFromMetaMaskLoading : getFromDebankLoading, - gasOptions, - } -} diff --git a/packages/dashboard/src/hooks/useMnemonicWordsPuzzle.ts b/packages/dashboard/src/hooks/useMnemonicWordsPuzzle.ts deleted file mode 100644 index e4e7eab27190..000000000000 --- a/packages/dashboard/src/hooks/useMnemonicWordsPuzzle.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * TODO: use @packages/plugin-wallet instead - */ - -import { shuffle } from 'lodash-unified' -import { useCallback, useMemo, useState } from 'react' -import { useAsyncRetry } from 'react-use' -import { WalletRPC } from '../../../mask/src/plugins/Wallet/messages' - -// How many fields should be filled by the user? -const PUZZLE_SIZE = 5 - -// The total count of mnemonic words. -const TOTAL_SIZE = 12 - -export function useMnemonicWordsPuzzle() { - const [answerWords, setAnswerWords] = useState([]) - const { - value: words = [], - loading: wordsLoading, - retry: wordsRetry, - } = useAsyncRetry(() => WalletRPC.createMnemonicWords(), []) - - // #region generate some mask indexes randomly which should be filled by the user - const [seed, setSeed] = useState(0) - const indexes = useMemo( - () => - shuffle( - Array.from({ length: TOTAL_SIZE }) - .fill(seed) - .map((_, i) => i), - ).slice(0, PUZZLE_SIZE), - [seed, words], - ) - // #endregion - - // #region a serial of words and the user gonna complete those empty ones - const puzzleWords = useMemo(() => { - const words_ = words.slice(0) - for (let i = 0; i < indexes.length; i += 1) words_[indexes[i]] = answerWords[i] ?? '' - return words_ - }, [answerWords, indexes, words]) - // #endregion - - const answerCallback = useCallback( - (word: string, index: number) => { - setAnswerWords((x) => { - const words_ = x.slice(0) - words_[index] = word - return words_ - }) - }, - [answerWords], - ) - - const resetCallback = useCallback(() => { - setAnswerWords([]) - setSeed((x) => (x + 1) % 3) - }, []) - - const refreshCallback = wordsRetry - - return { words, puzzleWords, indexes, answerCallback, resetCallback, refreshCallback } as const -} diff --git a/packages/dashboard/src/index.tsx b/packages/dashboard/src/index.tsx deleted file mode 100644 index ed5182f09e6d..000000000000 --- a/packages/dashboard/src/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// This entry is for developing dashboard as an isolated website. - -import type {} from 'react/next' -import type {} from 'react-dom/next' - -import './initialization/isolated_bridge' -import './initialization/i18n' -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import Dashboard from './initialization/Dashboard' - -if (import.meta.webpackHot) { - import.meta.webpackHot.accept() -} else if (location.host === 'compassionate-northcutt-326a3a.netlify.app') { - document.getElementById('warning')?.remove() -} - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/packages/dashboard/src/initialization/Dashboard.tsx b/packages/dashboard/src/initialization/Dashboard.tsx deleted file mode 100644 index 84236295ed53..000000000000 --- a/packages/dashboard/src/initialization/Dashboard.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { HashRouter } from 'react-router-dom' -import { CssBaseline, ThemeProvider, StyledEngineProvider, Theme } from '@mui/material' -import { - CustomSnackbarProvider, - MaskLightTheme, - applyMaskColorVars, - MaskDarkTheme, - useSystemPreferencePalette, -} from '@masknet/theme' -import { I18NextProviderHMR, SharedContextProvider } from '@masknet/shared' -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { createInjectHooksRenderer, useActivatedPluginsDashboard } from '@masknet/plugin-infra/dashboard' -import { NetworkPluginID, PluginsWeb3ContextProvider, useAllPluginsWeb3State } from '@masknet/plugin-infra/web3' -import { Web3Provider } from '@masknet/web3-shared-evm' - -import { i18NextInstance } from '@masknet/shared-base' - -import '../utils/kv-storage' - -import './PluginHost' -import { Pages } from '../pages/routes' -import { Web3Context } from '../web3/context' -import { useAppearance, usePluginID } from '../pages/Personas/api' -import { PersonaContext } from '../pages/Personas/hooks/usePersonaContext' -import { fixWeb3State } from '../../../mask/src/plugins/EVM/UI/Web3State' - -const PluginRender = createInjectHooksRenderer(useActivatedPluginsDashboard, (x) => x.GlobalInjection) - -export default function DashboardRoot() { - const pluginID = usePluginID() - const PluginsWeb3State = useAllPluginsWeb3State() - - // TODO: - // migrate EVM plugin - fixWeb3State(PluginsWeb3State[NetworkPluginID.PLUGIN_EVM], Web3Context) - - // #region theme - const appearance = useAppearance() - const mode = useSystemPreferencePalette() - const themes: Record = { - dark: MaskDarkTheme, - light: MaskLightTheme, - default: mode === 'dark' ? MaskDarkTheme : MaskLightTheme, - } - const theme = themes[appearance] - - applyMaskColorVars(document.body, appearance === 'default' ? mode : appearance) - // #endregion - - return ( - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/packages/dashboard/src/initialization/PluginHost.ts b/packages/dashboard/src/initialization/PluginHost.ts deleted file mode 100644 index 272c30c82d69..000000000000 --- a/packages/dashboard/src/initialization/PluginHost.ts +++ /dev/null @@ -1,42 +0,0 @@ -import './plugins' - -import { Emitter } from '@servie/events' -import { startPluginDashboard, Plugin } from '@masknet/plugin-infra/dashboard' -import { Services, Messages } from '../API' -import { createI18NBundle, createSubscriptionFromAsync, i18NextInstance } from '@masknet/shared-base' -import { InMemoryStorages, PersistentStorages } from '../utils/kv-storage' - -const PluginHost: Plugin.__Host.Host = { - minimalMode: { - events: new Emitter(), - isEnabled: (id) => { - return Services.Settings.getPluginMinimalModeEnabled(id) - }, - }, - addI18NResource(plugin, resource) { - createI18NBundle(plugin, resource)(i18NextInstance) - }, - createContext: (pluginID, signal) => { - const currentPersonaSub = createSubscriptionFromAsync( - Services.Settings.getCurrentPersonaIdentifier, - undefined, - Messages.events.currentPersonaIdentifier.on, - signal, - ) - return { - createKVStorage(type, defaultValues) { - if (type === 'memory') return InMemoryStorages.Plugin.createSubScope(pluginID, defaultValues, signal) - else return PersistentStorages.Plugin.createSubScope(pluginID, defaultValues, signal) - }, - personaSign: Services.Identity.signWithPersona, - walletSign: Services.Ethereum.personalSign, - currentPersona: currentPersonaSub, - } - }, -} -setTimeout(() => { - Messages.events.pluginMinimalModeChanged.on(([id, status]) => { - PluginHost.minimalMode.events.emit(status ? 'enabled' : 'disabled', id) - }) - startPluginDashboard(PluginHost) -}) diff --git a/packages/dashboard/src/initialization/i18n.ts b/packages/dashboard/src/initialization/i18n.ts deleted file mode 100644 index 503c40f00cc3..000000000000 --- a/packages/dashboard/src/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' -import { initReactI18next } from 'react-i18next' - -initReactI18next.init(i18NextInstance) -addSharedI18N(i18NextInstance) -addDashboardI18N(i18NextInstance) diff --git a/packages/dashboard/src/initialization/isolated_bridge.ts b/packages/dashboard/src/initialization/isolated_bridge.ts deleted file mode 100644 index 9e941f0812b0..000000000000 --- a/packages/dashboard/src/initialization/isolated_bridge.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Environment, UnboundedRegistry, WebExtensionMessage } from '@dimensiondev/holoflows-kit' -import { AsyncCall } from 'async-call-rpc' -import { serializer } from '@masknet/shared-base' -import { PluginMessages, setMessages, setPluginMessages, setPluginServices, setService } from '../API' - -class WebExtensionExternalChannel extends WebExtensionMessage { - // cspell:disable-next-line - constructor(domain: string, id = 'jkoeaghipilijlahjplgbfiocjhldnap') { - super({ externalExtensionID: id, domain }) - } -} -installService() -installPluginService() - -function installService() { - setMessages(new WebExtensionExternalChannel('mask')) - const servicesChannel = new WebExtensionExternalChannel('services') - const service = initProxy((prop) => initRPCBridge(servicesChannel.events[String(prop)])) - setService(service) -} - -function installPluginService() { - const channelOf = (id: string) => new WebExtensionExternalChannel('@plugin/' + id) - const Wallet = channelOf('com.maskbook.wallet') - const Transak = channelOf('com.maskbook.transak') - const Swap = channelOf('com.maskbook.trader') - const Pets = channelOf('com.maskbook.pets') - setPluginMessages({ Wallet, Transak, Swap, Pets }) - setPluginServices({ - Wallet: initRPCBridge(PluginMessages.Wallet.events.rpc), - Swap: initRPCBridge(PluginMessages.Swap.rpc), - }) -} - -function initRPCBridge(channel: UnboundedRegistry): any { - return AsyncCall( - {}, - { - channel: channel.bind(Environment.ManifestBackground), - serializer, - log: 'all', - thenable: false, - }, - ) -} - -function initProxy(init: (key: string) => any) { - return new Proxy({} as any, { - get(target, prop) { - if (typeof prop !== 'string') throw new TypeError() - if (target[prop]) return target[prop] - target[prop] = init(prop) - return target[prop] - }, - }) -} diff --git a/packages/dashboard/src/initialization/plugins.d.ts b/packages/dashboard/src/initialization/plugins.d.ts deleted file mode 100644 index 0a5c2294e09a..000000000000 --- a/packages/dashboard/src/initialization/plugins.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// This file is used to skip the type check to the ./plugins file. -export {} diff --git a/packages/dashboard/src/initialization/plugins.js b/packages/dashboard/src/initialization/plugins.js deleted file mode 100644 index 253437e475d5..000000000000 --- a/packages/dashboard/src/initialization/plugins.js +++ /dev/null @@ -1,3 +0,0 @@ -// Please make sure it is also added in -// packages/mask/src/plugin-infra/register.ts -import '@masknet/plugin-example' diff --git a/packages/dashboard/src/locales/en-US.json b/packages/dashboard/src/locales/en-US.json deleted file mode 100644 index a9ec274dc317..000000000000 --- a/packages/dashboard/src/locales/en-US.json +++ /dev/null @@ -1,394 +0,0 @@ -{ - "about": "About", - "wallets": "Wallets", - "personas": "Personas", - "persona": "Persona", - "refresh": "Refresh", - "next": "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", - "successful": "Successful", - "close": "Close", - "send": "Send", - "resend": "Resend", - "print": "Print", - "download": "Download", - "print_preview": "Print Preview", - "download_preview": "Download Preview", - "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 & chats on social networks, allow only your friends to decrypt.", - "setup_page_create_account_title": "Create an Identity", - "setup_page_create_account_subtitle": "Create your digital identity system, explore Web 3.0", - "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": "You can create personas and connect social accounts", - "create_account_persona_successfully": "Create persona successfully", - "create_account_connect_social_media_title": "Connect Social Media", - "create_account_failed": "Create Account Failed", - "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", - "sign_in_account_cloud_backup_email_format_error": "The email is incorrect.", - "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 Backups", - "wallets_transfer": "Transfer", - "wallets_assets": "Assets", - "wallets_transfer_memo": "Memo", - "wallets_transfer_amount": "Amount", - "wallets_transfer_to_address": "To Address", - "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": "Invalid recipient 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", - "wallet_transactions_pending": "Pending", - "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 a 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_token_sent_not_connect_tip": "Not connected to {{chainName}}.", - "wallets_assets_token_sent_switch_network_tip": "Click to switch.", - "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_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_address_copied": "Address 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 Token", - "personas_setup_connect_tips": "Please connect to your {{type}} account.", - "personas_setup_tip": "Please to create/restore persona.", - "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 Web 3 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 use your private key for recovery.", - "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", - "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_account": "Associated Account", - "settings_backup_preview_posts": "Encrypted Post", - "settings_backup_preview_contacts": "Contacts", - "settings_backup_preview_fils": "File", - "settings_backup_preview_wallets": "Mask Wallet", - "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": "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_markets": "ITO", - "labs_markets_desc": "Launch decentralized asset freely and participate in token launch directly on Twitter.", - "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 and vote for proposals directly on social media.", - "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_dhedge": "dHEDGE", - "labs_dhedge_desc": "Decentralized hedge funds on Ethereum and Polygon (Matic).", - "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_good_ghosting": "GoodGhosting", - "labs_good_ghosting_desc": "Cultivate a weekly saving habit on Twitter.", - "labs_pool_together": "PoolTogether", - "labs_pool_together_desc": "Participate in lossless lottery on Twitter.", - "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_cryptoartai": "CRYPTOART.AI", - "labs_cryptoartai_desc": "Connect CryptoArt Digital Artworks. The Internationale is a classic anthem that has been a shared memory for generations.", - "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": "Do not forget to save your mnemonic phrase. You will need this to access your wallet.", - "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 password does not match.", - "create_wallet_password_length_error": "The password length is incorrect.", - "create_wallet_name_placeholder": "Enter 1-12 characters", - "create_wallet_form_title": "Create a wallet", - "create_wallet_wallet_name": "Wallet Name", - "create_wallet_payment_password": "Payment Password", - "create_wallet_re_enter_payment_password": "Re-enter the payment password", - "create_wallet_payment_password_tip": "Payment Password must be a combination of 2 categories out of numbers, letters and special characters with a length of 8-20 characters.", - "create_wallet_your_wallet_address": "Your wallet address", - "create_wallet_done": "Done", - "create_wallet_verify_words": "Verify mnemonic words", - "create_wallet_mnemonic_word_not_match": "The mnemonic word is incorrect" -} diff --git a/packages/dashboard/src/locales/index.ts b/packages/dashboard/src/locales/index.ts deleted file mode 100644 index d6ead60252e4..000000000000 --- a/packages/dashboard/src/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' diff --git a/packages/dashboard/src/locales/ja-JP.json b/packages/dashboard/src/locales/ja-JP.json deleted file mode 100644 index 34a763ceab8e..000000000000 --- a/packages/dashboard/src/locales/ja-JP.json +++ /dev/null @@ -1,310 +0,0 @@ -{ - "about": "About", - "wallets": "ウォレット", - "personas": "ペルソナ", - "persona": "ペルソナ", - "refresh": "更新", - "next": "Next", - "cancel": "Cancel", - "back": "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は、ソーシャルネットワーク上で暗号化された投稿を送ることができます。暗号化された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_identity_title": "Mask Networkのアイデンティティを作成", - "create_account_sign_in_button": "復元またはサインイン", - "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": "アカウント作成が失敗しました", - "sign_in_account_identity_title": "復元またはサインイン", - "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": "復号に失敗しました。パスワードを確認してください。", - "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 を1つ選択してください", - "wallets_transfer_error_invalid_address": "無効な受け手のアドレス", - "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": "完了!", - "labs": "Mask Labs", - "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ウォレットは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_token_sent_not_connect_tip": "{{chainName}} に接続されていません。", - "wallets_assets_token_sent_switch_network_tip": "クリックして切り替えます", - "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": "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_address_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_warning": "{{network}} の {{userId}}アカウントを切断してもよろしいですか? 切断後、このアカウントはマスクネットワークで情報を復号化および暗号化することができなくなります。", - "personas_logout_warning": "ログアウト後、あなたに関連づけられたソーシャルアカウントは過去に暗号化されたメッセージを復号することができなくなります。アカウントを再利用する場合は、秘密鍵を利用して復元することができます。", - "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": "投稿を作成", - "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_account": "紐付けられたアカウント", - "settings_backup_preview_posts": "暗号化された投稿", - "settings_backup_preview_contacts": "コンタクト", - "settings_backup_preview_fils": "ファイル", - "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_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_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_markets": "マーケット", - "labs_markets_desc": "クリプトの友人にデジタル資産を販売します。", - "labs_red_packet": "幸運ドロップ", - "labs_red_packet_desc": "あなたの幸運を暗号化された幸運の小包としてあなたの友人に送信します。", - "labs_swap": "スワップ", - "labs_swap_desc": "追加料金と制限なしに Dex を通じてトークンを購入します。", - "labs_transak": "Transak", - "labs_transak_desc": "Transak より 60 以上の国でクリプトを購入することができます。" -} diff --git a/packages/dashboard/src/locales/ko-KR.json b/packages/dashboard/src/locales/ko-KR.json deleted file mode 100644 index 3714085af384..000000000000 --- a/packages/dashboard/src/locales/ko-KR.json +++ /dev/null @@ -1,377 +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를 통해 소셜 네트워크에서 암호화 게시글을 공유할 수도 있다. 레드 패킷 보내기, 암호화폐 구매, 파일 서비스 등 기능도 지원한다.", - "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_identity_title": "Mask Network 아이디 만들기", - "create_account_sign_in_button": "복구 또는 로그인", - "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": "계정 만들기 실패", - "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": "해독 실패, 비밀번호를 확인하세요", - "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_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", - "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 월렛은 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_token_sent_not_connect_tip": "{{chainName}} 에 연결되지 않습니다.", - "wallets_assets_token_sent_switch_network_tip": "전환하기", - "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": "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_address_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_warning": "{{userId}} 의 {{network}} 계정 연결을 해제하시겠습니까? 연결이 끊긴 후 이 계정은 더 이상 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_associated_account": "관련 계정", - "settings_backup_preview_posts": "암호화된 게시물", - "settings_backup_preview_contacts": "연락처", - "settings_backup_preview_fils": "파일", - "settings_backup_preview_wallets": "로컬 월렛", - "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_markets": "마켓", - "labs_markets_desc": "암호 친구에게 디지털 자산을 보내기", - "labs_red_packet": "레드 패킷", - "labs_red_packet_desc": "축복을 암호화된 레드 패킷으로 포장하고 친구들에게 보내세요.", - "labs_swap": "스왑", - "labs_swap_desc": "추가 비용과 제한 없이 DEX로 토큰을 구매하기", - "labs_transak": "Transak", - "labs_transak_desc": "Transak 지원으로 60+ 국가에서 암호화폐 구매하기", - "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_dhedge": "DHEDGE", - "labs_dhedge_desc": "이더리움의 탈중앙화 헤지 펀드", - "labs_mask_box": "MaskBox", - "labs_mask_box_desc": "NFT 미스터리박스를 출시하는 멀티체인 탈중앙화 플랫폼", - "labs_loot_man": "LootMan by MintTeam", - "labs_loot_man_desc": "NFT의 무한한 가능성을 탐색. 소셜미디어에서 보유하는 NFT를 전시하는 혁신적인 방식", - "labs_good_ghosting": "GoodGhosting", - "labs_good_ghosting_desc": "트위터에서 매주 저축 습관을 기른다.", - "labs_pool_together": "PoolTogether", - "labs_pool_together_desc": "트위터에서 무손실 로또에 참여한다.", - "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_setup_tutorial": "튜토리얼 설정", - "labs_do_not_show_again": "다시 보이지 않기", - "labs_cryptoartai": "CRYPTOART.AI", - "labs_cryptoartai_desc": "암호화 디지털 예술 연결.", - "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_wallet_name": "월렛 이름", - "create_wallet_payment_password": "결재 비밀번호", - "create_wallet_re_enter_payment_password": "결재 비밀번호 다시 설정", - "create_wallet_payment_password_tip": "백업 암호는 8자에서 20자 사이여야 하며 숫자, 대문자, 소문자 및 특수 문자를 포함해야 합니다.", - "create_wallet_your_wallet_address": "월렛 주소", - "create_wallet_done": "완료", - "create_wallet_verify_words": "이모닉 단어 인증", - "create_wallet_mnemonic_word_not_match": "니모닉 단어가 불일치합니다" -} diff --git a/packages/dashboard/src/locales/languages.ts b/packages/dashboard/src/locales/languages.ts deleted file mode 100644 index 56f04baa0e9a..000000000000 --- a/packages/dashboard/src/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/dashboard/src/locales/qya-AA.json b/packages/dashboard/src/locales/qya-AA.json deleted file mode 100644 index bd02ffc4a6ae..000000000000 --- a/packages/dashboard/src/locales/qya-AA.json +++ /dev/null @@ -1,394 +0,0 @@ -{ - "about": "crwdns1619:0crwdne1619:0", - "wallets": "crwdns1621:0crwdne1621:0", - "personas": "crwdns1623:0crwdne1623:0", - "persona": "crwdns7557:0crwdne7557:0", - "refresh": "crwdns7559:0crwdne7559:0", - "next": "crwdns7561:0crwdne7561: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", - "successful": "crwdns8077:0crwdne8077:0", - "close": "crwdns8173:0crwdne8173:0", - "send": "crwdns8203:0crwdne8203:0", - "resend": "crwdns8205:0crwdne8205:0", - "print": "crwdns13039:0crwdne13039:0", - "download": "crwdns13041:0crwdne13041:0", - "print_preview": "crwdns13043:0crwdne13043:0", - "download_preview": "crwdns13045:0crwdne13045: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", - "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", - "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_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", - "wallet_transactions_pending": "crwdns7921:0crwdne7921: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_token_sent_not_connect_tip": "crwdns9343:0{{chainName}}crwdne9343:0", - "wallets_assets_token_sent_switch_network_tip": "crwdns9345:0crwdne9345: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_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_address_copied": "crwdns1793:0crwdne1793: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_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", - "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_account": "crwdns7643:0crwdne7643:0", - "settings_backup_preview_posts": "crwdns7645:0crwdne7645:0", - "settings_backup_preview_contacts": "crwdns7647:0crwdne7647:0", - "settings_backup_preview_fils": "crwdns7649:0crwdne7649:0", - "settings_backup_preview_wallets": "crwdns7651:0crwdne7651: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_markets": "crwdns1889:0crwdne1889:0", - "labs_markets_desc": "crwdns1891:0crwdne1891: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_dhedge": "crwdns1925:0crwdne1925:0", - "labs_dhedge_desc": "crwdns1927:0crwdne1927: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_good_ghosting": "crwdns10331:0crwdne10331:0", - "labs_good_ghosting_desc": "crwdns10333:0crwdne10333:0", - "labs_pool_together": "crwdns10335:0crwdne10335:0", - "labs_pool_together_desc": "crwdns10337:0crwdne10337: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_cryptoartai": "crwdns10519:0crwdne10519:0", - "labs_cryptoartai_desc": "crwdns10521:0crwdne10521: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_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", - "create_wallet_wallet_name": "crwdns7741:0crwdne7741:0", - "create_wallet_payment_password": "crwdns7743:0crwdne7743:0", - "create_wallet_re_enter_payment_password": "crwdns7745:0crwdne7745:0", - "create_wallet_payment_password_tip": "crwdns7747:0crwdne7747:0", - "create_wallet_your_wallet_address": "crwdns7749:0crwdne7749:0", - "create_wallet_done": "crwdns7751:0crwdne7751:0", - "create_wallet_verify_words": "crwdns7753:0crwdne7753:0", - "create_wallet_mnemonic_word_not_match": "crwdns8029:0crwdne8029:0" -} diff --git a/packages/dashboard/src/locales/zh-CN.json b/packages/dashboard/src/locales/zh-CN.json deleted file mode 100644 index e0a348915df8..000000000000 --- a/packages/dashboard/src/locales/zh-CN.json +++ /dev/null @@ -1,384 +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": "重新发送", - "print": "打印", - "download": "下载", - "print_preview": "打印预览", - "download_preview": "下载预览", - "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": "构建您的数字身份系统,探索 Web 3.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_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": "创建帐户失败", - "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": "备份解密失败,请检查密码", - "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_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", - "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/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": "收藏品地址", - "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_token_sent_not_connect_tip": "未连接到 {{chainName}}。", - "wallets_assets_token_sent_switch_network_tip": "点击切换", - "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_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_address_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朋友将不能再通过此身份向您发送加密的消息,或查看您与此身份相关的Web 3产品。", - "personas_logout_warning": "身份登出后,您所关联的社交网络账户将不能解密过去的加密消息。 如果您需要重新使用您的身份,您可以使用您的身份私钥进行恢复。", - "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": "创建贴文", - "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_account": "关联账户", - "settings_backup_preview_posts": "加密贴文", - "settings_backup_preview_contacts": "联系人", - "settings_backup_preview_fils": "文件", - "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_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_markets": "ITO", - "labs_markets_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_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": "Valuables", - "labs_valuables_desc": "购买和销售由其原创作者发布的推文。", - "labs_dhedge": "dHEDGE", - "labs_dhedge_desc": "以太坊上的去中心化对冲基金。", - "labs_mask_box": "Mask盲盒", - "labs_mask_box_desc": "支持多链的去中心化NFT盲盒发布平台。", - "labs_loot_man_desc": "探索NFT无尽的可能性。以革命性的方式在社交媒体上展示您的 NFT。", - "labs_good_ghosting": "GoodGhosting", - "labs_good_ghosting_desc": "在Twitter上培养每周储蓄的习惯。", - "labs_pool_together": "无损彩票", - "labs_pool_together_desc": "在 Twitter 上参与零损失彩票活动。", - "labs_settings_market_trend": "市场趋势设置", - "labs_settings_market_trend_source": "默认趋势来源", - "labs_settings_swap": "交易所设置", - "labs_settings_swap_network": "{{network}} 网络默认交易所", - "labs_pets_desc": "探索NFT无尽的可能性。以革命性的方式在社交媒体上展示您的 NFT。", - "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_wallet_name": "钱包名称", - "create_wallet_payment_password": "支付密码", - "create_wallet_re_enter_payment_password": "再次输入支付密码", - "create_wallet_payment_password_tip": "支付密码必须是数字、大小写字母和特殊字符中的2个类别的组合,长度为 8-20 个字符。", - "create_wallet_your_wallet_address": "您的钱包地址", - "create_wallet_done": "完成", - "create_wallet_verify_words": "验证助记词", - "create_wallet_mnemonic_word_not_match": "助记词不正确" -} diff --git a/packages/dashboard/src/locales/zh-TW.json b/packages/dashboard/src/locales/zh-TW.json deleted file mode 100644 index d39137f02191..000000000000 --- a/packages/dashboard/src/locales/zh-TW.json +++ /dev/null @@ -1,362 +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_token_sent_not_connect_tip": "未连接至 {{chainName}}.", - "wallets_assets_token_sent_switch_network_tip": "點擊切換", - "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_associated_account": "關聯帳戶", - "settings_backup_preview_posts": "加密貼文", - "settings_backup_preview_contacts": "聯繫人", - "settings_backup_preview_fils": "文件", - "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_markets": "首次代幣發行", - "labs_markets_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_dhedge": "dHEDGE", - "labs_dhedge_desc": "在eth/Matic鏈上的去中心化對衝基金", - "labs_mask_box_desc": "使用專業的多鏈去中心平台以發售NFT盲盒", - "labs_loot_man": "Loot小人 by MintTeam", - "labs_loot_man_desc": "用全新方式在社交平台上連結展示你的NFT收藏並探索NFT的無限可能", - "labs_good_ghosting": "GoodGhosting", - "labs_good_ghosting_desc": "在Twitter上養成每週儲蓄習慣", - "labs_pool_together": "PoolTogether", - "labs_pool_together_desc": "在Twitter上參與無損彩票遊戲", - "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": "不再顯示", - "labs_cryptoartai": "CRYPTOART.AI", - "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_wallet_name": "錢包名稱", - "create_wallet_payment_password": "支付密碼", - "create_wallet_re_enter_payment_password": "再次輸入支付密碼", - "create_wallet_payment_password_tip": "支付密碼必須是數字、大小寫字母和特殊字母中至少兩種類別的組合,長度為8-20個字符", - "create_wallet_your_wallet_address": "錢包地址", - "create_wallet_done": "完成", - "create_wallet_verify_words": "驗證助記詞", - "create_wallet_mnemonic_word_not_match": "助記詞錯誤" -} diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx deleted file mode 100644 index fc44340b5553..000000000000 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { memo, useCallback, useEffect, useState } from 'react' -import { Alert, Box, Button, Typography } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { InfoIcon, RefreshIcon } from '@masknet/icons' -import { useDashboardI18N } from '../../../../locales' -import { ChainId, ProviderType } from '@masknet/web3-shared-evm' -import { MnemonicReveal } from '../../../../components/Mnemonic' -import { VerifyMnemonicDialog } from '../VerifyMnemonicDialog' -import { useAsyncFn, useAsyncRetry } from 'react-use' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import { PluginServices, Services } from '../../../../API' -import { DashboardRoutes } from '@masknet/shared-base' -import { WalletMessages } from '@masknet/plugin-wallet' -import { useMnemonicWordsPuzzle } from '../../../../hooks/useMnemonicWordsPuzzle' - -// Private key at m/purpose'/coin_type'/account'/change -export const HD_PATH_WITHOUT_INDEX_ETHEREUM = "m/44'/60'/0'/0" - -const useStyles = makeStyles()((theme) => ({ - container: { - padding: '120px 18%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - flexDirection: 'column', - }, - title: { - fontSize: 24, - lineHeight: 1.25, - fontWeight: 500, - }, - refresh: { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-end', - marginTop: 24, - fontSize: 14, - lineHeight: '20px', - width: '100%', - color: MaskColorVar.linkText, - }, - words: { - marginTop: 24, - backgroundColor: MaskColorVar.bottom, - padding: 30, - width: '100%', - borderRadius: 8, - }, - controller: { - marginTop: 24, - display: 'grid', - gridTemplateColumns: 'repeat(2, 33%)', - justifyContent: 'center', - gridColumnGap: 10, - padding: '27px 0', - width: '100%', - }, - button: { - height: 48, - borderRadius: 24, - fontSize: 18, - }, - cancelButton: { - height: 48, - borderRadius: 24, - fontSize: 18, - background: theme.palette.mode === 'dark' ? '#1A1D20' : '#F7F9FA', - }, - alert: { - marginTop: 24, - padding: 24, - backgroundColor: MaskColorVar.errorBackground, - color: MaskColorVar.redMain, - }, -})) - -const CreateMnemonic = memo(() => { - const location = useLocation() - const navigate = useNavigate() - const [open, setOpen] = useState(false) - const { words, puzzleWords, indexes, answerCallback, resetCallback, refreshCallback } = useMnemonicWordsPuzzle() - const [searchParams] = useSearchParams() - const { value: hasPassword, loading, retry } = useAsyncRetry(PluginServices.Wallet.hasPassword, []) - - useEffect(() => { - WalletMessages.events.walletLockStatusUpdated.on(retry) - }, [retry]) - - const onVerifyClick = useCallback(() => { - setOpen(true) - }, []) - - const [walletState, onSubmit] = useAsyncFn(async () => { - const name = new URLSearchParams(location.search).get('name') - const password = (location.state as any)?.password - // if the name doesn't exist, navigate to form page - if (!name) { - resetCallback() - navigate(DashboardRoutes.CreateMaskWalletForm) - return - } - - if (!hasPassword) { - await PluginServices.Wallet.setPassword(password) - } - - const address_ = await PluginServices.Wallet.recoverWalletFromMnemonic( - name, - words.join(' '), - `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`, - ) - - await PluginServices.Wallet.updateMaskAccount({ account: address_ }) - - const account = await Services.Settings.getSelectedWalletAddress() - - if (!account) { - await PluginServices.Wallet.updateAccount({ - account: address_, - providerType: ProviderType.MaskWallet, - }) - const chainId = searchParams.get('chainId') - if (chainId) { - await PluginServices.Wallet.selectAccount([address_], Number(chainId) as ChainId) - } - } - - return address_ - }, [location.search, words, resetCallback, hasPassword, searchParams]) - - const onClose = useCallback(() => { - refreshCallback() - resetCallback() - setOpen(false) - }, [refreshCallback, resetCallback]) - - useEffect(() => { - if (!(location.state as any)?.password && !hasPassword && !loading) navigate(-1) - }, [location.state, hasPassword, loading]) - - return ( - <> - - - - ) -}) - -export interface CreateMnemonicUIProps { - words: string[] - onRefreshWords: () => void - onVerifyClick: () => void -} - -export const CreateMnemonicUI = memo(({ words, onRefreshWords, onVerifyClick }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - const navigate = useNavigate() - const [open, setOpen] = useState(true) - - return ( -
- Create a wallet -
- - - {t.wallets_create_wallet_refresh()} - -
-
- -
- - - - - {open ? ( - } severity="error" onClose={() => setOpen(false)} className={classes.alert}> - {t.create_wallet_mnemonic_tip()} - - ) : null} -
- ) -}) - -export default CreateMnemonic diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx deleted file mode 100644 index 224307ebbb42..000000000000 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { memo, useEffect, useMemo, useState } from 'react' -import { Alert, Box, Button, formHelperTextClasses, Typography } from '@mui/material' -import { makeStyles, MaskColorVar, MaskTextField } from '@masknet/theme' -import { z as zod } from 'zod' -import { useForm, Controller } from 'react-hook-form' -import { zodResolver } from '@hookform/resolvers/zod' -import { useNavigate, useSearchParams } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { useDashboardI18N } from '../../../../locales' -import { useAsyncRetry } from 'react-use' -import { WalletMessages } from '@masknet/plugin-wallet' -import { PluginServices } from '../../../../API' -import urlcat from 'urlcat' -import PasswordField from '../../../../components/PasswordField' -import { InfoIcon } from '@masknet/icons' - -const useStyles = makeStyles()((theme) => ({ - container: { - padding: '120px 18%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - flexDirection: 'column', - height: '100%', - }, - title: { - fontSize: 24, - lineHeight: 1.25, - fontWeight: 500, - }, - form: { - marginTop: 70, - width: '100%', - }, - label: { - fontSize: 12, - lineHeight: '16px', - color: MaskColorVar.main, - }, - input: { - width: '100%', - marginTop: 10, - }, - textField: { - background: theme.palette.mode === 'dark' ? '#1D2023' : '#F7F9FA', - padding: theme.spacing(1), - fontSize: 12, - lineHeight: '16px', - borderRadius: 6, - [`&.${formHelperTextClasses.error}`]: { - boxShadow: `0 0 0 ${theme.spacing(0.5)} ${MaskColorVar.redMain.alpha(0.2)}`, - border: `1px solid ${MaskColorVar.redMain.alpha(0.8)}`, - }, - }, - tips: { - fontSize: 12, - lineHeight: '16px', - color: '#7B8192', - marginTop: 10, - }, - controller: { - marginTop: 24, - display: 'grid', - gridTemplateColumns: 'repeat(2, 33%)', - justifyContent: 'center', - gridColumnGap: 10, - padding: '27px 0', - }, - button: { - height: 48, - borderRadius: 24, - fontSize: 18, - }, - cancelButton: { - height: 48, - borderRadius: 24, - fontSize: 18, - background: theme.palette.mode === 'dark' ? '#1A1D20' : '#F7F9FA', - }, - alert: { - marginTop: 24, - padding: 24, - backgroundColor: MaskColorVar.errorBackground, - color: MaskColorVar.redMain, - }, -})) - -const CreateWalletForm = memo(() => { - const t = useDashboardI18N() - const [open, setOpen] = useState(true) - const { classes } = useStyles() - const navigate = useNavigate() - const [searchParams] = useSearchParams() - const { value: hasPassword, loading, retry } = useAsyncRetry(PluginServices.Wallet.hasPassword, []) - - useEffect(() => { - return WalletMessages.events.walletLockStatusUpdated.on(retry) - }, [retry]) - - const schema = useMemo(() => { - const passwordRule = zod - .string() - .min(8, t.create_wallet_password_length_error()) - .max(20, t.create_wallet_password_length_error()) - .refine( - (input) => [/[A-Z]/, /[a-z]/, /\d/, /[^\dA-Za-z]/].filter((regex) => regex.test(input)).length >= 2, - t.create_wallet_password_satisfied_requirement(), - ) - - return zod - .object( - hasPassword - ? { name: zod.string().min(1).max(12) } - : { - name: zod.string().min(1).max(12), - password: hasPassword ? passwordRule.optional() : passwordRule, - confirm: zod.string().optional(), - }, - ) - .refine((data) => (!hasPassword ? data.password === data.confirm : true), { - message: t.create_wallet_password_match_tip(), - path: ['confirm'], - }) - }, [hasPassword]) - - const { - control, - handleSubmit, - formState: { errors, isValid }, - } = useForm>({ - mode: 'onBlur', - resolver: zodResolver(schema), - defaultValues: { - name: '', - password: '', - confirm: '', - }, - }) - - const onSubmit = handleSubmit((data) => { - navigate( - urlcat(DashboardRoutes.CreateMaskWalletMnemonic, { name: data.name, chainId: searchParams.get('chainId') }), - data.password - ? { - state: { password: data.password }, - } - : undefined, - ) - }) - - return ( -
- {t.create_wallet_form_title()} -
- - {t.create_wallet_wallet_name()} - ( - - )} - control={control} - name="name" - /> - - {!loading && !hasPassword ? ( - <> - - {t.create_wallet_payment_password()} - ( - - )} - name="password" - /> - ( - - )} - name="confirm" - control={control} - /> - - {t.create_wallet_payment_password_tip()} - - ) : null} - - - - - {open ? ( - } - severity="error" - onClose={() => setOpen(false)} - className={classes.alert}> - {t.create_wallet_mnemonic_tip()} - - ) : null} -
-
- ) -}) - -export default CreateWalletForm diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx deleted file mode 100644 index 331360251fe4..000000000000 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { memo, useState } from 'react' -import { Box, Typography, styled, Button, Dialog, DialogTitle, DialogContent } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { LoadingButton } from '@mui/lab' -import { useSnackbarCallback } from '@masknet/shared' -import { SuccessIcon, CopyIcon } from '@masknet/icons' -import { DesktopMnemonicConfirm } from '../../../../components/Mnemonic' -import { useDashboardI18N } from '../../../../locales' -import { useCopyToClipboard } from 'react-use' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' - -const useStyles = makeStyles()((theme) => ({ - dialogTitle: { - backgroundColor: theme.palette.mode === 'dark' ? '#000000' : '#ffffff', - }, - container: { - padding: '40px 60px', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - backgroundColor: theme.palette.mode === 'dark' ? '#000000' : '#ffffff', - }, - title: { - fontSize: 24, - lineHeight: 1.25, - }, - confirm: { - marginTop: 24, - }, - tips: { - marginTop: 40, - color: MaskColorVar.redMain, - fontSize: 18, - lineHeight: '24px', - alignSelf: 'flex-start', - }, - button: { - height: 48, - borderRadius: 24, - fontSize: 18, - marginTop: 24, - }, - addressTitle: { - color: MaskColorVar.normalText, - fontSize: 14, - lineHeight: '20px', - }, - address: { - display: 'flex', - alignItems: 'center', - marginTop: 12, - fontSize: 14, - lineHeight: '20px', - }, - copy: { - fontSize: 20, - marginLeft: 12, - cursor: 'pointer', - stroke: MaskColorVar.textPrimary, - }, -})) - -const SuccessTitle = styled(Typography)(({ theme }) => ({ - fontSize: theme.typography.h5.fontSize, - color: theme.palette.success.main, - fontWeight: theme.typography.fontWeightMedium as any, - margin: theme.spacing(2, 0), -})) - -export interface VerifyMnemonicDialogProps { - matched: boolean - open: boolean - onClose: () => void - puzzleWords: string[] - indexes: number[] - onUpdateAnswerWords: (word: string, index: number) => void - onSubmit: () => void - address?: string - loading: boolean -} - -export const VerifyMnemonicDialog = memo( - ({ matched, open, onClose, puzzleWords, indexes, onUpdateAnswerWords, onSubmit, loading, address }) => { - const navigate = useNavigate() - const t = useDashboardI18N() - const [, copyToClipboard] = useCopyToClipboard() - - const copyWalletAddress = useSnackbarCallback({ - executor: async (address: string) => copyToClipboard(address), - deps: [], - successText: t.wallets_address_copied(), - }) - - return ( - navigate(DashboardRoutes.Wallets)} - /> - ) - }, -) - -export interface VerifyMnemonicDialogUIProps extends VerifyMnemonicDialogProps { - onCopy: (address: string) => void - onDoneClick: () => void -} - -export const VerifyMnemonicDialogUI = memo( - ({ - matched, - open, - onClose, - puzzleWords, - indexes, - onUpdateAnswerWords, - onSubmit, - loading, - address, - onCopy, - onDoneClick, - }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - const [verified, setVerified] = useState(false) - - return ( - - {t.wallets_create_wallet_verification()} - -
- {address ? ( - <> - - {t.wallets_create_successfully_title()} - - - {t.create_wallet_your_wallet_address()} - - - - {address} - onCopy(address)} /> - - - - ) : ( - <> - {t.create_wallet_verify_words()} - - - - - {!matched && verified ? ( - - {t.create_wallet_mnemonic_word_not_match()} - - ) : null} - - { - if (!verified) setVerified(true) - if (matched) onSubmit() - }}> - {t.verify()} - - - )} -
-
-
- ) - }, -) diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx deleted file mode 100644 index b6f2a4d25c0f..000000000000 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { memo, MutableRefObject, useEffect, useMemo, useRef } from 'react' -import { useNavigate, useSearchParams } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' -import { styled, useTheme } from '@mui/material/styles' -import { useDashboardI18N } from '../../../../locales' -import { Button } from '@mui/material' -import { MaskNotSquareIcon } from '@masknet/icons' -import urlcat from 'urlcat' -import { openWindow } from '@masknet/shared-base-ui' - -const Content = styled('div')` - width: 100%; - overflow: auto; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` - -const ButtonGroup = styled('div')` - display: grid; - grid-template-columns: repeat(2, 1fr); - padding: 33px 120px; - gap: 10px; - margin-top: 24px; - width: 100%; - max-width: 864px; -` - -const IFrame = styled('iframe')` - border: none; - width: 100%; - min-height: 498px; - max-width: 864px; -` - -const StyledButton = styled(Button)( - () => ` - font-size: 14px; - font-weight: 600; - line-height: 20px; - padding: 8px 16px; - border-radius: 20px; -`, -) - -const CancelButton = styled(Button)( - ({ theme }) => ` - font-size: 14px; - font-weight: 600; - line-height: 20px; - padding: 8px 16px; - border-radius: 20px; - background: ${theme.palette.mode === 'dark' ? '#1A1D20' : '#F7F9FA'} -`, -) - -const MASK_PRIVACY_POLICY = 'https://legal.mask.io/maskbook/privacy-policy-browser.html' - -const Welcome = memo(() => { - const iframeRef = useRef(null) - const mode = useTheme().palette.mode - const navigate = useNavigate() - const [searchParams] = useSearchParams() - - const privacyPolicyURL = new URL('../../en.html', import.meta.url).toString() - const privacyPolicyDocument = useMemo(() => () => iframeRef?.current?.contentWindow?.document, [iframeRef]) - - const updateIFrameStyle = () => { - const iframeDocument = privacyPolicyDocument() - if (!iframeDocument) return - - const style = iframeDocument.createElement('style') - style.textContent = ` - h3, h6 { color: ${mode === 'dark' ? '#D4D4D4' : '#111432'}; } - p { color: ${mode === 'dark' ? '#D4D4D4;' : '#7b8192'}; } - body::-webkit-scrollbar { - width: 7px; - } - body::-webkit-scrollbar-track { - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0); - -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0); - } - body::-webkit-scrollbar-thumb { - border-radius: 4px; - } - ` - iframeDocument.head?.appendChild(style) - } - - const handleIFrameLoad = () => { - updateIFrameStyle() - - const iframeDocument = privacyPolicyDocument() - if (!iframeDocument) return - - const link = iframeDocument.getElementById('link') - link?.addEventListener('click', handleLinkClick) - } - - const handleLinkClick = () => openWindow(MASK_PRIVACY_POLICY) - - useEffect( - () => () => { - const link = privacyPolicyDocument()?.getElementById('link') - link?.removeEventListener('click', handleLinkClick) - }, - [], - ) - - useEffect(() => { - updateIFrameStyle() - }, [mode]) - - return ( - - navigate(urlcat(DashboardRoutes.CreateMaskWalletForm, { chainId: searchParams.get('chainId') })) - } - cancelHandler={() => window.close()} - /> - ) -}) - -interface WelcomeUIProps { - privacyPolicyURL: string - iframeRef: MutableRefObject - iframeLoadHandler(): void - agreeHandler(): void - cancelHandler(): void -} - -export const WelcomeUI = memo( - ({ privacyPolicyURL, iframeLoadHandler, agreeHandler, cancelHandler, iframeRef }: WelcomeUIProps) => { - const t = useDashboardI18N() - - return ( - - -