mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Implementation encrypt/decrypt feature (#7831)
Implement `eth_decrypt` and `eth_getEncryptionPublicKey`. This allows decryption backed by the user's private key. The message decryption uses a confirmation flow similar to the messaging signing flow, where the message to be decrypted is also able to be decrypted inline for the user to read directly before confirming.
This commit is contained in:
parent
cab2f1b769
commit
6f47fece56
@ -385,7 +385,7 @@
|
||||
"message": "Custom Spend Limit"
|
||||
},
|
||||
"dataBackupFoundInfo": {
|
||||
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts and tokens. Would you like to restore this data now?"
|
||||
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts, and tokens. Would you like to restore this data now?"
|
||||
},
|
||||
"decimalsMustZerotoTen": {
|
||||
"message": "Decimals must be at least 0, and not over 36."
|
||||
@ -1609,5 +1609,35 @@
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Zero gas price on speed up"
|
||||
},
|
||||
"decryptRequest": {
|
||||
"message": "Decrypt request"
|
||||
},
|
||||
"decrypt": {
|
||||
"message": "Decrypt"
|
||||
},
|
||||
"decryptMessageNotice": {
|
||||
"message": "$1 would like to read this message to complete your action",
|
||||
"description": "$1 is website or dapp name"
|
||||
},
|
||||
"decryptMetamask": {
|
||||
"message": "Decrypt message"
|
||||
},
|
||||
"decryptCopy": {
|
||||
"message": "Copy encrypted message"
|
||||
},
|
||||
"decryptInlineError": {
|
||||
"message": "This message cannot be decrypted due to error: $1",
|
||||
"description": "$1 is error message"
|
||||
},
|
||||
"provide": {
|
||||
"message": "Provide"
|
||||
},
|
||||
"encryptionPublicKeyRequest": {
|
||||
"message": "Request encryption public key"
|
||||
},
|
||||
"encryptionPublicKeyNotice": {
|
||||
"message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.",
|
||||
"description": "$1 is website or dapp name"
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,9 @@
|
||||
"customRPC": {
|
||||
"message": "Пользовательский RPC"
|
||||
},
|
||||
"dataBackupFoundInfo": {
|
||||
"message": "Некоторые данные вашей учетной записи были экспортированы во время предыдущей установки MetaMask. Это может включать ваши настройки, контакты и токены. Вы хотите импортировать эти данные сейчас?"
|
||||
},
|
||||
"decimalsMustZerotoTen": {
|
||||
"message": "Количество десятичных разрядов должно быть минимум 0 и максимум 36."
|
||||
},
|
||||
@ -962,6 +965,9 @@
|
||||
"noConversionRateAvailable": {
|
||||
"message": "Курсы валют недоступны"
|
||||
},
|
||||
"noThanks": {
|
||||
"message": "Нет, спасибо"
|
||||
},
|
||||
"notEnoughGas": {
|
||||
"message": "Не хватает газа"
|
||||
},
|
||||
@ -1046,6 +1052,10 @@
|
||||
"restoreAccountWithSeed": {
|
||||
"message": "Восстановите свой аккаунт с помощью секретной фразы"
|
||||
},
|
||||
"restoreWalletPreferences": {
|
||||
"message": "Были найдены данные экспортированные от $1. Вы желаете восстановить настройки вашего кошелька?",
|
||||
"description": "$1 is the date at which the data was backed up"
|
||||
},
|
||||
"requestsAwaitingAcknowledgement": {
|
||||
"message": "запросы, ожидающие подтверждения"
|
||||
},
|
||||
@ -1339,5 +1349,35 @@
|
||||
},
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ваша сид-фраза"
|
||||
},
|
||||
"decryptRequest": {
|
||||
"message": "Запрос расшифровки"
|
||||
},
|
||||
"decrypt": {
|
||||
"message": "Расшифровать"
|
||||
},
|
||||
"decryptMessageNotice": {
|
||||
"message": "Для $1 необходимо прочитать это сообщение, чтобы завершить Ваше действие",
|
||||
"description": "$1 is website or dapp name"
|
||||
},
|
||||
"decryptMetamask": {
|
||||
"message": "Расшифровать сообщение"
|
||||
},
|
||||
"decryptCopy": {
|
||||
"message": "Скопировать расшифрованное сообщение"
|
||||
},
|
||||
"decryptInlineError": {
|
||||
"message": "Это сообщение не может быть дешифровано из-за ошибки: $1",
|
||||
"description": "$1 is error message"
|
||||
},
|
||||
"provide": {
|
||||
"message": "Предоставить"
|
||||
},
|
||||
"encryptionPublicKeyRequest": {
|
||||
"message": "Запрос публичного ключа шифрования"
|
||||
},
|
||||
"encryptionPublicKeyNotice": {
|
||||
"message": "$1 запрашивает ваш открытый ключ шифрования. По согласованию, этот сайт сможет создавать для Вас зашифрованные сообщения.",
|
||||
"description": "$1 is website or dapp name"
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,10 @@ initialize().catch(log.error)
|
||||
* @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
|
||||
* @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
||||
* @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
|
||||
* @property {Object} EncryptionPublicKeyMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
||||
* @property {number} unapprovedEncryptionPublicKeyMsgCount - The number of messages in EncryptionPublicKeyMsgs.
|
||||
* @property {Object} unapprovedDecryptMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
||||
* @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs.
|
||||
* @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
||||
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
|
||||
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
|
||||
@ -413,6 +417,8 @@ function setupController (initState, initLangCode) {
|
||||
controller.txController.on('update:badge', updateBadge)
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge)
|
||||
controller.decryptMessageManager.on('updateBadge', updateBadge)
|
||||
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge)
|
||||
controller.typedMessageManager.on('updateBadge', updateBadge)
|
||||
controller.permissionsController.permissions.subscribe(updateBadge)
|
||||
|
||||
@ -424,10 +430,13 @@ function setupController (initState, initLangCode) {
|
||||
let label = ''
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
const unapprovedPersonalMsgCount = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
const unapprovedDecryptMsgCount = controller.decryptMessageManager.unapprovedDecryptMsgCount
|
||||
const unapprovedEncryptionPublicKeyMsgCount = controller.encryptionPublicKeyManager.unapprovedEncryptionPublicKeyMsgCount
|
||||
const unapprovedTypedMessagesCount = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
|
||||
unapprovedTypedMessagesCount + pendingPermissionRequests
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ function createMetamaskMiddleware ({
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
processDecryptMessage,
|
||||
processEncryptionPublicKey,
|
||||
getPendingNonce,
|
||||
getPendingTransactionByHash,
|
||||
}) {
|
||||
@ -31,6 +33,8 @@ function createMetamaskMiddleware ({
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
processDecryptMessage,
|
||||
processEncryptionPublicKey,
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
createPendingTxMiddleware({ getPendingTransactionByHash }),
|
||||
|
@ -69,4 +69,6 @@ export const SAFE_METHODS = [
|
||||
'eth_uninstallFilter',
|
||||
'metamask_watchAsset',
|
||||
'wallet_watchAsset',
|
||||
'eth_getEncryptionPublicKey',
|
||||
'eth_decrypt',
|
||||
]
|
||||
|
311
app/scripts/lib/decrypt-message-manager.js
Normal file
311
app/scripts/lib/decrypt-message-manager.js
Normal file
@ -0,0 +1,311 @@
|
||||
import EventEmitter from 'events'
|
||||
import ObservableStore from 'obs-store'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
import log from 'loglevel'
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a
|
||||
* decryption for an eth_decrypt call is requested.
|
||||
*
|
||||
* @typedef {Object} DecryptMessage
|
||||
* @property {number} id An id to track and identify the message object
|
||||
* @property {Object} msgParams The parameters to pass to the decryptMessage method once the decryption request is
|
||||
* approved.
|
||||
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
|
||||
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the decryption request
|
||||
* @property {number} time The epoch time at which the this message was created
|
||||
* @property {string} status Indicates whether the decryption request is 'unapproved', 'approved', 'decrypted' or 'rejected'
|
||||
* @property {string} type The json-prc decryption method for which a decryption request has been made. A 'Message' will
|
||||
* always have a 'eth_decrypt' type.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class DecryptMessageManager extends EventEmitter {
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - DecryptMessage.
|
||||
*
|
||||
* @typedef {Object} DecryptMessageManager
|
||||
* @property {Object} memStore The observable store where DecryptMessage are saved with persistance.
|
||||
* @property {Object} memStore.unapprovedDecryptMsgs A collection of all DecryptMessages in the 'unapproved' state
|
||||
* @property {number} memStore.unapprovedDecryptMsgCount The count of all DecryptMessages in this.memStore.unapprobedMsgs
|
||||
* @property {array} messages Holds all messages that have been created by this DecryptMessageManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedDecryptMsgs: {},
|
||||
unapprovedDecryptMsgCount: 0,
|
||||
})
|
||||
this.messages = []
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' DecryptMessages in this.messages
|
||||
*
|
||||
* @returns {number} The number of 'unapproved' DecryptMessages in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedDecryptMsgCount () {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the 'unapproved' DecryptMessages in this.messages
|
||||
*
|
||||
* @returns {Object} An index of DecryptMessage ids to DecryptMessages, for all 'unapproved' DecryptMessages in
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg; return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {Object} msgParams The params for the eth_decrypt call to be made after the message is approved.
|
||||
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||
* @returns {Promise<Buffer>} The raw decrypted message contents
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (msgParams, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!msgParams.from) {
|
||||
reject(new Error('MetaMask Message for Decryption: from field is required.'))
|
||||
}
|
||||
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||
this.once(`${msgId}:finished`, (data) => {
|
||||
switch (data.status) {
|
||||
case 'decrypted':
|
||||
return resolve(data.rawData)
|
||||
case 'rejected':
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for Decryption: User denied message decryption.'))
|
||||
case 'errored':
|
||||
return reject(new Error('This message cannot be decrypted'))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message for Decryption: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {Object} msgParams The params for the eth_decryptMsg call to be made after the message is approved.
|
||||
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||
* @returns {number} The id of the newly created DecryptMessage.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (msgParams, req) {
|
||||
log.debug(`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||
// add origin from request
|
||||
if (req) {
|
||||
msgParams.origin = req.origin
|
||||
}
|
||||
msgParams.data = this.normalizeMsgData(msgParams.data)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_decrypt',
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
// signal update
|
||||
this.emit('update')
|
||||
return msgId
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a passed DecryptMessage to this.messages, and calls this._saveMsgList() to save the unapproved DecryptMessages from that
|
||||
* list to this.memStore.
|
||||
*
|
||||
* @param {Message} msg The DecryptMessage to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specified DecryptMessage.
|
||||
*
|
||||
* @param {number} msgId The id of the DecryptMessage to get
|
||||
* @returns {DecryptMessage|undefined} The DecryptMessage with the id that matches the passed msgId, or undefined
|
||||
* if no DecryptMessage has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves a DecryptMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
|
||||
* with the message params modified for proper decryption.
|
||||
*
|
||||
* @param {Object} msgParams The msgParams to be used when eth_decryptMsg is called, plus data added by MetaMask.
|
||||
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
|
||||
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForDecryption(msgParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DecryptMessage status to 'approved' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the DecryptMessage to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DecryptMessage status to 'decrypted' via a call to this._setMsgStatus and updates that DecryptMessage in
|
||||
* this.messages by adding the raw decryption data of the decryption request to the DecryptMessage
|
||||
*
|
||||
* @param {number} msgId The id of the DecryptMessage to decrypt.
|
||||
* @param {buffer} rawData The raw data of the message request
|
||||
*
|
||||
*/
|
||||
setMsgStatusDecrypted (msgId, rawData) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawData = rawData
|
||||
this._updateMsg(msg)
|
||||
this._setMsgStatus(msgId, 'decrypted')
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
|
||||
*
|
||||
* @param {Object} msgParams The msgParams to modify
|
||||
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForDecryption (msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DecryptMessage status to 'rejected' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the DecryptMessage to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the TypedMessage to error
|
||||
*
|
||||
*/
|
||||
errorMessage (msgId, error) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.error = error
|
||||
this._updateMsg(msg)
|
||||
this._setMsgStatus(msgId, 'errored')
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of a DecryptMessage in this.messages via a call to this._updateMsg
|
||||
*
|
||||
* @private
|
||||
* @param {number} msgId The id of the DecryptMessage to update.
|
||||
* @param {string} status The new status of the DecryptMessage.
|
||||
* @throws A 'DecryptMessageManager - DecryptMessage not found for id: "${msgId}".' if there is no DecryptMessage
|
||||
* in this.messages with an id equal to the passed msgId
|
||||
* @fires An event with a name equal to `${msgId}:${status}`. The DecryptMessage is also fired.
|
||||
* @fires If status is 'rejected' or 'decrypted', an event with a name equal to `${msgId}:finished` is fired along
|
||||
* with the DecryptMessage
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error('DecryptMessageManager - Message not found for id: "${msgId}".')
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
this.emit(`${msgId}:${status}`, msg)
|
||||
if (status === 'rejected' || status === 'decrypted' || status === 'errored') {
|
||||
this.emit(`${msgId}:finished`, msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DecryptMessage in this.messages to the passed DecryptMessage if the ids are equal. Then saves the
|
||||
* unapprovedDecryptMsgs index to storage via this._saveMsgList
|
||||
*
|
||||
* @private
|
||||
* @param {msg} DecryptMessage A DecryptMessage that will replace an existing DecryptMessage (with the same
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
}
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the unapproved DecryptMessages, and their count, to this.memStore
|
||||
*
|
||||
* @private
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
const unapprovedDecryptMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length
|
||||
this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount })
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
|
||||
*
|
||||
* @param {any} data The buffer data to convert to a hex
|
||||
* @returns {string} A hex string conversion of the buffer data
|
||||
*
|
||||
*/
|
||||
normalizeMsgData (data) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(data)
|
||||
if (stripped.match(hexRe)) {
|
||||
return ethUtil.addHexPrefix(stripped)
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug(`Message was not hex encoded, interpreting as utf8.`)
|
||||
}
|
||||
|
||||
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
|
||||
}
|
||||
|
||||
}
|
286
app/scripts/lib/encryption-public-key-manager.js
Normal file
286
app/scripts/lib/encryption-public-key-manager.js
Normal file
@ -0,0 +1,286 @@
|
||||
import EventEmitter from 'events'
|
||||
import ObservableStore from 'obs-store'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
import log from 'loglevel'
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
|
||||
* an eth_getEncryptionPublicKey call is requested.
|
||||
*
|
||||
* @typedef {Object} EncryptionPublicKey
|
||||
* @property {number} id An id to track and identify the message object
|
||||
* @property {Object} msgParams The parameters to pass to the encryptionPublicKey method once the request is
|
||||
* approved.
|
||||
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
|
||||
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the request
|
||||
* @property {number} time The epoch time at which the this message was created
|
||||
* @property {string} status Indicates whether the request is 'unapproved', 'approved', 'received' or 'rejected'
|
||||
* @property {string} type The json-prc method for which a request has been made. A 'Message' will
|
||||
* always have a 'eth_getEncryptionPublicKey' type.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey.
|
||||
*
|
||||
* @typedef {Object} EncryptionPublicKeyManager
|
||||
* @property {Object} memStore The observable store where EncryptionPublicKey are saved with persistance.
|
||||
* @property {Object} memStore.unapprovedEncryptionPublicKeyMsgs A collection of all EncryptionPublicKeys in the 'unapproved' state
|
||||
* @property {number} memStore.unapprovedEncryptionPublicKeyMsgCount The count of all EncryptionPublicKeys in this.memStore.unapprobedMsgs
|
||||
* @property {array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 0,
|
||||
})
|
||||
this.messages = []
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
* @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedEncryptionPublicKeyMsgCount () {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
* @returns {Object} An index of EncryptionPublicKey ids to EncryptionPublicKeys, for all 'unapproved' EncryptionPublicKeys in
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg; return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
|
||||
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||
* @returns {Promise<Buffer>} The raw public key contents
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (address, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!address) {
|
||||
reject(new Error('MetaMask Message for EncryptionPublicKey: address field is required.'))
|
||||
}
|
||||
const msgId = this.addUnapprovedMessage(address, req)
|
||||
this.once(`${msgId}:finished`, (data) => {
|
||||
switch (data.status) {
|
||||
case 'received':
|
||||
return resolve(data.rawData)
|
||||
case 'rejected':
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for EncryptionPublicKey: User denied message EncryptionPublicKey.'))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message for EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
|
||||
* @param {Object} _req (optional) The original request object possibly containing the origin
|
||||
* @returns {number} The id of the newly created EncryptionPublicKey.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (address, _req) {
|
||||
log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
msgParams: address,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_getEncryptionPublicKey',
|
||||
}
|
||||
|
||||
if (_req) {
|
||||
msgData.origin = _req.origin
|
||||
}
|
||||
|
||||
this.addMsg(msgData)
|
||||
|
||||
// signal update
|
||||
this.emit('update')
|
||||
return msgId
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a passed EncryptionPublicKey to this.messages, and calls this._saveMsgList() to save the unapproved EncryptionPublicKeys from that
|
||||
* list to this.memStore.
|
||||
*
|
||||
* @param {Message} msg The EncryptionPublicKey to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specified EncryptionPublicKey.
|
||||
*
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to get
|
||||
* @returns {EncryptionPublicKey|undefined} The EncryptionPublicKey with the id that matches the passed msgId, or undefined
|
||||
* if no EncryptionPublicKey has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves a EncryptionPublicKey. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
|
||||
* with any the message params modified for proper providing.
|
||||
*
|
||||
* @param {Object} msgParams The msgParams to be used when eth_getEncryptionPublicKey is called, plus data added by MetaMask.
|
||||
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
|
||||
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForEncryptionPublicKey(msgParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'approved' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'received' via a call to this._setMsgStatus and updates that EncryptionPublicKey in
|
||||
* this.messages by adding the raw data of request to the EncryptionPublicKey
|
||||
*
|
||||
* @param {number} msgId The id of the EncryptionPublicKey.
|
||||
* @param {buffer} rawData The raw data of the message request
|
||||
*
|
||||
*/
|
||||
setMsgStatusReceived (msgId, rawData) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawData = rawData
|
||||
this._updateMsg(msg)
|
||||
this._setMsgStatus(msgId, 'received')
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
|
||||
*
|
||||
* @param {Object} msgParams The msgParams to modify
|
||||
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForEncryptionPublicKey (msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'rejected' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId The id of the TypedMessage to error
|
||||
*
|
||||
*/
|
||||
errorMessage (msgId, error) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.error = error
|
||||
this._updateMsg(msg)
|
||||
this._setMsgStatus(msgId, 'errored')
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg
|
||||
*
|
||||
* @private
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to update.
|
||||
* @param {string} status The new status of the EncryptionPublicKey.
|
||||
* @throws A 'EncryptionPublicKeyManager - EncryptionPublicKey not found for id: "${msgId}".' if there is no EncryptionPublicKey
|
||||
* in this.messages with an id equal to the passed msgId
|
||||
* @fires An event with a name equal to `${msgId}:${status}`. The EncryptionPublicKey is also fired.
|
||||
* @fires If status is 'rejected' or 'received', an event with a name equal to `${msgId}:finished` is fired along
|
||||
* with the EncryptionPublicKey
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error('EncryptionPublicKeyManager - Message not found for id: "${msgId}".')
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
this.emit(`${msgId}:${status}`, msg)
|
||||
if (status === 'rejected' || status === 'received') {
|
||||
this.emit(`${msgId}:finished`, msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey in this.messages to the passed EncryptionPublicKey if the ids are equal. Then saves the
|
||||
* unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList
|
||||
*
|
||||
* @private
|
||||
* @param {msg} EncryptionPublicKey A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
}
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the unapproved EncryptionPublicKeys, and their count, to this.memStore
|
||||
*
|
||||
* @private
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length
|
||||
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
}
|
@ -35,6 +35,8 @@ import ThreeBoxController from './controllers/threebox'
|
||||
import RecentBlocksController from './controllers/recent-blocks'
|
||||
import IncomingTransactionsController from './controllers/incoming-transactions'
|
||||
import MessageManager from './lib/message-manager'
|
||||
import DecryptMessageManager from './lib/decrypt-message-manager'
|
||||
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager'
|
||||
import PersonalMessageManager from './lib/personal-message-manager'
|
||||
import TypedMessageManager from './lib/typed-message-manager'
|
||||
import TransactionController from './controllers/transactions'
|
||||
@ -278,6 +280,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController.lookupNetwork()
|
||||
this.messageManager = new MessageManager()
|
||||
this.personalMessageManager = new PersonalMessageManager()
|
||||
this.decryptMessageManager = new DecryptMessageManager()
|
||||
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager()
|
||||
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
|
||||
|
||||
// ensure isClientOpenAndUnlocked is updated when memState updates
|
||||
@ -313,6 +317,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
TokenRatesController: this.tokenRatesController.store,
|
||||
MessageManager: this.messageManager.memStore,
|
||||
PersonalMessageManager: this.personalMessageManager.memStore,
|
||||
DecryptMessageManager: this.decryptMessageManager.memStore,
|
||||
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
|
||||
TypesMessageManager: this.typedMessageManager.memStore,
|
||||
KeyringController: this.keyringController.memStore,
|
||||
PreferencesController: this.preferencesController.store,
|
||||
@ -363,6 +369,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
|
||||
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
|
||||
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
getPendingTransactionByHash: (hash) => this.txController.getFilteredTxList({ hash, status: 'submitted' })[0],
|
||||
}
|
||||
@ -539,6 +547,15 @@ export default class MetamaskController extends EventEmitter {
|
||||
signTypedMessage: nodeify(this.signTypedMessage, this),
|
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this),
|
||||
|
||||
// decryptMessageManager
|
||||
decryptMessage: nodeify(this.decryptMessage, this),
|
||||
decryptMessageInline: nodeify(this.decryptMessageInline, this),
|
||||
cancelDecryptMessage: this.cancelDecryptMessage.bind(this),
|
||||
|
||||
// EncryptionPublicKeyManager
|
||||
encryptionPublicKey: nodeify(this.encryptionPublicKey, this),
|
||||
cancelEncryptionPublicKey: this.cancelEncryptionPublicKey.bind(this),
|
||||
|
||||
// onboarding controller
|
||||
setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),
|
||||
|
||||
@ -1168,6 +1185,147 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// eth_decrypt methods
|
||||
|
||||
/**
|
||||
* Called when a dapp uses the eth_decrypt method.
|
||||
*
|
||||
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @param {Object} req - (optional) the original request, containing the origin
|
||||
* Passed back to the requesting Dapp.
|
||||
*/
|
||||
async newRequestDecryptMessage (msgParams, req) {
|
||||
const promise = this.decryptMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Only decypt message and don't touch transaction state
|
||||
*
|
||||
* @param {Object} msgParams - The params of the message to decrypt.
|
||||
* @returns {Promise<Object>} - A full state update.
|
||||
*/
|
||||
async decryptMessageInline (msgParams) {
|
||||
log.info('MetaMaskController - decryptMessageInline')
|
||||
// decrypt the message inline
|
||||
const msgId = msgParams.metamaskId
|
||||
const msg = this.decryptMessageManager.getMsg(msgId)
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(msgParams.data)
|
||||
const buff = Buffer.from(stripped, 'hex')
|
||||
msgParams.data = JSON.parse(buff.toString('utf8'))
|
||||
|
||||
msg.rawData = await this.keyringController.decryptMessage(msgParams)
|
||||
} catch (e) {
|
||||
msg.error = e.message
|
||||
}
|
||||
this.decryptMessageManager._updateMsg(msg)
|
||||
|
||||
return this.getState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a user's approval to decrypt a message in queue.
|
||||
* Triggers decrypt, and the callback function from newUnsignedDecryptMessage.
|
||||
*
|
||||
* @param {Object} msgParams - The params of the message to decrypt & return to the Dapp.
|
||||
* @returns {Promise<Object>} - A full state update.
|
||||
*/
|
||||
async decryptMessage (msgParams) {
|
||||
log.info('MetaMaskController - decryptMessage')
|
||||
const msgId = msgParams.metamaskId
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for decryption
|
||||
try {
|
||||
const cleanMsgParams = await this.decryptMessageManager.approveMessage(msgParams)
|
||||
|
||||
const stripped = ethUtil.stripHexPrefix(cleanMsgParams.data)
|
||||
const buff = Buffer.from(stripped, 'hex')
|
||||
cleanMsgParams.data = JSON.parse(buff.toString('utf8'))
|
||||
|
||||
// decrypt the message
|
||||
const rawMess = await this.keyringController.decryptMessage(cleanMsgParams)
|
||||
// tells the listener that the message has been decrypted and can be returned to the dapp
|
||||
this.decryptMessageManager.setMsgStatusDecrypted(msgId, rawMess)
|
||||
} catch (error) {
|
||||
log.info('MetaMaskController - eth_decrypt failed.', error)
|
||||
this.decryptMessageManager.errorMessage(msgId, error)
|
||||
}
|
||||
return this.getState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a eth_decrypt type message.
|
||||
* @param {string} msgId - The ID of the message to cancel.
|
||||
* @param {Function} cb - The callback function called with a full state update.
|
||||
*/
|
||||
cancelDecryptMessage (msgId, cb) {
|
||||
const messageManager = this.decryptMessageManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb(null, this.getState())
|
||||
}
|
||||
}
|
||||
|
||||
// eth_getEncryptionPublicKey methods
|
||||
|
||||
/**
|
||||
* Called when a dapp uses the eth_getEncryptionPublicKey method.
|
||||
*
|
||||
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @param {Object} req - (optional) the original request, containing the origin
|
||||
* Passed back to the requesting Dapp.
|
||||
*/
|
||||
async newRequestEncryptionPublicKey (msgParams, req) {
|
||||
const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(msgParams, req)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a user's approval to receiving encryption public key in queue.
|
||||
* Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey.
|
||||
*
|
||||
* @param {Object} msgParams - The params of the message to receive & return to the Dapp.
|
||||
* @returns {Promise<Object>} - A full state update.
|
||||
*/
|
||||
async encryptionPublicKey (msgParams) {
|
||||
log.info('MetaMaskController - encryptionPublicKey')
|
||||
const msgId = msgParams.metamaskId
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for decryption
|
||||
try {
|
||||
const params = await this.encryptionPublicKeyManager.approveMessage(msgParams)
|
||||
|
||||
// EncryptionPublicKey message
|
||||
const publicKey = await this.keyringController.getEncryptionPublicKey(params.data)
|
||||
|
||||
// tells the listener that the message has been processed
|
||||
// and can be returned to the dapp
|
||||
this.encryptionPublicKeyManager.setMsgStatusReceived(msgId, publicKey)
|
||||
} catch (error) {
|
||||
log.info('MetaMaskController - eth_getEncryptionPublicKey failed.', error)
|
||||
this.encryptionPublicKeyManager.errorMessage(msgId, error)
|
||||
}
|
||||
return this.getState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a eth_getEncryptionPublicKey type message.
|
||||
* @param {string} msgId - The ID of the message to cancel.
|
||||
* @param {Function} cb - The callback function called with a full state update.
|
||||
*/
|
||||
cancelEncryptionPublicKey (msgId, cb) {
|
||||
const messageManager = this.encryptionPublicKeyManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb(null, this.getState())
|
||||
}
|
||||
}
|
||||
|
||||
// eth_signTypedData methods
|
||||
|
||||
/**
|
||||
|
@ -88,7 +88,7 @@
|
||||
"eth-json-rpc-errors": "^2.0.2",
|
||||
"eth-json-rpc-filters": "^4.1.1",
|
||||
"eth-json-rpc-infura": "^4.0.2",
|
||||
"eth-json-rpc-middleware": "^4.4.0",
|
||||
"eth-json-rpc-middleware": "^4.4.1",
|
||||
"eth-keyring-controller": "^5.5.0",
|
||||
"eth-ledger-bridge-keyring": "^0.2.0",
|
||||
"eth-method-registry": "^1.2.0",
|
||||
|
@ -18,6 +18,8 @@
|
||||
"unapprovedMsgCount": 0,
|
||||
"unapprovedPersonalMsgs": {},
|
||||
"unapprovedPersonalMsgCount": 0,
|
||||
"unapprovedDecryptMsgs": {},
|
||||
"unapprovedDecryptMsgCount": 0,
|
||||
"unapprovedTypedMessages": {},
|
||||
"unapprovedTypedMessagesCount": 0,
|
||||
"isUnlocked": true,
|
||||
|
@ -136,6 +136,10 @@
|
||||
"unapprovedMsgCount": 0,
|
||||
"unapprovedPersonalMsgs": {},
|
||||
"unapprovedPersonalMsgCount": 0,
|
||||
"unapprovedDecryptMsgs": {},
|
||||
"unapprovedDecryptMsgCount": 0,
|
||||
"unapprovedEncryptionPublicKeyMsgs": {},
|
||||
"unapprovedEncryptionPublicKeyMsgCount": 0,
|
||||
"unapprovedTypedMessages": {},
|
||||
"unapprovedTypedMessagesCount": 0,
|
||||
"send": {
|
||||
|
@ -42,6 +42,10 @@
|
||||
|
||||
@import './request-signature.scss';
|
||||
|
||||
@import './request-encryption-public-key.scss';
|
||||
|
||||
@import './request-decrypt-message.scss';
|
||||
|
||||
@import './account-details-dropdown.scss';
|
||||
|
||||
@import './editable-label.scss';
|
||||
|
293
ui/app/css/itcss/components/request-decrypt-message.scss
Normal file
293
ui/app/css/itcss/components/request-decrypt-message.scss
Normal file
@ -0,0 +1,293 @@
|
||||
.request-decrypt-message {
|
||||
&__container {
|
||||
width: 380px;
|
||||
border-radius: 8px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
z-index: 25;
|
||||
align-items: center;
|
||||
font-family: Roboto;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 100%;
|
||||
top: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
height: 620px;
|
||||
}
|
||||
}
|
||||
|
||||
&__typed-container {
|
||||
padding: 17px;
|
||||
|
||||
h1 {
|
||||
font-weight: 900;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
* {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
height: 64px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__header-background {
|
||||
position: absolute;
|
||||
background-color: $athens-grey;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__header__text {
|
||||
color: #5B5D67;
|
||||
font-family: Roboto;
|
||||
font-size: 22px;
|
||||
line-height: 29px;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__header__tip-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__header__tip {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
background: $athens-grey;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__account-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__account {
|
||||
color: $dusty-gray;
|
||||
margin-left: 17px;
|
||||
}
|
||||
|
||||
&__account-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__account-item {
|
||||
height: 22px;
|
||||
background-color: $white;
|
||||
font-family: Roboto;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
width: 124px;
|
||||
|
||||
.account-list-item {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.account-list-item__account-name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.account-list-item__top-row {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__balance {
|
||||
color: $dusty-gray;
|
||||
margin-right: 17px;
|
||||
width: 124px;
|
||||
}
|
||||
|
||||
&__balance-text {
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__balance-value {
|
||||
text-align: right;
|
||||
margin-top: 2.5px;
|
||||
}
|
||||
|
||||
&__request-icon {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&__notice {
|
||||
font-family: "Avenir Next";
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 11px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__message {
|
||||
overflow-wrap: break-word;
|
||||
margin: 20px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dedede;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
|
||||
&-text {
|
||||
font-size: 0.7em;
|
||||
height: 115px;
|
||||
}
|
||||
|
||||
&-cover {
|
||||
background-color: white;
|
||||
opacity: 0.75;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&-lock {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
cursor: pointer;
|
||||
img {
|
||||
padding: 5px;
|
||||
background-color: #fff;
|
||||
left: calc(50% - 24px);
|
||||
position: absolute;
|
||||
top: calc(50% - 34px);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&--pressed {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-lock-text {
|
||||
width: 200px;
|
||||
font-size: 0.75em;
|
||||
position: absolute;
|
||||
top: calc(50% + 5px);
|
||||
text-align: center;
|
||||
left: calc(50% - 100px);
|
||||
background-color: white;
|
||||
line-height: 1em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&-copy {
|
||||
justify-content: space-evenly;
|
||||
font-size: 0.75em;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-copy-text {
|
||||
margin-right: 10px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&-copy-tooltip {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid $geyser;
|
||||
padding: 1.6rem;
|
||||
|
||||
button {
|
||||
width: 165px;
|
||||
}
|
||||
|
||||
&__cancel-button {
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__visual {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
&--default {
|
||||
background-color: #777A87;
|
||||
color: white;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
222
ui/app/css/itcss/components/request-encryption-public-key.scss
Normal file
222
ui/app/css/itcss/components/request-encryption-public-key.scss
Normal file
@ -0,0 +1,222 @@
|
||||
.request-encryption-public-key {
|
||||
&__container {
|
||||
width: 380px;
|
||||
border-radius: 8px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
z-index: 25;
|
||||
align-items: center;
|
||||
font-family: Roboto;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 100%;
|
||||
top: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
height: 620px;
|
||||
}
|
||||
}
|
||||
|
||||
&__typed-container {
|
||||
padding: 17px;
|
||||
|
||||
h1 {
|
||||
font-weight: 900;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
* {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
height: 64px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__header-background {
|
||||
position: absolute;
|
||||
background-color: $athens-grey;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__header__text {
|
||||
color: #5B5D67;
|
||||
font-family: Roboto;
|
||||
font-size: 22px;
|
||||
line-height: 29px;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__header__tip-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__header__tip {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
background: $athens-grey;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__account-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__account {
|
||||
color: $dusty-gray;
|
||||
margin-left: 17px;
|
||||
}
|
||||
|
||||
&__account-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__account-item {
|
||||
height: 22px;
|
||||
background-color: $white;
|
||||
font-family: Roboto;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
width: 124px;
|
||||
|
||||
.account-list-item {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.account-list-item__account-name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.account-list-item__top-row {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__balance {
|
||||
color: $dusty-gray;
|
||||
margin-right: 17px;
|
||||
width: 124px;
|
||||
}
|
||||
|
||||
&__balance-text {
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__balance-value {
|
||||
text-align: right;
|
||||
margin-top: 2.5px;
|
||||
}
|
||||
|
||||
&__request-icon {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&__notice {
|
||||
font-family: "Avenir Next";
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
text-align: center;
|
||||
margin-top: 41px;
|
||||
margin-bottom: 11px;
|
||||
width: 100%;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
color: $dusty-gray;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid $geyser;
|
||||
padding: 1.6rem;
|
||||
|
||||
button {
|
||||
width: 165px;
|
||||
}
|
||||
|
||||
&__cancel-button {
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__visual {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
&--default {
|
||||
background-color: #777A87;
|
||||
color: white;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,8 @@ const CONFIRM_APPROVE_PATH = '/approve'
|
||||
const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from'
|
||||
const CONFIRM_TOKEN_METHOD_PATH = '/token-method'
|
||||
const SIGNATURE_REQUEST_PATH = '/signature-request'
|
||||
const DECRYPT_MESSAGE_REQUEST_PATH = '/decrypt-message-request'
|
||||
const ENCRYPTION_PUBLIC_KEY_REQUEST_PATH = '/encryption-public-key-request'
|
||||
|
||||
export {
|
||||
DEFAULT_ROUTE,
|
||||
@ -81,6 +83,8 @@ export {
|
||||
CONFIRM_TRANSFER_FROM_PATH,
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
DECRYPT_MESSAGE_REQUEST_PATH,
|
||||
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
|
||||
INITIALIZE_METAMETRICS_OPT_IN_ROUTE,
|
||||
ADVANCED_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
|
@ -18,6 +18,8 @@ export const APPROVE_ACTION_KEY = 'approve'
|
||||
export const SEND_TOKEN_ACTION_KEY = 'sentTokens'
|
||||
export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
|
||||
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
|
||||
export const DECRYPT_REQUEST_KEY = 'decryptRequest'
|
||||
export const ENCRYPTION_PUBLIC_KEY_REQUEST_KEY = 'encryptionPublicKeyRequest'
|
||||
export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
|
||||
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
|
||||
export const DEPOSIT_TRANSACTION_KEY = 'deposit'
|
||||
|
@ -19,6 +19,8 @@ import {
|
||||
SEND_TOKEN_ACTION_KEY,
|
||||
TRANSFER_FROM_ACTION_KEY,
|
||||
SIGNATURE_REQUEST_KEY,
|
||||
DECRYPT_REQUEST_KEY,
|
||||
ENCRYPTION_PUBLIC_KEY_REQUEST_KEY,
|
||||
CONTRACT_INTERACTION_KEY,
|
||||
CANCEL_ATTEMPT_ACTION_KEY,
|
||||
DEPOSIT_TRANSACTION_KEY,
|
||||
@ -132,8 +134,14 @@ export function getTransactionActionKey (transaction) {
|
||||
}
|
||||
|
||||
if (msgParams) {
|
||||
if (type === 'eth_decrypt') {
|
||||
return DECRYPT_REQUEST_KEY
|
||||
} else if (type === 'eth_getEncryptionPublicKey') {
|
||||
return ENCRYPTION_PUBLIC_KEY_REQUEST_KEY
|
||||
} else {
|
||||
return SIGNATURE_REQUEST_KEY
|
||||
}
|
||||
}
|
||||
|
||||
if (isConfirmDeployContract(transaction)) {
|
||||
return DEPLOY_CONTRACT_ACTION_KEY
|
||||
|
@ -0,0 +1,333 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Tooltip from '../../components/ui/tooltip-v2'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
|
||||
import Identicon from '../../components/ui/identicon'
|
||||
import AccountListItem from '../send/account-list-item/account-list-item.component'
|
||||
import { conversionUtil } from '../../helpers/utils/conversion-util'
|
||||
import Button from '../../components/ui/button'
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
|
||||
|
||||
export default class ConfirmDecryptMessage extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
metricsEvent: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
balance: PropTypes.string,
|
||||
clearConfirmTransaction: PropTypes.func.isRequired,
|
||||
cancelDecryptMessage: PropTypes.func.isRequired,
|
||||
decryptMessage: PropTypes.func.isRequired,
|
||||
decryptMessageInline: PropTypes.func.isRequired,
|
||||
conversionRate: PropTypes.number,
|
||||
history: PropTypes.object.isRequired,
|
||||
requesterAddress: PropTypes.string,
|
||||
selectedAccount: PropTypes.object,
|
||||
txData: PropTypes.object,
|
||||
domainMetadata: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
selectedAccount: this.props.selectedAccount,
|
||||
hasCopied: false,
|
||||
copyToClipboardPressed: false,
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
window.addEventListener('beforeunload', this._beforeUnload)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this._removeBeforeUnload()
|
||||
}
|
||||
|
||||
_beforeUnload = (event) => {
|
||||
const { clearConfirmTransaction, cancelDecryptMessage } = this.props
|
||||
const { metricsEvent } = this.context
|
||||
metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Decrypt Message Request',
|
||||
name: 'Cancel Via Notification Close',
|
||||
},
|
||||
})
|
||||
clearConfirmTransaction()
|
||||
cancelDecryptMessage(event)
|
||||
}
|
||||
|
||||
_removeBeforeUnload = () => {
|
||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
window.removeEventListener('beforeunload', this._beforeUnload)
|
||||
}
|
||||
}
|
||||
|
||||
copyMessage = () => {
|
||||
copyToClipboard(this.state.rawMessage)
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Decrypt Message Copy',
|
||||
name: 'Copy',
|
||||
},
|
||||
})
|
||||
this.setState({ hasCopied: true })
|
||||
setTimeout(() => this.setState({ hasCopied: false }), 3000)
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
return (
|
||||
<div className="request-decrypt-message__header">
|
||||
<div className="request-decrypt-message__header-background" />
|
||||
|
||||
<div className="request-decrypt-message__header__text">
|
||||
{ this.context.t('decryptRequest') }
|
||||
</div>
|
||||
|
||||
<div className="request-decrypt-message__header__tip-container">
|
||||
<div className="request-decrypt-message__header__tip" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccount = () => {
|
||||
const { selectedAccount } = this.state
|
||||
|
||||
return (
|
||||
<div className="request-decrypt-message__account">
|
||||
<div className="request-decrypt-message__account-text">
|
||||
{ `${this.context.t('account')}:` }
|
||||
</div>
|
||||
|
||||
<div className="request-decrypt-message__account-item">
|
||||
<AccountListItem
|
||||
account={selectedAccount}
|
||||
displayBalance={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderBalance = () => {
|
||||
const { balance, conversionRate } = this.props
|
||||
|
||||
const balanceInEther = conversionUtil(balance, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
numberOfDecimals: 6,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="request-decrypt-message__balance">
|
||||
<div className="request-decrypt-message__balance-text">
|
||||
{ `${this.context.t('balance')}:` }
|
||||
</div>
|
||||
<div className="request-decrypt-message__balance-value">
|
||||
{ `${balanceInEther} ETH` }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderRequestIcon = () => {
|
||||
const { requesterAddress } = this.props
|
||||
|
||||
return (
|
||||
<div className="request-decrypt-message__request-icon">
|
||||
<Identicon
|
||||
diameter={40}
|
||||
address={requesterAddress}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountInfo = () => {
|
||||
return (
|
||||
<div className="request-decrypt-message__account-info">
|
||||
{ this.renderAccount() }
|
||||
{ this.renderRequestIcon() }
|
||||
{ this.renderBalance() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderBody = () => {
|
||||
const { txData } = this.props
|
||||
|
||||
const origin = this.props.domainMetadata[txData.msgParams.origin]
|
||||
const notice = this.context.t('decryptMessageNotice', [origin.name])
|
||||
|
||||
const {
|
||||
hasCopied,
|
||||
hasDecrypted,
|
||||
hasError,
|
||||
rawMessage,
|
||||
errorMessage,
|
||||
copyToClipboardPressed,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
<div className="request-decrypt-message__body">
|
||||
{ this.renderAccountInfo() }
|
||||
<div
|
||||
className="request-decrypt-message__visual"
|
||||
>
|
||||
<section>
|
||||
{origin.icon ? (
|
||||
<img
|
||||
className="request-decrypt-message__visual-identicon"
|
||||
src={origin.icon}
|
||||
/>
|
||||
) : (
|
||||
<i className="request-decrypt-message__visual-identicon--default">
|
||||
{origin.name.charAt(0).toUpperCase()}
|
||||
</i>
|
||||
)}
|
||||
<div
|
||||
className="request-decrypt-message__notice"
|
||||
>
|
||||
{ notice }
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
className="request-decrypt-message__message"
|
||||
>
|
||||
<div
|
||||
className="request-decrypt-message__message-text"
|
||||
>
|
||||
{ !hasDecrypted && !hasError ? txData.msgParams.data : rawMessage }
|
||||
{ !hasError ? '' : errorMessage }
|
||||
</div>
|
||||
<div
|
||||
className={classnames({
|
||||
'request-decrypt-message__message-cover': true,
|
||||
'request-decrypt-message__message-lock--pressed': hasDecrypted || hasError,
|
||||
})}
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
className={classnames({
|
||||
'request-decrypt-message__message-lock': true,
|
||||
'request-decrypt-message__message-lock--pressed': hasDecrypted || hasError,
|
||||
})}
|
||||
onClick={(event) => {
|
||||
this.props.decryptMessageInline(txData, event).then((result) => {
|
||||
if (!result.error) {
|
||||
this.setState({ hasDecrypted: true, rawMessage: result.rawData })
|
||||
} else {
|
||||
this.setState({ hasError: true, errorMessage: this.context.t('decryptInlineError', [result.error]) })
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<img src="images/lock.svg" />
|
||||
<div
|
||||
className="request-decrypt-message__message-lock-text"
|
||||
>
|
||||
{this.context.t('decryptMetamask')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ hasDecrypted ?
|
||||
(
|
||||
<div
|
||||
className={classnames({
|
||||
'request-decrypt-message__message-copy': true,
|
||||
'request-decrypt-message__message-copy--pressed': copyToClipboardPressed,
|
||||
})}
|
||||
onClick={() => this.copyMessage()}
|
||||
onMouseDown={() => this.setState({ copyToClipboardPressed: true })}
|
||||
onMouseUp={() => this.setState({ copyToClipboardPressed: false })}
|
||||
>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
title={hasCopied ? this.context.t('copiedExclamation') : this.context.t('copyToClipboard')}
|
||||
wrapperClassName="request-decrypt-message__message-copy-tooltip"
|
||||
>
|
||||
<div
|
||||
className="request-decrypt-message__message-copy-text"
|
||||
>
|
||||
{this.context.t('decryptCopy')}
|
||||
</div>
|
||||
<img src="images/copy-to-clipboard.svg" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
<div></div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { txData } = this.props
|
||||
|
||||
return (
|
||||
<div className="request-decrypt-message__footer">
|
||||
<Button
|
||||
type="default"
|
||||
large
|
||||
className="request-decrypt-message__footer__cancel-button"
|
||||
onClick={async (event) => {
|
||||
this._removeBeforeUnload()
|
||||
await this.props.cancelDecryptMessage(txData, event)
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Decrypt Message Request',
|
||||
name: 'Cancel',
|
||||
},
|
||||
})
|
||||
this.props.clearConfirmTransaction()
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
}}
|
||||
>
|
||||
{ this.context.t('cancel') }
|
||||
</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
large
|
||||
className="request-decrypt-message__footer__sign-button"
|
||||
onClick={async (event) => {
|
||||
this._removeBeforeUnload()
|
||||
await this.props.decryptMessage(txData, event)
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Decrypt Message Request',
|
||||
name: 'Confirm',
|
||||
},
|
||||
})
|
||||
this.props.clearConfirmTransaction()
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
}}
|
||||
>
|
||||
{ this.context.t('decrypt') }
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div className="request-decrypt-message__container">
|
||||
{ this.renderHeader() }
|
||||
{ this.renderBody() }
|
||||
{ this.renderFooter() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { goHome, decryptMsg, cancelDecryptMsg, decryptMsgInline } from '../../store/actions'
|
||||
|
||||
import {
|
||||
getSelectedAccount,
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
getSelectedAddress,
|
||||
conversionRateSelector,
|
||||
} from '../../selectors/selectors.js'
|
||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
|
||||
import ConfirmDecryptMessage from './confirm-decrypt-message.component'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { confirmTransaction,
|
||||
metamask: { domainMetadata = {} },
|
||||
} = state
|
||||
|
||||
const {
|
||||
txData = {},
|
||||
} = confirmTransaction
|
||||
|
||||
return {
|
||||
txData: txData,
|
||||
domainMetadata: domainMetadata,
|
||||
balance: getSelectedAccount(state).balance,
|
||||
selectedAccount: getCurrentAccountWithSendEtherInfo(state),
|
||||
selectedAddress: getSelectedAddress(state),
|
||||
requester: null,
|
||||
requesterAddress: null,
|
||||
conversionRate: conversionRateSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
goHome: () => dispatch(goHome()),
|
||||
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
||||
decryptMessage: (msgData, event) => {
|
||||
const params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
event.stopPropagation(event)
|
||||
return dispatch(decryptMsg(params))
|
||||
},
|
||||
cancelDecryptMessage: (msgData, event) => {
|
||||
event.stopPropagation(event)
|
||||
return dispatch(cancelDecryptMsg(msgData))
|
||||
},
|
||||
decryptMessageInline: (msgData, event) => {
|
||||
const params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
event.stopPropagation(event)
|
||||
return dispatch(decryptMsgInline(params))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(ConfirmDecryptMessage)
|
1
ui/app/pages/confirm-decrypt-message/index.js
Normal file
1
ui/app/pages/confirm-decrypt-message/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './confirm-decrypt-message.container'
|
@ -0,0 +1,238 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
|
||||
import Identicon from '../../components/ui/identicon'
|
||||
import AccountListItem from '../send/account-list-item/account-list-item.component'
|
||||
import { conversionUtil } from '../../helpers/utils/conversion-util'
|
||||
import Button from '../../components/ui/button'
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
|
||||
|
||||
export default class ConfirmEncryptionPublicKey extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
metricsEvent: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
balance: PropTypes.string,
|
||||
clearConfirmTransaction: PropTypes.func.isRequired,
|
||||
cancelEncryptionPublicKey: PropTypes.func.isRequired,
|
||||
encryptionPublicKey: PropTypes.func.isRequired,
|
||||
conversionRate: PropTypes.number,
|
||||
history: PropTypes.object.isRequired,
|
||||
requesterAddress: PropTypes.string,
|
||||
selectedAccount: PropTypes.object,
|
||||
txData: PropTypes.object,
|
||||
domainMetadata: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
selectedAccount: this.props.selectedAccount,
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
window.addEventListener('beforeunload', this._beforeUnload)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this._removeBeforeUnload()
|
||||
}
|
||||
|
||||
_beforeUnload = (event) => {
|
||||
const { clearConfirmTransaction, cancelEncryptionPublicKey } = this.props
|
||||
const { metricsEvent } = this.context
|
||||
metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Encryption public key Request',
|
||||
name: 'Cancel Via Notification Close',
|
||||
},
|
||||
})
|
||||
clearConfirmTransaction()
|
||||
cancelEncryptionPublicKey(event)
|
||||
}
|
||||
|
||||
_removeBeforeUnload = () => {
|
||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
window.removeEventListener('beforeunload', this._beforeUnload)
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
return (
|
||||
<div className="request-encryption-public-key__header">
|
||||
<div className="request-encryption-public-key__header-background" />
|
||||
|
||||
<div className="request-encryption-public-key__header__text">
|
||||
{ this.context.t('encryptionPublicKeyRequest') }
|
||||
</div>
|
||||
|
||||
<div className="request-encryption-public-key__header__tip-container">
|
||||
<div className="request-encryption-public-key__header__tip" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccount = () => {
|
||||
const { selectedAccount } = this.state
|
||||
|
||||
return (
|
||||
<div className="request-encryption-public-key__account">
|
||||
<div className="request-encryption-public-key__account-text">
|
||||
{ `${this.context.t('account')}:` }
|
||||
</div>
|
||||
|
||||
<div className="request-encryption-public-key__account-item">
|
||||
<AccountListItem
|
||||
account={selectedAccount}
|
||||
displayBalance={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderBalance = () => {
|
||||
const { balance, conversionRate } = this.props
|
||||
|
||||
const balanceInEther = conversionUtil(balance, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
numberOfDecimals: 6,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="request-encryption-public-key__balance">
|
||||
<div className="request-encryption-public-key__balance-text">
|
||||
{ `${this.context.t('balance')}:` }
|
||||
</div>
|
||||
<div className="request-encryption-public-key__balance-value">
|
||||
{ `${balanceInEther} ETH` }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderRequestIcon = () => {
|
||||
const { requesterAddress } = this.props
|
||||
|
||||
return (
|
||||
<div className="request-encryption-public-key__request-icon">
|
||||
<Identicon
|
||||
diameter={40}
|
||||
address={requesterAddress}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountInfo = () => {
|
||||
return (
|
||||
<div className="request-encryption-public-key__account-info">
|
||||
{ this.renderAccount() }
|
||||
{ this.renderRequestIcon() }
|
||||
{ this.renderBalance() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderBody = () => {
|
||||
const { txData } = this.props
|
||||
|
||||
const origin = this.props.domainMetadata[txData.origin]
|
||||
const notice = this.context.t('encryptionPublicKeyNotice', [origin.name])
|
||||
|
||||
return (
|
||||
<div className="request-encryption-public-key__body">
|
||||
{ this.renderAccountInfo() }
|
||||
<div
|
||||
className="request-encryption-public-key__visual"
|
||||
>
|
||||
<section>
|
||||
{origin.icon ? (
|
||||
<img
|
||||
className="request-encryption-public-key__visual-identicon"
|
||||
src={origin.icon}
|
||||
/>
|
||||
) : (
|
||||
<i className="request-encryption-public-key__visual-identicon--default">
|
||||
{origin.name.charAt(0).toUpperCase()}
|
||||
</i>
|
||||
)}
|
||||
<div
|
||||
className="request-encryption-public-key__notice"
|
||||
>
|
||||
{ notice }
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { txData } = this.props
|
||||
|
||||
return (
|
||||
<div className="request-encryption-public-key__footer">
|
||||
<Button
|
||||
type="default"
|
||||
large
|
||||
className="request-encryption-public-key__footer__cancel-button"
|
||||
onClick={async (event) => {
|
||||
this._removeBeforeUnload()
|
||||
await this.props.cancelEncryptionPublicKey(txData, event)
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Encryption public key Request',
|
||||
name: 'Cancel',
|
||||
},
|
||||
})
|
||||
this.props.clearConfirmTransaction()
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
}}
|
||||
>
|
||||
{ this.context.t('cancel') }
|
||||
</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
large
|
||||
className="request-encryption-public-key__footer__sign-button"
|
||||
onClick={async (event) => {
|
||||
this._removeBeforeUnload()
|
||||
await this.props.encryptionPublicKey(txData, event)
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Messages',
|
||||
action: 'Encryption public key Request',
|
||||
name: 'Confirm',
|
||||
},
|
||||
})
|
||||
this.props.clearConfirmTransaction()
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
}}
|
||||
>
|
||||
{ this.context.t('provide') }
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div className="request-encryption-public-key__container">
|
||||
{ this.renderHeader() }
|
||||
{ this.renderBody() }
|
||||
{ this.renderFooter() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { goHome, encryptionPublicKeyMsg, cancelEncryptionPublicKeyMsg } from '../../store/actions'
|
||||
|
||||
import {
|
||||
getSelectedAccount,
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
getSelectedAddress,
|
||||
conversionRateSelector,
|
||||
} from '../../selectors/selectors.js'
|
||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
|
||||
import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { confirmTransaction,
|
||||
metamask: { domainMetadata = {} },
|
||||
} = state
|
||||
|
||||
const {
|
||||
txData = {},
|
||||
} = confirmTransaction
|
||||
|
||||
return {
|
||||
txData: txData,
|
||||
domainMetadata: domainMetadata,
|
||||
balance: getSelectedAccount(state).balance,
|
||||
selectedAccount: getCurrentAccountWithSendEtherInfo(state),
|
||||
selectedAddress: getSelectedAddress(state),
|
||||
requester: null,
|
||||
requesterAddress: null,
|
||||
conversionRate: conversionRateSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
goHome: () => dispatch(goHome()),
|
||||
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
||||
encryptionPublicKey: (msgData, event) => {
|
||||
const params = { data: msgData.msgParams, metamaskId: msgData.id }
|
||||
event.stopPropagation()
|
||||
return dispatch(encryptionPublicKeyMsg(params))
|
||||
},
|
||||
cancelEncryptionPublicKey: (msgData, event) => {
|
||||
event.stopPropagation()
|
||||
return dispatch(cancelEncryptionPublicKeyMsg(msgData))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(ConfirmEncryptionPublicKey)
|
1
ui/app/pages/confirm-encryption-public-key/index.js
Normal file
1
ui/app/pages/confirm-encryption-public-key/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './confirm-encryption-public-key.container'
|
@ -11,6 +11,8 @@ import {
|
||||
CONFIRM_TRANSFER_FROM_PATH,
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
DECRYPT_MESSAGE_REQUEST_PATH,
|
||||
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
|
||||
} from '../../helpers/constants/routes'
|
||||
import {
|
||||
TOKEN_METHOD_TRANSFER,
|
||||
@ -68,11 +70,15 @@ export default class ConfirmTransactionSwitch extends Component {
|
||||
|
||||
render () {
|
||||
const { txData } = this.props
|
||||
|
||||
if (txData.txParams) {
|
||||
return this.redirectToTransaction()
|
||||
} else if (txData.msgParams) {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
|
||||
let pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
|
||||
if (txData.type === 'eth_decrypt') {
|
||||
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${DECRYPT_MESSAGE_REQUEST_PATH}`
|
||||
} else if (txData.type === 'eth_getEncryptionPublicKey') {
|
||||
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`
|
||||
}
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,9 @@ import ConfirmDeployContract from '../confirm-deploy-contract'
|
||||
import ConfirmApprove from '../confirm-approve'
|
||||
import ConfirmTokenTransactionBaseContainer from '../confirm-token-transaction-base'
|
||||
import ConfTx from './conf-tx'
|
||||
import ConfirmDecryptMessage from '../confirm-decrypt-message'
|
||||
import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key'
|
||||
|
||||
import {
|
||||
DEFAULT_ROUTE,
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
@ -20,6 +23,8 @@ import {
|
||||
CONFIRM_TRANSFER_FROM_PATH,
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
DECRYPT_MESSAGE_REQUEST_PATH,
|
||||
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
|
||||
} from '../../helpers/constants/routes'
|
||||
|
||||
export default class ConfirmTransaction extends Component {
|
||||
@ -155,6 +160,16 @@ export default class ConfirmTransaction extends Component {
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`}
|
||||
component={ConfTx}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${DECRYPT_MESSAGE_REQUEST_PATH}`}
|
||||
component={ConfirmDecryptMessage}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`}
|
||||
component={ConfirmEncryptionPublicKey}
|
||||
/>
|
||||
<Route path="*" component={ConfirmTransactionSwitch} />
|
||||
</Switch>
|
||||
)
|
||||
|
@ -130,6 +130,10 @@ export default {
|
||||
'unapprovedMsgCount': 0,
|
||||
'unapprovedPersonalMsgs': {},
|
||||
'unapprovedPersonalMsgCount': 0,
|
||||
'unapprovedDecryptMsgs': {},
|
||||
'unapprovedDecryptMsgCount': 0,
|
||||
'unapprovedEncryptionPublicKeyMsgs': {},
|
||||
'unapprovedEncryptionPublicKeyMsgCount': 0,
|
||||
'keyringTypes': [
|
||||
'Simple Key Pair',
|
||||
'HD Key Tree',
|
||||
|
@ -16,6 +16,8 @@ import {
|
||||
const unapprovedTxsSelector = (state) => state.metamask.unapprovedTxs
|
||||
const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs
|
||||
const unapprovedPersonalMsgsSelector = (state) => state.metamask.unapprovedPersonalMsgs
|
||||
const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
|
||||
const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
|
||||
const unapprovedTypedMessagesSelector = (state) => state.metamask.unapprovedTypedMessages
|
||||
const networkSelector = (state) => state.metamask.network
|
||||
|
||||
@ -23,18 +25,24 @@ export const unconfirmedTransactionsListSelector = createSelector(
|
||||
unapprovedTxsSelector,
|
||||
unapprovedMsgsSelector,
|
||||
unapprovedPersonalMsgsSelector,
|
||||
unapprovedDecryptMsgsSelector,
|
||||
unapprovedEncryptionPublicKeyMsgsSelector,
|
||||
unapprovedTypedMessagesSelector,
|
||||
networkSelector,
|
||||
(
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgs = {},
|
||||
unapprovedPersonalMsgs = {},
|
||||
unapprovedDecryptMsgs = {},
|
||||
unapprovedEncryptionPublicKeyMsgs = {},
|
||||
unapprovedTypedMessages = {},
|
||||
network
|
||||
) => txHelper(
|
||||
unapprovedTxs,
|
||||
unapprovedMsgs,
|
||||
unapprovedPersonalMsgs,
|
||||
unapprovedDecryptMsgs,
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
unapprovedTypedMessages,
|
||||
network
|
||||
) || []
|
||||
@ -44,12 +52,16 @@ export const unconfirmedTransactionsHashSelector = createSelector(
|
||||
unapprovedTxsSelector,
|
||||
unapprovedMsgsSelector,
|
||||
unapprovedPersonalMsgsSelector,
|
||||
unapprovedDecryptMsgsSelector,
|
||||
unapprovedEncryptionPublicKeyMsgsSelector,
|
||||
unapprovedTypedMessagesSelector,
|
||||
networkSelector,
|
||||
(
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgs = {},
|
||||
unapprovedPersonalMsgs = {},
|
||||
unapprovedDecryptMsgs = {},
|
||||
unapprovedEncryptionPublicKeyMsgs = {},
|
||||
unapprovedTypedMessages = {},
|
||||
network
|
||||
) => {
|
||||
@ -68,6 +80,8 @@ export const unconfirmedTransactionsHashSelector = createSelector(
|
||||
...filteredUnapprovedTxs,
|
||||
...unapprovedMsgs,
|
||||
...unapprovedPersonalMsgs,
|
||||
...unapprovedDecryptMsgs,
|
||||
...unapprovedEncryptionPublicKeyMsgs,
|
||||
...unapprovedTypedMessages,
|
||||
}
|
||||
}
|
||||
@ -75,18 +89,24 @@ export const unconfirmedTransactionsHashSelector = createSelector(
|
||||
|
||||
const unapprovedMsgCountSelector = (state) => state.metamask.unapprovedMsgCount
|
||||
const unapprovedPersonalMsgCountSelector = (state) => state.metamask.unapprovedPersonalMsgCount
|
||||
const unapprovedDecryptMsgCountSelector = (state) => state.metamask.unapprovedDecryptMsgCount
|
||||
const unapprovedEncryptionPublicKeyMsgCountSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgCount
|
||||
const unapprovedTypedMessagesCountSelector = (state) => state.metamask.unapprovedTypedMessagesCount
|
||||
|
||||
export const unconfirmedTransactionsCountSelector = createSelector(
|
||||
unapprovedTxsSelector,
|
||||
unapprovedMsgCountSelector,
|
||||
unapprovedPersonalMsgCountSelector,
|
||||
unapprovedDecryptMsgCountSelector,
|
||||
unapprovedEncryptionPublicKeyMsgCountSelector,
|
||||
unapprovedTypedMessagesCountSelector,
|
||||
networkSelector,
|
||||
(
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgCount = 0,
|
||||
unapprovedPersonalMsgCount = 0,
|
||||
unapprovedDecryptMsgCount = 0,
|
||||
unapprovedEncryptionPublicKeyMsgCount = 0,
|
||||
unapprovedTypedMessagesCount = 0,
|
||||
network
|
||||
) => {
|
||||
@ -96,7 +116,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
|
||||
})
|
||||
|
||||
return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount +
|
||||
unapprovedPersonalMsgCount
|
||||
unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -326,11 +326,13 @@ export function getTotalUnapprovedCount ({ metamask }) {
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgCount,
|
||||
unapprovedPersonalMsgCount,
|
||||
unapprovedDecryptMsgCount,
|
||||
unapprovedEncryptionPublicKeyMsgCount,
|
||||
unapprovedTypedMessagesCount,
|
||||
} = metamask
|
||||
|
||||
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
|
||||
unapprovedTypedMessagesCount
|
||||
unapprovedTypedMessagesCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
|
||||
}
|
||||
|
||||
export function getIsMainnet (state) {
|
||||
|
@ -132,6 +132,10 @@ export default {
|
||||
'unapprovedMsgCount': 0,
|
||||
'unapprovedPersonalMsgs': {},
|
||||
'unapprovedPersonalMsgCount': 0,
|
||||
'unapprovedDecryptMsgs': {},
|
||||
'unapprovedDecryptMsgCount': 0,
|
||||
'unapprovedEncryptionPublicKeyMsgs': {},
|
||||
'unapprovedEncryptionPublicKeyMsgCount': 0,
|
||||
'keyringTypes': [
|
||||
'Simple Key Pair',
|
||||
'HD Key Tree',
|
||||
|
@ -33,23 +33,31 @@ export const incomingTxListSelector = (state) => {
|
||||
export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs
|
||||
export const selectedAddressTxListSelector = (state) => state.metamask.selectedAddressTxList
|
||||
export const unapprovedPersonalMsgsSelector = (state) => state.metamask.unapprovedPersonalMsgs
|
||||
export const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
|
||||
export const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
|
||||
export const unapprovedTypedMessagesSelector = (state) => state.metamask.unapprovedTypedMessages
|
||||
export const networkSelector = (state) => state.metamask.network
|
||||
|
||||
export const unapprovedMessagesSelector = createSelector(
|
||||
unapprovedMsgsSelector,
|
||||
unapprovedPersonalMsgsSelector,
|
||||
unapprovedDecryptMsgsSelector,
|
||||
unapprovedEncryptionPublicKeyMsgsSelector,
|
||||
unapprovedTypedMessagesSelector,
|
||||
networkSelector,
|
||||
(
|
||||
unapprovedMsgs = {},
|
||||
unapprovedPersonalMsgs = {},
|
||||
unapprovedDecryptMsgs = {},
|
||||
unapprovedEncryptionPublicKeyMsgs = {},
|
||||
unapprovedTypedMessages = {},
|
||||
network
|
||||
) => txHelper(
|
||||
{},
|
||||
unapprovedMsgs,
|
||||
unapprovedPersonalMsgs,
|
||||
unapprovedDecryptMsgs,
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
unapprovedTypedMessages,
|
||||
network
|
||||
) || []
|
||||
|
@ -631,6 +631,80 @@ export function signPersonalMsg (msgData) {
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptMsgInline (decryptedMsgData) {
|
||||
log.debug('action - decryptMsgInline')
|
||||
return (dispatch) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
log.debug(`actions calling background.decryptMessageInline`)
|
||||
background.decryptMessageInline(decryptedMsgData, (err, newState) => {
|
||||
log.debug('decryptMsgInline called back')
|
||||
dispatch(updateMetamaskState(newState))
|
||||
|
||||
if (err) {
|
||||
log.error(err)
|
||||
dispatch(displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
decryptedMsgData = newState.unapprovedDecryptMsgs[decryptedMsgData.metamaskId]
|
||||
return resolve(decryptedMsgData)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptMsg (decryptedMsgData) {
|
||||
log.debug('action - decryptMsg')
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
log.debug(`actions calling background.decryptMessage`)
|
||||
background.decryptMessage(decryptedMsgData, (err, newState) => {
|
||||
log.debug('decryptMsg called back')
|
||||
dispatch(updateMetamaskState(newState))
|
||||
dispatch(hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
log.error(err)
|
||||
dispatch(displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(completedTx(decryptedMsgData.metamaskId))
|
||||
dispatch(closeCurrentNotificationWindow())
|
||||
console.log(decryptedMsgData)
|
||||
return resolve(decryptedMsgData)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function encryptionPublicKeyMsg (msgData) {
|
||||
log.debug('action - encryptionPublicKeyMsg')
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
log.debug(`actions calling background.encryptionPublicKey`)
|
||||
background.encryptionPublicKey(msgData, (err, newState) => {
|
||||
log.debug('encryptionPublicKeyMsg called back')
|
||||
dispatch(updateMetamaskState(newState))
|
||||
dispatch(hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
log.error(err)
|
||||
dispatch(displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(completedTx(msgData.metamaskId))
|
||||
dispatch(closeCurrentNotificationWindow())
|
||||
|
||||
return resolve(msgData)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function signTypedMsg (msgData) {
|
||||
log.debug('action - signTypedMsg')
|
||||
return (dispatch) => {
|
||||
@ -1005,6 +1079,50 @@ export function cancelPersonalMsg (msgData) {
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelDecryptMsg (msgData) {
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = msgData.id
|
||||
background.cancelDecryptMessage(id, (err, newState) => {
|
||||
dispatch(updateMetamaskState(newState))
|
||||
dispatch(hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(completedTx(id))
|
||||
dispatch(closeCurrentNotificationWindow())
|
||||
|
||||
return resolve(msgData)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelEncryptionPublicKeyMsg (msgData) {
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = msgData.id
|
||||
background.cancelEncryptionPublicKey(id, (err, newState) => {
|
||||
dispatch(updateMetamaskState(newState))
|
||||
dispatch(hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(completedTx(id))
|
||||
dispatch(closeCurrentNotificationWindow())
|
||||
|
||||
return resolve(msgData)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelTypedMsg (msgData) {
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication())
|
||||
|
@ -59,7 +59,7 @@ async function startApp (metamaskState, backgroundConnection, opts) {
|
||||
})
|
||||
|
||||
// if unconfirmed txs, start on txConf page
|
||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedDecryptMsgs, metamaskState.unapprovedEncryptionPublicKeyMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
||||
const numberOfUnapprivedTx = unapprovedTxsAll.length
|
||||
if (numberOfUnapprivedTx > 0) {
|
||||
store.dispatch(actions.showConfTxPage({
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { valuesFor } from '../app/helpers/utils/util'
|
||||
import log from 'loglevel'
|
||||
|
||||
export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) {
|
||||
export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, decryptMsgs, encryptionPublicKeyMsgs, typedMessages, network) {
|
||||
log.debug('tx-helper called with params:')
|
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network })
|
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, decryptMsgs, encryptionPublicKeyMsgs, typedMessages, network })
|
||||
|
||||
const txValues = network ? valuesFor(unapprovedTxs).filter((txMeta) => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs)
|
||||
log.debug(`tx helper found ${txValues.length} unapproved txs`)
|
||||
@ -16,6 +16,14 @@ export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, t
|
||||
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`)
|
||||
allValues = allValues.concat(personalValues)
|
||||
|
||||
const decryptValues = valuesFor(decryptMsgs)
|
||||
log.debug(`tx helper found ${decryptValues.length} decrypt requests`)
|
||||
allValues = allValues.concat(decryptValues)
|
||||
|
||||
const encryptionPublicKeyValues = valuesFor(encryptionPublicKeyMsgs)
|
||||
log.debug(`tx helper found ${encryptionPublicKeyValues.length} encryptionPublicKey requests`)
|
||||
allValues = allValues.concat(encryptionPublicKeyValues)
|
||||
|
||||
const typedValues = valuesFor(typedMessages)
|
||||
log.debug(`tx helper found ${typedValues.length} unsigned typed messages`)
|
||||
allValues = allValues.concat(typedValues)
|
||||
|
28
yarn.lock
28
yarn.lock
@ -10337,30 +10337,10 @@ eth-json-rpc-middleware@^1.5.0:
|
||||
promise-to-callback "^1.0.0"
|
||||
tape "^4.6.3"
|
||||
|
||||
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.2.0.tgz#cfb77c5056cb8001548c6c7d54f4af5fce04d489"
|
||||
integrity sha512-90LljqRyJhkg7fOwKunh1lu1Mr5bspXMBDitaTGyGPPNiFTbMrhtfbf9fteYlXRFCbq+aIFWwl/X+P7nkrdkLg==
|
||||
dependencies:
|
||||
btoa "^1.2.1"
|
||||
clone "^2.1.1"
|
||||
eth-json-rpc-errors "^1.0.1"
|
||||
eth-query "^2.1.2"
|
||||
eth-sig-util "^1.4.2"
|
||||
ethereumjs-block "^1.6.0"
|
||||
ethereumjs-tx "^1.3.7"
|
||||
ethereumjs-util "^5.1.2"
|
||||
ethereumjs-vm "^2.6.0"
|
||||
fetch-ponyfill "^4.0.0"
|
||||
json-rpc-engine "^5.1.3"
|
||||
json-stable-stringify "^1.0.1"
|
||||
pify "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-json-rpc-middleware@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.0.tgz#ef63b783b48dcbea9c1fe25c79e6ea01510e5877"
|
||||
integrity sha512-IeOsil/XiHsybJO9nFf86+1+YIqGQWPPfiTEp3WLkpLZhJm97kw6tFM7GttIZXIcwtaO3zEXgY6PWAH1jkB3ag==
|
||||
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-middleware@^4.4.1:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.1.tgz#07d3dd0724c24a8d31e4a172ee96271da71b4228"
|
||||
integrity sha512-yoSuRgEYYGFdVeZg3poWOwAlRI+MoBIltmOB86MtpoZjvLbou9EB/qWMOWSmH2ryCWLW97VYY6NWsmWm3OAA7A==
|
||||
dependencies:
|
||||
btoa "^1.2.1"
|
||||
clone "^2.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user