diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4fbd4eba5..437387c35 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -66,8 +66,8 @@ jobs:
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ./.npmrc
- run: npm run setup
- run: npm run compile
- - run: npm run test:main -- --testTimeout=500 --verbose
- - run: npm run test:components
+ - run: npm run test:unit:main -- --testTimeout=500 --verbose
+ - run: npm run test:unit:components
- run: npm run bundle
- name: Sleep for 4 seconds
shell: bash
diff --git a/.github/workflows/compile-and-test.yml b/.github/workflows/compile-and-test.yml
index e758a6d43..9e487c170 100644
--- a/.github/workflows/compile-and-test.yml
+++ b/.github/workflows/compile-and-test.yml
@@ -19,5 +19,4 @@ jobs:
- run: sudo apt install -y libudev-dev
- run: npm run setup
- run: npm run compile
- - run: npm run test:main
- - run: npm run test:components
+ - run: npm run test:unit
diff --git a/app/App/Panel/Main/Account/Requests/SignatureRequest/index.js b/app/App/Panel/Main/Account/Requests/SignatureRequest/index.js
index 687ccb718..c09195511 100644
--- a/app/App/Panel/Main/Account/Requests/SignatureRequest/index.js
+++ b/app/App/Panel/Main/Account/Requests/SignatureRequest/index.js
@@ -11,7 +11,8 @@ function decodeMessage (rawMessage) {
return buff.length === 32 ? rawMessage : buff.toString('utf8')
}
- return rawMessage
+ // replace all multiple line returns with just one to prevent excess space in message
+ return rawMessage.replaceAll(/[\n\r]+/g, '\n')
}
class TransactionRequest extends React.Component {
@@ -21,6 +22,8 @@ class TransactionRequest extends React.Component {
const props = args[0] || {}
+ this.signRefs = [React.createRef(), React.createRef()]
+
setTimeout(() => {
this.setState({ allowInput: true })
}, props.signingDelay || 1500)
@@ -50,6 +53,21 @@ class TransactionRequest extends React.Component {
return (Math.round(parseFloat(fromWei(hex, 'ether')) * 1000000) / 1000000).toFixed(6)
}
+ renderMessage (message) {
+ let showMore = false
+ if (this.signRefs[0].current && this.signRefs[1].current) {
+ const inner = this.signRefs[1].current.clientHeight
+ const wrap = this.signRefs[0].current.clientHeight + this.signRefs[0].current.scrollTop
+ if (inner > wrap) showMore = true
+ }
+ return (
+
{account.smart ? (
@@ -61,8 +81,12 @@ class Verify extends React.Component {
{this.state.verifyAddressResponse ? (
{this.state.verifyAddressResponse}
) : null}
-
this.verifyAddress()}>
- {signerKind === 'hot' ? 'Verify Address' : 'Verify Address on Device'}
+
{
+ if (evt.button === 0 && !this.state.verifyInProgress) {
+ this.verifyAddress()
+ }
+ }}>
+ {this.getText(signerType)}
>
diff --git a/app/App/Panel/Main/Account/style/index.styl b/app/App/Panel/Main/Account/style/index.styl
index 0b1300dfc..6ba1d87a7 100644
--- a/app/App/Panel/Main/Account/style/index.styl
+++ b/app/App/Panel/Main/Account/style/index.styl
@@ -1716,7 +1716,6 @@
align-items center
font-weight 400
font-size 13px
- cursor pointer
box-sizing border-box
text-transform uppercase
box-shadow 0px 2px 2px var(--ghostA)
@@ -1730,7 +1729,12 @@
*
pointer-events none
-.moduleButton:hover
+.moduleButton.signerVerifyInProgress
+ background var(--ghostC)
+ color var(--outerspace04)
+
+.moduleButton:hover:not(.signerVerifyInProgress)
+ cursor pointer
background var(--ghostD)
box-shadow 0px 4px 6px var(--ghostA)
diff --git a/app/flex/index.js b/app/flex/index.js
deleted file mode 100644
index cec938bea..000000000
--- a/app/flex/index.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Access browser specific libraries from the main process
-// Helpful when only a browser library is avliable...
-// or when a particular Chrome API like web-bluetooth may be a more stable option cross platform
-import link from '../../resources/link'
-
-// Flexed Libs
-// import Ledger from './ledger'
-import Trezor from './trezor'
-
-// Emitter Transport
-const emit = (eventName, ...args) => {
- // console.log('emit', eventName, ...args)
- link.send('tray:flex:event', eventName, ...args.map(arg => arg instanceof Error ? wrap(arg.message) : wrap(arg)))
-}
-
-const flex = {
- // ledger: new Ledger(emit),
- trezor: new Trezor(emit)
-}
-
-const unwrap = v => v !== undefined || v !== null ? JSON.parse(v) : v
-const wrap = v => v !== undefined || v !== null ? JSON.stringify(v) : v
-
-link.on('flex', (id, target, ...args) => {
- id = unwrap(id)
- target = unwrap(target)
- const lib = target.split('.')[0]
- const method = target.split('.')[1]
- args = args.map(arg => unwrap(arg))
- if (flex[lib] && flex[lib][method]) {
- flex[lib][method](...args, (...args) => {
- link.send('tray:flex:res', id, ...args.map(arg => arg instanceof Error ? wrap(arg.message) : wrap(arg)))
- })
- } else {
- const args = [new Error('Unknown flex lib:method: ' + target)]
- link.send('tray:flex:res', id, ...args.map(arg => arg instanceof Error ? wrap(arg.message) : wrap(arg)))
- }
-})
diff --git a/app/flex/ledger/index.js b/app/flex/ledger/index.js
deleted file mode 100644
index 2e7212c60..000000000
--- a/app/flex/ledger/index.js
+++ /dev/null
@@ -1,217 +0,0 @@
-import 'babel-polyfill'
-
-// Libraries
-import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
-import AppEth from '@ledgerhq/hw-app-eth'
-import EthereumTx from 'ethereumjs-tx'
-
-class Device {
- constructor (device, emit) {
- this.device = device
- this.id = this.device.id
- this.eth = new AppEth(device)
- this.emit = emit
- this.setup()
- }
-
- setup () {
- this.connect()
- }
-
- summary () {
- return {
- id: this.id
- }
- }
-
- connect () {
- this.emit('ledger:connect', this.summary())
- }
-
- disconnect () {
- this.emit('ledger:disconnect', this.summary())
- }
-
- update (device = this.device) {
- this.device = device
- this.emit('ledger:update', this.summary())
- }
-
- async ethereumGetAddress (path, display, cb) {
- try {
- cb(null, await this.eth.getAddress(path, display, true))
- } catch (err) {
- cb(err.message)
- }
- }
-
- ethereumSignTransaction (path, rawTx, cb) {
- const tx = new EthereumTx(rawTx)
- tx.raw[6] = Buffer.from([rawTx.chainId]) // v
- tx.raw[7] = Buffer.from([]) // r
- tx.raw[8] = Buffer.from([]) // s
- const rawTxHex = tx.serialize().toString('hex')
- this.eth.signTransaction(this.getPath(), rawTxHex).then(result => {
- cb(null, result)
- }).catch(err => {
- cb(err.message)
- })
- }
-
- ethereumSignMessage (path, message, cb) {
- console.log('ethereumSignMessage')
- }
-
- ethereumVerifyMessage (path, message, cb) {
- console.log('ethereumVerifyMessage')
- }
-}
-
-class Ledger {
- constructor (emit) {
- this.emit = emit
- this.devices = {}
- document.addEventListener('mousedown', e => { // Synthetic input event created by main process for web-ble scan
- if (e.clientX === -124816 && e.clientX === -124816) this.scan()
- })
- this.emit('ledger:scan') // Request synthetic input
- this.scanner = setInterval(() => this.emit('ledger:scan'), 20000)
- }
-
- async scan () {
- try {
- const device = await TransportWebBLE.create()
- if (!this.devices[device.id]) {
- this.devices[device.id] = new Device(device, this.emit)
- device.on('disconnect', () => {
- this.devices[device.id].disconnect()
- delete this.devices[device.id]
- // remove list
- this.emit('ledger:scan') // Request scan
- })
- }
- } catch (e) {
- console.log(e)
- }
- }
-
- deviceNotFound (id, cb) {
- cb(new Error(`Device with id: ${id} not found`))
- }
-
- ethereumGetAddress (id, path, display, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumGetAddress(path, display, cb)
- }
-
- ethereumSignTransaction (id, path, tx, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignTransaction(path, tx, cb)
- }
-
- ethereumSignMessage (id, path, message, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignMessage(path, message, cb)
- }
-
- ethereumVerifyMessage (id, path, message, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumVerifyMessage(path, message, cb)
- }
-}
-
-module.exports = Ledger
-
-// class Ledger {
-// constructor (emit) {
-// this.emit = emit
-// this.signers = {}
-// }
-// current (cb) {
-// // Return current signer summaries
-// let s = {}
-// Object.keys(this.signers).map(id => { s[id] = this.summary(id) })
-// cb(null, s)
-// }
-// summary (id) {
-// return {
-// id: this.signers[id].id,
-// status: this.signers[id].status,
-// statusMessage: this.signers[id].statusMessage,
-// publicKey: this.signers[id].publicKey,
-// address: this.signers[id].address,
-// chainId: this.signers[id].chainId
-// }
-// }
-// async getAddress (id, path, verify, chainId, cb) {
-// console.log(id, path, verify, chainId, cb)
-// try {
-// // const transport = await TransportWebBLE.create()
-// const eth = new AppEth(this.signers[id].transport)
-// cb(null, await eth.getAddress(path, false, true))
-// // transport.close()
-// } catch (err) {
-// cb(err)
-// }
-// }
-// update (id) {
-// this.emit('ledger:device:update', this.summary(id))
-// }
-// async getDeviceInfo (id) {
-// const eth = new AppEth(this.signers[id].transport)
-// const path = `44'/60'/0'/0/0` // HD derivation path
-// const result = await eth.getAddress(path, false, true)
-// this.signers[id].publicKey = result.publicKey
-// this.signers[id].address = result.address
-// this.signers[id].chainId = result.chainId
-// this.signers[id].status = 'ready'
-// this.signers[id].statusMessage = ''
-// this.emit('ledger:device:added', this.summary(id))
-// this.update(id)
-// }
-// async addDevice (transport) {
-// let id = transport.device.id
-// this.signers[id] = { id, transport, status: 'pending' }
-// this.emit('ledger:device:added', this.summary(id))
-// try {
-// this.getDeviceInfo(id)
-// } catch (err) {
-// this.signers[id].status = 'error'
-// this.signers[id].statusMessage = err.message
-// this.update(id)
-// }
-// }
-// async getTransport () {
-//
-// }
-// async scan () {
-// let cancled = false
-// const error = err => {
-// cancled = true
-// this.emit('ledger:scan:failed', err)
-// }
-// try {
-// const timer = setTimeout(() => {
-// if (cancled) return
-// error(new Error('Time limit reached...'))
-// }, 20000)
-// const transport = await TransportWebBLE.create()
-// if (cancled) return
-// clearTimeout(timer)
-// this.addDevice(transport)
-// } catch (err) {
-// error(err)
-// }
-// }
-// }
-//
-// module.exports = Ledger
-
-// Events
-
-// 'ledger:device:added'
-// - { address }
-// 'ledger:device:removed'
-// - id
-// 'ledger:device:changed'
-// - { address }
diff --git a/app/flex/trezor/index.js b/app/flex/trezor/index.js
deleted file mode 100644
index 20122394a..000000000
--- a/app/flex/trezor/index.js
+++ /dev/null
@@ -1,224 +0,0 @@
-const EventEmitter = require('events')
-const {
- default: TrezorConnect,
- DEVICE_EVENT,
- DEVICE,
- UI_EVENT,
- UI
-} = require('trezor-connect')
-
-const events = new EventEmitter()
-events.setMaxListeners(128)
-let ready = false
-
-class Device {
- constructor (device, emit) {
- this.device = device
- this.id = device.path
- this.emit = emit
- if (ready) return this.setup()
- events.once('ready', () => this.setup())
- }
-
- setup () {
- this.connect()
- }
-
- connect () {
- this.emit('trezor:connect', this.device)
- }
-
- disconnect () {
- this.emit('trezor:disconnect', this.device)
- }
-
- update (device = this.device) {
- this.device = device
- this.emit('trezor:update', this.device)
- }
-
- needPin () {
- this.emit('trezor:needPin', this.device)
- }
-
- needPhrase () {
- this.emit('trezor:needPhrase', this.device)
- }
-
- enteringPhrase () {
- this.emit('trezor:enteringPhrase', this.device)
-
- TrezorConnect.uiResponse({
- type: UI.RECEIVE_PASSPHRASE,
- payload: { value: '', passphraseOnDevice: true }
- })
- }
-
- inputPin (pin, cb) {
- TrezorConnect.uiResponse({ device: this.device, type: UI.RECEIVE_PIN, payload: pin })
- cb()
- }
-
- inputPhrase (phrase, cb) {
- TrezorConnect.uiResponse({ device: this.device, type: UI.RECEIVE_PASSPHRASE, payload: { value: phrase } })
- cb()
- }
-
- getPublicKey (path, cb) {
- TrezorConnect.getPublicKey({ device: this.device, path }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumGetAddress (path, showOnTrezor = false, cb) {
- TrezorConnect.ethereumGetAddress({ device: this.device, path, showOnTrezor }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumSignTransaction (path, transaction, cb) {
- TrezorConnect.ethereumSignTransaction({ device: this.device, path, transaction }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumSignMessage (path, message, cb) {
- TrezorConnect.ethereumSignMessage({ device: this.device, path, message, hex: true }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumSignTypedData (path, data, cb) {
- TrezorConnect.ethereumSignTypedData({ device: this.device, path, data, metamask_v4_compat: true }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumSignTypedHash (path, data, domainSeparatorHash, messageHash, cb) {
- TrezorConnect.ethereumSignTypedData({ device: this.device, path, data, domain_separator_hash: domainSeparatorHash, message_hash: messageHash, metamask_v4_compat: true }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-
- ethereumVerifyMessage (path, address, message, signature, cb) {
- TrezorConnect.ethereumVerifyMessage({ device: this.device, path, address, message, signature }).then(res => {
- if (!res.success) return cb(new Error(res.payload.error))
- cb(null, res.payload)
- }).catch(err => cb(err))
- }
-}
-
-class Trezor {
- constructor (emit) {
- this.emit = emit
- this.devices = {}
-
- TrezorConnect.on(DEVICE_EVENT, e => {
- if (e.type === DEVICE.CONNECT || e.type === DEVICE.CHANGED) {
- // when plugging in the Trezor, the first event can sometimes be "unacquired" which
- // does not have any information about the firmware, so ignore it and wait
- // for an "acquired" event
- if (e.payload.type === 'acquired') {
- if (!this.devices[e.payload.path]) {
- this.devices[e.payload.path] = new Device(e.payload, this.emit)
- } else {
- this.devices[e.payload.path].update(e.payload)
- }
- }
- } else if (e.type === 'device-disconnect') {
- if (this.devices[e.payload.path]) this.devices[e.payload.path].disconnect()
- delete this.devices[e.payload.path]
- }
- })
-
- TrezorConnect.on(UI_EVENT, e => {
- if (e.type === UI.REQUEST_PIN) {
- const device = this.devices[e.payload.device.path]
- if (device) device.needPin()
- } else if (e.type === UI.REQUEST_PASSPHRASE) {
- const device = this.devices[e.payload.device.path]
-
- if (device) {
- const capabilities = (device.device.features || {}).capabilities || []
-
- if (capabilities.includes('Capability_PassphraseEntry')) {
- device.enteringPhrase()
- } else {
- device.needPhrase()
- }
- }
- }
- })
-
- const manifest = { email: 'jordan@frame.sh', appUrl: 'https://frame.sh' }
- const config = { manifest, popup: false, webusb: false, debug: false, lazyLoad: false }
-
- try {
- TrezorConnect.init(config).then(() => {
- ready = true
- events.emit('ready')
- }).catch(err => console.warn('TrezorConnect Init Error', err))
- } catch (e) { console.warn(e) }
- }
-
- scan (cb) {
- Object.keys(this.devices).forEach(id => this.devices[id].update())
- }
-
- deviceNotFound (id, cb) {
- cb(new Error(`Device with id: ${id} not found`))
- }
-
- inputPin (id, pin, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].inputPin(pin, cb)
- }
-
- inputPhrase (id, phrase, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].inputPhrase(phrase, cb)
- }
-
- getPublicKey (id, path, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].getPublicKey(path, cb)
- }
-
- ethereumGetAddress (id, path, display, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumGetAddress(path, display, cb)
- }
-
- ethereumSignTransaction (id, path, tx, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignTransaction(path, tx, cb)
- }
-
- ethereumSignMessage (id, path, message, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignMessage(path, message, cb)
- }
-
- ethereumSignTypedData (id, path, message, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignTypedData(path, message, cb)
- }
-
- ethereumSignTypedHash (id, path, data, domainSeparatorHash, messageHash, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumSignTypedHash(path, data, domainSeparatorHash, messageHash, cb)
- }
-
- ethereumVerifyMessage (id, path, message, cb) {
- if (!this.devices[id]) return this.deviceNotFound(id, cb)
- this.devices[id].ethereumVerifyMessage(path, message, cb)
- }
-}
-
-module.exports = Trezor
diff --git a/app/index.js b/app/index.js
index e13a288ec..df83863ce 100644
--- a/app/index.js
+++ b/app/index.js
@@ -8,8 +8,6 @@ import Panel from './App/Panel'
import link from '../resources/link'
import _store from './store'
-import './flex'
-
Sentry.init({ dsn: 'https://7b09a85b26924609bef5882387e2c4dc@o1204372.ingest.sentry.io/6331069' })
// window.removeAllAccountsAndSigners = () => link.send('tray:removeAllAccountsAndSigners')
diff --git a/dash/App/Signer/index.js b/dash/App/Signer/index.js
index 5b0708f3f..8ff3238f5 100644
--- a/dash/App/Signer/index.js
+++ b/dash/App/Signer/index.js
@@ -13,6 +13,18 @@ function isLoading (status = '') {
return ['loading', 'connecting', 'addresses', 'input', 'pairing'].some(s => statusToCheck.includes(s))
}
+function isDisconnected (type, status, isLoading) {
+ if (type === 'lattice') {
+ return status !== 'ok' && !isLoading
+ }
+
+ if (type === 'trezor') {
+ return (status === 'disconnected' || status.includes('reconnect')) && !isLoading
+ }
+
+ return false
+}
+
class Signer extends React.Component {
constructor (...args) {
super(...args)
@@ -21,7 +33,8 @@ class Signer extends React.Component {
page: 0,
addressLimit: 5,
latticePairCode: '',
- tPin: ''
+ tPin: '',
+ tPhrase: ''
}
}
@@ -40,8 +53,8 @@ class Signer extends React.Component {
}
submitPhrase () {
- link.rpc('trezorPhrase', this.props.id, this.state.tPhrase, () => {})
this.setState({ tPhrase: '' })
+ link.rpc('trezorPhrase', this.props.id, this.state.tPhrase || '', () => {})
}
renderLoadingLive () {
@@ -106,6 +119,8 @@ class Signer extends React.Component {
}
renderTrezorPhrase (active) {
+ const allowsDeviceEntry = (this.props.capabilities || []).includes('Capability_PassphraseEntry')
+
return (
{active ? (
@@ -113,61 +128,27 @@ class Signer extends React.Component {
this.setState({ tPhrase: e.target.value })} onKeyPress={e => this.phraseKeyPress(e)} autoFocus />
-
this.submitPhrase()}>
+
{
+ if (evt.button === 0) { // left click only
+ this.submitPhrase()
+ }
+ }}>
Submit Passphrase
+
{'or'}
+
{
+ if (evt.button === 0) { // left click only
+ link.rpc('trezorEnterPhrase', this.props.id, () => {})
+ }
+ }}>
+ Enter passphrase on device
+
>
) : null}
)
}
- // render () {
- // const open = this.store('selected.open')
- // const style = {}
- // if (open) {
- // style.opacity = 0
- // style.pointerEvents = 'none'
- // style.transform = 'translate(0px, -100px)'
- // }
- // if (this.props.type === 'trezor' && this.props.status === 'Need Pin') style.height = '300px'
- // if (this.props.type === 'trezor' && this.props.status === 'Enter Passphrase') style.height = '180px'
-
- // style.transition = '0.48s cubic-bezier(.82,0,.12,1) all'
-
- // const status = this.props.status ? this.props.status.charAt(0).toUpperCase() + this.props.status.substring(1) : ''
-
- // return (
- //
- //
- //
- // {this.renderLoadingLive()}
- //
- // {this.props.type === 'ledger' && (
- //
- // {svg.ledger(25)}
- //
- // )}
- // {this.props.type === 'trezor' && (
- // svg.trezor(25)
- // )}
- // {this.props.type === 'lattice' && (
- // svg.lattice(25)
- // )}
- //
- //
- //
{this.props.type + ' Found'}
- //
{status}
- //
- //
- //
- // {this.renderTrezorPin(this.props.type === 'trezor' && this.props.status === 'Need Pin')}
- // {this.renderTrezorPhrase(this.props.type === 'trezor' && this.props.status === 'Enter Passphrase')}
- //
- //
- //
- // )
-
getStatus () {
return (this.props.status || '').toLowerCase()
}
@@ -241,6 +222,18 @@ class Signer extends React.Component {
this.setState({ latticePairCode: '' })
}
+ reconnectButton (hwSigner) {
+ return (
+
{
+ link.send('dash:reloadSigner', this.props.id)
+ }}
+ >
+ {hwSigner ? 'Reconnect' : 'Reload Signer'}
+
+ )
+ }
+
render () {
const signer = this.store('main.signers', this.props.id)
const { page, addressLimit } = this.state
@@ -250,7 +243,11 @@ class Signer extends React.Component {
const hwSigner = isHardwareSigner(this.props.type)
const loading = isLoading(status)
- const disconnected = this.props.type === 'lattice' && !loading && status !== 'ok'
+ const disconnected = isDisconnected(this.props.type, status, loading)
+
+ // TODO: create well-defined signer states that drive these UI features
+ const canReconnect =
+ (this.props.type !== 'trezor' || status === 'disconnected' || status.includes('reconnect'))
// UI changes for this status only apply to hot signers
const isLocked = !hwSigner && status === 'locked'
@@ -366,14 +363,7 @@ class Signer extends React.Component {
) : null}
- {/* {
- link.send('dash:reloadSigner', this.props.id)
- }}>{hwSigner ? 'Reconnect' : 'Reload Signer'}
+ {canReconnect ? this.reconnectButton(hwSigner) : null}
{
link.send('dash:removeSigner', this.props.id)
}}>Remove Signer
@@ -384,4 +374,4 @@ class Signer extends React.Component {
}
}
-export default Restore.connect(Signer)
\ No newline at end of file
+export default Restore.connect(Signer)
diff --git a/dash/App/Signer/style/index.styl b/dash/App/Signer/style/index.styl
index 0dc3af830..6c25c3235 100644
--- a/dash/App/Signer/style/index.styl
+++ b/dash/App/Signer/style/index.styl
@@ -517,6 +517,7 @@
border-radius 8px
width calc(100% - 20px)
overflow hidden
+ margin-bottom 18px
svg
margin 0px 5px
@@ -549,20 +550,26 @@
align-items center
flex-direction column
z-index 20
+ padding-bottom 0.5em
+
+ .signerPinMessageOr
+ font-size 10px
+ text-transform uppercase
+ font-weight 600
+ padding 5px
.signerPinMessage
- height 24px
- margin-top 15px
+ height 32px
width 140px
display flex
justify-content center
align-items center
- transition var(--standard)
overflow hidden
position relative
font-size 13px
padding-bottom 2px
- background var(--ghostC)
+ background var(--ghostB)
+ box-shadow 0px 2px 6px var(--ghostZ)
.signerPinDelete
position absolute
@@ -571,20 +578,32 @@
align-items center
top 0px
bottom 0px
- width 25px
+ width 40px
left -40px
- border-radius 60px
- transition var(--standard)
+ border-radius 8px
+ background var(--ghostC)
+ cursor pointer
+
+ .signerPinDelete:hover
+ background var(--ghostD)
.signerPinSubmit
- margin-top 10px
- margin-bottom 0px
- width 180px
- border-radius 60px
+ width 250px
+ border-radius 8px
+ cursor pointer
+ font-size 12px
+ text-transform uppercase
+ text-align center
+ font-weight 600
+ display flex
+ justify-content center
.signerPinDelete
left 0px
+ .signerPinSubmit:hover
+ background var(--ghostC)
+
.trezorPinInputWrap
width 100%
position relative
diff --git a/electron-builder.json b/electron-builder.json
new file mode 100644
index 000000000..dd463354b
--- /dev/null
+++ b/electron-builder.json
@@ -0,0 +1,34 @@
+{
+ "appId": "sh.frame.app",
+ "afterSign": "./build/notarize.js",
+ "productName": "Frame",
+ "linux": {
+ "target": [
+ "AppImage",
+ "deb",
+ "snap",
+ "tar.gz"
+ ]
+ },
+ "mac": {
+ "target": {
+ "target": "default",
+ "arch": [
+ "x64",
+ "arm64"
+ ]
+ },
+ "hardenedRuntime": true,
+ "gatekeeperAssess": false,
+ "entitlements": "build/entitlements.mac.plist",
+ "requirements": "build/electron-builder-requirements.txt"
+ },
+ "win": {
+ "publisherName": "Frame Labs, Inc.",
+ "signAndEditExecutable": true
+ },
+ "files": [
+ "compiled",
+ "bundle"
+ ]
+}
diff --git a/jest.config.json b/jest.config.json
new file mode 100644
index 000000000..5782e6052
--- /dev/null
+++ b/jest.config.json
@@ -0,0 +1,14 @@
+{
+ "roots": [
+ "test"
+ ],
+ "testPathIgnorePatterns": [
+ "/node_modules/",
+ "