From d262f5b425f2f1cacaa5992e78bec425bcca8671 Mon Sep 17 00:00:00 2001 From: Umair Sarfraz Date: Fri, 8 Feb 2019 18:41:55 +0500 Subject: [PATCH 1/5] Correctly assign new account name in realm storage (#1045) --- src/shared/storage/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/storage/index.js b/src/shared/storage/index.js index 2caf2e7722..f5f7299d1a 100644 --- a/src/shared/storage/index.js +++ b/src/shared/storage/index.js @@ -166,7 +166,7 @@ class Account { realm.write(() => { // Create account with new name. - realm.create('Account', assign({}, accountData, { accountName: to })); + realm.create('Account', assign({}, accountData, { name: to })); // Delete account with old name. realm.delete(accountData); }); From c4b1d8347d0bc697099b9bc3c78d43d6d8941e6d Mon Sep 17 00:00:00 2001 From: Umair Sarfraz Date: Fri, 8 Feb 2019 18:42:18 +0500 Subject: [PATCH 2/5] Update isFailedTransaction prop when modal props are updated (#1046) --- src/mobile/src/ui/views/wallet/History.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mobile/src/ui/views/wallet/History.js b/src/mobile/src/ui/views/wallet/History.js index f732fc208d..15d9652afe 100644 --- a/src/mobile/src/ui/views/wallet/History.js +++ b/src/mobile/src/ui/views/wallet/History.js @@ -146,6 +146,7 @@ class History extends Component { newProps.isPromotingTransaction || newProps.isRetryingFailedTransaction, isRetryingFailedTransaction: newProps.isRetryingFailedTransaction, + isFailedTransaction: !newBundleProps.broadcasted, bundleIsBeingPromoted: newProps.currentlyPromotingBundleHash === modalProps.bundle && !newBundleProps.persistence, }); From a3ce40e454419ba13e395d3382f19df13e728c01 Mon Sep 17 00:00:00 2001 From: Charlie Varley Date: Fri, 8 Feb 2019 17:22:24 +0000 Subject: [PATCH 3/5] Mobile: Add retry button, error log and change node to Realm migration (#1041) * Mobile: Add ability to change node and retry during migration * Mobile: Address comments * Mobile: Fix notification button import and padding * Mobile: Disable iOS pop gesture --- src/mobile/src/ui/components/Migration.js | 102 ++++++++++++++--- .../src/ui/components/NotificationButton.js | 104 ++++++++++++++++++ .../src/ui/components/NotificationLogModal.js | 8 +- src/mobile/src/ui/components/TopBar.js | 44 +------- src/mobile/src/ui/routes/entry.js | 1 + src/shared/actions/migrations.js | 1 + src/shared/locales/en/translation.json | 4 +- 7 files changed, 209 insertions(+), 55 deletions(-) create mode 100644 src/mobile/src/ui/components/NotificationButton.js diff --git a/src/mobile/src/ui/components/Migration.js b/src/mobile/src/ui/components/Migration.js index 633bca690e..7dd657ddb4 100644 --- a/src/mobile/src/ui/components/Migration.js +++ b/src/mobile/src/ui/components/Migration.js @@ -1,4 +1,5 @@ import size from 'lodash/size'; +import sample from 'lodash/sampleSize'; import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; @@ -8,13 +9,16 @@ import { startTrackingProgress } from 'shared-modules/actions/progress'; import { connect } from 'react-redux'; import { Styling } from 'ui/theme/general'; import { migrate } from 'shared-modules/actions/migrations'; +import { setFullNode } from 'shared-modules/actions/settings'; import { reduxPersistStorageAdapter } from 'libs/store'; import ProgressSteps from 'libs/progressSteps'; import { getThemeFromState } from 'shared-modules/selectors/global'; import Header from 'ui/components/Header'; import InfoBox from 'ui/components/InfoBox'; +import NotificationButtonComponent from 'ui/components/NotificationButton'; import ProgressBar from 'ui/components/OldProgressBar'; import { height } from 'libs/dimensions'; +import DualFooterButtons from './DualFooterButtons'; const styles = StyleSheet.create({ container: { @@ -29,15 +33,25 @@ const styles = StyleSheet.create({ paddingTop: height / 16, }, midContainer: { - flex: 2.6, + flex: 1.9, alignItems: 'center', }, + bottomContainer: { + flex: 0.7, + alignItems: 'center', + justifyContent: 'flex-end', + }, infoText: { fontFamily: 'SourceSansPro-Light', fontSize: Styling.fontSize3, textAlign: 'left', backgroundColor: 'transparent', }, + notificationButton: { + position: 'absolute', + top: height / 30 + Styling.statusBarHeight, + left: height / 30, + }, }); /** @@ -59,11 +73,27 @@ class Migration extends Component { startTrackingProgress: PropTypes.func.isRequired, /** @ignore */ completedMigration: PropTypes.bool.isRequired, + /** @ignore */ + notificationLog: PropTypes.array.isRequired, + /** @ignore */ + nodes: PropTypes.array.isRequired, + /** @ignore */ + setFullNode: PropTypes.func.isRequired, + /** @ignore */ + isChangingNode: PropTypes.bool.isRequired, }; + constructor() { + super(); + this.state = { + hasFailedMigration: false, + }; + this.changeNode = this.changeNode.bind(this); + this.retryMigration = this.retryMigration.bind(this); + } + componentDidMount() { this.props.startTrackingProgress(ProgressSteps.migration); - this.props.migrate(reduxPersistStorageAdapter); } @@ -71,6 +101,9 @@ class Migration extends Component { if (!this.props.completedMigration && newProps.completedMigration) { this.navigateToLoadingScreen(); } + if (size(this.props.notificationLog) !== size(newProps.notificationLog)) { + this.setState({ hasFailedMigration: true }); + } } /** @@ -107,6 +140,25 @@ class Migration extends Component { }); } + /** + * Changes to a random node + * + * @method changeNode + */ + changeNode() { + this.props.setFullNode(...sample(this.props.nodes)); + } + + /** + * Retries migration in case of failure + * + * @method retryMigration + */ + retryMigration() { + this.setState({ hasFailedMigration: false }); + this.props.migrate(reduxPersistStorageAdapter); + } + /** * Renders progress bar textual information * @@ -119,7 +171,7 @@ class Migration extends Component { } render() { - const { t, theme: { body, primary }, activeSteps, activeStepIndex } = this.props; + const { t, theme: { body, primary }, activeSteps, activeStepIndex, isChangingNode } = this.props; const textColor = { color: body.color }; const sizeOfActiveSteps = size(activeSteps) - 1; @@ -135,18 +187,36 @@ class Migration extends Component { - - {t(this.renderProgressBarChildren())} - + {activeStepIndex > -1 && ( + + {t(this.renderProgressBarChildren())} + + )} + + + {this.state.hasFailedMigration && ( + + )} + {this.state.hasFailedMigration && ( + + + + )} ); } @@ -157,11 +227,15 @@ const mapStateToProps = (state) => ({ activeStepIndex: state.progress.activeStepIndex, activeSteps: state.progress.activeSteps, completedMigration: state.settings.completedMigration, + notificationLog: state.alerts.notificationLog, + nodes: state.settings.nodes, + isChangingNode: state.ui.isChangingNode, }); const mapDispatchToProps = { migrate, startTrackingProgress, + setFullNode, }; export default withNamespaces(['migration'])(connect(mapStateToProps, mapDispatchToProps)(Migration)); diff --git a/src/mobile/src/ui/components/NotificationButton.js b/src/mobile/src/ui/components/NotificationButton.js new file mode 100644 index 0000000000..e9b1381698 --- /dev/null +++ b/src/mobile/src/ui/components/NotificationButton.js @@ -0,0 +1,104 @@ +import size from 'lodash/size'; +import React, { Component } from 'react'; +import { StyleSheet, View, TouchableOpacity } from 'react-native'; +import { getThemeFromState } from 'shared-modules/selectors/global'; +import { clearLog } from 'shared-modules/actions/alerts'; +import { toggleModalActivity } from 'shared-modules/actions/ui'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; +import { width } from 'libs/dimensions'; +import { Icon } from 'ui/theme/icons'; + +const styles = StyleSheet.create({ + container: { + width: width / 9, + height: width / 9, + }, + innerContainer: { + justifyContent: 'center', + alignItems: 'center', + width: width / 9, + flex: 1, + }, + disabled: { + color: '#a9a9a9', + }, +}); + +export class NotificationButton extends Component { + static propTypes = { + /** @ignore */ + theme: PropTypes.object.isRequired, + /** @ignore */ + isModalActive: PropTypes.bool.isRequired, + /** @ignore */ + clearLog: PropTypes.func.isRequired, + /** @ignore */ + toggleModalActivity: PropTypes.func.isRequired, + /** @ignore */ + isTransitioning: PropTypes.bool.isRequired, + /** @ignore */ + notificationLog: PropTypes.array.isRequired, + /** Determines whether to display topBar when modal is open */ + displayTopBar: PropTypes.bool, + }; + + static defaultProps = { + displayTopBar: true, + }; + + /** + * Displays error log + * + * @method showModal + */ + showModal() { + const { isTransitioning, theme, notificationLog, displayTopBar } = this.props; + if (!isTransitioning) { + this.props.toggleModalActivity('notificationLog', { + hideModal: this.props.toggleModalActivity, + theme, + notificationLog, + clearLog: this.props.clearLog, + displayTopBar, + }); + } + } + + render() { + const { theme: { bar }, isModalActive, notificationLog } = this.props; + const hasNotifications = size(notificationLog) && notificationLog.length > 0; + + return ( + + {(hasNotifications && ( + this.showModal()} style={styles.innerContainer}> + + + )) || } + + ); + } +} + +const mapStateToProps = (state) => ({ + theme: getThemeFromState(state), + isModalActive: state.ui.isModalActive, + isTransitioning: state.ui.isTransitioning, + notificationLog: state.alerts.notificationLog, +}); + +const mapDispatchToProps = { + clearLog, + toggleModalActivity, +}; + +export default withNamespaces(['enterSeed', 'global'])( + connect(mapStateToProps, mapDispatchToProps)(NotificationButton), +); diff --git a/src/mobile/src/ui/components/NotificationLogModal.js b/src/mobile/src/ui/components/NotificationLogModal.js index 5abcd5acf4..72fee5754a 100644 --- a/src/mobile/src/ui/components/NotificationLogModal.js +++ b/src/mobile/src/ui/components/NotificationLogModal.js @@ -52,6 +52,12 @@ export class NotificationLogModal extends PureComponent { clearLog: PropTypes.func.isRequired, /** @ignore */ t: PropTypes.func.isRequired, + /** Deteremines whether to display the topbar */ + displayTopBar: PropTypes.bool, + }; + + static defaultProps = { + displayTopBar: true, }; constructor() { @@ -84,7 +90,7 @@ export class NotificationLogModal extends PureComponent { return ( this.clearNotificationLog()} onRightButtonPress={() => this.props.hideModal()} diff --git a/src/mobile/src/ui/components/TopBar.js b/src/mobile/src/ui/components/TopBar.js index 18250088ec..7c6f32b49b 100644 --- a/src/mobile/src/ui/components/TopBar.js +++ b/src/mobile/src/ui/components/TopBar.js @@ -8,7 +8,6 @@ import { connect } from 'react-redux'; import { withNamespaces } from 'react-i18next'; import { toggleTopBarDisplay } from 'shared-modules/actions/home'; import { setSeedIndex } from 'shared-modules/actions/wallet'; -import { clearLog } from 'shared-modules/actions/alerts'; import { toggleModalActivity } from 'shared-modules/actions/ui'; import { getBalanceForSelectedAccount, @@ -34,6 +33,7 @@ import { accumulateBalance } from 'shared-modules/libs/iota/addresses'; import { Icon } from 'ui/theme/icons'; import { isIPhoneX } from 'libs/device'; import { Styling } from 'ui/theme/general'; +import NotificationButtonComponent from 'ui/components/NotificationButton'; const { height, width } = Dimensions.get('window'); @@ -130,10 +130,6 @@ class TopBar extends Component { theme: PropTypes.object.isRequired, /** @ignore */ setPollFor: PropTypes.func.isRequired, - /** @ignore */ - notificationLog: PropTypes.array.isRequired, - /** @ignore */ - clearLog: PropTypes.func.isRequired, /** Top bar height */ topBarHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), /** @ignore */ @@ -277,18 +273,6 @@ class TopBar extends Component { ); } - showModal() { - const { isTransitioning, theme, notificationLog } = this.props; - if (!isTransitioning) { - this.props.toggleModalActivity('notificationLog', { - hideModal: () => this.hideModal(), - theme, - notificationLog, - clearLog: this.props.clearLog, - }); - } - } - hideModal() { this.props.toggleModalActivity(); } @@ -302,7 +286,6 @@ class TopBar extends Component { seedIndex, theme: { bar, primary }, isKeyboardActive, - notificationLog, mode, minimised, currentRoute, @@ -327,7 +310,6 @@ class TopBar extends Component { const withSubtitles = (title, index) => ({ title, subtitle: getBalance(index), index }); const titles = map(accountNames, withSubtitles); const hasMultipleSeeds = size(TopBar.filterSeedTitles(accountNames, seedIndex)); - const hasNotifications = size(notificationLog) && notificationLog.length > 0; const shouldDisable = this.shouldDisable(); const baseContent = ( @@ -344,22 +326,10 @@ class TopBar extends Component { {(!isKeyboardActive && !minimised && ( - - {(hasNotifications && - !isKeyboardActive && - mode === 'Advanced' && ( - this.showModal()} - style={styles.iconWrapper} - > - - - )) || } + + {(!isKeyboardActive && mode === 'Advanced' && ) || ( + + )} - + {(hasMultipleSeeds && ( ({ isTopBarActive: state.home.isTopBarActive, selectedAccount: selectAccountInfo(state), theme: getThemeFromState(state), - notificationLog: state.alerts.notificationLog, isFetchingLatestAccountInfo: state.ui.isFetchingAccountInfo, currentRoute: state.home.childRoute, isKeyboardActive: state.ui.isKeyboardActive, @@ -557,7 +526,6 @@ const mapDispatchToProps = { toggleModalActivity, setSeedIndex, setPollFor, - clearLog, }; export default withNamespaces('global')(connect(mapStateToProps, mapDispatchToProps)(TopBar)); diff --git a/src/mobile/src/ui/routes/entry.js b/src/mobile/src/ui/routes/entry.js index 797ecd1529..acb09da422 100644 --- a/src/mobile/src/ui/routes/entry.js +++ b/src/mobile/src/ui/routes/entry.js @@ -95,6 +95,7 @@ const renderInitialScreen = (initialScreen, state) => { drawBehind: true, backgroundColor: theme.body.bg, }, + popGesture: false, }; Navigation.setDefaultOptions(options); diff --git a/src/shared/actions/migrations.js b/src/shared/actions/migrations.js index aa740136c3..4ba6103355 100644 --- a/src/shared/actions/migrations.js +++ b/src/shared/actions/migrations.js @@ -95,6 +95,7 @@ export const migrate = (oldStorageAdapter) => (dispatch) => { // If there is not data to migrate, just mark migration as complete onComplete(); } else { + dispatch(resetProgress()); dispatch( generateAlert( 'error', diff --git a/src/shared/locales/en/translation.json b/src/shared/locales/en/translation.json index 2cfbba9f60..9dc667a6fc 100644 --- a/src/shared/locales/en/translation.json +++ b/src/shared/locales/en/translation.json @@ -781,7 +781,7 @@ }, "migration": { "dataMigration": "Data migration", - "dataMigrationExplanation": "Due to performance and storage limitations, Trinity is using a new database for storing settings and account data. Please wait while Trinity automatically migrates your existing data", + "dataMigrationExplanation": "Due to performance and storage limitations, Trinity is using a new database for storing settings and account data. Please wait while Trinity automatically migrates your existing data.", "problemMigratingYourData": "There was a problem migrating your data. Please try again" }, "ledger": { @@ -816,4 +816,4 @@ "applicationNotInitialised": "Warning unconfirmed", "applicationNotInitialisedExplanation": "Reopen the IOTA app on your Ledger device, accept the informational warning and try again." } -} \ No newline at end of file +} From 068fca6b113fe444f1e43ec91ad161e969bb5817 Mon Sep 17 00:00:00 2001 From: Charlie Varley Date: Fri, 8 Feb 2019 17:36:37 +0000 Subject: [PATCH 4/5] Mobile Release 0.6.1 (42) (#1048) * Mobile: Bump build no to 42 * Mobile: Update realm migration versioning --- src/mobile/android/app/build.gradle | 2 +- src/mobile/ios/iotaWallet-tvOS/Info.plist | 2 +- src/mobile/ios/iotaWallet-tvOSTests/Info.plist | 2 +- src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj | 4 ++-- src/mobile/ios/iotaWallet/Info.plist | 2 +- src/mobile/ios/iotaWalletTests/Info.plist | 2 +- src/mobile/ios/iotaWalletUITests/Info.plist | 2 +- src/mobile/src/ui/routes/entry.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/mobile/android/app/build.gradle b/src/mobile/android/app/build.gradle index f3f3fd20ab..191912644f 100644 --- a/src/mobile/android/app/build.gradle +++ b/src/mobile/android/app/build.gradle @@ -92,7 +92,7 @@ android { buildToolsVersion '27.0.3' defaultConfig { applicationId "com.iota.trinity" - versionCode 41 + versionCode 42 versionName "0.6.1" minSdkVersion 21 targetSdkVersion 26 diff --git a/src/mobile/ios/iotaWallet-tvOS/Info.plist b/src/mobile/ios/iotaWallet-tvOS/Info.plist index 6ba8f8b118..f343b1a4da 100644 --- a/src/mobile/ios/iotaWallet-tvOS/Info.plist +++ b/src/mobile/ios/iotaWallet-tvOS/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/src/mobile/ios/iotaWallet-tvOSTests/Info.plist b/src/mobile/ios/iotaWallet-tvOSTests/Info.plist index c7363ad129..cf3b3e3d16 100644 --- a/src/mobile/ios/iotaWallet-tvOSTests/Info.plist +++ b/src/mobile/ios/iotaWallet-tvOSTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 diff --git a/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj b/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj index 6d1edbcbef..c099f2e455 100644 --- a/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj +++ b/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj @@ -2910,7 +2910,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 42; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; @@ -3305,7 +3305,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 42; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/src/mobile/ios/iotaWallet/Info.plist b/src/mobile/ios/iotaWallet/Info.plist index 15b67428e4..9ba45d3e1f 100644 --- a/src/mobile/ios/iotaWallet/Info.plist +++ b/src/mobile/ios/iotaWallet/Info.plist @@ -25,7 +25,7 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/src/mobile/ios/iotaWalletTests/Info.plist b/src/mobile/ios/iotaWalletTests/Info.plist index 2c142cde1a..7b7776f67e 100644 --- a/src/mobile/ios/iotaWalletTests/Info.plist +++ b/src/mobile/ios/iotaWalletTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 diff --git a/src/mobile/ios/iotaWalletUITests/Info.plist b/src/mobile/ios/iotaWalletUITests/Info.plist index 19665b427c..c3a73854c0 100644 --- a/src/mobile/ios/iotaWalletUITests/Info.plist +++ b/src/mobile/ios/iotaWalletUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 0.6.1 CFBundleVersion - 41 + 42 diff --git a/src/mobile/src/ui/routes/entry.js b/src/mobile/src/ui/routes/entry.js index acb09da422..d5aaca435f 100644 --- a/src/mobile/src/ui/routes/entry.js +++ b/src/mobile/src/ui/routes/entry.js @@ -204,7 +204,7 @@ onAppStart() const { settings: { versions, completedMigration } } = reduxState; if ( - versions.buildNumber < 42 && + versions.buildNumber < 43 && completedMigration === false && // Also check if there is persisted data in AsyncStorage that needs to be migrated // If this check is omitted, the condition will be satisfied on a fresh install. From 2a8390406c301e6be7e9c87fafbf8a3fd503fe92 Mon Sep 17 00:00:00 2001 From: Rihards Gravis <31288628+rihardsgravis@users.noreply.github.com> Date: Tue, 12 Feb 2019 15:49:47 +0200 Subject: [PATCH 5/5] Realm Database implementation desktop bugfixes (#1025) * - Fix Realm storage path - Add missing wallet reset triggers - Fix address component prop use - Remove failed bundle hash action * Update Realm path for test environment * - Remove Realm instance init from Tray application - Keep Realm encryption key on keychain initialisation * Focus wallet window after initial store update * Add missing Windows required dependency --- src/desktop/README.md | 6 + src/desktop/main.js | 10 ++ src/desktop/native/preload/Electron.js | 17 +++ src/desktop/src/index.js | 136 +++++++++--------- src/desktop/src/libs/crypto.js | 10 +- src/desktop/src/ui/global/Alerts.js | 7 +- src/desktop/src/ui/views/settings/Advanced.js | 20 ++- .../ui/views/settings/account/Addresses.js | 5 +- src/shared/storage/index.js | 8 +- 9 files changed, 138 insertions(+), 81 deletions(-) diff --git a/src/desktop/README.md b/src/desktop/README.md index 49a0820bbb..ad5c3780b5 100644 --- a/src/desktop/README.md +++ b/src/desktop/README.md @@ -15,7 +15,13 @@ This is the repository for the IOTA Trinity Desktop Wallet. The application is b On **Windows** platforms you'll need to install build tools to succesfully compile native modules: ``` +# Install Visual C++ Build Tools and Python 2.7 npm install --global windows-build-tools +# Install OpenSSL VC++ Static 64bit Library +git clone https://github.com/Microsoft/vcpkg C:\src\vcpkg +cd C:\src\vcpkg +.\bootstrap-vcpkg.bat +.\vcpkg install openssl:x64-windows-static ``` On **Linux** platforms you'll need to additional packages to build native modules: diff --git a/src/desktop/main.js b/src/desktop/main.js index 0d84fcba2f..c70798c5ac 100644 --- a/src/desktop/main.js +++ b/src/desktop/main.js @@ -366,6 +366,16 @@ ipc.on('request.deepLink', () => { } }); +/** + * Proxy store update event to tray window + */ + +ipc.on('store.update', (_e, payload) => { + if (process.platform === 'darwin' && windows.tray && !windows.tray.isDestroyed()) { + windows.tray.webContents.send('store.update', payload); + } +}); + /** * Proxy menu update event to tray window */ diff --git a/src/desktop/native/preload/Electron.js b/src/desktop/native/preload/Electron.js index 64e26fca23..710f7db656 100644 --- a/src/desktop/native/preload/Electron.js +++ b/src/desktop/native/preload/Electron.js @@ -109,6 +109,14 @@ const Electron = { return addresses; }, + /** + * Returns per-user application data directory + * @returns {string} - Full app data path + */ + getUserDataPath: () => { + return remote.app.getPath('userData'); + }, + /** * Gets machine UUID * @return {string} @@ -310,6 +318,15 @@ const Electron = { ipc.send('menu.popup'); }, + /** + * Proxy store updates to Tray application + * @param {string} payload - Store state + * @returns {undefined} + */ + storeUpdate: (payload) => { + ipc.send('store.update', payload); + }, + /** * Set onboarding seed variable to bypass Redux * @param {array} Seed - Target seed byte array diff --git a/src/desktop/src/index.js b/src/desktop/src/index.js index 10334dfdcf..7f9c0ac824 100644 --- a/src/desktop/src/index.js +++ b/src/desktop/src/index.js @@ -36,77 +36,85 @@ export const bugsnagClient = bugsnag({ const ErrorBoundary = bugsnagClient.use(createPlugin(React)); -initialiseStorage(getEncryptionKey) - .then(() => { - return new Promise((resolve, reject) => { - persistElectronStorage.getAllKeys((err, keys) => (err ? reject(err) : resolve(keys))); - }); - }) - .then((keys) => { - const getItemAsync = (key) => - new Promise((resolve, reject) => { - persistElectronStorage.getItem(key, (err, item) => (err ? reject(err) : resolve(item))); +if (Electron.mode === 'tray') { + Electron.onEvent('store.update', (payload) => { + const data = JSON.parse(payload); + store.dispatch(mapStorageToStateAction(data)); + }); +} else { + initialiseStorage(getEncryptionKey) + .then(() => { + return new Promise((resolve, reject) => { + persistElectronStorage.getAllKeys((err, keys) => (err ? reject(err) : resolve(keys))); }); + }) + .then((keys) => { + const getItemAsync = (key) => + new Promise((resolve, reject) => { + persistElectronStorage.getItem(key, (err, item) => (err ? reject(err) : resolve(item))); + }); - return keys.reduce( - (promise, key) => - promise.then((result) => - getItemAsync(key).then((item) => { - result[key.split(':')[1]] = parse(item); - - return result; - }), - ), - Promise.resolve({}), - ); - }) - .then((oldPersistedData) => { - // TODO: Also check version & completedMigration state prop - const hasDataToMigrate = !isEmpty(oldPersistedData); - - // Get persisted data from Realm storage - const persistedDataFromRealm = mapStorageToState(); - const data = hasDataToMigrate ? oldPersistedData : persistedDataFromRealm; - - // Change provider on global iota instance - const node = get(data, 'settings.node'); - changeIotaNode(node); - - // Update store with persisted state - store.dispatch(mapStorageToStateAction(data)); + return keys.reduce( + (promise, key) => + promise.then((result) => + getItemAsync(key).then((item) => { + result[key.split(':')[1]] = parse(item); + + return result; + }), + ), + Promise.resolve({}), + ); + }) + .then((oldPersistedData) => { + // TODO: Also check version & completedMigration state prop + const hasDataToMigrate = !isEmpty(oldPersistedData); + + // Get persisted data from Realm storage + const persistedDataFromRealm = mapStorageToState(); + const data = hasDataToMigrate ? oldPersistedData : persistedDataFromRealm; + + // Change provider on global iota instance + const node = get(data, 'settings.node'); + changeIotaNode(node); - // Assign accountIndex to every account in accountInfo if it is not assigned already - store.dispatch(assignAccountIndexIfNecessary(get(data, 'accounts.accountInfo'))); + // Update store with persisted state + store.dispatch(mapStorageToStateAction(data)); - if (Electron.mode === 'tray') { - // Add Realm change listener to sync read-only Tray app with Main app + // Assign accountIndex to every account in accountInfo if it is not assigned already + store.dispatch(assignAccountIndexIfNecessary(get(data, 'accounts.accountInfo'))); + + // Proxy realm changes to Tray application realm.addListener('change', () => { const data = mapStorageToState(); - store.dispatch(mapStorageToStateAction(data)); + Electron.storeUpdate(JSON.stringify(data)); }); - } else { + // Start Tray application if enabled in settings const isTrayEnabled = get(data, 'settings.isTrayEnabled'); Electron.setTray(isTrayEnabled); - } - - render( - - - - - {Electron.mode === 'tray' ? ( - - ) : ( - - - - - )} - - - - , - document.getElementById('root'), - ); - }); + + // Show Wallet window after inital store update + Electron.focus(); + }); +} + +render( + + + + + {Electron.mode === 'tray' ? ( + + ) : ( + + + + + )} + + + + , + document.getElementById('root'), +); diff --git a/src/desktop/src/libs/crypto.js b/src/desktop/src/libs/crypto.js index aa1b4b565b..59bf582129 100644 --- a/src/desktop/src/libs/crypto.js +++ b/src/desktop/src/libs/crypto.js @@ -1,5 +1,6 @@ /* global Electron */ import { MAX_SEED_LENGTH } from 'libs/iota/utils'; +import { ALIAS_REALM } from 'libs/realm'; export const ACC_MAIN = 'Trinity'; // Maximum allowed account title @@ -125,7 +126,7 @@ export const setTwoFA = async (password, key) => { * Set and store random salt to keychain */ export const initKeychain = async () => { - await clearVault(); + await clearVault([ALIAS_REALM]); const salt = crypto.getRandomValues(new Uint8Array(16)); const saltHex = salt.toString(); await Electron.setKeychain(`${ACC_MAIN}-salt`, saltHex); @@ -154,14 +155,17 @@ export const authorize = async (key) => { /** * Clear the vault + * @param {array} keepAccounts - Account names that should not be cleared * @returns {boolean} True if vault cleared */ -export const clearVault = async () => { +export const clearVault = async (keepAccounts = []) => { const vault = await Electron.listKeychain(); const accounts = Object.keys(vault); for (let i = 0; i < accounts.length; i++) { - await Electron.removeKeychain(vault[i].account); + if (keepAccounts.indexOf(vault[i].account) < 0) { + await Electron.removeKeychain(vault[i].account); + } } return true; diff --git a/src/desktop/src/ui/global/Alerts.js b/src/desktop/src/ui/global/Alerts.js index 33a8311711..c64d5f912c 100644 --- a/src/desktop/src/ui/global/Alerts.js +++ b/src/desktop/src/ui/global/Alerts.js @@ -35,7 +35,7 @@ class Alerts extends React.PureComponent { if (this.timeout) { clearTimeout(this.timeout); } - if (nextProps.alerts.category.length && nextProps.alerts.closeInterval > 0) { + if (nextProps.alerts.category && nextProps.alerts.category.length && nextProps.alerts.closeInterval > 0) { this.timeout = setTimeout(() => { this.props.dismissAlert(); }, nextProps.alerts.closeInterval); @@ -80,7 +80,10 @@ class Alerts extends React.PureComponent { ) : (
dismissAlert()} - className={classNames(alerts.category && alerts.category.length ? css.visible : null, css[`${alerts.category}`])} + className={classNames( + alerts.category && alerts.category.length ? css.visible : null, + css[`${alerts.category}`], + )} > diff --git a/src/desktop/src/ui/views/settings/Advanced.js b/src/desktop/src/ui/views/settings/Advanced.js index 9db2719660..1c27c49055 100644 --- a/src/desktop/src/ui/views/settings/Advanced.js +++ b/src/desktop/src/ui/views/settings/Advanced.js @@ -13,10 +13,13 @@ import { setTray, setNotifications, setProxy, + resetWallet, } from 'actions/settings'; import { generateAlert } from 'actions/alerts'; +import { reinitialise as reinitialiseStorage } from 'storage'; + import Button from 'ui/components/Button'; import Confirm from 'ui/components/modal/Confirm'; import ModalPassword from 'ui/components/modal/Password'; @@ -53,6 +56,8 @@ class Advanced extends PureComponent { /** @ignore */ generateAlert: PropTypes.func.isRequired, /** @ignore */ + resetWallet: PropTypes.func.isRequired, + /** @ignore */ t: PropTypes.func.isRequired, }; @@ -91,13 +96,18 @@ class Advanced extends PureComponent { * Hard reset wallet * @returns {undefined} */ - resetWallet = () => { - const { t, generateAlert } = this.props; + resetWallet = async () => { + const { t, generateAlert, resetWallet } = this.props; try { + await reinitialiseStorage(); + resetWallet(); + clearVault(); + localStorage.clear(); Electron.clearStorage(); + location.reload(); } catch (err) { generateAlert( @@ -320,9 +330,7 @@ const mapDispatchToProps = { setTray, setNotifications, setProxy, + resetWallet, }; -export default connect( - mapStateToProps, - mapDispatchToProps, -)(withI18n()(Advanced)); +export default connect(mapStateToProps, mapDispatchToProps)(withI18n()(Advanced)); diff --git a/src/desktop/src/ui/views/settings/account/Addresses.js b/src/desktop/src/ui/views/settings/account/Addresses.js index 717e087614..e5e9c9ce1a 100644 --- a/src/desktop/src/ui/views/settings/account/Addresses.js +++ b/src/desktop/src/ui/views/settings/account/Addresses.js @@ -24,10 +24,7 @@ class Addresses extends PureComponent { const { account, t } = this.props; const isSpent = ({ spent: { local, remote } }) => local || remote; - const addressData = account - .addressData() - .slice() - .sort((a, b) => b.index - a.index); + const addressData = account.addressData.slice().sort((a, b) => b.index - a.index); return (
diff --git a/src/shared/storage/index.js b/src/shared/storage/index.js index f5f7299d1a..d11b9058cc 100644 --- a/src/shared/storage/index.js +++ b/src/shared/storage/index.js @@ -23,11 +23,15 @@ import { WalletVersionsSchema, ErrorLogSchema, } from '../schema'; -import { __MOBILE__, __TEST__ } from '../config'; +import { __MOBILE__, __TEST__, __DEV__ } from '../config'; import { preserveAddressLocalSpendStatus } from '../libs/iota/addresses'; const SCHEMA_VERSION = 0; -const STORAGE_PATH = `trinity-${SCHEMA_VERSION}.realm`; + +const STORAGE_PATH = + __MOBILE__ || __TEST__ + ? `trinity-${SCHEMA_VERSION}.realm` + : `${Electron.getUserDataPath()}/trinity${__DEV__ ? '-dev' : ''}-${SCHEMA_VERSION}.realm`; // Initialise realm instance let realm = {}; // eslint-disable-line import/no-mutable-exports