mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Add Snaps via Flask (#13462)
This PR adds `snaps` under Flask build flags to the extension. This branch is mostly equivalent to the current production version of Flask, excepting some bug fixes and tweaks. Closes #11626
This commit is contained in:
parent
2b5b787ca9
commit
35ac762e10
@ -171,7 +171,7 @@ module.exports = {
|
||||
'app/scripts/migrations/*.test.js',
|
||||
'app/scripts/platforms/*.test.js',
|
||||
'app/scripts/controllers/network/**/*.test.js',
|
||||
'app/scripts/controllers/permissions/*.test.js',
|
||||
'app/scripts/controllers/permissions/**/*.test.js',
|
||||
],
|
||||
extends: ['@metamask/eslint-config-mocha'],
|
||||
rules: {
|
||||
@ -198,7 +198,7 @@ module.exports = {
|
||||
'app/scripts/migrations/*.test.js',
|
||||
'app/scripts/platforms/*.test.js',
|
||||
'app/scripts/controllers/network/**/*.test.js',
|
||||
'app/scripts/controllers/permissions/*.test.js',
|
||||
'app/scripts/controllers/permissions/**/*.test.js',
|
||||
],
|
||||
extends: ['@metamask/eslint-config-jest'],
|
||||
rules: {
|
||||
|
@ -6,7 +6,7 @@ module.exports = {
|
||||
'./app/scripts/migrations/*.test.js',
|
||||
'./app/scripts/platforms/*.test.js',
|
||||
'./app/scripts/controllers/network/**/*.test.js',
|
||||
'./app/scripts/controllers/permissions/*.test.js',
|
||||
'./app/scripts/controllers/permissions/**/*.test.js',
|
||||
],
|
||||
recursive: true,
|
||||
require: ['test/env.js', 'test/setup.js'],
|
||||
|
@ -102,6 +102,85 @@ const state = {
|
||||
swapsFeatureIsLive: false,
|
||||
swapsQuoteRefreshTime: 60000,
|
||||
},
|
||||
"snapStates": {},
|
||||
"snaps": {
|
||||
"local:http://localhost:8080/": {
|
||||
"enabled": true,
|
||||
"id": "local:http://localhost:8080/",
|
||||
"initialPermissions": {
|
||||
"snap_confirm": {}
|
||||
},
|
||||
"manifest": {
|
||||
"description": "An example MetaMask Snap.",
|
||||
"initialPermissions": {
|
||||
"snap_confirm": {}
|
||||
},
|
||||
"manifestVersion": "0.1",
|
||||
"proposedName": "MetaMask Example Snap",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MetaMask/snaps-skunkworks.git"
|
||||
},
|
||||
"source": {
|
||||
"location": {
|
||||
"npm": {
|
||||
"filePath": "dist/bundle.js",
|
||||
"iconPath": "images/icon.svg",
|
||||
"packageName": "@metamask/example-snap",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
},
|
||||
"shasum": "3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE="
|
||||
},
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"permissionName": "wallet_snap_local:http://localhost:8080/",
|
||||
"sourceCode": "(...)",
|
||||
"status": "stopped",
|
||||
"svgIcon": "<svg>...</svg>",
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"Filecoin Snap": {
|
||||
"enabled": true,
|
||||
"id": "npm:http://localhost:8080/",
|
||||
"initialPermissions": {
|
||||
"snap_confirm": {},
|
||||
"eth_accounts": {},
|
||||
"snap_manageState": {},
|
||||
},
|
||||
"manifest": {
|
||||
"description": "This swap provides developers everywhere access to an entirely new data storage paradigm, even letting your programs store data autonomously. Learn more.",
|
||||
"initialPermissions": {
|
||||
"snap_confirm": {},
|
||||
"eth_accounts": {},
|
||||
"snap_manageState": {},
|
||||
},
|
||||
"manifestVersion": "0.1",
|
||||
"proposedName": "Filecoin Snap",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MetaMask/snaps-skunkworks.git"
|
||||
},
|
||||
"source": {
|
||||
"location": {
|
||||
"npm": {
|
||||
"filePath": "dist/bundle.js",
|
||||
"iconPath": "images/icon.svg",
|
||||
"packageName": "@metamask/example-snap",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
},
|
||||
"shasum": "3lEt0yUu080DwV78neROaAAIQWXukSkMnP4OBhOhBnE="
|
||||
},
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"permissionName": "wallet_snap_npm:http://localhost:8080/",
|
||||
"sourceCode": "(...)",
|
||||
"status": "stopped",
|
||||
"svgIcon": "<svg>...</svg>",
|
||||
"version": "0.6.0"
|
||||
},
|
||||
},
|
||||
accountArray: [
|
||||
{
|
||||
name: 'This is a Really Long Account Name',
|
||||
@ -1030,6 +1109,17 @@ const state = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"local:http://localhost:8080/": {
|
||||
permissions: {
|
||||
'snap_confirm': {
|
||||
invoker: "local:http://localhost:8080/",
|
||||
parentCapability: 'snap_confirm',
|
||||
id: 'a7342F4b-beae-4525-a36c-c0635fd03359',
|
||||
date: 1620710693178,
|
||||
caveats: []
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
permissionActivityLog: [
|
||||
{
|
||||
@ -1172,20 +1262,6 @@ const state = {
|
||||
},
|
||||
},
|
||||
},
|
||||
subjectMetadata: {
|
||||
'https://metamask.github.io': {
|
||||
name: 'E2E Test Dapp',
|
||||
origin: 'https://metamask.github.io',
|
||||
iconUrl: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||
subjectType: 'website',
|
||||
},
|
||||
'https://app.uniswap.org': {
|
||||
name: 'Uniswap',
|
||||
origin: 'https://app.uniswap.org',
|
||||
iconUrl: './UNI.png',
|
||||
subjectType: 'website',
|
||||
},
|
||||
},
|
||||
threeBoxSyncingAllowed: false,
|
||||
showRestorePrompt: true,
|
||||
threeBoxLastUpdated: 0,
|
||||
@ -1212,6 +1288,31 @@ const state = {
|
||||
ensResolutionsByAddress: {},
|
||||
pendingApprovals: {},
|
||||
pendingApprovalCount: 0,
|
||||
subjectMetadata: {
|
||||
"http://localhost:8080": {
|
||||
extensionId: null,
|
||||
iconUrl: null,
|
||||
name: "Hello, Snaps!",
|
||||
origin: "http://localhost:8080",
|
||||
subjectType: "website"
|
||||
},
|
||||
"https://metamask.github.io": {
|
||||
extensionId: null,
|
||||
iconUrl: null,
|
||||
name: "Snaps Iframe Execution Environment",
|
||||
origin: "https://metamask.github.io",
|
||||
subjectType: "website"
|
||||
},
|
||||
"local:http://localhost:8080/": {
|
||||
extensionId: null,
|
||||
iconUrl: null,
|
||||
name: "MetaMask Example Snap",
|
||||
origin: "local:http://localhost:8080/",
|
||||
subjectType: "snap",
|
||||
svgIcon: "<svg>...</svg>",
|
||||
version: "0.6.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
appState: {
|
||||
shouldClose: false,
|
||||
|
8
app/_locales/de/messages.json
generated
8
app/_locales/de/messages.json
generated
@ -1015,10 +1015,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Der Gaspreis, der sich aus der Gaseinschätzung ergibt, ist derzeit nicht verfügbar."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Siehe Adresse, Kontostand, Aktivität und Einleitung von Transaktionen",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Öffentliche Ethereum-Adresse"
|
||||
},
|
||||
@ -2076,6 +2072,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Berechtigungsanfrage"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Siehe Adresse, Kontostand, Aktivität und Einleitung von Transaktionen",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Berechtigungen"
|
||||
},
|
||||
|
8
app/_locales/el/messages.json
generated
8
app/_locales/el/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Το εφεδρικό τέλος συναλλαγής που παρέχεται ως η κύρια υπηρεσία εκτίμησης τελών συναλλαγής, δεν είναι διαθέσιμο αυτή τη στιγμή."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Βλέπε διεύθυνση, υπόλοιπο λογαριασμού, δραστηριότητα και έναρξη συναλλαγών",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Δημόσια Διεύθυνση Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Αίτημα άδειας"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Βλέπε διεύθυνση, υπόλοιπο λογαριασμού, δραστηριότητα και έναρξη συναλλαγών",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Άδειες"
|
||||
},
|
||||
|
99
app/_locales/en/messages.json
generated
99
app/_locales/en/messages.json
generated
@ -248,6 +248,9 @@
|
||||
"approve": {
|
||||
"message": "Approve spend limit"
|
||||
},
|
||||
"approveAndInstall": {
|
||||
"message": "Approve & Install"
|
||||
},
|
||||
"approveButtonText": {
|
||||
"message": "Approve"
|
||||
},
|
||||
@ -261,6 +264,12 @@
|
||||
"approvedAmountWithColon": {
|
||||
"message": "Approved amount:"
|
||||
},
|
||||
"areYouDeveloper": {
|
||||
"message": "Are you a developer?"
|
||||
},
|
||||
"areYouSure": {
|
||||
"message": "Are you sure?"
|
||||
},
|
||||
"asset": {
|
||||
"message": "Asset"
|
||||
},
|
||||
@ -539,6 +548,10 @@
|
||||
"message": "$1 is not connected to any sites.",
|
||||
"description": "$1 is the account name"
|
||||
},
|
||||
"connectedSnapSites": {
|
||||
"message": "$1 snap is connected to these sites. They have access to the permissions listed above.",
|
||||
"description": "$1 represents the name of the snap"
|
||||
},
|
||||
"connecting": {
|
||||
"message": "Connecting..."
|
||||
},
|
||||
@ -1077,10 +1090,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Backup gas price is provided as the main gas estimation service is unavailable right now."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "See address, account balance, activity and suggest transactions to approve",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Ethereum Public Address"
|
||||
},
|
||||
@ -1093,6 +1102,9 @@
|
||||
"etherscanViewOn": {
|
||||
"message": "View on Etherscan"
|
||||
},
|
||||
"expandExperience": {
|
||||
"message": "Expand your web3 experience"
|
||||
},
|
||||
"expandView": {
|
||||
"message": "Expand view"
|
||||
},
|
||||
@ -1673,6 +1685,9 @@
|
||||
"malformedData": {
|
||||
"message": "Malformed data"
|
||||
},
|
||||
"manageSnaps": {
|
||||
"message": "Manage your installed Snaps"
|
||||
},
|
||||
"max": {
|
||||
"message": "Max"
|
||||
},
|
||||
@ -1954,6 +1969,9 @@
|
||||
"noNFTs": {
|
||||
"message": "No NFTs yet"
|
||||
},
|
||||
"noSnaps": {
|
||||
"message": "No Snaps installed"
|
||||
},
|
||||
"noThanks": {
|
||||
"message": "No Thanks"
|
||||
},
|
||||
@ -2155,6 +2173,9 @@
|
||||
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
|
||||
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
|
||||
},
|
||||
"openSourceCode": {
|
||||
"message": "Check the source code"
|
||||
},
|
||||
"optional": {
|
||||
"message": "Optional"
|
||||
},
|
||||
@ -2211,6 +2232,37 @@
|
||||
"permissionRequest": {
|
||||
"message": "Permission request"
|
||||
},
|
||||
"permissionRequestCapitalized": {
|
||||
"message": "Permission Request"
|
||||
},
|
||||
"permission_accessNetwork": {
|
||||
"message": "Access the Internet.",
|
||||
"description": "The description of the `endowment:network-access` permission."
|
||||
},
|
||||
"permission_accessSnap": {
|
||||
"message": "Connect to the $1 Snap.",
|
||||
"description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap."
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Display a confirmation in MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "See address, account balance, activity and suggest transactions to approve",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permission_manageBip44Keys": {
|
||||
"message": "Control your \"$1\" accounts and assets.",
|
||||
"description": "The description for the `snap_getBip44Entropy_*` permission. $1 is the name of a protocol, e.g. 'Filecoin'."
|
||||
},
|
||||
"permission_manageState": {
|
||||
"message": "Store and manage its data on your device.",
|
||||
"description": "The description for the `snap_manageState` permission"
|
||||
},
|
||||
"permission_unknown": {
|
||||
"message": "Unknown permission: $1",
|
||||
"description": "$1 is the name of a requested permission that is not recognized."
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permissions"
|
||||
},
|
||||
@ -2346,6 +2398,12 @@
|
||||
"removeNFT": {
|
||||
"message": "Remove NFT"
|
||||
},
|
||||
"removeSnap": {
|
||||
"message": "Remove Snap"
|
||||
},
|
||||
"removeSnapDescription": {
|
||||
"message": "This action will delete the snap, its data and revoke your given permissions."
|
||||
},
|
||||
"replace": {
|
||||
"message": "replace"
|
||||
},
|
||||
@ -2661,6 +2719,39 @@
|
||||
"slow": {
|
||||
"message": "Slow"
|
||||
},
|
||||
"snapAccess": {
|
||||
"message": "$1 snap has access to:",
|
||||
"description": "$1 represents the name of the snap"
|
||||
},
|
||||
"snapError": {
|
||||
"message": "Snap Error: '$1'. Error Code: '$2'",
|
||||
"description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code."
|
||||
},
|
||||
"snapInstall": {
|
||||
"message": "Install Snap"
|
||||
},
|
||||
"snapInstallWarningCheck": {
|
||||
"message": "To confirm you understand, check all."
|
||||
},
|
||||
"snapInstallWarningKeyAccess": {
|
||||
"message": "You are granting key access to the snap \"$1\". This is irrevocable and grants \"$1\" control of your accounts and assets. Make sure you trust \"$1\" before proceeding.",
|
||||
"description": "The parameter is the name of the snap"
|
||||
},
|
||||
"snapRequestsPermission": {
|
||||
"message": "This snap is requesting the following permissions:"
|
||||
},
|
||||
"snaps": {
|
||||
"message": "Snaps"
|
||||
},
|
||||
"snapsSettingsDescription": {
|
||||
"message": "Manage your Snaps"
|
||||
},
|
||||
"snapsStatus": {
|
||||
"message": "Snap status is dependent on activity."
|
||||
},
|
||||
"snapsToggle": {
|
||||
"message": "A snap will only run if it is enabled"
|
||||
},
|
||||
"somethingWentWrong": {
|
||||
"message": "Oops! Something went wrong."
|
||||
},
|
||||
|
8
app/_locales/es/messages.json
generated
8
app/_locales/es/messages.json
generated
@ -665,10 +665,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Se muestra el precio del gas de respaldo, ya que el servicio para calcular el precio del gas principal no se encuentra disponible en este momento."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Ver las direcciones de las cuentas permitidas (requerido)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Dirección pública de Ethereum"
|
||||
},
|
||||
@ -1296,6 +1292,10 @@
|
||||
"pending": {
|
||||
"message": "Pendiente"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Ver las direcciones de las cuentas permitidas (requerido)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permisos"
|
||||
},
|
||||
|
8
app/_locales/es_419/messages.json
generated
8
app/_locales/es_419/messages.json
generated
@ -1035,10 +1035,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Se muestra el precio del gas de respaldo, ya que el servicio para calcular el precio del gas principal no se encuentra disponible en este momento."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Ver dirección, saldo de cuenta, actividad e iniciar transacciones",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Dirección pública de Ethereum"
|
||||
},
|
||||
@ -2125,6 +2121,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Solicitud de permiso"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Ver dirección, saldo de cuenta, actividad e iniciar transacciones",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permisos"
|
||||
},
|
||||
|
8
app/_locales/fr/messages.json
generated
8
app/_locales/fr/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Le prix de carburant de sauvegarde est fourni, car le service principal d’estimation du carburant est momentanément indisponible."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Consultez l’adresse, le solde du compte et l’activité, et lancez des transactions",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Adresse publique d’Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Demande d’autorisation"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Consultez l’adresse, le solde du compte et l’activité, et lancez des transactions",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Autorisations"
|
||||
},
|
||||
|
8
app/_locales/hi/messages.json
generated
8
app/_locales/hi/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "बैकअप गैस की कीमत प्रदान की जाती है क्योंकि मुख्य गैस अनुमान सर्विस अभी उपलब्ध नहीं है।"
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "पता, खाते की शेषराशि, गतिविधि देखें और लेन-देन शुरू करें",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Ethereum सार्वजनिक पता"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "अनुमति अनुरोध"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "पता, खाते की शेषराशि, गतिविधि देखें और लेन-देन शुरू करें",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "अनुमतियाँ"
|
||||
},
|
||||
|
8
app/_locales/id/messages.json
generated
8
app/_locales/id/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Biaya gas cadangan diberikan karena layanan estimasi gas utama saat ini tidak tersedia."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Lihat alamat, saldo akun, aktivitas, dan mulai transaksi",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Alamat Publik Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Permohonan izin"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Lihat alamat, saldo akun, aktivitas, dan mulai transaksi",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Izin"
|
||||
},
|
||||
|
8
app/_locales/it/messages.json
generated
8
app/_locales/it/messages.json
generated
@ -565,10 +565,6 @@
|
||||
"estimatedProcessingTimes": {
|
||||
"message": "Tempi di Elaborazione Stimati"
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Accesso agli indirizzi dei tuoi account autorizzati (richiesto)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Indirizzo pubblico Ethereum "
|
||||
},
|
||||
@ -1054,6 +1050,10 @@
|
||||
"pending": {
|
||||
"message": "in corso"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Accesso agli indirizzi dei tuoi account autorizzati (richiesto)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permessi"
|
||||
},
|
||||
|
8
app/_locales/ja/messages.json
generated
8
app/_locales/ja/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "現在メインのガスの見積もりサービスが利用できないため、バックアップのガス代が提供されています。"
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "アドレス、アカウント残高、アクティビティを表示してトランザクションを開始",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "パブリックイーサリアムアドレス"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "許可のリクエスト"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "アドレス、アカウント残高、アクティビティを表示してトランザクションを開始",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "許可"
|
||||
},
|
||||
|
8
app/_locales/ko/messages.json
generated
8
app/_locales/ko/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "현재 주요 가스 견적 서비스를 사용할 수 없으므로 백업 가스 가격을 제공합니다."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "허용되는 계정의 주소 보기(필수)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "이더리움 공개 주소"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "승인 요청"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "허용되는 계정의 주소 보기(필수)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "권한"
|
||||
},
|
||||
|
8
app/_locales/ph/messages.json
generated
8
app/_locales/ph/messages.json
generated
@ -668,10 +668,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Ibinibigay ang backup na presyo ng gas dahil hindi available ang pangunahing serbisyo sa pagtatantya ng gas sa ngayon."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Tingnan ang mga address ng iyong mga pinapayagang account (kinakailangan)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Pampublikong Address ng Ethereum"
|
||||
},
|
||||
@ -1321,6 +1317,10 @@
|
||||
"pending": {
|
||||
"message": "Nakabinbin"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Tingnan ang mga address ng iyong mga pinapayagang account (kinakailangan)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Mga Pahintulot"
|
||||
},
|
||||
|
8
app/_locales/pt_BR/messages.json
generated
8
app/_locales/pt_BR/messages.json
generated
@ -1019,10 +1019,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "O preço de backup do gás é fornecido porque a estimativa de gás principal está indisponível no momento."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Ver endereço, saldo da conta, atividade e iniciar transações",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Endereço público do Ethereum"
|
||||
},
|
||||
@ -2109,6 +2105,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Solicitação de permissão"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Ver endereço, saldo da conta, atividade e iniciar transações",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permissões"
|
||||
},
|
||||
|
8
app/_locales/ru/messages.json
generated
8
app/_locales/ru/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Указана резервная цена газа, поскольку основной сервис определения цены газа сейчас недоступен."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "См. адрес, баланс счета, активность и инициируйте транзакции",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Открытый адрес Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Запрос разрешения"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "См. адрес, баланс счета, активность и инициируйте транзакции",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Разрешения"
|
||||
},
|
||||
|
8
app/_locales/tl/messages.json
generated
8
app/_locales/tl/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Ang backup gas price ay inilalaan dahil ang pangunahing pagtantiya ng presyo ng gas ay hindi available sa ngayon."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Tingnan ang mga address, balanse ng account, aktibidad at simulan ang iyong mga transaksyon",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Pampublikong Address ng Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Kahilingan sa pahintulot"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Tingnan ang mga address, balanse ng account, aktibidad at simulan ang iyong mga transaksyon",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Mga Pahintulot"
|
||||
},
|
||||
|
8
app/_locales/tr/messages.json
generated
8
app/_locales/tr/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Ana gaz tahmini hizmeti olarak sunulan yedek gaz fiyatı şu anda kullanılamıyor."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Adrese, hesap bakiyesine, aktiviteye bakın ve işlemleri başlatın",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Ethereum Genel Adresi"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "İzin talebi"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Adrese, hesap bakiyesine, aktiviteye bakın ve işlemleri başlatın",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "İzinler"
|
||||
},
|
||||
|
8
app/_locales/vi/messages.json
generated
8
app/_locales/vi/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "Giá gas dự phòng được cung cấp vì dịch vụ ước tính giá gas chính hiện không hoạt động."
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "Xem địa chỉ, số dư tài khoản, hoạt động và bắt đầu giao dịch",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Địa chỉ công khai trên Ethereum"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "Yêu cầu quyền"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "Xem địa chỉ, số dư tài khoản, hoạt động và bắt đầu giao dịch",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Quyền"
|
||||
},
|
||||
|
8
app/_locales/zh_CN/messages.json
generated
8
app/_locales/zh_CN/messages.json
generated
@ -1005,10 +1005,6 @@
|
||||
"ethGasPriceFetchWarning": {
|
||||
"message": "由于目前主要的燃料估算服务不可用,因此提供了备用燃料价格。"
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "查看您允许的账户的地址(必填)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "以太坊 Ethereum 公开地址"
|
||||
},
|
||||
@ -2079,6 +2075,10 @@
|
||||
"permissionRequest": {
|
||||
"message": "权限请求"
|
||||
},
|
||||
"permission_ethereumAccounts": {
|
||||
"message": "查看您允许的账户的地址(必填)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
},
|
||||
"permissions": {
|
||||
"message": "权限"
|
||||
},
|
||||
|
@ -0,0 +1,34 @@
|
||||
import {
|
||||
restrictedMethodPermissionBuilders,
|
||||
selectHooks,
|
||||
} from '@metamask/rpc-methods';
|
||||
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
|
||||
|
||||
/**
|
||||
* @returns {Record<string, Record<string, unknown>>} All endowment permission
|
||||
* specifications.
|
||||
*/
|
||||
export const buildSnapEndowmentSpecifications = () =>
|
||||
Object.values(endowmentPermissionBuilders).reduce(
|
||||
(allSpecifications, { targetKey, specificationBuilder }) => {
|
||||
allSpecifications[targetKey] = specificationBuilder();
|
||||
return allSpecifications;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {Record<string, Function>} hooks - The hooks for the Snap
|
||||
* restricted method implementations.
|
||||
*/
|
||||
export function buildSnapRestrictedMethodSpecifications(hooks) {
|
||||
return Object.values(restrictedMethodPermissionBuilders).reduce(
|
||||
(specifications, { targetKey, specificationBuilder, methodHooks }) => {
|
||||
specifications[targetKey] = specificationBuilder({
|
||||
methodHooks: selectHooks(hooks, methodHooks),
|
||||
});
|
||||
return specifications;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import {
|
||||
EndowmentPermissions,
|
||||
RestrictedMethods,
|
||||
} from '../../../../../shared/constants/permissions';
|
||||
import {
|
||||
buildSnapEndowmentSpecifications,
|
||||
buildSnapRestrictedMethodSpecifications,
|
||||
} from './snap-permissions';
|
||||
|
||||
describe('buildSnapRestrictedMethodSpecifications', () => {
|
||||
it('creates valid permission specification objects', () => {
|
||||
const hooks = {
|
||||
addSnap: () => undefined,
|
||||
clearSnapState: () => undefined,
|
||||
getMnemonic: () => undefined,
|
||||
getSnap: () => undefined,
|
||||
getSnapRpcHandler: () => undefined,
|
||||
getSnapState: () => undefined,
|
||||
showConfirmation: () => undefined,
|
||||
updateSnapState: () => undefined,
|
||||
};
|
||||
|
||||
const specifications = buildSnapRestrictedMethodSpecifications(hooks);
|
||||
|
||||
const allRestrictedMethods = Object.keys(RestrictedMethods);
|
||||
Object.keys(specifications).forEach((permissionKey) =>
|
||||
expect(allRestrictedMethods).toContain(permissionKey),
|
||||
);
|
||||
|
||||
Object.values(specifications).forEach((specification) => {
|
||||
expect(specification).toMatchObject({
|
||||
targetKey: expect.stringMatching(/^(snap_|wallet_)/u),
|
||||
methodImplementation: expect.any(Function),
|
||||
allowedCaveats: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildSnapEndowmentSpecifications', () => {
|
||||
it('creates valid permission specification objects', () => {
|
||||
expect(
|
||||
Object.keys(buildSnapEndowmentSpecifications()).sort(),
|
||||
).toStrictEqual(Object.keys(EndowmentPermissions).sort());
|
||||
});
|
||||
});
|
@ -4,3 +4,6 @@ export * from './enums';
|
||||
export * from './permission-log';
|
||||
export * from './specifications';
|
||||
export * from './selectors';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
export * from './flask/snap-permissions';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { constructPermission } from '@metamask/snap-controllers';
|
||||
import {
|
||||
constructPermission,
|
||||
PermissionType,
|
||||
} from '@metamask/snap-controllers';
|
||||
import {
|
||||
CaveatTypes,
|
||||
RestrictedMethods,
|
||||
@ -90,6 +93,7 @@ export const getPermissionSpecifications = ({
|
||||
}) => {
|
||||
return {
|
||||
[PermissionKeys.eth_accounts]: {
|
||||
permissionType: PermissionType.RestrictedMethod,
|
||||
targetKey: PermissionKeys.eth_accounts,
|
||||
allowedCaveats: [CaveatTypes.restrictReturnedAccounts],
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import { handlers as permittedSnapMethods } from '@metamask/rpc-methods/dist/permitted';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { flatten } from 'lodash';
|
||||
import { permissionRpcMethods } from '@metamask/snap-controllers';
|
||||
import { selectHooks } from '@metamask/rpc-methods';
|
||||
@ -30,7 +33,7 @@ const expectedHookNames = Array.from(
|
||||
* controllers.
|
||||
* @returns {(req: Object, res: Object, next: Function, end: Function) => void}
|
||||
*/
|
||||
export default function createMethodMiddleware(hooks) {
|
||||
export function createMethodMiddleware(hooks) {
|
||||
// Fail immediately if we forgot to provide any expected hooks.
|
||||
const missingHookNames = expectedHookNames.filter(
|
||||
(hookName) => !Object.hasOwnProperty.call(hooks, hookName),
|
||||
@ -60,6 +63,7 @@ export default function createMethodMiddleware(hooks) {
|
||||
selectHooks(hooks, hookNames),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return end(error);
|
||||
}
|
||||
}
|
||||
@ -67,3 +71,40 @@ export default function createMethodMiddleware(hooks) {
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
const snapHandlerMap = permittedSnapMethods.reduce((map, handler) => {
|
||||
for (const methodName of handler.methodNames) {
|
||||
map.set(methodName, handler);
|
||||
}
|
||||
return map;
|
||||
}, new Map());
|
||||
|
||||
export function createSnapMethodMiddleware(isSnap, hooks) {
|
||||
return async function methodMiddleware(req, res, next, end) {
|
||||
const handler = snapHandlerMap.get(req.method);
|
||||
if (handler) {
|
||||
if (/^snap_/iu.test(req.method) && !isSnap) {
|
||||
return end(ethErrors.rpc.methodNotFound());
|
||||
}
|
||||
|
||||
const { implementation, hookNames } = handler;
|
||||
try {
|
||||
// Implementations may or may not be async, so we must await them.
|
||||
return await implementation(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
end,
|
||||
selectHooks(hooks, hookNames),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return end(error);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './createMethodMiddleware';
|
||||
export * from './createMethodMiddleware';
|
||||
|
@ -38,7 +38,13 @@ import {
|
||||
import {
|
||||
PermissionController,
|
||||
SubjectMetadataController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SnapController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '@metamask/snap-controllers';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import { IframeExecutionService } from '@metamask/iframe-execution-environment-service';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
import {
|
||||
TRANSACTION_STATUSES,
|
||||
@ -57,11 +63,17 @@ import {
|
||||
import {
|
||||
CaveatTypes,
|
||||
RestrictedMethods,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
EndowmentPermissions,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../shared/constants/permissions';
|
||||
import { UI_NOTIFICATIONS } from '../../shared/notifications';
|
||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||
import { MILLISECOND } from '../../shared/constants/time';
|
||||
import {
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
MESSAGE_TYPE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
POLLING_TOKEN_ENVIRONMENT_TYPES,
|
||||
SUBJECT_TYPES,
|
||||
} from '../../shared/constants/app';
|
||||
@ -73,7 +85,12 @@ import { isEqualCaseInsensitive } from '../../ui/helpers/utils/util';
|
||||
import ComposableObservableStore from './lib/ComposableObservableStore';
|
||||
import AccountTracker from './lib/account-tracker';
|
||||
import createLoggerMiddleware from './lib/createLoggerMiddleware';
|
||||
import createMethodMiddleware from './lib/rpc-method-middleware';
|
||||
import {
|
||||
createMethodMiddleware,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
createSnapMethodMiddleware,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from './lib/rpc-method-middleware';
|
||||
import createOriginMiddleware from './lib/createOriginMiddleware';
|
||||
import createTabIdMiddleware from './lib/createTabIdMiddleware';
|
||||
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
|
||||
@ -107,9 +124,13 @@ import {
|
||||
getPermissionBackgroundApiMethods,
|
||||
getPermissionSpecifications,
|
||||
getPermittedAccountsByOrigin,
|
||||
PermissionLogController,
|
||||
NOTIFICATION_NAMES,
|
||||
PermissionLogController,
|
||||
unrestrictedMethods,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
buildSnapEndowmentSpecifications,
|
||||
buildSnapRestrictedMethodSpecifications,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from './controllers/permissions';
|
||||
|
||||
export const METAMASK_CONTROLLER_EVENTS = {
|
||||
@ -500,36 +521,41 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
state: initState.PermissionController,
|
||||
caveatSpecifications: getCaveatSpecifications({ getIdentities }),
|
||||
permissionSpecifications: getPermissionSpecifications({
|
||||
getIdentities,
|
||||
getAllAccounts: this.keyringController.getAccounts.bind(
|
||||
this.keyringController,
|
||||
),
|
||||
captureKeyringTypesWithMissingIdentities: (
|
||||
identities = {},
|
||||
accounts = [],
|
||||
) => {
|
||||
const accountsMissingIdentities = accounts.filter(
|
||||
(address) => !identities[address],
|
||||
);
|
||||
const keyringTypesWithMissingIdentities = accountsMissingIdentities.map(
|
||||
(address) =>
|
||||
this.keyringController.getKeyringForAccount(address)?.type,
|
||||
);
|
||||
permissionSpecifications: {
|
||||
...getPermissionSpecifications({
|
||||
getIdentities,
|
||||
getAllAccounts: this.keyringController.getAccounts.bind(
|
||||
this.keyringController,
|
||||
),
|
||||
captureKeyringTypesWithMissingIdentities: (
|
||||
identities = {},
|
||||
accounts = [],
|
||||
) => {
|
||||
const accountsMissingIdentities = accounts.filter(
|
||||
(address) => !identities[address],
|
||||
);
|
||||
const keyringTypesWithMissingIdentities = accountsMissingIdentities.map(
|
||||
(address) =>
|
||||
this.keyringController.getKeyringForAccount(address)?.type,
|
||||
);
|
||||
|
||||
const identitiesCount = Object.keys(identities || {}).length;
|
||||
const identitiesCount = Object.keys(identities || {}).length;
|
||||
|
||||
const accountTrackerCount = Object.keys(
|
||||
this.accountTracker.store.getState().accounts || {},
|
||||
).length;
|
||||
const accountTrackerCount = Object.keys(
|
||||
this.accountTracker.store.getState().accounts || {},
|
||||
).length;
|
||||
|
||||
captureException(
|
||||
new Error(
|
||||
`Attempt to get permission specifications failed because their were ${accounts.length} accounts, but ${identitiesCount} identities, and the ${keyringTypesWithMissingIdentities} keyrings included accounts with missing identities. Meanwhile, there are ${accountTrackerCount} accounts in the account tracker.`,
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
captureException(
|
||||
new Error(
|
||||
`Attempt to get permission specifications failed because their were ${accounts.length} accounts, but ${identitiesCount} identities, and the ${keyringTypesWithMissingIdentities} keyrings included accounts with missing identities. Meanwhile, there are ${accountTrackerCount} accounts in the account tracker.`,
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
...this.getSnapPermissionSpecifications(),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
},
|
||||
unrestrictedMethods,
|
||||
});
|
||||
|
||||
@ -547,6 +573,53 @@ export default class MetamaskController extends EventEmitter {
|
||||
subjectCacheLimit: 100,
|
||||
});
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
this.workerController = new IframeExecutionService({
|
||||
onError: this.onExecutionEnvironmentError.bind(this),
|
||||
iframeUrl: new URL(
|
||||
'https://metamask.github.io/iframe-execution-environment/0.3.1',
|
||||
),
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'ExecutionService',
|
||||
}),
|
||||
setupSnapProvider: this.setupSnapProvider.bind(this),
|
||||
});
|
||||
|
||||
const snapControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'SnapController',
|
||||
allowedEvents: [
|
||||
'ExecutionService:unhandledError',
|
||||
'ExecutionService:unresponsive',
|
||||
],
|
||||
allowedActions: [
|
||||
`${this.permissionController.name}:getEndowments`,
|
||||
`${this.permissionController.name}:getPermissions`,
|
||||
`${this.permissionController.name}:hasPermission`,
|
||||
`${this.permissionController.name}:requestPermissions`,
|
||||
`${this.permissionController.name}:revokeAllPermissions`,
|
||||
],
|
||||
});
|
||||
|
||||
this.snapController = new SnapController({
|
||||
endowmentPermissionNames: Object.values(EndowmentPermissions),
|
||||
terminateAllSnaps: this.workerController.terminateAllSnaps.bind(
|
||||
this.workerController,
|
||||
),
|
||||
terminateSnap: this.workerController.terminateSnap.bind(
|
||||
this.workerController,
|
||||
),
|
||||
executeSnap: this.workerController.executeSnap.bind(
|
||||
this.workerController,
|
||||
),
|
||||
getRpcMessageHandler: this.workerController.getRpcMessageHandler.bind(
|
||||
this.workerController,
|
||||
),
|
||||
closeAllConnections: this.removeAllConnections.bind(this),
|
||||
state: initState.SnapController,
|
||||
messenger: snapControllerMessenger,
|
||||
});
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
this.detectTokensController = new DetectTokensController({
|
||||
preferences: this.preferencesController,
|
||||
tokensController: this.tokensController,
|
||||
@ -799,6 +872,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
TokenListController: this.tokenListController,
|
||||
TokensController: this.tokensController,
|
||||
CollectiblesController: this.collectiblesController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SnapController: this.snapController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
});
|
||||
|
||||
this.memStore = new ComposableObservableStore({
|
||||
@ -835,6 +911,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
TokenListController: this.tokenListController,
|
||||
TokensController: this.tokensController,
|
||||
CollectiblesController: this.collectiblesController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SnapController: this.snapController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
},
|
||||
controllerMessenger: this.controllerMessenger,
|
||||
});
|
||||
@ -866,6 +945,58 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.publicConfigStore = this.createPublicConfigStore();
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
/**
|
||||
* Constructor helper for getting Snap permission specifications.
|
||||
*/
|
||||
getSnapPermissionSpecifications() {
|
||||
return {
|
||||
...buildSnapEndowmentSpecifications(),
|
||||
...buildSnapRestrictedMethodSpecifications({
|
||||
addSnap: this.controllerMessenger.call.bind(
|
||||
this.controllerMessenger,
|
||||
'SnapController:add',
|
||||
),
|
||||
clearSnapState: (fromSubject) =>
|
||||
this.controllerMessenger(
|
||||
'SnapController:updateSnap',
|
||||
fromSubject,
|
||||
{},
|
||||
),
|
||||
getMnemonic: this.getPrimaryKeyringMnemonic.bind(this),
|
||||
getSnap: this.controllerMessenger.call.bind(
|
||||
this.controllerMessenger,
|
||||
'SnapController:get',
|
||||
),
|
||||
getSnapRpcHandler: this.controllerMessenger.call.bind(
|
||||
this.controllerMessenger,
|
||||
'SnapController:getRpcMessageHandler',
|
||||
),
|
||||
getSnapState: async (...args) => {
|
||||
// TODO:flask Just return the action result directly in the next
|
||||
// @metamask/snap-controllers update.
|
||||
return (
|
||||
(await this.controllerMessenger.call(
|
||||
'SnapController:getSnapState',
|
||||
...args,
|
||||
)) ?? null
|
||||
);
|
||||
},
|
||||
showConfirmation: (origin, confirmationData) =>
|
||||
this.approvalController.addAndShowApprovalRequest({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SNAP_CONFIRM,
|
||||
requestData: confirmationData,
|
||||
}),
|
||||
updateSnapState: this.controllerMessenger.call.bind(
|
||||
this.controllerMessenger,
|
||||
'SnapController:updateSnapState',
|
||||
),
|
||||
}),
|
||||
};
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
/**
|
||||
* Sets up BaseController V2 event subscriptions. Currently, this includes
|
||||
* the subscriptions necessary to notify permission subjects of account
|
||||
@ -929,6 +1060,39 @@ export default class MetamaskController extends EventEmitter {
|
||||
},
|
||||
getPermittedAccountsByOrigin,
|
||||
);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
// Record Snap metadata whenever a Snap is added to state.
|
||||
this.controllerMessenger.subscribe(
|
||||
`${this.snapController.name}:snapAdded`,
|
||||
(snapId, snap, svgIcon = null) => {
|
||||
const {
|
||||
manifest: { proposedName },
|
||||
version,
|
||||
} = snap;
|
||||
this.subjectMetadataController.addSubjectMetadata({
|
||||
subjectType: SUBJECT_TYPES.SNAP,
|
||||
name: proposedName,
|
||||
origin: snapId,
|
||||
version,
|
||||
svgIcon,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
this.controllerMessenger.subscribe(
|
||||
`${this.snapController.name}:snapInstalled`,
|
||||
(snapId) => {
|
||||
this.metaMetricsController.trackEvent({
|
||||
event: 'Snap Installed',
|
||||
category: 'Snaps',
|
||||
properties: {
|
||||
snap_id: snapId,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1400,6 +1564,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
...getPermissionBackgroundApiMethods(permissionController),
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
// snaps
|
||||
removeSnapError: this.snapController.removeSnapError.bind(
|
||||
this.snapController,
|
||||
),
|
||||
disableSnap: this.snapController.disableSnap.bind(this.snapController),
|
||||
enableSnap: this.snapController.enableSnap.bind(this.snapController),
|
||||
removeSnap: this.removeSnap.bind(this),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// swaps
|
||||
fetchAndSetQuotes: swapsController.fetchAndSetQuotes.bind(
|
||||
swapsController,
|
||||
@ -1826,6 +2000,17 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.preferencesController.setSelectedAddress(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mnemonic of the user's primary keyring.
|
||||
*/
|
||||
getPrimaryKeyringMnemonic() {
|
||||
const keyring = this.keyringController.getKeyringsByType('HD Key Tree')[0];
|
||||
if (!keyring.mnemonic) {
|
||||
throw new Error('Primary keyring mnemonic unavailable.');
|
||||
}
|
||||
return keyring.mnemonic;
|
||||
}
|
||||
|
||||
//
|
||||
// Hardware
|
||||
//
|
||||
@ -2201,6 +2386,32 @@ export default class MetamaskController extends EventEmitter {
|
||||
return await promise;
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
/**
|
||||
* Gets an "app key" corresponding to an Ethereum address. An app key is more
|
||||
* or less an addrdess hashed together with some string, in this case a
|
||||
* subject identifier / origin.
|
||||
*
|
||||
* @todo Figure out a way to derive app keys that doesn't depend on the user's
|
||||
* Ethereum addresses.
|
||||
* @param {string} subject - The identifier of the subject whose app key to
|
||||
* retrieve.
|
||||
* @param {string} [requestedAccount] - The account whose app key to retrieve.
|
||||
* The first account in the keyring will be used by default.
|
||||
*/
|
||||
async getAppKeyForSubject(subject, requestedAccount) {
|
||||
let account;
|
||||
|
||||
if (requestedAccount) {
|
||||
account = requestedAccount;
|
||||
} else {
|
||||
account = (await this.keyringController.getAccounts())[0];
|
||||
}
|
||||
|
||||
return this.keyringController.exportAppKeyForAddress(account, subject);
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
/**
|
||||
* Signifies user intent to complete an eth_sign method.
|
||||
*
|
||||
@ -2671,6 +2882,13 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @property {string} - The URL of the page or frame hosting the script that sent the message.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Snap sender object.
|
||||
*
|
||||
* @typedef {Object} SnapSender
|
||||
* @property {string} snapId - The ID of the snap.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used to create a multiplexed stream for connecting to an untrusted context
|
||||
* like a Dapp or other extension.
|
||||
@ -2682,6 +2900,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
setupUntrustedCommunication({ connectionStream, sender, subjectType }) {
|
||||
const { usePhishDetect } = this.preferencesController.store.getState();
|
||||
|
||||
let _subjectType;
|
||||
if (subjectType) {
|
||||
_subjectType = subjectType;
|
||||
@ -2794,14 +3013,20 @@ export default class MetamaskController extends EventEmitter {
|
||||
* A method for serving our ethereum provider over a given stream.
|
||||
*
|
||||
* @param {*} outStream - The stream to provide over.
|
||||
* @param {MessageSender} sender - The sender of the messages on this stream
|
||||
* @param {MessageSender | SnapSender} sender - The sender of the messages on this stream
|
||||
* @param {string} subjectType - The type of the sender, i.e. subject.
|
||||
*/
|
||||
setupProviderConnection(outStream, sender, subjectType) {
|
||||
let origin;
|
||||
if (subjectType === SUBJECT_TYPES.INTERNAL) {
|
||||
origin = 'metamask';
|
||||
} else {
|
||||
}
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
else if (subjectType === SUBJECT_TYPES.SNAP) {
|
||||
origin = sender.snapId;
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
else {
|
||||
origin = new URL(sender.url).origin;
|
||||
}
|
||||
|
||||
@ -2820,9 +3045,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const engine = this.setupProviderEngine({
|
||||
origin,
|
||||
tabId,
|
||||
sender,
|
||||
subjectType,
|
||||
tabId,
|
||||
});
|
||||
|
||||
// setup connection
|
||||
@ -2844,6 +3069,33 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
/**
|
||||
* For snaps running in workers.
|
||||
*
|
||||
* @param snapId
|
||||
* @param error
|
||||
*/
|
||||
onExecutionEnvironmentError(snapId, error) {
|
||||
this.snapController.stopPlugin(snapId);
|
||||
this.snapController.addSnapError(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* For snaps running in workers.
|
||||
*
|
||||
* @param snapId
|
||||
* @param connectionStream
|
||||
*/
|
||||
setupSnapProvider(snapId, connectionStream) {
|
||||
this.setupUntrustedCommunication({
|
||||
connectionStream,
|
||||
sender: { snapId },
|
||||
subjectType: SUBJECT_TYPES.SNAP,
|
||||
});
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
/**
|
||||
* A method for creating a provider that is safely restricted for the requesting subject.
|
||||
*
|
||||
@ -2872,13 +3124,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// append origin to each request
|
||||
engine.push(createOriginMiddleware({ origin }));
|
||||
|
||||
// append tabId to each request if it exists
|
||||
if (tabId) {
|
||||
engine.push(createTabIdMiddleware({ tabId }));
|
||||
}
|
||||
|
||||
// logging
|
||||
engine.push(createLoggerMiddleware({ origin }));
|
||||
engine.push(this.permissionLogController.createMiddleware());
|
||||
|
||||
// onboarding
|
||||
if (subjectType === SUBJECT_TYPES.WEBSITE) {
|
||||
engine.push(
|
||||
@ -2888,6 +3143,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Unrestricted/permissionless RPC method implementations
|
||||
engine.push(
|
||||
createMethodMiddleware({
|
||||
origin,
|
||||
@ -2975,6 +3232,34 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
engine.push(
|
||||
createSnapMethodMiddleware(subjectType === SUBJECT_TYPES.SNAP, {
|
||||
getAppKey: this.getAppKeyForSubject.bind(this, origin),
|
||||
getSnaps: this.snapController.getPermittedSnaps.bind(
|
||||
this.snapController,
|
||||
origin,
|
||||
),
|
||||
requestPermissions: async (requestedPermissions) => {
|
||||
const [
|
||||
approvedPermissions,
|
||||
] = await this.permissionController.requestPermissions(
|
||||
{ origin },
|
||||
requestedPermissions,
|
||||
);
|
||||
|
||||
return Object.values(approvedPermissions);
|
||||
},
|
||||
getAccounts: this.getPermittedAccounts.bind(this, origin),
|
||||
installSnaps: this.snapController.installSnaps.bind(
|
||||
this.snapController,
|
||||
origin,
|
||||
),
|
||||
}),
|
||||
);
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// filter and subscription polyfills
|
||||
engine.push(filterMiddleware);
|
||||
engine.push(subscriptionManager.middleware);
|
||||
@ -2986,6 +3271,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// forward to metamask primary provider
|
||||
engine.push(providerAsMiddleware(provider));
|
||||
return engine;
|
||||
@ -3060,6 +3346,24 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all connections for the given origin, and removes the references
|
||||
* to them.
|
||||
* Ignores unknown origins.
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
*/
|
||||
removeAllConnections(origin) {
|
||||
const connections = this.connections[origin];
|
||||
if (!connections) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(connections).forEach((id) => {
|
||||
this.removeConnection(origin, id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the RPC engines associated with the connections to the given origin
|
||||
* to emit a notification event with the given payload.
|
||||
@ -3513,4 +3817,23 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
return this.keyringController.setLocked();
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
// SNAPS
|
||||
/**
|
||||
* Removes the specified snap, and all of its associated permissions.
|
||||
* If we didn't revoke the permission to access the snap from all subjects,
|
||||
* they could just reinstall without any confirmation.
|
||||
*
|
||||
* TODO: This should be implemented in `SnapController.removeSnap` via a controller action.
|
||||
*
|
||||
* @param {{ id: string, permissionName: string }} snap - The wrapper object of the snap to remove.
|
||||
*/
|
||||
removeSnap(snap) {
|
||||
this.snapController.removeSnap(snap.id);
|
||||
this.permissionController.revokePermissionForAllSubjects(
|
||||
snap.permissionName,
|
||||
);
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ this.store.updateStructure({
|
||||
...,
|
||||
GasFeeController: this.gasFeeController,
|
||||
TokenListController: this.tokenListController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(beta)
|
||||
PluginController: this.pluginController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SnapController: this.snapController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
});
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/app/scripts/controllers/permissions/*.js',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
||||
'<rootDir>/shared/**/*.js',
|
||||
'<rootDir>/ui/**/*.js',
|
||||
],
|
||||
@ -14,7 +14,7 @@ module.exports = {
|
||||
lines: 43,
|
||||
statements: 43,
|
||||
},
|
||||
'./app/scripts/controllers/permissions/*.js': {
|
||||
'./app/scripts/controllers/permissions/**/*.js': {
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
@ -33,7 +33,7 @@ module.exports = {
|
||||
'<rootDir>/app/scripts/migrations/*.test.js',
|
||||
'<rootDir>/app/scripts/platforms/*.test.js',
|
||||
'<rootDir>app/scripts/controllers/network/**/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/permissions/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
||||
],
|
||||
testTimeout: 2500,
|
||||
transform: {
|
||||
|
@ -10,6 +10,11 @@
|
||||
"@babel/runtime": true
|
||||
}
|
||||
},
|
||||
"keccak": {
|
||||
"packages": {
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"globals": {
|
||||
"fetch": true
|
||||
|
@ -700,11 +700,11 @@
|
||||
},
|
||||
"@metamask/snap-controllers": {
|
||||
"globals": {
|
||||
"URL": true,
|
||||
"Worker": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.log": true,
|
||||
"console.warn": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
@ -715,14 +715,22 @@
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/safe-event-emitter": true,
|
||||
"@metamask/snap-workers": true,
|
||||
"ajv": true,
|
||||
"buffer": true,
|
||||
"concat-stream": true,
|
||||
"crypto-browserify": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
"fast-deep-equal": true,
|
||||
"gunzip-maybe": true,
|
||||
"immer": true,
|
||||
"json-rpc-engine": true,
|
||||
"json-rpc-middleware-stream": true,
|
||||
"nanoid": true,
|
||||
"pump": true
|
||||
"pump": true,
|
||||
"readable-web-to-node-stream": true,
|
||||
"semver": true,
|
||||
"tar-stream": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
@ -973,6 +981,11 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"packages": {
|
||||
"fast-deep-equal": true
|
||||
}
|
||||
},
|
||||
"analytics-node": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1158,6 +1171,7 @@
|
||||
"bl": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
@ -1270,6 +1284,16 @@
|
||||
"btoa": true
|
||||
}
|
||||
},
|
||||
"browserify-zlib": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"buffer": true,
|
||||
"pako": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"bs58": {
|
||||
"packages": {
|
||||
"base-x": true
|
||||
@ -1296,6 +1320,11 @@
|
||||
"ieee754": true
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"buffer-split": {
|
||||
"packages": {
|
||||
"buffer-indexof": true
|
||||
@ -1426,6 +1455,13 @@
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"concat-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"constant-case": {
|
||||
"packages": {
|
||||
"snake-case": true,
|
||||
@ -1707,6 +1743,16 @@
|
||||
"stream-browserify": true
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"stream-shift": true
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"packages": {
|
||||
"bn.js": true,
|
||||
@ -2212,6 +2258,11 @@
|
||||
"postMessage": true
|
||||
}
|
||||
},
|
||||
"fs-constants": {
|
||||
"packages": {
|
||||
"constants-browserify": true
|
||||
}
|
||||
},
|
||||
"fsm-event": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
@ -2280,6 +2331,16 @@
|
||||
"superagent": true
|
||||
}
|
||||
},
|
||||
"gunzip-maybe": {
|
||||
"packages": {
|
||||
"browserify-zlib": true,
|
||||
"is-deflate": true,
|
||||
"is-gzip": true,
|
||||
"peek-stream": true,
|
||||
"pumpify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"hamt-sharding": {
|
||||
"packages": {
|
||||
"is-buffer": true,
|
||||
@ -3930,6 +3991,14 @@
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"peek-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"buffer-from": true,
|
||||
"duplexify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"peer-book": {
|
||||
"packages": {
|
||||
"bs58": true,
|
||||
@ -4152,6 +4221,13 @@
|
||||
"process": true
|
||||
}
|
||||
},
|
||||
"pumpify": {
|
||||
"packages": {
|
||||
"duplexify": true,
|
||||
"inherits": true,
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"globals": {
|
||||
"define": true
|
||||
@ -4465,6 +4541,11 @@
|
||||
"util-deprecate": true
|
||||
}
|
||||
},
|
||||
"readable-web-to-node-stream": {
|
||||
"packages": {
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"receptacle": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -4786,6 +4867,19 @@
|
||||
"upper-case": true
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"packages": {
|
||||
"bl": true,
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"fs-constants": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"string_decoder": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"textarea-caret": {
|
||||
"globals": {
|
||||
"document.body.appendChild": true,
|
||||
|
@ -617,6 +617,25 @@
|
||||
"URL": true
|
||||
}
|
||||
},
|
||||
"@metamask/iframe-execution-environment-service": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
"console.log": true,
|
||||
"document.body.appendChild": true,
|
||||
"document.createElement": true,
|
||||
"document.getElementById": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/snap-controllers": true,
|
||||
"@metamask/snap-workers": true,
|
||||
"json-rpc-engine": true,
|
||||
"json-rpc-middleware-stream": true,
|
||||
"nanoid": true,
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"globals": {
|
||||
"document.createElement": true,
|
||||
@ -700,11 +719,11 @@
|
||||
},
|
||||
"@metamask/snap-controllers": {
|
||||
"globals": {
|
||||
"URL": true,
|
||||
"Worker": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.log": true,
|
||||
"console.warn": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
@ -715,14 +734,22 @@
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/safe-event-emitter": true,
|
||||
"@metamask/snap-workers": true,
|
||||
"ajv": true,
|
||||
"buffer": true,
|
||||
"concat-stream": true,
|
||||
"crypto-browserify": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
"fast-deep-equal": true,
|
||||
"gunzip-maybe": true,
|
||||
"immer": true,
|
||||
"json-rpc-engine": true,
|
||||
"json-rpc-middleware-stream": true,
|
||||
"nanoid": true,
|
||||
"pump": true
|
||||
"pump": true,
|
||||
"readable-web-to-node-stream": true,
|
||||
"semver": true,
|
||||
"tar-stream": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
@ -973,6 +1000,11 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"packages": {
|
||||
"fast-deep-equal": true
|
||||
}
|
||||
},
|
||||
"analytics-node": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1158,6 +1190,7 @@
|
||||
"bl": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
@ -1270,6 +1303,16 @@
|
||||
"btoa": true
|
||||
}
|
||||
},
|
||||
"browserify-zlib": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"buffer": true,
|
||||
"pako": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"bs58": {
|
||||
"packages": {
|
||||
"base-x": true
|
||||
@ -1296,6 +1339,11 @@
|
||||
"ieee754": true
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"buffer-split": {
|
||||
"packages": {
|
||||
"buffer-indexof": true
|
||||
@ -1426,6 +1474,13 @@
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"concat-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"constant-case": {
|
||||
"packages": {
|
||||
"snake-case": true,
|
||||
@ -1707,6 +1762,16 @@
|
||||
"stream-browserify": true
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"stream-shift": true
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"packages": {
|
||||
"bn.js": true,
|
||||
@ -2212,6 +2277,11 @@
|
||||
"postMessage": true
|
||||
}
|
||||
},
|
||||
"fs-constants": {
|
||||
"packages": {
|
||||
"constants-browserify": true
|
||||
}
|
||||
},
|
||||
"fsm-event": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
@ -2280,6 +2350,16 @@
|
||||
"superagent": true
|
||||
}
|
||||
},
|
||||
"gunzip-maybe": {
|
||||
"packages": {
|
||||
"browserify-zlib": true,
|
||||
"is-deflate": true,
|
||||
"is-gzip": true,
|
||||
"peek-stream": true,
|
||||
"pumpify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"hamt-sharding": {
|
||||
"packages": {
|
||||
"is-buffer": true,
|
||||
@ -3930,6 +4010,14 @@
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"peek-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"buffer-from": true,
|
||||
"duplexify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"peer-book": {
|
||||
"packages": {
|
||||
"bs58": true,
|
||||
@ -4152,6 +4240,13 @@
|
||||
"process": true
|
||||
}
|
||||
},
|
||||
"pumpify": {
|
||||
"packages": {
|
||||
"duplexify": true,
|
||||
"inherits": true,
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"globals": {
|
||||
"define": true
|
||||
@ -4465,6 +4560,11 @@
|
||||
"util-deprecate": true
|
||||
}
|
||||
},
|
||||
"readable-web-to-node-stream": {
|
||||
"packages": {
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"receptacle": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -4786,6 +4886,19 @@
|
||||
"upper-case": true
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"packages": {
|
||||
"bl": true,
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"fs-constants": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"string_decoder": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"textarea-caret": {
|
||||
"globals": {
|
||||
"document.body.appendChild": true,
|
||||
|
@ -10,6 +10,11 @@
|
||||
"@babel/runtime": true
|
||||
}
|
||||
},
|
||||
"keccak": {
|
||||
"packages": {
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"globals": {
|
||||
"fetch": true
|
||||
|
@ -700,11 +700,11 @@
|
||||
},
|
||||
"@metamask/snap-controllers": {
|
||||
"globals": {
|
||||
"URL": true,
|
||||
"Worker": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.log": true,
|
||||
"console.warn": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
@ -715,14 +715,22 @@
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/safe-event-emitter": true,
|
||||
"@metamask/snap-workers": true,
|
||||
"ajv": true,
|
||||
"buffer": true,
|
||||
"concat-stream": true,
|
||||
"crypto-browserify": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
"fast-deep-equal": true,
|
||||
"gunzip-maybe": true,
|
||||
"immer": true,
|
||||
"json-rpc-engine": true,
|
||||
"json-rpc-middleware-stream": true,
|
||||
"nanoid": true,
|
||||
"pump": true
|
||||
"pump": true,
|
||||
"readable-web-to-node-stream": true,
|
||||
"semver": true,
|
||||
"tar-stream": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
@ -973,6 +981,11 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"packages": {
|
||||
"fast-deep-equal": true
|
||||
}
|
||||
},
|
||||
"analytics-node": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1158,6 +1171,7 @@
|
||||
"bl": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
@ -1270,6 +1284,16 @@
|
||||
"btoa": true
|
||||
}
|
||||
},
|
||||
"browserify-zlib": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"buffer": true,
|
||||
"pako": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"bs58": {
|
||||
"packages": {
|
||||
"base-x": true
|
||||
@ -1296,6 +1320,11 @@
|
||||
"ieee754": true
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"buffer-split": {
|
||||
"packages": {
|
||||
"buffer-indexof": true
|
||||
@ -1426,6 +1455,13 @@
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"concat-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"inherits": true,
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"constant-case": {
|
||||
"packages": {
|
||||
"snake-case": true,
|
||||
@ -1707,6 +1743,16 @@
|
||||
"stream-browserify": true
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"stream-shift": true
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"packages": {
|
||||
"bn.js": true,
|
||||
@ -2212,6 +2258,11 @@
|
||||
"postMessage": true
|
||||
}
|
||||
},
|
||||
"fs-constants": {
|
||||
"packages": {
|
||||
"constants-browserify": true
|
||||
}
|
||||
},
|
||||
"fsm-event": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
@ -2280,6 +2331,16 @@
|
||||
"superagent": true
|
||||
}
|
||||
},
|
||||
"gunzip-maybe": {
|
||||
"packages": {
|
||||
"browserify-zlib": true,
|
||||
"is-deflate": true,
|
||||
"is-gzip": true,
|
||||
"peek-stream": true,
|
||||
"pumpify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"hamt-sharding": {
|
||||
"packages": {
|
||||
"is-buffer": true,
|
||||
@ -3930,6 +3991,14 @@
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"peek-stream": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"buffer-from": true,
|
||||
"duplexify": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"peer-book": {
|
||||
"packages": {
|
||||
"bs58": true,
|
||||
@ -4152,6 +4221,13 @@
|
||||
"process": true
|
||||
}
|
||||
},
|
||||
"pumpify": {
|
||||
"packages": {
|
||||
"duplexify": true,
|
||||
"inherits": true,
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"globals": {
|
||||
"define": true
|
||||
@ -4465,6 +4541,11 @@
|
||||
"util-deprecate": true
|
||||
}
|
||||
},
|
||||
"readable-web-to-node-stream": {
|
||||
"packages": {
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"receptacle": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -4786,6 +4867,19 @@
|
||||
"upper-case": true
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"packages": {
|
||||
"bl": true,
|
||||
"buffer": true,
|
||||
"end-of-stream": true,
|
||||
"fs-constants": true,
|
||||
"inherits": true,
|
||||
"process": true,
|
||||
"readable-stream": true,
|
||||
"string_decoder": true,
|
||||
"util": true
|
||||
}
|
||||
},
|
||||
"textarea-caret": {
|
||||
"globals": {
|
||||
"document.body.appendChild": true,
|
||||
|
@ -113,13 +113,15 @@
|
||||
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
|
||||
"@metamask/eth-token-tracker": "^4.0.0",
|
||||
"@metamask/etherscan-link": "^2.1.0",
|
||||
"@metamask/iframe-execution-environment-service": "^0.9.0",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@metamask/logo": "^3.1.1",
|
||||
"@metamask/obs-store": "^5.0.0",
|
||||
"@metamask/post-message-stream": "^4.0.0",
|
||||
"@metamask/providers": "^8.1.1",
|
||||
"@metamask/rpc-methods": "^0.5.0",
|
||||
"@metamask/snap-controllers": "^0.4.0",
|
||||
"@metamask/rpc-methods": "^0.9.0",
|
||||
"@metamask/slip44": "^2.0.0",
|
||||
"@metamask/snap-controllers": "^0.9.0",
|
||||
"@ngraveio/bc-ur": "^1.1.6",
|
||||
"@popperjs/core": "^2.4.0",
|
||||
"@reduxjs/toolkit": "^1.6.2",
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { RestrictedMethods } from './permissions';
|
||||
|
||||
/**
|
||||
* A string representing the type of environment the application is currently running in
|
||||
* popup - When the user click's the icon in their browser's extension bar; the default view
|
||||
@ -31,7 +33,7 @@ export const PLATFORM_OPERA = 'Opera';
|
||||
|
||||
export const MESSAGE_TYPE = {
|
||||
ADD_ETHEREUM_CHAIN: 'wallet_addEthereumChain',
|
||||
ETH_ACCOUNTS: 'eth_accounts',
|
||||
ETH_ACCOUNTS: RestrictedMethods.eth_accounts,
|
||||
ETH_DECRYPT: 'eth_decrypt',
|
||||
ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey',
|
||||
ETH_REQUEST_ACCOUNTS: 'eth_requestAccounts',
|
||||
@ -44,6 +46,9 @@ export const MESSAGE_TYPE = {
|
||||
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
|
||||
WATCH_ASSET: 'wallet_watchAsset',
|
||||
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SNAP_CONFIRM: RestrictedMethods.snap_confirm,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
/**
|
||||
@ -55,6 +60,9 @@ export const SUBJECT_TYPES = {
|
||||
INTERNAL: 'internal',
|
||||
UNKNOWN: 'unknown',
|
||||
WEBSITE: 'website',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SNAP: 'snap',
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
export const POLLING_TOKEN_ENVIRONMENT_TYPES = {
|
||||
|
@ -4,4 +4,21 @@ export const CaveatTypes = Object.freeze({
|
||||
|
||||
export const RestrictedMethods = Object.freeze({
|
||||
eth_accounts: 'eth_accounts',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
snap_confirm: 'snap_confirm',
|
||||
snap_manageState: 'snap_manageState',
|
||||
'snap_getBip44Entropy_*': 'snap_getBip44Entropy_*',
|
||||
'wallet_snap_*': 'wallet_snap_*',
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
});
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
export const PermissionNamespaces = Object.freeze({
|
||||
snap_getBip44Entropy_: 'snap_getBip44Entropy_*',
|
||||
wallet_snap_: 'wallet_snap_*',
|
||||
});
|
||||
|
||||
export const EndowmentPermissions = Object.freeze({
|
||||
'endowment:network-access': 'endowment:network-access',
|
||||
});
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
22
shared/constants/permissions.test.js
Normal file
22
shared/constants/permissions.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
|
||||
import { restrictedMethodPermissionBuilders } from '@metamask/rpc-methods';
|
||||
import { EndowmentPermissions, RestrictedMethods } from './permissions';
|
||||
|
||||
describe('EndowmentPermissions', () => {
|
||||
it('has the expected permission keys', () => {
|
||||
expect(Object.keys(EndowmentPermissions).sort()).toStrictEqual(
|
||||
Object.keys(endowmentPermissionBuilders).sort(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RestrictedMethods', () => {
|
||||
it('has the expected permission keys', () => {
|
||||
expect(Object.keys(RestrictedMethods).sort()).toStrictEqual(
|
||||
[
|
||||
'eth_accounts',
|
||||
...Object.keys(restrictedMethodPermissionBuilders),
|
||||
].sort(),
|
||||
);
|
||||
});
|
||||
});
|
@ -27,8 +27,11 @@
|
||||
@import 'edit-gas-fee-popover/edit-gas-item/index';
|
||||
@import 'edit-gas-fee-popover/network-statistics/index';
|
||||
@import 'edit-gas-fee-popover/network-statistics/status-slider/index';
|
||||
@import 'flask/snaps-authorship-pill/index';
|
||||
@import 'edit-gas-fee-popover/edit-gas-tooltip/index';
|
||||
@import 'flask/experimental-area/index';
|
||||
@import 'flask/snap-install-warning/index';
|
||||
@import 'flask/snap-settings-card/index';
|
||||
@import 'flask/snaps-authorship-pill/index';
|
||||
@import 'gas-customization/gas-modal-page-container/index';
|
||||
@import 'gas-customization/gas-price-button-group/index';
|
||||
@import 'gas-customization/index';
|
||||
@ -51,7 +54,6 @@
|
||||
@import 'selected-account/index';
|
||||
@import 'signature-request/index';
|
||||
@import 'signature-request-original/index';
|
||||
@import 'flask/snap-settings-card/index';
|
||||
@import 'tab-bar/index';
|
||||
@import 'token-cell/token-cell';
|
||||
@import 'token-list-display/token-list-display';
|
||||
@ -68,7 +70,6 @@
|
||||
@import 'wallet-overview/index';
|
||||
@import 'whats-new-popup/index';
|
||||
@import 'loading-network-screen/index';
|
||||
@import 'flask/experimental-area/index';
|
||||
@import 'transaction-decoding/index';
|
||||
@import 'advanced-gas-fee-popover/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-gas-limit/index';
|
||||
|
1
ui/components/app/flask/snap-install-warning/index.js
Normal file
1
ui/components/app/flask/snap-install-warning/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-install-warning';
|
30
ui/components/app/flask/snap-install-warning/index.scss
Normal file
30
ui/components/app/flask/snap-install-warning/index.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.snap-install-warning {
|
||||
.checkbox-label {
|
||||
@include H7;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0 16px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 16px 24px;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
&__footer-button {
|
||||
margin-right: 16px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import CheckBox from '../../../ui/check-box/check-box.component';
|
||||
import Typography from '../../../ui/typography/typography';
|
||||
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
||||
import Popover from '../../../ui/popover';
|
||||
import Button from '../../../ui/button';
|
||||
|
||||
export default function SnapInstallWarning({ onCancel, onSubmit, snapName }) {
|
||||
const t = useI18nContext();
|
||||
const [isConfirmed, setIsConfirmed] = useState(false);
|
||||
|
||||
const onCheckboxClicked = useCallback(
|
||||
() => setIsConfirmed((confirmedState) => !confirmedState),
|
||||
[],
|
||||
);
|
||||
|
||||
const SnapInstallWarningFooter = () => {
|
||||
return (
|
||||
<div className="snap-install-warning__footer">
|
||||
<Button
|
||||
className="snap-install-warning__footer-button"
|
||||
type="default"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className="snap-install-warning__footer-button"
|
||||
type="primary"
|
||||
disabled={!isConfirmed}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{t('confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className="snap-install-warning"
|
||||
title={t('areYouSure')}
|
||||
footer={<SnapInstallWarningFooter />}
|
||||
>
|
||||
<div className="snap-install-warning__content">
|
||||
<Typography variant={TYPOGRAPHY.H6} boxProps={{ paddingBottom: 4 }}>
|
||||
{t('snapInstallWarningCheck')}
|
||||
</Typography>
|
||||
<div className="checkbox-label">
|
||||
<CheckBox
|
||||
checked={isConfirmed}
|
||||
id="warning-accept"
|
||||
onClick={onCheckboxClicked}
|
||||
/>
|
||||
<label htmlFor="warning-accept">
|
||||
{t('snapInstallWarningKeyAccess', [snapName])}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
SnapInstallWarning.propTypes = {
|
||||
/**
|
||||
* onCancel handler
|
||||
*/
|
||||
onCancel: PropTypes.func,
|
||||
/**
|
||||
* onSubmit handler
|
||||
*/
|
||||
onSubmit: PropTypes.func,
|
||||
/**
|
||||
* Name of snap
|
||||
*/
|
||||
snapName: PropTypes.string,
|
||||
};
|
@ -13,12 +13,14 @@ import ToggleButton from '../../../ui/toggle-button';
|
||||
import Chip from '../../../ui/chip';
|
||||
import ColorIndicator from '../../../ui/color-indicator';
|
||||
import Button from '../../../ui/button';
|
||||
import Tooltip from '../../../ui/tooltip';
|
||||
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
FONT_WEIGHT,
|
||||
ALIGN_ITEMS,
|
||||
JUSTIFY_CONTENT,
|
||||
DISPLAY,
|
||||
TEXT_ALIGN,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
@ -41,7 +43,7 @@ const SnapSettingsCard = ({
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
dateAdded,
|
||||
dateAdded = '',
|
||||
version,
|
||||
url,
|
||||
onToggle,
|
||||
@ -87,12 +89,14 @@ const SnapSettingsCard = ({
|
||||
{name}
|
||||
</Typography>
|
||||
<Box paddingLeft={4} className="snap-settings-card__toggle-container">
|
||||
<ToggleButton
|
||||
value={isEnabled}
|
||||
onToggle={onToggle}
|
||||
className="snap-settings-card__toggle-container__toggle-button"
|
||||
{...toggleButtonProps}
|
||||
/>
|
||||
<Tooltip interactive position="bottom" html={t('snapsToggle')}>
|
||||
<ToggleButton
|
||||
value={isEnabled}
|
||||
onToggle={onToggle}
|
||||
className="snap-settings-card__toggle-container__toggle-button"
|
||||
{...toggleButtonProps}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography
|
||||
@ -113,6 +117,7 @@ const SnapSettingsCard = ({
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
marginBottom={4}
|
||||
>
|
||||
<Box>
|
||||
@ -124,24 +129,26 @@ const SnapSettingsCard = ({
|
||||
{t('flaskSnapSettingsCardButtonCta')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Chip
|
||||
leftIcon={
|
||||
<Box paddingLeft={1}>
|
||||
<ColorIndicator
|
||||
color={STATUS_COLORS[status]}
|
||||
type={ColorIndicator.TYPES.FILLED}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
label={status}
|
||||
labelProps={{
|
||||
color: COLORS.UI4,
|
||||
margin: [0, 1],
|
||||
}}
|
||||
backgroundColor={COLORS.UI1}
|
||||
className="snap-settings-card__chip"
|
||||
{...chipProps}
|
||||
/>
|
||||
<Tooltip interactive position="bottom" html={t('snapsStatus')}>
|
||||
<Chip
|
||||
leftIcon={
|
||||
<Box paddingLeft={1}>
|
||||
<ColorIndicator
|
||||
color={STATUS_COLORS[status]}
|
||||
type={ColorIndicator.TYPES.FILLED}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
label={status}
|
||||
labelProps={{
|
||||
color: COLORS.UI4,
|
||||
margin: [0, 1],
|
||||
}}
|
||||
backgroundColor={COLORS.UI1}
|
||||
className="snap-settings-card__chip"
|
||||
{...chipProps}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
|
||||
|
@ -44,4 +44,3 @@
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,11 +71,14 @@ export default class PermissionPageContainerContent extends PureComponent {
|
||||
subjectMetadata,
|
||||
selectedIdentities,
|
||||
allIdentitiesSelected,
|
||||
selectedPermissions,
|
||||
} = this.props;
|
||||
const { t } = this.context;
|
||||
|
||||
if (subjectMetadata.extensionId) {
|
||||
return t('externalExtension', [subjectMetadata.extensionId]);
|
||||
} else if (!selectedPermissions.eth_accounts) {
|
||||
return t('permissionRequestCapitalized');
|
||||
} else if (allIdentitiesSelected) {
|
||||
return t('connectToAll', [
|
||||
this.renderAccountTooltip(t('connectToAllAccounts')),
|
||||
|
@ -1,8 +1,5 @@
|
||||
.permissions-connect-header {
|
||||
display: flex;
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 92%;
|
||||
|
||||
&__icon {
|
||||
|
@ -1,6 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import SiteOrigin from '../../ui/site-origin/site-origin';
|
||||
import SiteOrigin from '../../ui/site-origin';
|
||||
import Box from '../../ui/box';
|
||||
import {
|
||||
FLEX_DIRECTION,
|
||||
JUSTIFY_CONTENT,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import SnapsAuthorshipPill from '../flask/snaps-authorship-pill';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
export default class PermissionsConnectHeader extends Component {
|
||||
static propTypes = {
|
||||
@ -8,13 +16,18 @@ export default class PermissionsConnectHeader extends Component {
|
||||
iconName: PropTypes.string.isRequired,
|
||||
siteOrigin: PropTypes.string.isRequired,
|
||||
headerTitle: PropTypes.node,
|
||||
boxProps: PropTypes.shape({ ...Box.propTypes }),
|
||||
headerText: PropTypes.string,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
npmPackageName: PropTypes.string,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
iconUrl: null,
|
||||
headerTitle: '',
|
||||
headerText: '',
|
||||
boxProps: {},
|
||||
};
|
||||
|
||||
renderHeaderIcon() {
|
||||
@ -28,13 +41,38 @@ export default class PermissionsConnectHeader extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { headerTitle, headerText } = this.props;
|
||||
const {
|
||||
boxProps,
|
||||
headerTitle,
|
||||
headerText,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
npmPackageName,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} = this.props;
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
const npmPackageUrl = `https://www.npmjs.com/package/${npmPackageName}`;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
return (
|
||||
<div className="permissions-connect-header">
|
||||
<Box
|
||||
className="permissions-connect-header"
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
{...boxProps}
|
||||
>
|
||||
{this.renderHeaderIcon()}
|
||||
<div className="permissions-connect-header__title">{headerTitle}</div>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
npmPackageName ? (
|
||||
<SnapsAuthorshipPill
|
||||
packageName={npmPackageName}
|
||||
url={npmPackageUrl}
|
||||
/>
|
||||
) : null
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
<div className="permissions-connect-header__subtitle">{headerText}</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
.permission {
|
||||
@include H6;
|
||||
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--Grey-100);
|
||||
display: flex;
|
||||
|
@ -1,33 +1,129 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
RestrictedMethods,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
EndowmentPermissions,
|
||||
PermissionNamespaces,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../../../shared/constants/permissions';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import { coinTypeToProtocolName } from '../../../helpers/utils/util';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const UNKNOWN_PERMISSION = Symbol('unknown');
|
||||
|
||||
/**
|
||||
* @typedef {Object} PermissionLabelObject
|
||||
* @property {string} label - The text label.
|
||||
* @property {string} leftIcon - The left icon.
|
||||
* @property {string} rightIcon - The right icon.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the permission list label dictionary key for the specified permission
|
||||
* name.
|
||||
*
|
||||
* @param {string} permissionName - The name of the permission whose key to
|
||||
* retrieve.
|
||||
* @param {Record<string, PermissionLabelObject>} permissionDictionary - The
|
||||
* dictionary object mapping permission keys to label objects.
|
||||
*/
|
||||
function getPermissionKey(permissionName, permissionDictionary) {
|
||||
if (Object.hasOwnProperty.call(permissionDictionary, permissionName)) {
|
||||
return permissionName;
|
||||
}
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
for (const namespace of Object.keys(PermissionNamespaces)) {
|
||||
if (permissionName.startsWith(namespace)) {
|
||||
return PermissionNamespaces[namespace];
|
||||
}
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
return UNKNOWN_PERMISSION;
|
||||
}
|
||||
|
||||
export default function PermissionsConnectPermissionList({ permissions }) {
|
||||
const t = useI18nContext();
|
||||
|
||||
const PERMISSION_TYPES = useMemo(() => {
|
||||
const PERMISSION_LIST_VALUES = useMemo(() => {
|
||||
return {
|
||||
eth_accounts: {
|
||||
[RestrictedMethods.eth_accounts]: {
|
||||
leftIcon: 'fas fa-eye',
|
||||
label: t('eth_accounts'),
|
||||
label: t('permission_ethereumAccounts'),
|
||||
rightIcon: null,
|
||||
},
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
[RestrictedMethods.snap_confirm]: {
|
||||
leftIcon: 'fas fa-user-check',
|
||||
label: t('permission_customConfirmation'),
|
||||
rightIcon: null,
|
||||
},
|
||||
[RestrictedMethods['snap_getBip44Entropy_*']]: (permissionName) => {
|
||||
const coinType = permissionName.split('_').slice(-1);
|
||||
return {
|
||||
leftIcon: 'fas fa-door-open',
|
||||
label: t('permission_manageBip44Keys', [
|
||||
coinTypeToProtocolName(coinType) ||
|
||||
`${coinType} (Unrecognized protocol)`,
|
||||
]),
|
||||
rightIcon: null,
|
||||
};
|
||||
},
|
||||
[RestrictedMethods.snap_manageState]: {
|
||||
leftIcon: 'fas fa-download',
|
||||
label: t('permission_manageState'),
|
||||
rightIcon: null,
|
||||
},
|
||||
[RestrictedMethods['wallet_snap_*']]: (permissionName) => {
|
||||
const snapId = permissionName.split('_').slice(-1);
|
||||
return {
|
||||
leftIcon: 'fas fa-bolt',
|
||||
label: t('permission_accessSnap', [snapId]),
|
||||
rightIcon: null,
|
||||
};
|
||||
},
|
||||
[EndowmentPermissions['endowment:network-access']]: {
|
||||
leftIcon: 'fas fa-wifi',
|
||||
label: t('permission_accessNetwork'),
|
||||
rightIcon: null,
|
||||
},
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
[UNKNOWN_PERMISSION]: (permissionName) => {
|
||||
return {
|
||||
leftIcon: 'fas fa-times-circle',
|
||||
label: t('permission_unknown', [permissionName ?? 'undefined']),
|
||||
rightIcon: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<div className="permissions-connect-permission-list">
|
||||
{Object.keys(permissions).map((permission) => (
|
||||
<div className="permission" key={PERMISSION_TYPES[permission].label}>
|
||||
<i className={PERMISSION_TYPES[permission].leftIcon} />
|
||||
{PERMISSION_TYPES[permission].label}
|
||||
<i className={PERMISSION_TYPES[permission].rightIcon} />
|
||||
</div>
|
||||
))}
|
||||
{Object.keys(permissions).map((permission) => {
|
||||
const listValue =
|
||||
PERMISSION_LIST_VALUES[
|
||||
getPermissionKey(permission, PERMISSION_LIST_VALUES)
|
||||
];
|
||||
|
||||
const { label, leftIcon, rightIcon } =
|
||||
typeof listValue === 'function' ? listValue(permission) : listValue;
|
||||
|
||||
return (
|
||||
<div className="permission" key={permission}>
|
||||
<i className={leftIcon} />
|
||||
{label}
|
||||
{rightIcon && <i className={rightIcon} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PermissionsConnectPermissionList.propTypes = {
|
||||
permissions: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
permissions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
@ -113,7 +113,7 @@ export const DISPLAY = {
|
||||
LIST_ITEM: 'list-item',
|
||||
};
|
||||
|
||||
const FRACTIONS = {
|
||||
export const FRACTIONS = {
|
||||
HALF: '1/2',
|
||||
ONE_THIRD: '1/3',
|
||||
TWO_THIRDS: '2/3',
|
||||
|
@ -12,6 +12,8 @@ const ALERTS_ROUTE = '/settings/alerts';
|
||||
const NETWORKS_ROUTE = '/settings/networks';
|
||||
const NETWORKS_FORM_ROUTE = '/settings/networks/form';
|
||||
const ADD_NETWORK_ROUTE = '/settings/networks/add-network';
|
||||
const SNAPS_LIST_ROUTE = '/settings/snaps-list';
|
||||
const SNAPS_VIEW_ROUTE = '/settings/snaps-view';
|
||||
const CONTACT_LIST_ROUTE = '/settings/contact-list';
|
||||
const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact';
|
||||
const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact';
|
||||
@ -28,6 +30,9 @@ const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
||||
const SEND_ROUTE = '/send';
|
||||
const CONNECT_ROUTE = '/connect';
|
||||
const CONNECT_CONFIRM_PERMISSIONS_ROUTE = '/confirm-permissions';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
const CONNECT_SNAP_INSTALL_ROUTE = '/snap-install';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
const CONNECTED_ROUTE = '/connected';
|
||||
const CONNECTED_ACCOUNTS_ROUTE = '/connected/accounts';
|
||||
const SWAPS_ROUTE = '/swaps';
|
||||
@ -202,6 +207,8 @@ export {
|
||||
SECURITY_ROUTE,
|
||||
GENERAL_ROUTE,
|
||||
ABOUT_US_ROUTE,
|
||||
SNAPS_LIST_ROUTE,
|
||||
SNAPS_VIEW_ROUTE,
|
||||
CONTACT_LIST_ROUTE,
|
||||
CONTACT_EDIT_ROUTE,
|
||||
CONTACT_ADD_ROUTE,
|
||||
@ -213,6 +220,9 @@ export {
|
||||
INITIALIZE_SEED_PHRASE_INTRO_ROUTE,
|
||||
CONNECT_ROUTE,
|
||||
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
CONNECT_SNAP_INSTALL_ROUTE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
CONNECTED_ROUTE,
|
||||
CONNECTED_ACCOUNTS_ROUTE,
|
||||
PATH_NAME_MAP,
|
||||
|
@ -4,6 +4,7 @@ import BigNumber from 'bignumber.js';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import { DateTime } from 'luxon';
|
||||
import { util } from '@metamask/controllers';
|
||||
import slip44 from '@metamask/slip44';
|
||||
import { addHexPrefix } from '../../../app/scripts/lib/util';
|
||||
import {
|
||||
GOERLI_CHAIN_ID,
|
||||
@ -580,3 +581,18 @@ export function roundToDecimalPlacesRemovingExtraZeroes(
|
||||
.dec(toBigNumber.dec(numberish).toFixed(numberOfDecimalPlaces))
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the SLIP-44 protocol corresponding to the specified
|
||||
* `coin_type`.
|
||||
*
|
||||
* @param {string | number} coinType - The SLIP-44 `coin_type` value whose name
|
||||
* to retrieve.
|
||||
* @returns {string | undefined} The name of the protocol if found.
|
||||
*/
|
||||
export function coinTypeToProtocolName(coinType) {
|
||||
if (String(coinType) === '1') {
|
||||
return 'Test Networks';
|
||||
}
|
||||
return slip44[coinType]?.name || undefined;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getSubjectMetadata } from '../selectors';
|
||||
import { getTargetSubjectMetadata } from '../selectors';
|
||||
import { SUBJECT_TYPES } from '../../shared/constants/app';
|
||||
|
||||
/**
|
||||
@ -19,24 +19,35 @@ import { SUBJECT_TYPES } from '../../shared/constants/app';
|
||||
* current origin
|
||||
*/
|
||||
export function useOriginMetadata(origin) {
|
||||
const subjectMetadata = useSelector(getSubjectMetadata);
|
||||
const targetSubjectMetadata = useSelector((state) =>
|
||||
getTargetSubjectMetadata(state, origin),
|
||||
);
|
||||
|
||||
if (!origin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = new URL(origin);
|
||||
const minimumOriginMetadata = {
|
||||
host: url.host,
|
||||
hostname: url.hostname,
|
||||
origin,
|
||||
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||
};
|
||||
let minimumOriginMetadata = null;
|
||||
try {
|
||||
const url = new URL(origin);
|
||||
minimumOriginMetadata = {
|
||||
host: url.host,
|
||||
hostname: url.hostname,
|
||||
origin,
|
||||
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||
};
|
||||
} catch (_) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
if (subjectMetadata?.[origin]) {
|
||||
if (targetSubjectMetadata && minimumOriginMetadata) {
|
||||
return {
|
||||
...minimumOriginMetadata,
|
||||
...subjectMetadata[origin],
|
||||
...targetSubjectMetadata,
|
||||
};
|
||||
} else if (targetSubjectMetadata) {
|
||||
return targetSubjectMetadata;
|
||||
}
|
||||
|
||||
return minimumOriginMetadata;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import 'components/confirmation-footer/confirmation-footer';
|
||||
@import 'templates/flask/snap-confirm/index';
|
||||
|
||||
.confirmation-page {
|
||||
width: 100%;
|
||||
@ -20,7 +21,6 @@
|
||||
|
||||
&__content {
|
||||
grid-area: content;
|
||||
padding: 16px 16px 0;
|
||||
min-width: 0;
|
||||
|
||||
& > :last-child {
|
||||
|
@ -206,6 +206,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
pendingApproval.id,
|
||||
ethErrors.provider.userRejectedRequest(),
|
||||
),
|
||||
networkDisplay: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
.snap-confirm {
|
||||
padding: 16px 32px;
|
||||
border-top: 1px solid var(--Grey-100);
|
||||
border-bottom: 1px solid var(--Grey-100);
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.text {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
import {
|
||||
RESIZE,
|
||||
TYPOGRAPHY,
|
||||
} from '../../../../../helpers/constants/design-system';
|
||||
|
||||
function getValues(pendingApproval, t, actions) {
|
||||
const { prompt, description, textAreaContent } = pendingApproval.requestData;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
element: 'Typography',
|
||||
key: 'title',
|
||||
children: prompt,
|
||||
props: {
|
||||
variant: TYPOGRAPHY.H3,
|
||||
align: 'center',
|
||||
fontWeight: 'bold',
|
||||
boxProps: {
|
||||
margin: [0, 0, 4],
|
||||
},
|
||||
},
|
||||
},
|
||||
...(description
|
||||
? [
|
||||
{
|
||||
element: 'Typography',
|
||||
key: 'subtitle',
|
||||
children: description,
|
||||
props: {
|
||||
variant: TYPOGRAPHY.H6,
|
||||
align: 'center',
|
||||
boxProps: {
|
||||
margin: [0, 0, 4],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(textAreaContent
|
||||
? [
|
||||
{
|
||||
element: 'div',
|
||||
key: 'text-area',
|
||||
children: {
|
||||
element: 'TextArea',
|
||||
props: {
|
||||
// TODO(ritave): Terrible hard-coded height hack. Fixing this to adjust automatically to current window height would
|
||||
// mean allowing template compoments to change global css, and since the intended use of the template
|
||||
// renderer was to allow users to build their own UIs, this would be a big no-no.
|
||||
height: '238px',
|
||||
value: textAreaContent,
|
||||
readOnly: true,
|
||||
resize: RESIZE.VERTICAL,
|
||||
scrollable: true,
|
||||
className: 'text',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
className: 'snap-confirm',
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
element: 'Typography',
|
||||
key: 'only-interact-with-entities-you-trust',
|
||||
children: [
|
||||
{
|
||||
element: 'span',
|
||||
key: 'only-connect-trust',
|
||||
children: `${t('onlyConnectTrust')} `,
|
||||
},
|
||||
{
|
||||
element: 'a',
|
||||
children: t('learnMore'),
|
||||
key: 'learnMore-a-href',
|
||||
props: {
|
||||
href:
|
||||
'https://metamask.zendesk.com/hc/en-us/articles/4405506066331-User-guide-Dapps',
|
||||
target: '__blank',
|
||||
},
|
||||
},
|
||||
],
|
||||
props: {
|
||||
variant: TYPOGRAPHY.H7,
|
||||
align: 'center',
|
||||
boxProps: {
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
approvalText: t('approveButtonText'),
|
||||
cancelText: t('reject'),
|
||||
onApprove: () => actions.resolvePendingApproval(pendingApproval.id, true),
|
||||
onCancel: () => actions.resolvePendingApproval(pendingApproval.id, false),
|
||||
};
|
||||
}
|
||||
|
||||
const snapConfirm = {
|
||||
getValues,
|
||||
};
|
||||
|
||||
export default snapConfirm;
|
@ -6,10 +6,16 @@ import {
|
||||
} from '../../../store/actions';
|
||||
import addEthereumChain from './add-ethereum-chain';
|
||||
import switchEthereumChain from './switch-ethereum-chain';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import snapConfirm from './flask/snap-confirm/snap-confirm';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const APPROVAL_TEMPLATES = {
|
||||
[MESSAGE_TYPE.ADD_ETHEREUM_CHAIN]: addEthereumChain,
|
||||
[MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN]: switchEthereumChain,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
[MESSAGE_TYPE.SNAP_CONFIRM]: snapConfirm,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
export const TEMPLATED_CONFIRMATION_MESSAGE_TYPES = Object.keys(
|
||||
|
@ -85,6 +85,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
pendingApproval.id,
|
||||
ethErrors.provider.userRejectedRequest(),
|
||||
),
|
||||
networkDisplay: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,13 @@ import WhatsNewPopup from '../../components/app/whats-new-popup';
|
||||
import RecoveryPhraseReminder from '../../components/app/recovery-phrase-reminder';
|
||||
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
|
||||
import Typography from '../../components/ui/typography/typography';
|
||||
import { TYPOGRAPHY, FONT_WEIGHT } from '../../helpers/constants/design-system';
|
||||
import {
|
||||
TYPOGRAPHY,
|
||||
FONT_WEIGHT,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
COLORS,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../helpers/constants/design-system';
|
||||
|
||||
import {
|
||||
ASSET_ROUTE,
|
||||
@ -105,6 +111,11 @@ export default class Home extends PureComponent {
|
||||
showWhatsNewPopup: PropTypes.bool.isRequired,
|
||||
hideWhatsNewPopup: PropTypes.func.isRequired,
|
||||
notificationsToShow: PropTypes.bool.isRequired,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
errorsToShow: PropTypes.object.isRequired,
|
||||
shouldShowErrors: PropTypes.bool.isRequired,
|
||||
removeSnapError: PropTypes.func.isRequired,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
showRecoveryPhraseReminder: PropTypes.bool.isRequired,
|
||||
setRecoveryPhraseReminderHasBeenShown: PropTypes.func.isRequired,
|
||||
setRecoveryPhraseReminderLastShown: PropTypes.func.isRequired,
|
||||
@ -242,6 +253,11 @@ export default class Home extends PureComponent {
|
||||
setWeb3ShimUsageAlertDismissed,
|
||||
originOfCurrentTab,
|
||||
disableWeb3ShimUsageAlert,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
removeSnapError,
|
||||
errorsToShow,
|
||||
shouldShowErrors,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
infuraBlocked,
|
||||
newNetworkAdded,
|
||||
setNewNetworkAdded,
|
||||
@ -250,6 +266,43 @@ export default class Home extends PureComponent {
|
||||
} = this.props;
|
||||
return (
|
||||
<MultipleNotifications>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
shouldShowErrors
|
||||
? Object.entries(errorsToShow).map(([errorId, error]) => {
|
||||
return (
|
||||
<HomeNotification
|
||||
classNames={['home__error-message']}
|
||||
infoText={error.data.snapId}
|
||||
descriptionText={
|
||||
<>
|
||||
<Typography
|
||||
color={COLORS.UI1}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
>
|
||||
{t('somethingWentWrong')}
|
||||
</Typography>
|
||||
<Typography
|
||||
color={COLORS.UI1}
|
||||
variant={TYPOGRAPHY.H7}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
>
|
||||
{t('snapError', [error.message, error.code])}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
onIgnore={async () => {
|
||||
await removeSnapError(errorId);
|
||||
}}
|
||||
ignoreText="Dismiss"
|
||||
key="home-error-message"
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
{newCollectibleAddedMessage ? (
|
||||
<ActionableMessage
|
||||
type={newCollectibleAddedMessage === 'success' ? 'info' : 'warning'}
|
||||
|
@ -35,6 +35,9 @@ import {
|
||||
setRecoveryPhraseReminderLastShown,
|
||||
setNewNetworkAdded,
|
||||
setNewCollectibleAddedMessage,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
removeSnapError,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../store/actions';
|
||||
import { setThreeBoxLastUpdated, hideWhatsNewPopup } from '../../ducks/app/app';
|
||||
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask';
|
||||
@ -118,6 +121,10 @@ const mapStateToProps = (state) => {
|
||||
pendingConfirmations,
|
||||
infuraBlocked: getInfuraBlocked(state),
|
||||
notificationsToShow: getSortedNotificationsToShow(state).length > 0,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
errorsToShow: metamask.snapErrors,
|
||||
shouldShowErrors: Object.entries(metamask.snapErrors || []).length > 0,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
showWhatsNewPopup: getShowWhatsNewPopup(state),
|
||||
showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state),
|
||||
seedPhraseBackedUp,
|
||||
@ -140,6 +147,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
}
|
||||
});
|
||||
},
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
removeSnapError: async (id) => await removeSnapError(id),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
restoreFromThreeBox: (address) => dispatch(restoreFromThreeBox(address)),
|
||||
setShowRestorePromptToFalse: () => dispatch(setShowRestorePromptToFalse()),
|
||||
setConnectedStatusPopoverHasBeenShown: () =>
|
||||
|
@ -152,4 +152,8 @@
|
||||
background: none;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
left: 8px;
|
||||
}
|
||||
}
|
||||
|
1
ui/pages/permissions-connect/flask/snap-install/index.js
Normal file
1
ui/pages/permissions-connect/flask/snap-install/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-install';
|
28
ui/pages/permissions-connect/flask/snap-install/index.scss
Normal file
28
ui/pages/permissions-connect/flask/snap-install/index.scss
Normal file
@ -0,0 +1,28 @@
|
||||
.snap-install {
|
||||
box-shadow: none;
|
||||
|
||||
.permissions-connect-permission-list {
|
||||
padding: 0 24px;
|
||||
|
||||
.permission {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.source-code {
|
||||
@include H7;
|
||||
|
||||
display: flex;
|
||||
color: var(--ui-4);
|
||||
|
||||
.link {
|
||||
color: var(--primary-blue);
|
||||
margin-inline-start: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container__footer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
153
ui/pages/permissions-connect/flask/snap-install/snap-install.js
Normal file
153
ui/pages/permissions-connect/flask/snap-install/snap-install.js
Normal file
@ -0,0 +1,153 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { PageContainerFooter } from '../../../../components/ui/page-container';
|
||||
import PermissionsConnectPermissionList from '../../../../components/app/permissions-connect-permission-list';
|
||||
import PermissionsConnectFooter from '../../../../components/app/permissions-connect-footer';
|
||||
import PermissionConnectHeader from '../../../../components/app/permissions-connect-header';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import SnapInstallWarning from '../../../../components/app/flask/snap-install-warning';
|
||||
import Box from '../../../../components/ui/box/box';
|
||||
import {
|
||||
ALIGN_ITEMS,
|
||||
BLOCK_SIZES,
|
||||
BORDER_STYLE,
|
||||
FLEX_DIRECTION,
|
||||
JUSTIFY_CONTENT,
|
||||
TYPOGRAPHY,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import Typography from '../../../../components/ui/typography';
|
||||
|
||||
export default function SnapInstall({
|
||||
request,
|
||||
approveSnapInstall,
|
||||
rejectSnapInstall,
|
||||
targetSubjectMetadata,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
|
||||
const [isShowingWarning, setIsShowingWarning] = useState(false);
|
||||
|
||||
const onCancel = useCallback(() => rejectSnapInstall(request.metadata.id), [
|
||||
request,
|
||||
rejectSnapInstall,
|
||||
]);
|
||||
|
||||
const onSubmit = useCallback(() => approveSnapInstall(request), [
|
||||
request,
|
||||
approveSnapInstall,
|
||||
]);
|
||||
|
||||
const npmId = useMemo(() => {
|
||||
if (!targetSubjectMetadata.origin.startsWith('npm:')) {
|
||||
return undefined;
|
||||
}
|
||||
return targetSubjectMetadata.origin.substring(4);
|
||||
}, [targetSubjectMetadata]);
|
||||
|
||||
const shouldShowWarning = useMemo(
|
||||
() =>
|
||||
Boolean(
|
||||
request.permissions &&
|
||||
Object.keys(request.permissions).find((v) =>
|
||||
v.startsWith('snap_getBip44Entropy_'),
|
||||
),
|
||||
),
|
||||
[request.permissions],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="page-container snap-install"
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
height={BLOCK_SIZES.FULL}
|
||||
borderStyle={BORDER_STYLE.NONE}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<Box
|
||||
className="headers"
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
<PermissionConnectHeader
|
||||
icon={targetSubjectMetadata.iconUrl}
|
||||
iconName={targetSubjectMetadata.name}
|
||||
headerTitle={t('snapInstall')}
|
||||
headerText={null} // TODO(ritave): Add header text when snaps support description
|
||||
siteOrigin={targetSubjectMetadata.origin}
|
||||
npmPackageName={npmId}
|
||||
boxProps={{ alignItems: ALIGN_ITEMS.CENTER }}
|
||||
/>
|
||||
<Typography></Typography>
|
||||
<Box
|
||||
className="snap-requests-permission"
|
||||
padding={4}
|
||||
tag={TYPOGRAPHY.H7}
|
||||
>
|
||||
<span>{t('snapRequestsPermission')}</span>
|
||||
</Box>
|
||||
<PermissionsConnectPermissionList
|
||||
permissions={request.permissions || {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
className="footers"
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
>
|
||||
{targetSubjectMetadata.sourceCode ? (
|
||||
<>
|
||||
<div className="source-code">
|
||||
<div className="text">{t('areYouDeveloper')}</div>
|
||||
<div
|
||||
className="link"
|
||||
onClick={() =>
|
||||
global.platform.openTab({
|
||||
url: targetSubjectMetadata.sourceCode,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('openSourceCode')}
|
||||
</div>
|
||||
</div>
|
||||
<Box paddingBottom={4}>
|
||||
<PermissionsConnectFooter />
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box className="snap-install__footer--no-source-code" paddingTop={4}>
|
||||
<PermissionsConnectFooter />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<PageContainerFooter
|
||||
cancelButtonType="default"
|
||||
onCancel={onCancel}
|
||||
cancelText={t('cancel')}
|
||||
onSubmit={
|
||||
shouldShowWarning ? () => setIsShowingWarning(true) : onSubmit
|
||||
}
|
||||
submitText={t('approveAndInstall')}
|
||||
/>
|
||||
</Box>
|
||||
{isShowingWarning && (
|
||||
<SnapInstallWarning
|
||||
onCancel={() => setIsShowingWarning(false)}
|
||||
onSubmit={onSubmit}
|
||||
snapName={targetSubjectMetadata.name}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
SnapInstall.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
approveSnapInstall: PropTypes.func.isRequired,
|
||||
rejectSnapInstall: PropTypes.func.isRequired,
|
||||
targetSubjectMetadata: PropTypes.shape({
|
||||
iconUrl: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
origin: PropTypes.string.isRequired,
|
||||
sourceCode: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
@import 'choose-account/index';
|
||||
@import 'flask/snap-install/index';
|
||||
@import 'redirect/index';
|
||||
|
||||
.permissions-connect {
|
||||
|
@ -8,6 +8,9 @@ import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
|
||||
import PermissionPageContainer from '../../components/app/permission-page-container';
|
||||
import ChooseAccount from './choose-account';
|
||||
import PermissionsRedirect from './redirect';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import SnapInstall from './flask/snap-install';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const APPROVE_TIMEOUT = MILLISECOND * 1200;
|
||||
|
||||
@ -30,6 +33,11 @@ export default class PermissionConnect extends Component {
|
||||
history: PropTypes.object.isRequired,
|
||||
connectPath: PropTypes.string.isRequired,
|
||||
confirmPermissionPath: PropTypes.string.isRequired,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
snapInstallPath: PropTypes.string.isRequired,
|
||||
isSnap: PropTypes.bool.isRequired,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
totalPages: PropTypes.string.isRequired,
|
||||
page: PropTypes.string.isRequired,
|
||||
targetSubjectMetadata: PropTypes.shape({
|
||||
extensionId: PropTypes.string,
|
||||
@ -38,6 +46,7 @@ export default class PermissionConnect extends Component {
|
||||
origin: PropTypes.string.isRequired,
|
||||
subjectType: PropTypes.string,
|
||||
}),
|
||||
isRequestingAccounts: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -77,10 +86,17 @@ export default class PermissionConnect extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
connectPath,
|
||||
confirmPermissionPath,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
snapInstallPath,
|
||||
isSnap,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
getCurrentWindowTab,
|
||||
getRequestAccountTabIds,
|
||||
permissionsRequest,
|
||||
history,
|
||||
isRequestingAccounts,
|
||||
} = this.props;
|
||||
getCurrentWindowTab();
|
||||
getRequestAccountTabIds();
|
||||
@ -94,6 +110,18 @@ export default class PermissionConnect extends Component {
|
||||
if (environmentType === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
window.addEventListener('beforeunload', this.beforeUnload);
|
||||
}
|
||||
|
||||
if (history.location.pathname === connectPath && !isRequestingAccounts) {
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
if (isSnap) {
|
||||
history.push(snapInstallPath);
|
||||
} else {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
history.push(confirmPermissionPath);
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
@ -165,11 +193,11 @@ export default class PermissionConnect extends Component {
|
||||
|
||||
renderTopBar() {
|
||||
const { redirecting } = this.state;
|
||||
const { page } = this.props;
|
||||
const { page, isRequestingAccounts, totalPages } = this.props;
|
||||
const { t } = this.context;
|
||||
return redirecting ? null : (
|
||||
<div className="permissions-connect__top-bar">
|
||||
{page === '2' ? (
|
||||
{page === '2' && isRequestingAccounts ? (
|
||||
<div
|
||||
className="permissions-connect__back"
|
||||
onClick={() => this.goBack()}
|
||||
@ -178,9 +206,11 @@ export default class PermissionConnect extends Component {
|
||||
{t('back')}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="permissions-connect__page-count">
|
||||
{t('xOfY', [page, '2'])}
|
||||
</div>
|
||||
{isRequestingAccounts ? (
|
||||
<div className="permissions-connect__page-count">
|
||||
{t('xOfY', [page, totalPages])}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -197,6 +227,9 @@ export default class PermissionConnect extends Component {
|
||||
permissionsRequestId,
|
||||
connectPath,
|
||||
confirmPermissionPath,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
snapInstallPath,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} = this.props;
|
||||
const {
|
||||
selectedAccountAddresses,
|
||||
@ -257,6 +290,29 @@ export default class PermissionConnect extends Component {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
}
|
||||
<Route
|
||||
path={snapInstallPath}
|
||||
exact
|
||||
render={() => (
|
||||
<SnapInstall
|
||||
request={permissionsRequest || {}}
|
||||
approveSnapInstall={(...args) => {
|
||||
approvePermissionsRequest(...args);
|
||||
this.redirect(true);
|
||||
}}
|
||||
rejectSnapInstall={(requestId) =>
|
||||
this.cancelPermissionsRequest(requestId)
|
||||
}
|
||||
targetSubjectMetadata={targetSubjectMetadata}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
</Switch>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
getPermissionsRequests,
|
||||
getAccountsWithLabels,
|
||||
getLastConnectedInfo,
|
||||
getSubjectMetadata,
|
||||
getPermissionsRequests,
|
||||
getSelectedAddress,
|
||||
getTargetSubjectMetadata,
|
||||
} from '../../selectors';
|
||||
import { getNativeCurrency } from '../../ducks/metamask/metamask';
|
||||
|
||||
import { formatDate } from '../../helpers/utils/util';
|
||||
import { formatDate, getURLHostName } from '../../helpers/utils/util';
|
||||
import {
|
||||
approvePermissionsRequest,
|
||||
rejectPermissionsRequest,
|
||||
@ -20,6 +20,9 @@ import {
|
||||
import {
|
||||
CONNECT_ROUTE,
|
||||
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
CONNECT_SNAP_INSTALL_ROUTE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../helpers/constants/routes';
|
||||
import { SUBJECT_TYPES } from '../../../shared/constants/app';
|
||||
import PermissionApproval from './permissions-connect.component';
|
||||
@ -38,27 +41,25 @@ const mapStateToProps = (state, ownProps) => {
|
||||
(req) => req.metadata.id === permissionsRequestId,
|
||||
);
|
||||
|
||||
const isRequestingAccounts = Boolean(
|
||||
permissionsRequest?.permissions.eth_accounts,
|
||||
);
|
||||
|
||||
const { metadata = {} } = permissionsRequest || {};
|
||||
const { origin } = metadata;
|
||||
const nativeCurrency = getNativeCurrency(state);
|
||||
|
||||
const subjectMetadata = getSubjectMetadata(state);
|
||||
const targetSubjectMetadata = getTargetSubjectMetadata(state, origin) ?? {
|
||||
name: getURLHostName(origin) || origin,
|
||||
origin,
|
||||
iconUrl: null,
|
||||
extensionId: null,
|
||||
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||
};
|
||||
|
||||
let targetSubjectMetadata = null;
|
||||
if (origin) {
|
||||
if (subjectMetadata[origin]) {
|
||||
targetSubjectMetadata = subjectMetadata[origin];
|
||||
} else {
|
||||
const targetUrl = new URL(origin);
|
||||
targetSubjectMetadata = {
|
||||
name: targetUrl.hostname,
|
||||
origin,
|
||||
iconUrl: null,
|
||||
extensionId: null,
|
||||
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||
};
|
||||
}
|
||||
}
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
const isSnap = targetSubjectMetadata.subjectType === SUBJECT_TYPES.SNAP;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const accountsWithLabels = getAccountsWithLabels(state);
|
||||
|
||||
@ -74,17 +75,35 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const connectPath = `${CONNECT_ROUTE}/${permissionsRequestId}`;
|
||||
const confirmPermissionPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_CONFIRM_PERMISSIONS_ROUTE}`;
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
let totalPages = 1 + isRequestingAccounts;
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
totalPages += isSnap;
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
totalPages = totalPages.toString();
|
||||
|
||||
let page = '';
|
||||
if (pathname === connectPath) {
|
||||
page = '1';
|
||||
} else if (pathname === confirmPermissionPath) {
|
||||
page = '2';
|
||||
page = isRequestingAccounts ? '2' : '1';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
} else if (pathname === snapInstallPath) {
|
||||
page = isRequestingAccounts ? '3' : '2';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} else {
|
||||
throw new Error('Incorrect path for permissions-connect component');
|
||||
}
|
||||
|
||||
return {
|
||||
isRequestingAccounts,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
isSnap,
|
||||
snapInstallPath,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
permissionsRequest,
|
||||
permissionsRequestId,
|
||||
accounts: accountsWithLabels,
|
||||
@ -96,6 +115,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
lastConnectedInfo,
|
||||
connectPath,
|
||||
confirmPermissionPath,
|
||||
totalPages,
|
||||
page,
|
||||
targetSubjectMetadata,
|
||||
};
|
||||
|
1
ui/pages/settings/flask/snaps-list-tab/index.js
Normal file
1
ui/pages/settings/flask/snaps-list-tab/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-list-tab';
|
21
ui/pages/settings/flask/snaps-list-tab/index.scss
Normal file
21
ui/pages/settings/flask/snaps-list-tab/index.scss
Normal file
@ -0,0 +1,21 @@
|
||||
.snap-list-tab {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&__wrapper {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 12px 18px;
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.snap-settings-card {
|
||||
margin: 8px 0;
|
||||
max-width: 344px;
|
||||
}
|
||||
}
|
94
ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js
Normal file
94
ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js
Normal file
@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import SnapSettingsCard from '../../../../components/app/flask/snap-settings-card';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import Typography from '../../../../components/ui/typography/typography';
|
||||
import {
|
||||
TYPOGRAPHY,
|
||||
COLORS,
|
||||
FLEX_DIRECTION,
|
||||
JUSTIFY_CONTENT,
|
||||
ALIGN_ITEMS,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import Box from '../../../../components/ui/box';
|
||||
import { SNAPS_VIEW_ROUTE } from '../../../../helpers/constants/routes';
|
||||
import { disableSnap, enableSnap } from '../../../../store/actions';
|
||||
import { getSnaps } from '../../../../selectors';
|
||||
|
||||
const SnapListTab = () => {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const snaps = useSelector(getSnaps);
|
||||
const onClick = (snap) => {
|
||||
const route = `${SNAPS_VIEW_ROUTE}/${window.btoa(
|
||||
unescape(encodeURIComponent(snap.id)),
|
||||
)}`;
|
||||
history.push(route);
|
||||
};
|
||||
const onToggle = (snap) => {
|
||||
if (snap.enabled) {
|
||||
dispatch(disableSnap(snap.id));
|
||||
} else {
|
||||
dispatch(enableSnap(snap.id));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="snap-list-tab">
|
||||
{Object.entries(snaps).length ? (
|
||||
<div className="snap-list-tab__body">
|
||||
<Box display="flex" flexDirection={FLEX_DIRECTION.COLUMN}>
|
||||
<Typography variant={TYPOGRAPHY.H5} marginBottom={2}>
|
||||
{t('expandExperience')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
color={COLORS.UI4}
|
||||
marginBottom={2}
|
||||
>
|
||||
{t('manageSnaps')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<div className="snap-list-tab__wrapper">
|
||||
{Object.entries(snaps).map(([key, snap]) => {
|
||||
return (
|
||||
<SnapSettingsCard
|
||||
className="snap-settings-card"
|
||||
isEnabled={snap.enabled}
|
||||
key={key}
|
||||
onToggle={() => {
|
||||
onToggle(snap);
|
||||
}}
|
||||
description={snap.manifest.description}
|
||||
url={snap.id}
|
||||
name={snap.manifest.proposedName}
|
||||
status={snap.status}
|
||||
version={snap.version}
|
||||
onClick={() => {
|
||||
onClick(snap);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Box
|
||||
className="snap-list-tab__container--no-snaps"
|
||||
width="full"
|
||||
height="full"
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
>
|
||||
<Typography variant={TYPOGRAPHY.H4} color={COLORS.UI4}>
|
||||
<span>{t('noSnaps')}</span>
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SnapListTab;
|
@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../../../../store/store';
|
||||
import testData from '../../../../../.storybook/test-data';
|
||||
import SnapListTab from './snap-list-tab';
|
||||
|
||||
// Using Test Data For Redux
|
||||
const store = configureStore(testData);
|
||||
|
||||
export default {
|
||||
title: 'Pages/Settings/SnapListTab',
|
||||
id: __filename,
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
argTypes: {
|
||||
onToggle: {
|
||||
action: 'onToggle',
|
||||
},
|
||||
onRemove: {
|
||||
action: 'onRemove',
|
||||
},
|
||||
},
|
||||
};
|
||||
export const DefaultStory = (args) => {
|
||||
const state = store.getState();
|
||||
const [viewingSnap, setViewingSnap] = useState();
|
||||
const [snap, setSnap] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SnapListTab
|
||||
{...args}
|
||||
snaps={state.metamask.snaps}
|
||||
viewingSnap={viewingSnap}
|
||||
currentSnap={snap}
|
||||
onToggle={args.onToggle}
|
||||
onRemove={args.onRemove}
|
||||
onClick={(_, s) => {
|
||||
setSnap(s);
|
||||
setViewingSnap(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const state = store.getState();
|
||||
DefaultStory.args = {
|
||||
snaps: state.metamask.snaps,
|
||||
viewingSnap: false,
|
||||
};
|
||||
DefaultStory.storyName = 'Default';
|
1
ui/pages/settings/flask/view-snap/index.js
Normal file
1
ui/pages/settings/flask/view-snap/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './view-snap';
|
114
ui/pages/settings/flask/view-snap/index.scss
Normal file
114
ui/pages/settings/flask/view-snap/index.scss
Normal file
@ -0,0 +1,114 @@
|
||||
.view-snap {
|
||||
padding: 12px 18px;
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
&__subheader {
|
||||
@include H4;
|
||||
|
||||
padding: 16px 4px;
|
||||
border-bottom: 1px solid var(--alto);
|
||||
margin-right: 24px;
|
||||
height: 72px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
margin-right: 0;
|
||||
padding: 0 0 16px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__pill-toggle-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__pill-container {
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding-left: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__toggle-container {
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding-left: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__content-container {
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--Grey-100);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
height: initial;
|
||||
padding: 5px 0 16px;
|
||||
}
|
||||
|
||||
.connected-sites-list__content-row {
|
||||
border-top: none;
|
||||
border-bottom: 1px solid var(--ui-2);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__permission-list {
|
||||
padding-bottom: 0;
|
||||
|
||||
.permission {
|
||||
padding-top: 16px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__remove-button {
|
||||
max-width: 175px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
171
ui/pages/settings/flask/view-snap/view-snap.js
Normal file
171
ui/pages/settings/flask/view-snap/view-snap.js
Normal file
@ -0,0 +1,171 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Button from '../../../../components/ui/button';
|
||||
import Typography from '../../../../components/ui/typography';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import {
|
||||
TYPOGRAPHY,
|
||||
COLORS,
|
||||
TEXT_ALIGN,
|
||||
FRACTIONS,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import SnapsAuthorshipPill from '../../../../components/app/flask/snaps-authorship-pill';
|
||||
import Box from '../../../../components/ui/box';
|
||||
import ToggleButton from '../../../../components/ui/toggle-button';
|
||||
import PermissionsConnectPermissionList from '../../../../components/app/permissions-connect-permission-list/permissions-connect-permission-list';
|
||||
import ConnectedSitesList from '../../../../components/app/connected-sites-list';
|
||||
import Tooltip from '../../../../components/ui/tooltip';
|
||||
import { SNAPS_LIST_ROUTE } from '../../../../helpers/constants/routes';
|
||||
import {
|
||||
disableSnap,
|
||||
enableSnap,
|
||||
removeSnap,
|
||||
removePermissionsFor,
|
||||
} from '../../../../store/actions';
|
||||
import { getSnaps, getSubjectsWithPermission } from '../../../../selectors';
|
||||
|
||||
function ViewSnap() {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
const pathNameTail = pathname.match(/[^/]+$/u)[0];
|
||||
const snaps = useSelector(getSnaps);
|
||||
const snap = Object.entries(snaps)
|
||||
.map(([_, snapState]) => snapState)
|
||||
.find((snapState) => {
|
||||
const decoded = decodeURIComponent(escape(window.atob(pathNameTail)));
|
||||
return snapState.id === decoded;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!snap) {
|
||||
history.push(SNAPS_LIST_ROUTE);
|
||||
}
|
||||
}, [history, snap]);
|
||||
|
||||
const authorshipPillUrl = `https://npmjs.com/package/${snap?.manifest.source.location.npm.packageName}`;
|
||||
const connectedSubjects = useSelector((state) =>
|
||||
getSubjectsWithPermission(state, snap?.permissionName),
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const onDisconnect = (connectedOrigin, snapPermissionName) => {
|
||||
dispatch(
|
||||
removePermissionsFor({
|
||||
[connectedOrigin]: [snapPermissionName],
|
||||
}),
|
||||
);
|
||||
};
|
||||
const onToggle = () => {
|
||||
if (snap.enabled) {
|
||||
dispatch(disableSnap(snap.id));
|
||||
} else {
|
||||
dispatch(enableSnap(snap.id));
|
||||
}
|
||||
};
|
||||
|
||||
if (!snap) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="view-snap">
|
||||
<div className="settings-page__content-row">
|
||||
<div className="view-snap__subheader">
|
||||
<Typography
|
||||
className="view-snap__title"
|
||||
variant={TYPOGRAPHY.H3}
|
||||
boxProps={{ textAlign: TEXT_ALIGN.CENTER }}
|
||||
>
|
||||
{snap.manifest.proposedName}
|
||||
</Typography>
|
||||
<Box className="view-snap__pill-toggle-container">
|
||||
<Box className="view-snap__pill-container" paddingLeft={2}>
|
||||
<SnapsAuthorshipPill
|
||||
packageName={snap.id}
|
||||
url={authorshipPillUrl}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
paddingLeft={4}
|
||||
className="snap-settings-card__toggle-container view-snap__toggle-container"
|
||||
>
|
||||
<Tooltip interactive position="bottom" html={t('snapsToggle')}>
|
||||
<ToggleButton
|
||||
value={snap.enabled}
|
||||
onToggle={onToggle}
|
||||
className="snap-settings-card__toggle-container__toggle-button"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
<Box
|
||||
className="view-snap__content-container"
|
||||
width={FRACTIONS.SEVEN_TWELFTHS}
|
||||
>
|
||||
<div className="view-snap__section">
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
color={COLORS.UI4}
|
||||
boxProps={{ marginTop: 5 }}
|
||||
>
|
||||
{snap.manifest.description}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="view-snap__section view-snap__permission-list">
|
||||
<Typography variant={TYPOGRAPHY.H4}>{t('permissions')}</Typography>
|
||||
<Typography variant={TYPOGRAPHY.H6} color={COLORS.UI4}>
|
||||
{t('snapAccess', [snap.manifest.proposedName])}
|
||||
</Typography>
|
||||
<Box width={FRACTIONS.TEN_TWELFTHS}>
|
||||
<PermissionsConnectPermissionList
|
||||
permissions={snap.manifest.initialPermissions}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
<div className="view-snap__section">
|
||||
<Box width="11/12">
|
||||
<Typography variant={TYPOGRAPHY.H4}>
|
||||
{t('connectedSites')}
|
||||
</Typography>
|
||||
<Typography variant={TYPOGRAPHY.H6} color={COLORS.UI4}>
|
||||
{t('connectedSnapSites', [snap.manifest.proposedName])}
|
||||
</Typography>
|
||||
<ConnectedSitesList
|
||||
connectedSubjects={connectedSubjects}
|
||||
onDisconnect={(origin) => {
|
||||
onDisconnect(origin, snap.permissionName);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
<div className="view-snap__section">
|
||||
<Typography variant={TYPOGRAPHY.H4}>{t('removeSnap')}</Typography>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
color={COLORS.UI4}
|
||||
boxProps={{ paddingBottom: 3 }}
|
||||
>
|
||||
{t('removeSnapDescription')}
|
||||
</Typography>
|
||||
<Button
|
||||
className="view-snap__remove-button"
|
||||
type="danger"
|
||||
css={{
|
||||
maxWidth: '175px',
|
||||
}}
|
||||
onClick={async () => {
|
||||
await dispatch(removeSnap(snap));
|
||||
}}
|
||||
>
|
||||
{t('removeSnap')}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ViewSnap);
|
@ -3,6 +3,8 @@
|
||||
@import 'networks-tab/index';
|
||||
@import 'settings-tab/index';
|
||||
@import 'contact-list-tab/index';
|
||||
@import 'flask/snaps-list-tab/index';
|
||||
@import 'flask/view-snap/index';
|
||||
|
||||
.settings-page {
|
||||
position: relative;
|
||||
|
@ -11,6 +11,10 @@ import {
|
||||
ABOUT_US_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
NETWORKS_ROUTE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SNAPS_VIEW_ROUTE,
|
||||
SNAPS_LIST_ROUTE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
CONTACT_LIST_ROUTE,
|
||||
CONTACT_ADD_ROUTE,
|
||||
CONTACT_EDIT_ROUTE,
|
||||
@ -26,6 +30,10 @@ import InfoTab from './info-tab';
|
||||
import SecurityTab from './security-tab';
|
||||
import ContactListTab from './contact-list-tab';
|
||||
import ExperimentalTab from './experimental-tab';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import SnapListTab from './flask/snaps-list-tab';
|
||||
import ViewSnap from './flask/view-snap';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
class SettingsPage extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -35,6 +43,7 @@ class SettingsPage extends PureComponent {
|
||||
history: PropTypes.object,
|
||||
isAddressEntryPage: PropTypes.bool,
|
||||
isPopup: PropTypes.bool,
|
||||
isSnapViewPage: PropTypes.bool,
|
||||
pathnameI18nKey: PropTypes.string,
|
||||
initialBreadCrumbRoute: PropTypes.string,
|
||||
breadCrumbTextKey: PropTypes.string,
|
||||
@ -74,8 +83,8 @@ class SettingsPage extends PureComponent {
|
||||
currentPath,
|
||||
mostRecentOverviewPage,
|
||||
addNewNetwork,
|
||||
isSnapViewPage,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('main-container settings-page', {
|
||||
@ -106,7 +115,7 @@ class SettingsPage extends PureComponent {
|
||||
{this.renderTabs()}
|
||||
</div>
|
||||
<div className="settings-page__content__modules">
|
||||
{this.renderSubHeader()}
|
||||
{isSnapViewPage ? null : this.renderSubHeader()}
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
@ -116,11 +125,16 @@ class SettingsPage extends PureComponent {
|
||||
|
||||
renderTitle() {
|
||||
const { t } = this.context;
|
||||
const { isPopup, pathnameI18nKey, addressName } = this.props;
|
||||
|
||||
const {
|
||||
isPopup,
|
||||
pathnameI18nKey,
|
||||
addressName,
|
||||
isSnapViewPage,
|
||||
} = this.props;
|
||||
let titleText;
|
||||
|
||||
if (isPopup && addressName) {
|
||||
if (isSnapViewPage) {
|
||||
titleText = t('snaps');
|
||||
} else if (isPopup && addressName) {
|
||||
titleText = t('details');
|
||||
} else if (pathnameI18nKey && isPopup) {
|
||||
titleText = t(pathnameI18nKey);
|
||||
@ -152,7 +166,7 @@ class SettingsPage extends PureComponent {
|
||||
} else if (initialBreadCrumbKey) {
|
||||
subheaderText = t(initialBreadCrumbKey);
|
||||
} else {
|
||||
subheaderText = t(pathnameI18nKey || 'contacts');
|
||||
subheaderText = t(pathnameI18nKey || 'general');
|
||||
}
|
||||
|
||||
return (
|
||||
@ -207,6 +221,18 @@ class SettingsPage extends PureComponent {
|
||||
content: t('contacts'),
|
||||
key: CONTACT_LIST_ROUTE,
|
||||
},
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
{
|
||||
icon: (
|
||||
<img
|
||||
src="images/experimental-icon.svg"
|
||||
alt={t('snapsSettingsDescription')}
|
||||
/>
|
||||
),
|
||||
content: t('snaps'),
|
||||
key: SNAPS_LIST_ROUTE,
|
||||
},
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
{
|
||||
icon: <img src="images/security-icon.svg" alt="" />,
|
||||
content: t('securityAndPrivacy'),
|
||||
@ -280,6 +306,16 @@ class SettingsPage extends PureComponent {
|
||||
path={`${CONTACT_VIEW_ROUTE}/:id`}
|
||||
component={ContactListTab}
|
||||
/>
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
<Route exact path={SNAPS_LIST_ROUTE} component={SnapListTab} />
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
{
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
<Route exact path={`${SNAPS_VIEW_ROUTE}/:id`} component={ViewSnap} />
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
}
|
||||
<Route
|
||||
render={(routeProps) => (
|
||||
<SettingsTab
|
||||
|
@ -25,6 +25,8 @@ import {
|
||||
SETTINGS_ROUTE,
|
||||
EXPERIMENTAL_ROUTE,
|
||||
ADD_NETWORK_ROUTE,
|
||||
SNAPS_LIST_ROUTE,
|
||||
SNAPS_VIEW_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import Settings from './settings.component';
|
||||
|
||||
@ -36,6 +38,8 @@ const ROUTES_TO_I18N_KEYS = {
|
||||
[CONTACT_ADD_ROUTE]: 'newContact',
|
||||
[CONTACT_EDIT_ROUTE]: 'editContact',
|
||||
[CONTACT_LIST_ROUTE]: 'contacts',
|
||||
[SNAPS_LIST_ROUTE]: 'snaps',
|
||||
[SNAPS_VIEW_ROUTE]: 'snaps',
|
||||
[CONTACT_VIEW_ROUTE]: 'viewContact',
|
||||
[NETWORKS_ROUTE]: 'networks',
|
||||
[NETWORKS_FORM_ROUTE]: 'networks',
|
||||
@ -52,8 +56,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
} = state;
|
||||
|
||||
const pathNameTail = pathname.match(/[^/]+$/u)[0];
|
||||
|
||||
const isAddressEntryPage = pathNameTail.includes('0x');
|
||||
const isSnapViewPage = Boolean(pathname.match(SNAPS_VIEW_ROUTE));
|
||||
const isAddContactPage = Boolean(pathname.match(CONTACT_ADD_ROUTE));
|
||||
const isEditContactPage = Boolean(pathname.match(CONTACT_EDIT_ROUTE));
|
||||
const isNetworksFormPage =
|
||||
@ -71,6 +75,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
backRoute = CONTACT_LIST_ROUTE;
|
||||
} else if (isNetworksFormPage) {
|
||||
backRoute = NETWORKS_ROUTE;
|
||||
} else if (isSnapViewPage) {
|
||||
backRoute = SNAPS_LIST_ROUTE;
|
||||
}
|
||||
|
||||
let initialBreadCrumbRoute;
|
||||
@ -96,6 +102,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||
addNewNetwork,
|
||||
conversionDate,
|
||||
isSnapViewPage,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -15,6 +15,8 @@ import {
|
||||
NETWORKS_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
SNAPS_LIST_ROUTE,
|
||||
SNAPS_VIEW_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import SettingsPage from './settings.component';
|
||||
|
||||
@ -38,6 +40,8 @@ const ROUTES_TO_I18N_KEYS = {
|
||||
[CONTACT_ADD_ROUTE]: 'newContact',
|
||||
[CONTACT_EDIT_ROUTE]: 'editContact',
|
||||
[CONTACT_LIST_ROUTE]: 'contacts',
|
||||
[SNAPS_LIST_ROUTE]: 'snaps',
|
||||
[SNAPS_VIEW_ROUTE]: 'snaps',
|
||||
[CONTACT_VIEW_ROUTE]: 'viewContact',
|
||||
[NETWORKS_ROUTE]: 'networks',
|
||||
[NETWORKS_FORM_ROUTE]: 'networks',
|
||||
@ -54,9 +58,9 @@ const Settings = ({ history }) => {
|
||||
location.pathname === '/iframe.html'
|
||||
? '/settings/general'
|
||||
: location.pathname;
|
||||
|
||||
const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname];
|
||||
const backRoute = SETTINGS_ROUTE;
|
||||
const isSnapViewPage = Boolean(pathname.match(SNAPS_VIEW_ROUTE));
|
||||
const backRoute = isSnapViewPage ? SNAPS_LIST_ROUTE : SETTINGS_ROUTE;
|
||||
|
||||
return (
|
||||
<div style={{ height: 500 }}>
|
||||
@ -66,6 +70,7 @@ const Settings = ({ history }) => {
|
||||
history={history}
|
||||
pathnameI18nKey={pathnameI18nKey}
|
||||
backRoute={backRoute}
|
||||
isSnapViewPage={isSnapViewPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
getOriginOfCurrentTab,
|
||||
getSelectedAddress,
|
||||
getSubjectMetadata,
|
||||
getTargetSubjectMetadata,
|
||||
} from '.';
|
||||
|
||||
// selectors
|
||||
@ -96,6 +97,27 @@ export function getConnectedSubjectsForSelectedAddress(state) {
|
||||
return connectedSubjects;
|
||||
}
|
||||
|
||||
export function getSubjectsWithPermission(state, permissionName) {
|
||||
const subjects = getPermissionSubjects(state);
|
||||
|
||||
const connectedSubjects = [];
|
||||
|
||||
Object.entries(subjects).forEach(([origin, { permissions }]) => {
|
||||
if (permissions[permissionName]) {
|
||||
const { extensionId, name, iconUrl } =
|
||||
getTargetSubjectMetadata(state, origin) || {};
|
||||
|
||||
connectedSubjects.push({
|
||||
extensionId,
|
||||
origin,
|
||||
name,
|
||||
iconUrl,
|
||||
});
|
||||
}
|
||||
});
|
||||
return connectedSubjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object mapping addresses to objects mapping origins to connected
|
||||
* subject info. Subject info objects have the following properties:
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { createSelector } from 'reselect';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import { memoize } from 'lodash';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { addHexPrefix } from '../../app/scripts/lib/util';
|
||||
import {
|
||||
MAINNET_CHAIN_ID,
|
||||
@ -16,13 +19,20 @@ import {
|
||||
TRANSPORT_STATES,
|
||||
} from '../../shared/constants/hardware-wallets';
|
||||
|
||||
import {
|
||||
MESSAGE_TYPE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SUBJECT_TYPES,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../shared/constants/app';
|
||||
|
||||
import { TRUNCATED_NAME_CHAR_LIMIT } from '../../shared/constants/labels';
|
||||
|
||||
import {
|
||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||
ALLOWED_SWAPS_CHAIN_IDS,
|
||||
} from '../../shared/constants/swaps';
|
||||
|
||||
import { TRUNCATED_NAME_CHAR_LIMIT } from '../../shared/constants/labels';
|
||||
|
||||
import {
|
||||
shortenAddress,
|
||||
getAccountByAddress,
|
||||
@ -50,7 +60,6 @@ import {
|
||||
getLedgerWebHidConnectedStatus,
|
||||
getLedgerTransportStatus,
|
||||
} from '../ducks/app/app';
|
||||
import { MESSAGE_TYPE } from '../../shared/constants/app';
|
||||
|
||||
/**
|
||||
* One of the only remaining valid uses of selecting the network subkey of the
|
||||
@ -526,6 +535,31 @@ export function getSubjectMetadata(state) {
|
||||
return state.metamask.subjectMetadata;
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
/**
|
||||
* @param {string} svgString - The raw SVG string to make embeddable.
|
||||
* @returns {string} The embeddable SVG string.
|
||||
*/
|
||||
const getEmbeddableSvg = memoize(
|
||||
(svgString) => `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`,
|
||||
);
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
export function getTargetSubjectMetadata(state, origin) {
|
||||
const metadata = getSubjectMetadata(state)[origin];
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
if (metadata?.subjectType === SUBJECT_TYPES.SNAP) {
|
||||
const { svgIcon, ...remainingMetadata } = metadata;
|
||||
return {
|
||||
...remainingMetadata,
|
||||
iconUrl: svgIcon ? getEmbeddableSvg(svgIcon) : null,
|
||||
};
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function getRpcPrefsForCurrentProvider(state) {
|
||||
const { frequentRpcListDetail, provider } = state.metamask;
|
||||
const selectRpcInfo = frequentRpcListDetail.find(
|
||||
@ -650,6 +684,12 @@ export function getShowWhatsNewPopup(state) {
|
||||
return state.appState.showWhatsNewPopup;
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
export function getSnaps(state) {
|
||||
return state.metamask.snaps;
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
/**
|
||||
* Get an object of notification IDs and if they are allowed or not.
|
||||
*
|
||||
|
@ -807,6 +807,33 @@ export function txError(err) {
|
||||
};
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
export function disableSnap(snapId) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.disableSnap(snapId);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function enableSnap(snapId) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.enableSnap(snapId);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function removeSnap(snap) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.removeSnap(snap);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export async function removeSnapError(msgData) {
|
||||
return promisifiedBackground.removeSnapError(msgData);
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
export function cancelMsg(msgData) {
|
||||
return async (dispatch) => {
|
||||
dispatch(showLoadingIndication());
|
||||
|
241
yarn.lock
241
yarn.lock
@ -2586,49 +2586,12 @@
|
||||
semver "^7.3.5"
|
||||
yargs "^17.0.1"
|
||||
|
||||
"@metamask/contract-metadata@^1.29.0", "@metamask/contract-metadata@^1.31.0":
|
||||
"@metamask/contract-metadata@^1.31.0":
|
||||
version "1.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.31.0.tgz#9e3e46de7a955ea1ca61f7db20d9a17b5e91d3d0"
|
||||
integrity sha512-4FBJkg/vDiYp/thIiZknxrJ0lfsj2eWIPenwlNZmoqOhoL4VqhK5eKWxi+EuGMvv9taP+QBRk6Key7wC1uL78A==
|
||||
|
||||
"@metamask/controllers@^17.0.0":
|
||||
version "17.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-17.0.0.tgz#7ef00b4f7583d8075115e8a2f074d7b66646bbe8"
|
||||
integrity sha512-myPlAk8SpNm5SwHHKGgm2XDLP4bxNR2UsKoQlYtV7bJq3l8FV1agSFwHBwDhg61/52Xvqdqy+1YDVdV3kOwPgg==
|
||||
dependencies:
|
||||
"@ethereumjs/common" "^2.3.1"
|
||||
"@ethereumjs/tx" "^3.2.1"
|
||||
"@metamask/contract-metadata" "^1.29.0"
|
||||
"@types/uuid" "^8.3.0"
|
||||
abort-controller "^3.0.0"
|
||||
async-mutex "^0.2.6"
|
||||
babel-runtime "^6.26.0"
|
||||
eth-ens-namehash "^2.0.8"
|
||||
eth-json-rpc-infura "^5.1.0"
|
||||
eth-keyring-controller "^6.2.1"
|
||||
eth-method-registry "1.1.0"
|
||||
eth-phishing-detect "^1.1.14"
|
||||
eth-query "^2.1.2"
|
||||
eth-rpc-errors "^4.0.0"
|
||||
eth-sig-util "^3.0.0"
|
||||
ethereumjs-util "^7.0.10"
|
||||
ethereumjs-wallet "^1.0.1"
|
||||
ethers "^5.4.1"
|
||||
ethjs-unit "^0.1.6"
|
||||
ethjs-util "^0.1.6"
|
||||
human-standard-collectible-abi "^1.0.2"
|
||||
human-standard-token-abi "^2.0.0"
|
||||
immer "^9.0.6"
|
||||
isomorphic-fetch "^3.0.0"
|
||||
jsonschema "^1.2.4"
|
||||
nanoid "^3.1.12"
|
||||
punycode "^2.1.1"
|
||||
single-call-balance-checker-abi "^1.0.0"
|
||||
uuid "^8.3.2"
|
||||
web3 "^0.20.7"
|
||||
web3-provider-engine "^16.0.3"
|
||||
|
||||
"@metamask/controllers@^25.0.0":
|
||||
"@metamask/controllers@^25.0.0", "@metamask/controllers@^25.1.0":
|
||||
version "25.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-25.1.0.tgz#2efee24a9a2b03ab2a2b0422c8f250931c269560"
|
||||
integrity sha512-syndn2lIhtlACzaqjDrw23dJzw8pZ6en4Cr35C7B9RRS87EhahUqkPP73moAzLtvbyqtBlAUO1HHrqV3lw4E5g==
|
||||
@ -2717,6 +2680,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.1.0.tgz#13829d8244bbf19ea658c0b20d21a77b67de0bdd"
|
||||
integrity sha512-Hggj4y0QIjDzKGTXzarhEPIQyFSB2bi2y6YLJNwaT4JmP30UB5Cj6gqoY0M4pj3QT57fzp0BUuGp7F/AUe28tw==
|
||||
|
||||
"@metamask/iframe-execution-environment-service@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/iframe-execution-environment-service/-/iframe-execution-environment-service-0.9.0.tgz#721e15ee4651741a599940dbcfa524cc55eaaa47"
|
||||
integrity sha512-a240sg83sX1dxfBDdRd0uoujaN4V9VtHKELMcTMgpYCI0uE83//Q01a7L8MiBtLhzr8o4D/xXRUIDR0Y9NKc3Q==
|
||||
dependencies:
|
||||
"@metamask/controllers" "^25.1.0"
|
||||
"@metamask/object-multiplex" "^1.2.0"
|
||||
"@metamask/post-message-stream" "^4.0.0"
|
||||
"@metamask/snap-controllers" "^0.9.0"
|
||||
"@metamask/snap-types" "^0.9.0"
|
||||
"@metamask/snap-workers" "^0.9.0"
|
||||
eth-rpc-errors "^4.0.3"
|
||||
json-rpc-engine "^6.1.0"
|
||||
json-rpc-middleware-stream "^3.0.0"
|
||||
nanoid "^3.1.31"
|
||||
pump "^3.0.0"
|
||||
stream "^0.0.2"
|
||||
|
||||
"@metamask/jazzicon@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/jazzicon/-/jazzicon-2.0.0.tgz#5615528e91c0fc5c9d79202d1f0954a7922525a0"
|
||||
@ -2747,10 +2728,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-2.1.0.tgz#316c2e72373506f1a0120b76e432760a27eb6806"
|
||||
integrity sha512-T8LBEB0PQo0N1tZQKZ2K8BGmv+IDLcXkzt8Pn7x0YnwZD6YpCIvKqYM3iy2fJ6wFXeCvRKqpn4K6EqwnkSJAbQ==
|
||||
|
||||
"@metamask/object-multiplex@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.1.0.tgz#6b1507c4d10caafd2ea82dd2a5360b91631e036e"
|
||||
integrity sha512-ImDw5+NdO5qnzmK/rpSlPmQMQm6HIC6wAHdR9nBaDK8TpeuRik5H8DCUcoNrxSeUAk1iHwchZ03lpZu6mZfrdw==
|
||||
"@metamask/object-multiplex@^1.1.0", "@metamask/object-multiplex@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.2.0.tgz#38fc15c142f61939391e1b9a8eed679696c7e4f4"
|
||||
integrity sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==
|
||||
dependencies:
|
||||
end-of-stream "^1.4.4"
|
||||
once "^1.4.0"
|
||||
@ -2765,15 +2746,6 @@
|
||||
readable-stream "^2.2.2"
|
||||
through2 "^2.0.3"
|
||||
|
||||
"@metamask/obs-store@^6.0.2":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/obs-store/-/obs-store-6.0.2.tgz#1fbc458cc617557a4557f9ab58e6676c474df2b1"
|
||||
integrity sha512-MjnP+xNZGBx46YZrR8ZYPb+ScPfxJUbs09MTByuQKxMsf7Lxz17oBTI5ZMkOZOTSBBxhknKdjJg+nAM8mMopwg==
|
||||
dependencies:
|
||||
"@metamask/safe-event-emitter" "^2.0.0"
|
||||
readable-stream "^2.2.2"
|
||||
through2 "^2.0.3"
|
||||
|
||||
"@metamask/obs-store@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/obs-store/-/obs-store-7.0.0.tgz#6cae5f28306bb3e83a381bc9ae22682316095bd3"
|
||||
@ -2807,13 +2779,13 @@
|
||||
pump "^3.0.0"
|
||||
webextension-polyfill-ts "^0.25.0"
|
||||
|
||||
"@metamask/rpc-methods@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.5.0.tgz#3c0073d80e68eceb8b9fa19bea0b2daef8638a42"
|
||||
integrity sha512-OFGd4T20dYTYxdB8WK0xa6FXRaNmJR5mAS7Wp7+6n8rqKljKJ0jDyfpGia1YKI6gKsB7Xdn5efnWxviuF/XQXQ==
|
||||
"@metamask/rpc-methods@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.9.0.tgz#eb55cc39d2ea9a663211e8d805bdf566af70c764"
|
||||
integrity sha512-wii0TMuRscet8+x3tqfAcEmY0TrMFzOnD3QFpFVUy3fznv4b/EzDD/XLQToafd2yUaDjUrrS9FHwU9omqzPxcg==
|
||||
dependencies:
|
||||
"@metamask/key-tree" "^3.0.1"
|
||||
"@metamask/snap-controllers" "^0.5.0"
|
||||
"@metamask/snap-controllers" "^0.9.0"
|
||||
eth-rpc-errors "^4.0.2"
|
||||
|
||||
"@metamask/safe-event-emitter@^2.0.0":
|
||||
@ -2821,57 +2793,49 @@
|
||||
resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c"
|
||||
integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==
|
||||
|
||||
"@metamask/snap-controllers@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.4.0.tgz#20647f061e20263f462347c2bf0d59f7aedd6898"
|
||||
integrity sha512-pZ9S72Y9u2KBrMdzFauz6t4LIxJBqcT6uzxstxdY8y0qroeeTJum6Z0L9HFkVSsTLP3JMyVSDD6FwRsHIwXewg==
|
||||
dependencies:
|
||||
"@metamask/controllers" "^17.0.0"
|
||||
"@metamask/object-multiplex" "^1.1.0"
|
||||
"@metamask/obs-store" "^6.0.2"
|
||||
"@metamask/post-message-stream" "4.0.0"
|
||||
"@metamask/safe-event-emitter" "^2.0.0"
|
||||
"@metamask/snap-workers" "^0.4.0"
|
||||
"@types/deep-freeze-strict" "^1.1.0"
|
||||
deep-freeze-strict "^1.1.1"
|
||||
eth-rpc-errors "^4.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
immer "^9.0.6"
|
||||
json-rpc-engine "^6.1.0"
|
||||
json-rpc-middleware-stream "^3.0.0"
|
||||
nanoid "^3.1.28"
|
||||
pump "^3.0.0"
|
||||
"@metamask/slip44@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/slip44/-/slip44-2.0.0.tgz#1b646a1418af341d5ea979c28015a817ff23af33"
|
||||
integrity sha512-eRomm783ti/1b/TlNnlTCUkYRuTaMYkeTAG0z2rt/WyT8UzxY+8+v/kbl9vk5qhDHeclzBrd9gbqLnLU1kh+Ow==
|
||||
|
||||
"@metamask/snap-controllers@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.5.0.tgz#a77563ea7bea0ba7c6fd73059f9ecbf86be16fd4"
|
||||
integrity sha512-eKuKQh17LrHfkpk8T5J87jg4TTmnoG65JpPHgpV+GLyohF4CMEtyK6uJYoPcm5c3z7IArcYbuucc1bqLbz9JoA==
|
||||
"@metamask/snap-controllers@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.9.0.tgz#e0006fc9991e995dd86dff792106990aae2aeda0"
|
||||
integrity sha512-os3fEai0w4ctpyy6ExlthY8tnww98Vm+RVwOZgrCKDY5dAXqlSXpyWc1uOfkQyiPhUEJtdznJTWzaWzNIO9MfQ==
|
||||
dependencies:
|
||||
"@metamask/controllers" "^17.0.0"
|
||||
"@metamask/controllers" "^25.1.0"
|
||||
"@metamask/object-multiplex" "^1.1.0"
|
||||
"@metamask/obs-store" "^7.0.0"
|
||||
"@metamask/post-message-stream" "4.0.0"
|
||||
"@metamask/safe-event-emitter" "^2.0.0"
|
||||
"@metamask/snap-workers" "^0.5.0"
|
||||
"@metamask/snap-workers" "^0.9.0"
|
||||
"@types/deep-freeze-strict" "^1.1.0"
|
||||
ajv "^8.8.2"
|
||||
concat-stream "^2.0.0"
|
||||
deep-freeze-strict "^1.1.1"
|
||||
eth-rpc-errors "^4.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
gunzip-maybe "^1.4.2"
|
||||
immer "^9.0.6"
|
||||
json-rpc-engine "^6.1.0"
|
||||
json-rpc-middleware-stream "^3.0.0"
|
||||
nanoid "^3.1.28"
|
||||
nanoid "^3.1.31"
|
||||
pump "^3.0.0"
|
||||
readable-web-to-node-stream "^3.0.2"
|
||||
semver "^7.3.5"
|
||||
tar-stream "^2.2.0"
|
||||
|
||||
"@metamask/snap-workers@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-workers/-/snap-workers-0.4.0.tgz#ba561eb15a7b7e7b353738ad5635a68c03cf64b0"
|
||||
integrity sha512-usPEnwRXIwaDc06f8Jis4/CxXzmZJpPOLucOMqkxGAAz3hepA/T5fbfus12sibo5h6QsG0VTqBQ5AqKFlTr0zQ==
|
||||
"@metamask/snap-types@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.9.0.tgz#aa164111be1b5c53fbaaf03c1bccbdbd0741daa4"
|
||||
integrity sha512-pK4tvurUhcKMEkTD0XvQze5HCbtrgmpFWDztBekNIMJTXDrnYIEw4Dxn+LwCX7WJ0DN/03brQSEzmIrYbcBw7Q==
|
||||
dependencies:
|
||||
"@metamask/controllers" "^25.1.0"
|
||||
|
||||
"@metamask/snap-workers@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-workers/-/snap-workers-0.5.0.tgz#9f1b8243f64819e40d66e659d580b6da59cb8015"
|
||||
integrity sha512-sR30/nmkndPeLox282BdTNnU3g6Mo5Gt8rdr6PUSyfosbwrYtrbZcXFqR+ozK/gNhJ3de7hpjLXKNkVSF8OjOQ==
|
||||
"@metamask/snap-workers@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/snap-workers/-/snap-workers-0.9.0.tgz#215407b632fef4723dd75af7accf1f02a6a46916"
|
||||
integrity sha512-+4YY5CQ7OPFPWh4QF5e4COgc0aWL6Df7Oc8/y//Sabp1rmXWI429OzCOlBi+NGJfQ1K7ORBMlRtOwYB9ZmWyLA==
|
||||
|
||||
"@metamask/test-dapp@^5.0.0":
|
||||
version "5.0.0"
|
||||
@ -5303,10 +5267,10 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.1:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.0.2.tgz#1396e27f208ed56dd5638ab5a251edeb1c91d402"
|
||||
integrity sha512-V0HGxJd0PiDF0ecHYIesTOqfd1gJguwQUOYfMfAWnRsWQEXfc5ifbUFhD3Wjc+O+y7VAqL+g07prq9gHQ/JOZQ==
|
||||
ajv@^8.0.1, ajv@^8.8.2:
|
||||
version "8.8.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb"
|
||||
integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
@ -6914,6 +6878,13 @@ browserify-unibabel@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/browserify-unibabel/-/browserify-unibabel-3.0.0.tgz#5a6b8f0f704ce388d3927df47337e25830f71dda"
|
||||
integrity sha1-WmuPD3BM44jTkn30czfiWDD3Hdo=
|
||||
|
||||
browserify-zlib@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
|
||||
integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=
|
||||
dependencies:
|
||||
pako "~0.2.0"
|
||||
|
||||
browserify-zlib@^0.2.0, browserify-zlib@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
|
||||
@ -9839,10 +9810,10 @@ duplexer@^0.1.1, duplexer@~0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
|
||||
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
|
||||
|
||||
duplexify@^3.1.2, duplexify@^3.4.2:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd"
|
||||
integrity sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==
|
||||
duplexify@^3.1.2, duplexify@^3.4.2, duplexify@^3.5.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||
integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
|
||||
dependencies:
|
||||
end-of-stream "^1.0.0"
|
||||
inherits "^2.0.1"
|
||||
@ -9921,6 +9892,11 @@ elliptic@6.5.3, elliptic@6.5.4, elliptic@=3.0.3, elliptic@^6.0.0, elliptic@^6.4.
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emitter-component@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6"
|
||||
integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=
|
||||
|
||||
emittery@0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.0.tgz#bb373c660a9d421bb44706ec4967ed50c02a8026"
|
||||
@ -11417,7 +11393,7 @@ ethjs-util@0.1.3:
|
||||
is-hex-prefixed "1.0.0"
|
||||
strip-hex-prefix "1.0.0"
|
||||
|
||||
ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6:
|
||||
ethjs-util@0.1.6, ethjs-util@^0.1.3:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536"
|
||||
integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==
|
||||
@ -13433,6 +13409,18 @@ gulplog@^1.0.0:
|
||||
dependencies:
|
||||
glogg "^1.0.0"
|
||||
|
||||
gunzip-maybe@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
|
||||
integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
|
||||
dependencies:
|
||||
browserify-zlib "^0.1.4"
|
||||
is-deflate "^1.0.0"
|
||||
is-gzip "^1.0.0"
|
||||
peek-stream "^1.1.0"
|
||||
pumpify "^1.3.3"
|
||||
through2 "^2.0.3"
|
||||
|
||||
gzip-size@5.1.1, gzip-size@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
|
||||
@ -15019,6 +15007,11 @@ is-decimal@^1.0.0, is-decimal@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
|
||||
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
|
||||
|
||||
is-deflate@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
|
||||
integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=
|
||||
|
||||
is-descriptor@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||
@ -15147,6 +15140,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-gzip@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
|
||||
integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=
|
||||
|
||||
is-hex-prefixed@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554"
|
||||
@ -19105,7 +19103,7 @@ nanoid@^2.0.0, nanoid@^2.1.6:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
|
||||
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
|
||||
|
||||
nanoid@^3.1.12, nanoid@^3.1.23, nanoid@^3.1.28, nanoid@^3.1.31:
|
||||
nanoid@^3.1.23, nanoid@^3.1.31:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
|
||||
@ -20444,6 +20442,11 @@ package-json@^6.3.0:
|
||||
registry-url "^5.0.0"
|
||||
semver "^6.2.0"
|
||||
|
||||
pako@~0.2.0:
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
|
||||
integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
|
||||
|
||||
pako@~1.0.2, pako@~1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
|
||||
@ -20816,6 +20819,15 @@ pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.0.9:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
peek-stream@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
|
||||
integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
duplexify "^3.5.0"
|
||||
through2 "^2.0.3"
|
||||
|
||||
peer-book@^0.9.1, peer-book@~0.9.0:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/peer-book/-/peer-book-0.9.1.tgz#42dffd7b1faf263bd6abe2907a26f7411f4dbf34"
|
||||
@ -22811,6 +22823,13 @@ readable-stream@~1.0.15:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-web-to-node-stream@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
|
||||
integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==
|
||||
dependencies:
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
readdir-scoped-modules@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
|
||||
@ -24937,6 +24956,13 @@ stream-to-pull-stream@^1.7.2, stream-to-pull-stream@^1.7.3:
|
||||
looper "^3.0.0"
|
||||
pull-stream "^3.2.3"
|
||||
|
||||
stream@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef"
|
||||
integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=
|
||||
dependencies:
|
||||
emitter-component "^1.1.1"
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
@ -25522,7 +25548,7 @@ tar-fs@^2.0.0:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-stream@^2.0.0, tar-stream@^2.0.1, tar-stream@^2.1.4:
|
||||
tar-stream@^2.0.0, tar-stream@^2.0.1, tar-stream@^2.1.4, tar-stream@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
@ -26751,7 +26777,7 @@ util.promisify@1.0.0:
|
||||
define-properties "^1.1.2"
|
||||
object.getownpropertydescriptors "^2.0.3"
|
||||
|
||||
util@0.10.3, util@~0.10.1:
|
||||
util@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
|
||||
integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
|
||||
@ -26777,6 +26803,13 @@ util@^0.12.0, util@^0.12.3, util@~0.12.0:
|
||||
safe-buffer "^5.1.2"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
util@~0.10.1:
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
|
||||
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
|
||||
dependencies:
|
||||
inherits "2.0.3"
|
||||
|
||||
utila@^0.4.0, utila@~0.4:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
|
||||
|
Loading…
Reference in New Issue
Block a user