From a24cd09773ef30dcfe54012a52a291520a4f3351 Mon Sep 17 00:00:00 2001 From: Fernando Boucquez Date: Sat, 21 Mar 2020 10:52:34 -0300 Subject: [PATCH 01/17] Database and storage refactoring. --- __mocks__/Database.ts | 75 -- .../components/MosaicInputManager.spec.ts | 28 +- .../components/WalletSelectorField.spec.ts | 28 +- __tests__/database/BaseStorageAdapter.spec.ts | 160 ---- __tests__/database/DatabaseModel.spec.ts | 131 --- __tests__/database/JSONFormatter.spec.ts | 262 ------ .../database/SimpleStorageAdapter.spec.ts | 66 -- .../backends/ObjectStorageBackend.spec.ts | 110 --- .../SimpleObjectStorage.spec.ts} | 36 +- __tests__/services/AccountService.spec.ts | 5 +- __tests__/services/DerivationService.spec.ts | 3 +- __tests__/services/MosaicModel.spec.ts | 57 ++ __tests__/services/MultisigService.spec.ts | 2 +- __tests__/services/WalletService.spec.ts | 2 +- __tests__/store/Account.spec.ts | 12 +- __tests__/store/Database.spec.ts | 40 - .../transactions/ViewAliasTransaction.spec.ts | 2 +- .../ViewHashLockTransaction.spec.ts | 2 +- .../ViewTransferTransaction.spec.ts | 2 +- __tests__/utils/TimeHelpers.spec.ts | 54 ++ __tests__/utils/URLHelpers.spec.ts | 6 +- config/network.conf.json | 74 +- package-lock.json | 112 ++- package.json | 3 +- src/app/AppTs.ts | 15 +- .../AccountBalancesPanel.vue | 14 +- .../AccountBalancesPanelTs.ts | 117 +-- .../ActionDisplay/ActionDisplayTs.ts | 27 +- .../AmountDisplay/AmountDisplayTs.ts | 44 +- .../ApprovalAndRemovalInputTs.ts | 31 +- .../HardwareConfirmationButtonTs.ts | 37 +- .../ImportanceScoreDisplayTs.ts | 31 +- .../LanguageSelector/LanguageSelectorTs.ts | 4 +- .../MaxFeeSelector/MaxFeeSelectorTs.ts | 26 +- src/components/MessageInput/MessageInputTs.ts | 4 +- .../MnemonicInput/MnemonicInput.vue | 2 +- .../MnemonicInput/MnemonicInputTs.ts | 5 +- .../MnemonicVerificationTs.ts | 1 - .../MosaicAmountDisplayTs.ts | 61 +- .../MosaicAttachmentInput.vue | 2 +- .../MosaicAttachmentInputTs.ts | 41 +- .../MosaicBalanceList/MosaicBalanceList.vue | 4 +- .../MosaicBalanceList/MosaicBalanceListTs.ts | 139 +-- .../MosaicSelector/MosaicSelector.vue | 9 +- .../MosaicSelector/MosaicSelectorTs.ts | 60 +- .../MultisigCosignatoriesDisplay.vue | 2 +- .../MultisigCosignatoriesDisplayTs.ts | 5 +- .../NamespaceNameInputTs.ts | 6 +- .../NamespaceSelector/NamespaceSelector.vue | 11 +- .../NamespaceSelector/NamespaceSelectorTs.ts | 36 +- .../NavigationTabs/NavigationTabs.vue | 1 + .../NetworkStatisticsPanel.vue | 2 +- .../NetworkStatisticsPanelTs.ts | 53 +- .../PageNavigator/PageNavigatorTs.ts | 11 +- src/components/PageTitle/PageTitleTs.ts | 2 +- src/components/PeerSelector/PeerSelector.vue | 29 +- src/components/PeerSelector/PeerSelectorTs.ts | 143 ++- .../ProtectedMnemonicDisplayButton.vue | 3 +- .../ProtectedMnemonicDisplayButtonTs.ts | 8 +- .../ProtectedMnemonicQRButton.vue | 3 +- .../ProtectedMnemonicQRButtonTs.ts | 4 +- .../ProtectedPrivateKeyDisplayTs.ts | 8 +- .../RemoveCosignatoryInputTs.ts | 4 +- .../SignerSelector/SignerSelector.vue | 3 +- .../SignerSelector/SignerSelectorTs.ts | 11 +- src/components/TableDisplay/TableDisplayTs.ts | 99 +-- .../TransactionDetails/Alias/Alias.vue | 26 +- .../TransactionDetailRow.vue | 8 +- .../TransactionDetailsTs.ts | 67 +- .../TransactionList/TransactionListTs.ts | 62 +- .../TransactionRow/TransactionRowTs.ts | 27 +- .../TransactionListOptions.vue | 2 +- .../TransactionListOptionsTs.ts | 90 +- .../WalletActions/WalletActionsTs.ts | 7 +- .../WalletAddressDisplay.vue | 4 +- .../WalletAddressDisplayTs.ts | 17 +- .../WalletAliasDisplayTs.ts | 44 +- .../WalletBackupOptionsTs.ts | 5 +- .../WalletContactQR/WalletContactQRTs.ts | 18 +- .../WalletDetailsDisplay.vue | 8 +- .../WalletDetailsDisplayTs.ts | 7 +- src/components/WalletLinks/WalletLinksTs.ts | 11 +- .../WalletNameDisplay/WalletNameDisplay.vue | 4 +- .../WalletNameDisplay/WalletNameDisplayTs.ts | 4 +- .../WalletPublicKeyDisplay.vue | 4 +- .../WalletPublicKeyDisplayTs.ts | 7 +- .../WalletSelectorField.vue | 6 +- .../WalletSelectorFieldTs.ts | 58 +- .../WalletSelectorPanel.vue | 4 +- .../WalletSelectorPanelTs.ts | 202 ++--- src/core/database/AppDatabase.ts | 48 - src/core/database/BaseStorageAdapter.ts | 162 ---- src/core/database/DatabaseModel.ts | 170 ---- src/core/database/DatabaseRelation.ts | 41 - src/core/database/DatabaseTable.ts | 118 --- src/core/database/IStorageAdapter.ts | 60 -- src/core/database/SimpleStorageAdapter.ts | 84 -- src/core/database/backends/IStorageBackend.ts | 6 + .../database/backends/LocalStorageBackend.ts | 16 +- .../database/backends/ObjectStorageBackend.ts | 18 +- .../database/backends/SimpleObjectStorage.ts | 88 ++ src/core/database/entities/AccountModel.ts | 36 + src/core/database/entities/AccountsModel.ts | 45 - src/core/database/entities/AccountsTable.ts | 55 -- .../entities/MosaicConfigurationModel.ts | 24 + src/core/database/entities/MosaicModel.ts | 56 ++ src/core/database/entities/MosaicsModel.ts | 82 -- src/core/database/entities/MosaicsTable.ts | 66 -- src/core/database/entities/NamespaceModel.ts | 59 ++ src/core/database/entities/NamespacesModel.ts | 86 -- src/core/database/entities/NamespacesTable.ts | 57 -- .../entities/NetworkConfigurationModel.ts | 32 + .../database/entities/NetworkCurrencyModel.ts | 62 ++ src/core/database/entities/NetworkModel.ts | 39 + src/core/database/entities/NodeModel.ts | 31 + src/core/database/entities/PeersModel.ts | 56 -- src/core/database/entities/PeersTable.ts | 57 -- src/core/database/entities/SettingsModel.ts | 52 +- src/core/database/entities/SettingsTable.ts | 46 - src/core/database/entities/WalletModel.ts | 70 ++ src/core/database/entities/WalletsModel.ts | 73 -- src/core/database/entities/WalletsTable.ts | 51 -- .../database/formatters/AbstractFormatter.ts | 89 -- src/core/database/formatters/JSONFormatter.ts | 131 --- .../migrations/accounts/AccountsMigrations.ts | 46 - .../migrations/endpoints/PeersMigrations.ts | 55 -- .../migrations/mosaics/MosaicsMigrations.ts | 143 --- .../namespaces/NamespacesMigrations.ts | 64 -- src/core/transactions/TransactionFactory.ts | 29 +- .../transactions/ViewHashLockTransaction.ts | 36 +- .../ViewMosaicDefinitionTransaction.ts | 3 +- .../ViewMosaicSupplyChangeTransaction.ts | 3 +- .../ViewNamespaceRegistrationTransaction.ts | 23 +- .../transactions/ViewTransferTransaction.ts | 43 +- .../transactions/ViewUnknownTransaction.ts | 3 +- src/core/utils/CacheKey.ts | 62 -- src/core/utils/Formatters.ts | 14 +- src/core/utils/NetworkConfigurationHelpers.ts | 136 +++ src/core/utils/ObservableHelpers.ts | 69 ++ src/core/utils/RESTDispatcher.ts | 32 +- src/core/utils/TimeHelpers.ts | 49 +- src/core/utils/URLHelpers.ts | 53 +- src/core/utils/URLInfo.ts | 26 + src/core/validation/CustomValidationRules.ts | 53 +- src/core/validation/ValidationRuleset.ts | 27 +- src/core/views/MosaicWithInfoView.ts | 2 +- src/language/en-US.json | 1 + src/repositories/AccountsRepository.ts | 194 ---- src/repositories/IStorable.ts | 53 -- src/repositories/ModelRepository.ts | 238 ----- src/repositories/MosaicsRepository.ts | 171 ---- src/repositories/NamespacesRepository.ts | 171 ---- src/repositories/PeersRepository.ts | 222 ----- src/repositories/SettingsRepository.ts | 171 ---- src/repositories/WalletsRepository.ts | 171 ---- src/router/AppRouter.ts | 15 +- src/router/routes.ts | 6 +- src/services/AESEncryptionService.ts | 56 +- src/services/AbstractService.ts | 1 - src/services/AccountService.ts | 93 +- .../AssetTableService/AssetTableService.ts | 54 +- .../AssetTableService/MosaicTableService.ts | 69 +- .../NamespaceTableService.ts | 87 +- src/services/CommunityService.ts | 38 +- src/services/DerivationService.ts | 63 +- src/services/MosaicService.ts | 576 +++++------- src/services/MultisigService.ts | 89 +- src/services/NamespaceService.ts | 262 ++---- src/services/NetworkService.ts | 154 ++++ src/services/NodeService.ts | 86 ++ src/services/PeerService.ts | 143 --- src/services/RESTService.ts | 34 +- src/services/RemoteAccountService.ts | 151 ---- src/services/RentalFeesService.ts | 50 -- src/services/SettingService.ts | 135 +-- src/services/TransactionService.ts | 157 ++-- src/services/WalletService.ts | 285 +++--- src/store/Account.ts | 70 +- src/store/AppInfo.ts | 148 ++-- src/store/Community.ts | 2 +- src/store/Database.ts | 61 +- src/store/Diagnostic.ts | 2 +- src/store/Market.ts | 8 +- src/store/Mosaic.ts | 308 ++----- src/store/Namespace.ts | 105 +-- src/store/Network.ts | 435 ++++----- src/store/Notification.ts | 8 +- src/store/Statistics.ts | 16 +- src/store/Temporary.ts | 2 +- src/store/Wallet.ts | 826 +++++------------- src/store/index.ts | 20 +- src/store/plugins/onPeerConnection.ts | 11 +- .../FormAccountCreationTs.ts | 71 +- .../FormAccountPasswordUpdateTs.ts | 50 +- .../FormAccountUnlock/FormAccountUnlockTs.ts | 44 +- .../FormAliasTransactionTs.ts | 90 +- src/views/forms/FormDefaults.ts | 132 --- ...ormExtendNamespaceDurationTransactionTs.ts | 61 +- .../FormGeneralSettings.vue | 4 +- .../FormGeneralSettingsTs.ts | 89 +- .../FormMosaicDefinitionTransactionTs.ts | 31 +- .../FormMosaicSupplyChangeTransaction.vue | 2 +- .../FormMosaicSupplyChangeTransactionTs.ts | 44 +- ...ultisigAccountModificationTransactionTs.ts | 120 ++- .../FormNamespaceRegistrationTransactionTs.ts | 69 +- .../FormSubWalletCreationTs.ts | 135 ++- .../FormTransactionBase.ts | 205 +---- .../FormTransferTransactionTs.ts | 127 ++- .../MosaicInputsManager.ts | 22 +- .../FormWalletNameUpdateTs.ts | 63 +- src/views/layout/PageLayout/PageLayout.vue | 2 +- src/views/layout/PageLayout/PageLayoutTs.ts | 33 +- .../ModalDebugConsole/ModalDebugConsole.vue | 5 +- .../ModalFormAccountUnlockTs.ts | 29 +- .../ModalFormSubWalletCreationTs.ts | 10 +- .../ModalFormWalletNameUpdateTs.ts | 5 +- .../ModalMnemonicDisplay.vue | 3 +- .../ModalMnemonicDisplayTs.ts | 10 +- .../ModalMnemonicExport.vue | 3 +- .../ModalMnemonicExportTs.ts | 42 +- .../ModalTransactionConfirmation.vue | 2 +- .../ModalTransactionConfirmationTs.ts | 61 +- .../ModalTransactionCosignatureTs.ts | 23 +- .../ModalTransactionDetailsTs.ts | 8 +- src/views/pages/accounts/LoginPageTs.ts | 124 ++- .../create-account/CreateAccountTs.ts | 2 +- .../create-account/finalize/FinalizeTs.ts | 69 +- .../generate-mnemonic/GenerateMnemonicTs.ts | 31 +- .../show-mnemonic/ShowMnemonicTs.ts | 7 +- .../verify-mnemonic/VerifyMnemonicTs.ts | 24 +- .../import-account/ImportAccountTs.ts | 2 +- .../import-mnemonic/ImportMnemonicTs.ts | 59 +- .../import-strategy/ImportStrategyTs.ts | 11 +- .../wallet-selection/WalletSelectionTs.ts | 128 +-- .../AssetDashboardWrap/AssetDashboardWrap.vue | 3 +- src/views/pages/dashboard/Dashboard.vue | 2 +- src/views/pages/dashboard/DashboardTs.ts | 1 - .../harvesting/DashboardHarvestingPageTs.ts | 15 +- .../dashboard/home/DashboardHomePage.vue | 2 +- .../dashboard/home/DashboardHomePageTs.ts | 27 +- .../invoice/DashboardInvoicePageTs.ts | 8 +- .../transfer/DashboardTransferPageTs.ts | 23 +- .../MultisigDashboardPage.vue | 3 +- .../pages/settings/AboutPage/AboutPage.vue | 24 +- .../WalletBackupPage/WalletBackupPageTs.ts | 8 +- .../WalletDetailsPage/WalletDetailsPage.vue | 6 +- .../WalletDetailsPage/WalletDetailsPageTs.ts | 41 +- .../WalletHarvestingPage.vue | 1 + .../WalletHarvestingPageTs.ts | 22 +- src/views/pages/wallets/WalletsTs.ts | 39 +- 250 files changed, 4480 insertions(+), 10034 deletions(-) delete mode 100644 __mocks__/Database.ts delete mode 100644 __tests__/database/BaseStorageAdapter.spec.ts delete mode 100644 __tests__/database/DatabaseModel.spec.ts delete mode 100644 __tests__/database/JSONFormatter.spec.ts delete mode 100644 __tests__/database/SimpleStorageAdapter.spec.ts delete mode 100644 __tests__/database/backends/ObjectStorageBackend.spec.ts rename __tests__/database/{DatabaseTable.spec.ts => backends/SimpleObjectStorage.spec.ts} (50%) create mode 100644 __tests__/services/MosaicModel.spec.ts delete mode 100644 __tests__/store/Database.spec.ts create mode 100644 __tests__/utils/TimeHelpers.spec.ts delete mode 100644 src/core/database/AppDatabase.ts delete mode 100644 src/core/database/BaseStorageAdapter.ts delete mode 100644 src/core/database/DatabaseModel.ts delete mode 100644 src/core/database/DatabaseRelation.ts delete mode 100644 src/core/database/DatabaseTable.ts delete mode 100644 src/core/database/IStorageAdapter.ts delete mode 100644 src/core/database/SimpleStorageAdapter.ts create mode 100644 src/core/database/backends/SimpleObjectStorage.ts create mode 100644 src/core/database/entities/AccountModel.ts delete mode 100644 src/core/database/entities/AccountsModel.ts delete mode 100644 src/core/database/entities/AccountsTable.ts create mode 100644 src/core/database/entities/MosaicConfigurationModel.ts create mode 100644 src/core/database/entities/MosaicModel.ts delete mode 100644 src/core/database/entities/MosaicsModel.ts delete mode 100644 src/core/database/entities/MosaicsTable.ts create mode 100644 src/core/database/entities/NamespaceModel.ts delete mode 100644 src/core/database/entities/NamespacesModel.ts delete mode 100644 src/core/database/entities/NamespacesTable.ts create mode 100644 src/core/database/entities/NetworkConfigurationModel.ts create mode 100644 src/core/database/entities/NetworkCurrencyModel.ts create mode 100644 src/core/database/entities/NetworkModel.ts create mode 100644 src/core/database/entities/NodeModel.ts delete mode 100644 src/core/database/entities/PeersModel.ts delete mode 100644 src/core/database/entities/PeersTable.ts delete mode 100644 src/core/database/entities/SettingsTable.ts create mode 100644 src/core/database/entities/WalletModel.ts delete mode 100644 src/core/database/entities/WalletsModel.ts delete mode 100644 src/core/database/entities/WalletsTable.ts delete mode 100644 src/core/database/formatters/AbstractFormatter.ts delete mode 100644 src/core/database/formatters/JSONFormatter.ts delete mode 100644 src/core/database/migrations/accounts/AccountsMigrations.ts delete mode 100644 src/core/database/migrations/endpoints/PeersMigrations.ts delete mode 100644 src/core/database/migrations/mosaics/MosaicsMigrations.ts delete mode 100644 src/core/database/migrations/namespaces/NamespacesMigrations.ts delete mode 100644 src/core/utils/CacheKey.ts create mode 100644 src/core/utils/NetworkConfigurationHelpers.ts create mode 100644 src/core/utils/ObservableHelpers.ts create mode 100644 src/core/utils/URLInfo.ts delete mode 100644 src/repositories/AccountsRepository.ts delete mode 100644 src/repositories/IStorable.ts delete mode 100644 src/repositories/ModelRepository.ts delete mode 100644 src/repositories/MosaicsRepository.ts delete mode 100644 src/repositories/NamespacesRepository.ts delete mode 100644 src/repositories/PeersRepository.ts delete mode 100644 src/repositories/SettingsRepository.ts delete mode 100644 src/repositories/WalletsRepository.ts create mode 100644 src/services/NetworkService.ts create mode 100644 src/services/NodeService.ts delete mode 100644 src/services/PeerService.ts delete mode 100644 src/services/RemoteAccountService.ts delete mode 100644 src/services/RentalFeesService.ts delete mode 100644 src/views/forms/FormDefaults.ts diff --git a/__mocks__/Database.ts b/__mocks__/Database.ts deleted file mode 100644 index 2e07e0b9d..000000000 --- a/__mocks__/Database.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {ObjectStorageBackend} from '@/core/database/backends/ObjectStorageBackend' -import {SimpleStorageAdapter} from '@/core/database/SimpleStorageAdapter' - -/// region mocks -export class FakeAdapter extends SimpleStorageAdapter {} -export class FakeModel extends DatabaseModel {} -export class FakeTable extends DatabaseTable { - public createModel(values: Map): DatabaseModel { - return new FakeModel(['id'], values) - } - - public getMigrations(): DatabaseMigration[] { - return [] - } -} -/// end-region mocks - -/// region helpers -/** - * Mock database adapter - * @return {FakeAdapter} - */ -export const getAdapter = (data: any = undefined): FakeAdapter => { - const adapter = new FakeAdapter(new ObjectStorageBackend(data || { - accounts: '{' - + '"1234":{"id":"1234","name":"one","version":0},' - + '"5678":{"id":"5678","name":"two","version":0}' - + '}', - wallets: '{' - + '"abcd":{"id":"abcd","name":"w_one","address":"w_addr","version":0},' - + '"efgh":{"id":"efgh","name":"w_two","address":"w_addr2","version":0},' - + '"ijkl":{"id":"ijkl","name":"w_thr","address":"w_addr3","version":0}' - + '}', - crashes: '[{item: "this is a corrupted table data format"}]' - })) - adapter.setSchemas(new Map([ - ['accounts', new FakeTable('accounts', ['id', 'name'])], - ['wallets', new FakeTable('wallets', ['id', 'name', 'address'])], - ['endpoints', new FakeTable('endpoints', ['id', 'host', 'port'])], - ['aliased', new FakeTable('crashes', ['id', 'time', 'error'])], - ])) - - return adapter -} - -/** - * Mock database model - * @return {FakeModel} - */ -export const getFakeModel = (identifier?: string, fields?: any[]): FakeModel => { - return new FakeModel(['id'], new Map(fields ? fields.concat([['id', identifier]]) : [ - ['fake_field_1', 'fake_value'], - ['fake_field_2', 'fake_value2'], - ['fake_field_3', 'fake_value3'], - ['id', identifier], - ])) -} -/// end-region helpers diff --git a/__tests__/components/MosaicInputManager.spec.ts b/__tests__/components/MosaicInputManager.spec.ts index fc693f1ea..0b5e3a8f9 100644 --- a/__tests__/components/MosaicInputManager.spec.ts +++ b/__tests__/components/MosaicInputManager.spec.ts @@ -1,23 +1,23 @@ -import {Mosaic, MosaicId, UInt64} from 'symbol-sdk' import {MosaicInputsManager} from '@/views/forms/FormTransferTransaction/MosaicInputsManager.ts' +import {MosaicModel} from '@/core/database/entities/MosaicModel' -export const mockMosaic1 = new Mosaic( - new MosaicId('619CE7E50DB644DE'), - UInt64.fromUint(1), -) +export const mockMosaic1: MosaicModel = { + mosaicIdHex: '619CE7E50DB644DE', + balance: 1, +} as MosaicModel -export const mockMosaic2 = new Mosaic( - new MosaicId('28A59CC8B0C9E4DD'), - UInt64.fromUint(1), -) +export const mockMosaic2: MosaicModel = { + mosaicIdHex: '28A59CC8B0C9E4DD', + balance: 1, +} as MosaicModel -export const mockMosaic3 = new Mosaic( - new MosaicId('2D58F9BF5F8C014D'), - UInt64.fromUint(1), -) +export const mockMosaic3: MosaicModel = { + mosaicIdHex: '2D58F9BF5F8C014D', + balance: 1, +} as MosaicModel const mockMosaics = [ mockMosaic1, mockMosaic2, mockMosaic3 ] -const mockMosaicHexIds = mockMosaics.map(({id}) => id.toHex()) +const mockMosaicHexIds = mockMosaics.map(({mosaicIdHex}) => mosaicIdHex) describe('components/MosaicInputManager', () => { describe('initialize() should', () => { diff --git a/__tests__/components/WalletSelectorField.spec.ts b/__tests__/components/WalletSelectorField.spec.ts index 7d8ff1d2b..f54003216 100644 --- a/__tests__/components/WalletSelectorField.spec.ts +++ b/__tests__/components/WalletSelectorField.spec.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,13 +14,11 @@ * limitations under the License. */ // internal dependencies -import {getFakeModel, getAdapter} from '@MOCKS/Database' import {getComponent} from '@MOCKS/Components' import WalletStore from '@/store/Wallet' - // @ts-ignore import WalletSelectorField from '@/components/WalletSelectorField/WalletSelectorField.vue' -import {WalletService} from '@/services/WalletService' +import {WalletModel} from '@/core/database/entities/WalletModel' describe('components/WalletSelectorField', () => { describe('getter for property "currentWalletIdentifier" should', () => { @@ -41,9 +39,9 @@ describe('components/WalletSelectorField', () => { test('return wallet identifier given value', () => { // prepare - const wallet = getFakeModel('5678') + const wallet = {id: '5678'} as WalletModel const wrapper = getComponent(WalletSelectorField, {wallet: WalletStore}, {}, { - value: wallet.getIdentifier(), + value: wallet.id, }) const component = (wrapper.vm as WalletSelectorField) @@ -68,20 +66,6 @@ describe('components/WalletSelectorField', () => { expect(wrapper.vm.$store.dispatch).not.toHaveBeenCalled() }) - test('dispatch "notification/ADD_ERROR" given invalid identifier', () => { - // prepare - const wrapper = getComponent(WalletSelectorField, {wallet: WalletStore}, {}) - const service = new WalletService(wrapper.vm.$store, getAdapter()) - const component = (wrapper.vm as WalletSelectorField) - component.service = service - - // act - component.currentWalletIdentifier = '1234' // wallet identifier does not exist - expect(component.$store.dispatch).toHaveBeenCalledWith( - 'notification/ADD_ERROR', - 'Wallet with identifier \'1234\' does not exist.', - ) - }) }) describe('getter for property "currentWallets" should', () => { diff --git a/__tests__/database/BaseStorageAdapter.spec.ts b/__tests__/database/BaseStorageAdapter.spec.ts deleted file mode 100644 index a4968d618..000000000 --- a/__tests__/database/BaseStorageAdapter.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - FakeAdapter, - FakeTable, - FakeModel, - getAdapter, -} from '@MOCKS/Database' -import {JSONFormatter} from '@/core/database/formatters/JSONFormatter' - -describe('database/BaseStorageAdapter ==>', () => { - describe('constructor() should', () => { - test('set default backend given no parameters', () => { - const adapter = new FakeAdapter() - expect(adapter).toBeDefined() - expect(adapter.storage).toBeDefined() - }) - - test('set default formatter given no parameters', () => { - const adapter = new FakeAdapter() - expect(adapter).toBeDefined() - expect(adapter.formatter).toBeDefined() - expect(adapter.formatter).toBeInstanceOf(JSONFormatter) - }) - }) - - describe('setSchemas() should', () => { - test('map schemas to names', () => { - const adapter = getAdapter() - - expect(adapter.schemas.has('accounts')).toBe(true) - expect(adapter.schemas.has('wallets')).toBe(true) - expect(adapter.schemas.has('endpoints')).toBe(true) - expect(adapter.schemas.has('aliased')).toBe(true) - expect(adapter.schemas.get('accounts')).toBeInstanceOf(FakeTable) - expect(adapter.schemas.get('accounts').tableName).toBe('accounts') - expect(adapter.schemas.get('wallets')).toBeInstanceOf(FakeTable) - expect(adapter.schemas.get('wallets').tableName).toBe('wallets') - expect(adapter.schemas.get('endpoints')).toBeInstanceOf(FakeTable) - expect(adapter.schemas.get('endpoints').tableName).toBe('endpoints') - - // test schema aliases too - expect(adapter.schemas.get('aliased')).toBeInstanceOf(FakeTable) - expect(adapter.schemas.get('aliased').tableName).toBe('crashes') - }) - }) - - describe('getSchema() should', () => { - test('throw given unregistered schema', () => { - const adapter = getAdapter() - expect(() => { - adapter.write('unknown_table', new Map()) - }).toThrow('Schema with identifier \'unknown_table\' is not registered.') - }) - }) - - describe('read() should', () => { - test('throw given corrupted/invalid JSON data', () => { - const adapter = getAdapter() - expect(() => { - adapter.read('crashes') - }).toThrow('Data stored for schema \'crashes\' does not comply with JSONFormatter derivate.') - }) - - test('return empty map given empty table', () => { - const adapter = getAdapter() - const peers = adapter.read('endpoints') - expect(peers.size).toBe(0) - }) - - test('return correct rows count given populated table', () => { - const adapter = getAdapter() - const accounts = adapter.read('accounts') - const wallets = adapter.read('wallets') - expect(accounts.size).toBe(2) - expect(wallets.size).toBe(3) - }) - - test('return correct values given populated table', () => { - const adapter = getAdapter() - const accounts = adapter.read('accounts') - const wallets = adapter.read('wallets') - - // first table - expect(accounts.size).toBe(2) - expect(wallets.size).toBe(3) - expect(accounts.has('1234')).toBe(true) - expect(accounts.has('5678')).toBe(true) - expect(accounts.get('1234')).toBeInstanceOf(FakeModel) - expect(accounts.get('1234').values.has('id')).toBe(true) - expect(accounts.get('1234').values.get('id')).toBe('1234') - expect(accounts.get('1234').values.has('name')).toBe(true) - expect(accounts.get('1234').values.get('name')).toBe('one') - // second table - expect(wallets.has('abcd')).toBe(true) - expect(wallets.has('efgh')).toBe(true) - expect(wallets.has('ijkl')).toBe(true) - expect(wallets.get('ijkl')).toBeInstanceOf(FakeModel) - expect(wallets.get('ijkl').values.has('id')).toBe(true) - expect(wallets.get('ijkl').values.get('id')).toBe('ijkl') - expect(wallets.get('ijkl').values.has('name')).toBe(true) - expect(wallets.get('ijkl').values.get('name')).toBe('w_thr') - expect(wallets.get('ijkl').values.has('address')).toBe(true) - expect(wallets.get('ijkl').values.get('address')).toBe('w_addr3') - }) - }) - - describe('write() should', () => { - test('write empty table given no values', () => { - const adapter = getAdapter() - const entries = adapter.write('endpoints', new Map()) - expect(entries).toBe(0) - }) - - test('update storage during write operation', () => { - const adapter = getAdapter() - const entries = adapter.write('endpoints', new Map([ - [ 'http://localhost:3000', new FakeModel(['host'], new Map([ - [ 'host', 'http://localhost:3000' ], - [ 'port', '3000' ], - ])) ], - [ 'http://localhost:3001', new FakeModel(['host'], new Map([ - [ 'host', 'http://localhost:3001' ], - [ 'port', '3001' ], - ])) ], - ])) - expect(entries).toBe(2) - - // re-read storage and check mapped rows - const mapped = adapter.read('endpoints') - const keys = [...mapped.keys()] - expect(mapped.size).toBe(2) - expect(mapped.get(keys[0])).toBeInstanceOf(FakeModel) - expect(mapped.get(keys[0]).values.has('id')).toBe(true) - expect(mapped.get(keys[0]).values.has('host')).toBe(true) - expect(mapped.get(keys[0]).values.has('port')).toBe(true) - expect(mapped.get(keys[0]).values.get('host')).toBe('http://localhost:3000') - expect(mapped.get(keys[0]).values.get('port')).toBe('3000') - expect(mapped.get(keys[1])).toBeInstanceOf(FakeModel) - expect(mapped.get(keys[1]).values.has('id')).toBe(true) - expect(mapped.get(keys[1]).values.has('host')).toBe(true) - expect(mapped.get(keys[1]).values.has('port')).toBe(true) - expect(mapped.get(keys[1]).values.get('host')).toBe('http://localhost:3001') - expect(mapped.get(keys[1]).values.get('port')).toBe('3001') - }) - }) -}) diff --git a/__tests__/database/DatabaseModel.spec.ts b/__tests__/database/DatabaseModel.spec.ts deleted file mode 100644 index 13d6431a2..000000000 --- a/__tests__/database/DatabaseModel.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {FakeModel} from '@MOCKS/Database' - -describe('database/DatabaseModel', () => { - describe('constructor() should', () => { - test('set primary keys given array', () => { - const model = new FakeModel([ 'pk1', 'pk2' ]) - expect(model).toBeDefined() - expect(model.primaryKeys).toMatchObject([ 'pk1', 'pk2' ]) - }) - - test('set values given map', () => { - const model = new FakeModel(['pk1'], new Map([ - [ 'pk1', 'id_value' ], - [ 'db_field2', true ], - ])) - - expect(model.values.get('pk1')).toBe('id_value') - expect(model.values.get('db_field2')).toBe(true) - }) - - test('set identifier given primary key value', () => { - const model = new FakeModel(['pk1'], new Map([ - [ 'pk1', 'id_value' ], - ])) - - expect(model.identifier).toBe('id_value') - }) - - test('auto-generate identifier given \'id\' primary key', () => { - const model = new FakeModel(['id'], new Map([ - [ 'other', 'field_value' ], - ])) - - expect(model.identifier).toBeDefined() - expect(model.identifier.length).toBe(16) - }) - }) - - describe('generatedIdentifier() should', () => { - test('generate distinct identifier values always', () => { - const model1 = new FakeModel(['id'], new Map([])) - const model2 = new FakeModel(['id'], new Map([])) - const model3 = new FakeModel(['id'], new Map([])) - const model4 = new FakeModel(['id'], new Map([])) - - const id1 = model1.getIdentifier() - const id2 = model2.getIdentifier() - const id3 = model3.getIdentifier() - const id4 = model4.getIdentifier() - - expect(id1 === id2).toBe(false) - expect(id1 === id3).toBe(false) - expect(id1 === id4).toBe(false) - expect(id2 === id3).toBe(false) - expect(id2 === id4).toBe(false) - expect(id3 === id4).toBe(false) - }) - }) - - describe('hasIdentifier() should', () => { - test('return false given custom primary key and no value', () => { - const model = new FakeModel(['pk1'], new Map()) - expect(model.hasIdentifier()).toBe(false) - }) - - test('return true given custom primary key and value', () => { - const model = new FakeModel(['pk1'], new Map([ - [ 'pk1', 'id_value' ], - ])) - expect(model.hasIdentifier()).toBe(true) - }) - - test('return true given \'id\' primary key and no value', () => { - const model = new FakeModel(['id'], new Map()) - expect(model.hasIdentifier()).toBe(true) - }) - }) - - describe('getIdentifier() should', () => { - test('get identifier value given primary key value', () => { - const model = new FakeModel(['pk1'], new Map([ - [ 'pk1', 'id_value' ], - ])) - - expect(model).toBeDefined() - expect(model.getIdentifier()).toBe('id_value') - }) - - test('get dash-separated identifier value given multiple primary key values', () => { - const model = new FakeModel([ 'pk1', 'pk2' ], new Map([ - [ 'pk1', 'id_value_part1' ], - [ 'pk2', 'id_value_part2' ], - ])) - - expect(model).toBeDefined() - expect(model.getIdentifier()).toBe('id_value_part1-id_value_part2') - }) - - test('get pre-defined identifier value given \'id\' primary key', () => { - const model = new FakeModel(['id'], new Map([ - [ 'id', 'id_value' ], - ])) - - expect(model).toBeDefined() - expect(model.getIdentifier()).toBe('id_value') - }) - - test('get auto-generated identifier value given \'id\' primary key and no value', () => { - const model = new FakeModel(['id'], new Map([])) - - expect(model).toBeDefined() - expect(model.getIdentifier()).toBeDefined() - expect(model.getIdentifier().length).toBe(16) - }) - }) -}) diff --git a/__tests__/database/JSONFormatter.spec.ts b/__tests__/database/JSONFormatter.spec.ts deleted file mode 100644 index 2e4069ed9..000000000 --- a/__tests__/database/JSONFormatter.spec.ts +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - FakeTable, - FakeModel, -} from '@MOCKS/Database' -import {JSONFormatter} from '@/core/database/formatters/JSONFormatter' - -// HELPERS -const getFormatter = (): JSONFormatter => { - const formatter = new JSONFormatter() - formatter.setSchema(new FakeTable('db_table', [ - 'id', - 'db_column1', - 'db_column2', - 'db_column3', - ])) - - return formatter -} - -describe('database/AbstractFormatter ==>', () => { - describe('setSchema() should', () => { - test('update $schema property', () => { - const formatter = new JSONFormatter() - formatter.setSchema(new FakeTable('table', ['col1'])) - - expect(formatter.getSchema()).toBeDefined() - expect(formatter.getSchema().tableName).toBe('table') - expect(formatter.getSchema().columns).toMatchObject(['col1']) - }) - }) -}) - -describe('database/JSONFormatter ==>', () => { - describe('format() should', () => { - test('return empty JSON object given no values', () => { - // prepare - const formatter = getFormatter() - - // act - const json = formatter.format(new FakeTable('table', ['col1']), new Map()) - expect(json).toBe('{}') - }) - - test('return correctly formatted JSON from single entry', () => { - // prepare - const formatter = getFormatter() - - // use "id" auto generation - const model = new FakeModel(['id'], new Map([ - [ 'db_column1', 'value1' ], - [ 'db_column2', 'value2' ], - [ 'db_column3', 'value3' ], - ])) - - // act - const expected = '{"' + model.getIdentifier() + '":{' - + '"db_column1":"value1",' - + '"db_column2":"value2",' - + '"db_column3":"value3",' - + '"id":"' + model.getIdentifier() + '",' - + '"version":0' - + '}}' - const json = formatter.format( - new FakeTable('table', ['col1']), - new Map([[ model.getIdentifier(), model ]]), - ) - expect(json).toBe(expected) - - // make sure JSON is valid - const parsed = JSON.parse(json) - expect(parsed).toMatchObject(JSON.parse(expected)) - }) - - test('return entries mapped by identifier in JSON', () => { - // prepare - const formatter = getFormatter() - - const model1 = new FakeModel(['id'], new Map([ - [ 'db_column1', 'value1' ], - [ 'db_column2', 'value2' ], - [ 'db_column3', 'value3' ], - ])) - - const model2 = new FakeModel(['id'], new Map([ - [ 'db_column1', 'value1' ], - [ 'db_column2', 'value2' ], - [ 'db_column3', 'value3' ], - ])) - - // act - const json = formatter.format( - new FakeTable('table', ['col1']), - new Map([ - [ model1.getIdentifier(), model1 ], - [ model2.getIdentifier(), model2 ], - ])) - - const parsed = JSON.parse(json) - expect(parsed[model1.getIdentifier()]).toBeDefined() - expect(parsed[model2.getIdentifier()]).toBeDefined() - }) - - test('return entries values in JSON', () => { - // prepare - const formatter = getFormatter() - - const model1 = new FakeModel(['id'], new Map([ - [ 'db_column1', 'value1' ], - [ 'db_column2', 'value2' ], - [ 'db_column3', 'value3' ], - ])) - - const model2 = new FakeModel(['id'], new Map([ - [ 'db_column1', 'value1_2' ], - [ 'db_column2', 'value2_2' ], - [ 'db_column3', 'value3_2' ], - ])) - - // act - const json = formatter.format( - new FakeTable('table', ['col1']), - new Map([ - [ model1.getIdentifier(), model1 ], - [ model2.getIdentifier(), model2 ], - ])) - - const parsed = JSON.parse(json) - expect(Object.keys(parsed).length).toBe(2) - - // model 1 - expect(Object.keys(parsed[model1.getIdentifier()]).length).toBe(5) - expect(parsed[model1.getIdentifier()].id).toBe(model1.getIdentifier()) - expect(parsed[model1.getIdentifier()].db_column1).toBe('value1') - expect(parsed[model1.getIdentifier()].db_column2).toBe('value2') - expect(parsed[model1.getIdentifier()].db_column3).toBe('value3') - expect(parsed[model1.getIdentifier()].version).toBe(0) - - // model 2 - expect(Object.keys(parsed[model2.getIdentifier()]).length).toBe(5) - expect(parsed[model2.getIdentifier()].id).toBe(model2.getIdentifier()) - expect(parsed[model2.getIdentifier()].db_column1).toBe('value1_2') - expect(parsed[model2.getIdentifier()].db_column2).toBe('value2_2') - expect(parsed[model2.getIdentifier()].db_column3).toBe('value3_2') - expect(parsed[model1.getIdentifier()].version).toBe(0) - }) - }) - - describe('parse() should', () => { - test('return empty map given no values', () => { - // prepare - const formatter = getFormatter() - const json = '{}' - - // act - const map = formatter.parse(new FakeTable('table', ['col1']), json) - expect(map.size).toBe(0) - }) - - test('return model mapped by identifier given values', () => { - // prepare - const formatter = getFormatter() - - const json = '{"123456789":{' - + '"db_column1":"value1",' - + '"db_column2":"value2",' - + '"db_column3":"value3",' - + '"id":"123456789"' - + '}}' - - // act - const map = formatter.parse( - new FakeTable('table', [ 'db_column1', 'db_column2', 'db_column3' ]), - json, - ) - expect(map.size).toBe(1) - expect(map.has('123456789')).toBe(true) - expect(map.get('123456789')).toBeInstanceOf(FakeModel) - }) - - test('return multiple models mapped by identifier given multiple rows', () => { - // prepare - const formatter = getFormatter() - - const json = '{"1234":{' - + '"db_column1":"value1",' - + '"db_column2":"value2",' - + '"db_column3":"value3",' - + '"id":"1234"' - + '},"5678":{' - + '"db_column1":"value1_2",' - + '"db_column2":"value2_2",' - + '"db_column3":"value3_2",' - + '"id":"5678"' - + '}}' - - // act - const map = formatter.parse( - new FakeTable('table', [ 'db_column1', 'db_column2', 'db_column3' ]), - json, - ) - expect(map.size).toBe(2) - expect(map.has('1234')).toBe(true) - expect(map.get('1234')).toBeInstanceOf(FakeModel) - expect(map.has('5678')).toBe(true) - expect(map.get('5678')).toBeInstanceOf(FakeModel) - }) - - test('return parsed models with values given multiple rows', () => { - // prepare - const formatter = getFormatter() - - const json = '{"1234":{' - + '"db_column1":"value1",' - + '"db_column2":"value2",' - + '"db_column3":"value3",' - + '"id":"1234"' - + '},"5678":{' - + '"db_column1":"value1_2",' - + '"db_column2":"value2_2",' - + '"db_column3":"value3_2",' - + '"id":"5678"' - + '}}' - - // act - const map = formatter.parse( - new FakeTable('table', [ 'db_column1', 'db_column2', 'db_column3' ]), - json, - ) - const model1 = map.get('1234') - const model2 = map.get('5678') - - // assert - expect(model1).toBeDefined() - expect(model2).toBeDefined() - expect(model1.getIdentifier()).toBe('1234') - expect(model1.values.get('id')).toBe('1234') - expect(model1.values.get('db_column1')).toBe('value1') - expect(model1.values.get('db_column2')).toBe('value2') - expect(model1.values.get('db_column3')).toBe('value3') - expect(model2.getIdentifier()).toBe('5678') - expect(model2.values.get('id')).toBe('5678') - expect(model2.values.get('db_column1')).toBe('value1_2') - expect(model2.values.get('db_column2')).toBe('value2_2') - expect(model2.values.get('db_column3')).toBe('value3_2') - }) - }) -}) diff --git a/__tests__/database/SimpleStorageAdapter.spec.ts b/__tests__/database/SimpleStorageAdapter.spec.ts deleted file mode 100644 index e8f0c30cb..000000000 --- a/__tests__/database/SimpleStorageAdapter.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {getAdapter} from '@MOCKS/Database' -import {AESEncryptionService} from '@/services/AESEncryptionService' -import {Password} from 'symbol-sdk' - -describe('database/BaseStorageAdapter ==>', () => { - describe('getSessionId() should', () => { - test('auto-generate session id given no value', () => { - const adapter = getAdapter() - const sessionId = adapter.getSessionId() - expect(sessionId).toBeDefined() - expect(sessionId.length).toBe(64) - }) - - test('read stored session id given storage', () => { - const adapter = getAdapter({sessionId: '123456789'}) - const sessionId = adapter.getSessionId() - expect(sessionId).toBe('123456789') - }) - }) - - describe('getSaltForSession() should', () => { - test('auto-generate salt given no value', () => { - const adapter = getAdapter() - const salt = adapter.getSaltForSession() - expect(salt).toBeDefined() - expect(salt.length).toBe(64) - }) - - test('encrypt access salt given session id', () => { - const adapter = getAdapter({ - sessionId: 'password', - }) - - const salt = adapter.getSaltForSession() - const encrypted = adapter.storage.getItem('accessSalt') - const decrypted = AESEncryptionService.decrypt(encrypted, new Password('password')) - expect(decrypted.length).toBe(64) - expect(decrypted).toBe(salt) - }) - - test('read encrypted salt given storage', () => { - const adapter = getAdapter({ - sessionId: 'password', - accessSalt: '9c3afe1b658403d7522886cda510a3714c389ce697128ab8d3877bbbb53c2ecdY+QgfP/KHmUl+wk7rPwmEQ==', - }) - - const salt = adapter.getSaltForSession() - expect(salt).toBe('987654321') - }) - }) -}) diff --git a/__tests__/database/backends/ObjectStorageBackend.spec.ts b/__tests__/database/backends/ObjectStorageBackend.spec.ts deleted file mode 100644 index 6f76b1a26..000000000 --- a/__tests__/database/backends/ObjectStorageBackend.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {ObjectStorageBackend} from '@/core/database/backends/ObjectStorageBackend' - -const getBackend = (data?: any) => new ObjectStorageBackend(data) - -describe('database/ObjectStorageBackend ==>', () => { - describe('constructor() should', () => { - test('create instance given no data', () => { - const backend = getBackend() - expect(backend).toBeDefined() - }) - - test('create instances given any data', () => { - const b1 = getBackend(false) - const b2 = getBackend([ 1, 2, 3 ]) - const b3 = getBackend({field: 'value'}) - const b4 = getBackend('text') - - expect(b1).toBeDefined() - expect(b2).toBeDefined() - expect(b3).toBeDefined() - expect(b4).toBeDefined() - }) - }) - - describe('length property should', () => { - test('contain 0 given no data', () => { - const backend = getBackend() - expect(backend.length).toBe(0) - }) - - test('contain correct item lengths', () => { - const b1 = getBackend({item: 'value'}) - const b2 = getBackend({item: 'value', item2: 'value'}) - const b3 = getBackend({item: 'value', item2: 'value', item3: false}) - expect(b1.length).toBe(1) - expect(b2.length).toBe(2) - expect(b3.length).toBe(3) - }) - }) - - describe('isAvailable() should', () => { - test('always return true', () => { - const backend = getBackend() - expect(backend.isAvailable()).toBe(true) - }) - }) - - describe('getItem() should', () => { - test('return null given unknown item name', () => { - const backend = getBackend() - expect(backend.getItem('unknown')).toBe(null) - }) - - test('return value given known item names', () => { - const backend = getBackend({ - boolField: true, - numberField: 1, - stringField: 'value', - arrayField: [ 1, 2, 3 ], - objectField: {item: 'value'}, - }) - expect(backend.length).toBe(5) - expect(backend.getItem('boolField')).toBe(true) - expect(backend.getItem('numberField')).toBe(1) - expect(backend.getItem('stringField')).toBe('value') - expect(backend.getItem('arrayField')).toMatchObject([ 1, 2, 3 ]) - expect(backend.getItem('objectField')).toMatchObject({item: 'value'}) - }) - }) - - describe('setItem() should', () => { - test('set values in storage', () => { - const backend = getBackend() - backend.setItem('boolField', true) - backend.setItem('numberField', 1) - backend.setItem('stringField', 'value') - backend.setItem('arrayField', [ 1, 2, 3 ]) - backend.setItem('objectField', {item: 'value'}) - - expect(backend.length).toBe(5) - expect(backend.getItem('boolField')).toBe(true) - expect(backend.getItem('numberField')).toBe(1) - expect(backend.getItem('stringField')).toBe('value') - expect(backend.getItem('arrayField')).toMatchObject([ 1, 2, 3 ]) - expect(backend.getItem('objectField')).toMatchObject({item: 'value'}) - }) - - test('set new value given known item name', () => { - const backend = getBackend({item: 'value'}) - backend.setItem('item', 'newValue') - - expect(backend.getItem('item')).toBe('newValue') - }) - }) -}) diff --git a/__tests__/database/DatabaseTable.spec.ts b/__tests__/database/backends/SimpleObjectStorage.spec.ts similarity index 50% rename from __tests__/database/DatabaseTable.spec.ts rename to __tests__/database/backends/SimpleObjectStorage.spec.ts index 1e64fa03e..1f46ae709 100644 --- a/__tests__/database/DatabaseTable.spec.ts +++ b/__tests__/database/backends/SimpleObjectStorage.spec.ts @@ -1,32 +1,40 @@ -/** +/* * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {FakeTable} from '@MOCKS/Database' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' -describe('database/DatabaseTable ==>', () => { +const getStorage = () => new SimpleObjectStorage('SomeStorageKey') + +describe('database/SimpleObjectStorage.spec ==>', () => { describe('constructor() should', () => { - test('set table name', () => { - const table = new FakeTable('table') - expect(table).toBeDefined() - expect(table.tableName).toBe('table') + test('create instance given no data', () => { + const storage = getStorage() + expect(storage).toBeDefined() }) - test('set columns names', () => { - const table = new FakeTable('table', [ 'col1', 'col2', 'col3' ]) - expect(table).toBeDefined() - expect(table.columns).toMatchObject([ 'col1', 'col2', 'col3' ]) + test('Get/Set/Delete', () => { + const storage = getStorage() + expect(storage.get()).toBeUndefined() + storage.set(123) + expect(storage.get()).toBe(123) + storage.set(456) + expect(storage.get()).toBe(456) + storage.remove() + expect(storage.get()).toBeUndefined() }) }) + + }) diff --git a/__tests__/services/AccountService.spec.ts b/__tests__/services/AccountService.spec.ts index c82f46aab..aaac3ad39 100644 --- a/__tests__/services/AccountService.spec.ts +++ b/__tests__/services/AccountService.spec.ts @@ -14,16 +14,15 @@ * limitations under the License. */ import {AccountService} from '@/services/AccountService' -import { Password } from 'symbol-sdk' +import {Password} from 'symbol-sdk' // prepare all -const service = new AccountService() describe('services/AccountService', () => { describe('getPasswordHash() should', () => { test('create 64 bytes hash of password', () => { // act - const hash = service.getPasswordHash(new Password('1234567a')) + const hash = AccountService.getPasswordHash(new Password('1234567a')) // assert expect(hash).toBeDefined() diff --git a/__tests__/services/DerivationService.spec.ts b/__tests__/services/DerivationService.spec.ts index c057c4e12..1fc9eb0cf 100644 --- a/__tests__/services/DerivationService.spec.ts +++ b/__tests__/services/DerivationService.spec.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {DerivationService, DerivationPathLevels} from '@/services/DerivationService' +import {DerivationPathLevels, DerivationService} from '@/services/DerivationService' import {WalletService} from '@/services/WalletService' + const {DEFAULT_WALLET_PATH} = WalletService // Standard account paths diff --git a/__tests__/services/MosaicModel.spec.ts b/__tests__/services/MosaicModel.spec.ts new file mode 100644 index 000000000..4de018903 --- /dev/null +++ b/__tests__/services/MosaicModel.spec.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +import {Address, MosaicFlags, MosaicId, MosaicInfo, NetworkType, PublicAccount, UInt64} from 'symbol-sdk' +import {MosaicModel} from '@/core/database/entities/MosaicModel' + +describe('services/MosaicData', () => { + describe('serialization', () => { + test('canSerializeDeserialize', () => { + // act + const address = Address.createFromEncoded('917E7E29A01014C2F300000000000000000000000000000000') + + const id = new MosaicId('85BBEA6CC462B244') + const mosaicInfo = new MosaicInfo( + id, // mosaicId + new UInt64([ 3403414400, 2095475 ]), // supply + new UInt64([ 1, 0 ]), // height + PublicAccount.createFromPublicKey('B4F12E7C9F6946091E2CB8B6D3A12B50D17CCBBF646386EA27CE2946A7423DCF', NetworkType.MIJIN_TEST), + 1, // revision + MosaicFlags.create(true, true, true), + 3, + UInt64.fromUint(1000), + ) + const expected = new MosaicModel(address.plain(), address.plain(), 'someName', true, 1234, mosaicInfo) + + // assert + expect(expected).not.toBeNull() + expect(expected.mosaicIdHex).toBe('85BBEA6CC462B244') + expect(expected.addressRawPlain).toBe(address.plain()) + + const deserialized: MosaicModel = JSON.parse(JSON.stringify(expected)) + expect(deserialized).not.toBeNull() + expect(deserialized.mosaicIdHex).toBe('85BBEA6CC462B244') + expect(deserialized.addressRawPlain).toBe(address.plain()) + expect(JSON.stringify(expected)).toBe(JSON.stringify(deserialized)) + + const deserializedMosaicInfo: MosaicInfo = JSON.parse(JSON.stringify(mosaicInfo)) + // NOTE I lose the methods! + expect(deserializedMosaicInfo).not.toBeNull() + expect(deserializedMosaicInfo.id.toHex).toBe(undefined) + expect(deserializedMosaicInfo.duration.compact).toBe(undefined) + + }) + }) +}) diff --git a/__tests__/services/MultisigService.spec.ts b/__tests__/services/MultisigService.spec.ts index ff0d1b054..046fcb681 100644 --- a/__tests__/services/MultisigService.spec.ts +++ b/__tests__/services/MultisigService.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {multisigGraphInfo1, multisigEntries1, multisigEntries2} from '@MOCKS/multisigGraphInfo' +import {multisigEntries1, multisigEntries2, multisigGraphInfo1} from '@MOCKS/multisigGraphInfo' import {MultisigService} from '@/services/MultisigService' describe('services/MultisigService', () => { diff --git a/__tests__/services/WalletService.spec.ts b/__tests__/services/WalletService.spec.ts index c5843ffab..ffbceea20 100644 --- a/__tests__/services/WalletService.spec.ts +++ b/__tests__/services/WalletService.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {NetworkType, Account} from 'symbol-sdk' +import {Account, NetworkType} from 'symbol-sdk' import {WalletService} from '@/services/WalletService' import {MnemonicPassPhrase} from 'symbol-hd-wallets' diff --git a/__tests__/store/Account.spec.ts b/__tests__/store/Account.spec.ts index fff66a6ba..a80d949a3 100644 --- a/__tests__/store/Account.spec.ts +++ b/__tests__/store/Account.spec.ts @@ -1,20 +1,20 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {getFakeModel} from '@MOCKS/Database' import AccountStore from '@/store/Account' +import {AccountModel} from '@/core/database/entities/AccountModel' describe('store/Account', () => { describe('action "RESET_STATE" should', () => { @@ -36,7 +36,7 @@ describe('store/Account', () => { test('dispatch "RESET_STATE"', () => { // prepare const dispatch = jest.fn() - const rootGetters = {'wallet/currentWallet': getFakeModel('1234')} + const rootGetters = {'wallet/currentWallet': {}} // act AccountStore.actions.LOG_OUT({dispatch, rootGetters}) @@ -54,7 +54,7 @@ describe('store/Account', () => { // prepare const commit = jest.fn() const dispatch = jest.fn() - const model = getFakeModel('1234') + const model = new AccountModel() // act await AccountStore.actions.SET_CURRENT_ACCOUNT( diff --git a/__tests__/store/Database.spec.ts b/__tests__/store/Database.spec.ts deleted file mode 100644 index e9b2a85e7..000000000 --- a/__tests__/store/Database.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import DatabaseStore from '@/store/Database' - -describe('store/Database', () => { - describe('action "SYNCHRONIZE" should', () => { - test('mutate hasFeed and dataFeed', () => { - // prepare - const commit = jest.fn() - - // act - DatabaseStore.actions.SYNCHRONIZE({commit}) - - // assert - expect(commit).toHaveBeenCalledTimes(2) - expect(commit).toHaveBeenNthCalledWith(1, 'setHasFeed', false) // no accounts = no feed - expect(commit).toHaveBeenNthCalledWith(2, 'setFeed', { - 'accounts': [], - 'endpoints': [], - 'mosaics': [], - 'namespaces': [], - 'settings': [], - 'wallets': [], - }) - }) - }) -}) diff --git a/__tests__/transactions/ViewAliasTransaction.spec.ts b/__tests__/transactions/ViewAliasTransaction.spec.ts index fd920b0c2..920eccd87 100644 --- a/__tests__/transactions/ViewAliasTransaction.spec.ts +++ b/__tests__/transactions/ViewAliasTransaction.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Deadline, NetworkType, UInt64, NamespaceId, AliasAction, TransactionType, MosaicId, MosaicAliasTransaction} from 'symbol-sdk' +import {AliasAction, Deadline, MosaicAliasTransaction, MosaicId, NamespaceId, NetworkType, TransactionType, UInt64} from 'symbol-sdk' import {createStore} from '@MOCKS/Store' import {getTestAccount} from '@MOCKS/accounts' import {getFakeTransaction} from '@MOCKS/Transactions' diff --git a/__tests__/transactions/ViewHashLockTransaction.spec.ts b/__tests__/transactions/ViewHashLockTransaction.spec.ts index 0a1e8e759..9efdc884d 100644 --- a/__tests__/transactions/ViewHashLockTransaction.spec.ts +++ b/__tests__/transactions/ViewHashLockTransaction.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Deadline, NetworkType, UInt64, TransactionType, MosaicId, HashLockTransaction, Mosaic} from 'symbol-sdk' +import {Deadline, HashLockTransaction, Mosaic, MosaicId, NetworkType, TransactionType, UInt64} from 'symbol-sdk' import {createStore} from '@MOCKS/Store' import {getTestAccount} from '@MOCKS/accounts' import {getFakeTransaction} from '@MOCKS/Transactions' diff --git a/__tests__/transactions/ViewTransferTransaction.spec.ts b/__tests__/transactions/ViewTransferTransaction.spec.ts index bfdc92290..50f89f032 100644 --- a/__tests__/transactions/ViewTransferTransaction.spec.ts +++ b/__tests__/transactions/ViewTransferTransaction.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Deadline, NetworkType, PlainMessage, TransferTransaction, UInt64, TransactionType} from 'symbol-sdk' +import {Deadline, NetworkType, PlainMessage, TransactionType, TransferTransaction, UInt64} from 'symbol-sdk' import {createStore} from '@MOCKS/Store' import {getTestAccount} from '@MOCKS/accounts' import {getFakeTransaction} from '@MOCKS/Transactions' diff --git a/__tests__/utils/TimeHelpers.spec.ts b/__tests__/utils/TimeHelpers.spec.ts new file mode 100644 index 000000000..0771ecc85 --- /dev/null +++ b/__tests__/utils/TimeHelpers.spec.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +import {TimeHelpers} from '@/core/utils/TimeHelpers' + +describe('utils/TimeHelpers', () => { + describe('durationStringToSeconds', () => { + test('returns how many seconds there is in a duration string', () => { + // assert + expect(TimeHelpers.durationStringToSeconds('1m')).toBe(60) + expect(TimeHelpers.durationStringToSeconds('2m')).toBe(60 * 2) + expect(TimeHelpers.durationStringToSeconds('1s')).toBe(1) + expect(TimeHelpers.durationStringToSeconds('50s')).toBe(50) + expect(TimeHelpers.durationStringToSeconds('1h')).toBe(60 * 60) + expect(TimeHelpers.durationStringToSeconds('2h')).toBe(60 * 60 * 2) + expect(TimeHelpers.durationStringToSeconds('1d')).toBe(24 * 60 * 60) + expect(TimeHelpers.durationStringToSeconds('2d')).toBe(24 * 60 * 60 * 2) + expect(TimeHelpers.durationStringToSeconds('20ms')).toBe(0) + expect(TimeHelpers.durationStringToSeconds('2010ms')).toBe(2) + expect(TimeHelpers.durationStringToSeconds('2d 2h 10m 50s 20ms')).toBe(24 * 60 * 60 * 2 + 60 * 60 * 2 + 60 * 10 + 50) + }) + }) + + describe('durationStringToMilliseconds', () => { + test('returns how many milliseconds there is in a duration string', () => { + // assert + expect(TimeHelpers.durationStringToMilliseconds('1m')).toBe(1000 * 60) + expect(TimeHelpers.durationStringToMilliseconds('2m')).toBe(1000 * 60 * 2) + expect(TimeHelpers.durationStringToMilliseconds('1s')).toBe(1000) + expect(TimeHelpers.durationStringToMilliseconds('50s')).toBe(1000 * 50) + expect(TimeHelpers.durationStringToMilliseconds('1h')).toBe(1000 * 60 * 60) + expect(TimeHelpers.durationStringToMilliseconds('2h')).toBe(1000 * 60 * 60 * 2) + expect(TimeHelpers.durationStringToMilliseconds('1d')).toBe(1000 * 24 * 60 * 60) + expect(TimeHelpers.durationStringToMilliseconds('2d')).toBe(1000 * 24 * 60 * 60 * 2) + expect(TimeHelpers.durationStringToMilliseconds('20ms')).toBe(20) + expect(TimeHelpers.durationStringToMilliseconds('2010ms')).toBe(2010) + expect(TimeHelpers.durationStringToMilliseconds('2d 2h 10m 50s 20ms')).toBe(1000 * (24 * 60 * 60 * 2 + 60 * 60 * 2 + 60 * 10 + 50) + 20) + }) + }) + + +}) diff --git a/__tests__/utils/URLHelpers.spec.ts b/__tests__/utils/URLHelpers.spec.ts index 5b41dc29e..0dceefaf6 100644 --- a/__tests__/utils/URLHelpers.spec.ts +++ b/__tests__/utils/URLHelpers.spec.ts @@ -47,7 +47,7 @@ describe('utils/URLHelpers', () => { describe('completeUrlWithHostAndProtocol() should', () => { test('add protocol and port given hostname only', () => { // act - const url = URLHelpers.completeUrlWithHostAndProtocol('localhost') + const url = URLHelpers.getNodeUrl('localhost') // assert expect(url).toBeDefined() @@ -56,7 +56,7 @@ describe('utils/URLHelpers', () => { test('add port given protocol and hostname', () => { // act - const url = URLHelpers.completeUrlWithHostAndProtocol('http://localhost') + const url = URLHelpers.getNodeUrl('http://localhost') // assert expect(url).toBeDefined() @@ -65,7 +65,7 @@ describe('utils/URLHelpers', () => { test('add protocol given hostname and port', () => { // act - const url = URLHelpers.completeUrlWithHostAndProtocol('localhost:3000') + const url = URLHelpers.getNodeUrl('localhost:3000') // assert expect(url).toBeDefined() diff --git a/config/network.conf.json b/config/network.conf.json index 8f8cc433c..a71b6f772 100644 --- a/config/network.conf.json +++ b/config/network.conf.json @@ -2,55 +2,29 @@ "explorerUrl": "http://explorer.symboldev.network", "faucetUrl": "http://faucet-01.symboldev.network", "defaultNetworkType": 152, - "defaultNode": { - "protocol": "http", - "hostname": "api-01.us-west-1.symboldev.network", - "port": 3000, - "url": "http://api-01.us-west-1.symboldev.network:3000" + "defaultNodeUrl": "http://api-01.ap-northeast-1.symboldev.network:3000", + "networkConfigurationDefaults": { + "maxMosaicDivisibility": 6, + "namespaceGracePeriodDuration": 2592000, + "lockedFundsPerAggregate": "10000000", + "maxCosignatoriesPerAccount": 25, + "blockGenerationTargetTime": 15, + "maxNamespaceDepth": 3, + "maxMosaicDuration": 21024000, + "minNamespaceDuration": 172800, + "maxNamespaceDuration": 2102400, + "maxTransactionsPerAggregate": 1000, + "maxCosignedAccountsPerAccount": 25, + "maxMessageSize": 1024, + "maxMosaicAtomicUnits": 9000000000000000 }, - "networks": { - "testnet-publicTest": { - "generationHash": "44D2225B8932C9A96DCB13508CBCDFFA9A9663BFBA2354FEEC8FCFCB7E19846C", - "currencyMosaic": "symbol.xym", - "harvestMosaic": "symbol.xym", - "networkType": 152, - "nodes": [ - {"friendly": "API US West 1", "roles": 2, "url": "http://api-01.us-west-1.symboldev.network:3000"}, - {"friendly": "API AP North-East 1", "roles": 2, "url": "http://api-01.ap-northeast-1.symboldev.network:3000"}, - {"friendly": "API AP South-East 1", "roles": 2, "url": "http://api-01.ap-southeast-1.symboldev.network:3000"}, - {"friendly": "API EU West 1", "roles": 2, "url": "http://api-01.eu-west-1.symboldev.network:3000"}, - {"friendly": "API EU Central 1", "roles": 2, "url": "http://api-01.eu-central-1.symboldev.network:3000"}, - {"friendly": "Dual AP North-East 1", "roles": 3, "url": "http://api-harvest-01.ap-northeast-1.symboldev.network:3000"}, - {"friendly": "Dual EU Central 1", "roles": 3, "url": "http://api-harvest-01.eu-central-1.symboldev.network:3000"} - ], - "properties": { - "reservedRootNamespaceNames": ["xem", "nem", "user", "account", "org", "com", "biz", "net", "edu", "mil", "gov", "info", "symbol", "symbl", "xym"], - "targetBlockTime": 15, - "defaultDynamicFeeMultiplier": 1000, - "mosaicRentalFee": 500, - "rootNamespaceRentalFeePerBlock": 1, - "childNamespaceRentalFee": 100, - "lockedFundsPerAggregate": 10000000, - "minHarvesterBalance": 10000000000, - "maxMosaicDuration": 21024000, - "minNamespaceDuration": 172800, - "maxNamespaceDuration": 2102400, - "namespaceGracePeriodDuration": 172800, - "maxTransactionsPerBlock": 1500, - "maxTransactionsPerAggregate": 1000, - "maxCosignaturesPerAggregate": 25, - "maxMosaicsPerAccount": 10000, - "maxMosaicDivisibility": 6, - "maxMultisigDepth": 3, - "maxCosignatoriesPerAccount": 25, - "maxCosignedAccountsPerAccount": 25, - "maxChildNamespaces": 256, - "maxNamespaceDepth": 3, - "maxAccountRestrictionValues": 512, - "maxMosaicRestrictionValues": 20, - "maxMessageSize": 1024, - "maxMosaicAtomicUnits": 9000000000000000 - } - } - } + "nodes": [ + {"friendlyName": "API US West 1", "roles": 2, "url": "http://api-01.us-west-1.symboldev.network:3000"}, + {"friendlyName": "API AP North-East 1", "roles": 2, "url": "http://api-01.ap-northeast-1.symboldev.network:3000"}, + {"friendlyName": "API AP South-East 1", "roles": 2, "url": "http://api-01.ap-southeast-1.symboldev.network:3000"}, + {"friendlyName": "API EU West 1", "roles": 2, "url": "http://api-01.eu-west-1.symboldev.network:3000"}, + {"friendlyName": "API EU Central 1", "roles": 2, "url": "http://api-01.eu-central-1.symboldev.network:3000"}, + {"friendlyName": "Dual AP North-East 1", "roles": 3, "url": "http://api-harvest-01.ap-northeast-1.symboldev.network:3000"}, + {"friendlyName": "Dual EU Central 1", "roles": 3, "url": "http://api-harvest-01.eu-central-1.symboldev.network:3000"} + ] } diff --git a/package-lock.json b/package-lock.json index 50e77017a..69a39a5a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1746,6 +1746,11 @@ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -8688,9 +8693,9 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "futoin-hkdf": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.1.tgz", - "integrity": "sha512-k1DvCXIFAIx3hK8CSwApotX3JUDwA2Wb55zxyIgqwQpCBF2ZHgVqfHpyjG8mRpmsjRH7SWS1N/vj8EdSF9zBhw==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz", + "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==" }, "gauge": { "version": "2.7.4", @@ -16387,6 +16392,33 @@ "tweetnacl": "^1.0.3" }, "dependencies": { + "symbol-sdk": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.3.tgz", + "integrity": "sha512-V2/7/x17/vZFywLvoN77lNd4eb238+0dzfa3Mk7IiNxMELk8CTSBZ3gm5K+qK4MlCnS1qS6TW8P0QBAXTxKHfg==", + "requires": { + "bluebird": "^3.5.5", + "catbuffer-typescript": "0.0.11", + "crypto-js": "^3.1.9-1", + "diff": "^4.0.2", + "futoin-hkdf": "^1.3.1", + "js-joda": "^1.6.2", + "js-sha256": "^0.9.0", + "js-sha3": "^0.8.0", + "js-sha512": "^0.8.0", + "long": "^4.0.0", + "merkletreejs": "^0.1.7", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "ripemd160": "^2.0.2", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3", + "symbol-openapi-typescript-node-client": "0.8.5", + "tweetnacl": "^1.0.3", + "utf8": "^2.1.2", + "ws": "^5.2.0" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -16428,15 +16460,47 @@ "requires": { "glob": "^7.1.3" } + }, + "symbol-sdk": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.3.tgz", + "integrity": "sha512-V2/7/x17/vZFywLvoN77lNd4eb238+0dzfa3Mk7IiNxMELk8CTSBZ3gm5K+qK4MlCnS1qS6TW8P0QBAXTxKHfg==", + "requires": { + "bluebird": "^3.5.5", + "catbuffer-typescript": "0.0.11", + "crypto-js": "^3.1.9-1", + "diff": "^4.0.2", + "futoin-hkdf": "^1.3.1", + "js-joda": "^1.6.2", + "js-sha256": "^0.9.0", + "js-sha3": "^0.8.0", + "js-sha512": "^0.8.0", + "long": "^4.0.0", + "merkletreejs": "^0.1.7", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "ripemd160": "^2.0.2", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3", + "symbol-openapi-typescript-node-client": "0.8.5", + "tweetnacl": "^1.0.3", + "utf8": "^2.1.2", + "ws": "^5.2.0" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" } } }, "symbol-sdk": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.3.tgz", - "integrity": "sha512-V2/7/x17/vZFywLvoN77lNd4eb238+0dzfa3Mk7IiNxMELk8CTSBZ3gm5K+qK4MlCnS1qS6TW8P0QBAXTxKHfg==", + "version": "0.17.4-alpha-202003201053", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.4-alpha-202003201053.tgz", + "integrity": "sha512-2XhJfCio9+rJnny/90G9bHyne+oDwiuQcrMxZBdSukRkcMJIaDykmbzNcJLbpw1mfSoC5RozxzR/Y60CF0ixVw==", "requires": { - "bluebird": "^3.5.5", + "bluebird": "^3.7.2", "catbuffer-typescript": "0.0.11", "crypto-js": "^3.1.9-1", "diff": "^4.0.2", @@ -16447,21 +16511,49 @@ "js-sha512": "^0.8.0", "long": "^4.0.0", "merkletreejs": "^0.1.7", + "minimist": "^1.2.5", "request": "^2.88.0", "request-promise-native": "^1.0.5", "ripemd160": "^2.0.2", "rxjs": "^6.5.3", "rxjs-compat": "^6.5.3", - "symbol-openapi-typescript-node-client": "0.8.5", + "symbol-openapi-typescript-node-client": "0.8.7", "tweetnacl": "^1.0.3", - "utf8": "^2.1.2", - "ws": "^5.2.0" + "utf8": "^3.0.0", + "ws": "^7.2.3" }, "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "symbol-openapi-typescript-node-client": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/symbol-openapi-typescript-node-client/-/symbol-openapi-typescript-node-client-0.8.7.tgz", + "integrity": "sha512-oOkShSqP565AOM1/6iT88BnL6HoS1lZuc55Gkdb9LmitpLSm5zqhx72iYW1u4HVEHyGi+Q3e1ZxdjMS2tEcg3A==", + "requires": { + "@types/bluebird": "*", + "@types/request": "*", + "bluebird": "^3.5.0", + "request": "^2.81.0", + "rewire": "^3.0.2" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" } } }, diff --git a/package.json b/package.json index 89c8e3198..5890c3668 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@types/crypto-js": "^3.1.43", + "@types/lodash": "^4.14.149", "@types/vue-moment": "^4.0.0", "animate.css": "^3.7.2", "await-lock": "^2.0.1", @@ -35,7 +36,7 @@ "rxjs": "^6.5.2", "symbol-hd-wallets": "^0.9.2", "symbol-qr-library": "^0.9.0", - "symbol-sdk": "^0.17.3", + "symbol-sdk": "0.17.4-alpha-202003201053\n", "trezor-connect": "^7.0.5", "typescript": "^3.7.2", "utf-8-validate": "^5.0.2", diff --git a/src/app/AppTs.ts b/src/app/AppTs.ts index 22e82719b..03c1fa8f9 100644 --- a/src/app/AppTs.ts +++ b/src/app/AppTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,6 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // child components // @ts-ignore import DisabledUiOverlay from '@/components/DisabledUiOverlay/DisabledUiOverlay.vue' @@ -26,7 +25,6 @@ import SpinnerLoading from '@/components/SpinnerLoading/SpinnerLoading.vue' computed: { ...mapGetters({ hasLoadingOverlay: 'app/shouldShowLoadingOverlay', - currentPeer: 'network/currentPeer', currentAccount: 'account/currentAccount', }), }, @@ -49,13 +47,6 @@ export class AppTs extends Vue { */ public currentAccount: string - /** - * Currently active peer - * @see {Store.Network} - * @var {Object} - */ - public currentPeer: Record - /** * Whether a loading overlay must be displayed * @see {Store.App} diff --git a/src/components/AccountBalancesPanel/AccountBalancesPanel.vue b/src/components/AccountBalancesPanel/AccountBalancesPanel.vue index 573fc320e..f15ec7f8c 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanel.vue +++ b/src/components/AccountBalancesPanel/AccountBalancesPanel.vue @@ -11,20 +11,20 @@ >
- {{ currentSignerAddress }} + {{ currentSignerAddress.plain() }}
-
-
{{ networkMosaicTicker }}
+
+
{{ networkCurrency.ticker }}
diff --git a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts index fa69d9af2..744b24ffa 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts +++ b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts @@ -1,73 +1,67 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {MosaicId, Mosaic} from 'symbol-sdk' +import {MosaicId, Address} from 'symbol-sdk' import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' -import {MosaicService} from '@/services/MosaicService' - // child components // @ts-ignore import MosaicAmountDisplay from '@/components/MosaicAmountDisplay/MosaicAmountDisplay.vue' // @ts-ignore import MosaicBalanceList from '@/components/MosaicBalanceList/MosaicBalanceList.vue' -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' @Component({ components: { MosaicAmountDisplay, MosaicBalanceList, }, - computed: {...mapGetters({ - currentWallet: 'wallet/currentWallet', - currentSigner: 'wallet/currentSigner', - currentWalletMosaics: 'wallet/currentWalletMosaics', - currentSignerMosaics: 'wallet/currentSignerMosaics', - isCosignatoryMode: 'wallet/isCosignatoryMode', - networkMosaic: 'mosaic/networkMosaic', - networkMosaicTicker: 'mosaic/networkMosaicTicker', - })}, + computed: { + ...mapGetters({ + currentWallet: 'wallet/currentWallet', + currentSignerAddress: 'wallet/currentSignerAddress', + balanceMosaics: 'mosaic/balanceMosaics', + isCosignatoryMode: 'wallet/isCosignatoryMode', + networkCurrency: 'mosaic/networkCurrency', + networkMosaicId: 'mosaic/networkMosaic', + }), + }, }) export class AccountBalancesPanelTs extends Vue { /** * Currently active wallet - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Currently active signer * @var {any} */ - public currentSigner: WalletsModel + public currentSignerAddress: Address /** * Currently active wallet's balances * @var {Mosaic[]} */ - public currentWalletMosaics: Mosaic[] - - /** - * Currently active signers's balances - * @var {Mosaic[]} - */ - public currentSignerMosaics: Mosaic[] + public balanceMosaics: MosaicModel[] /** * Whether currently active wallet is in cosignatory mode @@ -79,13 +73,10 @@ export class AccountBalancesPanelTs extends Vue { * Networks currency mosaic * @var {MosaicId} */ - public networkMosaic: MosaicId + public networkCurrency: NetworkCurrencyModel + + public networkMosaicId: MosaicId - /** - * Currency mosaic's ticker - * @var {string} - */ - public networkMosaicTicker: string /** * UI Helpers @@ -93,16 +84,8 @@ export class AccountBalancesPanelTs extends Vue { */ public uiHelpers = UIHelpers - private mosaicService: MosaicService = new MosaicService(this.$store) - - /** - * collection of known mosaics from database - * @readonly - * @protected - * @type {MosaicsModel[]} - */ - protected get allMosaics(): MosaicsModel[] { - return this.mosaicService.getMosaics() + public async created() { + this.$store.dispatch('mosaic/LOAD_MOSAICS') } /** @@ -112,52 +95,12 @@ export class AccountBalancesPanelTs extends Vue { * @type {number} */ protected get divisibility(): number { - if (!this.networkMosaic) return null - const networkMosaicId = this.networkMosaic.id.toHex() - const networkMosaicModel = this.allMosaics.find(m => m.getIdentifier() === networkMosaicId) - if (networkMosaicModel === undefined) return null - return networkMosaicModel.values.get('divisibility') - } - - /// region computed properties getter/setter - public get currentMosaics(): Mosaic[] { - if (this.isCosignatoryMode) { - return this.currentSignerMosaics - } - - return this.currentWalletMosaics - } - - public get currentSignerAddress(): string { - if (this.isCosignatoryMode && this.currentSigner) { - return this.currentSigner.values.get('address') - } - - if (!this.currentWallet) { - return this.$t('loading').toString() - } - - return this.currentWallet.values.get('address') + return this.networkCurrency && this.networkCurrency.divisibility || 0 } public get absoluteBalance() { - const mosaics = [...this.currentMosaics] - - if (!mosaics.length || !this.networkMosaic) { - return 0 - } - - // - search for network mosaic - const entry = mosaics.find( - mosaic => mosaic.id.id.equals(this.networkMosaic.id), - ) - - if (undefined === entry) { - return 0 - } - - // - format to relative - return entry.amount.compact() + const networkMosaicData = this.balanceMosaics.filter(m => m.isCurrencyMosaic).find(i => i) + return networkMosaicData && networkMosaicData.balance || 0 } public get networkMosaicBalance(): number { @@ -165,5 +108,5 @@ export class AccountBalancesPanelTs extends Vue { if (balance === 0 || !this.divisibility) return 0 return balance / Math.pow(10, this.divisibility) } -/// end-region computed properties getter/setter + } diff --git a/src/components/ActionDisplay/ActionDisplayTs.ts b/src/components/ActionDisplay/ActionDisplayTs.ts index ef153bbcd..366f6eef0 100644 --- a/src/components/ActionDisplay/ActionDisplayTs.ts +++ b/src/components/ActionDisplay/ActionDisplayTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,15 +14,23 @@ * limitations under the License. */ import {Component, Prop, Vue} from 'vue-property-decorator' -import {Address, Transaction, TransactionType, TransferTransaction, NamespaceId, NamespaceName} from 'symbol-sdk' +import {Address, NamespaceId, Transaction, TransactionType, TransferTransaction} from 'symbol-sdk' +import {mapGetters} from 'vuex' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' -@Component +@Component({ + computed: { + ...mapGetters({ + namespaces: 'namespace/namespaces', + }), + }, +}) export class ActionDisplayTs extends Vue { /** * Transaction * @type {Transaction} */ - @Prop({ default: null }) transaction: Transaction + @Prop({default: null}) transaction: Transaction /** * Action descriptor @@ -44,6 +52,8 @@ export class ActionDisplayTs extends Vue { */ protected needsCosignature: boolean = false + public namespaces: NamespaceModel[] + /** * Hook called when the component is mounted * @return {void} @@ -53,6 +63,7 @@ export class ActionDisplayTs extends Vue { this.loadDetails() } + /// region computed properties getter/setter /// end-region computed properties getter/setter @@ -72,8 +83,8 @@ export class ActionDisplayTs extends Vue { else if (this.transaction.type === this.transactionType.TRANSFER && (this.transaction as TransferTransaction).recipientAddress instanceof NamespaceId) { const id = ((this.transaction as TransferTransaction).recipientAddress as NamespaceId) - const namespaceNames: NamespaceName[] = await this.$store.dispatch('namespace/REST_FETCH_NAMES', [id]) - this.descriptor = namespaceNames.shift().name + const namespaceName = this.namespaces.find(n => n.namespaceIdHex == id.toHex()) + this.descriptor = namespaceName && namespaceName.name || '' } // - otherwise use *translated* transaction descriptor else { diff --git a/src/components/AmountDisplay/AmountDisplayTs.ts b/src/components/AmountDisplay/AmountDisplayTs.ts index 63b885374..8fc92fbf2 100644 --- a/src/components/AmountDisplay/AmountDisplayTs.ts +++ b/src/components/AmountDisplay/AmountDisplayTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,26 +19,30 @@ import {mapGetters} from 'vuex' import {MosaicId} from 'symbol-sdk' // configuration -import networkConfig from '@/../config/network.conf.json' -const currentNetworkConfig = networkConfig.networks['testnet-publicTest'] +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + @Component({ - computed: {...mapGetters({ - networkMosaicTicker: 'mosaic/networkMosaicTicker', - networkMosaic: 'mosaic/networkMosaic', - })}, + computed: { + ...mapGetters({ + networkMosaicTicker: 'mosaic/networkMosaicTicker', + networkMosaic: 'mosaic/networkMosaic', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class AmountDisplayTs extends Vue { - @Prop({ default: 0 }) value: number - - @Prop({ default: currentNetworkConfig.properties.maxMosaicDivisibility }) decimals: number - - @Prop({ default: false }) showTicker: false - - @Prop({ default: '' }) ticker: string - - @Prop({ default: 'normal' }) size: 'normal' | 'smaller' | 'bigger' | 'biggest' + @Prop({default: 0}) value: number + + @Prop() decimals: number | undefined + + @Prop({default: false}) showTicker: false + @Prop({default: ''}) ticker: string + + @Prop({default: 'normal'}) size: 'normal' | 'smaller' | 'bigger' | 'biggest' + + public networkConfiguration: NetworkConfigurationModel /** * Currency mosaic's ticker * @var {string} @@ -60,8 +64,9 @@ export class AmountDisplayTs extends Vue { const rest = this.value - Math.floor(this.value) if (rest === 0) return '' + const decimals = this.networkConfiguration.maxMosaicDivisibility // remove leftmost-0 and rightmost-0 - return Number(rest.toFixed(this.decimals)).toPrecision().toString().replace(/^0/, '') + return Number(rest.toFixed(decimals)).toPrecision().toString().replace(/^0/, '') } /** @@ -74,5 +79,6 @@ export class AmountDisplayTs extends Vue { if (this.ticker === this.networkMosaic.id.toHex()) return this.networkMosaicTicker return this.ticker || this.networkMosaicTicker } + /// end-region computed properties getter/setter } diff --git a/src/components/ApprovalAndRemovalInput/ApprovalAndRemovalInputTs.ts b/src/components/ApprovalAndRemovalInput/ApprovalAndRemovalInputTs.ts index 7b74c3c2c..af03ba2b8 100644 --- a/src/components/ApprovalAndRemovalInput/ApprovalAndRemovalInputTs.ts +++ b/src/components/ApprovalAndRemovalInput/ApprovalAndRemovalInputTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,22 +14,20 @@ * limitations under the License. */ // external dependencies -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {MultisigAccountInfo} from 'symbol-sdk' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' - // child components import {ValidationProvider} from 'vee-validate' // @ts-ignore import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' - // configuration -import networkConfig from '@/../config/network.conf.json' -const currentNetworkConfig = networkConfig.networks['testnet-publicTest'] +import {mapGetters} from 'vuex' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + @Component({ components: { @@ -37,7 +35,13 @@ const currentNetworkConfig = networkConfig.networks['testnet-publicTest'] ErrorTooltip, FormRow, }, + computed: { + ...mapGetters({ + networkConfiguration: 'network/networkConfiguration', + }), + }, }) +@Component export class ApprovalAndRemovalInputTs extends Vue { /** * Value bound to the form v-model @@ -71,6 +75,9 @@ export class ApprovalAndRemovalInputTs extends Vue { default: null, }) multisig: MultisigAccountInfo + + private networkConfiguration: NetworkConfigurationModel + /// region computed properties getter/setter /** * Gets the input value from the value prop @@ -106,6 +113,7 @@ export class ApprovalAndRemovalInputTs extends Vue { ? 'form_label_description_min_approval' : 'form_label_description_min_removal' } + /** * Current minApproval or minRemoval of the target account * @readonly @@ -130,12 +138,12 @@ export class ApprovalAndRemovalInputTs extends Vue { * @protected * @type {{label: string, value: number}} */ - protected get deltaOptions(): {value: number, newDelta: number}[] { + protected get deltaOptions(): { value: number, newDelta: number }[] { // For an account conversion, the minimum delta is 1 const isConversion = this.operation === 'conversion' // minimum possible delta - const {maxCosignatoriesPerAccount} = currentNetworkConfig.properties + const maxCosignatoriesPerAccount = this.networkConfiguration.maxCosignatoriesPerAccount const minDelta = isConversion ? 1 : 0 - this.currentValue return [...Array(maxCosignatoriesPerAccount).keys()].map( @@ -146,5 +154,6 @@ export class ApprovalAndRemovalInputTs extends Vue { }, ) } + /// end-region computed properties getter/setter } diff --git a/src/components/HardwareConfirmationButton/HardwareConfirmationButtonTs.ts b/src/components/HardwareConfirmationButton/HardwareConfirmationButtonTs.ts index 7c1176054..b21fb4331 100644 --- a/src/components/HardwareConfirmationButton/HardwareConfirmationButtonTs.ts +++ b/src/components/HardwareConfirmationButton/HardwareConfirmationButtonTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,18 +18,18 @@ import {mapGetters} from 'vuex' import {Transaction, SignedTransaction, NetworkType} from 'symbol-sdk' // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {TransactionService} from '@/services/TransactionService' import TrezorConnect from '@/core/utils/TrezorConnect' @Component({ - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - currentWallet: 'wallet/currentWallet', - stagedTransactions: 'wallet/stagedTransactions', - })}, + computed: { + ...mapGetters({ + networkType: 'network/networkType', + currentWallet: 'wallet/currentWallet', + stagedTransactions: 'wallet/stagedTransactions', + }), + }, }) export class HardwareConfirmationButtonTs extends Vue { /** @@ -39,19 +39,12 @@ export class HardwareConfirmationButtonTs extends Vue { */ public networkType: NetworkType - /** - * Currently active account - * @see {Store.Account} - * @var {AccountsModel} - */ - public currentAccount: AccountsModel - /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Staged transactions (to-be-signed) @@ -91,10 +84,10 @@ export class HardwareConfirmationButtonTs extends Vue { for (let i = 0, m = transactions.length; i < m; i ++) { // - order matters, get first transaction on-stage const stagedTx = transactions.shift() - + // - sign each transaction with TrezorConnect const result = await TrezorConnect.nemSignTransaction({ - path: this.currentWallet.values.get('path'), + path: this.currentWallet.path, transaction: stagedTx, }) diff --git a/src/components/ImportanceScoreDisplay/ImportanceScoreDisplayTs.ts b/src/components/ImportanceScoreDisplay/ImportanceScoreDisplayTs.ts index 33f89a446..23c3dbedc 100644 --- a/src/components/ImportanceScoreDisplay/ImportanceScoreDisplayTs.ts +++ b/src/components/ImportanceScoreDisplay/ImportanceScoreDisplayTs.ts @@ -1,51 +1,50 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {AccountInfo} from 'symbol-sdk' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' import {mapGetters} from 'vuex' @Component({ - computed: {...mapGetters({ - 'knownWalletsInfo': 'wallet/knownWalletsInfo', - })}, + computed: { + ...mapGetters({ + 'accountsInfo': 'wallet/accountsInfo', + }), + }, }) export class ImportanceScoreDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) address: string /** - * + * */ - public knownWalletsInfo: any + private accountsInfo: AccountInfo[] /// region computed properties getter/setter get score(): string { - const addr = Object.keys(this.knownWalletsInfo).find(k => k === this.wallet.objects.address.plain()) - if (undefined === addr) { + const accountInfo = this.accountsInfo.find(k => k.address.plain() === this.address) + if (!accountInfo) { return '0' } - - const accountInfo: AccountInfo = this.knownWalletsInfo[addr] const importance = accountInfo.importance.compact() return importance.toString() } + /// end-region computed properties getter/setter } diff --git a/src/components/LanguageSelector/LanguageSelectorTs.ts b/src/components/LanguageSelector/LanguageSelectorTs.ts index a1732d5ea..dfaca0dce 100644 --- a/src/components/LanguageSelector/LanguageSelectorTs.ts +++ b/src/components/LanguageSelector/LanguageSelectorTs.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' @Component({computed: {...mapGetters({ - currentLanguage: 'app/currentLanguage', + language: 'app/currentLanguage', languageList: 'app/languages', })}}) export class LanguageSelectorTs extends Vue { diff --git a/src/components/MaxFeeSelector/MaxFeeSelectorTs.ts b/src/components/MaxFeeSelector/MaxFeeSelectorTs.ts index 561aafe33..421cfd97d 100644 --- a/src/components/MaxFeeSelector/MaxFeeSelectorTs.ts +++ b/src/components/MaxFeeSelector/MaxFeeSelectorTs.ts @@ -1,26 +1,25 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {MosaicId, MosaicInfo} from 'symbol-sdk' - // configuration import feesConfig from '@/../config/fees.conf.json' // @ts-ignore import FormLabel from '@/components/FormLabel/FormLabel.vue' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' @Component({ components: { @@ -28,9 +27,8 @@ import FormLabel from '@/components/FormLabel/FormLabel.vue' }, computed: {...mapGetters({ defaultFee: 'app/defaultFee', - networkMosaic: 'mosaic/networkMosaic', networkMosaicName: 'mosaic/networkMosaicName', - mosaicsInfo: 'mosaic/mosaicsInfoList', + networkCurrency: 'mosaic/networkCurrency', })}, }) export class MaxFeeSelectorTs extends Vue { @@ -39,11 +37,6 @@ export class MaxFeeSelectorTs extends Vue { default: 'form-line-container', }) className: string - /** - * Networks currency mosaic id - * @var {MosaicId} - */ - public networkMosaic: MosaicId /** * Networks currency mosaic name @@ -55,7 +48,7 @@ export class MaxFeeSelectorTs extends Vue { * Known mosaics info * @var {MosaicInfo[]} */ - public mosaicsInfo: MosaicInfo[] + public networkCurrency: NetworkCurrencyModel /** * Default fee setting @@ -104,11 +97,10 @@ export class MaxFeeSelectorTs extends Vue { * @return {number} */ public getRelative(amount: number): number { - const info = this.mosaicsInfo.find(i => i.id.equals(this.networkMosaic)) - if (info === undefined) { + if (this.networkCurrency === undefined) { return amount } - return amount / Math.pow(10, info.divisibility) + return amount / Math.pow(10, this.networkCurrency.divisibility) } } diff --git a/src/components/MessageInput/MessageInputTs.ts b/src/components/MessageInput/MessageInputTs.ts index 2cbf814c2..02a65f8a5 100644 --- a/src/components/MessageInput/MessageInputTs.ts +++ b/src/components/MessageInput/MessageInputTs.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' - +import {Component, Prop, Vue} from 'vue-property-decorator' // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' - // child components // @ts-ignore import {ValidationProvider} from 'vee-validate' diff --git a/src/components/MnemonicInput/MnemonicInput.vue b/src/components/MnemonicInput/MnemonicInput.vue index 462b1d310..cecce0b28 100644 --- a/src/components/MnemonicInput/MnemonicInput.vue +++ b/src/components/MnemonicInput/MnemonicInput.vue @@ -14,7 +14,7 @@ -
+ diff --git a/src/components/MosaicSelector/MosaicSelectorTs.ts b/src/components/MosaicSelector/MosaicSelectorTs.ts index 0c12bf19c..2735aabca 100644 --- a/src/components/MosaicSelector/MosaicSelectorTs.ts +++ b/src/components/MosaicSelector/MosaicSelectorTs.ts @@ -1,32 +1,29 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {MosaicId, MosaicInfo, Mosaic} from 'symbol-sdk' -import {Component, Vue, Prop} from 'vue-property-decorator' +import {MosaicId} from 'symbol-sdk' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {MosaicService} from '@/services/MosaicService' -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' - // child components import {ValidationProvider} from 'vee-validate' // @ts-ignore import ErrorTooltip from '@/components//ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import FormLabel from '@/components//FormLabel/FormLabel.vue' +import {MosaicModel} from '@/core/database/entities/MosaicModel' @Component({ components: { @@ -38,8 +35,7 @@ import FormLabel from '@/components//FormLabel/FormLabel.vue' ...mapGetters({ networkMosaic: 'mosaic/networkMosaic', networkMosaicName: 'mosaic/networkMosaicName', - mosaicsInfo: 'mosaic/mosaicsInfoList', - mosaicsNames: 'mosaic/mosaicsNames', + mosaics: 'mosaic/mosaics', }), }, }) @@ -47,20 +43,17 @@ export class MosaicSelectorTs extends Vue { /** * Prop bound to the parent v-model - * @type {string} */ @Prop({default: ''}) value: string /** * Mosaics to display as options - * @type {Mosaic[]} */ - @Prop({default: []}) mosaics: Mosaic[] + @Prop({default: []}) mosaicHexIds: string[] /** * Field label hidden by default - * @type {string} */ @Prop({default: null}) label: string @@ -68,51 +61,32 @@ export class MosaicSelectorTs extends Vue { @Prop({default: 'networkMosaic'}) defaultMosaic: 'networkMosaic' | 'firstInList' /** * Networks currency mosaic - * @var {MosaicId} */ public networkMosaic: MosaicId /** * Networks currency mosaic name - * @var {string} */ public networkMosaicName: string /** - * Network mosaics info (all) - * @see {Store.Mosaic} - * @var {MosaicInfo[]} + * All the known mosaics. */ - public mosaicsInfo: MosaicInfo[] + public mosaics: MosaicModel[] - /** - * Network mosaics names (all) - * @see {Store.Mosaic} - * @var {string[]} - */ - public mosaicsNames: any /// region computed properties getter/setter - /** - * All mosaics stored in db - * @readonly - * @type {MosaicsModel[]} - */ - public get allMosaics(): MosaicsModel[] { - const service = new MosaicService(this.$store) - return service.getMosaics() - } + /** * Mosaics shown as options in the select * @readonly * @protected - * @type {MosaicsModel[]} */ - protected get displayedMosaics(): MosaicsModel[] { - return this.mosaics - .map(({id}) => this.allMosaics.find(m => m.getIdentifier() === id.toHex())) - .filter(x => x) // filter out the mosaics of which info has not yet been fetched + protected get displayedMosaics(): MosaicModel[] { + return this.mosaicHexIds + .map((mosaicIdHex) => this.mosaics.find(m => m.mosaicIdHex === mosaicIdHex)) + .filter(x => x) } /** @@ -144,8 +118,8 @@ export class MosaicSelectorTs extends Vue { } // otherwise... set default value to the first mosaic from the props - if (this.defaultMosaic === 'firstInList' && this.mosaics.length) { - this.selectedMosaic = this.mosaics[0].id.toHex() + if (this.defaultMosaic === 'firstInList' && this.mosaicHexIds.length) { + this.selectedMosaic = this.mosaicHexIds[0] } } } diff --git a/src/components/MultisigCosignatoriesDisplay/MultisigCosignatoriesDisplay.vue b/src/components/MultisigCosignatoriesDisplay/MultisigCosignatoriesDisplay.vue index 121894754..fbdafe021 100644 --- a/src/components/MultisigCosignatoriesDisplay/MultisigCosignatoriesDisplay.vue +++ b/src/components/MultisigCosignatoriesDisplay/MultisigCosignatoriesDisplay.vue @@ -1,6 +1,6 @@ diff --git a/src/components/PeerSelector/PeerSelectorTs.ts b/src/components/PeerSelector/PeerSelectorTs.ts index ece311ada..f698dd322 100644 --- a/src/components/PeerSelector/PeerSelectorTs.ts +++ b/src/components/PeerSelector/PeerSelectorTs.ts @@ -1,59 +1,60 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {NetworkType} from 'symbol-sdk' +import {NetworkType, RepositoryFactory} from 'symbol-sdk' import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {PeersModel} from '@/core/database/entities/PeersModel' -import {PeersRepository} from '@/repositories/PeersRepository' -import {PeerService} from '@/services/PeerService' import {URLHelpers} from '@/core/utils/URLHelpers' import {NotificationType} from '@/core/utils/NotificationType' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' +// child components +import {ValidationObserver, ValidationProvider} from 'vee-validate' +// @ts-ignore +import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' +// resources +import {dashboardImages} from '@/views/resources/Images' +import {NodeModel} from '@/core/database/entities/NodeModel' // helpers const getNetworkTypeText = (networkType: NetworkType) => { switch (networkType) { default: - case NetworkType.MIJIN_TEST: return 'MIJIN_TEST' - case NetworkType.MIJIN: return 'MIJIN' - case NetworkType.TEST_NET: return 'TEST_NET' - case NetworkType.MAIN_NET: return 'MAIN_NET' + case NetworkType.MIJIN_TEST: + return 'MIJIN_TEST' + case NetworkType.MIJIN: + return 'MIJIN' + case NetworkType.TEST_NET: + return 'TEST_NET' + case NetworkType.MAIN_NET: + return 'MAIN_NET' } } -// child components -import {ValidationObserver, ValidationProvider} from 'vee-validate' -// @ts-ignore -import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' - -// resources -import {dashboardImages} from '@/views/resources/Images' - @Component({ - computed: {...mapGetters({ - currentPeer: 'network/currentPeer', - isConnected: 'network/isConnected', - networkType: 'network/networkType', - generationHash: 'network/generationHash', - knownPeers: 'network/knownPeers', - })}, + computed: { + ...mapGetters({ + currentPeerInfo: 'network/currentPeerInfo', + isConnected: 'network/isConnected', + networkType: 'network/networkType', + repositoryFactory: 'network/repositoryFactory', + generationHash: 'network/generationHash', + knowNodes: 'network/knowNodes', + }), + }, components: {ValidationObserver, ValidationProvider, ErrorTooltip}, }) export class PeerSelectorTs extends Vue { @@ -62,7 +63,7 @@ export class PeerSelectorTs extends Vue { * @see {Store.Network} * @var {Object} */ - public currentPeer: Record + public currentPeerInfo: NodeModel /** * Whether the connection is up @@ -90,13 +91,9 @@ export class PeerSelectorTs extends Vue { * @see {Store.Network} * @var {string[]} */ - public knownPeers: string[] + public knowNodes: NodeModel[] - /** - * Peers list - * @var {Map} - */ - public collection: Map + public repositoryFactory: RepositoryFactory /** * Form items @@ -104,6 +101,7 @@ export class PeerSelectorTs extends Vue { */ public formItems = { nodeUrl: '', + filter: '', setDefault: false, } @@ -119,29 +117,37 @@ export class PeerSelectorTs extends Vue { public imageResources = dashboardImages /** - * Type the ValidationObserver refs - * @type {{ - * observer: InstanceType - * }} - */ + * Type the ValidationObserver refs + * @type {{ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } /// region computed properties getter/setter - get peersList(): string[] { - return this.knownPeers + get peersList(): NodeModel[] { + return this.knowNodes.filter(p => { + if (!this.formItems.filter) { + return true + } else { + return p.url.indexOf(this.formItems.filter) > -1 + || (p.friendlyName && p.friendlyName.indexOf(this.formItems.filter) > -1) + } + }) } get networkTypeText(): string { if (!this.isConnected) return this.$t('Invalid_node').toString() return !!this.networkType ? getNetworkTypeText(this.networkType) : this.$t('Loading').toString() } + /// end-region computed properties getter/setter /** * Switch the currently active peer - * @param peer + * @param peer */ public switchPeer(url: string) { this.$store.dispatch('network/SET_CURRENT_PEER', url) @@ -152,18 +158,13 @@ export class PeerSelectorTs extends Vue { * @return {void} */ public async addPeer() { - const service = new PeerService(this.$store) - const repository = new PeersRepository() // validate and parse input - const nodeUrl = service.getNodeUrl(this.formItems.nodeUrl) + const nodeUrl = URLHelpers.getNodeUrl(this.formItems.nodeUrl) const node = URLHelpers.formatUrl(nodeUrl) // return if node already exists in the database - if (service - .getEndpoints() - .map(model => model.values.get('rest_url')) - .findIndex(url => url === nodeUrl) > -1) { + if (this.knowNodes.find(node => node.url === nodeUrl)) { this.$store.dispatch('notification/ADD_ERROR', NotificationType.NODE_EXISTS_ERROR) return } @@ -176,38 +177,17 @@ export class PeerSelectorTs extends Vue { // read network type from node pre-saving try { - const { - networkType, - generationHash, - peerInfo, - } = await this.$store.dispatch('network/REST_FETCH_PEER_INFO', nodeUrl) // hide loading overlay this.$store.dispatch('app/SET_LOADING_OVERLAY', {show: false}) - - // prepare model - const peer = new PeersModel(new Map([ - [ 'rest_url', nodeUrl ], - [ 'host', node.hostname ], - [ 'port', parseInt(node.port) ], - [ 'protocol', node.protocol ], - [ 'networkType', networkType ], - [ 'generationHash', generationHash ], - [ 'roles', peerInfo.roles ], - [ 'is_default', this.formItems.setDefault ], - [ 'friendly_name', peerInfo.friendlyName ], - ])) - - // save in storage - repository.create(peer.values) this.$store.dispatch('network/ADD_KNOWN_PEER', nodeUrl) this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) - this.$store.dispatch('diagnostic/ADD_DEBUG', `PeerSelector added peer: ${nodeUrl}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', 'PeerSelector added peer: ' + nodeUrl) // reset the form input this.formItems.nodeUrl = '' - Vue.nextTick().then(() =>{ + Vue.nextTick().then(() => { // reset the form validation this.$refs.observer.reset() @@ -215,11 +195,10 @@ export class PeerSelectorTs extends Vue { const container = this.$el.querySelector('#node-list-container') container.scrollTop = container.scrollHeight }) - } - catch(e) { + } catch (e) { // hide loading overlay this.$store.dispatch('app/SET_LOADING_OVERLAY', {show: false}) - this.$store.dispatch('diagnostic/ADD_ERROR', `PeerSelector unreachable host with URL: ${nodeUrl}`) + this.$store.dispatch('diagnostic/ADD_ERROR', 'PeerSelector unreachable host with URL: ' + nodeUrl) this.$store.dispatch('notification/ADD_ERROR', NotificationType.ERROR_PEER_UNREACHABLE) } } @@ -229,24 +208,16 @@ export class PeerSelectorTs extends Vue { * @return {void} */ public removePeer(url: string) { - // get peer service - const service = new PeerService(this.$store) - // don't allow deleting all the nodes - if (service.getEndpoints().length === 1) { + if (this.knowNodes.length === 1) { this.$store.dispatch('notification/ADD_ERROR', NotificationType.ERROR_DELETE_ALL_PEERS) return } - // get full node URL - const nodeUrl = service.getNodeUrl(url) - + const nodeUrl = URLHelpers.getNodeUrl(url) // remove the mode from the vuex store this.$store.dispatch('network/REMOVE_KNOWN_PEER', nodeUrl) this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) - - // remove the node from the storage - service.deleteEndpoint(url) } /** diff --git a/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButton.vue b/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButton.vue index 70054bfc3..1a822f909 100644 --- a/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButton.vue +++ b/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButton.vue @@ -21,6 +21,7 @@ diff --git a/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButtonTs.ts b/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButtonTs.ts index 35f41700a..3246cba87 100644 --- a/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButtonTs.ts +++ b/src/components/ProtectedMnemonicDisplayButton/ProtectedMnemonicDisplayButtonTs.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' - // child components // @ts-ignore import ModalMnemonicDisplay from '@/views/modals/ModalMnemonicDisplay/ModalMnemonicDisplay.vue' @@ -37,7 +35,7 @@ import ModalMnemonicDisplay from '@/views/modals/ModalMnemonicDisplay/ModalMnemo export class ProtectedMnemonicDisplayButtonTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers diff --git a/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButton.vue b/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButton.vue index 35365011c..0c1b3e8e9 100644 --- a/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButton.vue +++ b/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButton.vue @@ -21,6 +21,7 @@ diff --git a/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButtonTs.ts b/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButtonTs.ts index 0c5086b66..811464e3e 100644 --- a/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButtonTs.ts +++ b/src/components/ProtectedMnemonicQRButton/ProtectedMnemonicQRButtonTs.ts @@ -17,7 +17,7 @@ import {Component, Vue, Prop} from 'vue-property-decorator' import {mapGetters} from 'vuex' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' // child components @@ -37,7 +37,7 @@ import ModalMnemonicExport from '@/views/modals/ModalMnemonicExport/ModalMnemoni export class ProtectedMnemonicQRButtonTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers diff --git a/src/components/ProtectedPrivateKeyDisplay/ProtectedPrivateKeyDisplayTs.ts b/src/components/ProtectedPrivateKeyDisplay/ProtectedPrivateKeyDisplayTs.ts index 500e503fb..38abf6931 100644 --- a/src/components/ProtectedPrivateKeyDisplay/ProtectedPrivateKeyDisplayTs.ts +++ b/src/components/ProtectedPrivateKeyDisplay/ProtectedPrivateKeyDisplayTs.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {Account} from 'symbol-sdk' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' - // child components // @ts-ignore import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' @@ -32,7 +30,7 @@ import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalF export class ProtectedPrivateKeyDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers diff --git a/src/components/RemoveCosignatoryInput/RemoveCosignatoryInputTs.ts b/src/components/RemoveCosignatoryInput/RemoveCosignatoryInputTs.ts index 5c8ae59e2..db6b18efd 100644 --- a/src/components/RemoveCosignatoryInput/RemoveCosignatoryInputTs.ts +++ b/src/components/RemoveCosignatoryInput/RemoveCosignatoryInputTs.ts @@ -16,8 +16,7 @@ // external dependencies import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {Address, PublicAccount, NetworkType} from 'symbol-sdk' - +import {Address, NetworkType, PublicAccount} from 'symbol-sdk' // child components // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' @@ -31,7 +30,6 @@ import ButtonRemove from '@/components/ButtonRemove/ButtonRemove.vue' }, computed: { ...mapGetters({ - currentPeer: 'network/currentPeer', networkType: 'network/networkType', }), }, diff --git a/src/components/SignerSelector/SignerSelector.vue b/src/components/SignerSelector/SignerSelector.vue index 80572b38f..e28305a93 100644 --- a/src/components/SignerSelector/SignerSelector.vue +++ b/src/components/SignerSelector/SignerSelector.vue @@ -17,13 +17,14 @@ :key="item.publicKey" :value="item.publicKey" > - {{ item.label }} + {{ item.label }} {{ item.multisig ? $t('label_postfix_multisig') : '' }}
{{ signers[0] ? signers[0].label : '' }} + {{ (signers[0] && signers[0].multisig) ? $t('label_postfix_multisig') : '' }}
diff --git a/src/components/SignerSelector/SignerSelectorTs.ts b/src/components/SignerSelector/SignerSelectorTs.ts index dcbe6084a..ac422fc17 100644 --- a/src/components/SignerSelector/SignerSelectorTs.ts +++ b/src/components/SignerSelector/SignerSelectorTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,10 +14,10 @@ * limitations under the License. */ import {Component, Prop, Vue} from 'vue-property-decorator' - // child components // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' +import {Signer} from '@/store/Wallet' @Component({ components: {FormRow}, @@ -33,7 +33,7 @@ export class SignerSelectorTs extends Vue { @Prop({ default: () => [], - }) signers: {publicKey: string, label: string}[] + }) signers: Signer[] @Prop({ default: 'sender', @@ -58,5 +58,6 @@ export class SignerSelectorTs extends Vue { set chosenSigner(newValue: string) { this.$emit('input', newValue) } + /// end-region computed properties getter/setter } diff --git a/src/components/TableDisplay/TableDisplayTs.ts b/src/components/TableDisplay/TableDisplayTs.ts index 7e40b6eb1..a9b254635 100644 --- a/src/components/TableDisplay/TableDisplayTs.ts +++ b/src/components/TableDisplay/TableDisplayTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,21 +16,11 @@ // external dependencies import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NamespaceId, AliasAction, MosaicId, Address, MosaicInfo, NamespaceInfo} from 'symbol-sdk' - +import {Address, AliasAction, MosaicId, NamespaceId} from 'symbol-sdk' // internal dependencies -import { - AssetTableService, - TableSortingOptions, - TableFilteringOptions, - TableField, - SortingDirections, - FilteringTypes, -} from '@/services/AssetTableService/AssetTableService' +import {AssetTableService, FilteringTypes, SortingDirections, TableField, TableFilteringOptions, TableSortingOptions} from '@/services/AssetTableService/AssetTableService' import {MosaicTableService} from '@/services/AssetTableService/MosaicTableService' import {NamespaceTableService} from '@/services/AssetTableService/NamespaceTableService' -import {MosaicService} from '@/services/MosaicService' - // child components // @ts-ignore import TableRow from '@/components/TableRow/TableRow.vue' @@ -42,6 +32,9 @@ import FormAliasTransaction from '@/views/forms/FormAliasTransaction/FormAliasTr import FormExtendNamespaceDurationTransaction from '@/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransaction.vue' // @ts-ignore import FormMosaicSupplyChangeTransaction from '@/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransaction.vue' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ components: { @@ -51,11 +44,14 @@ import FormMosaicSupplyChangeTransaction from '@/views/forms/FormMosaicSupplyCha FormExtendNamespaceDurationTransaction, FormMosaicSupplyChangeTransaction, }, - computed: {...mapGetters({ - currentWalletAddress: 'wallet/currentWalletAddress', - ownedMosaics: 'wallet/currentWalletOwnedMosaics', - ownedNamespaces: 'wallet/currentWalletOwnedNamespaces', - })}, + computed: { + ...mapGetters({ + currentHeight: 'network/currentHeight', + ownedMosaics: 'mosaic/ownedMosaics', + ownedNamespaces: 'namespace/ownedNamespaces', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class TableDisplayTs extends Vue { /** @@ -67,24 +63,28 @@ export class TableDisplayTs extends Vue { }) assetType: string /** - * Loading state of the data to be shown in the table - * @type {boolean} - */ + * Loading state of the data to be shown in the table + * @type {boolean} + */ @Prop({default: false}) loading: boolean /** * Current wallet owned mosaics * @protected - * @type {MosaicInfo[]} + * @type {MosaicModel[]} */ - protected ownedMosaics: MosaicInfo[] + private ownedMosaics: MosaicModel[] /** * Current wallet owned namespaces * @protected - * @type {NamespaceInfo[]} + * @type {NamespaceModel[]} */ - protected ownedNamespaces: NamespaceInfo[] + private ownedNamespaces: NamespaceModel[] + + private currentHeight: number + + private networkConfiguration: NetworkConfigurationModel /** * Current table sorting state @@ -93,17 +93,10 @@ export class TableDisplayTs extends Vue { public sortedBy: TableSortingOptions = {fieldName: undefined, direction: undefined} /** - * Current table filtering state - * @var {TableFilteringOptions} - */ - public filteredBy: TableFilteringOptions = {fieldName: undefined, filteringType: undefined} - - /** - * Current wallet address - * @private - * @type {Address} + * Current table filtering state + * @var {TableFilteringOptions} */ - private currentWalletAddress: Address + public filteredBy: TableFilteringOptions = {fieldName: undefined, filteringType: undefined} /** * Pagination page size @@ -121,8 +114,8 @@ export class TableDisplayTs extends Vue { protected get ownedAssetHexIds(): string[] { return this.assetType === 'namespace' - ? this.ownedNamespaces.map(({id}) => id.toHex()) - : this.ownedMosaics.map(({id}) => id.toHex()) + ? this.ownedNamespaces.map(({namespaceIdHex}) => namespaceIdHex) + : this.ownedMosaics.map(({mosaicIdHex}) => mosaicIdHex) } /** @@ -165,6 +158,7 @@ export class TableDisplayTs extends Vue { aliasAction: null, mosaicId: null, } + // Alias forms props /** @@ -173,12 +167,10 @@ export class TableDisplayTs extends Vue { */ protected getService(): AssetTableService { if ('mosaic' === this.assetType) { - return new MosaicTableService(this.$store) - } - else if ('namespace' === this.assetType) { - return new NamespaceTableService(this.$store) + return new MosaicTableService(this.currentHeight, this.ownedMosaics) + } else if ('namespace' === this.assetType) { + return new NamespaceTableService(this.currentHeight, this.ownedNamespaces, this.networkConfiguration) } - throw new Error(`Asset type '${this.assetType}' does not exist in TableDisplay.`) } @@ -233,6 +225,7 @@ export class TableDisplayTs extends Vue { return this.modalFormsProps.aliasAction === AliasAction.Link ? 'modal_title_link_alias' : 'modal_title_unlink_alias' } + /// end-region getters and setters /** @@ -252,14 +245,14 @@ export class TableDisplayTs extends Vue { * Refreshes the owned assets * @returns {void} */ - private async refresh(): Promise { - if (this.assetType === 'namespace') { - this.$store.dispatch('wallet/REST_FETCH_OWNED_NAMESPACES', this.currentWalletAddress.plain()) + private refresh(): void { + if ('mosaic' === this.assetType) { + this.$store.dispatch('mosaic/LOAD_MOSAICS') + } else if ('namespace' === this.assetType) { + this.$store.dispatch('namespace/LOAD_NAMESPACES') } - - const mosaics = await this.$store.dispatch('wallet/REST_FETCH_OWNED_MOSAICS', this.currentWalletAddress.plain()) - new MosaicService(this.$store).refreshMosaicModels(mosaics, true) } + /** * Sets the default filtering state */ @@ -295,7 +288,7 @@ export class TableDisplayTs extends Vue { public setFilteredBy(fieldName: string): void { const filteredBy = {...this.filteredBy} const filteringType: FilteringTypes = filteredBy.fieldName === fieldName - && filteredBy.filteringType === 'show' ? 'hide' : 'show' + && filteredBy.filteringType === 'show' ? 'hide' : 'show' this.filteredBy = {fieldName, filteringType} } @@ -307,7 +300,7 @@ export class TableDisplayTs extends Vue { public setSortedBy(fieldName: string): void { const sortedBy = {...this.sortedBy} const direction: SortingDirections = sortedBy.fieldName === fieldName - && sortedBy.direction === 'asc' + && sortedBy.direction === 'asc' ? 'desc' : 'asc' Vue.set(this, 'sortedBy', {fieldName, direction}) @@ -349,7 +342,7 @@ export class TableDisplayTs extends Vue { // populate asset form modal props if asset is a namespace if (this.assetType === 'namespace') { this.modalFormsProps.namespaceId = new NamespaceId(rowValues.name), - this.modalFormsProps.aliasTarget = rowValues.aliasIdentifier === 'N/A' ? null : rowValues.aliasIdentifier + this.modalFormsProps.aliasTarget = rowValues.aliasIdentifier === 'N/A' ? null : rowValues.aliasIdentifier ? getInstantiatedAlias(rowValues.aliasType, rowValues.aliasIdentifier) : null this.modalFormsProps.aliasAction = rowValues.aliasIdentifier === 'N/A' ? AliasAction.Link : AliasAction.Unlink } diff --git a/src/components/TransactionDetails/Alias/Alias.vue b/src/components/TransactionDetails/Alias/Alias.vue index acc9054be..bc0a8570f 100644 --- a/src/components/TransactionDetails/Alias/Alias.vue +++ b/src/components/TransactionDetails/Alias/Alias.vue @@ -12,13 +12,8 @@ diff --git a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue index b3605a229..bf83e7aae 100644 --- a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue +++ b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue @@ -16,7 +16,7 @@ @@ -30,12 +30,10 @@ diff --git a/src/components/WalletNameDisplay/WalletNameDisplayTs.ts b/src/components/WalletNameDisplay/WalletNameDisplayTs.ts index cb49d1e72..52f056e46 100644 --- a/src/components/WalletNameDisplay/WalletNameDisplayTs.ts +++ b/src/components/WalletNameDisplay/WalletNameDisplayTs.ts @@ -17,7 +17,7 @@ import {Component, Vue, Prop} from 'vue-property-decorator' import {ValidationProvider} from 'vee-validate' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' // child components @@ -40,7 +40,7 @@ export class WalletNameDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel @Prop({ default: false, diff --git a/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplay.vue b/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplay.vue index 9f89ff99e..a1b3982ce 100644 --- a/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplay.vue +++ b/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplay.vue @@ -2,11 +2,11 @@
{{ $t('Wallet_public_key') }}
- {{ wallet.objects.publicAccount.publicKey }} + {{ wallet.publicKey }}
diff --git a/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplayTs.ts b/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplayTs.ts index 26dbd4148..60b877a8d 100644 --- a/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplayTs.ts +++ b/src/components/WalletPublicKeyDisplay/WalletPublicKeyDisplayTs.ts @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' - +import {Component, Prop, Vue} from 'vue-property-decorator' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' @Component @@ -24,7 +23,7 @@ export class WalletPublicKeyDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers diff --git a/src/components/WalletSelectorField/WalletSelectorField.vue b/src/components/WalletSelectorField/WalletSelectorField.vue index ff2dfbb75..218af4435 100644 --- a/src/components/WalletSelectorField/WalletSelectorField.vue +++ b/src/components/WalletSelectorField/WalletSelectorField.vue @@ -9,9 +9,9 @@ :class="{'select-size select-style': defaultFormStyle, 'max-z-index': true }" > {{ name }} diff --git a/src/components/WalletSelectorField/WalletSelectorFieldTs.ts b/src/components/WalletSelectorField/WalletSelectorFieldTs.ts index 777e5c266..7b855c60b 100644 --- a/src/components/WalletSelectorField/WalletSelectorFieldTs.ts +++ b/src/components/WalletSelectorField/WalletSelectorFieldTs.ts @@ -1,29 +1,32 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {WalletService} from '@/services/WalletService' -@Component({computed: {...mapGetters({ - currentWallet: 'wallet/currentWallet', - knownWallets: 'wallet/knownWallets', -})}}) +@Component({ + computed: { + ...mapGetters({ + currentWallet: 'wallet/currentWallet', + knownWallets: 'wallet/knownWallets', + }), + }, +}) export class WalletSelectorFieldTs extends Vue { @Prop({ default: null, @@ -36,9 +39,9 @@ export class WalletSelectorFieldTs extends Vue { /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Known wallets identifiers @@ -50,46 +53,33 @@ export class WalletSelectorFieldTs extends Vue { * Wallets repository * @var {WalletService} */ - public service: WalletService + public readonly walletService: WalletService = new WalletService() - public created() { - this.service = new WalletService(this.$store) - } /// region computed properties getter/setter public get currentWalletIdentifier(): string { if (this.value) return this.value if (this.currentWallet) { - return {...this.currentWallet}.identifier + return this.currentWallet.id } // fallback value return '' } - public set currentWalletIdentifier(identifier: string) { - if (!identifier || !identifier.length) return + public set currentWalletIdentifier(id: string) { + if (!id || !id.length) return - this.$emit('input', identifier) + this.$emit('input', id) - const wallet = this.service.getWallet(identifier) + const wallet = this.walletService.getWallet(id) if (!wallet) return } - public get currentWallets(): {identifier: string, name: string}[] { - if (!this.knownWallets || !this.knownWallets.length) { - return [] - } - - // filter wallets to only known wallet names - const knownWallets = this.service.getWallets( - (e) => this.knownWallets.includes(e.getIdentifier()), - ) - - return [...knownWallets].map( - ({identifier, values}) => ({identifier, name: values.get('name')}), - ) + public get currentWallets(): WalletModel[] { + return this.walletService.getKnownWallets(this.knownWallets) } + /// end-region computed properties getter/setter } diff --git a/src/components/WalletSelectorPanel/WalletSelectorPanel.vue b/src/components/WalletSelectorPanel/WalletSelectorPanel.vue index 703a8f356..b25136ab2 100644 --- a/src/components/WalletSelectorPanel/WalletSelectorPanel.vue +++ b/src/components/WalletSelectorPanel/WalletSelectorPanel.vue @@ -8,17 +8,15 @@
-
diff --git a/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts b/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts index eec43ec33..c178a4e7a 100644 --- a/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts +++ b/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,17 +15,13 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {MosaicId, NetworkType, AccountInfo, Address} from 'symbol-sdk' +import {MosaicId, NetworkType} from 'symbol-sdk' import {ValidationProvider} from 'vee-validate' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {WalletService} from '@/services/WalletService' -import {MosaicService} from '@/services/MosaicService' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {WalletsRepository} from '@/repositories/WalletsRepository' - // child components // @ts-ignore import MosaicAmountDisplay from '@/components/MosaicAmountDisplay/MosaicAmountDisplay.vue' @@ -37,6 +33,8 @@ import FormLabel from '@/components/FormLabel/FormLabel.vue' import ModalFormSubWalletCreation from '@/views/modals/ModalFormSubWalletCreation/ModalFormSubWalletCreation.vue' // @ts-ignore import ModalMnemonicExport from '@/views/modals/ModalMnemonicExport/ModalMnemonicExport.vue' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' +import {MosaicModel} from '@/core/database/entities/MosaicModel' @Component({ components: { @@ -46,16 +44,26 @@ import ModalMnemonicExport from '@/views/modals/ModalMnemonicExport/ModalMnemoni FormLabel, ValidationProvider, ModalMnemonicExport, - }, - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - currentWallet: 'wallet/currentWallet', - knownWallets: 'wallet/knownWallets', - knownWalletsInfo: 'wallet/knownWalletsInfo', - networkMosaic: 'mosaic/networkMosaic', - })}}) + }, + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + currentWallet: 'wallet/currentWallet', + knownWallets: 'wallet/knownWallets', + networkType: 'network/networkType', + mosaics: 'mosaic/mosaics', + networkMosaic: 'mosaic/networkMosaic', + networkCurrency: 'mosaic/networkCurrency', + }), + }, +}) export class WalletSelectorPanelTs extends Vue { + + /** + * The network currency. + */ + public networkCurrency: NetworkCurrencyModel + /** * Currently active networkType * @see {Store.Network} @@ -68,27 +76,20 @@ export class WalletSelectorPanelTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Known wallets identifiers * @var {string[]} */ public knownWallets: string[] - - /** - * Currently active wallet's balances - * @var {Mosaic[]} - */ - public knownWalletsInfo: AccountInfo[] - /** * Networks currency mosaic * @var {MosaicId} @@ -96,25 +97,27 @@ export class WalletSelectorPanelTs extends Vue { public networkMosaic: MosaicId /** - * Wallets repository - * @var {WalletService} + * Current wallet owned mosaics + * @private + * @type {MosaicInfo[]} */ - public service: WalletService + private mosaics: MosaicModel[] /** - * Temporary storage of clicked wallets - * @var {WalletsModel} + * Wallets repository + * @var {WalletService} */ - public clickedWallet: WalletsModel + public walletService: WalletService /** * Whether user is currently adding a wallet (modal) * @var {boolean} */ - public isAddingWallet: boolean = false/** - * Whether currently viewing export - * @var {boolean} - */ + public isAddingWallet: boolean = false + /** + * Whether currently viewing export + * @var {boolean} + */ public isViewingExportModal: boolean = false /** @@ -123,118 +126,49 @@ export class WalletSelectorPanelTs extends Vue { */ public validationRules = ValidationRuleset - public addressesBalances: any = {} - - /** - * the toggle of the Spin - * @type string - */ - public isLoading: boolean = true /** * Hook called when the component is created * @return {void} */ public async created() { - this.service = new WalletService(this.$store) - const mosaicService = new MosaicService(this.$store) - - // - read known addresses - const repository = new WalletsRepository() - const addresses = Array.from(repository.entries( - (w: WalletsModel) => this.knownWallets.includes(w.getIdentifier()), - ).values()).map(w => Address.createFromRawAddress(w.values.get('address'))) - - // - fetch latest accounts infos (1 request) - const knownWalletsInfo = await this.$store.dispatch('wallet/REST_FETCH_INFOS', addresses) - - // - filter available wallets info - const knownWallets = knownWalletsInfo.filter( - info => { - const wallet = Array.from(repository.entries( - (w: WalletsModel) => info.address.plain() === w.values.get('address'), - ).values()) - - return wallet.length > 0 - }) - - if (!knownWallets.length) { - this.isLoading = false - } - - // - format balances - for (let i = 0, m = knownWallets.length; i < m; i ++) { - const currentInfo = knownWallets[i] - - // read info and balance - const address = currentInfo.address.plain() - const netBalance = currentInfo.mosaics.find(m => m.id.equals(this.networkMosaic)) - - // store relative balance - const balance = await mosaicService.getRelativeAmount( - {...netBalance}.amount.compact(), - this.networkMosaic, - ) - - Vue.set(this.addressesBalances, address, balance) - - // remove spin - this.isLoading = false - } + this.walletService = new WalletService() } /// region computed properties getter/setter - public get balances(): any { - return this.addressesBalances + public get balances(): Map { + const networkMosaics = this.mosaics.filter(m => m.mosaicIdHex === this.networkMosaic.toHex()) + return Object.assign({}, ...networkMosaics.map( + s => ({[s.addressRawPlain]: s.balance / Math.pow(10, this.networkCurrency.divisibility)}))) + // return this.addressesBalances } public get currentWalletIdentifier(): string { - return !this.currentWallet ? '' : {...this.currentWallet}.identifier + return !this.currentWallet ? '' : this.currentWallet.id } - public set currentWalletIdentifier(identifier: string) { - if (!identifier || !identifier.length) { - return + public set currentWalletIdentifier(id: string) { + if (!id || !id.length) { + return } - const wallet = this.service.getWallet(identifier) + const wallet = this.walletService.getWallet(id) if (!wallet) { - return + return } - if (!this.currentWallet || identifier !== this.currentWallet.getIdentifier()) { - this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: wallet}) - this.$emit('input', wallet.getIdentifier()) + if (!this.currentWallet || id !== this.currentWallet.id) { + this.$store.dispatch('wallet/SET_CURRENT_WALLET', wallet) + this.$emit('input', wallet.id) } } - public get currentWallets(): { - identifier: string - address: string - name: string - type: number - isMultisig: boolean - path: string - }[] { + public get currentWallets(): WalletModel[] { if (!this.knownWallets || !this.knownWallets.length) { return [] } - // filter wallets to only known wallet names - const knownWallets = this.service.getWallets( - (e) => this.knownWallets.includes(e.getIdentifier()), - ) - - return [...knownWallets].map( - ({identifier, values}) => ({ - identifier, - address: values.get('address'), - name: values.get('name'), - type: values.get('type'), - isMultisig: values.get('isMultisig'), - path: values.get('path'), - }), - ) + return this.walletService.getKnownWallets(this.knownWallets) } public get hasAddWalletModal(): boolean { @@ -248,10 +182,11 @@ export class WalletSelectorPanelTs extends Vue { public get hasMnemonicExportModal(): boolean { return this.isViewingExportModal } - + public set hasMnemonicExportModal(f: boolean) { this.isViewingExportModal = f } + /// end-region computed properties getter/setter /** @@ -260,19 +195,6 @@ export class WalletSelectorPanelTs extends Vue { * @return {boolean} */ public isActiveWallet(item): boolean { - return item.identifier === this.currentWallet.getIdentifier() - } - - /** - * Whether the wallet item is a seed wallet - * @param item - * @return {boolean} - */ - public isSeedWallet(item): boolean { - return item.type === WalletType.SEED - } - - public getBalance(item): number { - return this.addressesBalances[item.address] + return item.id === this.currentWallet.id } } diff --git a/src/core/database/AppDatabase.ts b/src/core/database/AppDatabase.ts deleted file mode 100644 index 5d4538f1f..000000000 --- a/src/core/database/AppDatabase.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {SimpleStorageAdapter} from './SimpleStorageAdapter' -import {LocalStorageBackend} from '@/core/database/backends/LocalStorageBackend' -import {JSONFormatter} from '@/core/database/formatters/JSONFormatter' -import {DatabaseTable} from '@/core/database/DatabaseTable' -import {AccountsTable} from '@/core/database/entities/AccountsTable' -import {WalletsTable} from '@/core/database/entities/WalletsTable' -import {PeersTable} from '@/core/database/entities/PeersTable' -import {MosaicsTable} from '@/core/database/entities/MosaicsTable' -import {NamespacesTable} from '@/core/database/entities/NamespacesTable' -import {SettingsTable} from '@/core/database/entities/SettingsTable' - -export class AppDatabase { - - public static getAdapter(): SimpleStorageAdapter{ - const adapter = new SimpleStorageAdapter( - new LocalStorageBackend(), - new JSONFormatter(), - ) - - // - configure database tables - adapter.setSchemas(new Map([ - [ 'accounts', new AccountsTable() ], - [ 'wallets', new WalletsTable() ], - [ 'endpoints', new PeersTable() ], - [ 'mosaics', new MosaicsTable() ], - [ 'namespaces', new NamespacesTable() ], - [ 'settings', new SettingsTable() ], - ])) - - return adapter - } -} diff --git a/src/core/database/BaseStorageAdapter.ts b/src/core/database/BaseStorageAdapter.ts deleted file mode 100644 index aa577eb8c..000000000 --- a/src/core/database/BaseStorageAdapter.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {IStorageAdapter} from './IStorageAdapter' -import {DatabaseTable} from './DatabaseTable' -import {DatabaseModel} from './DatabaseModel' -import {IStorageBackend} from './backends/IStorageBackend' -import {LocalStorageBackend} from './backends/LocalStorageBackend' -import {ObjectStorageBackend} from './backends/ObjectStorageBackend' -import {AbstractFormatter} from './formatters/AbstractFormatter' -import {JSONFormatter} from './formatters/JSONFormatter' - -export abstract class BaseStorageAdapter -implements IStorageAdapter { - /** - * Storage backend - * @var {IStorageBackend} - */ - public readonly storage: IStorageBackend - - /** - * Data formatter - * @var {AbstractFormatter} - */ - public readonly formatter: AbstractFormatter - - /** - * List of database table schemas - * @var {Map - - /** - * Private constructor (singleton pattern) - * @access private - */ - public constructor( - storageBackend: IStorageBackend = !!localStorage ? new LocalStorageBackend() : new ObjectStorageBackend(), - dataFormatter: AbstractFormatter = new JSONFormatter(), - ) { - this.storage = storageBackend - this.formatter = dataFormatter - this.schemas = new Map() - } - - /** - * Getter for table schemas - * @return {BaseStorageAdapter} - */ - public setSchemas(schemas: Map): BaseStorageAdapter { - this.schemas = schemas - return this - } - - /** - * Find schema by *alias* or by *table name*. - * @param {string} schemaId - * @return {DatabaseTable} - */ - public getSchema(schemaId: string): DatabaseTable { - let schemaKey: string = schemaId - if (!this.schemas.has(schemaKey)) { - // try to find aliased schema - schemaKey = [...this.schemas.keys()].find( - s => this.schemas.get(s).tableName === schemaId, - ) - - // catch unregistered schema - if (schemaKey === undefined) { - throw new Error(`Schema with identifier '${schemaId}' is not registered.`) - } - } - - return this.schemas.get(schemaKey) - } - - /** - * Read and parse data for schema with \a schemaId - * @param {string} schemaId - * @return {Map} - */ - public read(schemaId: string): Map { - // key can be alias or table name - // read schema from storage backend - const schema = this.getSchema(schemaId) - - // read from storage - const data = this.storage.getItem(schema.tableName) - if (!data || data === null || !data.length) { - return new Map() - } - - // valid stored data to identify invalid data format - if (!this.formatter.validate(data)) { - throw new Error(`Data stored for schema '${schemaId}' does not comply with JSONFormatter derivate.`) - } - - // map on-the-fly + validate singular entities format - const rows: Map = this.formatter.parse(schema, data) - - // identify out-of-date entities and run migrations - if (!this.checkSchemaVersion(schema, rows)) { - // there is at least one out-of-date entity, database needs migration(s) - const migratedRows = schema.migrateRows(rows) - - // persist migrated rows - this.write(schemaId, migratedRows) - return migratedRows // return *up-to-date* always - } - - return rows - } - - /** - * Read and parse data for schema with \a schemaId - * @param {Map} entities - * @return {number} The count of entities written - */ - public write(schemaId: string, entities: Map): number { - // key can be alias or table name - // read schema from storage backend - const schema = this.getSchema(schemaId) - - // format data - const data = this.formatter.format(schema, entities) - - // persist formatted data to storage - this.storage.setItem(schemaId, data) - return entities.size - } - - /** - * Iterates entities to find *out-of-date* data schemas. - * @param {DatabaseTable} schema - * @param {Map} rows - * @return {boolean} True if rows are up to date, false if any requires migration - */ - protected checkSchemaVersion( - schema: DatabaseTable, - rows: Map, - ): boolean { - const migratees = Array.from(rows.values()).filter( - model => !model.values.has('version') - || model.values.get('version') < schema.version, - ) - - return !migratees.length - } -} diff --git a/src/core/database/DatabaseModel.ts b/src/core/database/DatabaseModel.ts deleted file mode 100644 index 28ec87cc0..000000000 --- a/src/core/database/DatabaseModel.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {SHA3Hasher, Convert} from 'symbol-sdk' - -// internal dependencies -import {DatabaseRelation} from './DatabaseRelation' -import {AESEncryptionService} from '@/services/AESEncryptionService' - -export abstract class DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map - - /** - * Values of the model instance - * @var {Map} - */ - public values: Map - - /** - * Whether the current instance has dirty fields - * @var {boolean} - */ - public isDirty: boolean - - /** - * Entity identifier - * @var {string} - */ - public identifier: string - - /** - * Construct a database model instance - * @param tableName - * @param columns - * @param types - */ - public constructor( - primaryKeys: string[] = [], - values: Map = new Map(), - ) { - this.primaryKeys = primaryKeys - this.values = values - this.identifier = this.getIdentifier() - this.isDirty = false - } - - /** - * Getter for the *row* identifier - * @return {string} - */ - public getIdentifier(): string { - if (!this.primaryKeys.length) { - throw new Error('Primary keys must be described in derivate DatabaseModel classes.') - } - - return this.primaryKeys.map(pk => { - let val = this.values.get(pk) - if (!val && pk === 'id') { - val = this.generateIdentifier() - this.values.set('id', val) - } - return val - }).join('-') - } - - /** - * Returns true when all primary key *values* are set - * @return {boolean} - */ - public hasIdentifier(): boolean { - if (!this.primaryKeys.length) { - throw new Error('Primary keys must be described in derivate DatabaseModel classes.') - } - - // check value of *all* primary keys - for (let i = 0, m = this.primaryKeys.length; i < m; i ++) { - if (!this.values.has(this.primaryKeys[i])) { - return false - } - } - - return true - } - - /** - * Generate an identifier for a model instance from its fields - * @return {string} - */ - protected generateIdentifier(): string { - const raw = { - time: new Date().valueOf(), - seed: AESEncryptionService.generateRandomBytes(8), - } - - const fields = this.values.keys() - const values = this.values.values() - for (let j = null; !(j = fields.next()).done;) { - const field = j.value - if (!field.length) { - continue - } - raw[field] = values.next().value - } - - // to-json - const json = JSON.stringify(raw) - const hasher = SHA3Hasher.createHasher(64) - hasher.reset() - hasher.update(Convert.utf8ToHex(json)) - - const hash = new Uint8Array(64) - hasher.finalize(hash) - return Convert.uint8ToHex(hash).substr(0, 16) - } - - /** - * Update values - * @param {Map} values - * @return {DatabaseModel} - * @throws {Error} On overwrite of primary key with different value - */ - public update(values: Map): DatabaseModel { - this.values = values - this.isDirty = true - return this - } - - /** - * Update one field's value - * @param {string} field - * @param {any} value - * @return {DatabaseModel} - */ - public updateField(field: string, value: any): DatabaseModel { - this.values.set(field, value) - this.isDirty = true - return this - } - - /** - * Permits to return specific field's mapped object instances - * @return any - */ - public get objects(): any { - return {} - } -} diff --git a/src/core/database/DatabaseRelation.ts b/src/core/database/DatabaseRelation.ts deleted file mode 100644 index 525f2c318..000000000 --- a/src/core/database/DatabaseRelation.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -export enum DatabaseRelationType { - ONE_TO_ONE = 1, - ONE_TO_MANY = 2, - MANY_TO_MANY = 3, -} - -export class DatabaseRelation { - /** - * Relation type - * @var {DatabaseRelationType} - */ - public type: DatabaseRelationType - - /** - * Construct a database model instance - * @param tableName - * @param columns - * @param types - */ - public constructor( - type: DatabaseRelationType, - ) { - this.type = type - } -} diff --git a/src/core/database/DatabaseTable.ts b/src/core/database/DatabaseTable.ts deleted file mode 100644 index 7d07b07fb..000000000 --- a/src/core/database/DatabaseTable.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from './DatabaseModel' - -/// region custom types -export type DatabaseMigration = { - version: number - callback: (rows: Map) => Map -} -/// end-region custom types - -export abstract class DatabaseTable { - /** - * Table name - * @var {string} - */ - public tableName: string - - /** - * List of column names (field names) - * @var {string[]} - */ - public columns: string[] - - /** - * Version of the table schema - * @var {number} - */ - public version: number - - /** - * Construct a database table instance - * @param tableName - * @param columns - * @param types - */ - public constructor( - tableName: string, - columns: string[] = [], - version: number = 0, - ) { - this.tableName = tableName - this.columns = columns - this.version = version - } - - /// region abstract methods - /** - * Create a new model instance - * @return {DatabaseModel} - */ - public abstract createModel(values: Map): DatabaseModel - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public abstract getMigrations(): DatabaseMigration[] - /// end-region abstract methods - - /** - * Execute database migrations if any are needed - * @param {Map} rows - * @return {Map} Migrated rows - */ - public migrateRows( - rows: Map, - ): Map { - if (!rows.size) { - // no migration needed - return rows - } - - // always check if rows schema are up to date - const tempRow = rows.values().next().value - const dataVersion = tempRow.values.has('version') - ? tempRow.values.has('version') - : 0 - - // filter migration by min version - const migrations = this.getMigrations().filter(m => m.version >= dataVersion) - const migratees = Array.from(rows.values()).filter( - model => !model.values.has('version') - || model.values.get('version') < this.version, - ) - - console.log(`migrating ${migratees.length} rows for table ${this.tableName} to version ${this.version}`) - - if (!migratees.length || !migrations.length) { - // no migration needed - return rows - } - - for (let i = 0, m = migrations.length; i < m; i ++) { - const migration = migrations[i] - - // execute migration - rows = migration.callback(rows) - } - - return rows - } -} diff --git a/src/core/database/IStorageAdapter.ts b/src/core/database/IStorageAdapter.ts deleted file mode 100644 index 879161077..000000000 --- a/src/core/database/IStorageAdapter.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseTable} from './DatabaseTable' -import {DatabaseModel} from './DatabaseModel' -import {IStorageBackend} from './backends/IStorageBackend' -import {AbstractFormatter} from './formatters/AbstractFormatter' - -export interface IStorageAdapter { - /** - * Storage backend - * @var {IStorageBackend} - */ - storage: IStorageBackend - - /** - * Data formatter - * @var {AbstractFormatter} - */ - formatter: AbstractFormatter - - /** - * List of database table schemas - * @var {Map - - /** - * Getter for table schemas - * @return {Map} - */ - setSchemas(schemas: Map): IStorageAdapter - - /** - * Read and parse data for schema with \a schemaId - * @param {string} schemaId - * @return {Map} - */ - read(schemaId: string): Map - - /** - * Read and parse data for schema with \a schemaId - * @param {Map} entities - * @return {number} The count of entities written - */ - write(schemaId: string, entities: Map): number -} diff --git a/src/core/database/SimpleStorageAdapter.ts b/src/core/database/SimpleStorageAdapter.ts deleted file mode 100644 index 4112d0f54..000000000 --- a/src/core/database/SimpleStorageAdapter.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Password} from 'symbol-sdk' -import CryptoJS from 'crypto-js' - -// internal dependencies -import {BaseStorageAdapter} from './BaseStorageAdapter' -import {AbstractFormatter} from './formatters/AbstractFormatter' -import {JSONFormatter} from './formatters/JSONFormatter' -import {IStorageBackend} from './backends/IStorageBackend' -import {LocalStorageBackend} from './backends/LocalStorageBackend' -import {ObjectStorageBackend} from './backends/ObjectStorageBackend' -import {AESEncryptionService} from '@/services/AESEncryptionService' - -export class SimpleStorageAdapter - extends BaseStorageAdapter { - - /** - * Construct a simple storage adapter - * @param {IStorageBackend} storageBackend - * @param {IDataFormatter} dataFormatter - */ - public constructor( - storageBackend: IStorageBackend = !!localStorage ? new LocalStorageBackend() : new ObjectStorageBackend(), - dataFormatter: AbstractFormatter = new JSONFormatter(), - ) { - super(storageBackend, dataFormatter) - } - - /** - * Get session id from storage backend - * @return {string} - */ - public getSessionId(): string { - let sessionId: string = this.storage.getItem('sessionId') - if (null === sessionId) { - // sessionId doesn't exist, generate new - sessionId = CryptoJS.lib.WordArray.random(32).toString() - - // store sessionId - this.storage.setItem('sessionId', sessionId) - return sessionId - } - - return sessionId - } - - /** - * Get salt for encrypted storage session - * @return {string} - */ - public getSaltForSession(): string { - const sessionId: string = this.getSessionId() - let accessSalt: string = this.storage.getItem('accessSalt') - - if (null === accessSalt) { - // salt doesn't exist, generate new - accessSalt = CryptoJS.lib.WordArray.random(32).toString() - - // encrypt salt with \a sessionId - const ciphertext = AESEncryptionService.encrypt(accessSalt, new Password(sessionId)) - - // store encrypted salt - this.storage.setItem('accessSalt', ciphertext) - return accessSalt - } - - // decrypt stored encrypted salt with \a sessionId - return AESEncryptionService.decrypt(accessSalt, new Password(sessionId)) - } -} diff --git a/src/core/database/backends/IStorageBackend.ts b/src/core/database/backends/IStorageBackend.ts index f5511c537..6ab8c86f9 100644 --- a/src/core/database/backends/IStorageBackend.ts +++ b/src/core/database/backends/IStorageBackend.ts @@ -33,4 +33,10 @@ export interface IStorageBackend { * @param {any} value */ setItem(key: string, value: any): void + + /** + * Deletes the value for the given key + * @param {string} key + */ + removeItem(key: string): void } diff --git a/src/core/database/backends/LocalStorageBackend.ts b/src/core/database/backends/LocalStorageBackend.ts index 5502cf465..99892ccf7 100644 --- a/src/core/database/backends/LocalStorageBackend.ts +++ b/src/core/database/backends/LocalStorageBackend.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -36,7 +36,7 @@ export class LocalStorageBackend implements IStorageBackend { /** * Getter for value with \a key - * @param {string} key + * @param {string} key * @return {any} */ public getItem(key: string): any { @@ -51,4 +51,12 @@ export class LocalStorageBackend implements IStorageBackend { public setItem(key: string, value: any): void { localStorage.setItem(key, value) } + + /** + * Deletes the value for the given key + * @param {string} key + */ + public removeItem(key: string): void { + localStorage.removeItem(key) + } } diff --git a/src/core/database/backends/ObjectStorageBackend.ts b/src/core/database/backends/ObjectStorageBackend.ts index d3152ffe6..22f97a215 100644 --- a/src/core/database/backends/ObjectStorageBackend.ts +++ b/src/core/database/backends/ObjectStorageBackend.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,7 +25,7 @@ export class ObjectStorageBackend implements IStorageBackend { /** * Construct an object storage backend - * @param backend + * @param backend */ public constructor(backend: any = {}) { this.backend = backend @@ -49,7 +49,7 @@ export class ObjectStorageBackend implements IStorageBackend { /** * Getter for value with \a key - * @param {string} key + * @param {string} key * @return {any} */ public getItem(key: string): any { @@ -68,4 +68,12 @@ export class ObjectStorageBackend implements IStorageBackend { public setItem(key: string, value: any): void { this.backend[key] = value } + + /** + * Deletes the value for the given key + * @param {string} key + */ + public removeItem(key: string): void { + delete this.backend[key] + } } diff --git a/src/core/database/backends/SimpleObjectStorage.ts b/src/core/database/backends/SimpleObjectStorage.ts new file mode 100644 index 000000000..8fd66bc2a --- /dev/null +++ b/src/core/database/backends/SimpleObjectStorage.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {IStorageBackend} from '@/core/database/backends/IStorageBackend' +import {LocalStorageBackend} from '@/core/database/backends/LocalStorageBackend' +import {ObjectStorageBackend} from '@/core/database/backends/ObjectStorageBackend' +import {AESEncryptionService} from '@/services/AESEncryptionService' +import {Convert, SHA3Hasher} from 'symbol-sdk' + +/** + * A super simple object storage that keeps one object in a local storage table. + * + * This object is generic, you can type it with the class of object that it's going to be stored. + * The object could be a simple object, an array or a Map/Record with key->value. + * + */ +export class SimpleObjectStorage { + + /** + * The Storage backend, if localStorage is not available the storage will be in memory. + */ + private readonly storageBackend: IStorageBackend + + public constructor( + private readonly storageKey, + ) { + this.storageBackend = !!localStorage ? new LocalStorageBackend() : new ObjectStorageBackend() + } + + /** + * @return the stored value or undefined + */ + public get(): E | undefined { + const item = this.storageBackend.getItem(this.storageKey) + return item ? JSON.parse(item) : undefined + } + + /** + * Stores the provided value. + * @param value to be stored + */ + public set(value: E): void { + this.storageBackend.setItem(this.storageKey, JSON.stringify(value)) + } + + /** + * Deletes the stored value. + */ + public remove(): void { + this.storageBackend.removeItem(this.storageKey) + } + + /** + * Helper that generates an identifier base on the object value + * + * @param object the object used feed the generator. + */ + public static generateIdentifier(object: object | undefined = undefined): string { + const raw = { + ...{ + time: new Date().valueOf(), + seed: AESEncryptionService.generateRandomBytes(8), + }, ...(object || {}), + } + // to-json + const json = JSON.stringify(raw) + const hasher = SHA3Hasher.createHasher(64) + hasher.reset() + hasher.update(Convert.utf8ToHex(json)) + + const hash = new Uint8Array(64) + hasher.finalize(hash) + return Convert.uint8ToHex(hash).substr(0, 16) + } +} diff --git a/src/core/database/entities/AccountModel.ts b/src/core/database/entities/AccountModel.ts new file mode 100644 index 000000000..b531de016 --- /dev/null +++ b/src/core/database/entities/AccountModel.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NetworkType} from 'symbol-sdk' + +/** + * Stored POJO that holds account information. + * + * The stored data is cached from rest. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + * + */ +export class AccountModel { + + public readonly accountName: string + public readonly generationHash: string + public readonly hint: string + public readonly networkType: NetworkType + public readonly password: string + public readonly seed: string + public readonly wallets: string[] +} diff --git a/src/core/database/entities/AccountsModel.ts b/src/core/database/entities/AccountsModel.ts deleted file mode 100644 index 8318b56b3..000000000 --- a/src/core/database/entities/AccountsModel.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation, DatabaseRelationType} from '@/core/database/DatabaseRelation' - -export class AccountsModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'accountName', - ] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map([ - [ 'wallets', new DatabaseRelation(DatabaseRelationType.ONE_TO_MANY) ], - ]) - - /** - * Construct an account model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['accountName'], values) - } -} diff --git a/src/core/database/entities/AccountsTable.ts b/src/core/database/entities/AccountsTable.ts deleted file mode 100644 index 12df1fc68..000000000 --- a/src/core/database/entities/AccountsTable.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {AccountsModel} from '@/core/database/entities/AccountsModel' - -/// region database migrations -import {AccountsMigrations} from '@/core/database/migrations/accounts/AccountsMigrations' -/// end-region database migrations - -export class AccountsTable extends DatabaseTable { - public constructor() { - super('accounts', [ - 'accountName', - 'wallets', - 'password', - 'hint', - 'networkType', - 'seed', - 'generationHash', - ], 2) // version=2 - } - - /** - * Create a new model instance - * @param {Map} values - * @return {AccountsModel} - */ - public createModel(values: Map = new Map()): AccountsModel { - return new AccountsModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): DatabaseMigration[] { - return [ - {version: 2, callback: AccountsMigrations.version2_addGenHash}, - ] - } -} diff --git a/src/core/database/entities/MosaicConfigurationModel.ts b/src/core/database/entities/MosaicConfigurationModel.ts new file mode 100644 index 000000000..767366a7e --- /dev/null +++ b/src/core/database/entities/MosaicConfigurationModel.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +/** + * POJO that stores user provided configuration for mosaics. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + */ +export class MosaicConfigurationModel { + constructor(public readonly hidden: boolean = false) { + } +} diff --git a/src/core/database/entities/MosaicModel.ts b/src/core/database/entities/MosaicModel.ts new file mode 100644 index 000000000..3eb53775b --- /dev/null +++ b/src/core/database/entities/MosaicModel.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {MosaicInfo} from 'symbol-sdk' + +/** + * Stored POJO that holds mosaic information. + * + * The stored data is cached from rest. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + * + */ +export class MosaicModel { + + public readonly mosaicIdHex: string + public readonly divisibility: number + public readonly transferable: boolean + public readonly supplyMutable: boolean + public readonly restrictable: boolean + public readonly duration: number + public readonly height: number + public readonly supply: number + + constructor( + public readonly addressRawPlain: string, + public readonly ownerRawPlain: string, + public readonly name: string | undefined, + public readonly isCurrencyMosaic: boolean, + public readonly balance: number | undefined, + mosaicInfo: MosaicInfo, + ) { + this.mosaicIdHex = mosaicInfo.id.toHex() + this.divisibility = mosaicInfo.divisibility + this.transferable = mosaicInfo.isTransferable() + this.supplyMutable = mosaicInfo.isSupplyMutable() + this.restrictable = mosaicInfo.isRestrictable() + this.duration = mosaicInfo.duration.compact() + this.height = mosaicInfo.height.compact() + this.supply = mosaicInfo.supply.compact() + + } +} diff --git a/src/core/database/entities/MosaicsModel.ts b/src/core/database/entities/MosaicsModel.ts deleted file mode 100644 index fdbb6f427..000000000 --- a/src/core/database/entities/MosaicsModel.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation} from '@/core/database/DatabaseRelation' -import {UInt64, MosaicId, RawUInt64, MosaicInfo, PublicAccount, NetworkType, MosaicFlags} from 'symbol-sdk' - -// Custom types -export interface BalanceEntry { - id: MosaicId - name: string - amount: number -} - -export class MosaicsModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'hexId', - ] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map() - - /** - * Construct a mosaic model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['hexId'], values) - } - - /** - * Permits to return specific field's mapped object instances - * @return any - */ - public get objects(): { - mosaicId: MosaicId - mosaicInfo: MosaicInfo - } { - const hexId = this.getIdentifier() - const mosaicId = new MosaicId(RawUInt64.fromHex(hexId)) - const ownerPub = PublicAccount.createFromPublicKey( - this.values.get('ownerPublicKey'), - NetworkType.MIJIN_TEST, // ignored - ) - - return { - mosaicId, - mosaicInfo: new MosaicInfo( - mosaicId, - UInt64.fromHex(this.values.get('supply')), - UInt64.fromHex(this.values.get('startHeight')), - ownerPub, - 1, - new MosaicFlags(this.values.get('flags')), - this.values.get('divisibility'), - UInt64.fromHex(this.values.get('duration')), - ), - } - } -} diff --git a/src/core/database/entities/MosaicsTable.ts b/src/core/database/entities/MosaicsTable.ts deleted file mode 100644 index 53d578f47..000000000 --- a/src/core/database/entities/MosaicsTable.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable} from '@/core/database/DatabaseTable' -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' - -/// region database migrations -import {MosaicsMigrations} from '@/core/database/migrations/mosaics/MosaicsMigrations' -/// end-region database migrations - -export class MosaicsTable extends DatabaseTable { - public constructor() { - super('mosaics', [ - 'hexId', - 'name', - 'flags', - 'startHeight', - 'duration', - 'supply', - 'divisibility', - 'ownerPublicKey', - 'generationHash', - 'isCurrencyMosaic', - 'isHarvestMosaic', - 'isHidden', - ], 6) // version=6 - } - - /** - * Create a new model instance - * @return {MosaicsModel} - */ - public createModel(values: Map = new Map()): MosaicsModel { - return new MosaicsModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): { - version: number - callback: (rows: Map) => Map - }[] { - return [ - {version: 2, callback: MosaicsMigrations.version2_addGenHash}, - {version: 3, callback: MosaicsMigrations.version3_newSymbol}, - {version: 4, callback: MosaicsMigrations.version4_flagsAsNumber}, - {version: 5, callback: MosaicsMigrations.version5_uint_as_hex}, - {version: 6, callback: MosaicsMigrations.version6_isHidden}, - ] - } -} diff --git a/src/core/database/entities/NamespaceModel.ts b/src/core/database/entities/NamespaceModel.ts new file mode 100644 index 000000000..1c6267c2e --- /dev/null +++ b/src/core/database/entities/NamespaceModel.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NamespaceInfo} from 'symbol-sdk' + +/** + * Stored POJO that holds namespace information. + * + * The stored data is cached from rest. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be + * fined. + * + */ +export class NamespaceModel { + + public readonly namespaceIdHex: string + public readonly name: string + public readonly isRoot: boolean + public readonly ownerAddressRawPlain: string | undefined + public readonly aliasType: number + public readonly aliasTargetAddressRawPlain: string | undefined + public readonly aliasTargetMosaicIdHex: string | undefined + public readonly parentNamespaceIdHex: string | undefined + public readonly startHeight: number + public readonly endHeight: number + public readonly depth: number + + + constructor(namespaceInfo: NamespaceInfo, name: string) { + this.namespaceIdHex = namespaceInfo.id.toHex() + this.name = name + this.isRoot = namespaceInfo.isRoot() + this.aliasType = namespaceInfo.alias.type + this.ownerAddressRawPlain = namespaceInfo.owner.address.plain() + this.aliasTargetAddressRawPlain = namespaceInfo.alias && namespaceInfo.alias.address && + namespaceInfo.alias.address.plain() || undefined + this.aliasTargetMosaicIdHex = namespaceInfo.alias && namespaceInfo.alias.mosaicId && + namespaceInfo.alias.mosaicId.toHex() || undefined + this.parentNamespaceIdHex = this.isRoot ? undefined : namespaceInfo.parentNamespaceId().toHex() + this.startHeight = namespaceInfo.startHeight.compact() + this.endHeight = namespaceInfo.endHeight.compact() + this.depth = namespaceInfo.depth + } + +} diff --git a/src/core/database/entities/NamespacesModel.ts b/src/core/database/entities/NamespacesModel.ts deleted file mode 100644 index 091467b98..000000000 --- a/src/core/database/entities/NamespacesModel.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation} from '@/core/database/DatabaseRelation' -import {NamespaceInfo, NamespaceRegistrationType, PublicAccount, UInt64, NamespaceId, Alias, MosaicId, Address, MosaicAlias, EmptyAlias, AddressAlias} from 'symbol-sdk' - -export class NamespacesModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'hexId', - ] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map() - - /** - * Construct a namespace model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['hexId'], values) - } - - /** - * Permits to return specific field's mapped object instances - * @readonly - * @return {{ - * namespaceInfo: NamespaceInfo, - * }} - */ - public get objects(): { namespaceInfo: NamespaceInfo } { - const registrationType = this.values.get('parentId') === undefined - ? NamespaceRegistrationType.RootNamespace : NamespaceRegistrationType.SubNamespace - - const levels = [ this.values.get('level0'), this.values.get('level1'), this.values.get('level2') ] - .filter(x => x).map(hexId => NamespaceId.createFromEncoded(hexId)) - - const owner = PublicAccount.createFromPublicKey( - this.values.get('ownerPublicKey'), this.values.get('generationHash'), - ) - - // instantiate a namespace alias - const getAlias = (alias: any): Alias => { - if (alias.mosaicId) return new MosaicAlias(new MosaicId(alias.mosaicId)) - if (alias.address) return new AddressAlias(Address.createFromRawAddress(alias.address)) - return new EmptyAlias() - } - return { - namespaceInfo: new NamespaceInfo( - this.values.get('active'), - 0, - '', - registrationType, - this.values.get('depth'), - levels, - this.values.get('parentId'), - owner, - UInt64.fromHex(this.values.get('startHeight')), - UInt64.fromHex(this.values.get('endHeight')), - getAlias(this.values.get('alias')), - ), - } - } -} diff --git a/src/core/database/entities/NamespacesTable.ts b/src/core/database/entities/NamespacesTable.ts deleted file mode 100644 index b5486ad6b..000000000 --- a/src/core/database/entities/NamespacesTable.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' -import {NamespacesMigrations} from '../migrations/namespaces/NamespacesMigrations' - -export class NamespacesTable extends DatabaseTable { - public constructor() { - super('namespaces', [ - 'hexId', - 'name', - 'active', - 'depth', - 'level0', - 'level1', - 'level2', - 'alias', - 'parentId', - 'startHeight', - 'endHeight', - 'ownerPublicKey', - 'generationHash', - ], 3) // version=3 - } - - /** - * Create a new model instance - * @return {NamespacesModel} - */ - public createModel(values: Map = new Map()): NamespacesModel { - return new NamespacesModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): DatabaseMigration[] { - return [ - {version: 3, callback: NamespacesMigrations.version3_uint_as_hex}, - ] - } -} diff --git a/src/core/database/entities/NetworkConfigurationModel.ts b/src/core/database/entities/NetworkConfigurationModel.ts new file mode 100644 index 000000000..877be4966 --- /dev/null +++ b/src/core/database/entities/NetworkConfigurationModel.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing software + * distributed under the License is distributed on an AS IS BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +export class NetworkConfigurationModel { + + public readonly maxMosaicDivisibility: number + public readonly namespaceGracePeriodDuration: number + public readonly lockedFundsPerAggregate: string + public readonly maxCosignatoriesPerAccount: number + public readonly blockGenerationTargetTime: number + public readonly maxNamespaceDepth: number + public readonly maxMosaicDuration: number + public readonly minNamespaceDuration: number + public readonly maxNamespaceDuration: number + public readonly maxTransactionsPerAggregate: number + public readonly maxCosignedAccountsPerAccount: number + public readonly maxMessageSize: number + public readonly maxMosaicAtomicUnits: number +} diff --git a/src/core/database/entities/NetworkCurrencyModel.ts b/src/core/database/entities/NetworkCurrencyModel.ts new file mode 100644 index 000000000..b157d7bfa --- /dev/null +++ b/src/core/database/entities/NetworkCurrencyModel.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +/** + * Stored POJO that holds network currency information. + * + * The stored data is cached from rest. + * + */ +export class NetworkCurrencyModel { + + constructor( + /** + * Mosaic id of this currency. This value is optional if the user only wants to provide the + * mosaic id. This value will be set if it's loaded by rest. + */ + public readonly mosaicIdHex: string | undefined, + /** + * The Namespace id of this currency. This value is option if the user only wants to provide the + * namespace id. This value will be set if it's loaded by rest. + */ + public readonly namespaceIdHex: string | undefined, + /** + * The Namespace id of this currency. This value is option if the user only wants to provide the + * namespace id. This value will be set if it's loaded by rest. + */ + public readonly namespaceIdFullname: string | undefined, + /** + * Divisibility of this currency, required to create Mosaic from relative amounts. + */ + public readonly divisibility: number, + /** + * Is this currency transferable. + */ + public readonly transferable: boolean, + /** + * Is this currency supply mutable. + */ + public readonly supplyMutable: boolean, + /** + * Is this currency restrictable. + */ + public readonly restrictable: boolean, + /** + * The ticket name like XYM when namespace fullname is symbol.xym + */ + public readonly ticker: string | undefined, + ) { + } +} diff --git a/src/core/database/entities/NetworkModel.ts b/src/core/database/entities/NetworkModel.ts new file mode 100644 index 000000000..64220773a --- /dev/null +++ b/src/core/database/entities/NetworkModel.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NetworkType, NodeInfo} from 'symbol-sdk' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + +/** + * Stored POJO that holds network information. + * + * The stored data is cached from rest. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + * + */ +export class NetworkModel { + + constructor(public readonly url: string, + public readonly networkType: NetworkType, + public readonly generationHash: string, + public readonly networkConfiguration: NetworkConfigurationModel, + public readonly nodeInfo: NodeInfo, + ) { + + } + +} diff --git a/src/core/database/entities/NodeModel.ts b/src/core/database/entities/NodeModel.ts new file mode 100644 index 000000000..62e5362c8 --- /dev/null +++ b/src/core/database/entities/NodeModel.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +/** + * Stored POJO that holds node information. + * + * The stored data is cached from rest. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + * + */ +export class NodeModel { + constructor( + public readonly url: string, + public readonly friendlyName: string, + ) { + + } +} diff --git a/src/core/database/entities/PeersModel.ts b/src/core/database/entities/PeersModel.ts deleted file mode 100644 index 308bad9a7..000000000 --- a/src/core/database/entities/PeersModel.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation} from '@/core/database/DatabaseRelation' - -export class PeersModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'id', - ] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map() - - /** - * Construct a peer model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['id'], values) - } - - /** - * Permits to return specific field's mapped object instances - * @return any - */ - public get objects(): any { - const url = `${this.values.get('protocol') - + this.values.get('host')}:${ - this.values.get('port')}` - - return {url} - } -} diff --git a/src/core/database/entities/PeersTable.ts b/src/core/database/entities/PeersTable.ts deleted file mode 100644 index 2c7f7b6c6..000000000 --- a/src/core/database/entities/PeersTable.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {PeersModel} from '@/core/database/entities/PeersModel' - -/// region database migrations -import {PeersMigrations} from '@/core/database/migrations/endpoints/PeersMigrations' -/// end-region database migrations - -export class PeersTable extends DatabaseTable { - public constructor() { - super('endpoints', [ - 'id', - 'rest_url', - 'host', - 'port', - 'protocol', - 'networkType', - 'generationHash', - 'roles', - 'is_default', - 'friendly_name', - ], 2) // version=2 - } - - /** - * Create a new model instance - * @return {PeersModel} - */ - public createModel(values: Map = new Map()): PeersModel { - return new PeersModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): DatabaseMigration[] { - return [ - {version: 2, callback: PeersMigrations.version2_addGenHash}, - ] - } -} diff --git a/src/core/database/entities/SettingsModel.ts b/src/core/database/entities/SettingsModel.ts index ff1f40e23..94247b023 100644 --- a/src/core/database/entities/SettingsModel.ts +++ b/src/core/database/entities/SettingsModel.ts @@ -1,44 +1,32 @@ -/** +/* * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. + * */ -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation} from '@/core/database/DatabaseRelation' - -export class SettingsModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'accountName', - ] +/** + * POJO that stores user provided general application settings. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + */ +export class SettingsModel { - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map([]) + constructor( + public readonly accountName: string, + public readonly language: string, + public readonly defaultFee: number, + public readonly defaultWallet: string, + public readonly explorerUrl: string, + ) { - /** - * Construct a wallet model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['accountName'], values) } } diff --git a/src/core/database/entities/SettingsTable.ts b/src/core/database/entities/SettingsTable.ts deleted file mode 100644 index d75382282..000000000 --- a/src/core/database/entities/SettingsTable.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {SettingsModel} from '@/core/database/entities/SettingsModel' - -export class SettingsTable extends DatabaseTable { - public constructor() { - super('settings', [ - 'accountName', - 'default_fee,', - 'language', - 'explorer_url', - 'default_wallet', - ]) - } - - /** - * Create a new model instance - * @return {SettingsModel} - */ - public createModel(values: Map = new Map()): SettingsModel { - return new SettingsModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): DatabaseMigration[] { - return [] - } -} diff --git a/src/core/database/entities/WalletModel.ts b/src/core/database/entities/WalletModel.ts new file mode 100644 index 000000000..6e5b38fd9 --- /dev/null +++ b/src/core/database/entities/WalletModel.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {Address, PublicAccount} from 'symbol-sdk' + +export class WalletType { + public static readonly SEED: number = 1 + public static readonly PRIVATE_KEY = 2 + public static readonly KEYSTORE = 3 + public static readonly TREZOR = 4 + + public static fromDescriptor(descriptor: string) { + switch (descriptor) { + default: + case 'Ks': + return WalletType.KEYSTORE + case 'Pk': + return WalletType.PRIVATE_KEY + case 'Seed': + return WalletType.SEED + case 'Trezor': + return WalletType.TREZOR + } + } +} + + +/** + * Stored POJO that holds user provided wallet information. + * + * The object is serialized and deserialized to/from JSON. no method or complex attributes can be fined. + */ +export class WalletModel { + + public readonly id: string + public readonly name: string + public readonly accountName: string + public readonly node: string + public readonly type: number + public readonly address: string + public readonly publicKey: string + public readonly encPrivate: string + public readonly encIv: string + public readonly path: string + public readonly isMultisig: boolean + + /** + * Permits to return specific field's mapped object instances + * @return any + */ + public static getObjects(model: WalletModel): { address: Address, publicAccount: PublicAccount } { + const address = Address.createFromRawAddress(model.address) + const publicAccount = PublicAccount.createFromPublicKey(model.publicKey, address.networkType) + return {address, publicAccount} + } + +} diff --git a/src/core/database/entities/WalletsModel.ts b/src/core/database/entities/WalletsModel.ts deleted file mode 100644 index 8187b7cf2..000000000 --- a/src/core/database/entities/WalletsModel.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Address, PublicAccount} from 'symbol-sdk' - -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseRelation} from '@/core/database/DatabaseRelation' - -export class WalletType { - public static readonly SEED: number = 1 - public static readonly PRIVATE_KEY = 2 - public static readonly KEYSTORE = 3 - public static readonly TREZOR = 4 - - public static fromDescriptor(descriptor: string) { - switch(descriptor) { - default: - case 'Ks': return WalletType.KEYSTORE - case 'Pk': return WalletType.PRIVATE_KEY - case 'Seed': return WalletType.SEED - case 'Trezor': return WalletType.TREZOR - } - } -} - -export class WalletsModel extends DatabaseModel { - /** - * Entity identifier *field names*. The identifier - * is a combination of the values separated by '-' - * @var {string[]} - */ - public primaryKeys: string[] = [ - 'id', - ] - - /** - * Entity relationships - * @var {Map} - */ - public relations: Map = new Map([]) - - /** - * Construct a wallet model instance - * - * @param {Map} values - */ - public constructor(values: Map = new Map()) { - super(['id'], values) - } - - /** - * Permits to return specific field's mapped object instances - * @return any - */ - public get objects(): any { - const address = Address.createFromRawAddress(this.values.get('address')) - const publicAccount = PublicAccount.createFromPublicKey(this.values.get('publicKey'), address.networkType) - return {address, publicAccount} - } -} diff --git a/src/core/database/entities/WalletsTable.ts b/src/core/database/entities/WalletsTable.ts deleted file mode 100644 index 50472343b..000000000 --- a/src/core/database/entities/WalletsTable.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseTable, DatabaseMigration} from '@/core/database/DatabaseTable' -import {WalletsModel} from '@/core/database/entities/WalletsModel' - -export class WalletsTable extends DatabaseTable { - public constructor() { - super('wallets', [ - 'id', - 'accountName', - 'name', - 'type', - 'address', - 'publicKey', - 'encPrivate', - 'encIv', - 'path', - 'isMultisig', - ]) - } - - /** - * Create a new model instance - * @return {WalletsModel} - */ - public createModel(values: Map = new Map()): WalletsModel { - return new WalletsModel(values) - } - - /** - * Returns a list of migration callbacks to execute - * for database versioning. - * @return {any[]} - */ - public getMigrations(): DatabaseMigration[] { - return [] - } -} diff --git a/src/core/database/formatters/AbstractFormatter.ts b/src/core/database/formatters/AbstractFormatter.ts deleted file mode 100644 index d0b378ebb..000000000 --- a/src/core/database/formatters/AbstractFormatter.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '../DatabaseModel' -import {DatabaseTable} from '../DatabaseTable' - -export abstract class AbstractFormatter { - - /** - * The schema used for instance creation - * @var {DatabaseTable} - */ - protected $schema: DatabaseTable - - /** - * Setter for active database table (schema) - * @param {DatabaseTable} schema - * @return {AbstractFormatter} - */ - public setSchema(schema: DatabaseTable): AbstractFormatter { - this.$schema = schema - return this - } - - /** - * Getter for active database table (schema) - * @return {DatabaseTable} - */ - public getSchema(): DatabaseTable { - return this.$schema - } - - /** - * Validate format of \a data and throw exception - * if not valid. - * @param {string} data - * @return {boolean} - * @throws {Error} On invalid JSON \a data - */ - public assertFormat(data: string): boolean { - if (this.validate(data) === true) { - return true - } - - throw new Error(`Expected JSON format for data but got: ${data}`) - } - - /// region abstract methods - /** - * Format an \a entity - * @param {DatabaseModel} entity - * @return {string} - */ - public abstract format( - schema: DatabaseTable, - entities: Map - ): string - - /** - * Parse formatted \a data to entities - * @param {string} data - * @return {Map} - */ - public abstract parse( - schema: DatabaseTable, - data: string - ): Map - - /** - * Validate format of \a data - * @param {string} data - * @return {boolean} - */ - public abstract validate(data: string): boolean - /// end-region abstract methods -} diff --git a/src/core/database/formatters/JSONFormatter.ts b/src/core/database/formatters/JSONFormatter.ts deleted file mode 100644 index 1ad5e80c3..000000000 --- a/src/core/database/formatters/JSONFormatter.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '../DatabaseModel' -import {AbstractFormatter} from './AbstractFormatter' -import { DatabaseTable } from '../DatabaseTable' - -export class JSONFormatter - extends AbstractFormatter { - - /** - * Format an \a entity - * @param {DatabaseModel} entity - * @return {string} - */ - public format( - schema: DatabaseTable, - entities: Map, - ): string { - // format each entity individually - const iterator = entities.keys() - const data: {} = {} - for (let i = 0, m = entities.size; i < m; i ++) { - // read next identifier and model data - const id = iterator.next().value - if (!id.length) { - continue - } - - // read model - const dto = entities.get(id) - const identifier = dto.getIdentifier() - - // expose only "values" from model + add VERSION - const raw = {} - const row: Map = dto.values - const keys = row.keys() - const values = row.values() - for (let j = null; !(j = keys.next()).done;) { - const field = j.value - if (!field.length) { - continue - } - raw[field] = values.next().value - } - - // add schema version - raw['version'] = schema.version - - // entities stored by id - data[identifier] = raw - } - - return JSON.stringify(data) - } - - /** - * Parse formatted \a data to entities - * @param {string} data - * @return {Map} - */ - public parse( - schema: DatabaseTable, - data: string, - ): Map { - let parsed = {} - try { - parsed = JSON.parse(data) - if (!parsed || parsed === null) { - return new Map() - } - } - catch(e) { return new Map() } - - if (!Object.keys(parsed).length) { - return new Map() - } - - // map entities to model instances by identifier - const entities = new Map() - Object.keys(parsed).map((identifier: string) => { - // read entity by identifier - const entity = parsed[identifier] - const fields = Object.keys(entity) - const values = [] - fields.map(field => values.push([ field, entity[field] ])) - - // create model & populate fields - const model = schema.createModel(new Map(values)) - - // store by identifier - entities.set(model.getIdentifier(), model) - }) - - return entities - } - - /** - * Validate format of \a data - * @param {string} data - * @return {boolean} - */ - public validate(data: string): boolean { - try { - if (!data || data === null || !data.length) { - return false - } - - JSON.parse(data) - return true - } - catch (e) { - return false - } - - return false - } -} diff --git a/src/core/database/migrations/accounts/AccountsMigrations.ts b/src/core/database/migrations/accounts/AccountsMigrations.ts deleted file mode 100644 index 706f74c2d..000000000 --- a/src/core/database/migrations/accounts/AccountsMigrations.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' - -export class AccountsMigrations { - /** - * Version 2 migration (first) - * - * @description Changed table structure - * - * Table columns added: - * - generationHash - * - * @params {Map} rows - * @return {Map} - */ - public static version2_addGenHash( - rows: Map, - ): Map { - - const entities = Array.from(rows.values()) - const migrated = new Map() - - // each row must be migrated (added table columns) - entities.map((outOfDate: AccountsModel) => { - outOfDate.values.set('generationHash', '45870419226A7E51D61D94AD728231EDC6C9B3086EF9255A8421A4F26870456A') - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } -} diff --git a/src/core/database/migrations/endpoints/PeersMigrations.ts b/src/core/database/migrations/endpoints/PeersMigrations.ts deleted file mode 100644 index 20d55eb20..000000000 --- a/src/core/database/migrations/endpoints/PeersMigrations.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {PeersModel} from '@/core/database/entities/PeersModel' - -export class PeersMigrations { - /** - * Version 2 migration (first) - * - * @description Changed table structure - * - * Table columns added: - * - rest_url - * - generationHash - * - roles - * - is_default - * - friendly_name - * - * @params {Map} rows - * @return {Map} - */ - public static version2_addGenHash( - rows: Map, - ): Map { - - const entities = Array.from(rows.values()) - const migrated = new Map() - - // each row must be migrated (added table columns) - entities.map((outOfDate: PeersModel, i: number) => { - outOfDate.values.set('rest_url', outOfDate.objects.url) - outOfDate.values.set('generationHash', '45870419226A7E51D61D94AD728231EDC6C9B3086EF9255A8421A4F26870456A') - outOfDate.values.set('roles', outOfDate.values.get('host').indexOf('harvest') ? 3 : 2) - outOfDate.values.set('is_default', i === 0) - outOfDate.values.set('friendly_name', '') - - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } -} diff --git a/src/core/database/migrations/mosaics/MosaicsMigrations.ts b/src/core/database/migrations/mosaics/MosaicsMigrations.ts deleted file mode 100644 index 94c610752..000000000 --- a/src/core/database/migrations/mosaics/MosaicsMigrations.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' -import {UInt64} from 'symbol-sdk' - -export class MosaicsMigrations { - /** - * Version 2 migration (first) - * - * @description Changed table structure - * - * Table columns added: - * - generationHash', - * - isCurrencyMosaic', - * - isHarvestMosaic', - * - * @params {Map} rows - * @return {Map} - */ - public static version2_addGenHash( - rows: Map, - ): Map { - - const entities = Array.from(rows.values()) - const migrated = new Map() - - // each row must be migrated (added table columns) - entities.map((outOfDate: MosaicsModel) => { - outOfDate.values.set('generationHash', '45870419226A7E51D61D94AD728231EDC6C9B3086EF9255A8421A4F26870456A') - outOfDate.values.set('isCurrencyMosaic', outOfDate.values.get('name') === 'symbol.xym') - outOfDate.values.set('isHarvestMosaic', outOfDate.values.get('name') === 'symbol.xym') - - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } - - /** - * Version 3 migration - * @description Setting new symbol.xym network mosaics - * @param rows - * @returns {Map} - */ - public static version3_newSymbol( - rows: Map, - ): Map { - const entities = Array.from(rows.values()) - const migrated = new Map() - - // each row must be migrated (added table columns) - entities.map((outOfDate: MosaicsModel) => { - if ('symbol.xym' === outOfDate.values.get('name')) { - outOfDate.values.set('isCurrencyMosaic', true) - outOfDate.values.set('isHarvestMosaic', true) - } - }) - - return migrated - } - - /** - * Version 4 migration - * @description Setting storing flags as numbers - * @param {Map} rows - * @returns {Map} - */ - public static version4_flagsAsNumber( - rows: Map, - ): Map { - const entities = Array.from(rows.values()) - const migrated = new Map() - entities.map((outOfDate: MosaicsModel) => { - outOfDate.values.set('flags', outOfDate.values.get('flags').flags) - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } - - /** - * Version 5 migration - * @description Uint64 should be stored as hex numbers - * @param {Map} rows - * @returns {Map} - */ - public static version5_uint_as_hex( - rows: Map, - ): Map { - const entities = Array.from(rows.values()) - const migrated = new Map() - entities.forEach((outOfDate: MosaicsModel) => { - const oldSupply = outOfDate.values.get('supply') - const newSupply = new UInt64([ oldSupply.lower, oldSupply.higher ]) - outOfDate.values.set('supply', newSupply.toHex()) - - const oldStartHeight = outOfDate.values.get('startHeight') - const newStartHeight = new UInt64([ oldStartHeight.lower, oldStartHeight.higher ]) - outOfDate.values.set('startHeight', newStartHeight.toHex()) - - const oldDuration = outOfDate.values.get('duration') - const newDuration = UInt64.fromUint(oldDuration) - outOfDate.values.set('duration', newDuration.toHex()) - - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } - - /** - * Version 6 migration - * @description add isHidden field - * @param {Map} rows - * @returns {Map} - */ - public static version6_isHidden( - rows: Map, - ): Map { - const entities = Array.from(rows.values()) - const migrated = new Map() - entities.forEach((outOfDate: MosaicsModel) => { - outOfDate.values.set('isHidden', false) - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } -} diff --git a/src/core/database/migrations/namespaces/NamespacesMigrations.ts b/src/core/database/migrations/namespaces/NamespacesMigrations.ts deleted file mode 100644 index ab9cb058c..000000000 --- a/src/core/database/migrations/namespaces/NamespacesMigrations.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// external dependencies -import {UInt64, AliasType, MosaicId} from 'symbol-sdk' - -// internal dependencies -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' - -export class NamespacesMigrations { - /** - * Version 3 migration - * @description Uint64 should be stored as hex numbers - * @param rows - * @returns {Map} - */ - public static version3_uint_as_hex( - rows: Map, - ): Map { - const entities = Array.from(rows.values()) - const migrated = new Map() - - entities.forEach((outOfDate: NamespacesModel) => { - const oldStartHeight = outOfDate.values.get('startHeight') - const newStartHeight = UInt64.fromUint(oldStartHeight) - outOfDate.values.set('startHeight', newStartHeight.toHex()) - - const oldEndHeight = outOfDate.values.get('endHeight') - const newEndHeight = UInt64.fromUint(oldEndHeight) - outOfDate.values.set('endHeight', newEndHeight.toHex()) - - // alias migration - const oldAlias = outOfDate.values.get('alias') - - // if alias is a mosaic, convert the mosaicId to an hex number - if (oldAlias && oldAlias.type == AliasType.Mosaic) { - const oldMosaicId = new MosaicId([ oldAlias.mosaicId.id.lower, oldAlias.mosaicId.id.higher ]) - const newMosaicId = oldMosaicId.toHex() - outOfDate.values.set('alias', {type: oldAlias.type, mosaicId: newMosaicId}) - } - - // if alias is an address, just store the rawAddress - if (oldAlias && oldAlias.type == AliasType.Address) { - outOfDate.values.set('alias', {type: oldAlias.type, address: oldAlias.address.address}) - } - - migrated.set(outOfDate.getIdentifier(), outOfDate) - }) - - return migrated - } -} diff --git a/src/core/transactions/TransactionFactory.ts b/src/core/transactions/TransactionFactory.ts index 0bc35fb24..07d6da58b 100644 --- a/src/core/transactions/TransactionFactory.ts +++ b/src/core/transactions/TransactionFactory.ts @@ -1,13 +1,13 @@ /** - * + * * Copyright 2020 Grégory Saive for NEM (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,7 +30,6 @@ import { Transaction, TransferTransaction, } from 'symbol-sdk' - // internal dependencies import {ViewAliasTransaction} from './ViewAliasTransaction' import {ViewMosaicDefinitionTransaction} from './ViewMosaicDefinitionTransaction' @@ -49,6 +48,7 @@ type TransactionViewType = ViewAliasTransaction | ViewNamespaceRegistrationTransaction | ViewTransferTransaction | ViewUnknownTransaction + /// end-region custom types export class TransactionFactory { @@ -67,13 +67,13 @@ export class TransactionFactory { public build(view: ViewMultisigAccountModificationTransaction): MultisigAccountModificationTransaction public build(view: ViewNamespaceRegistrationTransaction): NamespaceRegistrationTransaction public build(view: ViewTransferTransaction): TransferTransaction - + /// end-region specialised signatures /** * Create a REST repository instance around \a serviceOpts * @param {string} name - * @param {string} nodeUrl + * @param {string} nodeUrl */ public build( view: TransactionViewType, @@ -93,8 +93,7 @@ export class TransactionFactory { networkType, view.values.get('maxFee'), ) - } - else if (view instanceof ViewMosaicSupplyChangeTransaction) { + } else if (view instanceof ViewMosaicSupplyChangeTransaction) { return MosaicSupplyChangeTransaction.create( deadline, view.values.get('mosaicId'), @@ -103,8 +102,7 @@ export class TransactionFactory { networkType, view.values.get('maxFee'), ) - } - else if (view instanceof ViewNamespaceRegistrationTransaction) { + } else if (view instanceof ViewNamespaceRegistrationTransaction) { // - sub namespace if (NamespaceRegistrationType.SubNamespace === view.values.get('registrationType')) { return NamespaceRegistrationTransaction.createSubNamespace( @@ -123,8 +121,7 @@ export class TransactionFactory { networkType, view.values.get('maxFee'), ) - } - else if (view instanceof ViewTransferTransaction) { + } else if (view instanceof ViewTransferTransaction) { return TransferTransaction.create( deadline, view.values.get('recipient'), @@ -133,8 +130,7 @@ export class TransactionFactory { networkType, view.values.get('maxFee'), ) - } - else if (view instanceof ViewMultisigAccountModificationTransaction) { + } else if (view instanceof ViewMultisigAccountModificationTransaction) { return MultisigAccountModificationTransaction.create( deadline, parseInt(view.values.get('minApprovalDelta'), 10), @@ -144,8 +140,7 @@ export class TransactionFactory { networkType, view.values.get('maxFee'), ) - } - else if (view instanceof ViewAliasTransaction) { + } else if (view instanceof ViewAliasTransaction) { if (view.values.get('aliasTarget') instanceof Address) { return AddressAliasTransaction.create( deadline, diff --git a/src/core/transactions/ViewHashLockTransaction.ts b/src/core/transactions/ViewHashLockTransaction.ts index 769a02b18..3146bcddf 100644 --- a/src/core/transactions/ViewHashLockTransaction.ts +++ b/src/core/transactions/ViewHashLockTransaction.ts @@ -1,13 +1,13 @@ /** - * + * * Copyright 2020 Grégory Saive for NEM (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,11 +15,18 @@ * limitations under the License. */ // external dependencies -import {HashLockTransaction, SignedTransaction, MosaicId, UInt64, RawUInt64, Mosaic} from 'symbol-sdk' - +import { + HashLockTransaction, + Mosaic, + MosaicId, + RawUInt64, + SignedTransaction, + UInt64, +} from 'symbol-sdk' // internal dependencies import {TransactionView} from './TransactionView' -import {AttachedMosaic, MosaicService} from '@/services/MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {AttachedMosaic} from '@/services/MosaicService' export type HashLockTransactionFormFieldsType = { mosaic: { mosaicHex: string, amount: number } @@ -48,9 +55,9 @@ export class ViewHashLockTransaction extends TransactionView i.id.toHex() === formItems.mosaic.mosaicHex) + const mosaicInfo = mosaicsInfo.find(i => i.mosaicIdHex === formItems.mosaic.mosaicHex) const mosaicDivisibility = mosaicInfo ? mosaicInfo.divisibility : 0 // - create mosaic object const mosaic = new Mosaic( @@ -89,10 +96,13 @@ export class ViewHashLockTransaction extends TransactionView { @@ -73,15 +74,15 @@ export class ViewNamespaceRegistrationTransaction extends TransactionView n.namespaceIdHex === parentId.toHex() && n.name) + if (parent) { + this.values.set('rootNamespaceName', parent.name) } } diff --git a/src/core/transactions/ViewTransferTransaction.ts b/src/core/transactions/ViewTransferTransaction.ts index 1ce670d0e..1fea5856b 100644 --- a/src/core/transactions/ViewTransferTransaction.ts +++ b/src/core/transactions/ViewTransferTransaction.ts @@ -1,24 +1,34 @@ /** - * + * * Copyright 2020 Grégory Saive for NEM (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Address, Mosaic, MosaicId, NamespaceId, UInt64, RawUInt64, PlainMessage, EmptyMessage, TransferTransaction} from 'symbol-sdk' - +import { + Address, + EmptyMessage, + Mosaic, + MosaicId, + NamespaceId, + PlainMessage, + RawUInt64, + TransferTransaction, + UInt64, +} from 'symbol-sdk' // internal dependencies import {TransactionView} from './TransactionView' -import {MosaicService} from '@/services/MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {AttachedMosaic} from '@/services/MosaicService' /// region custom types export type TransferFormFieldsType = { @@ -30,6 +40,7 @@ export type TransferFormFieldsType = { message?: string maxFee: UInt64 } + /// end-region custom types export class ViewTransferTransaction extends TransactionView { @@ -58,11 +69,11 @@ export class ViewTransferTransaction extends TransactionView { - const info = mosaicsInfo.find(i => i.id.toHex() === spec.mosaicHex) + const info = mosaicsInfo.find(i => i.mosaicIdHex === spec.mosaicHex) const div = info ? info.divisibility : 0 // - format amount to absolute @@ -103,10 +114,16 @@ export class ViewTransferTransaction extends TransactionView { + return { + id: transactionMosaic.id, + mosaicHex: transactionMosaic.id.toHex(), + // TODO revisit divisibility!! + amount: transactionMosaic.amount.compact() / Math.pow(10, 0), + } as AttachedMosaic + }) + + this.values.set('mosaics', attachedMosaics) // - set message this.values.set('message', transaction.message) diff --git a/src/core/transactions/ViewUnknownTransaction.ts b/src/core/transactions/ViewUnknownTransaction.ts index f759cdd1f..18d121685 100644 --- a/src/core/transactions/ViewUnknownTransaction.ts +++ b/src/core/transactions/ViewUnknownTransaction.ts @@ -14,8 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {UInt64, Transaction} from 'symbol-sdk' - +import {Transaction, UInt64} from 'symbol-sdk' // internal dependencies import {TransactionView} from './TransactionView' diff --git a/src/core/utils/CacheKey.ts b/src/core/utils/CacheKey.ts deleted file mode 100644 index 89dd3fbc2..000000000 --- a/src/core/utils/CacheKey.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Convert, SHA3Hasher} from 'symbol-sdk' - -export class CacheKey { - - /** - * Cache key query arguments - * @var {any[]} - */ - protected argv: any[] - - /** - * Construct a cache key instance - * @param {any[]} args - */ - private constructor(...args) { - this.argv = args - } - - /** - * Create a cache key around \a args - * @param {any[]} args - * @return {string} - */ - public static create(...args): string { - const key = new CacheKey(args) - return key.getHash() - } - - /** - * Compute the cache key using arguments - * @return {string} - */ - public getHash(): string { - const query = this.argv.reduce( - (prev, cur) => `${prev},${cur}`) - - // create query hash - const hash = new Uint8Array(64) - const hasher = SHA3Hasher.createHasher(64) - hasher.reset() - hasher.update(Buffer.from(query)) - hasher.finalize(hash) - - // return hexadecimal notation of hash - return Convert.uint8ToHex(hash) - } -} diff --git a/src/core/utils/Formatters.ts b/src/core/utils/Formatters.ts index 55e3dbd68..75352ee1f 100644 --- a/src/core/utils/Formatters.ts +++ b/src/core/utils/Formatters.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,6 @@ */ import {Address} from 'symbol-sdk' import {decode} from 'utf8' - // configuration import networkConfig from '../../../config/network.conf.json' @@ -79,4 +78,11 @@ export class Formatters { } } + public static configurationNumberAsString(value: string | undefined): string { + return value ? value.replace(/'/g, '') : '0' + } + + public static configurationNumberAsNumber(value: string | undefined): number { + return parseInt(this.configurationNumberAsString(value)) + } } diff --git a/src/core/utils/NetworkConfigurationHelpers.ts b/src/core/utils/NetworkConfigurationHelpers.ts new file mode 100644 index 000000000..c436822ef --- /dev/null +++ b/src/core/utils/NetworkConfigurationHelpers.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NetworkConfiguration} from 'symbol-sdk' +import {Formatters} from '@/core/utils/Formatters' +import {TimeHelpers} from '@/core/utils/TimeHelpers' + +import networkConfig from '@/../config/network.conf.json' + +/** + * Helper class that retrieves properties from the SDK's NetworkConfiguration object when + * available. + * + * This helper: + * - It enumerates the network configuration properties the wallet uses + * - It handles possible problems when the network configuration coming from the server is + * incomplete. + * - It defines common defaults when properties from unknown + * - It parses configuration values to a format the wallet understands + */ +export class NetworkConfigurationHelpers { + + /** + * This are the absolute defaults if the network is down and the configuration hasn't been cached + * in the local storage. + */ + private static defaults = networkConfig.networkConfigurationDefaults + + public static maxMosaicDivisibility(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.mosaic && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.mosaic.maxMosaicDivisibility)) + || defaultValue || this.defaults.maxMosaicDivisibility + } + + public static maxNamespaceDepth(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.namespace && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.namespace.maxNamespaceDepth)) + || defaultValue || this.defaults.maxNamespaceDepth + } + + + public static namespaceGracePeriodDuration(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.namespace && + TimeHelpers.durationStringToSeconds(networkConfiguration.plugins.namespace.namespaceGracePeriodDuration)) + || defaultValue || this.defaults.namespaceGracePeriodDuration + } + + public static maxCosignatoriesPerAccount(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.multisig && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.multisig.maxCosignatoriesPerAccount)) + || defaultValue || this.defaults.maxCosignatoriesPerAccount + } + + public static blockGenerationTargetTime(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.chain && + TimeHelpers.durationStringToSeconds(networkConfiguration.chain.blockGenerationTargetTime)) + || defaultValue || this.defaults.blockGenerationTargetTime + } + + + public static lockedFundsPerAggregate(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: string | undefined = undefined): string { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.lockhash && + Formatters.configurationNumberAsString(networkConfiguration.plugins.lockhash.lockedFundsPerAggregate)) + || defaultValue || this.defaults.lockedFundsPerAggregate + } + + + public static maxMosaicDuration(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.mosaic && + TimeHelpers.durationStringToSeconds(networkConfiguration.plugins.mosaic.maxMosaicDuration)) + || defaultValue || this.defaults.maxMosaicDuration + } + + + public static minNamespaceDuration(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.namespace && + TimeHelpers.durationStringToSeconds(networkConfiguration.plugins.namespace.minNamespaceDuration)) + || defaultValue || this.defaults.minNamespaceDuration + } + + public static maxNamespaceDuration(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.namespace && + TimeHelpers.durationStringToSeconds(networkConfiguration.plugins.namespace.maxNamespaceDuration)) + || defaultValue || this.defaults.maxNamespaceDuration + } + + public static maxTransactionsPerAggregate(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.aggregate && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.aggregate.maxTransactionsPerAggregate)) + || defaultValue || this.defaults.maxTransactionsPerAggregate + } + + public static maxCosignedAccountsPerAccount(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.multisig && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.multisig.maxCosignedAccountsPerAccount)) + || defaultValue || this.defaults.maxCosignedAccountsPerAccount + } + + public static maxMessageSize(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.plugins && networkConfiguration.plugins.transfer && + Formatters.configurationNumberAsNumber(networkConfiguration.plugins.transfer.maxMessageSize)) + || defaultValue || this.defaults.maxMessageSize + } + + public static maxMosaicAtomicUnits(networkConfiguration: NetworkConfiguration | undefined, + defaultValue: number | undefined = undefined): number { + return (networkConfiguration && networkConfiguration.chain && + Formatters.configurationNumberAsNumber(networkConfiguration.chain.maxMosaicAtomicUnits)) + || defaultValue || this.defaults.maxMosaicAtomicUnits + } +} diff --git a/src/core/utils/ObservableHelpers.ts b/src/core/utils/ObservableHelpers.ts new file mode 100644 index 000000000..755bad8c4 --- /dev/null +++ b/src/core/utils/ObservableHelpers.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {EMPTY, merge, MonoTypeOperatorFunction, of, throwError} from 'rxjs' +import {catchError} from 'rxjs/operators' + + +/** + * Custom observable pipe style operations. + */ +export class ObservableHelpers { + + /** + * This pipe operation concatenates the default values first when provided on top what the + * current observable resolves. if the defaultValue is provided and the current observable fails, + * the error is logged and ignored. If default is not provided, the error is propagated. + * + * The idea is that clients will get a cached version of the data first, then the data will be + * upgraded when returned simulating a faster response. + * + * This observable may send one extra data to the observable. + * + * @param defaultValue the default value to be piped first before the observable. + */ + public static defaultFirst(defaultValue: T | undefined): MonoTypeOperatorFunction { + return observable => merge(defaultValue ? of(defaultValue) : EMPTY, + observable.pipe(catchError(e => { + if (defaultValue) { + return EMPTY + } else { + return throwError(e) + } + }))) + } + + /** + * This pipe operation appends the default data to the observable if this one fails. + * + * If the default data is not provided, the observable error is propagated. If the observable + * succeeds, the default value is ignored. + * + * The idea is that if the response cannot be obtained from rest, the cached data will be used. + * + * @param defaultValue the default value to be provided if the current observable fails. + */ + + public static defaultLast(defaultValue: T | undefined = undefined): MonoTypeOperatorFunction { + return observable => observable.pipe(catchError(e => { + if (defaultValue) { + return of(defaultValue) + } else { + return throwError(e) + } + })) + } +} diff --git a/src/core/utils/RESTDispatcher.ts b/src/core/utils/RESTDispatcher.ts index 65de1622a..3415234f2 100644 --- a/src/core/utils/RESTDispatcher.ts +++ b/src/core/utils/RESTDispatcher.ts @@ -1,30 +1,31 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ + export class RESTDispatcher { /** - * + * */ protected dispatch: ( action: string, payload?: any, - options?: any + options?: any, ) => void /** - * + * */ protected actions: { action: string @@ -34,25 +35,25 @@ export class RESTDispatcher { }[] = [] /** - * - * @param dispatchFn + * + * @param dispatchFn */ public constructor( dispatchFn: ( action: string, payload?: any, - options?: any + options?: any, ) => void, ) { this.dispatch = dispatchFn } /** - * - * @param action - * @param payload - * @param options - * @param isBlocking + * + * @param action + * @param payload + * @param options + * @param isBlocking */ public add( action: string, @@ -87,8 +88,7 @@ export class RESTDispatcher { try { const obs = this.dispatch(action.action, action.payload, action.options) return resolve(obs) - } - catch (e) { + } catch (e) { return reject(e) } }, delay) diff --git a/src/core/utils/TimeHelpers.ts b/src/core/utils/TimeHelpers.ts index d4bb27973..54b8ef941 100644 --- a/src/core/utils/TimeHelpers.ts +++ b/src/core/utils/TimeHelpers.ts @@ -1,22 +1,18 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import networkConfig from '../../../config/network.conf.json' - -// XXX network store getters -const {targetBlockTime} = networkConfig.networks['testnet-publicTest'].properties export class TimeHelpers { public static addZero = function (number: number): string { @@ -65,11 +61,11 @@ export class TimeHelpers { * eg. 15 blocks => 1s * @param duration in block number */ - public static durationToRelativeTime = (durationInBlocks: number): string => { + public static durationToRelativeTime = (durationInBlocks: number, blockGenerationTargetTime: number): string => { try { const isDurationNegative = durationInBlocks < 0 const absoluteDuration = isDurationNegative ? durationInBlocks * -1 : durationInBlocks - const relativeTime = TimeHelpers.formatSeconds(absoluteDuration * targetBlockTime) + const relativeTime = TimeHelpers.formatSeconds(absoluteDuration * blockGenerationTargetTime) const prefix = isDurationNegative ? '- ' : '' return `${prefix}${relativeTime}` } catch (error) { @@ -78,6 +74,41 @@ export class TimeHelpers { } } + public static durationStringToSeconds(str: string): number { + return Math.floor(this.durationStringToMilliseconds(str) / 1000) + } + + public static durationStringToMilliseconds(value: string): number { + let str = value + let total = 0 + const milliSeconds = str.match(/(\d+)\s*ms/) + if (milliSeconds) { + str = str.replace(milliSeconds[0], '') + total += parseInt(milliSeconds[1]) + } + const days = str.match(/(\d+)\s*d/) + if (days) { + str = str.replace(days[0], '') + total += parseInt(days[1]) * 24 * 60 * 60 * 1000 + } + const hours = str.match(/(\d+)\s*h/) + if (hours) { + str = str.replace(hours[0], '') + total += parseInt(hours[1]) * 60 * 60 * 1000 + } + const minutes = str.match(/(\d+)\s*m/) + if (minutes) { + str = str.replace(minutes[0], '') + total += parseInt(minutes[1]) * 60 * 1000 + } + const seconds = str.match(/(\d+)\s*s/) + if (seconds) { + str = str.replace(seconds[0], '') + total += parseInt(seconds[1]) * 1000 + } + return total + } + public static formatDate = (timestamp) => { const now = new Date(Number(timestamp)) const year = now.getFullYear() diff --git a/src/core/utils/URLHelpers.ts b/src/core/utils/URLHelpers.ts index 797c8a395..01bacd930 100644 --- a/src/core/utils/URLHelpers.ts +++ b/src/core/utils/URLHelpers.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,20 +15,21 @@ */ // internal dependencies import {UrlValidator} from '../validation/validators' +import {URLInfo} from '@/core/utils/URLInfo' export class URLHelpers { - public static formatUrl = (rawUrl: string) => { + + public static formatUrl = (rawUrl: string): URLInfo => { if (!UrlValidator.validate(rawUrl)) { throw new Error(`Invalid URL: ${rawUrl}`) } - const url = new URL(rawUrl) - return { - protocol: url.protocol, - hostname: url.hostname, - port: url.port, - url: rawUrl, - } + return new URLInfo( + url.protocol, + url.hostname, + url.port, + rawUrl, + ) } public static httpToWsUrl = (url: string) => { @@ -37,19 +38,21 @@ export class URLHelpers { } } - public static completeUrlWithHostAndProtocol(inputNodeValue: string): string { - - const protocolIdx = inputNodeValue.indexOf('http') - - let out: string = inputNodeValue - if (-1 === protocolIdx) { - out = `http://${out}` - } - - if (-1 === inputNodeValue.indexOf(':', protocolIdx + 6)) { - out = `${out}:3000` - } - - return out + /** + * Get full node url and add missing pieces + * @param {string} fromUrl + * @return {string} + */ + public static getNodeUrl(fromUrl: string): string { + let fixedUrl = -1 === fromUrl.indexOf('://') + ? 'http://' + fromUrl + : fromUrl + + fixedUrl = !fixedUrl.match(/https?:\/\/[^:]+:([0-9]+)\/?$/) + ? fixedUrl + ':3000' // default adds :3000 + : fixedUrl + + const url = URLHelpers.formatUrl(fixedUrl) + return url.protocol + '//' + url.hostname + (url.port ? ':' + url.port : ':3000') } } diff --git a/src/core/utils/URLInfo.ts b/src/core/utils/URLInfo.ts new file mode 100644 index 000000000..94f6ef5a4 --- /dev/null +++ b/src/core/utils/URLInfo.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +export class URLInfo { + + + constructor(public readonly protocol, + public readonly hostname, + public readonly port: string, + public readonly url: string) { + + } +} diff --git a/src/core/validation/CustomValidationRules.ts b/src/core/validation/CustomValidationRules.ts index f7dc39137..1710eb38b 100644 --- a/src/core/validation/CustomValidationRules.ts +++ b/src/core/validation/CustomValidationRules.ts @@ -1,29 +1,25 @@ // external dependencies import {extend} from 'vee-validate' import i18n from '@/language' -import {Address, Password, Account, NetworkType} from 'symbol-sdk' - +import {Account, Address, NetworkType, Password} from 'symbol-sdk' // internal dependencies -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {WalletsRepository} from '@/repositories/WalletsRepository' import {AccountService} from '@/services/AccountService' import {NotificationType} from '@/core/utils/NotificationType' import {AppStore} from '@/app/AppStore' - // configuration import networkConfig from '../../../config/network.conf.json' import appConfig from '../../../config/app.conf.json' -const currentNetwork = networkConfig.networks['testnet-publicTest'] +import {AddressValidator, AliasValidator, MaxDecimalsValidator, PublicKeyValidator, UrlValidator} from './validators' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {WalletService} from '@/services/WalletService' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + +// TODO CustomValidationRules needs to be created when the network configuration is resolved, UI +// needs to use the resolved CustomValidationRules +// ATM rules are using the hardcoded file +const currentNetwork: NetworkConfigurationModel = networkConfig.networkConfigurationDefaults const {MIN_PASSWORD_LENGTH} = appConfig.constants -import { - AddressValidator, - AliasValidator, - MaxDecimalsValidator, - UrlValidator, - PublicKeyValidator, -} from './validators' - export class CustomValidationRules { /** * Registers custom validation rules @@ -84,7 +80,7 @@ export class CustomValidationRules { extend('newAccountName', { validate(value) { - return new AccountsRepository().find(value) === false + return !new AccountService().getAccountByName(value) }, message: `${i18n.t(`${NotificationType.ACCOUNT_NAME_EXISTS_ERROR}`)}`, }) @@ -95,9 +91,9 @@ export class CustomValidationRules { return false } - const currentAccount = AppStore.getters['account/currentAccount'] - const currentHash = currentAccount.values.get('password') - const inputHash = new AccountService(AppStore).getPasswordHash(new Password(value)) + const currentAccount: AccountModel = AppStore.getters['account/currentAccount'] + const currentHash = currentAccount.password + const inputHash = AccountService.getPasswordHash(new Password(value)) return inputHash === currentHash }, message: `${i18n.t(`${NotificationType.WRONG_PASSWORD_ERROR}`)}`, @@ -105,18 +101,12 @@ export class CustomValidationRules { extend('accountWalletName', { validate(value) { - const accountsRepository = new AccountsRepository() - const walletsRepository = new WalletsRepository() + const walletService = new WalletService() // - fetch current account wallets - const currentAccount = AppStore.getters['account/currentAccount'] - const knownWallets = Array.from(accountsRepository.fetchRelations( - walletsRepository, - currentAccount, - 'wallets', - ).values()) - - return undefined === knownWallets.find(w => value === w.values.get('name')) + const currentAccount: AccountModel = AppStore.getters['account/currentAccount'] + const knownWallets = Object.values(walletService.getKnownWallets(currentAccount.wallets)) + return undefined === knownWallets.find(w => value === w.name) }, message: `${i18n.t(`${NotificationType.ERROR_WALLET_NAME_ALREADY_EXISTS}`)}`, }) @@ -133,7 +123,7 @@ export class CustomValidationRules { }, message: `${i18n.t(`${NotificationType.ACCOUNT_NAME_EXISTS_ERROR}`)}`, }) - + extend('addressOrPublicKey', { validate: (value) => { const isValidAddress = AddressValidator.validate(value) @@ -146,9 +136,10 @@ export class CustomValidationRules { extend('maxNamespaceDuration', { validate: (value) => { - return value <= currentNetwork.properties.maxNamespaceDuration + return value <= currentNetwork.maxNamespaceDuration }, - message: `${i18n.t('error_new_namespace_duration_max_value', {maxValue: currentNetwork.properties.maxNamespaceDuration})}`, + message: `${i18n.t('error_new_namespace_duration_max_value', + {maxValue: currentNetwork.maxNamespaceDuration})}`, }) extend('passwordRegex', { diff --git a/src/core/validation/ValidationRuleset.ts b/src/core/validation/ValidationRuleset.ts index e57649887..81db183ee 100644 --- a/src/core/validation/ValidationRuleset.ts +++ b/src/core/validation/ValidationRuleset.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,21 +14,20 @@ * limitations under the License. */ // configuration -import networkConfig from '../../../config/network.conf.json' import appConfig from '../../../config/app.conf.json' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + +import networkConfig from '../../../config/network.conf.json' + const {MIN_PASSWORD_LENGTH} = appConfig.constants -// XXX network store config getter -const currentNetwork = networkConfig.networks['testnet-publicTest'] -const { +export const createValidationRuleset = ({ maxMessageSize, maxMosaicAtomicUnits, maxMosaicDivisibility, maxMosaicDuration, minNamespaceDuration, -} = currentNetwork.properties - -export const ValidationRuleset = { +}: NetworkConfigurationModel) => ({ address: 'required|address|addressNetworkType:currentAccount', accountPassword: 'required|accountPassword', addressOrAlias: 'required|addressOrAlias|addressOrAliasNetworkType:currentAccount', @@ -55,7 +54,11 @@ export const ValidationRuleset = { supply: `required|integer|min_value: 1|max_value:${maxMosaicAtomicUnits}`, walletPassword: 'required|confirmWalletPassword:wallet', url: 'required|url', - newAccountName:'required|newAccountName', + newAccountName: 'required|newAccountName', accountWalletName: 'required|accountWalletName', addressOrPublicKey: 'addressOrPublicKey', -} +}) + +// TODO ValidationRuleset needs to be created when the network configuration is resolved, UI needs +// to use the resolved ValidationResulset ATM rules are using the hardocded ones +export const ValidationRuleset = createValidationRuleset(networkConfig.networkConfigurationDefaults) diff --git a/src/core/views/MosaicWithInfoView.ts b/src/core/views/MosaicWithInfoView.ts index a4e215157..8d3beee8e 100644 --- a/src/core/views/MosaicWithInfoView.ts +++ b/src/core/views/MosaicWithInfoView.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Mosaic, MosaicInfo } from 'symbol-sdk' +import {Mosaic, MosaicInfo} from 'symbol-sdk' export type MosaicWithInfoView = { mosaicInfo: MosaicInfo diff --git a/src/language/en-US.json b/src/language/en-US.json index 3e411b7d1..c6a2e9030 100644 --- a/src/language/en-US.json +++ b/src/language/en-US.json @@ -732,6 +732,7 @@ "PRIVATE_KEY_INVALID_ERROR": "This private key is invalid.", "address_unknown": "This address is unknown to the network, please try with its public key instead", "please_enter_a_custom_nod_address": "Enter a custom node URL", + "filter_peers": "Filter peers", "cosigners": "Co-signatories", "address_invalid": "This address is invalid", "public_key_invalid": "This public key is invalid", diff --git a/src/repositories/AccountsRepository.ts b/src/repositories/AccountsRepository.ts deleted file mode 100644 index f4d8a2d17..000000000 --- a/src/repositories/AccountsRepository.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {NetworkType} from 'symbol-sdk' - -// internal dependencies -import {AccountsTable} from '@/core/database/entities/AccountsTable' -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {ModelRepository} from './ModelRepository' - -export class AccountsRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {AccountsTable} - */ - public createTable(): AccountsTable { - return new AccountsTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {AccountsModel} - */ - public createModel(values: Map): AccountsModel { - return new AccountsModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {AccountsModel[]} - */ - public collect(): AccountsModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: AccountsModel, - index: number, - array: AccountsModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Account with name '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new AccountsModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {AccountsModel} - */ - public read(identifier: string): AccountsModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Account with name '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {AccountsModel} The new values - */ - public update(identifier: string, values: Map): AccountsModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Account with name '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository - - /** - * Get list of account names mapped by network type - * @return {Object} - */ - public getNamesByNetworkType(): any { - const accounts = this.collect() - const accountsMap = { - [NetworkType.MIJIN_TEST]: [], - [NetworkType.MIJIN]: [], - [NetworkType.TEST_NET]: [], - [NetworkType.MAIN_NET]: [], - } - for (let i = 0, m = accounts.length; i < m; i ++) { - const account = accounts[i] - const networkType = account.values.get('networkType') - accountsMap[networkType].push(account.values.get('accountName')) - } - - return accountsMap - } -} diff --git a/src/repositories/IStorable.ts b/src/repositories/IStorable.ts deleted file mode 100644 index b2424c4c0..000000000 --- a/src/repositories/IStorable.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {BaseStorageAdapter} from '@/core/database/BaseStorageAdapter' - -export interface IStorable< - StorageAdapterImpl extends BaseStorageAdapter -> { - /** - * Getter for the storage adapter - * @return {StorageAdapterImpl} - */ - getAdapter(): StorageAdapterImpl - - /** - * Setter for the storage adapter - * @param {StorageAdapterImpl} adapter - * @return {IStorable} - */ - setAdapter(adapter: StorageAdapterImpl): IStorable - - /** - * Fetch items from storage - * @return {Map} - */ - fetch(): Map - - /** - * Persist items to storage - * @return {number} - */ - persist(): number - - /** - * Reset storage (empty) - * @return boolean - */ - reset(): boolean -} diff --git a/src/repositories/ModelRepository.ts b/src/repositories/ModelRepository.ts deleted file mode 100644 index 4a40a2855..000000000 --- a/src/repositories/ModelRepository.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {DatabaseModel} from '@/core/database/DatabaseModel' -import {DatabaseTable} from '@/core/database/DatabaseTable' -import {AppDatabase} from '@/core/database/AppDatabase' -import {SimpleStorageAdapter} from '@/core/database/SimpleStorageAdapter' -import {IStorable} from './IStorable' - -export abstract class ModelRepository implements IStorable { - /** - * Storage adapter - * @see {IStorable} - * @var {SimpleStorageAdapter} - */ - protected _adapter: SimpleStorageAdapter - - /** - * Collection of items - * @see {IRepository} - * @var {Map} - */ - protected _collection: Map - - /** - * Construct a repository around \a adapter storage adapter. - * @param {SimpleStorageAdapter} adapter - */ - public constructor() { - this.setAdapter(AppDatabase.getAdapter()) - this.fetch() - } - - /// region implements IStorable - /** - * Getter for the adapter - * @see {IStorable} - * @return {SimpleStorageAdapter} - */ - public getAdapter(): SimpleStorageAdapter { - return this._adapter - } - - /** - * Setter for the storage adapter - * @see {IStorable} - * @param {SimpleStorageAdapter} adapter - * @return {ModelRepository} - */ - public setAdapter(adapter: SimpleStorageAdapter): ModelRepository { - this._adapter = adapter - return this - } - - /** - * Fetch items from storage - * @see {IStorable} - * @return {Map} - */ - public fetch(): Map { - // use DatabaseTable - const table = this.createTable() - - // read items from storage - this._collection = this._adapter.read(table.tableName) - return this._collection - } - - /** - * Persist items to storage - * @see {IStorable} - * @return {number} - */ - public persist(): number { - // use DatabaseTable - const table = this.createTable() - - // read items from storage - return this._adapter.write(table.tableName, this._collection) - } - - /** - * Reset storage (empty) - * @see {IStorable} - * @return boolean - */ - public reset() { - this._collection.clear() - this.persist() - return true - } - /// end-region implements IStorable - - /** - * Fetch many relations using \a repository and values from \a model - * @param {ModelRepository} repository - * @param {DatabaseModel} model - * @param {string} fieldName - * @return {Map} Collection of objects mapped by identifier - */ - public fetchRelations( - repository: ModelRepository, - model: DatabaseModel, - fieldName: string, - ): Map { - const resolved = new Map() - - // resolve object by identifier list stored in values - // with field name \a fieldName - const ids = model.values.get(fieldName) - ids.map(id => resolved.set(id, repository.read(id))) - return resolved - } - - /** - * Fetch one relation using \a repository and values from \a model - * @access protected - * @param {ModelRepository} repository - * @param {DatabaseModel} model - * @param {string} fieldName - * @return {DatabaseModel} Sub class Model instance - */ - public fetchRelation( - repository: ModelRepository, - model: DatabaseModel, - fieldName: string, - ): DatabaseModel { - return repository.read(model.values.get(fieldName)) - } - - /// region abstract methods - /** - * Create a table instance - * @return {DatabaseTable} - */ - public abstract createTable(): DatabaseTable - - /** - * Create a model instance - * @param {Map} values - * @return {DatabaseModel} - */ - public abstract createModel(values: Map): DatabaseModel - - /** - * Check for existence of entity by \a id - * @param {string} id - * @return {boolean} - */ - public abstract find(key: string): boolean - - /** - * Getter for the collection of items - * @return {ModelImpl[]} - */ - public abstract collect(): DatabaseModel[] - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public abstract entries(): Map - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public abstract entries( - filterFn?: ( - value: DatabaseModel, - index: number, - array: DatabaseModel[] - ) => boolean - ): Map - - /// region CRUD - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - public abstract create(values: Map): string - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {Map} - */ - public abstract read(identifier: string): DatabaseModel - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {DatabaseModel} The new values - */ - public abstract update(identifier: string, values: Map): DatabaseModel - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public abstract delete(identifier: string): boolean - /// end-region CRUD - /// end-region abstract methods - - /** - * Getter for the table name - * @return {string} - */ - public getTableName(): string { - return this.createTable().tableName - } - - /** - * Getter for the identifier names - * @return {string[]} - */ - public getPrimaryKeys(): string[] { - // proxy object to read primary keys - return this.createModel(new Map()).primaryKeys - } -} diff --git a/src/repositories/MosaicsRepository.ts b/src/repositories/MosaicsRepository.ts deleted file mode 100644 index e55a6b79b..000000000 --- a/src/repositories/MosaicsRepository.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {MosaicsTable} from '@/core/database/entities/MosaicsTable' -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' -import {ModelRepository} from './ModelRepository' - -export class MosaicsRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {MosaicsTable} - */ - public createTable(): MosaicsTable { - return new MosaicsTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {MosaicsModel} - */ - public createModel(values: Map): MosaicsModel { - return new MosaicsModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {MosaicsModel[]} - */ - public collect(): MosaicsModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: MosaicsModel, - index: number, - array: MosaicsModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Mosaic with identifier '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new MosaicsModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {MosaicsModel} - */ - public read(identifier: string): MosaicsModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Mosaic with identifier '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {MosaicsModel} The new values - */ - public update(identifier: string, values: Map): MosaicsModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Mosaic with identifier '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository -} diff --git a/src/repositories/NamespacesRepository.ts b/src/repositories/NamespacesRepository.ts deleted file mode 100644 index c30f3458b..000000000 --- a/src/repositories/NamespacesRepository.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {NamespacesTable} from '@/core/database/entities/NamespacesTable' -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' -import {ModelRepository} from './ModelRepository' - -export class NamespacesRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {NamespacesTable} - */ - public createTable(): NamespacesTable { - return new NamespacesTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {NamespacesModel} - */ - public createModel(values: Map): NamespacesModel { - return new NamespacesModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {NamespacesModel[]} - */ - public collect(): NamespacesModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: NamespacesModel, - index: number, - array: NamespacesModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Namespace with identifier '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new NamespacesModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {NamespacesModel} - */ - public read(identifier: string): NamespacesModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Namespace with identifier '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {NamespacesModel} The new values - */ - public update(identifier: string, values: Map): NamespacesModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Namespace with identifier '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository -} diff --git a/src/repositories/PeersRepository.ts b/src/repositories/PeersRepository.ts deleted file mode 100644 index 3e0fea0cb..000000000 --- a/src/repositories/PeersRepository.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {PeersTable} from '@/core/database/entities/PeersTable' -import {PeersModel} from '@/core/database/entities/PeersModel' -import {ModelRepository} from './ModelRepository' - -import {URLHelpers} from '@/core/utils/URLHelpers' - -// configuration -import networkConfig from '@/../config/network.conf.json' - -export class PeersRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {PeersTable} - */ - public createTable(): PeersTable { - return new PeersTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {ModelImpl} - */ - public createModel(values: Map): PeersModel { - return new PeersModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {PeersModel[]} - */ - public collect(): PeersModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: PeersModel, - index: number, - array: PeersModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Peer with host '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new PeersModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {PeersModel} - */ - public read(identifier: string): PeersModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Peer with host '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {PeersModel} The new values - */ - public update(identifier: string, values: Map): PeersModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Peer with host '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository - - /** - * Populates the database from configuration file network.conf.json - * @param {string} generationHash - * @return {PeersModel[]} - */ - public repopulateFromConfig( - generationHash: string, - ): PeersModel[] { - - // - resets storage to repopulate from config - this.reset() - - // - shortcuts - const networkLabel = Object.keys(networkConfig.networks).filter(label => { - return networkConfig.networks[label].generationHash === generationHash - }).shift() - const networkType = networkConfig.networks[networkLabel].networkType - - // - for each known node create a endpoints entry - const peers: PeersModel[] = [] - const nodes = networkConfig.networks[networkLabel].nodes - - for (let i = 0, m = nodes.length; i < m; i ++) { - const spec = nodes[i] - const node = URLHelpers.formatUrl(spec.url) - const isDefault = networkConfig.defaultNode.url === spec.url - - const model = new PeersModel(new Map([ - [ 'rest_url', spec.url ], - [ 'host', node.hostname ], - [ 'port', parseInt(node.port) ], - [ 'protocol', node.protocol ], - [ 'networkType', networkType ], - [ 'generationHash', generationHash ], - [ 'roles', spec.roles ], - [ 'is_default', isDefault ], - [ 'friendly_name', spec.friendly ], - ])) - - this.create(model.values) - peers.push(model) - } - - return peers - } -} diff --git a/src/repositories/SettingsRepository.ts b/src/repositories/SettingsRepository.ts deleted file mode 100644 index 87acbdb28..000000000 --- a/src/repositories/SettingsRepository.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {SettingsTable} from '@/core/database/entities/SettingsTable' -import {SettingsModel} from '@/core/database/entities/SettingsModel' -import {ModelRepository} from './ModelRepository' - -export class SettingsRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {SettingsTable} - */ - public createTable(): SettingsTable { - return new SettingsTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {ModelImpl} - */ - public createModel(values: Map): SettingsModel { - return new SettingsModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {SettingsModel[]} - */ - public collect(): SettingsModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: SettingsModel, - index: number, - array: SettingsModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Settings entry with identifier '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new SettingsModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {SettingsModel} - */ - public read(identifier: string): SettingsModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Settings entry with identifier '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {SettingsModel} The new values - */ - public update(identifier: string, values: Map): SettingsModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Settings entry with identifier '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository -} diff --git a/src/repositories/WalletsRepository.ts b/src/repositories/WalletsRepository.ts deleted file mode 100644 index bb0dedab5..000000000 --- a/src/repositories/WalletsRepository.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// internal dependencies -import {WalletsTable} from '@/core/database/entities/WalletsTable' -import {WalletsModel} from '@/core/database/entities/WalletsModel' -import {ModelRepository} from './ModelRepository' - -export class WalletsRepository - extends ModelRepository { - - /// region abstract methods - /** - * Create a table instance - * @return {WalletsTable} - */ - public createTable(): WalletsTable { - return new WalletsTable() - } - - /** - * Create a model instance - * @param {Map} values - * @return {ModelImpl} - */ - public createModel(values: Map): WalletsModel { - return new WalletsModel(values) - } - /// end-region abstract methods - - /// region implements IRepository - /** - * Check for existence of entity by \a identifier - * @param {string} identifier - * @return {boolean} - */ - public find(identifier: string): boolean { - return this._collection.has(identifier) - } - - /** - * Getter for the collection of items - * @return {WalletsModel[]} - */ - public collect(): WalletsModel[] { - return Array.from(this._collection.values()) - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public entries( - filterFn: ( - value: WalletsModel, - index: number, - array: WalletsModel[] - ) => boolean = () => true, - ): Map { - const filtered = this.collect().filter(filterFn) - const mapped = new Map() - - // map by identifier - filtered.map(f => mapped.set(f.getIdentifier(), f)) - return mapped - } - - /** - * Create an entity - * @param {Map} values - * @return {string} The assigned entity identifier - */ - create(values: Map): string { - const mapped = this.createModel(values) - - // created object must contain values for all primary keys - if (!mapped.hasIdentifier()) { - throw new Error(`Missing value for mandatory identifier fields '${mapped.primaryKeys.join(', ')}'.`) - } - - // verify uniqueness - const identifier = mapped.getIdentifier() - if (this.find(identifier)) { - throw new Error(`Wallet with name '${identifier}' already exists.`) - } - - // update collection - this._collection.set(identifier, new WalletsModel(values)) - - // persist to storage - this.persist() - return identifier - } - - /** - * Getter for the collection of items - * @param {string} identifier - * @return {WalletsModel} - */ - public read(identifier: string): WalletsModel { - // verify existence - if (!this.find(identifier)) { - throw new Error(`Wallet with name '${identifier}' does not exist.`) - } - - return this._collection.get(identifier) - } - - /** - * Update an entity - * @param {string} identifier - * @param {Map} values - * @return {WalletsModel} The new values - */ - public update(identifier: string, values: Map): WalletsModel { - // require existing - const previous = this.read(identifier) - - // populate/update values - const iterator = values.keys() - for (let i = 0, m = values.size; i < m; i ++) { - const key = iterator.next() - const value = values.get(key.value) - - // expose only "values" from model - previous.values.set(key.value, value) - } - - // update collection - this._collection.set(identifier, previous) - - // persist to storage - this.persist() - return previous - } - - /** - * Delete an entity - * @param {string} identifier - * @return {boolean} Whether an element was deleted - */ - public delete(identifier: string): boolean { - // require existing - if (!this.find(identifier)) { - throw new Error(`Wallet with name '${identifier}' does not exist.`) - } - - // update collection - if(!this._collection.delete(identifier)) { - return false - } - - // persist to storage - this.persist() - return true - } - /// end-region implements IRepository -} diff --git a/src/router/AppRouter.ts b/src/router/AppRouter.ts index c0d47bac8..b1e0196a0 100644 --- a/src/router/AppRouter.ts +++ b/src/router/AppRouter.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,13 +15,12 @@ */ // external dependencies import Router from 'vue-router' - // internal dependencies import {routes} from '@/router/routes' -import {AccountsRepository} from '@/repositories/AccountsRepository' import {AppRoute} from './AppRoute' import {TabEntry} from './TabEntry' import {AppStore} from '@/app/AppStore' +import {AccountService} from '@/services/AccountService' /** * Extension of Vue Router @@ -38,8 +37,8 @@ export class AppRouter extends Router { super(options) this.routes = options.routes this.beforeEach((to, from, next) => { - const repository = new AccountsRepository() - const hasAccounts = repository.entries().size > 0 + const service = new AccountService() + const hasAccounts = service.getAccounts().length > 0 // No account when app is opened: redirect to create account page const skipRedirect: string[] = ['accounts.importAccount.importStrategy'] @@ -136,7 +135,7 @@ export class AppRouter extends Router { */ private getChildRoutes(parentRoute: AppRoute): AppRoute[] { if (!parentRoute.children) return [] - return [...parentRoute.children].filter(({meta}) => !meta.hideFromMenu) + return [...parentRoute.children].filter(({meta}) => !meta.hideFromMenu) } } diff --git a/src/router/routes.ts b/src/router/routes.ts index 46b71e550..909c1887f 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/src/services/AESEncryptionService.ts b/src/services/AESEncryptionService.ts index d795bc58c..bfd2a7584 100644 --- a/src/services/AESEncryptionService.ts +++ b/src/services/AESEncryptionService.ts @@ -1,52 +1,28 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' import {Password} from 'symbol-sdk' import CryptoJS from 'crypto-js' -// internal dependencies -import {AbstractService} from './AbstractService' - -export class AESEncryptionService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'encryption' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } +export class AESEncryptionService { /** * Encrypt data - * @param {string} data - * @param {string} salt - * @param {Password} password + * @param {string} data + * @param {string} salt + * @param {Password} password */ public static encrypt( data: string, @@ -62,8 +38,8 @@ export class AESEncryptionService extends AbstractService { // encrypt using random IV const iv = CryptoJS.lib.WordArray.random(16) - const encrypted = CryptoJS.AES.encrypt(data, key, { - iv: iv, + const encrypted = CryptoJS.AES.encrypt(data, key, { + iv: iv, padding: CryptoJS.pad.Pkcs7, mode: CryptoJS.mode.CBC, }) @@ -75,9 +51,9 @@ export class AESEncryptionService extends AbstractService { /** * Decrypt data - * @param {string} data - * @param {string} salt - * @param {Password} password + * @param {string} data + * @param {string} salt + * @param {Password} password */ public static decrypt( data: string, @@ -94,8 +70,8 @@ export class AESEncryptionService extends AbstractService { }) // decrypt using custom IV - const decrypted = CryptoJS.AES.decrypt(encrypted, key, { - iv: iv, + const decrypted = CryptoJS.AES.decrypt(encrypted, key, { + iv: iv, padding: CryptoJS.pad.Pkcs7, mode: CryptoJS.mode.CBC, }) @@ -105,8 +81,8 @@ export class AESEncryptionService extends AbstractService { /** * Generate \a count random bytes - * @param {number} count - * @param {boolean} raw + * @param {number} count + * @param {boolean} raw * @return {string} */ public static generateRandomBytes( diff --git a/src/services/AbstractService.ts b/src/services/AbstractService.ts index 4ccf593a8..b33b5828b 100644 --- a/src/services/AbstractService.ts +++ b/src/services/AbstractService.ts @@ -16,7 +16,6 @@ // external dependencies import {Store} from 'vuex' import VueI18n from 'vue-i18n' - // internal dependencies import {IService} from './IService' diff --git a/src/services/AccountService.ts b/src/services/AccountService.ts index e2daf2ec3..63365327c 100644 --- a/src/services/AccountService.ts +++ b/src/services/AccountService.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,58 +14,65 @@ * limitations under the License. */ import {Convert, Password, SHA3Hasher} from 'symbol-sdk' -import {Store} from 'vuex' - // internal dependencies -import {AbstractService} from './AbstractService' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {AccountsModel} from '@/core/database/entities/AccountsModel' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' +import {AccountModel} from '@/core/database/entities/AccountModel' -export class AccountService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'account' - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store +/** + * Service in charge of loading accounts information from the wallet. + */ +export class AccountService { /** - * Construct a service instance around \a store - * @param store + * The storage to keep user configuration around mosaics. For example, the balance hidden + * feature. */ - constructor(store?: Store) { - super() - this.$store = store + private readonly accountsStorage = new SimpleObjectStorage>('accounts') + + + public getAccounts(): AccountModel[] { + return Object.values(this.getAccountsByAccountName()) } - /** - * Read the collection of known accounts from database. - * - * @param {Function} filterFn - * @return {MosaicsModel[]} - */ - public getAccounts( - filterFn: ( - value: AccountsModel, - index: number, - array: AccountsModel[] - ) => boolean = () => true, - ): AccountsModel[] { - const repository = new AccountsRepository() - return repository.collect().filter(filterFn) + public getAccountByName(accountName: string): AccountModel | undefined { + return this.getAccountsByAccountName()[accountName] + } + + public getAccountsByAccountName(): Record { + return this.accountsStorage.get() || {} + } + + public saveAccount(account: AccountModel) { + const accounts = this.getAccountsByAccountName() + accounts[account.accountName] = account + this.accountsStorage.set(accounts) + } + + public deleteAccount(accountName: string) { + const accounts = this.getAccountsByAccountName() + delete accounts[accountName] + this.accountsStorage.set(accounts) + } + + public updateSeed(account: AccountModel, seed: string) { + this.saveAccount({...account, ...{seed}}) + } + + public updatePassword(account: AccountModel, password: string, hint: string) { + this.saveAccount({...account, ...{password, hint}}) + } + + public updateWallets(account: AccountModel, wallets: string[]) { + this.saveAccount({...account, ...{wallets}}) } /** * Return password hash that can be compared - * @param {Password} password - * @return {string} + * @param password to be hashed + * @return the password hash */ - public getPasswordHash(password: Password): string { + public static getPasswordHash(password: Password): string { const hasher = SHA3Hasher.createHasher(64) hasher.reset() hasher.update(Convert.utf8ToHex(password.value)) @@ -74,4 +81,6 @@ export class AccountService extends AbstractService { hasher.finalize(hash) return Convert.uint8ToHex(hash) } + + } diff --git a/src/services/AssetTableService/AssetTableService.ts b/src/services/AssetTableService/AssetTableService.ts index 4f24474e2..28db0c3c4 100644 --- a/src/services/AssetTableService/AssetTableService.ts +++ b/src/services/AssetTableService/AssetTableService.ts @@ -1,23 +1,18 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -// external dependencies -import {Store} from 'vuex' - -// internal dependencies -import {AbstractService} from '../AbstractService' /** * Table field to be used in a table header @@ -63,43 +58,10 @@ export type TableFilteringOptions = { filteringType: FilteringTypes } -export abstract class AssetTableService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'asset-table' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Chain height - * @protected - * @var {number} - */ - protected currentHeight: number +export abstract class AssetTableService { - /** - * Creates an instance of AssetTableService. - * @param {*} store - */ - constructor(store?: Store) { - super() - this.$store = store - this.currentHeight = this.getCurrentHeight() - } + protected constructor(public readonly currentHeight: number) { - /** - * Fetch current height from network store - * @see {Store.Network} - * @return {number} - */ - protected getCurrentHeight(): number { - return this.$store.getters['network/currentHeight'] || 0 } /** @@ -165,13 +127,11 @@ export abstract class AssetTableService extends AbstractService { {numeric: true, ignorePunctuation: true}, ) }) - } - else if ('boolean' === typeof sampleValue) { + } else if ('boolean' === typeof sampleValue) { return [...values][sortingMethod]((a, b) => { return (a[options.fieldName] === b[options.fieldName]) ? 0 : a[options.fieldName] ? -1 : 1 }) - } - else if ('number' === typeof sampleValue) { + } else if ('number' === typeof sampleValue) { return values[sortingMethod]((a, b) => { if (!b[options.fieldName] || !a[options.fieldName]) return 1 return b[options.fieldName] - a[options.fieldName] diff --git a/src/services/AssetTableService/MosaicTableService.ts b/src/services/AssetTableService/MosaicTableService.ts index 5ade12272..dec1df348 100644 --- a/src/services/AssetTableService/MosaicTableService.ts +++ b/src/services/AssetTableService/MosaicTableService.ts @@ -1,32 +1,27 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' -import {Mosaic, UInt64, MosaicInfo} from 'symbol-sdk' - // internal dependencies import {AssetTableService, TableField} from './AssetTableService' -import {MosaicService} from '../MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {MosaicService} from '@/services/MosaicService' export class MosaicTableService extends AssetTableService { -/** - * Creates an instance of MosaicTableService. - * @param {*} store - */ - constructor(store?: Store) { - super(store) + + constructor(currentHeight: number, private readonly mosaics: MosaicModel[]) { + super(currentHeight) } /** @@ -48,44 +43,26 @@ export class MosaicTableService extends AssetTableService { } /** - * Return table values to be displayed in a table rows - * @returns {MosaicTableRowValues[]} - */ + * Return table values to be displayed in a table rows + * @returns {MosaicTableRowValues[]} + */ public getTableRows(): any[] { // - get reactive mosaic data from the store - const ownedMosaics: Mosaic[] = this.$store.getters['wallet/currentWalletMosaics'] - const mosaicsInfo: Record = this.$store.getters['mosaic/mosaicsInfo'] - const mosaicNames: Record = this.$store.getters['mosaic/mosaicsNames'] - - return ownedMosaics.map((mosaic) => { - const hexId = mosaic.id.toHex() - - // get mosaic info, return and wait for re-render if not available - const mosaicInfo = mosaicsInfo[hexId] - if (!mosaicInfo) return null - - // extract useful info - const flags = mosaicInfo.flags - const balance = mosaic.amount.compact() - const {supply, divisibility} = mosaicInfo - - // get expiration from mosaics service - const expiration = new MosaicService(this.$store).getExpiration(mosaicInfo) - + const mosaicsInfo = this.mosaics + const currentHeight = this.currentHeight + return mosaicsInfo.map((mosaicInfo) => { + const expiration = MosaicService.getExpiration(mosaicInfo, currentHeight) // - map table fields return { - 'hexId': hexId, - 'name': mosaicNames[hexId] || 'N/A', - 'supply': new UInt64([ supply.lower, supply.higher ]).compact().toLocaleString(), - 'balance': balance === 0 ? 0 : ( - // - get relative amount - balance / Math.pow(10, divisibility) - ), + 'hexId': mosaicInfo.mosaicIdHex, + 'name': mosaicInfo.name || 'N/A', + 'supply': mosaicInfo.supply.toLocaleString(), + 'balance': mosaicInfo.balance || 0 / Math.pow(10, mosaicInfo.divisibility), 'expiration': expiration, - 'divisibility': divisibility, - 'transferable': flags.transferable, - 'supplyMutable': flags.supplyMutable, - 'restrictable': flags.restrictable, + 'divisibility': mosaicInfo.divisibility, + 'transferable': mosaicInfo.transferable, + 'supplyMutable': mosaicInfo.supplyMutable, + 'restrictable': mosaicInfo.restrictable, } }).filter(x => x) // filter out mosaics that are not yet available } diff --git a/src/services/AssetTableService/NamespaceTableService.ts b/src/services/AssetTableService/NamespaceTableService.ts index 71324d699..83b4da0df 100644 --- a/src/services/AssetTableService/NamespaceTableService.ts +++ b/src/services/AssetTableService/NamespaceTableService.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,21 +14,18 @@ * limitations under the License. */ // external dependencies -import {Store} from 'vuex' -import {NamespaceInfo, AliasType} from 'symbol-sdk' - +import {AliasType} from 'symbol-sdk' // internal dependencies import {AssetTableService, TableField} from './AssetTableService' -import {TimeHelpers} from '@/core/utils/TimeHelpers' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' import {NamespaceService} from '@/services/NamespaceService' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export class NamespaceTableService extends AssetTableService { - /** - * Creates an instance of NamespaceTableService. - * @param {*} store - */ - constructor(store?: Store) { - super(store) + + constructor(currentHeight: number, private readonly namespaces: NamespaceModel[], + private readonly networkConfiguration: NetworkConfigurationModel) { + super(currentHeight) } /** @@ -46,77 +43,53 @@ export class NamespaceTableService extends AssetTableService { ] } - /** - * Return table values to be displayed in a table rows - * @returns {NamespaceTableRowValues} - */ - public getTableRows(): any[] { - // - get owned namespaces from the store - const ownedNamespaces: NamespaceInfo[] = this.$store.getters['wallet/currentWalletOwnedNamespaces'] - // - use service to get information about namespaces - const service = new NamespaceService(this.$store) + public getTableRows(): any[] { + const namespaces: NamespaceModel[] = this.namespaces - return ownedNamespaces.map((namespaceInfo) => { - const {expired, expiration} = this.getExpiration(namespaceInfo) - const model = service.getNamespaceSync(namespaceInfo.id) - if (!model) return null + return namespaces.map((namespaceModel) => { + const {expired, expiration} = this.getExpiration(namespaceModel) return { - 'hexId': namespaceInfo.id.toHex(), - 'name': model.values.get('name'), + 'hexId': namespaceModel.namespaceIdHex, + 'name': namespaceModel.name, 'expiration': expiration, 'expired': expired, - 'aliasIdentifier': this.getAliasIdentifier(namespaceInfo), - 'aliasType': this.getAliasType(namespaceInfo), + 'aliasIdentifier': this.getAliasIdentifier(namespaceModel), + 'aliasType': this.getAliasType(namespaceModel), } - }).filter(x => x) // filter out namespaces that are not yet available + }) } /** * Gets the namespace type to be displayed in the table * @private - * @param {NamespaceInfo} namespaceInfo + * @param the namespace model. * @returns {('N/A' | 'address' | 'mosaic')} */ - private getAliasType(namespaceInfo: NamespaceInfo): 'N/A' | 'address' | 'mosaic' { - if(!namespaceInfo.hasAlias()) return 'N/A' - return namespaceInfo.alias.type === AliasType.Address ? 'address' : 'mosaic' + private getAliasType(namespaceModel: NamespaceModel): 'N/A' | 'address' | 'mosaic' { + if (!namespaceModel.aliasTargetAddressRawPlain && !namespaceModel.aliasTargetMosaicIdHex) return 'N/A' + return namespaceModel.aliasType === AliasType.Address ? 'address' : 'mosaic' } /** * Gets the namespace identifier to be displayed in the table * @private - * @param {NamespaceInfo} namespaceInfo + * @param the namespace model. * @returns {string} */ - private getAliasIdentifier(namespaceInfo: NamespaceInfo): string { - if(!namespaceInfo.hasAlias()) return 'N/A' - const {alias} = namespaceInfo - return alias.address ? alias.address.pretty() : alias.mosaicId.toHex() + private getAliasIdentifier(namespaceModel: NamespaceModel): string { + return namespaceModel.aliasTargetMosaicIdHex || namespaceModel.aliasTargetAddressRawPlain || 'N/A' } /** * Returns a view of a namespace expiration info * @private - * @param {NamespaceInfo} mosaicInfo + * @param the namespace model. * @returns {string} */ - private getExpiration ( - namespaceInfo: NamespaceInfo, - ): {expiration: string, expired: boolean} { - const {currentHeight} = this - const endHeight = namespaceInfo.endHeight.compact() - const networkConfig = this.$store.getters['network/config'] - const {namespaceGracePeriodDuration} = networkConfig.networks['testnet-publicTest'] - - const expired = currentHeight > endHeight - namespaceGracePeriodDuration - const expiredIn = endHeight - namespaceGracePeriodDuration - currentHeight - const deletedIn = endHeight - currentHeight - const expiration = expired - ? TimeHelpers.durationToRelativeTime(expiredIn) - : TimeHelpers.durationToRelativeTime(deletedIn) - - return {expired, expiration} + private getExpiration(namespaceModel: NamespaceModel): { expiration: string, expired: boolean } { + return NamespaceService.getExpiration(this.networkConfiguration, this.currentHeight, + namespaceModel.endHeight) } } diff --git a/src/services/CommunityService.ts b/src/services/CommunityService.ts index f0fc1e39d..bea802ad7 100644 --- a/src/services/CommunityService.ts +++ b/src/services/CommunityService.ts @@ -1,27 +1,24 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' import RSSParser from 'rss-parser' import axios from 'axios' import * as XSSSanitizer from 'xss' - // internal dependencies import {AbstractService} from './AbstractService' import {Formatters} from '@/core/utils/Formatters' - // configuration import appConfig from '@/../config/app.conf.json' @@ -36,11 +33,11 @@ const request = async (): Promise => { if (process.env.NODE_ENV === 'development') { feedUrl = '/nemflash' } - // execute request - const response = await axios.get(feedUrl, { params: {} }) + const response = await axios.get(feedUrl, {params: {}}) return response.data } + /// end-region protected helpers export interface ArticleEntry { @@ -50,7 +47,7 @@ export interface ArticleEntry { */ pubDate: string /** - * Article creator + * Article creator * @var {string} */ creator: string @@ -67,26 +64,6 @@ export interface ArticleEntry { } export class CommunityService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'community' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } /** * Get latest articles from RSS feed @@ -100,7 +77,7 @@ export class CommunityService extends AbstractService { parser.parseString(data, (err, parsed) => { if (err) {return reject(`Error occured while parsing RSS Feed ${err.toString()}`)} - + // - parse item and sanitize content const articles = parsed.items.map(item => { return Object.assign({}, item, { @@ -108,7 +85,6 @@ export class CommunityService extends AbstractService { pubDate: Formatters.formatDate(Date.parse(item.pubDate)), }) }) - return resolve(articles) }) }) diff --git a/src/services/DerivationService.ts b/src/services/DerivationService.ts index 298c7c072..177717777 100644 --- a/src/services/DerivationService.ts +++ b/src/services/DerivationService.ts @@ -1,22 +1,19 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' - // internal dependencies -import {AbstractService} from './AbstractService' import {DerivationPathValidator} from '@/core/validation/validators' import {WalletService} from '@/services/WalletService' @@ -28,31 +25,11 @@ export enum DerivationPathLevels { Address = 5, } -export class DerivationService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'derivation' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } +export class DerivationService { /** * Validate derivation path - * @param {string} path + * @param {string} path * @return {boolean} */ public isValidPath(path: string): boolean { @@ -61,8 +38,8 @@ export class DerivationService extends AbstractService { /** * Increment a derivation path level - * @param {string} path - * @param {DerivationPathLevel} which + * @param {string} path + * @param {DerivationPathLevel} which * @return {string} */ public incrementPathLevel( @@ -80,7 +57,7 @@ export class DerivationService extends AbstractService { // read levels and increment const index = which as number const parts = path.split('/') - + // calculate next index (increment) const next = (step <= 1 ? 1 : step) + parseInt(parts[index].replace(/'/, '')) @@ -89,7 +66,7 @@ export class DerivationService extends AbstractService { if (idx !== index) { return level } - return `${next}'` + return '' + next + '\'' }).join('/') } @@ -117,9 +94,9 @@ export class DerivationService extends AbstractService { // get the first non consecutive path index const firstCandidate = pathsSortedByIndexes - // fill an array with indexes with no consecutive next index, and the last index + // fill an array with indexes with no consecutive next index, and the last index .filter(({pathIndex}, i, self) => { - // the last path is always a candidate + // the last path is always a candidate if (i === self.length - 1) return true // next path is not consecutive, add it to candidates @@ -128,15 +105,15 @@ export class DerivationService extends AbstractService { // next path is consecutive, skip return false }).find(path => path) // find the first candidate - + // return path incremented from the first candidate return this.incrementPathLevel(firstCandidate.path, DerivationPathLevels.Account) } /** * Decrement a derivation path level - * @param {string} path - * @param {DerivationPathLevel} which + * @param {string} path + * @param {DerivationPathLevel} which * @return {string} */ public decrementPathLevel( @@ -163,27 +140,27 @@ export class DerivationService extends AbstractService { if (idx !== index) { return level } - return `${next}'` + return '' + next + '\'' }).join('/') } /** * Assert whether \a path is a valid derivation path - * @param {string} path + * @param {string} path * @return {void} * @throws {Error} On \a path with invalid derivation path */ public assertValidPath(path: string): void { if (!this.isValidPath(path)) { - const errorMessage = `Invalid derivation path: ${path}` - this.$store.dispatch('diagnostic/ADD_ERROR', errorMessage) + const errorMessage = 'Invalid derivation path: ' + path + console.error(errorMessage) throw new Error(errorMessage) } } /** * Assert whether derivation path level can be modified - * @param {DerivationPathLevels} which + * @param {DerivationPathLevels} which * @return {void} * @throws {Error} On \a which with protected path level value */ @@ -194,7 +171,7 @@ export class DerivationService extends AbstractService { ] if (undefined !== protect.find(type => which === type)) { const errorMessage = 'Cannot modify a derivation path\'s purpose and coin type levels.' - this.$store.dispatch('diagnostic/ADD_ERROR', errorMessage) + console.error(errorMessage) throw new Error(errorMessage) } } diff --git a/src/services/MosaicService.ts b/src/services/MosaicService.ts index 6cb6f4532..e82fa2872 100644 --- a/src/services/MosaicService.ts +++ b/src/services/MosaicService.ts @@ -1,30 +1,34 @@ -/** +/* * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. + * */ -import {Store} from 'vuex' -import {MosaicId, AccountInfo, NamespaceId, Mosaic, MosaicInfo, UInt64} from 'symbol-sdk' -// internal dependencies -import {AbstractService} from './AbstractService' -import {MosaicsRepository} from '@/repositories/MosaicsRepository' -import {MosaicsModel} from '@/core/database/entities/MosaicsModel' -import {NamespaceService} from './NamespaceService' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {combineLatest, Observable, of} from 'rxjs' +import * as _ from 'lodash' +import {AccountInfo, MosaicAliasTransaction, MosaicDefinitionTransaction, MosaicId, MosaicInfo, MosaicNames, NamespaceId, NamespaceRegistrationTransaction, NamespaceRegistrationType, QueryParams, RepositoryFactory, TransactionType, UInt64} from 'symbol-sdk' +import {flatMap, map, tap, toArray} from 'rxjs/operators' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' +import {ObservableHelpers} from '@/core/utils/ObservableHelpers' +import {fromIterable} from 'rxjs/internal-compatibility' +import {MosaicConfigurationModel} from '@/core/database/entities/MosaicConfigurationModel' // custom types export type ExpirationStatus = 'unlimited' | 'expired' | string +// TODO. Can this interface be removed? export interface AttachedMosaic { id: MosaicId | NamespaceId mosaicHex: string @@ -34,412 +38,244 @@ export interface AttachedMosaic { amount: number } -export class MosaicService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'mosaic' +/** + * The service in charge of loading and caching anything related to Mosaics from Rest. + * The cache is done by storing the payloads in SimpleObjectStorage. + * + * The service also holds configuration about the current mosaics, for example which mosaic + * balances are currently hidden. + */ +export class MosaicService { /** - * Vuex Store - * @var {Vuex.Store} + * Store that caches the mosaic information of the current accounts when returned from rest. */ - public $store: Store + private readonly mosaicDataStorage = new SimpleObjectStorage('mosaicData') /** - * Construct a service instance around \a store - * @param store + * The storage to keep user configuration around mosaics. For example, the balance hidden + * feature. */ - constructor(store?: Store) { - super() - this.$store = store - } + private readonly mosaicConfigurationsStorage = new SimpleObjectStorage>( + 'mosaicConfiguration') /** - * Read the collection of known mosaics from database. + * Store that caches the information around the network currency. The network currency is + * currently calculated from the block 1 transactions. * - * @param {Function} filterFn - * @return {MosaicsModel[]} + * In the near future, rest will return the information without loading block 1. */ - public getMosaics( - filterFn: ( - value: MosaicsModel, - index: number, - array: MosaicsModel[] - ) => boolean = () => true, - ): MosaicsModel[] { - const repository = new MosaicsRepository() - return repository.collect().filter(filterFn) - } - - /** - * Refresh mosaic models data - * @param {Mosaic[] | MosaicInfo[]} mosaics Mosaics to create / refresh in the database - * @param {boolean} [forceUpdate=false] Option to bypass the cache - */ - public refreshMosaicModels( - mosaics: Mosaic[] | MosaicInfo[], - forceUpdate = false, - ): void { - // @ts-ignore - const mosaicIds = mosaics.map(mosaic => mosaic.id) - - // initialize repository - const repository = new MosaicsRepository() - - // if force update is selected, fetch info for all mosaics - if (forceUpdate) { - this.fetchMosaicsInfos(mosaicIds as MosaicId[]) - return - } - - // determine mosaics known and unknown from the repository - const mosaicsInRepository: {id: MosaicId, known: boolean}[] = mosaicIds.map( - id => ({ id: id as MosaicId, known: repository.find(id.toHex()) }), - ) + private readonly networkCurrencyStorage = new SimpleObjectStorage( + 'networkCurrencyStorage') - // dispatch async handling for unknown mosaics - const unknownMosaics = mosaicsInRepository.filter(({known}) => !known).map(({id}) => id) - if(unknownMosaics.length) this.fetchMosaicsInfos(unknownMosaics) - } /** - * Read mosaic from database or dispatch fetch action - * from REST. + * This method loads and caches the mosaic information for the given accounts. + * The returned Observable will announce the cached information first, then the rest returned + * information (if possible). * - * @param {MosaicId} mosaicId - * @return {MosaicsModel} + * @param repositoryFactory + * @param networkCurrencies + * @param accountsInfo */ - public async getMosaic( - mosaicId: MosaicId, - isCurrencyMosaic: boolean = false, - isHarvestMosaic: boolean = false, - ): Promise { - const repository = new MosaicsRepository() - let mosaic: MosaicsModel - - if (!repository.find(mosaicId.toHex())) { - // - mosaic is unknown, fetch from REST + add to storage - mosaic = await this.fetchMosaicInfo(mosaicId, isCurrencyMosaic, isHarvestMosaic) - } - else { - // - mosaic known, build MosaicInfo from model - mosaic = repository.read(mosaicId.toHex()) - } - - return mosaic + public getMosaics(repositoryFactory: RepositoryFactory, networkCurrencies: NetworkCurrencyModel[], + accountsInfo: AccountInfo[]): Observable { + const mosaicDataList = this.loadMosaicData() + const mosaicIdOrAliases = _.flatten(accountsInfo.map(a => a.mosaics.map(m => m.id)) + .concat(networkCurrencies.map(n => new MosaicId(n.mosaicIdHex)))) + return this.resolveMosaicIds(repositoryFactory, mosaicIdOrAliases).pipe(flatMap(mosaicIds => { + const nameObservables = repositoryFactory.createNamespaceRepository() + .getMosaicsNames(mosaicIds) + const mosaicInfoObservable = repositoryFactory.createMosaicRepository().getMosaics(mosaicIds) + return combineLatest([ nameObservables, mosaicInfoObservable ]).pipe(map(p => { + const names = p[0] + const mosaicInfos = p[1] + return this.toMosaicDtos(accountsInfo, mosaicInfos, names, networkCurrencies) + })) + }), + ).pipe(tap((d) => this.saveMosaicData(d)), + ObservableHelpers.defaultFirst(mosaicDataList)) } /** - * Returns mosaic from database - * if mosaic is not found, fetch from REST + add to storage as a side effect - * @param {MosaicId} mosaicId - * @returns {(MosaicsModel | null)} + * + * Utility method that returns the mosaic expiration status + * @param mosaicInfo the mosaic info + * @param currentHeight */ - public getMosaicSync(mosaicId: MosaicId | NamespaceId): MosaicsModel | null { - if (!mosaicId) return // @TODO: find route cause, should not happen - - const repository = new MosaicsRepository() + public static getExpiration(mosaicInfo: MosaicModel, currentHeight: number): ExpirationStatus { + const duration = mosaicInfo.duration + const startHeight = mosaicInfo.height - // If the id is a NamespaceId, get the mosaicId from the namespace Id - if (mosaicId instanceof NamespaceId) { - const model = new NamespaceService(this.$store).getNamespaceSync(mosaicId) - if (!model) return null - - const namespaceInfo = model.objects.namespaceInfo - if (namespaceInfo.hasAlias() && namespaceInfo.alias.mosaicId) { - return this.getMosaicSync(namespaceInfo.alias.mosaicId) - } - } - - if (!repository.find(mosaicId.toHex())) { - // - mosaic is unknown, fetch from REST + add to storage - this.fetchMosaicInfo(mosaicId as MosaicId) - return null - } + // unlimited duration mosaics are flagged as duration == 0 + if (duration === 0) return 'unlimited' - return repository.read(mosaicId.toHex()) + // get current height + // calculate expiration + const expiresIn = startHeight + duration - (currentHeight || 0) + if (expiresIn <= 0) return 'expired' + // number of blocks remaining + return expiresIn.toLocaleString() } - /** - * Fetch mosaics infos and updates the mosaic repository - * @private - * @param {Mosaic[]} mosaic - * @returns {Promise} - */ - private async fetchMosaicsInfos(mosaicIds: MosaicId[]): Promise { - try { - // call REST_FETCH_INFO and REST_FETCH_NAMES - const [ - mosaicsInfo, mosaicNames, - ]: [ MosaicInfo[], {hex: string, name: string}[] ] = await Promise.all([ - this.$store.dispatch('mosaic/REST_FETCH_INFOS', mosaicIds), - this.$store.dispatch('mosaic/REST_FETCH_NAMES', mosaicIds), - ]) - - // initialize repository - const repository = new MosaicsRepository() - - // - get network info from store - const generationHash = this.$store.getters['network/generationHash'] - const networkMosaic: MosaicId = this.$store.getters['network/networkMosaic'] - // Create and store models - mosaicIds.forEach(mosaicId => { - const hexId = mosaicId.toHex() - - // get mosaic info - const mosaicInfo = mosaicsInfo.find(({id}) => id.equals(mosaicId)) - if (mosaicsInfo === undefined) return - - // get mosaic name - const nameEntry = mosaicNames.find(({hex}) => hex === hexId) - const name = nameEntry ? nameEntry.name : '' - - - // - find eventual existing model - const existingModel = repository.find(hexId) ? repository.read(hexId) : null - - // create model - const model = repository.createModel(new Map([ - [ 'hexId', hexId ], - [ 'name', name ], - [ 'flags', mosaicInfo.flags.toDTO().flags ], - [ 'startHeight', mosaicInfo.height.toHex() ], - [ 'duration', mosaicInfo.duration.toHex() ], - [ 'divisibility', mosaicInfo.divisibility ], - [ 'supply', mosaicInfo.supply.toHex() ], - [ 'ownerPublicKey', mosaicInfo.owner.publicKey ], - [ 'generationHash', generationHash ], - [ 'isCurrencyMosaic', mosaicId.equals(networkMosaic) ], - [ 'isHarvestMosaic', false ], // @TODO: not managed - [ 'isHidden', existingModel ? existingModel.values.get('isHidden') : false ], - ])) - - // - update model if found - if (existingModel) { - repository.update(mosaicId.toHex(), model.values) - return - } - - // - store model - repository.create(model.values) + private toMosaicDtos(accountDtos: AccountInfo[], mosaicDtos: MosaicInfo[], + mosaicNames: MosaicNames[], + networkCurrencies: NetworkCurrencyModel[]): MosaicModel[] { + return _.flatten(accountDtos.map(accountDto => { + return accountDto.mosaics.map(accountMosaicDto => { + const name = _.first( + mosaicNames.filter(n => n.mosaicId.toHex() == accountMosaicDto.id.toHex()) + .flatMap(n => n.names).map(n => n.name)) + const mosaicDto = mosaicDtos.find(n => n.id.toHex() === accountMosaicDto.id.toHex()) + const isCurrencyMosaic = !!networkCurrencies.find( + n => n.mosaicIdHex == accountMosaicDto.id.toHex()) + return new MosaicModel(accountDto.address.plain(), mosaicDto.owner.address.plain(), name, + isCurrencyMosaic, + accountMosaicDto.amount.compact(), mosaicDto) }) - } catch (error) { - this.$store.dispatch( - 'diagnostic/ADD_DEBUG', - `MosaicService/fetchMosaicsInfos error: ${JSON.stringify(error)}`, - ) - } + })) } - /** - * Read mosaic from REST using store action. - * - * @internal - * @param {MosaicId} mosaicId - * @return {MosaicsModel} - */ - protected async fetchMosaicInfo( - mosaicId: MosaicId, - isCurrencyMosaic: boolean = false, - isHarvestMosaic: boolean = false, - ): Promise { - // - get network info from store - const generationHash = this.$store.getters['network/generationHash'] - - try { - const hexId = mosaicId.toHex() - - // - fetch INFO from REST - const mosaicInfo = await this.$store.dispatch('mosaic/REST_FETCH_INFO', mosaicId) - // - fetch NAMES from REST - const mosaicNames = await this.$store.dispatch('mosaic/REST_FETCH_NAMES', [mosaicId]) - - // - use repository for storage - const repository = new MosaicsRepository() - - // - find eventual existing model - const existingModel = repository.find(hexId) ? repository.read(hexId) : null - - // - CREATE model - const mosaic = repository.createModel(new Map([ - [ 'hexId', hexId ], - [ 'name', mosaicNames && mosaicNames.length ? mosaicNames.shift().name : '' ], - [ 'flags', mosaicInfo.flags.toDTO().flags ], - [ 'startHeight', mosaicInfo.height.toHex() ], - [ 'duration', mosaicInfo.duration.toHex() ], - [ 'divisibility', mosaicInfo.divisibility ], - [ 'supply', mosaicInfo.supply.toHex() ], - [ 'ownerPublicKey', mosaicInfo.owner.publicKey ], - [ 'generationHash', generationHash ], - [ 'isCurrencyMosaic', isCurrencyMosaic ], - [ 'isHarvestMosaic', isHarvestMosaic ], - [ 'isHidden', existingModel ? existingModel.values.get('isHidden') : false ], - ])) - - // - update the model if it exists in the repository - if (existingModel) { - repository.update(mosaicId.toHex(), mosaic.values) + private resolveMosaicIds(repositoryFactory: RepositoryFactory, + ids: (NamespaceId | MosaicId)[]): Observable { + const namespaceRepository = repositoryFactory.createNamespaceRepository() + return fromIterable(ids).pipe(flatMap(id => { + if (id instanceof MosaicId) { + return of(id as MosaicId) } else { - // - create a new entry in the repository - repository.create(mosaic.values) + return namespaceRepository.getLinkedMosaicId(id as NamespaceId) } - - return mosaic - } - catch (e) { - const repository = new MosaicsRepository() - return repository.createModel(new Map([ - [ 'hexId', mosaicId.toHex() ], - [ 'name', mosaicId.toHex() ], - [ 'flags', null ], - [ 'startHeight', UInt64.fromUint(0).toHex() ], - [ 'duration', UInt64.fromUint(0).toHex() ], - [ 'divisibility', 0 ], - [ 'supply', UInt64.fromUint(0).toHex() ], - [ 'ownerPublicKey', '' ], - [ 'generationHash', generationHash ], - [ 'isCurrencyMosaic', isCurrencyMosaic ], - [ 'isHarvestMosaic', isHarvestMosaic ], - [ 'isHidden', false ], - ])) - } + })).pipe(toArray()) } - /** - * Format a mosaic amount to relative format - * @param {number} amount - * @param {MosaicId} mosaic - * @return {Promise} - */ - public async getRelativeAmount( - amount: number, - mosaic: MosaicId, - ): Promise { - const info = await this.getMosaic(mosaic) - return amount / Math.pow(10, info.values.get('divisibility') || 0) - } /** - * Format a mosaic amount to relative format - * @param {number} amount - * @param {MosaicId} mosaic - * @return {number} + * This method returns the list of {@link NetworkCurrencyModel} found in block 1. + * + * The intent of this method is to resolve the configured main (like cat.currency or symbol.xym) + * and harvest currencies (cat.harvest). More currencies may be defined in the block one. + * + * @param repositoryFactory tge repository factory used to load the block 1 transactions + * @return the list of {@link NetworkCurrencyModel} found in block 1. */ - public getRelativeAmountSync( - amount: number, - mosaic: MosaicId, - ): number { - const info = this.getMosaicSync(mosaic) - return amount / Math.pow(10, info.values.get('divisibility') || 0) + public getNetworkCurrencies(repositoryFactory: RepositoryFactory): Observable { + const storedNetworkCurrencies = this.networkCurrencyStorage.get() + const blockHttp = repositoryFactory.createBlockRepository() + // TODO move this to a service in the SDK. + return blockHttp.getBlockTransactions(UInt64.fromUint(1), new QueryParams({pageSize: 100})) + .pipe(flatMap(transactions => { + const mosaicTransactions = transactions.filter( + t => t.type == TransactionType.MOSAIC_DEFINITION).map(t => t as MosaicDefinitionTransaction) + const aliasTransactions = transactions.filter(t => t.type == TransactionType.MOSAIC_ALIAS) + .map(t => t as MosaicAliasTransaction) + const namespaceRegistrations = transactions.filter( + t => t.type == TransactionType.NAMESPACE_REGISTRATION) + .map(t => t as NamespaceRegistrationTransaction) + const networkCurrencies = mosaicTransactions.map(mosaicTransaction => { + const mosaicAliasTransactions = aliasTransactions.filter( + a => a.mosaicId.toHex() == mosaicTransaction.mosaicId.toHex()) + return mosaicAliasTransactions.map(mosaicAliasTransaction => this.getNetworkCurrency( + mosaicTransaction, mosaicAliasTransaction, + namespaceRegistrations)).filter(c => c) + }) + return networkCurrencies + })).pipe(tap(d => this.networkCurrencyStorage.set(d)), + ObservableHelpers.defaultFirst(storedNetworkCurrencies)) } - /** - * Get list of balances mapped by address - * @param {AccountInfo[]} accountsInfo - * @param {MosaicId} mosaic - * @return {Record} Object with address as key and balance as value - */ - public mapBalanceByAddress( - accountsInfo: AccountInfo[], - mosaic: MosaicId, - ): Record { - return accountsInfo.map(({mosaics, address}) => { - // - check balance - const hasNetworkMosaic = mosaics.find( - mosaicOwned => mosaicOwned.id.equals(mosaic)) - - // - account doesn't hold network mosaic - if (hasNetworkMosaic === undefined) { - return null - } - - // - map balance to address - const balance = hasNetworkMosaic.amount.compact() - return { - address: address.plain(), - balance: this.getRelativeAmountSync(balance, mosaic), - } - }).reduce((acc, {address, balance}) => ({...acc, [address]: balance}), {}) + private loadMosaicData(): MosaicModel[] { + return this.mosaicDataStorage.get() } - public getAttachedMosaicsFromMosaics(mosaics: Mosaic[]): AttachedMosaic[] { - return mosaics.map( - mosaic => { - const model = this.getMosaicSync(mosaic.id) - - // Skip and return default values until the model is fetched - if (!model) { - return { - id: mosaic.id, - mosaicHex: mosaic.id.toHex(), - amount: mosaic.amount.compact() / Math.pow(10, 0), - } - } - - const info = model.objects.mosaicInfo - const divisibility = info ? info.divisibility : 0 + private saveMosaicData(mosaics: MosaicModel[]) { + this.mosaicDataStorage.set(mosaics) + } - return ({ - id: new MosaicId(model.getIdentifier()), - mosaicHex: mosaic.id.toHex(), - amount: mosaic.amount.compact() / Math.pow(10, divisibility), - }) - }) + public reset() { + this.mosaicDataStorage.remove() + this.networkCurrencyStorage.remove() } /** - * Returns a view of a mosaic expiration info - * @private - * @param {MosaicsInfo} mosaic - * @returns {ExpirationStatus} + * This method tries to {@link NetworkCurrencyModel} from the original {@link + * MosaicDefinitionTransaction} and {@link MosaicAliasTransaction}. + * + * @param mosaicTransaction the original mosiac transaction + * @param mosaicAliasTransaction the original mosaic alias transaction used to know the + * mosaic/currency namespace + * @param namespaceRegistrations the list of namespace registration used to resolve the + * mosaic/currency full name + * @return the {@link NetworkCurrencyModel} if it can be resolved. */ - public getExpiration(mosaicInfo: MosaicInfo): ExpirationStatus { - const duration = mosaicInfo.duration.compact() - const startHeight = mosaicInfo.height.compact() - - // unlimited duration mosaics are flagged as duration == 0 - if (duration === 0) return 'unlimited' - - // get current height - const currentHeight = this.$store.getters['network/currentHeight'] || 0 - - // calculate expiration - const expiresIn = startHeight + duration - currentHeight - if (expiresIn <= 0) return 'expired' - - // number of blocks remaining - return expiresIn.toLocaleString() + private getNetworkCurrency(mosaicTransaction: MosaicDefinitionTransaction, + mosaicAliasTransaction: MosaicAliasTransaction, + namespaceRegistrations: NamespaceRegistrationTransaction[]): NetworkCurrencyModel | undefined { + + const mosaicId = mosaicAliasTransaction.mosaicId + const namespaceName = this.getNamespaceFullName(namespaceRegistrations, + mosaicAliasTransaction.namespaceId) + if (!namespaceName) { + return undefined + } + const namespaceId = new NamespaceId(namespaceName) + const ticker = namespaceId && namespaceId.fullName && namespaceId.fullName.split('.').pop() + .toUpperCase() || undefined + return new NetworkCurrencyModel(mosaicId.toHex(), namespaceId.toHex(), namespaceId.fullName, + mosaicTransaction.divisibility, mosaicTransaction.flags.transferable, + mosaicTransaction.flags.supplyMutable, mosaicTransaction.flags.restrictable, ticker) } + // } /** - * Set the hidden state of a mosaic - * If no param is provided, the hidden state will be toggled - * @param {(MosaicId | NamespaceId)} mosaicId - * @param {boolean} [hide] Should the mosaic be hidden? + * This method resolves the full name of a leaf namespace if possible. It used the completed + * {@link NamespaceRegistrationTransaction} and creates the full name recursively from button + * (leaf) up (root) + * + * @param transactions the {@link NamespaceRegistrationTransaction} list + * @param namespaceId the leaf namespace. + * @return the full name of the namespace if all the parents namespace can be resolved. */ - public toggleHiddenState(mosaicId: MosaicId | NamespaceId, hide?: boolean): void { - const hexId = mosaicId.toHex() - - // get repository - const repository = new MosaicsRepository() - - // return if the mosaic is not found in the database - if (!repository.find(hexId)) return + private getNamespaceFullName(transactions: NamespaceRegistrationTransaction[], + namespaceId: NamespaceId): string | undefined { + if (namespaceId.fullName) { + return namespaceId.fullName + } + const namespaceRegistrationTransaction = transactions.find( + tx => tx.namespaceId.toHex() === namespaceId.toHex()) + if (!namespaceRegistrationTransaction) { + return undefined + } + if (namespaceRegistrationTransaction.registrationType == NamespaceRegistrationType.RootNamespace) { + return namespaceRegistrationTransaction.namespaceName + } else { + const parentNamespaceNameOptional = this.getNamespaceFullName(transactions, + namespaceRegistrationTransaction.parentId) + if (!parentNamespaceNameOptional) { + return undefined + } else { + return `${parentNamespaceNameOptional}.${namespaceRegistrationTransaction.namespaceName}` + } + } + } - // get model - const model = repository.read(hexId) + public getMosaicConfigurations(): Record { + return this.mosaicConfigurationsStorage.get() || {} + } - // get next visibility state - const nextVisibilityState = hide === undefined ? !model.values.get('isHidden') : hide - - // update visibility state - model.values.set('isHidden', nextVisibilityState) + public getMosaicConfiguration(mosaicId: MosaicId): MosaicConfigurationModel { + return this.getMosaicConfigurations()[mosaicId.toHex()] || new MosaicConfigurationModel() + } - // persist change - repository.update(hexId, model.values) + public changeMosaicConfiguration(mosaicId: MosaicId, + newConfigs: any): Record { + const mosaicConfigurationsStorage = this.getMosaicConfigurations() + mosaicConfigurationsStorage[mosaicId.toHex()] = { + ...this.getMosaicConfiguration(mosaicId), ...newConfigs, + } + this.mosaicConfigurationsStorage.set(mosaicConfigurationsStorage) + return mosaicConfigurationsStorage } + } diff --git a/src/services/MultisigService.ts b/src/services/MultisigService.ts index 5e2df92f7..2e2c90535 100644 --- a/src/services/MultisigService.ts +++ b/src/services/MultisigService.ts @@ -1,38 +1,24 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' -import {MultisigAccountGraphInfo, MultisigAccountInfo} from 'symbol-sdk' -import VueI18n from 'vue-i18n' - +import {Address, MultisigAccountGraphInfo, MultisigAccountInfo, NetworkType} from 'symbol-sdk' // internal dependencies -import {AbstractService} from './AbstractService' -import {WalletService} from './WalletService' - -export class MultisigService extends AbstractService { +import {WalletModel} from '@/core/database/entities/WalletModel' +import {Signer} from '@/store/Wallet' - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store, i18n?: VueI18n) { - super() - this.name = 'multisig' - this.$store = store - this.$i18n = i18n - } +export class MultisigService { /** * Returns all available multisig info from a multisig graph @@ -40,9 +26,7 @@ export class MultisigService extends AbstractService { * @param {MultisigAccountGraphInfo} multisig graph info * @returns {MultisigAccountInfo[]} multisig info */ - public static getMultisigInfoFromMultisigGraphInfo( - graphInfo: MultisigAccountGraphInfo, - ): MultisigAccountInfo[] { + public static getMultisigInfoFromMultisigGraphInfo(graphInfo: MultisigAccountGraphInfo): MultisigAccountInfo[] { const {multisigAccounts} = graphInfo const multisigsInfo = [...multisigAccounts.keys()] @@ -53,47 +37,38 @@ export class MultisigService extends AbstractService { return [].concat(...multisigsInfo).map(item => item) // flatten } - /** - * Returns self and signer to be injected in SignerSelector - * @param {string} label_postfix_multisig - * @returns {{publicKey: any; label: any;}[]} - */ - public getSigners(): {publicKey: any, label: any}[] { - if (!this.$store || !this.$i18n) { - throw new Error('multisig service getSigners method needs the store and i18n') - } - - // get the current wallet from the store - const currentWallet = this.$store.getters['wallet/currentWallet'] + public getSigners(networkType: NetworkType, + knownWallets: WalletModel[], + currentWallet: WalletModel, + currentWalletMultisigInfo: MultisigAccountInfo | undefined): Signer[] { if (!currentWallet) return [] - - const self = [ + const self: Signer[] = [ { - publicKey: currentWallet.values.get('publicKey'), - label: currentWallet.values.get('name'), + address: Address.createFromRawAddress(currentWallet.address), + publicKey: currentWallet.publicKey, + label: currentWallet.name, + multisig: currentWalletMultisigInfo && currentWalletMultisigInfo.isMultisig(), }, ] - const multisigInfo = this.$store.getters['wallet/currentWalletMultisigInfo'] - if (!multisigInfo) return self - - // in case "self" is a multi-signature account - if (multisigInfo && multisigInfo.isMultisig()) { - self[0].label = `${self[0].label}${this.$i18n.t('label_postfix_multisig')}` + if (!currentWalletMultisigInfo) { + return self } - // add multisig accounts of which "self" is a cosignatory - if (multisigInfo) { - const service = new WalletService(this.$store) - const networkType = this.$store.getters['wallet/currentWalletMultisigInfo'] - return self.concat(...multisigInfo.multisigAccounts.map( - ({publicKey}) => ({ - publicKey, - label: `${service.getWalletLabel(publicKey, networkType)}${this.$i18n.t('label_postfix_multisig')}`, - }))) - } + return self.concat(...currentWalletMultisigInfo.multisigAccounts.map( + ({publicKey, address}) => ({ + address, + publicKey, + multisig: true, + label: this.getWalletLabel(address, knownWallets), + }))) + + } + - return self + private getWalletLabel(address: Address, wallets: WalletModel[]): string { + const wallet = wallets.find(wlt => address.plain() === wlt.address) + return wallet && wallet.name || address.plain() } } diff --git a/src/services/NamespaceService.ts b/src/services/NamespaceService.ts index 5d3157e03..4b2cba3b3 100644 --- a/src/services/NamespaceService.ts +++ b/src/services/NamespaceService.ts @@ -1,192 +1,82 @@ -/** +/* * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. + * */ -import {Store} from 'vuex' -import {NamespaceId, NamespaceInfo, NamespaceName, Alias, AliasType} from 'symbol-sdk' - -// internal dependencies -import {AbstractService} from './AbstractService' -import {NamespacesRepository} from '@/repositories/NamespacesRepository' -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' -export class NamespaceService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'namespace' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' +import {Address, NamespaceName, RepositoryFactory} from 'symbol-sdk' +import {Observable} from 'rxjs' +import {flatMap, map, tap} from 'rxjs/operators' +import {ObservableHelpers} from '@/core/utils/ObservableHelpers' +import * as _ from 'lodash' +import {TimeHelpers} from '@/core/utils/TimeHelpers' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } - - /** - * Read the collection of known namespaces from database. - * - * @param {Function} filterFn - * @return {MosaicsModel[]} - */ - public getNamespaces( - filterFn: ( - value: NamespacesModel, - index: number, - array: NamespacesModel[] - ) => boolean = () => true, - ): NamespacesModel[] { - const repository = new NamespacesRepository() - return repository.collect().filter(filterFn) - } - - /** - * Read namespace from database or dispatch fetch action - * from REST. - * - * @param {MosaicId} mosaicId - * @return {NamespacesModel} - */ - public async getNamespace( - namespaceId: NamespaceId, - ): Promise { - - const repository = new NamespacesRepository() - let namespace: NamespacesModel - - if (!repository.find(namespaceId.toHex())) { - // - namespace is unknown, fetch from REST + add to storage - namespace = await this.fetchNamespaceInfo(namespaceId) - } - else { - // - mosaic known, read NamespacesModel - namespace = repository.read(namespaceId.toHex()) - } - - return namespace - } +/** + * The service in charge of loading and caching anything related to Namepsaces from Rest. + * The cache is done by storing the payloads in SimpleObjectStorage. + */ +export class NamespaceService { /** - * Returns namespace from database - * if namespace is not found, fetch from REST + add to storage as a side effect - * @param {NamespaceId} mosaicId - * @returns {(NamespacesModel | null)} + * The namespace information local cache. */ - public getNamespaceSync(namespaceId: NamespaceId): NamespacesModel | null { - const repository = new NamespacesRepository() - - if (!repository.find(namespaceId.toHex())) { - // - namespace is unknown, fetch from REST + add to storage - this.fetchNamespaceInfo(namespaceId) - return null - } - // - mosaic known, read NamespacesModel - return repository.read(namespaceId.toHex()) - } + private readonly namespaceModelStorage = new SimpleObjectStorage( + 'namespaceModel') /** - * Read namespace from REST using store action. + * This method loads and caches the namespace information for the given accounts. + * The returned Observable will announce the cached information first, then the rest returned + * information (if possible). * - * @internal - * @param {MosaicId} mosaicId - * @return {MosaicsModel} - */ - protected async fetchNamespaceInfo( - namespaceId: NamespaceId, - ): Promise { - // - fetch INFO from REST - const namespaceInfo: NamespaceInfo = await this.$store.dispatch('namespace/REST_FETCH_INFO', namespaceId) - - // - update and return namespace model - return this.updateNamespace(namespaceInfo) - } - - /** - * Update namespaces in database - * @param {NamespaceInfo[]} namespacesInfo - * @returns {Promise} + * @param repositoryFactory the repository factory + * @param addresses the current addresses. */ - public async updateNamespaces(namespacesInfo: NamespaceInfo[]): Promise { - for (const namespaceInfo of namespacesInfo) await this.updateNamespace(namespaceInfo) + public getNamespaces(repositoryFactory: RepositoryFactory, + addresses: Address[]): Observable { + const namespaceModelList = this.namespaceModelStorage.get() + const namespaceRepository = repositoryFactory.createNamespaceRepository() + return namespaceRepository.getNamespacesFromAccounts(addresses) + .pipe(flatMap(namespaceInfos => { + return namespaceRepository.getNamespacesName(namespaceInfos.map(info => info.id)) + .pipe(map(names => { + return namespaceInfos.map(namespaceInfo => { + const reference = _.first( + names.filter(n => n.namespaceId.toHex() === namespaceInfo.id.toHex())) + return new NamespaceModel(namespaceInfo, + NamespaceService.getFullNameFromNamespaceNames(reference, names)) + }) + })) + })).pipe(tap(d => this.namespaceModelStorage.set(d)), + ObservableHelpers.defaultFirst(namespaceModelList)) } - /** - * Update and return a namespace model - * @private - * @param {NamespaceInfo} namespaceInfo - * @returns {Promise} - */ - private async updateNamespace(namespaceInfo: NamespaceInfo): Promise { - const hexId = namespaceInfo.id.toHex() - - // - use repository for storage - const repository = new NamespacesRepository() - - // - get namespace model from database if it exists - const existingModel = repository.find(hexId) ? repository.read(hexId) : null - - // - get namespace full name - const fullName = existingModel - ? existingModel.values.get('name') - : await this.getNamespaceFullName(namespaceInfo) - - // - create model - const namespace = repository.createModel(new Map([ - [ 'hexId', hexId ], - [ 'name', fullName ], - [ 'depth', namespaceInfo.depth ], - [ 'level0', namespaceInfo.levels[0].toHex() ], - [ 'level1', namespaceInfo.levels.length > 1 ? namespaceInfo.levels[1].toHex() : '' ], - [ 'level2', namespaceInfo.levels.length > 2 ? namespaceInfo.levels[2].toHex() : '' ], - [ 'alias', this.getAliasInStorageFormat(namespaceInfo.alias) ], - [ 'parentId', namespaceInfo.depth !== 1 ? namespaceInfo.parentNamespaceId().toHex() : '' ], - [ 'startHeight', namespaceInfo.startHeight.toHex() ], - [ 'endHeight', namespaceInfo.endHeight.toHex() ], - [ 'ownerPublicKey', namespaceInfo.owner.publicKey ], - [ 'generationHash', this.$store.getters['network/generationHash'] ], - ])) - // - update the model in database if it exists... - if (existingModel) repository.update(hexId, namespace.values) - // ... or create a new model - else repository.create(namespace.values) - - return namespace - } - - /** - * Get a namespace full name from REST - * @private - * @param {NamespaceInfo} namespaceInfo - * @returns {Promise} - */ - private async getNamespaceFullName(namespaceInfo: NamespaceInfo): Promise { - const namespaceIds: NamespaceId[] = namespaceInfo.levels.map(id => id) - const namespaceNames: { - hex: string - name: string - }[] = await this.$store.dispatch('namespace/REST_FETCH_NAMES', namespaceIds) - return namespaceNames.find(({hex}) => hex === namespaceInfo.id.toHex()).name + public static getExpiration(networkConfiguration: NetworkConfigurationModel, + currentHeight: number, + endHeight: number): { expiration: string, expired: boolean } { + const blockGenerationTargetTime = networkConfiguration.blockGenerationTargetTime + const namespaceGracePeriodBlocks = Math.floor( + networkConfiguration.namespaceGracePeriodDuration / blockGenerationTargetTime) + const expired = currentHeight > endHeight - namespaceGracePeriodBlocks + const expiredIn = endHeight - namespaceGracePeriodBlocks - currentHeight + const deletedIn = endHeight - currentHeight + const expiration = expired + ? TimeHelpers.durationToRelativeTime(expiredIn, blockGenerationTargetTime) + : TimeHelpers.durationToRelativeTime(deletedIn, blockGenerationTargetTime) + return {expired, expiration} } /** @@ -194,36 +84,18 @@ export class NamespaceService extends AbstractService { * @static * @param {NamespaceName} reference * @param {NamespaceName[]} namespaceNames - * @returns {NamespaceName} + * @returns the full namespace name. */ - public static getFullNameFromNamespaceNames( - reference: NamespaceName, - namespaceNames: NamespaceName[], - ): NamespaceName { - if (!reference.parentId) return reference + public static getFullNameFromNamespaceNames(reference: NamespaceName, + namespaceNames: NamespaceName[]): string { + if (!reference) {return ''} + if (!reference.parentId) return reference.name const parent = namespaceNames.find( namespaceName => namespaceName.namespaceId.toHex() === reference.parentId.toHex(), ) - - if (parent === undefined) return reference - - return NamespaceService.getFullNameFromNamespaceNames( - new NamespaceName(parent.namespaceId, `${parent.name}.${reference.name}`, parent.parentId), - namespaceNames, - ) - } - - /** - * Converts an alias in the format used for alias storage in database - * @private - * @param {Alias} alias - * @returns {{type: number, mosaicId?: string, address?: string}} - */ - private getAliasInStorageFormat(alias: Alias): {type: number, mosaicId?: string, address?: string} { - const {type} = alias - if (type === AliasType.None) return {type} - if (type === AliasType.Mosaic) return {type, mosaicId: alias.mosaicId.toHex()} - if (type === AliasType.Address) return {type, address: alias.address.plain()} + if (parent === undefined) return reference.name + const parentName = NamespaceService.getFullNameFromNamespaceNames(parent, namespaceNames) + return `${parentName}.${reference.name}` } } diff --git a/src/services/NetworkService.ts b/src/services/NetworkService.ts new file mode 100644 index 000000000..0b2945738 --- /dev/null +++ b/src/services/NetworkService.ts @@ -0,0 +1,154 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NetworkModel} from '@/core/database/entities/NetworkModel' +import {Listener, NetworkConfiguration, RepositoryFactory, RepositoryFactoryHttp} from 'symbol-sdk' +import {URLHelpers} from '@/core/utils/URLHelpers' +import {combineLatest, defer, EMPTY, Observable} from 'rxjs' +import {catchError, flatMap, map, take, tap} from 'rxjs/operators' +import * as _ from 'lodash' + +import {ObservableHelpers} from '@/core/utils/ObservableHelpers' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' + +import networkConfig from '../../config/network.conf.json' +import {fromIterable} from 'rxjs/internal-compatibility' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' +import {NetworkConfigurationHelpers} from '@/core/utils/NetworkConfigurationHelpers' + +/** + * The service in charge of loading and caching anything related to Network from Rest. + * The cache is done by storing the payloads in SimpleObjectStorage. + */ +export class NetworkService { + /** + * The network information local cache. + */ + private readonly storage = new SimpleObjectStorage('network') + + /** + * The best default Url. It uses the stored condiguration if possible. + */ + public getDefaultUrl(): string { + const storedNetworkModel = this.loadNetworkModel() + return URLHelpers.formatUrl( + storedNetworkModel && storedNetworkModel.url || networkConfig.defaultNodeUrl).url + } + + /** + * This method get the network data from the provided URL. If the server in the newUrl is down, + * the next available url will be used. + * + * @param newUrl the new url. + */ + public getNetworkModel(newUrl: string): + Observable<{ networkModel: NetworkModel, repositoryFactory: RepositoryFactory }> { + const storedNetworkModel = this.loadNetworkModel() + const possibleUrls = this.resolveCandidates(newUrl, storedNetworkModel) + const repositoryFactoryObservable = defer( + () => fromIterable(possibleUrls).pipe(flatMap(url => this.createRepositoryFactory(url)))) + .pipe(take(1)) + return repositoryFactoryObservable.pipe(flatMap(({url, repositoryFactory}) => { + const networkRepository = repositoryFactory.createNetworkRepository() + const nodeRepository = repositoryFactory.createNodeRepository() + return combineLatest([ + repositoryFactory.getNetworkType().pipe(ObservableHelpers.defaultLast( + storedNetworkModel && storedNetworkModel.networkType || networkConfig.defaultNetworkType)), + repositoryFactory.getGenerationHash().pipe(ObservableHelpers.defaultLast( + storedNetworkModel && storedNetworkModel.generationHash)), + networkRepository.getNetworkProperties().pipe(map(d => this.toNetworkConfigurationModel(d)), + ObservableHelpers.defaultLast( + storedNetworkModel && storedNetworkModel.networkConfiguration)), + nodeRepository.getNodeInfo().pipe(ObservableHelpers.defaultLast( + storedNetworkModel && storedNetworkModel.nodeInfo)), + ]).pipe(map(restData => { + return { + networkModel: new NetworkModel(url, restData[0], restData[1], restData[2], restData[3]), + repositoryFactory, + } + }), tap(p => this.saveNetworkModel(p.networkModel))) + })) + } + + private createRepositoryFactory(url: string): Observable<{ url: string, repositoryFactory: RepositoryFactory }> { + + const repositoryFactory = NetworkService.createRepositoryFactory(url) + return defer(() => { + return repositoryFactory.getGenerationHash() + }).pipe(map(() => { + console.log(`Peer ${url} seems OK`) + return {url, repositoryFactory} + }), catchError(e => { + console.log(`Peer ${url} seems down. Ignoring. Error: ${e.message}`, e) + return EMPTY + })) + } + + private toNetworkConfigurationModel(dto: NetworkConfiguration): NetworkConfigurationModel { + const fileDefaults: NetworkConfigurationModel = networkConfig.networkConfigurationDefaults + const fromDto: NetworkConfigurationModel = { + maxMosaicDivisibility: NetworkConfigurationHelpers.maxMosaicDivisibility(dto), + namespaceGracePeriodDuration: NetworkConfigurationHelpers.namespaceGracePeriodDuration(dto), + lockedFundsPerAggregate: NetworkConfigurationHelpers.lockedFundsPerAggregate(dto), + maxCosignatoriesPerAccount: NetworkConfigurationHelpers.maxCosignatoriesPerAccount(dto), + blockGenerationTargetTime: NetworkConfigurationHelpers.blockGenerationTargetTime(dto), + maxNamespaceDepth: NetworkConfigurationHelpers.maxNamespaceDepth(dto), + maxMosaicDuration: NetworkConfigurationHelpers.maxMosaicDuration(dto), + minNamespaceDuration: NetworkConfigurationHelpers.minNamespaceDuration(dto), + maxNamespaceDuration: NetworkConfigurationHelpers.maxNamespaceDuration(dto), + maxTransactionsPerAggregate: NetworkConfigurationHelpers.maxTransactionsPerAggregate(dto), + maxCosignedAccountsPerAccount: NetworkConfigurationHelpers.maxCosignedAccountsPerAccount(dto), + maxMessageSize: NetworkConfigurationHelpers.maxMessageSize(dto), + maxMosaicAtomicUnits: NetworkConfigurationHelpers.maxMosaicAtomicUnits(dto), + } + return {...fileDefaults, ...fromDto} + } + + + private resolveCandidates(newUrl: string, + storedNetworkModel: NetworkModel | undefined): string[] { + // Should we load cached candidates in the node tables? + return _.uniq( + [ newUrl, storedNetworkModel && storedNetworkModel.url, networkConfig.defaultNodeUrl, + ...networkConfig.nodes.map(n => n.url) ].filter(p => p)) + } + + + private loadNetworkModel(): NetworkModel | undefined { + return this.storage.get() + } + + private saveNetworkModel(networkModel: NetworkModel) { + this.storage.set(networkModel) + } + + public reset() { + this.storage.remove() + } + + /** + * It creates the RepositoryFactory used to build the http repository/clients and listeners. + * @param url the url. + */ + public static createRepositoryFactory(url: string): RepositoryFactory { + const repositoryFactory = new RepositoryFactoryHttp(url) + const wsUrl = URLHelpers.httpToWsUrl(url) + repositoryFactory.createListener = () => { + return new Listener(wsUrl, WebSocket) + } + return repositoryFactory + } +} diff --git a/src/services/NodeService.ts b/src/services/NodeService.ts new file mode 100644 index 000000000..ee30be715 --- /dev/null +++ b/src/services/NodeService.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +import {NodeInfo, RepositoryFactory, RoleType} from 'symbol-sdk' +import {combineLatest, Observable} from 'rxjs' +import {ObservableHelpers} from '@/core/utils/ObservableHelpers' +import {map, tap} from 'rxjs/operators' +import {NodeModel} from '@/core/database/entities/NodeModel' +import {URLHelpers} from '@/core/utils/URLHelpers' +import * as _ from 'lodash' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' + + +import networkConfig from '@/../config/network.conf.json' + +/** + * The service in charge of loading and caching anything related to Node and Peers from Rest. + * The cache is done by storing the payloads in SimpleObjectStorage. + */ +export class NodeService { + + /** + * The peer information local cache. + */ + private readonly storage = new SimpleObjectStorage('node') + + public getNodes(repositoryFactory: RepositoryFactory, url: string): Observable { + const storedNodes = this.loadNodes().concat(this.loadStaticNodes()) + const nodeRepository = repositoryFactory.createNodeRepository() + + function toNodeModel(n: NodeInfo): NodeModel | undefined { + if (!n.host || n.roles == RoleType.PeerNode) { + return undefined + } + const resolvedUrl = URLHelpers.getNodeUrl(n.host) + return new NodeModel(resolvedUrl, n.friendlyName || resolvedUrl) + } + + return combineLatest([ + nodeRepository.getNodeInfo().pipe(map(dto => new NodeModel(url, dto.friendlyName || ''))) + .pipe(ObservableHelpers.defaultLast( + new NodeModel(url, url))), + nodeRepository.getNodePeers().pipe(map(l => l.map(toNodeModel).filter(n => n && n.url))) + .pipe(ObservableHelpers.defaultLast( + storedNodes)), + + ]).pipe(map(restData => { + const currentNode = restData[0] + const nodePeers = restData[1] + const nodeInfos = [currentNode].concat(nodePeers, storedNodes) + return _.sortBy(_.uniqBy(nodeInfos, 'url'), 'friendlyName') + }), tap(p => this.saveNodes(p))) + } + + + private loadStaticNodes(): NodeModel[] { + return networkConfig.nodes.map(n => { + return new NodeModel(n.url, n.friendlyName) + }) + } + + private loadNodes(): NodeModel[] { + return this.storage.get() || [] + } + + public saveNodes(nodes: NodeModel[]) { + this.storage.set(nodes) + } + + public reset() { + this.storage.remove() + } +} diff --git a/src/services/PeerService.ts b/src/services/PeerService.ts deleted file mode 100644 index 274f10719..000000000 --- a/src/services/PeerService.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Store} from 'vuex' - -// internal dependencies -import {AbstractService} from './AbstractService' -import {PeersRepository} from '@/repositories/PeersRepository' -import {PeersModel} from '@/core/database/entities/PeersModel' -import {URLHelpers} from '@/core/utils/URLHelpers' - -export class PeerService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'peer' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } - - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public getEndpoints( - filterFn: ( - value: PeersModel, - index: number, - array: PeersModel[] - ) => boolean = () => true, - ): PeersModel[] { - const repository = new PeersRepository() - return repository.collect().filter(filterFn) - } - - /** - * Get full node url and add missing pieces - * @param {string} fromUrl - * @return {string} - */ - public getNodeUrl(fromUrl: string): string { - let fixedUrl = -1 === fromUrl.indexOf('://') - ? `http://${fromUrl}` - : fromUrl - - fixedUrl = !fixedUrl.match(/https?:\/\/[^:]+:([0-9]+)\/?$/) - ? `${fixedUrl}:3000` // default adds :3000 - : fixedUrl - - const url = URLHelpers.formatUrl(fixedUrl) - return `${url.protocol}//${url.hostname}${url.port ? `:${url.port}` : ':3000'}` - } - - /** - * Delete endpoint from the database - * @param {string} url - */ - public deleteEndpoint(url: string): void { - // get full node url - const fullUrl = this.getNodeUrl(url) - - // find node model in database - const endpoint = this.getEndpoints().find( - model => model.values.get('rest_url') === fullUrl, - ) - - // throw if node is not found in the database - if (endpoint === undefined) { - throw new Error(`This url was not found in the peer repository: ${url}`) - } - - // delete the node from the database - new PeersRepository().delete(endpoint.getIdentifier()) - } - - /** - * Update the default node in the database - * @param {string} url - */ - public setDefaultNode(url: string): void { - // get full node url - const fullUrl = this.getNodeUrl(url) - - // find node model in database - const selectedEndpoint = this.getEndpoints().find( - model => model.values.get('rest_url') === fullUrl, - ) - - // throw if node is not found in the database - if (selectedEndpoint === undefined) { - throw new Error(`This url was not found in the peer repository: ${url}`) - } - - const currentlyActiveEndpoint = this.getEndpoints().find( - model => model.values.get('is_default') === true, - ) - - // throw if current active node is not found in the database - if (currentlyActiveEndpoint === undefined) { - throw new Error('The default endpoint was not found in the database') - } - - // return if the node has not changed - if (selectedEndpoint.getIdentifier() === currentlyActiveEndpoint.getIdentifier()) return - - // get the peers repository - const peersRepository = new PeersRepository() - - // update the currently active endpoint - currentlyActiveEndpoint.values.set('is_default', false) - peersRepository.update(currentlyActiveEndpoint.getIdentifier(), currentlyActiveEndpoint.values) - - // set the selected endpoint as default - selectedEndpoint.values.set('is_default', true) - peersRepository.update(selectedEndpoint.getIdentifier(), selectedEndpoint.values) - } -} diff --git a/src/services/RESTService.ts b/src/services/RESTService.ts index e97e7ec76..65aa231d3 100644 --- a/src/services/RESTService.ts +++ b/src/services/RESTService.ts @@ -13,15 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Address, - BlockInfo, - IListener, - Listener, - RepositoryFactory, - RepositoryFactoryHttp, - TransactionStatusError, -} from 'symbol-sdk' +import {Address, IListener, Listener, RepositoryFactory, RepositoryFactoryHttp, TransactionStatusError} from 'symbol-sdk' import {Subscription} from 'rxjs' // internal dependencies import {AddressValidator} from '@/core/validation/validators' @@ -125,28 +117,4 @@ export class RESTService { } return repositoryFactory } - - /** - * Subscribe to blocks websocket channels - * @param {Context} context the context - * @param {RepositoryFactory} repositoryFactory the repository factory used to create the listener - */ - public static async subscribeBlocks( - context: { dispatch: any, commit: any }, - repositoryFactory: RepositoryFactory, - ): Promise<{ listener: IListener, subscriptions: Subscription[] }> { - // open websocket connection - const listener = repositoryFactory.createListener() - await listener.open() - - context.dispatch('diagnostic/ADD_DEBUG', 'Opening REST block websocket channel connection', {root: true}) - - const newBlock = listener.newBlock().subscribe((block: BlockInfo) => { - context.dispatch('SET_CURRENT_HEIGHT', block.height.compact()) - context.dispatch('ADD_BLOCK', block) - context.dispatch('diagnostic/ADD_INFO', `New block height: ${block.height.compact()}`, {root: true}) - }) - - return {listener, subscriptions: [newBlock]} - } } diff --git a/src/services/RemoteAccountService.ts b/src/services/RemoteAccountService.ts deleted file mode 100644 index ac8fa08af..000000000 --- a/src/services/RemoteAccountService.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Store} from 'vuex' -import {Account, AccountType, Address, PublicAccount, RepositoryFactory} from 'symbol-sdk' -import {Wallet} from 'symbol-hd-wallets' -// internal dependencies -import {AbstractService} from '@/services/AbstractService' -import {WalletService} from '@/services/WalletService' -import {DerivationPathLevels, DerivationService} from '@/services/DerivationService' - -export class RemoteAccountService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'remote-account' - - /** - * Default remote account derivation path - * @var {string} - */ - public static readonly DEFAULT_PATH = 'm/44\'/43\'/0\'/1\'/0\'' - - /** - * Wallets service - * @var {WalletService} - */ - protected wallets: WalletService - - /** - * Derivation service - * @var {DerivationService} - */ - protected paths: DerivationService - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - this.wallets = new WalletService(store) - this.paths = new DerivationService(store) - } - - /** - * Get next available remote account public key - * @param {Wallet} wallet - * @param {string} path - * @return {Promise} - */ - async getNextRemoteAccountPublicKey( - wallet: Wallet, - path: string = RemoteAccountService.DEFAULT_PATH, - ): Promise { - // validate derivation path - this.paths.assertValidPath(path) - - try { - // prepare discovery process - const repositoryFactory = this.$store.getters['network/repositoryFactory'] as RepositoryFactory - const networkType = this.$store.getters['network/networkType'] - const accountHttp = repositoryFactory.createAccountRepository() - - // generate 10 remote accounts - let nextPath: string = path - const remoteAccounts: Map = new Map() - const remoteAddresses: Address[] = [] - for (let i = 0; i < 10; i ++) { - // derive child account and store - const remoteAccount = wallet.getChildAccount(nextPath, networkType) - // @ts-ignore // @TODO: SDK upgrade - remoteAccounts.set(remoteAccount.address.plain(), remoteAccount) - // @ts-ignore // @TODO: SDK upgrade - remoteAddresses.push(remoteAccount.address) - - // increment derivation path - nextPath = this.paths.incrementPathLevel(nextPath, DerivationPathLevels.Remote) - } - - // read account infos - const remoteInfos = await accountHttp.getAccountsInfo(remoteAddresses).toPromise() - const firstLinkable = remoteInfos.filter((remoteInfo) => { - return remoteInfo.accountType && remoteInfo.accountType === AccountType.Remote_Unlinked - }).shift() - - // instantiate with first available remote account - return PublicAccount.createFromPublicKey(firstLinkable.publicKey, networkType) - } - catch (error) { - const errorMessage = `An error happened while generating remote account: ${error}` - this.$store.dispatch('diagnostic/ADD_ERROR', errorMessage) - throw new Error(errorMessage) - } - } -/* - public getPersistentDelegationRequestTransaction( - deadline: Deadline, - recipientPublicKey: string, - feeAmount: UInt64, - password: Password, - ): PersistentDelegationRequestTransaction { - try { - const delegatedPrivateKey = this - .getRemoteAccountFromLinkedAccountKey(password) - .privateKey - - const accountPrivateKey = this.wallet.getAccount(password).privateKey - - return PersistentDelegationRequestTransaction - .createPersistentDelegationRequestTransaction( - deadline, - delegatedPrivateKey, - recipientPublicKey, - accountPrivateKey, - this.wallet.networkType, - feeAmount, - ) - } catch (error) { - throw new Error(error) - } - } - - public getHarvestingDelegationRequests(transactionList: FormattedTransaction[]): FormattedTransaction[] { - - - transactionList.filter((tx: any) => tx.rawTx instanceof TransferTransaction) - .filter((tx: any) => tx.rawTx.message instanceof PersistentHarvestingDelegationMessage) - } - */ -} diff --git a/src/services/RentalFeesService.ts b/src/services/RentalFeesService.ts deleted file mode 100644 index 34a4640c2..000000000 --- a/src/services/RentalFeesService.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Store} from 'vuex' - -// internal dependencies -import {AbstractService} from './AbstractService' - -// XXX network config store getter -import networkConfig from '../../config/network.conf.json' -const {defaultDynamicFeeMultiplier} = networkConfig.networks['testnet-publicTest'].properties - -export class RentalFeesService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'rental-fees' - - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store) { - super() - this.$store = store - } - - private static getAbsoluteCostFromDuration = (duration: number): number => { - return duration * defaultDynamicFeeMultiplier - } -} diff --git a/src/services/SettingService.ts b/src/services/SettingService.ts index 5b7c8b0e0..d5965d369 100644 --- a/src/services/SettingService.ts +++ b/src/services/SettingService.ts @@ -1,129 +1,56 @@ -/** +/* * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. + * */ -import {Store} from 'vuex' -// internal dependencies -import {AbstractService} from './AbstractService' -import {SettingsRepository} from '@/repositories/SettingsRepository' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' import {SettingsModel} from '@/core/database/entities/SettingsModel' -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import i18n from '@/language' -// configuration import feesConfig from '@/../config/fees.conf.json' +import appConfig from '@/../config/app.conf.json' import networkConfig from '@/../config/network.conf.json' +import i18n from '@/language' -export class SettingService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'setting' - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store +/** + * Service in charge of loading and storing the SettingsModel from local storage. + */ +export class SettingService { /** - * Construct a service instance around \a store - * @param store + * The the local storage that keeps the SettingsModel objects indexed by accountName. */ - constructor(store?: Store) { - super() - this.$store = store - } + private readonly storage = new SimpleObjectStorage>('settings') - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public allSettings( - filterFn: ( - value: SettingsModel, - index: number, - array: SettingsModel[] - ) => boolean = () => true, - ): SettingsModel[] { - const repository = new SettingsRepository() - return repository.collect().filter(filterFn) + public getAccountSettings(accountName: string): SettingsModel { + const storedData = this.storage.get() || {} + return {...storedData[accountName] || {}, ...this.createDefaultSettingsModel(accountName)} } - /** - * Get settings for \a account - * @param {AccountsModel} account - * @return {SettingsModel} - */ - public getSettings(account: AccountsModel): SettingsModel { - const repository = new SettingsRepository() - - // - read settings from storage if available - if (repository.find(account.getIdentifier())) { - return repository.read(account.getIdentifier()) - } - - // - defaults - return repository.createModel(new Map([ - [ 'explorer_url', networkConfig.explorerUrl ], - [ 'default_fee', feesConfig.normal ], - [ 'default_wallet', '' ], - [ 'language', i18n.locale ], - ])) + public changeAccountSettings(accountName: string, newConfigs: any): SettingsModel { + const storedData = this.storage.get() || {} + storedData[accountName] = {...this.getAccountSettings(accountName), ...newConfigs} + this.storage.set(storedData) + return storedData[accountName] } - /** - * Save settings - * @param {MosaicId} mosaicId - * @return {Promise} - */ - public saveSettingsForm( - formItems: { - currentLanguage: string - explorerUrl: string - maxFee: number - defaultWallet: string - }, - ) { - // - prepare - const currentAccount = this.$store.getters['account/currentAccount'] - const repository = new SettingsRepository() - const values = new Map([ - [ 'language', formItems.currentLanguage ], - [ 'default_fee', formItems.maxFee ], - [ 'explorer_url', formItems.explorerUrl ], - [ 'default_wallet', formItems.defaultWallet ], - ]) - - // - UPDATE when possible - if (repository.find(currentAccount.getIdentifier())) { - repository.update(currentAccount.getIdentifier(), values) - } - // - CREATE Just in Time - else { - values.set('accountName', currentAccount.getIdentifier()) - repository.create(values) - } - - // - update store state - this.$store.dispatch('app/SET_LANGUAGE', formItems.currentLanguage) - this.$store.dispatch('app/SET_EXPLORER_URL', formItems.explorerUrl) - this.$store.dispatch('app/SET_DEFAULT_FEE', formItems.maxFee) - this.$store.dispatch('app/SET_DEFAULT_WALLET', formItems.defaultWallet) - return true + public createDefaultSettingsModel(accountName: string): SettingsModel { + const browserLocale = i18n.locale + const language = appConfig.languages.find( + l => l.value == browserLocale) ? browserLocale : appConfig.languages[0].value + return new SettingsModel(accountName, language, feesConfig.normal, '', + networkConfig.explorerUrl) } + } diff --git a/src/services/TransactionService.ts b/src/services/TransactionService.ts index 80f25351b..550c0018b 100644 --- a/src/services/TransactionService.ts +++ b/src/services/TransactionService.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,44 +14,10 @@ * limitations under the License. */ import {Store} from 'vuex' -import { - Transaction, - TransactionType, - AccountAddressRestrictionTransaction, - AccountLinkTransaction, - AccountMetadataTransaction, - AccountMosaicRestrictionTransaction, - AccountOperationRestrictionTransaction, - AddressAliasTransaction, - AggregateTransaction, - HashLockTransaction, - MosaicAddressRestrictionTransaction, - MosaicAliasTransaction, - MosaicDefinitionTransaction, - MosaicGlobalRestrictionTransaction, - MosaicMetadataTransaction, - MosaicSupplyChangeTransaction, - MultisigAccountModificationTransaction, - NamespaceMetadataTransaction, - NamespaceRegistrationTransaction, - SecretLockTransaction, - SecretProofTransaction, - TransferTransaction, - BlockInfo, - Account, - SignedTransaction, - Deadline, - UInt64, - CosignatureTransaction, - LockFundsTransaction, - Mosaic, - PublicAccount, - CosignatureSignedTransaction, -} from 'symbol-sdk' - +import {Account, AccountAddressRestrictionTransaction, AccountLinkTransaction, AccountMetadataTransaction, AccountMosaicRestrictionTransaction, AccountOperationRestrictionTransaction, AddressAliasTransaction, AggregateTransaction, BlockInfo, CosignatureSignedTransaction, CosignatureTransaction, Deadline, HashLockTransaction, LockFundsTransaction, Mosaic, MosaicAddressRestrictionTransaction, MosaicAliasTransaction, MosaicDefinitionTransaction, MosaicGlobalRestrictionTransaction, MosaicMetadataTransaction, MosaicSupplyChangeTransaction, MultisigAccountModificationTransaction, NamespaceMetadataTransaction, NamespaceRegistrationTransaction, PublicAccount, SecretLockTransaction, SecretProofTransaction, SignedTransaction, Transaction, TransactionType, TransferTransaction, UInt64} from 'symbol-sdk' // internal dependencies import {AbstractService} from './AbstractService' -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {BroadcastResult} from '@/core/transactions/BroadcastResult' import {ViewMosaicDefinitionTransaction} from '@/core/transactions/ViewMosaicDefinitionTransaction' import {ViewMosaicSupplyChangeTransaction} from '@/core/transactions/ViewMosaicSupplyChangeTransaction' @@ -61,6 +27,7 @@ import {ViewAliasTransaction} from '@/core/transactions/ViewAliasTransaction' import {ViewMultisigAccountModificationTransaction} from '@/core/transactions/ViewMultisigAccountModificationTransaction' import {ViewHashLockTransaction} from '@/core/transactions/ViewHashLockTransaction' import {ViewUnknownTransaction} from '@/core/transactions/ViewUnknownTransaction' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' /// region custom types export type TransactionViewType = ViewMosaicDefinitionTransaction @@ -83,7 +50,7 @@ export class TransactionService extends AbstractService { public name: string = 'transaction' /** - * Vuex Store + * Vuex Store * @var {Vuex.Store} */ public $store: Store @@ -97,38 +64,6 @@ export class TransactionService extends AbstractService { this.$store = store } - /** - * Cast \a transaction to its specialized derivate class - * @param {Transaction} transaction - * @return {Transaction} - */ - public getDerivateTransaction(transaction: Transaction): Transaction { - switch(transaction.type) { - default: - throw new Error('Transaction type not supported yet.') - case TransactionType.ACCOUNT_ADDRESS_RESTRICTION: return transaction as AccountAddressRestrictionTransaction - case TransactionType.ACCOUNT_LINK: return transaction as AccountLinkTransaction - case TransactionType.ACCOUNT_METADATA: return transaction as AccountMetadataTransaction - case TransactionType.ACCOUNT_MOSAIC_RESTRICTION: return transaction as AccountMosaicRestrictionTransaction - case TransactionType.ACCOUNT_OPERATION_RESTRICTION: return transaction as AccountOperationRestrictionTransaction - case TransactionType.ADDRESS_ALIAS: return transaction as AddressAliasTransaction - case TransactionType.AGGREGATE_BONDED: - case TransactionType.AGGREGATE_COMPLETE: return transaction as AggregateTransaction - case TransactionType.HASH_LOCK: return transaction as HashLockTransaction - case TransactionType.MOSAIC_ADDRESS_RESTRICTION: return transaction as MosaicAddressRestrictionTransaction - case TransactionType.MOSAIC_ALIAS: return transaction as MosaicAliasTransaction - case TransactionType.MOSAIC_DEFINITION: return transaction as MosaicDefinitionTransaction - case TransactionType.MOSAIC_GLOBAL_RESTRICTION: return transaction as MosaicGlobalRestrictionTransaction - case TransactionType.MOSAIC_METADATA: return transaction as MosaicMetadataTransaction - case TransactionType.MOSAIC_SUPPLY_CHANGE: return transaction as MosaicSupplyChangeTransaction - case TransactionType.MULTISIG_ACCOUNT_MODIFICATION: return transaction as MultisigAccountModificationTransaction - case TransactionType.NAMESPACE_METADATA: return transaction as NamespaceMetadataTransaction - case TransactionType.NAMESPACE_REGISTRATION: return transaction as NamespaceRegistrationTransaction - case TransactionType.SECRET_LOCK: return transaction as SecretLockTransaction - case TransactionType.SECRET_PROOF: return transaction as SecretProofTransaction - case TransactionType.TRANSFER: return transaction as TransferTransaction - } - } /// region specialised signatures public getView(transaction: MosaicDefinitionTransaction): ViewMosaicDefinitionTransaction @@ -156,20 +91,20 @@ export class TransactionService extends AbstractService { /** * Returns true when \a transaction is an incoming transaction - * @param {Transaction} transaction + * @param {Transaction} transaction * @return {TransactionViewType} * @throws {Error} On unrecognized transaction type (view not implemented) */ public getView(transaction: Transaction): TransactionViewType { // - store shortcuts - const currentWallet: WalletsModel = this.$store.getters['wallet/currentWallet'] - const knownBlocks: {[h: number]: BlockInfo} = this.$store.getters['network/knownBlocks'] + const currentWallet: WalletModel = this.$store.getters['wallet/currentWallet'] + const knownBlocks: { [h: number]: BlockInfo } = this.$store.getters['network/knownBlocks'] // - interpret transaction type and initialize view let view: TransactionViewType switch (transaction.type) { - /// region XXX views for transaction types not yet implemented + /// region XXX views for transaction types not yet implemented case TransactionType.ACCOUNT_ADDRESS_RESTRICTION: case TransactionType.ACCOUNT_LINK: case TransactionType.ACCOUNT_METADATA: @@ -201,7 +136,7 @@ export class TransactionService extends AbstractService { view = view.use(transaction as MosaicDefinitionTransaction) break case TransactionType.MOSAIC_SUPPLY_CHANGE: - view = new ViewMosaicSupplyChangeTransaction(this.$store) + view = new ViewMosaicSupplyChangeTransaction(this.$store) view = view.use(transaction as MosaicSupplyChangeTransaction) break case TransactionType.NAMESPACE_REGISTRATION: @@ -221,8 +156,9 @@ export class TransactionService extends AbstractService { view = view.use(transaction as AddressAliasTransaction) break default: - // - throw on transaction view not implemented - this.$store.dispatch('diagnostic/ADD_ERROR', `View not implemented for transaction type '${transaction.type}'`) + // - throw on transaction view not implemented + this.$store.dispatch('diagnostic/ADD_ERROR', + `View not implemented for transaction type '${transaction.type}'`) throw new Error(`View not implemented for transaction type '${transaction.type}'`) } @@ -250,8 +186,9 @@ export class TransactionService extends AbstractService { // - update helper fields by transaction type if (TransactionType.TRANSFER === transaction.type) { - const transfer = this.getDerivateTransaction(transaction) as TransferTransaction - view.values.set('isIncoming', transfer.recipientAddress.equals(currentWallet.objects.address)) + const transfer = transaction as TransferTransaction + view.values.set('isIncoming', + transfer.recipientAddress.equals(WalletModel.getObjects(currentWallet).address)) } return view @@ -268,7 +205,7 @@ export class TransactionService extends AbstractService { // - shortcuts const networkMosaic = this.$store.getters['mosaic/networkMosaic'] const networkType = this.$store.getters['network/networkType'] - const networkProps = this.$store.getters['network/properties'] + const networkConfiguration = this.$store.getters['network/networkConfiguration'] as NetworkConfigurationModel const defaultFee = this.$store.getters['app/defaultFee'] // - create hash lock @@ -276,7 +213,7 @@ export class TransactionService extends AbstractService { Deadline.create(), new Mosaic( networkMosaic, - UInt64.fromUint(networkProps.lockedFundsPerAggregate), + UInt64.fromNumericString(networkConfiguration.lockedFundsPerAggregate), ), UInt64.fromUint(1000), // duration=1000 aggregateTx, @@ -298,10 +235,12 @@ export class TransactionService extends AbstractService { const signedCosig = account.signCosignatureTransaction(cosignature) // - notify diagnostics - this.$store.dispatch('diagnostic/ADD_DEBUG', `Co-signed transaction with account ${account.address.plain()} and result: ${JSON.stringify({ - parentHash: signedCosig.parentHash, - signature: signedCosig.signature, - })}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', + `Co-signed transaction with account ${account.address.plain()} and result: ${JSON.stringify( + { + parentHash: signedCosig.parentHash, + signature: signedCosig.signature, + })}`) return signedCosig } @@ -329,10 +268,12 @@ export class TransactionService extends AbstractService { signedTransactions.push(signedTx) // - notify diagnostics - this.$store.dispatch('diagnostic/ADD_DEBUG', `Signed transaction with account ${account.address.plain()} and result: ${JSON.stringify({ - hash: signedTx.hash, - payload: signedTx.payload, - })}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', + `Signed transaction with account ${account.address.plain()} and result: ${JSON.stringify( + { + hash: signedTx.hash, + payload: signedTx.payload, + })}`) } return signedTransactions @@ -342,7 +283,7 @@ export class TransactionService extends AbstractService { * Aggregate transactions that are on stage, then sign * with \a account. This will sign an AGGREGATE COMPLETE * transaction and should not be used for multi-signature. - * + * * @param {Account} account * @return {SignedTransaction[]} */ @@ -370,10 +311,12 @@ export class TransactionService extends AbstractService { signedTransactions.push(signedTx) // - notify diagnostics - this.$store.dispatch('diagnostic/ADD_DEBUG', `Signed aggregate transaction with account ${account.address.plain()} and result: ${JSON.stringify({ - hash: signedTx.hash, - payload: signedTx.payload, - })}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', + `Signed aggregate transaction with account ${account.address.plain()} and result: ${JSON.stringify( + { + hash: signedTx.hash, + payload: signedTx.payload, + })}`) return signedTransactions } @@ -426,11 +369,12 @@ export class TransactionService extends AbstractService { signedTransactions.push(signedTx) // - notify diagnostics - this.$store.dispatch('diagnostic/ADD_DEBUG', `Signed hash lock and aggregate bonded for account ${multisigAccount.address.plain() - } with cosignatory ${cosignatoryAccount.address.plain()} and result: ${JSON.stringify({ - hashLockTransactionHash: signedTransactions[0].hash, - aggregateTransactionHash: signedTransactions[1].hash, - })}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', + `Signed hash lock and aggregate bonded for account ${multisigAccount.address.plain()} with cosignatory ${cosignatoryAccount.address.plain()} and result: ${JSON.stringify( + { + hashLockTransactionHash: signedTransactions[0].hash, + aggregateTransactionHash: signedTransactions[1].hash, + })}`) return signedTransactions } @@ -486,11 +430,13 @@ export class TransactionService extends AbstractService { // - read transactions const hashLockTransaction = signedTransactions.find(tx => TransactionType.HASH_LOCK === tx.type) - const aggregateTransaction = signedTransactions.find(tx => TransactionType.AGGREGATE_BONDED === tx.type) + const aggregateTransaction = signedTransactions.find( + tx => TransactionType.AGGREGATE_BONDED === tx.type) // - validate hash lock availability if (undefined === hashLockTransaction) { - throw new Error('Partial transactions (aggregate bonded) must be preceeded by a hash lock transaction.') + throw new Error( + 'Partial transactions (aggregate bonded) must be preceeded by a hash lock transaction.') } // - announce lock, await confirmation and announce partial @@ -516,9 +462,8 @@ export class TransactionService extends AbstractService { * @return {Observable} * @throws {Error} On missing signed hash lock transaction. */ - public async announceCosignatureTransactions( - cosignatures: CosignatureSignedTransaction[], - ): Promise { + public async announceCosignatureTransactions(cosignatures: CosignatureSignedTransaction[]): + Promise { const results: BroadcastResult[] = [] for (let i = 0, m = cosignatures.length; i < m; i ++) { const cosignature = cosignatures[i] diff --git a/src/services/WalletService.ts b/src/services/WalletService.ts index ecde54e98..0761cde58 100755 --- a/src/services/WalletService.ts +++ b/src/services/WalletService.ts @@ -1,54 +1,30 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Store} from 'vuex' -import {Account, Address, NetworkType, SimpleWallet, Password} from 'symbol-sdk' -import { - ExtendedKey, - MnemonicPassPhrase, - NodeEd25519, - Wallet, -} from 'symbol-hd-wallets' - +import {Account, Address, NetworkType, Password, SimpleWallet} from 'symbol-sdk' +import {ExtendedKey, MnemonicPassPhrase, Wallet} from 'symbol-hd-wallets' // internal dependencies -import {AbstractService} from './AbstractService' -import {DerivationService, DerivationPathLevels} from './DerivationService' +import {DerivationPathLevels, DerivationService} from './DerivationService' import {DerivationPathValidator} from '@/core/validation/validators' -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel' -import {WalletsRepository} from '@/repositories/WalletsRepository' -import {SimpleStorageAdapter} from '@/core/database/SimpleStorageAdapter' -import {AccountsModel} from '@/core/database/entities/AccountsModel' - -export class WalletService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'wallet' +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store +export class WalletService { - /** - * Wallets repository - * @var {WalletsRepository} - */ - public wallets: WalletsRepository + private readonly storage = new SimpleObjectStorage>('wallets') /** * Default wallet derivation path @@ -56,74 +32,39 @@ export class WalletService extends AbstractService { */ public static readonly DEFAULT_WALLET_PATH = 'm/44\'/4343\'/0\'/0\'/0\'' - /** - * Construct a service instance around \a store - * @param store - */ - constructor(store?: Store, adapter?: SimpleStorageAdapter) { - super() - this.$store = store - this.wallets = new WalletsRepository() - - // - use overwritten adapter - if (!!adapter) { - this.$store.dispatch('diagnostic/ADD_DEBUG', 'Changing WalletService storage adapter.') - this.wallets.setAdapter(adapter) - this.wallets.fetch() - } + + public getWallets(): WalletModel[] { + return Object.values(this.getWalletsById()) } - /** - * Read wallet from store or dispatch fetch action. - * @param {MosaicId} mosaicId - * @return {Promise} - */ - public getWallet( - identifier: string, - ): WalletsModel { - try { - this.wallets.fetch() - const wallet = this.wallets.read(identifier) - return wallet - } - catch (e) { - this.$store.dispatch('notification/ADD_ERROR', `Wallet with identifier '${identifier}' does not exist.`) - } + public getWallet(id: string): WalletModel | undefined { + return this.getWalletsById()[id] + } - return null + public getWalletsById(): Record { + return this.storage.get() || {} } - /** - * Getter for the collection of items - * mapped by identifier - * @return {Map} - */ - public getWallets( - filterFn: ( - value: WalletsModel, - index: number, - array: WalletsModel[] - ) => boolean = () => true, - ): WalletsModel[] { - const repository = new WalletsRepository() - return repository.collect().filter(filterFn) + public saveWallet(wallet: WalletModel) { + const wallets = this.getWalletsById() + wallets[wallet.id] = wallet + this.storage.set(wallets) + } + + + public updateName(wallet: WalletModel, name: string) { + this.saveWallet({...wallet, ...{name}}) } + /** * Derive \a path using \a mnemonic pass phrase - * @param {MnemonicPassPhrase} mnemonic - * @param {string} path - * @param {NetworkType} networkType - * @return {Account} */ - public getAccountByPath( - mnemonic: MnemonicPassPhrase, - networkType: NetworkType, - path: string = WalletService.DEFAULT_WALLET_PATH, - ): Account { + public getAccountByPath(mnemonic: MnemonicPassPhrase, networkType: NetworkType, + path: string = WalletService.DEFAULT_WALLET_PATH): Account { if (false === DerivationPathValidator.validate(path)) { - const errorMessage = `Invalid derivation path: ${path}` - this.$store.dispatch('diagnostic/ADD_ERROR', errorMessage) + const errorMessage = 'Invalid derivation path: ' + path + console.error(errorMessage) throw new Error(errorMessage) } @@ -132,41 +73,20 @@ export class WalletService extends AbstractService { // create wallet const wallet = new Wallet(extendedKey) - return wallet.getChildAccount(path, networkType) + return wallet.getChildAccount(path, networkType) as unknown as Account } /** * Get extended key around \a mnemonic for \a networkTypw - * @param {MnemonicPassPhrase} mnemonic - * @param {NetworkType} networkType + * @param {MnemonicPassPhrase} mnemonic * @return {ExtendedKey} */ - public getExtendedKeyFromMnemonic( - mnemonic: MnemonicPassPhrase, - ): ExtendedKey { + public getExtendedKeyFromMnemonic(mnemonic: MnemonicPassPhrase): ExtendedKey { return ExtendedKey.createFromSeed( mnemonic.toSeed().toString('hex'), ) } - /** - * Get extended key around \a account for \a networkTypw - * @param {MnemonicPassPhrase} mnemonic - * @param {NetworkType} networkType - * @return {ExtendedKey} - */ - public getExtendedKeyFromAccount( - account: Account, - ): ExtendedKey { - // create HD node using curve ED25519 - const nodeEd25519 = new NodeEd25519( - Buffer.from(account.privateKey), - undefined, // publicKey - Buffer.from(''), // chainCode - ) - return new ExtendedKey(nodeEd25519, nodeEd25519.network) - } - /** * Generate \a count accounts using \a mnemonic * @param {MnemonicPassPhrase} mnemonic @@ -180,7 +100,7 @@ export class WalletService extends AbstractService { networkType: NetworkType, count: number = 10, ): Account[] { - const derivationService = new DerivationService(this.$store) + const derivationService = new DerivationService() // create hd extended key const xkey = this.getExtendedKeyFromMnemonic(mnemonic) @@ -197,7 +117,7 @@ export class WalletService extends AbstractService { }) const wallets = paths.map(path => new Wallet(xkey.derivePath(path))) - return wallets.map(wallet => wallet.getAccount(networkType)) + return wallets.map(wallet => wallet.getAccount(networkType) as unknown as Account) } /** @@ -216,7 +136,7 @@ export class WalletService extends AbstractService { const xkey = this.getExtendedKeyFromMnemonic(mnemonic) const wallets = paths.map(path => new Wallet(xkey.derivePath(path))) - return wallets.map(wallet => wallet.getAccount(networkType)) + return wallets.map(wallet => wallet.getAccount(networkType) as unknown as Account) } /** @@ -232,40 +152,23 @@ export class WalletService extends AbstractService { return accounts.map(acct => acct.address) } - /** - * Get a user friendly name for a public key - * @param {string} publicKey - */ - public getWalletLabel( - publicKey: string, - networkType: NetworkType, - ): string { - const address = Address.createFromPublicKey(publicKey, networkType) + public getKnownWallets(knownWallets: string[]): WalletModel[] { // search in known wallets - const knownWallets = this.$store.getters['wallet/knownWallets'] - const wallets = this.getWallets(wlt => knownWallets.includes(wlt.getIdentifier())) - - // find by public key - const findIt = wallets.find(wlt => publicKey === wlt.values.get('publicKey')) - if (undefined !== findIt) { - return findIt.values.get('name') - } - - // wallet not found by public key - return address.plain() + return this.getWallets().filter(wlt => knownWallets.includes(wlt.id)) } + /** * Create a wallet instance from mnemonic - * @return {WalletsModel} + * @return {WalletModel} */ public getDefaultWallet( - currentAccount: AccountsModel, + currentAccount: AccountModel, mnemonic: MnemonicPassPhrase, password: Password, networkType: NetworkType, - ): WalletsModel { + ): WalletModel { const account = this.getAccountByPath( mnemonic, networkType, @@ -279,31 +182,35 @@ export class WalletService extends AbstractService { networkType, ) - return new WalletsModel(new Map([ - [ 'accountName', currentAccount.getIdentifier() ], - [ 'name', 'Seed Wallet 1' ], - [ 'type', WalletType.fromDescriptor('Seed') ], - [ 'address', simpleWallet.address.plain() ], - [ 'publicKey', account.publicKey ], - [ 'encPrivate', simpleWallet.encryptedPrivateKey.encryptedKey ], - [ 'encIv', simpleWallet.encryptedPrivateKey.iv ], - [ 'path', WalletService.DEFAULT_WALLET_PATH ], - [ 'isMultisig', false ], - ])) + + return { + id: SimpleObjectStorage.generateIdentifier(), + accountName: currentAccount.accountName, + name: 'Seed Wallet 1', + node: '', + type: WalletType.fromDescriptor('Seed'), + address: simpleWallet.address.plain(), + publicKey: account.publicKey, + encPrivate: simpleWallet.encryptedPrivateKey.encryptedKey, + encIv: simpleWallet.encryptedPrivateKey.iv, + path: WalletService.DEFAULT_WALLET_PATH, + isMultisig: false, + } + } /** * Create a child wallet instance from mnemonic and path - * @return {WalletsModel} + * @return {WalletModel} */ public getChildWalletByPath( - currentAccount: AccountsModel, + currentAccount: AccountModel, password: Password, mnemonic: MnemonicPassPhrase, nextPath: string, networkType: NetworkType, childWalletName: string, - ): WalletsModel { + ): WalletModel { // - derive account const account = this.getAccountByPath( @@ -319,35 +226,37 @@ export class WalletService extends AbstractService { networkType, ) - return new WalletsModel(new Map([ - [ 'accountName', currentAccount.getIdentifier() ], - [ 'name', childWalletName ], - [ 'type', WalletType.fromDescriptor('Seed') ], - [ 'address', simpleWallet.address.plain() ], - [ 'publicKey', account.publicKey ], - [ 'encPrivate', simpleWallet.encryptedPrivateKey.encryptedKey ], - [ 'encIv', simpleWallet.encryptedPrivateKey.iv ], - [ 'path', nextPath ], - [ 'isMultisig', false ], - ])) + return { + id: SimpleObjectStorage.generateIdentifier(), + accountName: currentAccount.accountName, + name: childWalletName, + node: '', + type: WalletType.SEED, + address: simpleWallet.address.plain(), + publicKey: account.publicKey, + encPrivate: simpleWallet.encryptedPrivateKey.encryptedKey, + encIv: simpleWallet.encryptedPrivateKey.iv, + path: nextPath, + isMultisig: false, + } } /** * Create a sub wallet by private key - * @param currentAccount - * @param password - * @param childWalletName - * @param privateKey - * @param networkType - * @return {WalletsModel} + * @param currentAccount + * @param password + * @param childWalletName + * @param privateKey + * @param networkType + * @return {WalletModel} */ public getSubWalletByPrivateKey( - currentAccount: AccountsModel, + currentAccount: AccountModel, password: Password, childWalletName: string, privateKey: string, networkType: NetworkType, - ): WalletsModel { + ): WalletModel { const account = Account.createFromPrivateKey(privateKey, networkType) const simpleWallet = SimpleWallet.createFromPrivateKey( childWalletName, @@ -356,16 +265,20 @@ export class WalletService extends AbstractService { networkType, ) - return new WalletsModel(new Map([ - [ 'accountName', currentAccount.getIdentifier() ], - [ 'name', childWalletName ], - [ 'type', WalletType.fromDescriptor('Pk') ], - [ 'address', simpleWallet.address.plain() ], - [ 'publicKey', account.publicKey ], - [ 'encPrivate', simpleWallet.encryptedPrivateKey.encryptedKey ], - [ 'encIv', simpleWallet.encryptedPrivateKey.iv ], - [ 'path', '' ], - [ 'isMultisig', false ], - ])) + return { + id: SimpleObjectStorage.generateIdentifier(), + accountName: currentAccount.accountName, + name: childWalletName, + node: '', + type: WalletType.PRIVATE_KEY, + address: simpleWallet.address.plain(), + publicKey: account.publicKey, + encPrivate: simpleWallet.encryptedPrivateKey.encryptedKey, + encIv: simpleWallet.encryptedPrivateKey.iv, + path: '', + isMultisig: false, + } } + + } diff --git a/src/store/Account.ts b/src/store/Account.ts index b1aa4aee9..04e8a2676 100644 --- a/src/store/Account.ts +++ b/src/store/Account.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,35 +14,45 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import {$eventBus} from '../events' import {AwaitLock} from './AwaitLock' import {SettingService} from '@/services/SettingService' +import {AccountModel} from '@/core/database/entities/AccountModel' /// region globals const Lock = AwaitLock.create() + /// end-region globals +interface AccountState { + initialized: boolean + currentAccount: AccountModel + isAuthenticated: boolean +} + +const accountState: AccountState = { + initialized: false, + currentAccount: null, + isAuthenticated: false, +} export default { namespaced: true, - state: { - initialized: false, - currentAccount: null, - isAuthenticated: false, - }, + state: accountState, getters: { - getInitialized: state => state.initialized, - currentAccount: state => state.currentAccount, - isAuthenticated: state => state.isAuthenticated, + getInitialized: (state: AccountState) => state.initialized, + currentAccount: (state: AccountState) => state.currentAccount, + isAuthenticated: (state: AccountState) => state.isAuthenticated, }, mutations: { - setInitialized: (state, initialized) => { state.initialized = initialized }, - currentAccount: (state, accountModel) => Vue.set(state, 'currentAccount', accountModel), - setAuthenticated: (state, authState) => Vue.set(state, 'isAuthenticated', authState === true), + setInitialized: (state: AccountState, initialized: boolean) => { state.initialized = initialized }, + currentAccount: (state: AccountState, currentAccount: AccountModel) => Vue.set(state, + 'currentAccount', currentAccount), + setAuthenticated: (state: AccountState, isAuthenticated: boolean) => Vue.set(state, + 'isAuthenticated', isAuthenticated), }, actions: { - async initialize({ commit, getters }) { + async initialize({commit, getters}) { const callback = async () => { commit('setInitialized', true) } @@ -50,7 +60,7 @@ export default { // aquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, dispatch, getters }) { + async uninitialize({commit, dispatch, getters}) { const callback = async () => { await dispatch('RESET_STATE') commit('setInitialized', false) @@ -62,36 +72,42 @@ export default { commit('currentAccount', null) commit('setAuthenticated', false) }, + LOG_OUT({dispatch, rootGetters}) { const currentWallet = rootGetters['wallet/currentWallet'] - dispatch('wallet/uninitialize', {address: currentWallet.values.get('address')}, {root: true}) + dispatch('wallet/uninitialize', {address: currentWallet.address}, {root: true}) dispatch('wallet/SET_KNOWN_WALLETS', [], {root: true}) return dispatch('RESET_STATE') }, - async SET_CURRENT_ACCOUNT({commit, dispatch}, currentAccountModel) { + async SET_CURRENT_ACCOUNT({commit, dispatch}, currentAccount: AccountModel) { // update state - commit('currentAccount', currentAccountModel) + commit('currentAccount', currentAccount) commit('setAuthenticated', true) - dispatch('diagnostic/ADD_DEBUG', `Changing current account to ${currentAccountModel.getIdentifier()}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', 'Changing current account to ' + currentAccount.accountName, + {root: true}) - const settings = new SettingService().getSettings(currentAccountModel) - dispatch('app/USE_SETTINGS', settings, {root: true}) + const settings = new SettingService().getAccountSettings(currentAccount.accountName) + dispatch('app/SET_SETTINGS', settings, {root: true}) - dispatch('diagnostic/ADD_DEBUG', `Using account settings ${Array.from(settings.values)}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', 'Using account settings ' + Object.values(settings), + {root: true}) // reset store + re-initialize await dispatch('initialize') - $eventBus.$emit('onAccountChange', currentAccountModel.getIdentifier()) + $eventBus.$emit('onAccountChange', currentAccount.accountName) }, + ADD_WALLET({dispatch, getters}, walletModel) { - const resolvedAccount = getters['currentAccount'] + const resolvedAccount = getters['currentAccount'] if (!resolvedAccount || !resolvedAccount.values) { return } - dispatch('diagnostic/ADD_DEBUG', `Adding wallet to account: ${resolvedAccount.getIdentifier()} with: ${walletModel.values.get('address')}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Adding wallet to account: ' + resolvedAccount.getIdentifier() + ' with: ' + walletModel.address, + {root: true}) const wallets = resolvedAccount.values.get('wallets') wallets.push(walletModel.getIdentifier()) diff --git a/src/store/AppInfo.ts b/src/store/AppInfo.ts index 7e0eca251..d5a41829b 100644 --- a/src/store/AppInfo.ts +++ b/src/store/AppInfo.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,72 +14,83 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import i18n from '@/language' import app from '@/main' import {AwaitLock} from './AwaitLock' -const Lock = AwaitLock.create() - // configuration import appConfig from '@/../config/app.conf.json' -import feesConfig from '@/../config/fees.conf.json' import networkConfig from '@/../config/network.conf.json' +import {SettingsModel} from '@/core/database/entities/SettingsModel' +import {SettingService} from '@/services/SettingService' + +const Lock = AwaitLock.create() +const settingService = new SettingService() + +interface AppInfoState { + initialized: false + timezone: number + languages: { value: string, label: string }[] + hasLoadingOverlay: boolean + loadingOverlayMessage: string + loadingDisableCloseButton: false + hasControlsDisabled: false + controlsDisabledMessage: string + faucetUrl: string + settings: SettingsModel + isFetchingTransactions: boolean +} + +const appInfoState: AppInfoState = { + initialized: false, + timezone: new Date().getTimezoneOffset() / 60, + languages: appConfig.languages, + hasLoadingOverlay: false, + loadingOverlayMessage: '', + loadingDisableCloseButton: false, + hasControlsDisabled: false, + controlsDisabledMessage: '', + faucetUrl: networkConfig.faucetUrl, + settings: settingService.createDefaultSettingsModel(''), + isFetchingTransactions: false, +} + export default { namespaced: true, - state: { - initialized: false, - timezone: new Date().getTimezoneOffset() / 60, - languages: appConfig.languages, - hasLoadingOverlay: false, - loadingOverlayMessage: '', - loadingDisableCloseButton: false, - hasControlsDisabled: false, - controlsDisabledMessage: '', - explorerUrl: networkConfig.explorerUrl, - faucetUrl: networkConfig.faucetUrl, - defaultFee: feesConfig.normal, - defaultWallet: '', - isFetchingTransactions: false, - }, + state: appInfoState, getters: { - getInitialized: (state) => state.initialized, - currentTimezone: (state) => state.timezone, - currentLanguage: () => i18n.locale, - languages: (state) => state.languages, - shouldShowLoadingOverlay: (state) => state.hasLoadingOverlay, - loadingOverlayMessage: (state) => state.loadingOverlayMessage, - loadingDisableCloseButton: (state) => state.loadingDisableCloseButton, - shouldDisableControls: (state) => state.hasControlsDisabled, - controlsDisabledMessage: (state) => state.controlsDisabledMessage, - explorerUrl: (state) => state.explorerUrl, - faucetUrl: (state) => state.faucetUrl, - defaultFee: (state) => state.defaultFee, - defaultWallet: (state) => state.defaultWallet, - isFetchingTransactions: (state) => state.isFetchingTransactions, + getInitialized: (state: AppInfoState) => state.initialized, + currentTimezone: (state: AppInfoState) => state.timezone, + language: (state: AppInfoState) => state.settings.language, + languages: (state: AppInfoState) => state.languages, + shouldShowLoadingOverlay: (state: AppInfoState) => state.hasLoadingOverlay, + loadingOverlayMessage: (state: AppInfoState) => state.loadingOverlayMessage, + loadingDisableCloseButton: (state: AppInfoState) => state.loadingDisableCloseButton, + shouldDisableControls: (state: AppInfoState) => state.hasControlsDisabled, + controlsDisabledMessage: (state: AppInfoState) => state.controlsDisabledMessage, + explorerUrl: (state: AppInfoState) => state.settings.explorerUrl, + settings: (state: AppInfoState) => state.settings, + faucetUrl: (state: AppInfoState) => state.faucetUrl, + defaultFee: (state: AppInfoState) => state.settings.defaultFee, + defaultWallet: (state: AppInfoState) => state.settings.defaultWallet, + isFetchingTransactions: (state: AppInfoState) => state.isFetchingTransactions, }, mutations: { - setInitialized: (state, initialized) => { state.initialized = initialized }, - timezone: (state, timezone) => Vue.set(state, 'timezone', timezone), - toggleControlsDisabled: (state, {disable, message}) => { + setInitialized: (state: AppInfoState, initialized) => { state.initialized = initialized }, + timezone: (state: AppInfoState, timezone) => Vue.set(state, 'timezone', timezone), + settings: (state: AppInfoState, settings: SettingsModel) => Vue.set(state, 'settings', settings), + toggleControlsDisabled: (state: AppInfoState, {disable, message}) => { Vue.set(state, 'hasControlsDisabled', disable) Vue.set(state, 'controlsDisabledMessage', message || '') }, - toggleLoadingOverlay: (state, display) => Vue.set(state, 'hasLoadingOverlay', display), - setLoadingOverlayMessage: (state, message) => Vue.set(state, 'loadingOverlayMessage', message), - setLoadingDisableCloseButton: (state, bool) => Vue.set(state, 'loadingDisableCloseButton', bool), - setExplorerUrl: (state, url) => Vue.set(state, 'explorerUrl', url), - setCurrentLanguage: (state, lang) => { - i18n.locale = lang - window.localStorage.setItem('locale', lang) - }, - setDefaultFee: (state, maxFee) => Vue.set(state, 'defaultFee', maxFee), - setDefaultWallet: (state, defaultWallet) => Vue.set(state, 'defaultWallet', defaultWallet), - setFetchingTransactions: (state, bool: boolean) => Vue.set(state, 'isFetchingTransactions', bool), + toggleLoadingOverlay: (state: AppInfoState, display: boolean) => Vue.set(state, 'hasLoadingOverlay', display), + setLoadingOverlayMessage: (state: AppInfoState, message: string) => Vue.set(state, 'loadingOverlayMessage', message), + setLoadingDisableCloseButton: (state: AppInfoState, bool: boolean) => Vue.set(state, 'loadingDisableCloseButton', bool), + setFetchingTransactions: (state: AppInfoState, bool: boolean) => Vue.set(state, 'isFetchingTransactions', bool), }, actions: { - async initialize({ commit, getters }) { + async initialize({commit, getters}) { const callback = async () => { // update store commit('setInitialized', true) @@ -88,7 +99,7 @@ export default { // acquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, getters }) { + async uninitialize({commit, getters}) { const callback = async () => { commit('setInitialized', false) } @@ -98,7 +109,7 @@ export default { SET_TIME_ZONE({commit}, timezone: number): void { commit('timezone', timezone) }, - SET_UI_DISABLED({commit}, {isDisabled, message}: {isDisabled: boolean, message: string}) { + SET_UI_DISABLED({commit}, {isDisabled, message}: { isDisabled: boolean, message: string }) { commit('toggleControlsDisabled', {disable: isDisabled, message: message}) }, SET_LOADING_OVERLAY({commit}, loadingOverlay) { @@ -108,23 +119,28 @@ export default { commit('setLoadingOverlayMessage', loadingOverlay.message) commit('setLoadingDisableCloseButton', loadingOverlay.disableCloseButton || false) }, - SET_EXPLORER_URL({commit}, url: string) { - commit('setExplorerUrl', url) + + SET_SETTINGS({commit, rootGetters}, settingsModel: SettingsModel) { + if (settingsModel.language) { + i18n.locale = settingsModel.language + window.localStorage.setItem('locale', settingsModel.language) + } + const accountName = rootGetters['account/currentAccount'].accountName + commit('settings', settingService.changeAccountSettings(accountName, settingsModel)) }, - SET_LANGUAGE({commit}, language: string) { - commit('setCurrentLanguage', language) + + SET_EXPLORER_URL({dispatch}, explorerUrl: string) { + dispatch('SET_SETTINGS', {explorerUrl}) }, - SET_DEFAULT_FEE({commit}, maxFee: number) { - commit('setDefaultFee', maxFee) + + SET_LANGUAGE({dispatch}, language: string) { + dispatch('SET_SETTINGS', {language}) }, - SET_DEFAULT_WALLET({commit}, defaultWallet: string) { - commit('setDefaultWallet', defaultWallet) + SET_DEFAULT_FEE({dispatch}, defaultFee: number) { + dispatch('SET_SETTINGS', {defaultFee}) }, - USE_SETTINGS({commit}, settingsModel) { - commit('setExplorerUrl', settingsModel.values.get('explorer_url')) - commit('setCurrentLanguage', settingsModel.values.get('language')) - commit('setDefaultFee', settingsModel.values.get('default_fee')) - commit('setDefaultWallet', settingsModel.values.get('default_wallet')) + SET_DEFAULT_WALLET({dispatch}, defaultWallet: string) { + dispatch('SET_SETTINGS', {defaultWallet}) }, SET_FETCHING_TRANSACTIONS({commit}, bool: boolean) { commit('setFetchingTransactions', bool) diff --git a/src/store/Community.ts b/src/store/Community.ts index a5b7a3108..734b4fa61 100644 --- a/src/store/Community.ts +++ b/src/store/Community.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import {AwaitLock} from './AwaitLock' import {CommunityService} from '@/services/CommunityService' + const Lock = AwaitLock.create() /** diff --git a/src/store/Database.ts b/src/store/Database.ts index cbb0f37b9..876ed413d 100644 --- a/src/store/Database.ts +++ b/src/store/Database.ts @@ -1,100 +1,53 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import Vue from 'vue' - // internal dependencies import {AwaitLock} from './AwaitLock' -import {MosaicService} from '@/services/MosaicService' -import {NamespaceService} from '@/services/NamespaceService' -import {AccountService} from '@/services/AccountService' -import {WalletService} from '@/services/WalletService' -import {SettingService} from '@/services/SettingService' -import {PeerService} from '@/services/PeerService' /// region globals const Lock = AwaitLock.create() -const mosaicService = new MosaicService() -const namespaceService = new NamespaceService() -const accountService = new AccountService() -const walletService = new WalletService() -const settingService = new SettingService() -const peerService = new PeerService() /// end-region globals export default { namespaced: true, state: { initialized: false, - hasFeed: false, - dataFeed: { - accounts: [], - mosaics: [], - namespaces: [], - wallets: [], - settings: [], - endpoints: [], - }, }, getters: { getInitialized: state => state.initialized, - hasFeed: state => state.canFeed, - feed: state => state.dataFeed, }, mutations: { setInitialized: (state, initialized) => { state.initialized = initialized }, - setHasFeed: (state, f) => Vue.set(state, 'hasFeed', f), - setFeed: (state, feed) => Vue.set(state, 'dataFeed', feed), + }, actions: { - async initialize({ commit, dispatch, getters }) { + async initialize({commit, getters}) { const callback = async () => { - // - synchronize database data - await dispatch('SYNCHRONIZE') + // MIGRATIONS COULD GO HERE! commit('setInitialized', true) } // aquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, getters }) { + async uninitialize({commit, getters}) { const callback = async () => { commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - async SYNCHRONIZE({commit}) { - // - sync with database (this will run migrations if needed) - const accounts = accountService.getAccounts() - const mosaics = mosaicService.getMosaics() - const namespaces = namespaceService.getNamespaces() - const wallets = walletService.getWallets() - const settings = settingService.allSettings() - const endpoints = peerService.getEndpoints() - commit('setHasFeed', accounts && accounts.length > 0) - commit('setFeed', { - accounts, - mosaics, - namespaces, - wallets, - endpoints, - settings, - }) - }, - /// region scoped actions - /// end-region scoped actions }, } diff --git a/src/store/Diagnostic.ts b/src/store/Diagnostic.ts index 6eee92e17..6aab14762 100644 --- a/src/store/Diagnostic.ts +++ b/src/store/Diagnostic.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import {LogLevels} from '@/core/utils/LogLevels' import {AwaitLock} from './AwaitLock' + const Lock = AwaitLock.create() export default { diff --git a/src/store/Market.ts b/src/store/Market.ts index f802c7020..8e3c1d811 100644 --- a/src/store/Market.ts +++ b/src/store/Market.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,10 +14,10 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import {$eventBus} from '../events' import {AwaitLock} from './AwaitLock' + const Lock = AwaitLock.create() export default { diff --git a/src/store/Mosaic.ts b/src/store/Mosaic.ts index fd14474bf..5c91177d4 100644 --- a/src/store/Mosaic.ts +++ b/src/store/Mosaic.ts @@ -1,87 +1,53 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import { - MosaicId, - MosaicInfo, - NamespaceRegistrationType, - QueryParams, - RepositoryFactory, - Transaction, - TransactionType, - UInt64, -} from 'symbol-sdk' +import {AccountInfo, Address, MosaicId, RepositoryFactory} from 'symbol-sdk' import Vue from 'vue' // internal dependencies -import {MosaicService} from '@/services/MosaicService' import {AwaitLock} from './AwaitLock' +import {MosaicService} from '@/services/MosaicService' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {MosaicConfigurationModel} from '@/core/database/entities/MosaicConfigurationModel' const Lock = AwaitLock.create() -const getCurrencyMosaicFromNemesis = (transactions) => { - // - read first root namespace - const rootNamespaceTx = transactions.filter( - tx => tx.type === TransactionType.NAMESPACE_REGISTRATION - && tx.registrationType === NamespaceRegistrationType.RootNamespace).shift() - - // - read sub namespace - const subNamespaceTx = transactions.filter( - tx => tx.type === TransactionType.NAMESPACE_REGISTRATION - && tx.registrationType === NamespaceRegistrationType.SubNamespace - && tx.parentId.equals(rootNamespaceTx.namespaceId)).shift() - - // - read alias - const aliasTx = transactions.filter( - tx => tx.type === TransactionType.MOSAIC_ALIAS - && tx.namespaceId.equals(subNamespaceTx.namespaceId)).shift() - - // - build network mosaic name - const mosaicName = [ - rootNamespaceTx.namespaceName, - subNamespaceTx.namespaceName, - ].join('.') - - return { - name: mosaicName, - mosaicId: aliasTx.mosaicId, - ticker: subNamespaceTx.namespaceName.toUpperCase(), - } -} - // mosaic state typing interface MosaicState { initialized: boolean + networkCurrency: NetworkCurrencyModel + mosaics: MosaicModel[] + balanceMosaics: MosaicModel[] + ownedMosaics: MosaicModel[] networkMosaicId: MosaicId networkMosaicName: string networkMosaicTicker: string - nemesisTransactions: Transaction[] - mosaicsInfoByHex: Record - mosaicsNamesByHex: Record - hiddenMosaics: string[] + mosaicConfigurations: Record } // mosaic state initial definition const mosaicState: MosaicState = { initialized: false, networkMosaicId: null, + mosaics: [], + balanceMosaics: [], + ownedMosaics: [], + networkCurrency: null, networkMosaicName: '', networkMosaicTicker: '', - nemesisTransactions: [], - mosaicsInfoByHex: {}, - mosaicsNamesByHex: {}, - hiddenMosaics: [], + mosaicConfigurations: {}, } export default { @@ -89,208 +55,100 @@ export default { state: mosaicState, getters: { getInitialized: (state: MosaicState) => state.initialized, + networkCurrency: (state: MosaicState) => state.networkCurrency, + mosaics: (state: MosaicState) => state.mosaics, + ownedMosaics: (state: MosaicState) => state.ownedMosaics, + balanceMosaics: (state: MosaicState) => state.balanceMosaics, networkMosaic: (state: MosaicState) => state.networkMosaicId, networkMosaicTicker: (state: MosaicState) => state.networkMosaicTicker, - nemesisTransactions: (state: MosaicState) => state.nemesisTransactions, - mosaicsInfo: (state: MosaicState) => state.mosaicsInfoByHex, - mosaicsInfoList: (state: MosaicState) => Object.keys(state.mosaicsInfoByHex).map( - hex => state.mosaicsInfoByHex[hex], - ), - mosaicsNames: (state: MosaicState) => state.mosaicsNamesByHex, - hiddenMosaics: (state: MosaicState) => state.hiddenMosaics, + mosaicConfigurations: (state: MosaicState) => state.mosaicConfigurations, networkMosaicName: (state: MosaicState) => state.networkMosaicName, }, mutations: { - setInitialized: (state: MosaicState, initialized) => { state.initialized = initialized }, - setNetworkMosaicId: (state: MosaicState, mosaic) => Vue.set(state, 'networkMosaicId', mosaic), - setNetworkMosaicName: (state: MosaicState, name) => Vue.set(state, 'networkMosaicName', name), - setNetworkMosaicTicker: (state: MosaicState, ticker) => Vue.set(state, 'networkMosaicTicker', ticker), - addMosaicInfo: (state: MosaicState, mosaicInfo: MosaicInfo) => { - Vue.set(state.mosaicsInfoByHex, mosaicInfo.id.toHex(), mosaicInfo) + setInitialized: (state: MosaicState, + initialized: boolean) => { state.initialized = initialized }, + networkCurrency: (state: MosaicState, networkCurrency: NetworkCurrencyModel) => { + Vue.set(state, 'networkCurrency', networkCurrency) + Vue.set(state, 'networkMosaicId', new MosaicId(networkCurrency.mosaicIdHex)) + Vue.set(state, 'networkMosaicName', networkCurrency.namespaceIdFullname) + Vue.set(state, 'networkMosaicTicker', networkCurrency.ticker) }, - addMosaicName: (state: MosaicState, payload: {hex: string, name: string}) => { - Vue.set(state.mosaicsNamesByHex, payload.hex, payload.name) - }, - hideMosaic: (state: MosaicState, mosaicId) => { - const hiddenMosaics = [...state.hiddenMosaics] - - // find the index of the mosaic to hide - const index = hiddenMosaics.indexOf(mosaicId.toHex()) - - // the mosaic is already in the list, return - if (index > -1) return + mosaics: (state: MosaicState, {mosaics, currentSignerAddress, networkCurrency}: + { mosaics: MosaicModel[], currentSignerAddress: Address, networkCurrency: NetworkCurrencyModel }) => { + + const ownedMosaics = mosaics.filter( + m => m.ownerRawPlain === currentSignerAddress.plain() && m.addressRawPlain === currentSignerAddress.plain()) + + const balanceMosaics = mosaics.filter(m => m.addressRawPlain === currentSignerAddress.plain()) + if (networkCurrency && !balanceMosaics.find( + m => m.mosaicIdHex === networkCurrency.mosaicIdHex)) { + const mosaicInfo = { + mosaicIdHex: networkCurrency.mosaicIdHex, + divisibility: networkCurrency.divisibility, + name: networkCurrency.namespaceIdFullname, + isCurrencyMosaic: true, + balance: 0, + } as MosaicModel + balanceMosaics.push(mosaicInfo) + } - // update the state - Vue.set(state, 'hiddenMosaics', [ ...hiddenMosaics, mosaicId.toHex() ]) + Vue.set(state, 'mosaics', mosaics) + Vue.set(state, 'balanceMosaics', balanceMosaics) + Vue.set(state, 'ownedMosaics', ownedMosaics) }, - showMosaic: (state: MosaicState, mosaicId) => { - const hiddenMosaics = [...state.hiddenMosaics] + mosaicConfigurations: (state: MosaicState, + mosaicConfigurations: Record) => Vue.set( + state, 'mosaicConfigurations', mosaicConfigurations), - // find the index of the mosaic to show - const index = hiddenMosaics.indexOf(mosaicId.toHex()) - - // the mosaic is not in the list, return - if (index === -1) return - - // remove the mosaic from the list - hiddenMosaics.splice(index, 1) - - // update the state - Vue.set(state, 'hiddenMosaics', hiddenMosaics) - }, }, actions: { - async initialize({ commit, dispatch, getters, rootGetters }, withFeed) { - const callback = async () => { - const generationHash = rootGetters['network/generationHash'] - - // - initialize CURRENCY from database if available - if (undefined !== withFeed - && withFeed.mosaics - && withFeed.mosaics.length - && undefined !== withFeed.mosaics.find(m => m.values.get('isCurrencyMosaic') && generationHash === m.values.get('generationHash')) - ) { - await dispatch('INITIALIZE_FROM_DB', withFeed) - } - // - initialize CURRENCY from nemesis transactions - else { - const nodeUrl = rootGetters['network/currentPeer'].url - const repositoryFactory = rootGetters['network/repositoryFactory'] - await dispatch('INITIALIZE_FROM_NEMESIS', {nodeUrl, repositoryFactory}) - } - - // update store - commit('setInitialized', true) + async initialize({commit, getters, rootGetters}) { + const callback = () => { + const repositoryFactory = rootGetters['network/repositoryFactory'] + const mosaicService = new MosaicService() + mosaicService.getNetworkCurrencies(repositoryFactory).subscribe(networkCurrencies => { + commit('networkCurrency', networkCurrencies.find(i => i)) + commit('setInitialized', true) + }) + commit('mosaicConfigurations', mosaicService.getMosaicConfigurations()) } - // acquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, getters }) { + async uninitialize({commit, getters}) { const callback = async () => { commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - /// region scoped actions - async INITIALIZE_FROM_DB({commit, dispatch, rootGetters}, withFeed) { - const generationHash = rootGetters['network/generationHash'] - const currencyMosaic = withFeed.mosaics.find( - m => m.values.get('isCurrencyMosaic') - && generationHash === m.values.get('generationHash'), - ) - dispatch('diagnostic/ADD_DEBUG', `Store action mosaic/INITIALIZE_FROM_DB dispatched with currencyMosaic: ${currencyMosaic.values.get('name')}`, {root: true}) - - // - set network currency mosaic - dispatch('SET_NETWORK_CURRENCY_MOSAIC', { - mosaic: currencyMosaic, - name: currencyMosaic.values.get('name'), - ticker: currencyMosaic.values.get('name').split('.').pop().toUpperCase(), - mosaicId: currencyMosaic.objects.mosaicId, - }) - - withFeed.mosaics.forEach((model) => { - // - populate known mosaics - commit('addMosaicInfo', model.objects.mosaicInfo) - - // - set hidden state - if (model.values.get('isHidden')) commit('hideMosaic', new MosaicId(model.getIdentifier)) - - // - populate known mosaic names - const name = model.values.get('name') - if (name !== '') commit('addMosaicName', { hex: model.getIdentifier(), name }) - }) - return { - currencyMosaic, - knownMosaics: withFeed.mosaics, + LOAD_MOSAICS({commit, rootGetters}) { + const currentSignerAddress: Address = rootGetters['wallet/currentSignerAddress'] + if (!currentSignerAddress) { + return } + const repositoryFactory: RepositoryFactory = rootGetters['network/repositoryFactory'] + const networkCurrency: NetworkCurrencyModel = rootGetters['mosaic/networkCurrency'] + const mosaicService = new MosaicService() + const accountsInfo: AccountInfo[] = rootGetters['wallet/accountsInfo'] || [] + + mosaicService.getMosaics(repositoryFactory, networkCurrency ? [networkCurrency] : [], + accountsInfo).subscribe((mosaics) => { + commit('mosaics', {mosaics: mosaics, currentSignerAddress, networkCurrency}) + }) }, - async INITIALIZE_FROM_NEMESIS({commit, dispatch}, {repositoryFactory, nodeUrl}) { - // read first network block to identify currency mosaic - dispatch('diagnostic/ADD_DEBUG', `Store action mosaic/INITIALIZE_FROM_NEMESIS dispatched with nodeUrl: ${nodeUrl}`, {root: true}) - - const blockHttp = repositoryFactory.createBlockRepository() - blockHttp.getBlockTransactions(UInt64.fromUint(1), new QueryParams({pageSize: 100})).subscribe( - async (transactions: Transaction[]) => { - const payload = getCurrencyMosaicFromNemesis(transactions) - - // - will dispatch REST_FETCH_INFO+REST_FETCH_NAMES - const service = new MosaicService(this) - const currencyMosaic = await service.getMosaic(payload.mosaicId, true) - - // - add to known mosaics - commit('addMosaicInfo', currencyMosaic.objects.mosaicInfo) - - // - set network currency mosaic - dispatch('SET_NETWORK_CURRENCY_MOSAIC', { - mosaic: currencyMosaic, - name: payload.name, - ticker: payload.ticker, - mosaicId: payload.mosaicId, - }) - }, - err => dispatch( - 'diagnostic/ADD_DEBUG', - `Store action mosaic/INITIALIZE_FROM_NEMESIS error: ${JSON.stringify(err)}`, - {root: true}, - )) + SIGNER_CHANGED({dispatch}) { + dispatch('LOAD_MOSAICS') }, - SET_NETWORK_CURRENCY_MOSAIC({commit}, payload) { - commit('setNetworkMosaicName', payload.name) - commit('setNetworkMosaicId', payload.mosaicId) - commit('setNetworkMosaicTicker', payload.ticker) - commit('addMosaicName', { - hex: payload.mosaic.objects.mosaicId.toHex(), - name: payload.name, - }) - }, HIDE_MOSAIC({commit}, mosaicId) { - new MosaicService().toggleHiddenState(mosaicId, true) - commit('hideMosaic', mosaicId) + commit('mosaicConfigurations', + new MosaicService().changeMosaicConfiguration(mosaicId, {hidden: true})) }, SHOW_MOSAIC({commit}, mosaicId) { - new MosaicService().toggleHiddenState(mosaicId, false) - commit('showMosaic', mosaicId) - }, - async REST_FETCH_INFO({commit, rootGetters}, mosaicId): Promise { - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const mosaicHttp = repositoryFactory.createMosaicRepository() - const mosaicInfo = await mosaicHttp.getMosaic(mosaicId).toPromise() - - commit('addMosaicInfo', mosaicInfo) - return mosaicInfo - }, - async REST_FETCH_INFOS({commit, rootGetters}, mosaicIds): Promise { - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const mosaicHttp = repositoryFactory.createMosaicRepository() - const mosaicsInfo = await mosaicHttp.getMosaics(mosaicIds).toPromise() - - mosaicsInfo.forEach(info => commit('addMosaicInfo', info)) - return mosaicsInfo - }, - async REST_FETCH_NAMES({commit, rootGetters}, mosaicIds): Promise<{hex: string, name: string}[]> { - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const namespaceHttp = repositoryFactory.createNamespaceRepository() - const mosaicNames = await namespaceHttp.getMosaicsNames(mosaicIds).toPromise() - - // map by hex if names available - const mappedNames = mosaicNames - .filter(({names}) => names.length) - .map(({mosaicId, names}) => ({hex: mosaicId.toHex(), name: names.shift().name})) - - // update store - mosaicIds.forEach(id => { - const hexId = id.toHex() - const mappedName = mappedNames.find(({hex}) => hex === hexId) - const name = mappedName ? mappedName.name : '' - commit('addMosaicName', { hex: hexId, name }) - }) - - return mappedNames + commit('mosaicConfigurations', + new MosaicService().changeMosaicConfiguration(mosaicId, {hidden: false})) }, - /// end-region scoped actions }, } diff --git a/src/store/Namespace.ts b/src/store/Namespace.ts index 60a20dea0..9943bbcf3 100644 --- a/src/store/Namespace.ts +++ b/src/store/Namespace.ts @@ -1,105 +1,90 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {NamespaceId, NamespaceInfo, RepositoryFactory} from 'symbol-sdk' +import {Address} from 'symbol-sdk' import Vue from 'vue' // internal dependencies import {AwaitLock} from './AwaitLock' import {NamespaceService} from '@/services/NamespaceService' -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' const Lock = AwaitLock.create() + +interface NamespaceState { + initialized: boolean + namespaces: NamespaceModel[] + ownedNamespaces: NamespaceModel[] +} + +const namespaceState: NamespaceState = { + initialized: false, + namespaces: [], + ownedNamespaces: [], +} + export default { namespaced: true, - state: { - initialized: false, - namespacesInfoByHex: {}, - namespacesNamesByHex: {}, - }, + state: namespaceState, getters: { - getInitialized: state => state.initialized, - namespacesInfo: state => state.namespacesInfoByHex, - namespacesInfoList: state => Object.keys(state.namespacesInfoByHex).map(hex => state.namespacesInfoByHex[hex]), - namespacesNames: state => state.namespacesNamesByHex, + getInitialized: (state: NamespaceState) => state.initialized, + namespaces: (state: NamespaceState) => state.namespaces, + ownedNamespaces: (state: NamespaceState) => state.ownedNamespaces, }, mutations: { - setInitialized: (state, initialized) => { state.initialized = initialized }, - addNamespaceInfo: (state, namespaceInfo: NamespaceInfo) => { - Vue.set(state.namespacesInfoByHex, namespaceInfo.id.toHex(), namespaceInfo) - }, - addNamespaceName: (state, payload: {hex: string, name: string}) => { - Vue.set(state.namespacesNamesByHex, payload.hex, payload.name) + setInitialized: (state: NamespaceState, initialized) => { state.initialized = initialized }, + namespaces: (state: NamespaceState, + {namespaces, currentSignerAddress}: { namespaces: NamespaceModel[], currentSignerAddress: Address }) => { + Vue.set(state, 'namespaces', namespaces) + Vue.set(state, 'ownedNamespaces', + namespaces.filter(n => n.ownerAddressRawPlain === currentSignerAddress.plain())) }, }, actions: { - async initialize({ commit, dispatch, getters }, withFeed) { + async initialize({commit, getters}) { const callback = async () => { - if (undefined !== withFeed && withFeed.namespaces && withFeed.namespaces.length) { - await dispatch('INITIALIZE_FROM_DB', withFeed) - } + // Placeholder for initialization if necessary. commit('setInitialized', true) } - // aquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, getters }) { + + async uninitialize({commit, getters}) { const callback = async () => { commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - /// region scoped actions - async INITIALIZE_FROM_DB({commit, dispatch}, withFeed) { - dispatch('diagnostic/ADD_DEBUG', 'Store action namespace/INITIALIZE_FROM_DB dispatched', {root: true}) - withFeed.namespaces.forEach((model: NamespacesModel) => { - commit('addNamespaceInfo', model.objects.namespaceInfo) - commit('addNamespaceName', {hex: model.getIdentifier(), name: model.values.get('name')}) + + LOAD_NAMESPACES({commit, rootGetters}) { + const currentSignerAddress = rootGetters['wallet/currentSignerAddress'] as Address + const knownAddresses = rootGetters['wallet/knownAddresses'] as Address[] || [] + if (!currentSignerAddress) { + return + } + const repositoryFactory = rootGetters['network/repositoryFactory'] + const namespaceService = new NamespaceService() + namespaceService.getNamespaces(repositoryFactory, knownAddresses).subscribe((namespaces) => { + commit('namespaces', {namespaces, currentSignerAddress}) }) }, - async REST_FETCH_INFO({commit, rootGetters}, namespaceId: NamespaceId) { - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const namespaceHttp = repositoryFactory.createNamespaceRepository() - const namespaceInfo = await namespaceHttp.getNamespace(namespaceId).toPromise() - commit('addNamespaceInfo', namespaceInfo) - return namespaceInfo + SIGNER_CHANGED({dispatch}) { + dispatch('LOAD_NAMESPACES') }, - async REST_FETCH_NAMES({commit, rootGetters}, namespaceIds: NamespaceId[]): Promise<{hex: string, name: string}[]> { - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const namespaceHttp = repositoryFactory.createNamespaceRepository() - const namespaceNames = await namespaceHttp.getNamespacesName(namespaceIds).toPromise() - // map by hex if names available - const mappedNames = namespaceNames - .filter(({name}) => name.length) - .map((namespaceName) => { - return { - hex: namespaceName.namespaceId.toHex(), - name: NamespaceService.getFullNameFromNamespaceNames(namespaceName, namespaceNames).name, - } - }) - - // update store - mappedNames.forEach(mappedEntry => commit('addNamespaceName', mappedEntry)) - return mappedNames - }, - ADD_NAMESPACE_INFOS({commit}, namespacesInfo: NamespaceInfo[]): void { - namespacesInfo.forEach(namespace => commit('addNamespaceInfo', namespace)) - }, - /// end-region scoped actions }, } diff --git a/src/store/Network.ts b/src/store/Network.ts index d8b8b08d6..13bfcc5d8 100644 --- a/src/store/Network.ts +++ b/src/store/Network.ts @@ -14,20 +14,23 @@ * limitations under the License. */ import Vue from 'vue' -import {BlockInfo, IListener, NetworkType, RepositoryFactory, UInt64} from 'symbol-sdk' +import {BlockInfo, IListener, Listener, NetworkType, RepositoryFactory, UInt64} from 'symbol-sdk' import {Subscription} from 'rxjs' // internal dependencies import {$eventBus} from '../events' import {RESTService} from '@/services/RESTService' -import {PeersModel} from '@/core/database/entities/PeersModel' import {URLHelpers} from '@/core/utils/URLHelpers' import app from '@/main' import {AwaitLock} from './AwaitLock' // configuration import networkConfig from '../../config/network.conf.json' -import {PeersRepository} from '@/repositories/PeersRepository' import {UrlValidator} from '@/core/validation/validators' -import {PeerService} from '@/services/PeerService' +import {NetworkModel} from '@/core/database/entities/NetworkModel' +import {NetworkService} from '@/services/NetworkService' +import {NodeService} from '@/services/NodeService' +import {NodeModel} from '@/core/database/entities/NodeModel' +import {URLInfo} from '@/core/utils/URLInfo' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' const Lock = AwaitLock.create() @@ -59,202 +62,181 @@ const getBlockRanges = (heights: number[], ranges: BlockRangeType[] = []): Block * Type SubscriptionType for Wallet Store * @type {SubscriptionType} */ -type SubscriptionType = {listener: IListener, subscriptions: Subscription[]} +type SubscriptionType = { listener: IListener | undefined, subscriptions: Subscription[] } /** * Type BlockRangeType for Wallet Store * @type {BlockRangeType} */ -type BlockRangeType = {start: number} +type BlockRangeType = { start: number } + /// end-region custom types + +interface NetworkState { + initialized: boolean + currentPeer: URLInfo + currentPeerInfo: NodeModel + networkModel: NetworkModel + networkConfiguration: NetworkConfigurationModel + repositoryFactory: RepositoryFactory + listener: Listener + generationHash: string + networkType: NetworkType + isConnected: boolean + knowNodes: NodeModel[] + currentHeight: number + knownBlocks: Record + subscriptions: Subscription[] +} + +const defaultPeer = URLHelpers.formatUrl(networkConfig.defaultNodeUrl) + +const networkState: NetworkState = { + initialized: false, + currentPeer: defaultPeer, + currentPeerInfo: new NodeModel(defaultPeer.url, defaultPeer.url), + networkType: undefined, + generationHash: undefined, + networkModel: undefined, + networkConfiguration: networkConfig.networkConfigurationDefaults, + repositoryFactory: RESTService.createRepositoryFactory(networkConfig.defaultNodeUrl), + listener: undefined, + isConnected: false, + knowNodes: [], + currentHeight: 0, + knownBlocks: {}, + subscriptions: [], +} export default { namespaced: true, - state: { - initialized: false, - config: networkConfig, - defaultPeer: URLHelpers.formatUrl(networkConfig.defaultNode.url), - currentPeer: URLHelpers.formatUrl(networkConfig.defaultNode.url), - explorerUrl: networkConfig.explorerUrl, - networkType: NetworkType.TEST_NET, - generationHash: networkConfig.networks['testnet-publicTest'].generationHash, - properties: networkConfig.networks['testnet-publicTest'].properties, - repositoryFactory: RESTService.createRepositoryFactory(networkConfig.defaultNode.url), - isConnected: false, - nemesisTransactions: [], - knownPeers: [], - currentHeight: 0, - knownBlocks: {}, - // Subscriptions to websocket channels. - subscriptions: [], - }, + state: networkState, getters: { - getInitialized: state => state.initialized, - getSubscriptions: state => state.subscriptions, - networkType: state => state.networkType, - generationHash: state => state.generationHash, - repositoryFactory: state => state.repositoryFactory, - properties: state => state.properties, - defaultPeer: state => state.defaultPeer, - currentPeer: state => state.currentPeer, - currentPeerInfo: state => state.currentPeerInfo, - explorerUrl: state => state.explorerUrl, - isConnected: state => state.isConnected, - knownPeers: state => state.knownPeers, - currentHeight: state => state.currentHeight, - knownBlocks: state => state.knownBlocks, - config: state => state.config, + getInitialized: (state: NetworkState) => state.initialized, + subscriptions: (state: NetworkState) => state.subscriptions, + networkType: (state: NetworkState) => state.networkType, + generationHash: (state: NetworkState) => state.generationHash, + repositoryFactory: (state: NetworkState) => state.repositoryFactory, + listener: (state: NetworkState) => state.listener, + networkModel: (state: NetworkState) => state.networkModel, + networkConfiguration: (state: NetworkState) => state.networkConfiguration, + currentPeer: (state: NetworkState) => state.currentPeer, + currentPeerInfo: (state: NetworkState) => state.currentPeerInfo, + isConnected: (state: NetworkState) => state.isConnected, + knowNodes: (state: NetworkState) => state.knowNodes, + currentHeight: (state: NetworkState) => state.currentHeight, + knownBlocks: (state: NetworkState) => state.knownBlocks, }, mutations: { - setInitialized: (state, initialized) => { state.initialized = initialized }, - setConnected: (state, connected) => { state.isConnected = connected }, - currentHeight: (state, height) => Vue.set(state, 'currentHeight', height), - currentPeerInfo: (state, info) => Vue.set(state, 'currentPeerInfo', info), - repositoryFactory: (state, repositoryFactory) => Vue.set(state, 'repositoryFactory', repositoryFactory), - currentPeer: (state, payload) => { - if (undefined !== payload) { - const currentPeer = URLHelpers.formatUrl(payload) - Vue.set(state, 'currentPeer', currentPeer) - Vue.set(state, 'repositoryFactory', RESTService.createRepositoryFactory(currentPeer.url)) + setInitialized: (state: NetworkState, + initialized: boolean) => { state.initialized = initialized }, + setConnected: (state: NetworkState, connected: boolean) => { state.isConnected = connected }, + currentHeight: (state: NetworkState, currentHeight: number) => Vue.set(state, 'currentHeight', + currentHeight), + currentPeerInfo: (state: NetworkState, currentPeerInfo: NodeModel) => Vue.set(state, + 'currentPeerInfo', currentPeerInfo), + repositoryFactory: (state: NetworkState, repositoryFactory: RepositoryFactory) => Vue.set(state, + 'repositoryFactory', repositoryFactory), + networkConfiguration: (state: NetworkState, + networkConfiguration: NetworkConfigurationModel) => Vue.set(state, + 'networkConfiguration', networkConfiguration), + listener: (state: NetworkState, listener: Listener) => Vue.set(state, 'listener', listener), + networkModel: (state: NetworkState, networkModel: NetworkModel) => Vue.set(state, + 'networkModel', networkModel), + knowNodes: (state: NetworkState, knowNodes: NodeModel[]) => Vue.set(state, 'knowNodes', + knowNodes), + generationHash: (state: NetworkState, generationHash: string) => Vue.set(state, + 'generationHash', generationHash), + networkType: (state: NetworkState, networkType: NetworkType) => Vue.set(state, 'networkType', + networkType), + currentPeer: (state: NetworkState, currentPeer: URLInfo) => Vue.set(state, 'currentPeer', + currentPeer), + + addPeer: (state: NetworkState, peerUrl: string) => { + const knowNodes: NodeModel[] = state.knowNodes + const existNode = knowNodes.find((p: NodeModel) => p.url === peerUrl) + if (existNode) { + return } + const newNodes = [ ...knowNodes, new NodeModel(peerUrl, '') ] + new NodeService().saveNodes(newNodes) + Vue.set(state, 'knowNodes', newNodes) }, - addPeer: (state, peerUrl) => { - const knownPeers = state.knownPeers - knownPeers.push(peerUrl) - Vue.set(state, 'knownPeers', knownPeers) - }, - removePeer: (state, peerUrl) => { - const idx = state.knownPeers.findIndex(p => p === peerUrl) - if (idx === undefined) { - return + removePeer: (state: NetworkState, peerUrl: string) => { + const knowNodes: NodeModel[] = state.knowNodes + const toBeDeleted = knowNodes.find((p: NodeModel) => p.url === peerUrl) + if (!toBeDeleted) { + return } - - const leftPeers = state.knownPeers.splice(0, idx) - const rightPeers = state.knownPeers.splice(idx + 1, state.knownPeers.length - idx - 1) - const knownPeers = leftPeers.concat(rightPeers) - Vue.set(state, 'knownPeers', knownPeers) - }, - resetPeers: (state) => { - Vue.set(state, 'knownPeers', []) + const newNodes = knowNodes.filter(n => n !== toBeDeleted) + new NodeService().saveNodes(newNodes) + Vue.set(state, 'knowNodes', newNodes) }, - addBlock: (state, block: BlockInfo) => { + addBlock: (state: NetworkState, block: BlockInfo) => { const knownBlocks = state.knownBlocks knownBlocks[block.height.compact()] = block Vue.set(state, 'knownBlocks', knownBlocks) }, - networkType: (state, type) => { - Vue.set(state, 'networkType', type || NetworkType.TEST_NET) - }, - setSubscriptions: (state, data) => Vue.set(state, 'subscriptions', data), - addSubscriptions: (state, payload) => { + subscriptions: (state: NetworkState, data) => Vue.set(state, 'subscriptions', data), + addSubscriptions: (state: NetworkState, payload) => { const subscriptions = state.subscriptions - subscriptions.push(payload) - - Vue.set(state, 'subscriptions', subscriptions) + Vue.set(state, 'subscriptions', [ ...subscriptions, payload ]) }, - generationHash: (state, hash) => Vue.set(state, 'generationHash', hash), }, actions: { - async initialize({ commit, dispatch, getters }, withFeed) { + async initialize({commit, dispatch, getters}) { const callback = async () => { - - let initPayload: { - knownPeers: PeersModel[] - nodeUrl: string - } - - // - initialize current peer from database if available - if (undefined !== withFeed && withFeed.endpoints && withFeed.endpoints.length) { - initPayload = await dispatch('INITIALIZE_FROM_DB', withFeed) - } - // - initialize current peer from config and REST - else { - initPayload = await dispatch('INITIALIZE_FROM_CONFIG', getters.defaultPeer.url) - } - - const knownPeers: PeersModel[] = initPayload.knownPeers - const nodeUrl: string = initPayload.nodeUrl - - dispatch('diagnostic/ADD_DEBUG', `Store action network/initialize selected peer: ${nodeUrl}`, {root: true}) - - // - populate known peers - knownPeers.map(peer => commit('addPeer', peer.values.get('rest_url'))) - + const networkService = new NetworkService() + await dispatch('CONNECT', networkService.getDefaultUrl()) // update store commit('setInitialized', true) } - // acquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, dispatch, getters }) { + async uninitialize({commit, dispatch, getters}) { const callback = async () => { dispatch('UNSUBSCRIBE') commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - /// region scoped actions - async INITIALIZE_FROM_DB({dispatch}, withFeed) { - const defaultPeer = withFeed.endpoints.find(m => m.values.get('is_default')) - const nodeUrl = defaultPeer.values.get('rest_url') - const repositoryFactory: RepositoryFactory = RESTService.createRepositoryFactory(nodeUrl) - - // - height always from network - const chainHttp = repositoryFactory.createChainRepository() - const currentHeight = await chainHttp.getBlockchainHeight().toPromise() - - // - set current peer connection - dispatch('OPEN_PEER_CONNECTION', { - repositoryFactory: repositoryFactory, - url: nodeUrl, - networkType: defaultPeer.values.get('networkType'), - generationHash: defaultPeer.values.get('generationHash'), - currentHeight: currentHeight, - peerInfo: defaultPeer.objects.info, - }) - - // - populate known peers - return { - knownPeers: withFeed.endpoints, - nodeUrl: nodeUrl, - } - }, - async INITIALIZE_FROM_CONFIG({dispatch}, nodeUrl) { - try { - const payload = await dispatch('REST_FETCH_PEER_INFO', nodeUrl) - - // @OFFLINE: value should be defaulted to config data when REST_FETCH_PEER_INFO throws - // - set current peer connection - dispatch('OPEN_PEER_CONNECTION', {...payload}) - - // - initialize from config must populate DB - const repository = new PeersRepository() - const knownPeers: PeersModel[] = repository.repopulateFromConfig(payload.generationHash) - return { - knownPeers: knownPeers, - nodeUrl: nodeUrl, - } - } - catch (e) { - dispatch('diagnostic/ADD_ERROR', `Store action network/initialize default peer unreachable: ${e.toString()}`, {root: true}) - } - }, - async OPEN_PEER_CONNECTION({commit, dispatch}, payload) { - commit('currentPeer', payload.url) - commit('networkType', payload.networkType) - commit('setConnected', true) - $eventBus.$emit('newConnection', payload.url) - commit('currentHeight', payload.currentHeight.compact()) - commit('currentPeerInfo', payload.peerInfo) + async CONNECT({commit, dispatch}, rawUrl: string) { + const networkService = new NetworkService() + const currentPeer = URLHelpers.formatUrl(rawUrl) + const url = currentPeer.url + const {networkModel, repositoryFactory} = await networkService.getNetworkModel(url) + .toPromise() + const getNodesPromise = new NodeService().getNodes(repositoryFactory, url).toPromise() + const getBlockchainHeightPromise = repositoryFactory.createChainRepository() + .getBlockchainHeight().toPromise() + const nodes = await getNodesPromise + const currentHeight = (await getBlockchainHeightPromise).compact() + const listener = repositoryFactory.createListener() + await listener.open() + + commit('currentPeer', currentPeer) + commit('networkModel', networkModel) + commit('networkConfiguration', networkModel.networkConfiguration) + commit('networkType', networkModel.networkType) + commit('generationHash', networkModel.generationHash) + commit('repositoryFactory', repositoryFactory) + commit('knowNodes', nodes) + commit('listener', listener) + commit('currentHeight', currentHeight) + commit('currentPeerInfo', nodes.find(n => n.url === url)) + commit('setConnected', true) + $eventBus.$emit('newConnection', currentPeer) // subscribe to updates dispatch('SUBSCRIBE') }, - async SET_CURRENT_PEER({ dispatch, rootGetters }, currentPeerUrl) { + + + async SET_CURRENT_PEER({dispatch, rootGetters}, currentPeerUrl) { if (!UrlValidator.validate(currentPeerUrl)) { - throw Error(`Cannot change node. URL is not valid: ${currentPeerUrl}`) + throw Error('Cannot change node. URL is not valid: ' + currentPeerUrl) } // - show loading overlay @@ -264,37 +246,28 @@ export default { disableCloseButton: true, }, {root: true}) - dispatch('diagnostic/ADD_DEBUG', `Store action network/SET_CURRENT_PEER dispatched with: ${currentPeerUrl}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action network/SET_CURRENT_PEER dispatched with: ' + currentPeerUrl, {root: true}) try { // - disconnect from previous node await dispatch('UNSUBSCRIBE') - // - fetch info / connect to new node - const payload = await dispatch('REST_FETCH_PEER_INFO', currentPeerUrl) - - dispatch('OPEN_PEER_CONNECTION', {...payload}) + await dispatch('CONNECT', currentPeerUrl) const currentWallet = rootGetters['wallet/currentWallet'] - // - clear wallet balances - await dispatch('wallet/uninitialize', { - address: currentWallet.values.get('address'), - which: 'currentWalletMosaics', - }, {root: true}) - // - re-open listeners - dispatch('wallet/initialize', {address: currentWallet.values.get('address')}, {root: true}) + dispatch('wallet/initialize', {address: currentWallet.address}, {root: true}) - // - set chosen endpoint as the new default in the database - new PeerService().setDefaultNode(currentPeerUrl) } catch (e) { dispatch( 'notification/ADD_ERROR', `${app.$t('error_peer_connection_went_wrong', {peerUrl: currentPeerUrl})}`, {root: true}, ) - dispatch('diagnostic/ADD_ERROR', `Error with store action network/SET_CURRENT_PEER: ${JSON.stringify(e)}`, {root: true}) + dispatch('diagnostic/ADD_ERROR', + 'Error with store action network/SET_CURRENT_PEER: ' + JSON.stringify(e), {root: true}) } finally { // - hide loading overlay dispatch('app/SET_LOADING_OVERLAY', {show: false}, {root: true}) @@ -302,69 +275,69 @@ export default { }, ADD_KNOWN_PEER({commit}, peerUrl) { if (!UrlValidator.validate(peerUrl)) { - throw Error(`Cannot add node. URL is not valid: ${peerUrl}`) + throw Error('Cannot add node. URL is not valid: ' + peerUrl) } - commit('addPeer', peerUrl) }, REMOVE_KNOWN_PEER({commit}, peerUrl) { commit('removePeer', peerUrl) }, - RESET_PEERS({commit, getters, dispatch}) { - commit('resetPeers') - // - re-populate known peers from config - const repository = new PeersRepository() - const knownPeers = repository.repopulateFromConfig(getters.generationHash) + async RESET_PEERS({dispatch}) { + try { - // - populate known peers - knownPeers.map(peer => commit('addPeer', peer.values.get('rest_url'))) + const nodeService = new NodeService() + nodeService.reset() - dispatch('SET_CURRENT_PEER', knownPeers.shift().values.get('rest_url')) - }, - RESET_SUBSCRIPTIONS({commit}) { - commit('setSubscriptions', []) + const networkService = new NetworkService() + networkService.reset() + + const defaultUrl = networkService.getDefaultUrl() + dispatch('app/SET_LOADING_OVERLAY', { + show: true, + message: `${app.$t('info_connecting_peer', {peerUrl: defaultUrl})}`, + disableCloseButton: true, + }, {root: true}) + + await dispatch('SET_CURRENT_PEER', defaultUrl) + } finally { + dispatch('app/SET_LOADING_OVERLAY', {show: false}, {root: true}) + } }, + ADD_BLOCK({commit}, block: BlockInfo) { commit('addBlock', block) }, /** - * Websocket API - */ + * Websocket API + */ // Subscribe to latest account transactions. - async SUBSCRIBE({ commit, dispatch, getters }) { + async SUBSCRIBE({commit, dispatch, getters}) { // use RESTService to open websocket channel subscriptions - const repositoryFactory = getters['repositoryFactory'] as RepositoryFactory - const subscriptions: SubscriptionType = await RESTService.subscribeBlocks( - {commit, dispatch}, - repositoryFactory, - ) - + const listener = getters['listener'] as Listener + const subscription = listener.newBlock().subscribe((block: BlockInfo) => { + dispatch('SET_CURRENT_HEIGHT', block.height.compact()) + dispatch('ADD_BLOCK', block) + dispatch('diagnostic/ADD_INFO', 'New block height: ' + block.height.compact(), {root: true}) + }) // update state of listeners & subscriptions - commit('addSubscriptions', subscriptions) + commit('addSubscriptions', subscription) }, // Unsubscribe from all open websocket connections - async UNSUBSCRIBE({ dispatch, getters }) { - const subscriptions = getters.getSubscriptions - - for (let i = 0, m = subscriptions.length; i < m; i ++) { - const subscription = subscriptions[i] - - // subscribers - for (let j = 0, n = subscription.subscriptions; j < n; j ++) { - await subscription.subscriptions[j].unsubscribe() - } - - await subscription.listener.close() - } - + async UNSUBSCRIBE({commit, getters}) { + const subscriptions: Subscription[] = getters.subscriptions + subscriptions.forEach(s => s.unsubscribe()) + const listener: Listener = getters.listener + if (listener) {listener.close()} // update state - dispatch('RESET_SUBSCRIPTIONS') + commit('subscriptions', []) }, + SET_CURRENT_HEIGHT({commit}, height) { commit('currentHeight', height) }, + async REST_FETCH_BLOCKS({commit, dispatch, getters, rootGetters}, blockHeights: number[]) { // - filter out known blocks @@ -398,47 +371,17 @@ export default { const nextHeights = ranges.slice(3).map(r => r.start) if (nextHeights.length) { setTimeout(() => { - dispatch('diagnostic/ADD_DEBUG', `Store action network/REST_FETCH_BLOCKS delaying heights discovery for 2 seconds: ${JSON.stringify(nextHeights)}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action network/REST_FETCH_BLOCKS delaying heights discovery for 2 seconds: ' + JSON.stringify( + nextHeights), {root: true}) return dispatch('REST_FETCH_BLOCKS', nextHeights) }, 2000) } - } - catch (e) { - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch blocks information: ${e}`, {root: true}) + } catch (e) { + dispatch('diagnostic/ADD_ERROR', + 'An error happened while trying to fetch blocks information: ' + e, {root: true}) return false } }, - async REST_FETCH_PEER_INFO({dispatch}, nodeUrl: string) { - dispatch('diagnostic/ADD_DEBUG', `Store action network/REST_FETCH_PEER_INFO dispatched with: ${nodeUrl}`, {root: true}) - - try { - const repositoryFactory: RepositoryFactory = RESTService.createRepositoryFactory(nodeUrl) - const chainHttp = repositoryFactory.createChainRepository() - const nodeHttp = repositoryFactory.createNodeRepository() - - // - read nemesis from REST - const generationHash = await repositoryFactory.getGenerationHash().toPromise() - const networkType = await repositoryFactory.getNetworkType().toPromise() - - // - read peer info from REST - const peerInfo = await nodeHttp.getNodeInfo().toPromise() - - // - read chain height from REST - const currentHeight = await chainHttp.getBlockchainHeight().toPromise() - - return { - url: nodeUrl, - repositoryFactory, - networkType, - generationHash, - currentHeight, - peerInfo, - } - } - catch(e) { - throw new Error(e) - } - }, - /// end-region scoped actions }, } diff --git a/src/store/Notification.ts b/src/store/Notification.ts index 1a16e34d7..4ad47f5ee 100644 --- a/src/store/Notification.ts +++ b/src/store/Notification.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,10 +14,10 @@ * limitations under the License. */ import Vue from 'vue' - // internal dependencies import app from '@/main' import {AwaitLock} from './AwaitLock' + const Lock = AwaitLock.create() export default { diff --git a/src/store/Statistics.ts b/src/store/Statistics.ts index 63fb30985..eab3aab69 100644 --- a/src/store/Statistics.ts +++ b/src/store/Statistics.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,6 @@ */ import {RepositoryFactory, StorageInfo} from 'symbol-sdk' import Vue from 'vue' - // internal dependencies import {AwaitLock} from './AwaitLock' @@ -45,7 +44,7 @@ export default { countNodes: (state, cnt) => Vue.set(state, 'countNodes', cnt), }, actions: { - async initialize({ commit, getters, rootGetters }) { + async initialize({commit, getters, rootGetters}) { const callback = async () => { const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory const nodeHttp = repositoryFactory.createNodeRepository() @@ -54,7 +53,7 @@ export default { commit('countTransactions', diagnostic.numTransactions) commit('countBlocks', diagnostic.numBlocks) commit('countAccounts', diagnostic.numAccounts) - + // - fetch nodes (not yet in SDK) const nodes = await nodeHttp.getNodePeers().toPromise() commit('countNodes', nodes.length) @@ -66,14 +65,11 @@ export default { // aquire async lock until initialized await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, getters }) { + async uninitialize({commit, getters}) { const callback = async () => { commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - /// region scoped actions - - /// end-region scoped actions }, } diff --git a/src/store/Temporary.ts b/src/store/Temporary.ts index 87f375df4..54c251b98 100644 --- a/src/store/Temporary.ts +++ b/src/store/Temporary.ts @@ -15,9 +15,9 @@ */ import {Password} from 'symbol-sdk' import Vue from 'vue' - // internal dependencies import {AwaitLock} from './AwaitLock' + const Lock = AwaitLock.create() export default { diff --git a/src/store/Wallet.ts b/src/store/Wallet.ts index ef0353490..3db2ea9ab 100644 --- a/src/store/Wallet.ts +++ b/src/store/Wallet.ts @@ -14,125 +14,44 @@ * limitations under the License. */ import Vue from 'vue' -import { - Account, - AccountInfo, - Address, - AggregateTransaction, - CosignatureSignedTransaction, - IListener, - Mosaic, - MosaicInfo, - MultisigAccountInfo, - NamespaceInfo, - NetworkType, - Order, - PublicAccount, - QueryParams, - RepositoryFactory, - SignedTransaction, - Transaction, - TransactionType, - UInt64, -} from 'symbol-sdk' -import {Subscription} from 'rxjs' +import {AccountInfo, Address, AggregateTransaction, CosignatureSignedTransaction, IListener, MultisigAccountInfo, NetworkType, QueryParams, RepositoryFactory, SignedTransaction, Transaction, TransactionType} from 'symbol-sdk' +import {of, Subscription} from 'rxjs' // internal dependencies import {$eventBus} from '../events' import {RESTService} from '@/services/RESTService' import {AwaitLock} from './AwaitLock' import {BroadcastResult} from '@/core/transactions/BroadcastResult' -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {RESTDispatcher} from '@/core/utils/RESTDispatcher' -import {NamespaceService} from '@/services/NamespaceService' import {MultisigService} from '@/services/MultisigService' +import * as _ from 'lodash' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {WalletService} from '@/services/WalletService' +import {catchError, map} from 'rxjs/operators' /** * Helper to format transaction group in name of state variable. * * @internal * @param {string} group - * @return {string} One of 'confirmedTransactions', 'unconfirmedTransactions' or 'partialTransactions' + * @return {string} One of 'confirmedTransactions', 'unconfirmedTransactions' or + * 'partialTransactions' */ const transactionGroupToStateVariable = ( group: string, ): string => { let transactionGroup = group.toLowerCase() if (transactionGroup === 'unconfirmed' - || transactionGroup === 'confirmed' - || transactionGroup === 'partial') { - transactionGroup = `${transactionGroup}Transactions` - } - else { - throw new Error(`Unknown transaction group '${group}'.`) + || transactionGroup === 'confirmed' + || transactionGroup === 'partial') { + transactionGroup = transactionGroup + 'Transactions' + } else { + throw new Error('Unknown transaction group \'' + group + '\'.') } return transactionGroup } -/** - * Create an sdk address object by payload - * @param payload - */ -const getAddressByPayload = ( - payload: WalletPayloadType, -): Address => { - if (payload instanceof WalletsModel) { - return Address.createFromRawAddress(payload.values.get('address')) - } - else if (payload instanceof PublicAccount - || payload instanceof Account) { - return payload.address - } - else if (payload instanceof Address) { - return payload - } - - // - finally from payload - const publicAccount = PublicAccount.createFromPublicKey( - payload.publicKey, - payload.networkType, - ) - return publicAccount.address -} - -/** - * Create a wallet entity by payload - * @param payload - */ -const getWalletByPayload = ( - payload: WalletPayloadType, -): WalletsModel => { - if (payload instanceof WalletsModel) { - return payload - } - else if (payload instanceof Address) { - return new WalletsModel(new Map([ - [ 'name', payload.pretty() ], - [ 'address', payload.plain() ], - [ 'publicKey', payload.plain() ], - [ 'isMultisig', true ], - ])) - } - else if (payload instanceof PublicAccount || payload instanceof Account) { - return new WalletsModel(new Map([ - [ 'name', payload.address.pretty() ], - [ 'address', payload.address.plain() ], - [ 'publicKey', payload.publicKey ], - [ 'isMultisig', true ], - ])) - } - else if (payload && payload.networkType && payload.publicKey) { - const publicAccount = PublicAccount.createFromPublicKey(payload.publicKey, payload.networkType) - const walletName = payload.name && payload.name.length ? payload.name : publicAccount.address.pretty() - return new WalletsModel(new Map([ - [ 'name', walletName ], - [ 'address', publicAccount.address.plain() ], - [ 'publicKey', publicAccount.publicKey ], - [ 'isMultisig', true ], - ])) - } - else return undefined -} /// region globals const Lock = AwaitLock.create() @@ -147,30 +66,24 @@ type SubscriptionType = { subscriptions: Subscription[] } -type WalletPayloadType = WalletsModel | Account | PublicAccount | Address | { - networkType: NetworkType - publicKey?: string - name?: string -} +export type Signer = { label: string, publicKey: string, address: Address, multisig: boolean } // wallet state typing interface WalletState { initialized: boolean - currentWallet: WalletsModel + currentWallet: WalletModel currentWalletAddress: Address - currentWalletMosaics: Mosaic[] - currentWalletOwnedMosaics: MosaicInfo[] - currentWalletOwnedNamespaces: NamespaceInfo[] + currentWalletMultisigInfo: MultisigAccountInfo isCosignatoryMode: boolean - currentSigner: {networkType: NetworkType, publicKey: string} + signers: Signer[] + currentSigner: Signer currentSignerAddress: Address - currentSignerMosaics: Mosaic[] - currentSignerOwnedMosaics: MosaicInfo[] - currentSignerOwnedNamespaces: NamespaceInfo[] + currentSignerMultisigInfo: MultisigAccountInfo // Known wallet database identifiers knownWallets: string[] - knownWalletsInfo: Record - knownMultisigsInfo: Record + knownAddresses: Address[] + accountsInfo: AccountInfo[] + multisigAccountsInfo: MultisigAccountInfo[] transactionHashes: string[] confirmedTransactions: Transaction[] unconfirmedTransactions: Transaction[] @@ -178,7 +91,6 @@ interface WalletState { stageOptions: { isAggregate: boolean, isMultisig: boolean } stagedTransactions: Transaction[] signedTransactions: SignedTransaction[] - transactionCache: Record // Subscriptions to webSocket channels subscriptions: Record } @@ -188,18 +100,16 @@ const walletState: WalletState = { initialized: false, currentWallet: null, currentWalletAddress: null, - currentWalletMosaics: [], - currentWalletOwnedMosaics: [], - currentWalletOwnedNamespaces: [], + currentWalletMultisigInfo: null, isCosignatoryMode: false, + signers: [], currentSigner: null, currentSignerAddress: null, - currentSignerMosaics: [], - currentSignerOwnedMosaics: [], - currentSignerOwnedNamespaces: [], + currentSignerMultisigInfo: null, knownWallets: [], - knownWalletsInfo: {}, - knownMultisigsInfo: {}, + knownAddresses: [], + accountsInfo: [], + multisigAccountsInfo: [], transactionHashes: [], confirmedTransactions: [], unconfirmedTransactions: [], @@ -210,7 +120,6 @@ const walletState: WalletState = { }, stagedTransactions: [], signedTransactions: [], - transactionCache: {}, // Subscriptions to websocket channels. subscriptions: {}, } @@ -223,52 +132,20 @@ export default { state: walletState, getters: { getInitialized: (state: WalletState) => state.initialized, - currentWallet: (state: WalletState) => { - // - in case of a WalletsModel, the currentWallet instance is simply returned - // - in case of Address/Account or other, a fake model will be created - return getWalletByPayload(state.currentWallet) - }, - currentSigner: (state: WalletState) => { - // - in case of a WalletsModel, the currentWallet instance is simply returned - // - in case of Address/Account or other, a fake model will be created - return getWalletByPayload(state.currentSigner) + currentWallet: (state: WalletState): WalletModel => { + return state.currentWallet }, + signers: (state: WalletState): Signer[] => state.signers, + currentSigner: (state: WalletState): Signer => state.currentSigner, currentWalletAddress: (state: WalletState) => state.currentWalletAddress, - currentWalletInfo: (state: WalletState): AccountInfo | null => { - const plainAddress = state.currentWalletAddress ? state.currentWalletAddress.plain() : null - if(!plainAddress) return null - if(!state.knownWalletsInfo || !state.knownWalletsInfo[plainAddress]) return null - return state.knownWalletsInfo[plainAddress] - }, - currentWalletMosaics: (state: WalletState) => state.currentWalletMosaics, - currentWalletOwnedMosaics: (state: WalletState) => state.currentWalletOwnedMosaics, - currentWalletOwnedNamespaces: (state: WalletState) => state.currentWalletOwnedNamespaces, - currentWalletMultisigInfo: (state: WalletState) => { - const plainAddress = state.currentWalletAddress ? state.currentWalletAddress.plain() : null - if(!plainAddress) return null - if(!state.knownMultisigsInfo || !state.knownMultisigsInfo[plainAddress]) return null - return state.knownMultisigsInfo[plainAddress] - }, + knownAddresses: (state: WalletState) => state.knownAddresses, + currentWalletMultisigInfo: (state: WalletState) => state.currentWalletMultisigInfo, + currentSignerMultisigInfo: (state: WalletState) => state.currentSignerMultisigInfo, isCosignatoryMode: (state: WalletState) => state.isCosignatoryMode, currentSignerAddress: (state: WalletState) => state.currentSignerAddress, - currentSignerInfo: (state: WalletState): AccountInfo | null => { - const plainAddress = state.currentSignerAddress ? state.currentSignerAddress.plain() : null - if(!plainAddress) return null - if(!state.knownWalletsInfo || !state.knownWalletsInfo[plainAddress]) return null - return state.knownWalletsInfo[plainAddress] - }, - currentSignerMultisigInfo: (state: WalletState) => { - const plainAddress = state.currentSignerAddress ? state.currentSignerAddress.plain() : null - if(!plainAddress) return null - if(!state.knownMultisigsInfo || !state.knownMultisigsInfo[plainAddress]) return null - return state.knownMultisigsInfo[plainAddress] - }, - currentSignerMosaics: (state: WalletState) => state.currentSignerMosaics, - currentSignerOwnedMosaics: (state: WalletState) => state.currentSignerOwnedMosaics, - currentSignerOwnedNamespaces: (state: WalletState) => state.currentSignerOwnedNamespaces, knownWallets: (state: WalletState) => state.knownWallets, - knownWalletsInfo: (state: WalletState) => state.knownWalletsInfo, - knownMultisigsInfo: (state: WalletState) => state.knownMultisigsInfo, + accountsInfo: (state: WalletState) => state.accountsInfo, + multisigAccountsInfo: (state: WalletState) => state.multisigAccountsInfo, getSubscriptions: (state: WalletState) => state.subscriptions, transactionHashes: (state: WalletState) => state.transactionHashes, confirmedTransactions: (state: WalletState) => { @@ -297,42 +174,41 @@ export default { stageOptions: (state: WalletState) => state.stageOptions, stagedTransactions: (state: WalletState) => state.stagedTransactions, signedTransactions: (state: WalletState) => state.signedTransactions, - transactionCache: (state: WalletState) => state.transactionCache, - allTransactions: (state, getters) => { - return [].concat( - getters.partialTransactions, - getters.unconfirmedTransactions, - getters.confirmedTransactions, - ) - }, }, mutations: { - setInitialized: (state, initialized) => { state.initialized = initialized }, - currentWallet: (state, walletModel) => Vue.set(state, 'currentWallet', walletModel), - isCosignatoryMode: (state, mode) => Vue.set(state, 'isCosignatoryMode', mode), - currentWalletAddress: (state, walletAddress) => Vue.set(state, 'currentWalletAddress', walletAddress), - currentWalletMosaics: (state, currentWalletMosaics) => Vue.set(state, 'currentWalletMosaics', currentWalletMosaics), - currentWalletOwnedMosaics: (state, currentWalletOwnedMosaics) => Vue.set(state, 'currentWalletOwnedMosaics', currentWalletOwnedMosaics), - currentWalletOwnedNamespaces: (state, currentWalletOwnedNamespaces) => Vue.set(state, 'currentWalletOwnedNamespaces', currentWalletOwnedNamespaces), - currentSigner: (state, signerPayload) => Vue.set(state, 'currentSigner', signerPayload), - currentSignerAddress: (state, signerAddress) => Vue.set(state, 'currentSignerAddress', signerAddress), - currentSignerMosaics: (state, currentSignerMosaics) => Vue.set(state, 'currentSignerMosaics', currentSignerMosaics), - currentSignerOwnedMosaics: (state, currentSignerOwnedMosaics) => Vue.set(state, 'currentSignerOwnedMosaics', currentSignerOwnedMosaics), - currentSignerOwnedNamespaces: (state, currentSignerOwnedNamespaces) => Vue.set(state, 'currentSignerOwnedNamespaces', currentSignerOwnedNamespaces), - setKnownWallets: (state, wallets) => Vue.set(state, 'knownWallets', wallets), - addKnownWalletsInfo: (state, walletInfo) => { - Vue.set(state.knownWalletsInfo, walletInfo.address.plain(), walletInfo) - }, - addKnownMultisigInfo: (state, multisigInfo: MultisigAccountInfo) => { - Vue.set(state.knownMultisigsInfo, multisigInfo.account.address.plain(), multisigInfo) - }, - setKnownMultisigInfo: (state, payload) => Vue.set(state, 'knownMultisigsInfo', payload), - transactionHashes: (state, hashes) => Vue.set(state, 'transactionHashes', hashes), - confirmedTransactions: (state, transactions) => Vue.set(state, 'confirmedTransactions', transactions), - unconfirmedTransactions: (state, transactions) => Vue.set(state, 'unconfirmedTransactions', transactions), - partialTransactions: (state, transactions) => Vue.set(state, 'partialTransactions', transactions), - setSubscriptions: (state, data) => Vue.set(state, 'subscriptions', data), - addSubscriptions: (state, payload: {address: string, subscriptions: SubscriptionType}) => { + setInitialized: (state: WalletState, initialized: boolean) => { state.initialized = initialized }, + currentWallet: (state: WalletState, walletModel: WalletModel) => Vue.set(state, 'currentWallet', + walletModel), + currentWalletAddress: (state: WalletState, walletAddress: Address) => Vue.set(state, + 'currentWalletAddress', walletAddress), + currentSigner: (state: WalletState, currentSigner: Signer) => Vue.set(state, 'currentSigner', + currentSigner), + signers: (state: WalletState, signers: Signer[]) => Vue.set(state, 'signers', signers), + currentSignerAddress: (state: WalletState, signerAddress) => Vue.set(state, + 'currentSignerAddress', signerAddress), + knownWallets: (state: WalletState, wallets) => Vue.set(state, 'knownWallets', wallets), + knownAddresses: (state: WalletState, knownAddresses: Address[]) => Vue.set(state, + 'knownAddresses', knownAddresses), + isCosignatoryMode: (state: WalletState, mode: boolean) => Vue.set(state, 'isCosignatoryMode', + mode), + accountsInfo: (state: WalletState, accountsInfo) => Vue.set(state, 'accountsInfo', + accountsInfo), + multisigAccountsInfo: (state: WalletState, multisigAccountsInfo) => Vue.set(state, + 'multisigAccountsInfo', multisigAccountsInfo), + currentWalletMultisigInfo: (state: WalletState, currentWalletMultisigInfo) => Vue.set(state, + 'currentWalletMultisigInfo', currentWalletMultisigInfo), + currentSignerMultisigInfo: (state: WalletState, currentSignerMultisigInfo) => Vue.set(state, + 'currentSignerMultisigInfo', currentSignerMultisigInfo), + transactionHashes: (state: WalletState, hashes) => Vue.set(state, 'transactionHashes', hashes), + confirmedTransactions: (state: WalletState, transactions) => Vue.set(state, + 'confirmedTransactions', transactions), + unconfirmedTransactions: (state: WalletState, transactions) => Vue.set(state, + 'unconfirmedTransactions', transactions), + partialTransactions: (state: WalletState, transactions) => Vue.set(state, 'partialTransactions', + transactions), + setSubscriptions: (state: WalletState, data) => Vue.set(state, 'subscriptions', data), + addSubscriptions: (state: WalletState, + payload: { address: string, subscriptions: SubscriptionType }) => { const {address, subscriptions} = payload // skip when subscriptions is an empty array if (!subscriptions.subscriptions.length) return @@ -343,19 +219,11 @@ export default { // update state Vue.set(state.subscriptions, address, newSubscriptions) }, - addTransactionToCache: (state, payload): Record => { - if (payload === undefined) return - const {transaction, hash, cacheKey} = payload - // Get existing cached transactions with the same cache key - const cachedTransactions = state.transactionCache[cacheKey] || [] - // update state - Vue.set(state.cachedTransactions, cacheKey, [ ...cachedTransactions, {hash, transaction}]) - // update state - return state.transactionCache - }, - stageOptions: (state, options) => Vue.set(state, 'stageOptions', options), - setStagedTransactions: (state, transactions: Transaction[]) => Vue.set(state, 'stagedTransactions', transactions), - addStagedTransaction: (state, transaction: Transaction) => { + + stageOptions: (state: WalletState, options) => Vue.set(state, 'stageOptions', options), + setStagedTransactions: (state: WalletState, transactions: Transaction[]) => Vue.set(state, + 'stagedTransactions', transactions), + addStagedTransaction: (state: WalletState, transaction: Transaction) => { // - get previously staged transactions const staged = state.stagedTransactions @@ -366,7 +234,7 @@ export default { return Vue.set(state, 'stagedTransactions', staged) }, clearStagedTransaction: (state) => Vue.set(state, 'stagedTransactions', []), - addSignedTransaction: (state, transaction: SignedTransaction) => { + addSignedTransaction: (state: WalletState, transaction: SignedTransaction) => { // - get previously signed transactions const signed = state.signedTransactions @@ -374,14 +242,14 @@ export default { signed.push(transaction) return Vue.set(state, 'signedTransactions', signed) }, - removeSignedTransaction: (state, transaction: SignedTransaction) => { + removeSignedTransaction: (state: WalletState, transaction: SignedTransaction) => { // - get previously signed transactions const signed = state.signedTransactions // - find transaction by hash and delete const idx = signed.findIndex(tx => tx.hash === transaction.hash) if (undefined === idx) { - return + return } // skip `idx` @@ -398,26 +266,20 @@ export default { * Possible `options` values include: * @type { * skipTransactions: boolean, - * skipOwnedAssets: boolean, - * skipMultisig: boolean, * } */ - async initialize({ commit, dispatch, getters }, {address, options}) { + async initialize({commit, dispatch, getters}, {address}) { const callback = async () => { if (!address || !address.length) { - return + return } - - // fetch account info - await dispatch('REST_FETCH_WALLET_DETAILS', {address, options}) - // open websocket connections dispatch('SUBSCRIBE', address) commit('setInitialized', true) } await Lock.initialize(callback, {getters}) }, - async uninitialize({ commit, dispatch, getters }, {address, which}) { + async uninitialize({commit, dispatch, getters}, {address, which}) { const callback = async () => { // close websocket connections await dispatch('UNSUBSCRIBE', address) @@ -432,13 +294,6 @@ export default { async REST_FETCH_WALLET_DETAILS({dispatch}, {address, options}) { const dispatcher = new RESTDispatcher(dispatch) - // - blocking first action - dispatcher.add('REST_FETCH_INFO', address, null, true) - - // - other actions are all optional and can be disabled - if (!options || !options.skipMultisig) { - dispatcher.add('REST_FETCH_MULTISIG', address) - } if (!options || !options.skipTransactions) { dispatcher.add('REST_FETCH_TRANSACTIONS', { @@ -447,113 +302,109 @@ export default { address: address, }) } - - if (!options || !options.skipOwnedAssets) { - dispatcher.add('REST_FETCH_OWNED_MOSAICS', address) - dispatcher.add('REST_FETCH_OWNED_NAMESPACES', address) - } - // - delays of 1000ms will be added every second request dispatcher.throttle_dispatch() }, /** * Possible `options` values include: * @type { - * isCosignatoryMode: boolean, - * } - */ - async SET_CURRENT_WALLET({commit, dispatch, getters}, {model}) { - const previous = getters.currentWallet - const address: Address = getAddressByPayload(model) - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/SET_CURRENT_WALLET dispatched with ${address.plain()}`, {root: true}) + * isCosignatoryMode: boolean, + * } + */ + async SET_CURRENT_WALLET({commit, dispatch, getters}, currentWallet: WalletModel) { + const previous: WalletModel = getters.currentWallet + if (previous && previous.address === currentWallet.address) return - // skip if the wallet has not changed - if (!!previous && previous.values.get('address') === address.plain()) return + const currentWalletAddress: Address = Address.createFromRawAddress(currentWallet.address) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/SET_CURRENT_WALLET dispatched with ' + currentWalletAddress.plain(), + {root: true}) // set current wallet - commit('currentWallet', model) - commit('currentWalletAddress', address) + commit('currentWallet', currentWallet) - // reset current wallet mosaics - commit('currentWalletMosaics', []) // reset current signer - dispatch('SET_CURRENT_SIGNER', {model, options: {skipDetails: true}}) - - if (!!previous) { - // in normal initialize routine, old active wallet - // connections must be closed - await dispatch('uninitialize', {address: previous.values.get('address'), which: 'currentWalletMosaics'}) - } - - await dispatch('initialize', {address: address.plain(), options: {}}) - $eventBus.$emit('onWalletChange', address.plain()) + await dispatch('SET_CURRENT_SIGNER', {publicKey: currentWallet.publicKey}) + await dispatch('initialize', {address: currentWalletAddress.plain()}) + $eventBus.$emit('onWalletChange', currentWalletAddress.plain()) }, - async SET_CURRENT_SIGNER({commit, dispatch, getters}, {model, options}) { - const address: Address = getAddressByPayload(model) - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/SET_CURRENT_SIGNER dispatched with ${address.plain()}`, {root: true}) - - let payload = model - if (model instanceof WalletsModel) { - payload = { - networkType: address.networkType, - publicKey: model.values.get('publicKey'), - } - } - // set current signer - commit('currentSigner', payload) - commit('currentSignerAddress', address) - // whether entering in cosignatory mode - const currentWallet = getters['currentWallet'] - let isCosignatory = false - if (address.plain() !== currentWallet.values.get('address')) { - isCosignatory = true - } + async SET_CURRENT_SIGNER({commit, dispatch, getters, rootGetters}, + {publicKey}: { publicKey: string }) { + const networkType: NetworkType = rootGetters['network/networkType'] + const currentAccount: AccountModel = rootGetters['account/currentAccount'] + const currentWallet: WalletModel = getters.currentWallet + const previousSignerAddress: Address = getters.currentSignerAddress + const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory + + const currentSignerAddress: Address = Address.createFromPublicKey(publicKey, networkType) + + if (previousSignerAddress && previousSignerAddress.equals(currentSignerAddress)) return + + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/SET_CURRENT_SIGNER dispatched with ' + currentSignerAddress.plain(), + {root: true}) + + const currentWalletAddress = Address.createFromRawAddress(currentWallet.address) + const walletService = new WalletService() + const knownWallets = walletService.getKnownWallets(currentAccount.wallets) + const knownAddresses = _.uniqBy([ currentSignerAddress, + ...knownWallets.map(w => Address.createFromRawAddress(w.address)) ].filter(a => a), + 'address') + const accountsInfo = await repositoryFactory.createAccountRepository() + .getAccountsInfo(knownAddresses).toPromise() + const multisigAccountsInfo: MultisigAccountInfo[] = + await repositoryFactory.createMultisigRepository() + .getMultisigAccountGraphInfo(currentWalletAddress).pipe(map(g => { + return MultisigService.getMultisigInfoFromMultisigGraphInfo(g) + }), catchError(() => { + return of([]) + })).toPromise() + + const currentWalletMultisigInfo = multisigAccountsInfo.find( + m => m.account.address.equals(currentWalletAddress)) + const currentSignerMultisigInfo = multisigAccountsInfo.find( + m => m.account.address.equals(currentSignerAddress)) + const multisigService = new MultisigService() + const signers = multisigService.getSigners(networkType, knownWallets, currentWallet, + currentWalletMultisigInfo) + + commit('currentSignerAddress', currentSignerAddress) + commit('currentWalletAddress', currentWalletAddress) + commit('isCosignatoryMode', !currentSignerAddress.equals(currentWalletAddress)) + commit('currentSigner', signers.find(s => s.address.equals(currentSignerAddress))) + commit('signers', signers) + commit('knownWallets', currentAccount.wallets) + commit('knownAddresses', knownAddresses) + commit('accountsInfo', accountsInfo) + commit('multisigAccountsInfo', multisigAccountsInfo) + commit('currentWalletMultisigInfo', currentWalletMultisigInfo) + commit('currentSignerMultisigInfo', currentSignerMultisigInfo) - commit('isCosignatoryMode', isCosignatory) // setting current signer should not fetch ALL data - const detailOpts = { - skipTransactions: true, - skipMultisig: true, - skipOwnedAssets: false, - } + await dispatch('REST_FETCH_WALLET_DETAILS', { + address: currentSignerAddress.plain(), options: { + skipTransactions: true, + }, + }) - if (!options || !options.skipDetails) { - await dispatch('REST_FETCH_WALLET_DETAILS', {address: address.plain(), options: detailOpts}) - } + dispatch('namespace/SIGNER_CHANGED', {}, {root: true}) + dispatch('mosaic/SIGNER_CHANGED', {}, {root: true}) }, + SET_KNOWN_WALLETS({commit}, wallets: string[]) { - commit('setKnownWallets', wallets) - }, - RESET_BALANCES({dispatch}, which) { - if (!which) which = 'currentWalletMosaics' - dispatch('SET_BALANCES', {which, mosaics: []}) + commit('knownWallets', wallets) }, - SET_BALANCES({commit, rootGetters}, {mosaics, which}) { - // if no mosaics, set the mosaics to 0 networkCurrency for reactivity purposes - if (!mosaics.length) { - const networkMosaic = rootGetters['mosaic/networkMosaic'] - const defaultMosaic = new Mosaic(networkMosaic, UInt64.fromUint(0)) - commit(which, [defaultMosaic]) - return - } - commit(which, mosaics) - }, - RESET_SUBSCRIPTIONS({commit}) { - commit('setSubscriptions', []) - }, RESET_TRANSACTIONS({commit}) { commit('confirmedTransactions', []) commit('unconfirmedTransactions', []) commit('partialTransactions', []) }, - RESET_MULTISIG({commit}) { - commit('setKnownMultisigInfo', {}) - }, + ADD_COSIGNATURE({commit, getters}, cosignatureMessage) { if (!cosignatureMessage || !cosignatureMessage.parentHash) { throw Error('Missing mandatory field \'parentHash\' for action wallet/ADD_COSIGNATURE.') @@ -564,7 +415,8 @@ export default { // return if no transactions if (!transactions.length) return - const index = transactions.findIndex(t => t.transactionInfo.hash === cosignatureMessage.parentHash) + const index = transactions.findIndex( + t => t.transactionInfo.hash === cosignatureMessage.parentHash) // partial tx unknown, @TODO: handle this case (fetch partials) if (index === -1) return @@ -572,6 +424,7 @@ export default { transactions[index] = transactions[index].addCosignatures(cosignatureMessage) commit('partialTransactions', transactions) }, + ADD_TRANSACTION({commit, getters}, transactionMessage) { if (!transactionMessage || !transactionMessage.group) { throw Error('Missing mandatory field \'group\' for action wallet/ADD_TRANSACTION.') @@ -587,7 +440,8 @@ export default { // register transaction const transactions = getters[transactionGroup] - const findTx = transactions.find(t => t.transactionInfo.hash === transaction.transactionInfo.hash) + const findTx = transactions.find( + t => t.transactionInfo.hash === transaction.transactionInfo.hash) if (findTx === undefined) { transactions.push(transaction) } @@ -597,7 +451,6 @@ export default { } // update state - // commit('addTransactionToCache', {hash: transaction.transactionInfo.hash, transaction}) commit(transactionGroup, transactions) return commit('transactionHashes', hashes) }, @@ -644,12 +497,12 @@ export default { commit('setStagedTransactions', []) }, /** - * Websocket API - */ + * Websocket API + */ // Subscribe to latest account transactions. - async SUBSCRIBE({ commit, dispatch, rootGetters }, address) { + async SUBSCRIBE({commit, dispatch, rootGetters}, address) { if (!address || !address.length) { - return + return } // use RESTService to open websocket channel subscriptions @@ -665,12 +518,12 @@ export default { }, // Unsubscribe from all open websocket connections - async UNSUBSCRIBE({ dispatch, getters }, address) { + async UNSUBSCRIBE({dispatch, getters}, address) { const subscriptions = getters.getSubscriptions const currentWallet = getters.currentWallet if (!address) { - address = currentWallet.values.get('address') + address = currentWallet.address } const subsByAddress = subscriptions && subscriptions[address] ? subscriptions[address] : [] @@ -689,9 +542,10 @@ export default { dispatch('RESET_SUBSCRIPTIONS', address) }, /** - * REST API - */ - async REST_FETCH_TRANSACTIONS({dispatch, rootGetters}, {group, address, id}) { + * REST API + */ + async REST_FETCH_TRANSACTIONS({dispatch, rootGetters}, + {group, address, id}) { dispatch('app/SET_FETCHING_TRANSACTIONS', true, {root: true}) if (!group || ![ 'partial', 'unconfirmed', 'confirmed' ].includes(group)) { @@ -699,19 +553,19 @@ export default { } if (!address || address.length !== 40) { - return + return } - dispatch( - 'diagnostic/ADD_DEBUG', - `Store action wallet/REST_FETCH_TRANSACTIONS dispatched with : ${JSON.stringify({address: address, group})}`, - {root: true}, - ) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/REST_FETCH_TRANSACTIONS dispatched with : ' + JSON.stringify({ + address: address, + group, + }), {root: true}) try { // prepare REST parameters const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const queryParams = new QueryParams({ pageSize: 100, id }) + const queryParams = new QueryParams({pageSize: 100, id}) const addressObject = Address.createFromRawAddress(address) // fetch transactions from REST gateway @@ -720,21 +574,27 @@ export default { const blockHeights: number[] = [] if ('confirmed' === group) { - transactions = await accountHttp.getAccountTransactions(addressObject, queryParams).toPromise() + transactions = await accountHttp.getAccountTransactions(addressObject, queryParams) + .toPromise() // - record block height to be fetched - transactions.map(transaction => blockHeights.push(transaction.transactionInfo.height.compact())) + transactions.map( + transaction => blockHeights.push(transaction.transactionInfo.height.compact())) + } else if ('unconfirmed' === group) { + transactions = await accountHttp.getAccountUnconfirmedTransactions(addressObject, + queryParams).toPromise() + } else if ('partial' === group) { + transactions = await accountHttp.getAccountPartialTransactions(addressObject, queryParams) + .toPromise() } - else if ('unconfirmed' === group) - {transactions = await accountHttp.getAccountUnconfirmedTransactions(addressObject, queryParams).toPromise()} - else if ('partial' === group) - {transactions = await accountHttp.getAccountPartialTransactions(addressObject, queryParams).toPromise()} - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_TRANSACTIONS numTransactions: ${transactions.length}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/REST_FETCH_TRANSACTIONS numTransactions: ' + transactions.length, + {root: true}) // update store for (let i = 0, m = transactions.length; i < m; i ++) { const transaction = transactions[i] - await dispatch('ADD_TRANSACTION', { address, group, transaction }) + await dispatch('ADD_TRANSACTION', {address, group, transaction}) } // fetch block information if necessary @@ -744,264 +604,31 @@ export default { } return transactions - } - catch (e) { - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch transactions: ${e}`, {root: true}) + } catch (e) { + dispatch('diagnostic/ADD_ERROR', + 'An error happened while trying to fetch transactions: ' + e, {root: true}) return false } finally { dispatch('app/SET_FETCHING_TRANSACTIONS', false, {root: true}) } }, - async REST_FETCH_BALANCES({dispatch}, address) { - if (!address || address.length !== 40) { - return - } - - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_BALANCES dispatched with : ${address}`, {root: true}) - try { - const accountInfo = await dispatch('REST_FETCH_INFO', address) - return accountInfo.mosaics - } - catch(e) { - return [] - } - }, - async REST_FETCH_INFO({commit, dispatch, getters, rootGetters}, address) { - if (!address || address.length !== 40) { - return - } - - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_INFO dispatched with : ${JSON.stringify({address: address})}`, {root: true}) - - const currentWallet = getters.currentWallet - const currentSigner = getters.currentSigner - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - - try { - // prepare REST parameters - const addressObject = Address.createFromRawAddress(address) - - // fetch account info from REST gateway - const accountHttp = repositoryFactory.createAccountRepository() - const accountInfo = await accountHttp.getAccountInfo(addressObject).toPromise() - commit('addKnownWalletsInfo', accountInfo) - - // update current wallet state if necessary - if (currentWallet && address === getters.currentWalletAddress.plain()) { - dispatch('SET_BALANCES', {mosaics: accountInfo.mosaics, which: 'currentWalletMosaics'}) - } - // update current signer state if not current wallet - else if (currentSigner && address === getters.currentSignerAddress.plain()) { - dispatch('SET_BALANCES', {mosaics: accountInfo.mosaics, which: 'currentSignerMosaics'}) - } - - return accountInfo - } - catch (e) { - if (!!currentWallet && address === getters.currentWalletAddress.plain()) { - dispatch('SET_BALANCES', {mosaics: [], which: 'currentWalletMosaics'}) - } - else if (!!getters.currentSigner && address === getters.currentSignerAddress.plain()) { - dispatch('SET_BALANCES', {mosaics: [], which: 'currentSignerMosaics'}) - } - - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch account information: ${e}`, {root: true}) - return false - } - }, - async REST_FETCH_INFOS({commit, dispatch, getters, rootGetters}, addresses: Address[]): Promise { - dispatch( - 'diagnostic/ADD_DEBUG', - `Store action wallet/REST_FETCH_INFOS dispatched with : ${JSON.stringify(addresses.map(a => a.plain()))}`, - {root: true}, - ) - // read store - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - - try { - // fetch account info from REST gateway - const accountHttp = repositoryFactory.createAccountRepository() - const accountsInfo = await accountHttp.getAccountsInfo(addresses).toPromise() - - // add accounts to the store - accountsInfo.forEach(info => commit('addKnownWalletsInfo', info)) - - // if no current wallet address is available, skip and return accountsInfo - // (used in account import process) - if (!getters.currentWalletAddress) return accountsInfo - - // set current wallet info - const currentWalletInfo = accountsInfo.find( - info => info.address.equals(getters.currentWalletAddress), - ) - - if (currentWalletInfo !== undefined) { - dispatch('SET_BALANCES', {mosaics: currentWalletInfo.mosaics, which: 'currentWalletMosaics'}) - } - - // .. or set current signer info - const currentSignerInfo = accountsInfo.find( - info => info.address.equals(getters.currentSignerAddress), - ) - - if (currentSignerInfo !== undefined) { - dispatch('SET_BALANCES', {mosaics: currentWalletInfo.mosaics, which: 'currentSignerMosaics'}) - } - - // return accounts info - return accountsInfo - } - catch (e) { - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch accounts information: ${e}`, {root: true}) - throw new Error(e) - } - }, - async REST_FETCH_MULTISIG({commit, dispatch, rootGetters}, address): Promise { - if (!address || address.length !== 40) { - return - } - - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_MULTISIG dispatched with : ${address}`, {root: true}) - - // read store - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - - try { - // prepare REST parameters - const addressObject = Address.createFromRawAddress(address) - - // fetch multisig graph info from REST gateway - const multisigHttp = repositoryFactory.createMultisigRepository() - const multisigGraphInfo = await multisigHttp.getMultisigAccountGraphInfo(addressObject).toPromise() - - // extract all available multisigInfo from multisigGraphInfo - const multisigsInfo = MultisigService.getMultisigInfoFromMultisigGraphInfo(multisigGraphInfo) - - // store multisig info - multisigsInfo.forEach(multisigInfo => commit('addKnownMultisigInfo', multisigInfo)) - } - catch (e) { - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch multisig information: ${e}`, {root: true}) - return - } - }, - async REST_FETCH_OWNED_MOSAICS( - {commit, dispatch, getters, rootGetters}, - address, - ): Promise { - if (!address || address.length !== 40) return - - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_OWNED_MOSAICS dispatched with : ${address}`, {root: true}) - - // read store - const currentWallet = getters['currentWallet'] - const currentSigner = getters['currentSigner'] - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - try { - // prepare REST parameters - const addressObject = Address.createFromRawAddress(address) - - // fetch account info from REST gateway - const mosaicHttp = repositoryFactory.createMosaicRepository() - const ownedMosaics = await mosaicHttp.getMosaicsFromAccount(addressObject).toPromise() - - // store multisig info - if (currentWallet && address === currentWallet.values.get('address')) { - commit('currentWalletOwnedMosaics', ownedMosaics) - } - else if (currentSigner && address === getters.currentSignerAddress.plain()) { - commit('currentSignerOwnedMosaics', ownedMosaics) - } - - return ownedMosaics - } - catch (e) { - if (currentWallet && currentWallet.values.get('address') === address) { - commit('currentWalletOwnedMosaics', []) - } - else if (currentSigner && address === getters.currentSignerAddress.plain()) { - commit('currentSignerOwnedMosaics', []) - } - - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch owned mosaics: ${e}`, {root: true}) - return null - } - }, - async REST_FETCH_OWNED_NAMESPACES({commit, dispatch, getters, rootGetters}, address): Promise { - // @TODO: This method should be called by NamespaceService, like NamespaceService.fetchNamespaceInfo - // To be fixed that along with "Owned" namespaces getters (see below) - if (!address || address.length !== 40) { - return - } - - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_FETCH_OWNED_NAMESPACES dispatched with : ${address}`, {root: true}) - - // read store - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const currentWallet = getters['currentWallet'] - const currentSigner = getters['currentSigner'] - - try { - // prepare REST parameters - const addressObject = Address.createFromRawAddress(address) - - // fetch account info from REST gateway - const namespaceHttp = repositoryFactory.createNamespaceRepository() - - // @TODO: Handle more than 100 namespaces - const ownedNamespaces = await namespaceHttp.getNamespacesFromAccount( - addressObject, new QueryParams({pageSize: 100, order: Order.ASC}), - ).toPromise() - - // return if no namespace found - if (!ownedNamespaces.length) return - - // update namespaces in database - new NamespaceService(this).updateNamespaces(ownedNamespaces) - - // update namespaces info in the store - dispatch('namespace/ADD_NAMESPACE_INFOS', ownedNamespaces, { root: true }) - - // store multisig info - // @TODO: all namespaces should be stored in the same object - // "Owned" namespaces should be returned from it with a filter on the owner property - if (currentWallet && currentWallet.values.get('address') === address) { - commit('currentWalletOwnedNamespaces', ownedNamespaces) - } - else if (currentSigner && address === getters.currentSignerAddress.plain()) { - commit('currentSignerOwnedNamespaces', ownedNamespaces) - } - - return ownedNamespaces - } - catch (e) { - if (currentWallet && currentWallet.values.get('address') === address) { - commit('currentWalletOwnedNamespaces', []) - } - else if (currentSigner && address === getters.currentSignerAddress.plain()) { - commit('currentSignerOwnedNamespaces', []) - } - - dispatch('diagnostic/ADD_ERROR', `An error happened while trying to fetch owned namespaces: ${e}`, {root: true}) - return null - } - }, async REST_ANNOUNCE_PARTIAL( {commit, dispatch, rootGetters}, {issuer, signedLock, signedPartial}, ): Promise { if (!issuer || issuer.length !== 40) { - return + return } - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_ANNOUNCE_PARTIAL dispatched with: ${JSON.stringify({ - issuer: issuer, - signedLockHash: signedLock.hash, - signedPartialHash: signedPartial.hash, - })}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/REST_ANNOUNCE_PARTIAL dispatched with: ' + JSON.stringify({ + issuer: issuer, + signedLockHash: signedLock.hash, + signedPartialHash: signedPartial.hash, + }), {root: true}) try { // - prepare REST parameters @@ -1034,8 +661,7 @@ export default { }, ) }) - } - catch(e) { + } catch (e) { return new BroadcastResult(signedPartial, false, e.toString()) } }, @@ -1043,10 +669,11 @@ export default { {commit, dispatch, rootGetters}, signedTransaction: SignedTransaction, ): Promise { - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_ANNOUNCE_TRANSACTION dispatched with: ${JSON.stringify({ - hash: signedTransaction.hash, - payload: signedTransaction.payload, - })}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/REST_ANNOUNCE_TRANSACTION dispatched with: ' + JSON.stringify({ + hash: signedTransaction.hash, + payload: signedTransaction.payload, + }), {root: true}) try { // prepare REST parameters @@ -1057,22 +684,20 @@ export default { await transactionHttp.announce(signedTransaction) commit('removeSignedTransaction', signedTransaction) return new BroadcastResult(signedTransaction, true) - } - catch(e) { + } catch (e) { commit('removeSignedTransaction', signedTransaction) return new BroadcastResult(signedTransaction, false, e.toString()) } }, - async REST_ANNOUNCE_COSIGNATURE( - {dispatch, rootGetters}, - cosignature: CosignatureSignedTransaction, - ): Promise { + async REST_ANNOUNCE_COSIGNATURE({dispatch, rootGetters}, cosignature: CosignatureSignedTransaction): + Promise { - dispatch('diagnostic/ADD_DEBUG', `Store action wallet/REST_ANNOUNCE_COSIGNATURE dispatched with: ${JSON.stringify({ - hash: cosignature.parentHash, - signature: cosignature.signature, - signerPublicKey: cosignature.signerPublicKey, - })}`, {root: true}) + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/REST_ANNOUNCE_COSIGNATURE dispatched with: ' + JSON.stringify({ + hash: cosignature.parentHash, + signature: cosignature.signature, + signerPublicKey: cosignature.signerPublicKey, + }), {root: true}) try { // prepare REST parameters @@ -1082,8 +707,7 @@ export default { // prepare symbol-sdk TransactionService await transactionHttp.announceAggregateBondedCosignature(cosignature) return new BroadcastResult(cosignature, true) - } - catch(e) { + } catch (e) { return new BroadcastResult(cosignature, false, e.toString()) } }, @@ -1098,15 +722,12 @@ export default { // instantiate a dispatcher const dispatcher = new RESTDispatcher(dispatch) - // always refresh wallet balances - dispatcher.add('REST_FETCH_INFO', plainAddress) // extract transaction types from the transaction - const transactionTypes: TransactionType[] = transaction instanceof AggregateTransaction + const transactionTypes: TransactionType[] = _.uniq(transaction instanceof AggregateTransaction ? transaction.innerTransactions .map(({type}) => type) - .filter((el, i, a) => i === a.indexOf(el)) - : [transaction.type] + : [transaction.type]) // add actions to the dispatcher according to the transaction types if ([ @@ -1114,14 +735,13 @@ export default { TransactionType.MOSAIC_ALIAS, TransactionType.ADDRESS_ALIAS, ].some(a => transactionTypes.some(b => b === a))) { - dispatcher.add('REST_FETCH_OWNED_NAMESPACES', plainAddress) + dispatch('namespace/LOAD_NAMESPACES', {}, {root: true}) } - if ([ TransactionType.MOSAIC_DEFINITION, TransactionType.MOSAIC_SUPPLY_CHANGE, ].some(a => transactionTypes.some(b => b === a))) { - dispatcher.add('REST_FETCH_OWNED_MOSAICS', plainAddress) + dispatch('mosaic/LOAD_MOSAICS', {}, {root: true}) } if (transactionTypes.includes(TransactionType.MULTISIG_ACCOUNT_MODIFICATION)) { diff --git a/src/store/index.ts b/src/store/index.ts index ee0c446ca..4eeff191d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,16 +28,16 @@ import NamespaceStore from '@/store/Namespace' import StatisticsStore from '@/store/Statistics' import CommunityStore from '@/store/Community' import {onPeerConnection} from '@/store/plugins/onPeerConnection' - // use AwaitLock for initialization routines import {AwaitLock} from '@/store/AwaitLock' + const Lock = AwaitLock.create() Vue.use(Vuex) /** * Application Store - * + * * This store initializes peer connection */ const AppStore = new Vuex.Store({ @@ -60,22 +60,22 @@ const AppStore = new Vuex.Store({ onPeerConnection, ], actions: { - async initialize({ dispatch, getters }) { + async initialize({dispatch, getters}) { const callback = async () => { await dispatch('app/initialize') await dispatch('db/initialize') await dispatch('diagnostic/initialize') await dispatch('notification/initialize') - await dispatch('network/initialize', getters['db/feed']) - await dispatch('mosaic/initialize', getters['db/feed']) - await dispatch('namespace/initialize', getters['db/feed']) + await dispatch('network/initialize') + await dispatch('mosaic/initialize') + await dispatch('namespace/initialize') } // aquire async lock until initialized await Lock.initialize(callback, {getters}) }, // Uninitialize the stores (call on app destroyed). - async uninitialize({ dispatch }) { + async uninitialize({dispatch}) { await Promise.all([ dispatch('app/uninitialize'), dispatch('network/uninitialize'), diff --git a/src/store/plugins/onPeerConnection.ts b/src/store/plugins/onPeerConnection.ts index b17352563..06f534df2 100644 --- a/src/store/plugins/onPeerConnection.ts +++ b/src/store/plugins/onPeerConnection.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,7 +24,10 @@ export const onPeerConnection = store => { if (!!currentWallet) { console.log('onPeerConnection dispatching wallet actions..') - store.dispatch('wallet/REST_FETCH_INFO', currentWallet.objects.address.plain()) + + // store.dispatch('wallet/REST_FETCH_TRANSACTIONS') + store.dispatch('mosaic/LOAD_MOSAICS') + store.dispatch('namespace/LOAD_NAMESPACES') } } }) diff --git a/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts b/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts index 6fa25123a..4f74b94ac 100644 --- a/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts +++ b/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,14 +16,11 @@ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {NetworkType, Password} from 'symbol-sdk' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' import {NotificationType} from '@/core/utils/NotificationType' import {AccountService} from '@/services/AccountService' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {AccountsModel} from '@/core/database/entities/AccountsModel' - +import {AccountModel} from '@/core/database/entities/AccountModel' // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -34,7 +31,8 @@ import FormWrapper from '@/components/FormWrapper/FormWrapper.vue' import FormRow from '@/components/FormRow/FormRow.vue' /// region custom types -type NetworkNodeEntry = {value: NetworkType, label: string} +type NetworkNodeEntry = { value: NetworkType, label: string } + /// end-region custom types @Component({ @@ -45,10 +43,12 @@ type NetworkNodeEntry = {value: NetworkType, label: string} FormWrapper, FormRow, }, - computed: {...mapGetters({ - generationHash: 'network/generationHash', - currentAccount: 'account/currentAccount', - })}, + computed: { + ...mapGetters({ + generationHash: 'network/generationHash', + currentAccount: 'account/currentAccount', + }), + }, }) export class FormAccountCreationTs extends Vue { /** @@ -56,7 +56,7 @@ export class FormAccountCreationTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Currently active network type @@ -67,9 +67,9 @@ export class FormAccountCreationTs extends Vue { /** * Accounts repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accountsRepository = new AccountsRepository() + public accountService = new AccountService() /** * Validation rules @@ -101,11 +101,11 @@ export class FormAccountCreationTs extends Vue { ] /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } @@ -114,6 +114,7 @@ export class FormAccountCreationTs extends Vue { get nextPage() { return this.$route.meta.nextPage } + /// end-region computed properties getter/setter /** @@ -131,29 +132,27 @@ export class FormAccountCreationTs extends Vue { /** * Persist created account and redirect to next step - * @return {void} + * @return {void} */ private persistAccountAndContinue() { // - password stored as hash (never plain.) - const service = new AccountService(this.$store) - const passwordHash = service.getPasswordHash(new Password(this.formItems.password)) - - // - populate model - const model = new AccountsModel(new Map([ - [ 'accountName', this.formItems.accountName ], - [ 'wallets', [] ], - [ 'password', passwordHash ], - [ 'hint', this.formItems.hint ], - [ 'networkType', this.formItems.networkType ], - [ 'seed', '' ], - [ 'generationHash', this.generationHash ], - ])) - + const passwordHash = AccountService.getPasswordHash(new Password(this.formItems.password)) + + + const account: AccountModel = { + accountName: this.formItems.accountName, + wallets: [], + seed: '', + password: passwordHash, + hint: this.formItems.hint, + networkType: this.formItems.networkType, + generationHash: this.generationHash, + } // use repository for storage - this.accountsRepository.create(model.values) + this.accountService.saveAccount(account) // execute store actions - this.$store.dispatch('account/SET_CURRENT_ACCOUNT', model) + this.$store.dispatch('account/SET_CURRENT_ACCOUNT', account) this.$store.dispatch('temporary/SET_PASSWORD', this.formItems.password) this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) diff --git a/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts b/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts index 36b6df66c..4deaa197e 100644 --- a/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts +++ b/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,13 +16,9 @@ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {Password} from 'symbol-sdk' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {AccountsModel} from '@/core/database/entities/AccountsModel' import {AccountService} from '@/services/AccountService' -import {AccountsRepository} from '@/repositories/AccountsRepository' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -33,7 +29,8 @@ import FormWrapper from '@/components/FormWrapper/FormWrapper.vue' import FormRow from '@/components/FormRow/FormRow.vue' // @ts-ignore import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' -import { NotificationType } from '@/core/utils/NotificationType' +import {NotificationType} from '@/core/utils/NotificationType' +import {AccountModel} from '@/core/database/entities/AccountModel' @Component({ components: { @@ -44,9 +41,11 @@ import { NotificationType } from '@/core/utils/NotificationType' FormRow, ModalFormAccountUnlock, }, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - })}, + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + }), + }, }) export class FormAccountPasswordUpdateTs extends Vue { /** @@ -54,7 +53,7 @@ export class FormAccountPasswordUpdateTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Validation rules @@ -79,11 +78,11 @@ export class FormAccountPasswordUpdateTs extends Vue { } /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } @@ -96,6 +95,7 @@ export class FormAccountPasswordUpdateTs extends Vue { public set hasAccountUnlockModal(f: boolean) { this.isUnlockingAccount = f } + /// end-region computed properties getter/setter /** @@ -110,31 +110,23 @@ export class FormAccountPasswordUpdateTs extends Vue { this.$refs.observer.reset() }) } + /** * When account is unlocked, the sub wallet can be created */ public async onAccountUnlocked() { try { - const service = new AccountService(this.$store) - const repository = new AccountsRepository() + const accountService = new AccountService() // - create new password hash - const passwordHash = service.getPasswordHash(new Password(this.formItems.password)) - this.currentAccount.values.set('password', passwordHash) - this.currentAccount.values.set('hint', this.formItems.passwordHint) - - // - update in storage - repository.update( - this.currentAccount.getIdentifier(), - this.currentAccount.values, - ) + const passwordHash = AccountService.getPasswordHash(new Password(this.formItems.password)) + accountService.updatePassword(this.currentAccount, passwordHash, this.formItems.passwordHint) // - update state and finalize this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.SUCCESS_PASSWORD_CHANGED) this.$store.dispatch('account/LOG_OUT') this.$router.push({name: 'accounts.login'}) - } - catch (e) { + } catch (e) { this.$store.dispatch('notification/ADD_ERROR', 'An error happened, please try again.') console.error(e) } diff --git a/src/views/forms/FormAccountUnlock/FormAccountUnlockTs.ts b/src/views/forms/FormAccountUnlock/FormAccountUnlockTs.ts index 353a7b8fe..538d2a821 100644 --- a/src/views/forms/FormAccountUnlock/FormAccountUnlockTs.ts +++ b/src/views/forms/FormAccountUnlock/FormAccountUnlockTs.ts @@ -1,27 +1,24 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Account, Password, EncryptedPrivateKey, NetworkType} from 'symbol-sdk' +import {Account, EncryptedPrivateKey, NetworkType, Password} from 'symbol-sdk' import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' - // child components import {ValidationProvider} from 'vee-validate' // @ts-ignore @@ -38,11 +35,12 @@ import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' FormRow, ErrorTooltip, }, - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - currentWallet: 'wallet/currentWallet', - })}, + computed: { + ...mapGetters({ + networkType: 'network/networkType', + currentWallet: 'wallet/currentWallet', + }), + }, }) export class FormAccountUnlockTs extends Vue { /** @@ -51,17 +49,11 @@ export class FormAccountUnlockTs extends Vue { */ public networkType: NetworkType - /** - * Currently active account - * @var {AccountsModel} - */ - public currentAccount: AccountsModel - /** * Currently active wallet - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Validation rules @@ -77,6 +69,9 @@ export class FormAccountUnlockTs extends Vue { password: '', } + /// region computed properties getter/setter + /// end-region computed properties getter/setter + /** * Attempt decryption of private key to unlock * account. @@ -85,8 +80,8 @@ export class FormAccountUnlockTs extends Vue { public processVerification() { // - create encrypted payload for active wallet const encrypted = new EncryptedPrivateKey( - this.currentWallet.values.get('encPrivate'), - this.currentWallet.values.get('encIv'), + this.currentWallet.encPrivate, + this.currentWallet.encIv, ) try { @@ -100,8 +95,7 @@ export class FormAccountUnlockTs extends Vue { } return this.$emit('error', this.$t('error_invalid_password')) - } - catch(e) { + } catch (e) { this.$emit('error', e) } } diff --git a/src/views/forms/FormAliasTransaction/FormAliasTransactionTs.ts b/src/views/forms/FormAliasTransaction/FormAliasTransactionTs.ts index c1dc07e93..cdab80bcc 100644 --- a/src/views/forms/FormAliasTransaction/FormAliasTransactionTs.ts +++ b/src/views/forms/FormAliasTransaction/FormAliasTransactionTs.ts @@ -1,34 +1,28 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import { - AliasTransaction, NamespaceId, MosaicId, Address, AliasAction, AliasType, - AddressAliasTransaction, MosaicAliasTransaction, MosaicInfo, NamespaceInfo, - UInt64, Mosaic, -} from 'symbol-sdk' +import {Address, AddressAliasTransaction, AliasAction, AliasTransaction, AliasType, Mosaic, MosaicAliasTransaction, MosaicId, NamespaceId, UInt64} from 'symbol-sdk' import {Component, Prop} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' import {FormTransactionBase} from '../FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' import {ViewAliasTransaction} from '@/core/transactions/ViewAliasTransaction' - // child components -import {ValidationProvider, ValidationObserver} from 'vee-validate' +import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore import FormWrapper from '@/components/FormWrapper/FormWrapper.vue' // @ts-ignore @@ -47,6 +41,8 @@ import MaxFeeSelector from '@/components/MaxFeeSelector/MaxFeeSelector.vue' import ModalTransactionConfirmation from '@/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue' // @ts-ignore import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' +import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' @Component({ components: { @@ -62,35 +58,33 @@ import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' ModalTransactionConfirmation, MaxFeeAndSubmit, }, - computed: {...mapGetters({ - ownedNamespaces: 'wallet/currentWalletOwnedNamespaces', - ownedMosaics: 'wallet/currentWalletOwnedMosaics', - mosaicsNamesByHex: 'mosaic/mosaicsNames', - currentHeight: 'network/currentHeight', - })}, + computed: { + ...mapGetters({ + namespaces: 'namespace/ownedNamespaces', + mosaics: 'mosaic/ownedMosaics', + currentHeight: 'network/currentHeight', + }), + }, }) export class FormAliasTransactionTs extends FormTransactionBase { - @Prop({ default: null }) namespaceId: NamespaceId - @Prop({ default: null }) aliasTarget: MosaicId | Address - @Prop({ default: null, required: true }) aliasAction: AliasAction - @Prop({ default: false }) disableSubmit: boolean + @Prop({default: null}) namespaceId: NamespaceId + @Prop({default: null}) aliasTarget: MosaicId | Address + @Prop({default: null, required: true}) aliasAction: AliasAction + @Prop({default: false}) disableSubmit: boolean /** * Alias action - * @type {AliasAction[]} * @protected */ protected AliasAction = AliasAction /** * Validation rules - * @var {ValidationRuleset} */ protected validationRules = ValidationRuleset /** * Form items - * @var {any} */ protected formItems = { namespaceFullName: null, @@ -109,23 +103,14 @@ export class FormAliasTransactionTs extends FormTransactionBase { /** * Current wallet owned namespaces * @private - * @type {NamespaceInfo[]} */ - private ownedNamespaces: NamespaceInfo[] + private namespaces: NamespaceModel[] /** * Current wallet owned mosaics * @private - * @type {MosaicInfo[]} */ - private ownedMosaics: MosaicInfo[] - - /** - * Mosaics names by hex - * @private - * @type {Record} - */ - private mosaicsNamesByHex: Record + private mosaics: MosaicModel[] /** * Current network height @@ -140,30 +125,30 @@ export class FormAliasTransactionTs extends FormTransactionBase { * @protected * @type {string []} */ - protected get linkableNamespaces(): NamespaceInfo[] { - return this.ownedNamespaces.filter( - ({alias}) => alias && alias.type === AliasType.None, + protected get linkableNamespaces(): NamespaceModel[] { + return this.namespaces.filter( + ({aliasType}) => aliasType === AliasType.None, ) } - + /** * Current wallet mosaics hex Ids that can be linked - * @readonly + * @readonly * @protected * @type {Mosaic} */ protected get linkableMosaics(): Mosaic[] { - return this.ownedMosaics + return this.mosaics .filter((mosaicInfo) => { - // no mosaics with names - const mosaicName = this.mosaicsNamesByHex[mosaicInfo.id.toHex()] + // no mosaics with names + const mosaicName = mosaicInfo.name if (mosaicName && mosaicName.length) return false // mosaics must not be expired - if (mosaicInfo.duration.compact() == 0) return true - return mosaicInfo.height.compact() + mosaicInfo.duration.compact() > this.currentHeight + if (mosaicInfo.duration == 0) return true + return mosaicInfo.height + mosaicInfo.duration > this.currentHeight }) - .map(({id}) => new Mosaic(id, UInt64.fromUint(0))) + .map(({mosaicIdHex}) => new Mosaic(new MosaicId(mosaicIdHex), UInt64.fromUint(0))) } /** @@ -174,12 +159,9 @@ export class FormAliasTransactionTs extends FormTransactionBase { // - re-populate form if transaction staged // if (this.stagedTransactions.length) { // const transaction = this.stagedTransactions.find( - // staged => staged.type === TransactionType.MOSAIC_ALIAS || staged.type === TransactionType.ADDRESS_ALIAS, - // ) - // this.setTransactions([transaction as AliasTransaction]) - // this.isAwaitingSignature = true - // return - // } + // staged => staged.type === TransactionType.MOSAIC_ALIAS || staged.type === + // TransactionType.ADDRESS_ALIAS, ) this.setTransactions([transaction as AliasTransaction]) + // this.isAwaitingSignature = true return } /** * Helper function to get the alias target as a string @@ -232,7 +214,7 @@ export class FormAliasTransactionTs extends FormTransactionBase { aliasAction: this.formItems.aliasAction, maxFee: UInt64.fromUint(this.formItems.maxFee), }) - + // - prepare transaction return [this.factory.build(view)] } catch (error) { @@ -264,7 +246,7 @@ export class FormAliasTransactionTs extends FormTransactionBase { this.formItems.aliasTarget = transaction.namespaceId.toHex() this.formItems.aliasAction = transaction.aliasAction } - + // - populate maxFee this.formItems.maxFee = transaction.maxFee.compact() } diff --git a/src/views/forms/FormDefaults.ts b/src/views/forms/FormDefaults.ts deleted file mode 100644 index a17f35102..000000000 --- a/src/views/forms/FormDefaults.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright 2020 NEM Foundation (https://nem.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {NetworkType, MosaicSupplyChangeAction} from 'symbol-sdk' - -// configuration -import networkConfig from '@/../config/network.conf.json' - -export const formDataConfig = { - settingPassword: { - previousPassword: '', - newPassword: '', - confirmPassword: '', - cipher: '', - }, - createAccountForm: { - accountName: '', - password: '', - passwordAgain: '', - hint: '', - }, - importKeystoreConfig: { - walletName: 'keystore-wallet', - keystoreStr: '', - keystorePassword: '', - }, - transferForm: { - recipient: '', - remark: '', - multisigPublicKey: '', - feeSpeed: 'NORMAL', - mosaicTransferList: [], - isEncrypted: true, - }, - remoteForm: { - remotePublicKey: '', - feeSpeed: 'NORMAL', - password: '', - }, - mosaicAliasForm: { - mosaicName: '', - feeSpeed: 'NORMAL', - password: '', - }, - mosaicEditForm: { - delta: 1, - supplyType: MosaicSupplyChangeAction.Increase, - feeSpeed: 'NORMAL', - }, - mosaicUnAliasForm: { - feeSpeed: 'NORMAL', - password: '', - }, - addressAliasForm: { - address: '', - feeSpeed: 'NORMAL', - password: '', - }, - alias: { - feeSpeed: 'NORMAL', - password: '', - }, - mosaicTransactionForm: { - restrictable: false, - supply: 500000000, - divisibility: 0, - transferable: true, - supplyMutable: true, - permanent: true, - duration: 1000, - feeSpeed: 'NORMAL', - multisigPublicKey: '', - }, - multisigConversionForm: { - minApproval: 1, - minRemoval: 1, - feeSpeed: 'NORMAL', - multisigPublicKey: '', - }, - multisigModificationForm: { - minApproval: 0, - minRemoval: 0, - feeSpeed: 'NORMAL', - multisigPublicKey: '', - }, - namespaceEditForm: { - name: '', - duration: 1000, - feeSpeed: 'NORMAL', - }, - rootNamespaceForm: { - duration: networkConfig.networks['testnet-publicTest'].properties.maxNamespaceDuration, - rootNamespaceName: '', - multisigPublicKey: '', - feeSpeed: 'NORMAL', - }, - subNamespaceForm: { - rootNamespaceName: '', - subNamespaceName: '', - multisigPublicKey: '', - feeSpeed: 'NORMAL', - }, - walletImportMnemonicForm: { - mnemonic: '', - walletName: '', - }, - walletImportPrivateKeyForm: { - privateKey: '', - walletName: 'wallet-privateKey', - }, - trezorImportForm: { - networkType: NetworkType.MIJIN_TEST, - accountIndex: 0, - walletName: 'Trezor Wallet', - }, - walletCreateForm: { - walletName: 'wallet-create', - path: 0, - }, -} diff --git a/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts b/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts index 1e4061471..4f323f9a9 100644 --- a/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts +++ b/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,38 +20,28 @@ import {mapGetters} from 'vuex' // internal dependencies import {FormNamespaceRegistrationTransactionTs} from '../FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs' import {NamespaceId} from 'symbol-sdk' -import {TimeHelpers} from '@/core/utils/TimeHelpers' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' - // configuration -import networkConfig from '@/../config/network.conf.json' -import {NamespacesModel} from '@/core/database/entities/NamespacesModel' -import {NamespaceService} from '@/services/NamespaceService' - // child components // @ts-ignore import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import ModalTransactionConfirmation from '@/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue' +import {NamespaceService} from '@/services/NamespaceService' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' @Component({ components: {ErrorTooltip, ModalTransactionConfirmation}, computed: { ...mapGetters({ currentHeight: 'network/currentHeight', + namespaces: 'namespace/namespaces', }), }, }) export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegistrationTransactionTs { @Prop({default: null, required: true}) namespaceId: NamespaceId - /** - * Namespace grace period duration - * @private - * @type {number} - */ - private namespaceGracePeriodDuration: number = networkConfig.networks['testnet-publicTest'].properties.namespaceGracePeriodDuration - /** * Network current height * @private @@ -59,6 +49,7 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis */ private currentHeight: number + private namespaces: NamespaceModel[] /** * Validation rules * @var {ValidationRuleset} @@ -72,10 +63,8 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis * @type {NamespaceInfo} */ protected get currentNamespaceEndHeight(): number { - // @TODO: Should be read from store - const allNamespaces: NamespacesModel[] = new NamespaceService(this.$store).getNamespaces() - const currentNamespace = allNamespaces.find(model => model.getIdentifier() === this.namespaceId.toHex()) - return currentNamespace.objects.namespaceInfo.endHeight.compact() + const currentNamespace = this.namespaces.find(model => model.namespaceIdHex === this.namespaceId.toHex()) + return currentNamespace && currentNamespace.endHeight || 0 } /** @@ -83,9 +72,8 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis * @readonly * @type {string} */ - protected get currentExpirationInfoView(): {expired: boolean, expiration: string} { - const {expired, expiration} = this.getExpirationInfoFromEndHeight(this.currentNamespaceEndHeight) - return {expired, expiration} + protected get currentExpirationInfoView(): { expired: boolean, expiration: string } { + return this.getExpirationInfoFromEndHeight(this.currentNamespaceEndHeight) } /** @@ -107,7 +95,9 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis * @type {number} */ protected get newDuration(): number { - return this.newEndHeight - this.currentHeight - this.namespaceGracePeriodDuration + return this.newEndHeight - this.currentHeight - + Math.floor((this.networkConfiguration.namespaceGracePeriodDuration + / this.networkConfiguration.blockGenerationTargetTime)) } /** @@ -116,33 +106,16 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis * @type {string} */ protected get newExpirationInfoView(): string { - const {expiration} = this.getExpirationInfoFromEndHeight( - this.newEndHeight, - ) - return expiration + return this.getExpirationInfoFromEndHeight(this.newEndHeight).expiration } - // @TODO: duplicate with NamespaceTableService method /** * Returns a view of a namespace expiration info * @private * @param {NamespaceInfo} mosaicInfo * @returns {string} */ - private getExpirationInfoFromEndHeight( - endHeight: number, - ): {expiration: string, expired: boolean} { - const {currentHeight} = this - const networkConfig = this.$store.getters['network/config'] - const {namespaceGracePeriodDuration} = networkConfig.networks['testnet-publicTest'] - - const expired = currentHeight > endHeight - namespaceGracePeriodDuration - const expiredIn = endHeight - namespaceGracePeriodDuration - currentHeight - const deletedIn = endHeight - currentHeight - const expiration = expired - ? TimeHelpers.durationToRelativeTime(expiredIn) - : TimeHelpers.durationToRelativeTime(deletedIn) - - return {expired, expiration} + private getExpirationInfoFromEndHeight(endHeight: number): { expiration: string, expired: boolean } { + return NamespaceService.getExpiration(this.networkConfiguration, this.currentHeight, endHeight) } } diff --git a/src/views/forms/FormGeneralSettings/FormGeneralSettings.vue b/src/views/forms/FormGeneralSettings/FormGeneralSettings.vue index c35196d16..fd396119b 100644 --- a/src/views/forms/FormGeneralSettings/FormGeneralSettings.vue +++ b/src/views/forms/FormGeneralSettings/FormGeneralSettings.vue @@ -14,7 +14,7 @@ diff --git a/src/views/forms/FormGeneralSettings/FormGeneralSettingsTs.ts b/src/views/forms/FormGeneralSettings/FormGeneralSettingsTs.ts index e035ab572..03376f9e2 100644 --- a/src/views/forms/FormGeneralSettings/FormGeneralSettingsTs.ts +++ b/src/views/forms/FormGeneralSettings/FormGeneralSettingsTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,11 +15,8 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {SettingService} from '@/services/SettingService' import {NotificationType} from '@/core/utils/NotificationType' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -40,6 +37,8 @@ import WalletSelectorField from '@/components/WalletSelectorField/WalletSelector import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' // @ts-ignore import FormLabel from '@/components/FormLabel/FormLabel.vue' +import {SettingsModel} from '@/core/database/entities/SettingsModel' + @Component({ components: { ValidationObserver, @@ -54,47 +53,21 @@ import FormLabel from '@/components/FormLabel/FormLabel.vue' ModalFormAccountUnlock, FormLabel, }, - computed: {...mapGetters({ - currentLanguage: 'app/currentLanguage', - explorerUrl: 'app/explorerUrl', - languageList: 'app/languages', - defaultFee: 'app/defaultFee', - defaultWallet: 'app/defaultWallet', - knownWallets: 'wallet/knownWallets', - })}, + computed: { + ...mapGetters({ + language: 'app/currentLanguage', + settings: 'app/settings', + knownWallets: 'wallet/knownWallets', + }), + }, }) export class FormGeneralSettingsTs extends Vue { - /** - * Currently active language - * @see {Store.AppInfo} - * @var {string} - */ - public currentLanguage: string /** - * List of available languages - * @see {Store.AppInfo} - * @var {any[]} + * The current stored settings. */ - public languageList: {value: string, label: string}[] + public settings: SettingsModel - /** - * Default fee setting - * @var {number} - */ - public defaultFee: number - - /** - * Default wallet setting - * @var {number} - */ - public defaultWallet: string - - /** - * Explorer url setting - * @var {string} - */ - public explorerUrl: string /** * Known wallets identifiers @@ -113,8 +86,8 @@ export class FormGeneralSettingsTs extends Vue { * @var {Object} */ public formItems = { - maxFee: 0, - currentLanguage: '', + defaultFee: 0, + language: '', explorerUrl: '', defaultWallet: '', } @@ -124,13 +97,10 @@ export class FormGeneralSettingsTs extends Vue { } public resetForm() { - this.formItems.currentLanguage = this.currentLanguage - this.formItems.maxFee = this.defaultFee - this.formItems.explorerUrl = this.explorerUrl - this.formItems.defaultWallet = this.defaultWallet && this.defaultWallet.length - ? this.defaultWallet : (this.knownWallets.length - ? this.knownWallets.shift() - : '') + this.formItems = {...this.settings} + if (this.settings.defaultWallet && this.knownWallets.length) { + this.formItems.defaultWallet = this.knownWallets[0] + } } /// region computed properties getter/setter @@ -141,6 +111,7 @@ export class FormGeneralSettingsTs extends Vue { public set hasAccountUnlockModal(f: boolean) { this.isUnlockingAccount = f } + /// end-region computed properties getter/setter /** @@ -150,26 +121,18 @@ export class FormGeneralSettingsTs extends Vue { public onSubmit() { this.hasAccountUnlockModal = true } + /** * When account is unlocked, the sub wallet can be created */ - public onAccountUnlocked() { - try { - // - use service to bridge between database and store - const service = new SettingService(this.$store) - - // - dispatches 3 store actions: - // - app/SET_LANGUAGE - // - app/SET_EXPLORER_URL - // - app/SET_DEFAULT_FEE - // - app/SET_DEFAULT_WALLET - service.saveSettingsForm(this.formItems) + public async onAccountUnlocked() { + try { + await this.$store.dispatch('app/SET_SETTINGS', this.formItems) // - add notification and emit this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.SUCCESS_SETTINGS_UPDATED) this.$emit('submit', this.formItems) - } - catch (e) { + } catch (e) { this.$store.dispatch('notification/ADD_ERROR', 'An error happened, please try again.') console.error(e) } diff --git a/src/views/forms/FormMosaicDefinitionTransaction/FormMosaicDefinitionTransactionTs.ts b/src/views/forms/FormMosaicDefinitionTransaction/FormMosaicDefinitionTransactionTs.ts index fcea88dd0..b6ced71ec 100644 --- a/src/views/forms/FormMosaicDefinitionTransaction/FormMosaicDefinitionTransactionTs.ts +++ b/src/views/forms/FormMosaicDefinitionTransaction/FormMosaicDefinitionTransactionTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,22 +16,27 @@ // external dependencies import { MosaicDefinitionTransaction, - MosaicSupplyChangeTransaction, - MosaicNonce, - MosaicId, MosaicFlags, - UInt64, + MosaicId, + MosaicNonce, MosaicSupplyChangeAction, + MosaicSupplyChangeTransaction, + PublicAccount, Transaction, + UInt64, } from 'symbol-sdk' import {Component} from 'vue-property-decorator' - // internal dependencies -import {ViewMosaicDefinitionTransaction, MosaicDefinitionFormFieldsType} from '@/core/transactions/ViewMosaicDefinitionTransaction' -import {ViewMosaicSupplyChangeTransaction, MosaicSupplyChangeFormFieldsType} from '@/core/transactions/ViewMosaicSupplyChangeTransaction' +import { + MosaicDefinitionFormFieldsType, + ViewMosaicDefinitionTransaction, +} from '@/core/transactions/ViewMosaicDefinitionTransaction' +import { + MosaicSupplyChangeFormFieldsType, + ViewMosaicSupplyChangeTransaction, +} from '@/core/transactions/ViewMosaicSupplyChangeTransaction' import {FormTransactionBase} from '@/views/forms/FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -101,7 +106,7 @@ export class FormMosaicDefinitionTransactionTs extends FormTransactionBase { // } // - set default form values - this.formItems.signerPublicKey = this.currentWallet.values.get('publicKey') + this.formItems.signerPublicKey = this.currentWallet.publicKey this.formItems.supplyMutable = false this.formItems.restrictable = false this.formItems.permanent = false @@ -129,7 +134,7 @@ export class FormMosaicDefinitionTransactionTs extends FormTransactionBase { protected getTransactions(): Transaction[] { this.factory = new TransactionFactory(this.$store) try { - const publicAccount = this.currentSigner + const publicAccount = PublicAccount.createFromPublicKey(this.selectedSigner.publicKey, this.networkType) const randomNonce = MosaicNonce.createRandom() // - read form for definition diff --git a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransaction.vue b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransaction.vue index b17a4478b..39f1ea6d5 100644 --- a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransaction.vue +++ b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransaction.vue @@ -36,7 +36,7 @@
{{ $t('relative') }}: {{ currentMosaicRelativeSupply }} ({{ $t('absolute') }}: - {{ currentMosaicInfo.supply.compact().toLocaleString() }}) + {{ currentMosaicInfo.supply.toLocaleString() }})
diff --git a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts index 664716d53..4681699f6 100644 --- a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts +++ b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,23 +14,14 @@ * limitations under the License. */ // external dependencies -import { - MosaicId, - UInt64, - MosaicSupplyChangeAction, - Transaction, - MosaicInfo, - MosaicSupplyChangeTransaction, -} from 'symbol-sdk' +import {MosaicId, MosaicSupplyChangeAction, MosaicSupplyChangeTransaction, Transaction, UInt64} from 'symbol-sdk' import {Component, Prop} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {ViewMosaicSupplyChangeTransaction, MosaicSupplyChangeFormFieldsType} from '@/core/transactions/ViewMosaicSupplyChangeTransaction' +import {MosaicSupplyChangeFormFieldsType, ViewMosaicSupplyChangeTransaction} from '@/core/transactions/ViewMosaicSupplyChangeTransaction' import {FormTransactionBase} from '@/views/forms/FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -51,6 +42,7 @@ import FormRow from '@/components/FormRow/FormRow.vue' import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' +import {MosaicModel} from '@/core/database/entities/MosaicModel' @Component({ components: { @@ -66,14 +58,14 @@ import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' ErrorTooltip, MaxFeeAndSubmit, }, - computed: {...mapGetters({ownedMosaics: 'wallet/currentWalletOwnedMosaics'})}, + computed: {...mapGetters({mosaics: 'mosaic/mosaics'})}, }) export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { /** * Mosaic hex Id * @type {string} */ - @Prop({ default: null, required: true }) mosaicHexId: string + @Prop({default: null, required: true}) mosaicHexId: string /** * Validation rules @@ -102,18 +94,16 @@ export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { /** * Mosaics owned by the current wallet * @protected - * @type {MosaicInfo[]} */ - protected ownedMosaics: MosaicInfo[] + private mosaics: MosaicModel[] /** * Current mosaic info * @readonly * @protected - * @type {MosaicInfo} */ - protected get currentMosaicInfo(): MosaicInfo { - return this.ownedMosaics.find(({id}) => id.toHex() === this.formItems.mosaicHexId) + protected get currentMosaicInfo(): MosaicModel { + return this.mosaics.find(({mosaicIdHex}) => mosaicIdHex === this.formItems.mosaicHexId) } /** @@ -123,8 +113,9 @@ export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { * @type {number} */ protected get currentMosaicRelativeSupply(): string | null { - if (!this.currentMosaicInfo) return null - const relative = this.currentMosaicInfo.supply.compact() / Math.pow(10, this.currentMosaicInfo.divisibility) + const currentMosaicInfo = this.currentMosaicInfo + if (!currentMosaicInfo) return null + const relative = currentMosaicInfo.supply / Math.pow(10, currentMosaicInfo.divisibility) return isNaN(relative) ? null : relative.toLocaleString() } @@ -135,10 +126,11 @@ export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { * @type {(number | null)} */ protected get newMosaicAbsoluteSupply(): number | null { - if (this.currentMosaicInfo === undefined) return null + const currentMosaicInfo = this.currentMosaicInfo + if (currentMosaicInfo === undefined) return null const newSupply = this.formItems.action === MosaicSupplyChangeAction.Increase - ? this.currentMosaicInfo.supply.compact() + Number(this.formItems.delta) - : this.currentMosaicInfo.supply.compact() - Number(this.formItems.delta) + ? currentMosaicInfo.supply + Number(this.formItems.delta) + : currentMosaicInfo.supply - Number(this.formItems.delta) return isNaN(newSupply) ? null : newSupply } diff --git a/src/views/forms/FormMultisigAccountModificationTransaction/FormMultisigAccountModificationTransactionTs.ts b/src/views/forms/FormMultisigAccountModificationTransaction/FormMultisigAccountModificationTransactionTs.ts index 39013c9cc..b63ea0588 100644 --- a/src/views/forms/FormMultisigAccountModificationTransaction/FormMultisigAccountModificationTransactionTs.ts +++ b/src/views/forms/FormMultisigAccountModificationTransaction/FormMultisigAccountModificationTransactionTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,17 +14,12 @@ * limitations under the License. */ // external dependencies -import {TransactionType, MultisigAccountModificationTransaction, PublicAccount, MultisigAccountInfo} from 'symbol-sdk' -import {Component, Vue, Prop} from 'vue-property-decorator' - +import {MultisigAccountInfo, MultisigAccountModificationTransaction, PublicAccount, TransactionType} from 'symbol-sdk' +import {Component, Prop, Vue} from 'vue-property-decorator' // internal dependencies import {FormTransactionBase} from '@/views/forms/FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' -import { - MultisigAccountModificationFormFieldsType, CosignatoryModification, - ViewMultisigAccountModificationTransaction, CosignatoryModifications, -} from '@/core/transactions/ViewMultisigAccountModificationTransaction' - +import {CosignatoryModification, CosignatoryModifications, MultisigAccountModificationFormFieldsType, ViewMultisigAccountModificationTransaction} from '@/core/transactions/ViewMultisigAccountModificationTransaction' // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -47,6 +42,7 @@ import CosignatoryModificationsDisplay from '@/components/CosignatoryModificatio import ApprovalAndRemovalInput from '@/components/ApprovalAndRemovalInput/ApprovalAndRemovalInput.vue' // @ts-ignore import MultisigCosignatoriesDisplay from '@/components/MultisigCosignatoriesDisplay/MultisigCosignatoriesDisplay.vue' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ components: { @@ -84,9 +80,9 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio /// end-region component properties /** - * Form items - * @var {any} - */ + * Form items + * @var {any} + */ public formItems: MultisigAccountModificationFormFieldsType = { signerPublicKey: '', minApprovalDelta: 0, @@ -95,34 +91,25 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio maxFee: 0, } - /** - * Max number of cosignatories per account - * @private - * @type {number} - */ - private maxCosignatoriesPerAccount: number = this.$store.getters['network/properties'].maxCosignatoriesPerAccount + private networkConfiguration: NetworkConfigurationModel = this.$store.getters['network/networkConfiguration'] + public get multisigOperationType(): 'conversion' | 'modification' { if (this.isCosignatoryMode) { return 'modification' } - return 'conversion' } public get currentMultisigInfo(): MultisigAccountInfo { - if (this.isCosignatoryMode) { - return this.currentSignerMultisigInfo - } - - return this.currentWalletMultisigInfo + return this.currentSignerMultisigInfo } /** - * Reset the form with properties - * @see {FormTransactionBase} - * @return {void} - */ + * Reset the form with properties + * @see {FormTransactionBase} + * @return {void} + */ protected resetForm() { // - re-populate form if transaction staged if (this.stagedTransactions.length) { @@ -143,7 +130,7 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio this.formItems.minApprovalDelta = !!this.minApprovalDelta ? this.minApprovalDelta : defaultMinApprovalDelta this.formItems.minRemovalDelta = !!this.minRemovalDelta ? this.minRemovalDelta : defaultMinRemovalDelta this.formItems.cosignatoryModifications = {} - this.formItems.signerPublicKey = this.currentWallet.values.get('publicKey') + this.formItems.signerPublicKey = this.currentWallet.publicKey // - maxFee must be absolute this.formItems.maxFee = this.defaultFee @@ -168,10 +155,10 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio } /** - * Getter for TRANSFER transactions that will be staged - * @see {FormTransactionBase} - * @return {MultisigAccountModificationTransaction[]} - */ + * Getter for TRANSFER transactions that will be staged + * @see {FormTransactionBase} + * @return {MultisigAccountModificationTransaction[]} + */ protected getTransactions(): MultisigAccountModificationTransaction[] { this.factory = new TransactionFactory(this.$store) try { @@ -186,11 +173,11 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio } /** - * Setter for TRANSFER transactions that will be staged - * @see {FormTransactionBase} - * @param {TransferTransaction[]} transactions - * @throws {Error} If not overloaded in derivate component - */ + * Setter for TRANSFER transactions that will be staged + * @see {FormTransactionBase} + * @param {TransferTransaction[]} transactions + * @throws {Error} If not overloaded in derivate component + */ protected setTransactions(transactions: MultisigAccountModificationTransaction[]) { // this form creates only 1 transaction const transaction = transactions.shift() @@ -225,39 +212,28 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio * of long-loading (e.g. fetch of multisig data). * * @override - * @param {string} signerPublicKey + * @param {string} publicKey */ - public async onChangeSigner(signerPublicKey: string) { + public async onChangeSigner(publicKey: string) { // whether the new signer is a multisig account - const signerIsMultisigAccount = this.currentWallet.values.get('publicKey') !== signerPublicKey + const signerIsMultisigAccount = this.currentWallet.publicKey !== publicKey // force update form fields this.formItems.minApprovalDelta = signerIsMultisigAccount ? 0 : 1 this.formItems.minRemovalDelta = signerIsMultisigAccount ? 0 : 1 - this.formItems.signerPublicKey = signerPublicKey + this.formItems.signerPublicKey = publicKey this.formItems.cosignatoryModifications = {} - /// region super.onChangeSigner - this.currentSigner = PublicAccount.createFromPublicKey(signerPublicKey, this.networkType) - - const payload = !signerIsMultisigAccount ? this.currentWallet : { - networkType: this.networkType, - publicKey: signerPublicKey, - } - - await this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {model: payload}) + await this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {publicKey}) /// end-region super.onChangeSigner - // force fetch of multisig info for current signer - const address = this.currentSigner.address - this.$store.dispatch('wallet/REST_FETCH_MULTISIG', address.plain()) } /** * Hook called when the subcomponent MultisigCosignatoriesDisplay * emits the event `remove`. * - * @param {string} publicKey + * @param {string} publicKey */ public onClickRemove(publicKey: string) { const modifications = this.formItems.cosignatoryModifications @@ -282,7 +258,7 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio * Hook called when the subcomponent MultisigCosignatoriesDisplay * emits the event `add`. * - * @param {PublicAccount} publicAccount + * @param {PublicAccount} publicAccount */ public onClickAdd(publicAccount: PublicAccount) { Vue.set( @@ -316,7 +292,7 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio // calculate new min approval const newMinApproval = this.currentMultisigInfo ? this.currentMultisigInfo.minApproval + this.formItems.minApprovalDelta - : this.formItems.minApprovalDelta + : this.formItems.minApprovalDelta // calculate new min approval const newMinRemoval = this.currentMultisigInfo @@ -331,7 +307,7 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio const newCosignatoryNumber = this.currentMultisigInfo ? this.currentMultisigInfo.cosignatories.length + numberOfAddedCosigners : numberOfAddedCosigners - + return { minApproval: newMinApproval, minRemoval: newMinRemoval, @@ -347,10 +323,10 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio */ protected get areInputsValid(): 'OK' | false { const {minApproval, minRemoval, cosignatoryNumber} = this.newMultisigProperties - + const maxCosignatoriesPerAccount = this.networkConfiguration.maxCosignatoriesPerAccount return cosignatoryNumber >= minApproval - && cosignatoryNumber >= minRemoval - && cosignatoryNumber <= this.maxCosignatoriesPerAccount + && cosignatoryNumber >= minRemoval + && cosignatoryNumber <= maxCosignatoriesPerAccount ? 'OK' : false } @@ -369,24 +345,26 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio */ protected get errorMessage(): string { const {minApproval, minRemoval, cosignatoryNumber} = this.newMultisigProperties - const {maxCosignatoriesPerAccount} = this + const maxCosignatoriesPerAccount = this.networkConfiguration.maxCosignatoriesPerAccount // no message if inputs are OK if (this.areInputsValid === 'OK') return - if(cosignatoryNumber < minApproval) { + if (cosignatoryNumber < minApproval) { return `${this.$t('approval_greater_than_cosignatories', {delta: minApproval - cosignatoryNumber})}` } - if(cosignatoryNumber < minRemoval) { + if (cosignatoryNumber < minRemoval) { return `${this.$t('removal_greater_than_cosignatories', {delta: minRemoval - cosignatoryNumber})}` } - - if(cosignatoryNumber > maxCosignatoriesPerAccount) { - return `${this.$t('too_many_cosignatories'), { - maxCosignatoriesPerAccount, delta: cosignatoryNumber - maxCosignatoriesPerAccount, - }}` + + if (cosignatoryNumber > maxCosignatoriesPerAccount) { + return `${this.$t('too_many_cosignatories', { + maxCosignatoriesPerAccount, + delta: cosignatoryNumber - maxCosignatoriesPerAccount, + })}` } } + /// end-region validation handling } diff --git a/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts b/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts index 28ea49ba1..28edf13a6 100644 --- a/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts +++ b/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,15 +14,13 @@ * limitations under the License. */ // external dependencies -import {UInt64, NamespaceRegistrationTransaction, NamespaceRegistrationType, Transaction, NamespaceInfo, NamespaceId} from 'symbol-sdk' +import {NamespaceId, NamespaceRegistrationTransaction, NamespaceRegistrationType, Transaction, UInt64} from 'symbol-sdk' import {Component, Prop} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {ViewNamespaceRegistrationTransaction, NamespaceRegistrationFormFieldsType} from '@/core/transactions/ViewNamespaceRegistrationTransaction' +import {NamespaceRegistrationFormFieldsType, ViewNamespaceRegistrationTransaction} from '@/core/transactions/ViewNamespaceRegistrationTransaction' import {FormTransactionBase} from '@/views/forms/FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -41,9 +39,9 @@ import DurationInput from '@/components/DurationInput/DurationInput.vue' import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' // @ts-ignore import ModalTransactionConfirmation from '@/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue' - // configuration -import networkConfig from '@/../config/network.conf.json' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ components: { @@ -58,22 +56,26 @@ import networkConfig from '@/../config/network.conf.json' ModalTransactionConfirmation, MaxFeeAndSubmit, }, - computed: {...mapGetters({ - ownedNamespaces: 'wallet/currentWalletOwnedNamespaces', - })}, + computed: { + ...mapGetters({ + ownedNamespaces: 'namespace/ownedNamespaces', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase { - @Prop({ default: null }) signer: string - @Prop({ default: null }) registrationType: NamespaceRegistrationType - @Prop({ default: null }) namespaceId: NamespaceId - @Prop({ default: null }) parentNamespaceId: NamespaceId - @Prop({ default: null }) duration: number + @Prop({default: null}) signer: string + @Prop({default: null}) registrationType: NamespaceRegistrationType + @Prop({default: null}) namespaceId: NamespaceId + @Prop({default: null}) parentNamespaceId: NamespaceId + @Prop({default: null}) duration: number + + protected networkConfiguration: NetworkConfigurationModel /** * Current wallet's owned namespaces - * @var {NamespaceInfo[]} */ - public ownedNamespaces: NamespaceInfo[] + public ownedNamespaces: NamespaceModel[] /** * Root namespace type exposed to view @@ -100,21 +102,14 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase maxFee: 0, } - /** - * Max namespace depth allowed by the network - * @private - * @type {number} - */ - private maxNamespaceDepth: number = networkConfig.networks['testnet-publicTest'].properties.maxNamespaceDepth - /** * Namespaces that can have children * @readonly * @protected - * @type {NamespaceInfo[]} */ - protected get fertileNamespaces(): NamespaceInfo[] { - return this.ownedNamespaces.filter(({depth}) => depth < this.maxNamespaceDepth) + protected get fertileNamespaces(): NamespaceModel[] { + const maxNamespaceDepth = this.networkConfiguration.maxNamespaceDepth + return this.ownedNamespaces.filter(({depth}) => depth < maxNamespaceDepth) } /** @@ -123,7 +118,7 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase */ protected resetForm() { // - set default form values - this.formItems.signerPublicKey = this.currentWallet.values.get('publicKey') + this.formItems.signerPublicKey = this.currentWallet.publicKey this.formItems.registrationType = this.registrationType || NamespaceRegistrationType.RootNamespace this.formItems.newNamespaceName = this.namespaceId ? this.namespaceId.fullName : '' this.formItems.parentNamespaceName = this.parentNamespaceId ? this.parentNamespaceId.fullName : '' @@ -153,27 +148,27 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase // - read form for definition const data: NamespaceRegistrationFormFieldsType = { registrationType: this.formItems.registrationType, - rootNamespaceName: NamespaceRegistrationType.RootNamespace === this.formItems.registrationType + rootNamespaceName: NamespaceRegistrationType.RootNamespace === this.formItems.registrationType ? this.formItems.newNamespaceName : this.formItems.parentNamespaceName, - subNamespaceName: NamespaceRegistrationType.SubNamespace === this.formItems.registrationType + subNamespaceName: NamespaceRegistrationType.SubNamespace === this.formItems.registrationType ? this.formItems.newNamespaceName : '', duration: this.formItems.duration, maxFee: UInt64.fromUint(this.formItems.maxFee), } - + // - prepare transaction let view = new ViewNamespaceRegistrationTransaction(this.$store) view = view.parse(data) - + // - return instantiated Transaction return [this.factory.build(view)] } catch (error) { console.error('Error happened in FormNamespaceRegistrationTransaction.transactions(): ', error) } } - + /** * Setter for TRANSFER transactions that will be staged * @see {FormTransactionBase} @@ -183,13 +178,13 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase protected setTransactions(transactions: Transaction[]) { // - this form creates 2 transaction const transaction = transactions.shift() as NamespaceRegistrationTransaction - + // - populate from transaction this.formItems.registrationType = transaction.registrationType this.formItems.newNamespaceName = transaction.namespaceName this.formItems.parentNamespaceName = transaction.parentId ? transaction.parentId.toHex() : '' this.formItems.duration = transaction.duration ? transaction.duration.compact() : 0 - + // - populate maxFee this.formItems.maxFee = transaction.maxFee.compact() } diff --git a/src/views/forms/FormSubWalletCreation/FormSubWalletCreationTs.ts b/src/views/forms/FormSubWalletCreation/FormSubWalletCreationTs.ts index 4c6fc5a1e..4ce0d1d14 100644 --- a/src/views/forms/FormSubWalletCreation/FormSubWalletCreationTs.ts +++ b/src/views/forms/FormSubWalletCreation/FormSubWalletCreationTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,20 +15,16 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NetworkType, Password, Account} from 'symbol-sdk' -import { MnemonicPassPhrase } from 'symbol-hd-wallets' - +import {Account, NetworkType, Password} from 'symbol-sdk' +import {MnemonicPassPhrase} from 'symbol-hd-wallets' // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {AccountsModel} from '@/core/database/entities/AccountsModel' import {DerivationService} from '@/services/DerivationService' import {AESEncryptionService} from '@/services/AESEncryptionService' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {WalletsRepository} from '@/repositories/WalletsRepository' +import {AccountService} from '@/services/AccountService' import {NotificationType} from '@/core/utils/NotificationType' import {WalletService} from '@/services/WalletService' -import {WalletsModel} from '@/core/database/entities/WalletsModel' - +import {WalletModel} from '@/core/database/entities/WalletModel' // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -39,9 +35,10 @@ import FormWrapper from '@/components/FormWrapper/FormWrapper.vue' import FormRow from '@/components/FormRow/FormRow.vue' // @ts-ignore import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' - // configuration import appConfig from '@/../config/app.conf.json' +import {AccountModel} from '@/core/database/entities/AccountModel' + const {MAX_SEED_WALLETS_NUMBER} = appConfig.constants @Component({ @@ -53,66 +50,52 @@ const {MAX_SEED_WALLETS_NUMBER} = appConfig.constants FormRow, ModalFormAccountUnlock, }, - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - knownWallets: 'wallet/knownWallets', - })}, + computed: { + ...mapGetters({ + networkType: 'network/networkType', + currentAccount: 'account/currentAccount', + knownWallets: 'wallet/knownWallets', + }), + }, }) export class FormSubWalletCreationTs extends Vue { /** * Currently active account - * @see {Store.Account} - * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Known wallets identifiers - * @var {string[]} */ public knownWallets: string[] /** * Currently active network type - * @see {Store.Network} - * @var {NetworkType} */ public networkType: NetworkType /** * Wallets repository - * @var {WalletService} */ - public wallets: WalletService + public walletService: WalletService /** * Derivation paths service - * @var {DerivationService} */ public paths: DerivationService /** * Accounts repository - * @var {AccountsRepository} - */ - public accountsRepository: AccountsRepository - - /** - * Wallets repository - * @var {WalletsRepository} */ - public walletsRepository: WalletsRepository + public accountService: AccountService /** * Validation rules - * @var {ValidationRuleset} */ public validationRules = ValidationRuleset /** * Whether account is currently being unlocked - * @var {boolean} */ public isUnlockingAccount: boolean = false @@ -133,20 +116,19 @@ export class FormSubWalletCreationTs extends Vue { } /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } - + public created() { - this.wallets = new WalletService(this.$store) - this.paths = new DerivationService(this.$store) - this.accountsRepository = new AccountsRepository() - this.walletsRepository = new WalletsRepository() + this.walletService = new WalletService() + this.paths = new DerivationService() + this.accountService = new AccountService() } /// region computed properties getter/setter @@ -162,21 +144,11 @@ export class FormSubWalletCreationTs extends Vue { if (!this.knownWallets || !this.knownWallets.length) { return [] } - // filter wallets to only known wallet names - const knownWallets = this.wallets.getWallets( - (e) => this.knownWallets.includes(e.getIdentifier()), - ) - - return [...knownWallets].map( - ({identifier, values}) => ({ - identifier, - path: values.get('path'), - }), - ).filter( - w => w.path && w.path.length, - ).map(w => w.path) + const knownWallets = this.walletService.getKnownWallets(this.knownWallets) + return knownWallets.map(w => w.path).filter(p => p) } + /// end-region computed properties getter/setter /** @@ -206,15 +178,15 @@ export class FormSubWalletCreationTs extends Vue { try { // - create sub wallet (can be either derived or by private key) - let subWallet: WalletsModel - switch(type) { + let subWallet: WalletModel + switch (type) { default: case 'child_wallet': subWallet = this.deriveNextChildWallet(values.name) break case 'privatekey_wallet': - subWallet = this.wallets.getSubWalletByPrivateKey( + subWallet = this.walletService.getSubWalletByPrivateKey( this.currentAccount, this.currentPassword, this.formItems.name, @@ -231,28 +203,21 @@ export class FormSubWalletCreationTs extends Vue { this.currentPassword = null // - use repositories for storage - const walletsRepo = new WalletsRepository() - walletsRepo.create(subWallet.values) + this.walletService.saveWallet(subWallet) // - also add wallet to account (in storage) - const wallets = this.currentAccount.values.get('wallets') - wallets.push(subWallet.getIdentifier()) - this.currentAccount.values.set('wallets', wallets) - - const accountsRepo = new AccountsRepository() - accountsRepo.update( - this.currentAccount.getIdentifier(), - this.currentAccount.values, - ) + const wallets = this.currentAccount.wallets + wallets.push(subWallet.id) + + this.accountService.updateWallets(this.currentAccount, wallets) // - update app state this.$store.dispatch('account/ADD_WALLET', subWallet) - this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: subWallet}) + this.$store.dispatch('wallet/SET_CURRENT_WALLET', subWallet) this.$store.dispatch('wallet/SET_KNOWN_WALLETS', wallets) this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) this.$emit('submit', this.formItems) - } - catch (e) { + } catch (e) { this.$store.dispatch('notification/ADD_ERROR', 'An error happened, please try again.') console.error(e) } @@ -260,15 +225,16 @@ export class FormSubWalletCreationTs extends Vue { /** * Use HD wallet derivation to get next child wallet - * @param {string} childWalletName - * @return {WalletsModel} + * @param {string} childWalletName + * @return {WalletModel} */ - private deriveNextChildWallet(childWalletName: string): WalletsModel | null { + private deriveNextChildWallet(childWalletName: string): WalletModel | null { // - don't allow creating more than 10 wallets - if (this.knownPaths.length >= MAX_SEED_WALLETS_NUMBER) { + if (this.knownPaths.length >= MAX_SEED_WALLETS_NUMBER) { this.$store.dispatch( 'notification/ADD_ERROR', - this.$t(NotificationType.TOO_MANY_SEED_WALLETS_ERROR, {maxSeedWalletsNumber: MAX_SEED_WALLETS_NUMBER}), + this.$t(NotificationType.TOO_MANY_SEED_WALLETS_ERROR, + {maxSeedWalletsNumber: MAX_SEED_WALLETS_NUMBER}), ) return null } @@ -276,15 +242,16 @@ export class FormSubWalletCreationTs extends Vue { // - get next path const nextPath = this.paths.getNextAccountPath(this.knownPaths) - this.$store.dispatch('diagnostic/ADD_DEBUG', `Adding child wallet with derivation path: ${nextPath}`) + this.$store.dispatch('diagnostic/ADD_DEBUG', + 'Adding child wallet with derivation path: ' + nextPath) // - decrypt mnemonic - const encSeed = this.currentAccount.values.get('seed') + const encSeed = this.currentAccount.seed const passphrase = AESEncryptionService.decrypt(encSeed, this.currentPassword) const mnemonic = new MnemonicPassPhrase(passphrase) // create account by mnemonic - const wallet = this.wallets.getChildWalletByPath( + return this.walletService.getChildWalletByPath( this.currentAccount, this.currentPassword, mnemonic, @@ -292,7 +259,5 @@ export class FormSubWalletCreationTs extends Vue { this.networkType, childWalletName, ) - - return wallet } } diff --git a/src/views/forms/FormTransactionBase/FormTransactionBase.ts b/src/views/forms/FormTransactionBase/FormTransactionBase.ts index b84dbb001..691f5326b 100644 --- a/src/views/forms/FormTransactionBase/FormTransactionBase.ts +++ b/src/views/forms/FormTransactionBase/FormTransactionBase.ts @@ -1,63 +1,52 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import { - MosaicId, - Mosaic, - MultisigAccountInfo, - Transaction, - MosaicInfo, - PublicAccount, - NamespaceId, - NetworkType, -} from 'symbol-sdk' +import {MosaicId, MultisigAccountInfo, NetworkType, PublicAccount, Transaction} from 'symbol-sdk' import {Component, Vue, Watch} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {TransactionFactory} from '@/core/transactions/TransactionFactory' import {TransactionService} from '@/services/TransactionService' import {BroadcastResult} from '@/core/transactions/BroadcastResult' import {ValidationObserver} from 'vee-validate' -import {MultisigService} from '@/services/MultisigService' +import {Signer} from '@/store/Wallet' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' @Component({ - computed: {...mapGetters({ - generationHash: 'network/generationHash', - networkType: 'network/networkType', - defaultFee: 'app/defaultFee', - currentWallet: 'wallet/currentWallet', - selectedSigner: 'wallet/currentSigner', - currentWalletMosaics: 'wallet/currentWalletMosaics', - currentWalletMultisigInfo: 'wallet/currentWalletMultisigInfo', - isCosignatoryMode: 'wallet/isCosignatoryMode', - networkMosaic: 'mosaic/networkMosaic', - stagedTransactions: 'wallet/stagedTransactions', - mosaicsInfo: 'mosaic/mosaicsInfoList', - mosaicsInfoByHex: 'mosaic/mosaicsInfo', - mosaicsNames: 'mosaic/mosaicsNames', - namespacesNames: 'namespace/namespacesNames', - currentSignerMultisigInfo: 'wallet/currentSignerMultisigInfo', - })}, + computed: { + ...mapGetters({ + generationHash: 'network/generationHash', + networkType: 'network/networkType', + defaultFee: 'app/defaultFee', + currentWallet: 'wallet/currentWallet', + selectedSigner: 'wallet/currentSigner', + currentSignerMultisigInfo: 'wallet/currentSignerMultisigInfo', + currentWalletMultisigInfo: 'wallet/currentWalletMultisigInfo', + isCosignatoryMode: 'wallet/isCosignatoryMode', + stagedTransactions: 'wallet/stagedTransactions', + networkMosaic: 'mosaic/networkMosaic', + networkCurrency: 'mosaic/networkCurrency', + signers: 'wallet/signers', + }), + }, }) export class FormTransactionBase extends Vue { /// region store getters /** * Network generation hash - * @var {string} */ public generationHash: string @@ -69,27 +58,18 @@ export class FormTransactionBase extends Vue { /** * Default fee setting - * @var {number} */ public defaultFee: number /** * Currently active wallet - * @var {WalletsModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Currently active signer - * @var {WalletsModel} - */ - public selectedSigner: WalletsModel - - /** - * Currently active wallet's balances - * @var {Mosaic[]} */ - public currentWalletMosaics: Mosaic[] + public selectedSigner: Signer /** * Current wallet multisig info @@ -121,38 +101,18 @@ export class FormTransactionBase extends Vue { */ public stagedTransactions: Transaction[] - /** - * List of known mosaics - * @var {MosaicInfo[]} - */ - public mosaicsInfo: MosaicInfo[] - - /** - * List of known mosaics - * @type {Record} - */ - public mosaicsInfoByHex: Record - - /** - * List of known mosaics names - * @var {any} - */ - public mosaicsNames: any - - /** - * List of known namespaces names - * @var {any} - */ - public namespacesNames: any - /** * Public key of the current signer * @var {any} */ public currentSigner: PublicAccount + public signers: Signer[] + + public networkCurrency: NetworkCurrencyModel + /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ * observer: InstanceType * }} @@ -169,6 +129,7 @@ export class FormTransactionBase extends Vue { this.resetForm() // @TODO: probably not the best way this.resetFormValidation() } + /// end-region property watches /** @@ -183,25 +144,6 @@ export class FormTransactionBase extends Vue { */ public factory: TransactionFactory - /** - * Hook called when the component is mounted - * @return {void} - */ - public async mounted() { - if (this.currentWallet) { - const address = this.currentWallet.objects.address.plain() - try { this.$store.dispatch('wallet/REST_FETCH_OWNED_NAMESPACES', address) } - catch(e) { console.log('Error fetching namespaces') } - - if (!this.isCosignatoryMode) { - this.currentSigner = this.currentWallet.objects.publicAccount - } - else { - this.currentSigner = this.selectedSigner.objects.publicAccount - } - } - } - /** * Hook called when the component is mounted * @return {void} @@ -217,27 +159,18 @@ export class FormTransactionBase extends Vue { */ public beforeDestroy() { // reset the selected signer if it is not the current wallet - if (this.selectedSigner !== this.currentWallet.values.get('publicKey')) { - this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {model: this.currentWallet}) + if (this.selectedSigner.publicKey !== this.currentWallet.publicKey) { + this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {publicKey: this.currentWallet.publicKey}) } } - /// region computed properties getter/setter - get signers(): {publicKey: string, label: string}[] { - return this.getSigners() - } - /** * Current signer's multisig accounts * @readonly * @type {{publicKey: string, label: string}[]} */ - get multisigAccounts(): {publicKey: string, label: string}[] { - const signers = this.getSigners() - if (!signers.length || !this.currentWallet) { - return [] - } - + get multisigAccounts(): Signer[] { + const signers = this.signers // if "self" is multisig, also return self if (this.currentWalletMultisigInfo && this.currentWalletMultisigInfo.isMultisig()) { return signers @@ -254,6 +187,7 @@ export class FormTransactionBase extends Vue { set hasConfirmationModal(f: boolean) { this.isAwaitingSignature = f } + /// end-region computed properties getter/setter /** @@ -309,18 +243,11 @@ export class FormTransactionBase extends Vue { /** * Hook called when a signer is selected. - * @param {string} signerPublicKey + * @param {string} publicKey */ - public async onChangeSigner(signerPublicKey: string) { - this.currentSigner = PublicAccount.createFromPublicKey(signerPublicKey, this.networkType) - - const isCosig = this.currentWallet.values.get('publicKey') !== signerPublicKey - const payload = !isCosig ? this.currentWallet : { - networkType: this.networkType, - publicKey: signerPublicKey, - } - - await this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {model: payload}) + public async onChangeSigner(publicKey: string) { + // this.currentSigner = PublicAccount.createFromPublicKey(publicKey, this.networkType) + await this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {publicKey}) } /** @@ -330,7 +257,7 @@ export class FormTransactionBase extends Vue { public async onSubmit() { const transactions = this.getTransactions() - this.$store.dispatch('diagnostic/ADD_DEBUG', `Adding ${transactions.length} transaction(s) to stage (prepared & unsigned)`) + this.$store.dispatch('diagnostic/ADD_DEBUG', 'Adding ' + transactions.length + ' transaction(s) to stage (prepared & unsigned)') // - check whether transactions must be aggregated // - also set isMultisig flag in case of cosignatory mode @@ -363,7 +290,7 @@ export class FormTransactionBase extends Vue { // if the form was in multisig, set the signer to be the main wallet // this triggers resetForm in the @Watch('currentWallet') hook if (this.isMultisigMode()) { - this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: this.currentWallet}) + this.$store.dispatch('wallet/SET_CURRENT_WALLET', this.currentWallet) } else { this.resetForm() } @@ -391,7 +318,7 @@ export class FormTransactionBase extends Vue { const errors = results.filter(result => false === result.success) if (errors.length) { errors.map(result => this.$store.dispatch('notification/ADD_ERROR', result.error)) - return + return } // - notify about broadcast success (_transactions now unconfirmed_) @@ -433,51 +360,5 @@ export class FormTransactionBase extends Vue { this.hasConfirmationModal = false } - /** - * internal helper for mosaic names - * @param {Mosaic} mosaic - * @return {string} - */ - protected getMosaicName(mosaicId: MosaicId | NamespaceId): string { - if (this.mosaicsNames && this.mosaicsNames[mosaicId.toHex()]) { - return this.mosaicsNames[mosaicId.toHex()] - } - else if (this.namespacesNames && this.namespacesNames[mosaicId.toHex()]) { - return this.namespacesNames[mosaicId.toHex()] - } - - return mosaicId.toHex() - } - /** - * internal helper for mosaic divisibility - * @param {Mosaic} mosaic - * @return {string} - */ - protected getDivisibility(mosaicId: MosaicId): number { - const info = this.mosaicsInfo.find(i => i.id.equals(mosaicId)) - if (undefined === info) { - return 6 // XXX default divisibility? - } - - return info.divisibility - } - - /** - * internal helper for absolute fee amount - * @param {number} fee - * @return {number} - */ - protected getAbsoluteFee(fee: number): number { - const divisibility = this.getDivisibility(this.networkMosaic) - return fee * Math.pow(10, divisibility) - } - - /** - * Get a list of known signers given a `currentWallet` - * @return {{publicKey: string, label:string}[]} - */ - protected getSigners(): {publicKey: string, label: string}[] { - return new MultisigService(this.$store, this.$i18n).getSigners() - } } diff --git a/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts b/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts index 77637aa41..ff4f95086 100644 --- a/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts +++ b/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts @@ -1,41 +1,29 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import { - MosaicId, - Mosaic, - TransferTransaction, - Address, - Message, - PublicAccount, - NamespaceId, - UInt64, -} from 'symbol-sdk' -import {Component, Vue, Prop, Watch} from 'vue-property-decorator' +import {Address, Message, Mosaic, MosaicId, NamespaceId, TransferTransaction, UInt64} from 'symbol-sdk' +import {Component, Prop, Vue, Watch} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies import {Formatters} from '@/core/utils/Formatters' -import {ViewTransferTransaction, TransferFormFieldsType} from '@/core/transactions/ViewTransferTransaction' +import {TransferFormFieldsType, ViewTransferTransaction} from '@/core/transactions/ViewTransferTransaction' import {FormTransactionBase} from '@/views/forms/FormTransactionBase/FormTransactionBase' import {TransactionFactory} from '@/core/transactions/TransactionFactory' import {AddressValidator} from '@/core/validation/validators' import {MosaicInputsManager} from './MosaicInputsManager' -import {MosaicService} from '@/services/MosaicService' import {ITransactionEntry} from '@/views/pages/dashboard/invoice/DashboardInvoicePageTs' - // child components import {ValidationObserver} from 'vee-validate' // @ts-ignore @@ -58,6 +46,8 @@ import SignerSelector from '@/components/SignerSelector/SignerSelector.vue' import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' +import {MosaicService} from '@/services/MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' export interface MosaicAttachment { mosaicHex: string @@ -81,22 +71,19 @@ export interface MosaicAttachment { MaxFeeAndSubmit, FormRow, }, - computed: {...mapGetters({currentSignerMosaics: 'wallet/currentSignerMosaics'})}, + computed: { + ...mapGetters({ + currentHeight: 'network/currentHeight', + balanceMosaics: 'mosaic/balanceMosaics', + }), + }, }) export class FormTransferTransactionTs extends FormTransactionBase { - /// region component properties - @Prop({ - default: null, - }) signer: PublicAccount @Prop({ default: null, }) recipient: Address - @Prop({ - default: null, - }) mosaics: Mosaic[] - @Prop({ default: null, }) message: Message @@ -133,12 +120,9 @@ export class FormTransferTransactionTs extends FormTransactionBase { protected mosaicInputsManager = MosaicInputsManager.initialize([]) - /** - * Current signer mosaics - * @protected - * @type {Mosaic[]} - */ - protected currentSignerMosaics: Mosaic[] + public currentHeight: number + + private balanceMosaics: MosaicModel[] /** * Reset the form with properties @@ -149,22 +133,26 @@ export class FormTransferTransactionTs extends FormTransactionBase { this.formItems.attachedMosaics = [] // - set default form values - this.formItems.signerPublicKey = !!this.signer ? this.signer.publicKey : this.currentWallet.values.get('publicKey') + this.formItems.signerPublicKey = this.selectedSigner.publicKey this.formItems.selectedMosaicHex = this.networkMosaic.toHex() this.formItems.recipientRaw = !!this.recipient ? this.recipient.plain() : '' this.formItems.recipient = !!this.recipient ? this.recipient : null - const attachedMosaics = !!this.mosaics && this.mosaics.length - ? this.mosaicsToAttachments(this.mosaics) - : [{mosaicHex: this.networkMosaic.id.toHex(), amount: 0, uid: 1}] + const currentMosaics = this.currentMosaicList() - this.formItems.messagePlain = !!this.message ? Formatters.hexToUtf8(this.message.payload) : '' + const attachedMosaics: MosaicAttachment[] = [{ + id: new MosaicId(this.networkCurrency.mosaicIdHex), + mosaicHex: this.networkCurrency.mosaicIdHex, + name: this.networkCurrency.namespaceIdFullname, + amount: 0, + uid: Math.floor(Math.random() * 10e6), // used to index dynamic inputs + }] + this.formItems.messagePlain = !!this.message ? Formatters.hexToUtf8(this.message.payload) : '' // - maxFee must be absolute this.formItems.maxFee = this.defaultFee - // - initialize mosaics input manager - this.mosaicInputsManager = MosaicInputsManager.initialize(this.currentMosaicList()) + this.mosaicInputsManager = MosaicInputsManager.initialize(currentMosaics) // - set attachedMosaics and allocate slots Vue.nextTick(() => { @@ -187,41 +175,19 @@ export class FormTransferTransactionTs extends FormTransactionBase { } /** - * Returns the mosaic list of the current wallet or current signer + * Returns the mosaic list of the current wallet or current signer * depending on the multisig situation * @protected - * @returns + * @returns */ - protected currentMosaicList(): Mosaic[] { - if (!this.networkMosaic) return [] // @TODO: quickfix - - // get mosaic list according to the multisig status - const mosaics = this.isCosignatoryMode ? this.currentSignerMosaics : this.currentWalletMosaics - const defaultedMosaicList = mosaics && mosaics.length - ? mosaics - : [new Mosaic(this.networkMosaic, UInt64.fromUint(0))] - - // get mosaicService - const mosaicService = new MosaicService(this.$store) - + protected currentMosaicList(): MosaicModel[] { // filter out expired mosaics - const currentMosaicList = defaultedMosaicList.filter(mosaic => { - // get mosaic info - const mosaicInfo = this.mosaicsInfoByHex[mosaic.id.toHex()] - // skip if mosaic info is not available - if (!mosaicInfo) return false - + return this.balanceMosaics.filter(mosaicInfo => { // calculate expiration - const expiration = mosaicService.getExpiration(mosaicInfo) + const expiration = MosaicService.getExpiration(mosaicInfo, this.currentHeight) // skip if mosaic is expired - if (expiration === 'expired') return false - - return true + return expiration !== 'expired' }) - - // add eventual new mosaics in the mosaic inputs manager - if (this.mosaicInputsManager) this.mosaicInputsManager.addMosaics(currentMosaicList) - return currentMosaicList } /** @@ -299,6 +265,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { return new NamespaceId(recipientRaw) } + /// end-region computed properties getter/setter /** @@ -355,26 +322,29 @@ export class FormTransferTransactionTs extends FormTransactionBase { * Internal helper to format a {Mosaic} entry into * an array of MosaicAttachment used in this form. * @internal - * @param {Mosaic[]} mosaics + * @param {Mosaic[]} mosaics * @return {MosaicAttachment[]} */ - protected mosaicsToAttachments(mosaics: Mosaic[]): MosaicAttachment[] { + private mosaicsToAttachments(mosaics: Mosaic[]): MosaicAttachment[] { return mosaics.map( mosaic => { - const info = this.mosaicsInfo.find(i => i.id.equals(mosaic.id)) - const div = info ? info.divisibility : 0 + const info = this.balanceMosaics.find(m => mosaic.id.toHex() === m.mosaicIdHex) + if (!info) { + return null + } // amount will be converted to RELATIVE return { - id: mosaic.id as MosaicId, // XXX resolve mosaicId from namespaceId - mosaicHex: mosaic.id.toHex(), // XXX resolve mosaicId from namespaceId - name: this.getMosaicName(mosaic.id), - amount: mosaic.amount.compact() / Math.pow(10, div), + id: new MosaicId(info.mosaicIdHex), // XXX resolve mosaicId from namespaceId + mosaicHex: info.mosaicIdHex, // XXX resolve mosaicId from namespaceId + name: info.name, + amount: mosaic.amount.compact() / Math.pow(10, info.divisibility), uid: Math.floor(Math.random() * 10e6), // used to index dynamic inputs } - }) + }).filter(a => a) } - /** + + /** * Hook called when adding a new mosaic attachment input * @protected */ @@ -433,6 +403,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { * Is necessary to make the mosaic inputs reactive */ @Watch('selectedSigner') + @Watch('balanceMosaics') onSelectedSignerChange() { if (this.isMultisigMode) this.resetForm() } diff --git a/src/views/forms/FormTransferTransaction/MosaicInputsManager.ts b/src/views/forms/FormTransferTransaction/MosaicInputsManager.ts index 77dd673be..1c729ae50 100644 --- a/src/views/forms/FormTransferTransaction/MosaicInputsManager.ts +++ b/src/views/forms/FormTransferTransaction/MosaicInputsManager.ts @@ -1,6 +1,6 @@ // internal dependencies import Vue from 'vue' -import {Mosaic} from 'symbol-sdk' +import {MosaicModel} from '@/core/database/entities/MosaicModel' export class MosaicInputsManager { /** @@ -12,33 +12,33 @@ export class MosaicInputsManager { /** * Initialize a new instance of MosaicInputsManager * @static - * @param {Mosaic[]} mosaics + * @param {MosaicModel[]} mosaics * @param {MosaicService} mosaicService * @returns {MosaicInputsManager} */ - public static initialize(mosaics: Mosaic[]): MosaicInputsManager { + public static initialize(mosaics: MosaicModel[]): MosaicInputsManager { return new MosaicInputsManager(mosaics || []) } /** * Creates an instance of MosaicInputsManager. - * @param {Mosaic[]} mosaics + * @param {MosaicModel[]} mosaics */ - private constructor(mosaics: Mosaic[]) { + private constructor(mosaics: MosaicModel[]) { // Set mosaicMap with null values - mosaics.forEach(({id}) => Vue.set(this.mosaicMap, id.toHex(), null)) + mosaics.forEach(({mosaicIdHex}) => Vue.set(this.mosaicMap, mosaicIdHex, null)) } /** * Add mosaics to the manager after initialization - * @param {Mosaic[]} mosaics + * @param {MosaicModel[]} mosaics */ - public addMosaics(mosaics: Mosaic[]): void { - mosaics.forEach(({id}) => { + public addMosaics(mosaics: MosaicModel[]): void { + mosaics.forEach(({mosaicIdHex}) => { // skip if the mosaic is known - if (this.mosaicMap[id.toHex()]) return + if (this.mosaicMap[mosaicIdHex]) return // add the mosaic - Vue.set(this.mosaicMap, id.toHex(), null) + Vue.set(this.mosaicMap, mosaicIdHex, null) }) } diff --git a/src/views/forms/FormWalletNameUpdate/FormWalletNameUpdateTs.ts b/src/views/forms/FormWalletNameUpdate/FormWalletNameUpdateTs.ts index 48b688689..76c66487a 100644 --- a/src/views/forms/FormWalletNameUpdate/FormWalletNameUpdateTs.ts +++ b/src/views/forms/FormWalletNameUpdate/FormWalletNameUpdateTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,14 +16,11 @@ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {NetworkType, Password} from 'symbol-sdk' - // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {WalletsModel} from '@/core/database/entities/WalletsModel' -import {WalletsRepository} from '@/repositories/WalletsRepository' +import {WalletModel} from '@/core/database/entities/WalletModel' import {NotificationType} from '@/core/utils/NotificationType' import {WalletService} from '@/services/WalletService' - // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -44,19 +41,21 @@ import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalF FormRow, ModalFormAccountUnlock, }, - computed: {...mapGetters({ - networkType: 'network/networkType', - currentWallet: 'wallet/currentWallet', - knownWallets: 'wallet/knownWallets', - })}, + computed: { + ...mapGetters({ + networkType: 'network/networkType', + currentWallet: 'wallet/currentWallet', + knownWallets: 'wallet/knownWallets', + }), + }, }) export class FormWalletNameUpdateTs extends Vue { /** * Currently active account * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * Known wallets identifiers @@ -75,13 +74,7 @@ export class FormWalletNameUpdateTs extends Vue { * Wallets repository * @var {WalletService} */ - public wallets: WalletService - - /** - * Wallets repository - * @var {WalletsRepository} - */ - public walletsRepository: WalletsRepository + public walletService: WalletService /** * Validation rules @@ -110,18 +103,17 @@ export class FormWalletNameUpdateTs extends Vue { } /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } - + public created() { - this.wallets = new WalletService(this.$store) - this.walletsRepository = new WalletsRepository() + this.walletService = new WalletService() } /// region computed properties getter/setter @@ -132,6 +124,7 @@ export class FormWalletNameUpdateTs extends Vue { public set hasAccountUnlockModal(f: boolean) { this.isUnlockingAccount = f } + /// end-region computed properties getter/setter /** @@ -151,23 +144,13 @@ export class FormWalletNameUpdateTs extends Vue { * When account is unlocked, the sub wallet can be created */ public onAccountUnlocked() { - // - interpret form items - const values = this.formItems - try { - // - update model values - this.currentWallet.values.set('name', values.name) - // - use repositories for storage - this.walletsRepository.update( - this.currentWallet.getIdentifier(), - this.currentWallet.values, - ) + this.walletService.updateName(this.currentWallet, this.formItems.name) this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) this.$emit('submit', this.formItems) - } - catch (e) { + } catch (e) { this.$store.dispatch('notification/ADD_ERROR', 'An error happened, please try again.') console.error(e) } diff --git a/src/views/layout/PageLayout/PageLayout.vue b/src/views/layout/PageLayout/PageLayout.vue index 95f388ecd..298c3b302 100644 --- a/src/views/layout/PageLayout/PageLayout.vue +++ b/src/views/layout/PageLayout/PageLayout.vue @@ -49,7 +49,7 @@ diff --git a/src/views/modals/ModalMnemonicDisplay/ModalMnemonicDisplayTs.ts b/src/views/modals/ModalMnemonicDisplay/ModalMnemonicDisplayTs.ts index 7b23a7030..7cb19c279 100644 --- a/src/views/modals/ModalMnemonicDisplay/ModalMnemonicDisplayTs.ts +++ b/src/views/modals/ModalMnemonicDisplay/ModalMnemonicDisplayTs.ts @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {Account, Password} from 'symbol-sdk' import {MnemonicPassPhrase} from 'symbol-hd-wallets' - // internal dependencies import {AESEncryptionService} from '@/services/AESEncryptionService' -import {AccountsModel} from '@/core/database/entities/AccountsModel' - +import {AccountModel} from '@/core/database/entities/AccountModel' // child components // @ts-ignore import FormAccountUnlock from '@/views/forms/FormAccountUnlock/FormAccountUnlock.vue' @@ -47,7 +45,7 @@ export class ModalMnemonicDisplayTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel public hasMnemonicInfo: boolean = false public mnemonic: MnemonicPassPhrase @@ -82,7 +80,7 @@ export class ModalMnemonicDisplayTs extends Vue { public onAccountUnlocked(payload: {account: Account, password: Password}): boolean { // decrypt seed + create QR - const encSeed = this.currentAccount.values.get('seed') + const encSeed = this.currentAccount.seed const plnSeed = AESEncryptionService.decrypt(encSeed, payload.password) try { diff --git a/src/views/modals/ModalMnemonicExport/ModalMnemonicExport.vue b/src/views/modals/ModalMnemonicExport/ModalMnemonicExport.vue index c840037c7..29b46c462 100644 --- a/src/views/modals/ModalMnemonicExport/ModalMnemonicExport.vue +++ b/src/views/modals/ModalMnemonicExport/ModalMnemonicExport.vue @@ -40,7 +40,8 @@ diff --git a/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts b/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts index a8a4e11c5..a90d483ea 100644 --- a/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts +++ b/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts @@ -1,43 +1,42 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {Account, Password, NetworkType} from 'symbol-sdk' +import {Account, NetworkType, Password} from 'symbol-sdk' import {MnemonicPassPhrase} from 'symbol-hd-wallets' -import {QRCodeGenerator, MnemonicQR} from 'symbol-qr-library' - +import {MnemonicQR, QRCodeGenerator} from 'symbol-qr-library' // internal dependencies import {AESEncryptionService} from '@/services/AESEncryptionService' -import {AccountsModel} from '@/core/database/entities/AccountsModel' - +import {AccountModel} from '@/core/database/entities/AccountModel' // child components // @ts-ignore import FormAccountUnlock from '@/views/forms/FormAccountUnlock/FormAccountUnlock.vue' - // resources // @ts-ignore import failureIcon from '@/views/resources/img/monitor/failure.png' @Component({ components: {FormAccountUnlock}, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - networkType: 'network/networkType', - generationHash: 'network/generationHash', - })}, + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + networkType: 'network/networkType', + generationHash: 'network/generationHash', + }), + }, }) export class ModalMnemonicExportTs extends Vue { @Prop({ @@ -49,7 +48,7 @@ export class ModalMnemonicExportTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Current networkType @@ -64,7 +63,7 @@ export class ModalMnemonicExportTs extends Vue { * @var {string} */ public generationHash: string - + public qrBase64: string = failureIcon public hasMnemonicInfo: boolean = false public exportMnemonicQR: MnemonicQR @@ -96,13 +95,13 @@ export class ModalMnemonicExportTs extends Vue { /** * Hook called when the account has been unlocked - * @param {Account} account + * @param {Account} account * @return {boolean} */ - public onAccountUnlocked(payload: {account: Account, password: Password}): boolean { + public onAccountUnlocked(payload: { account: Account, password: Password }): boolean { // decrypt seed + create QR - const encSeed = this.currentAccount.values.get('seed') + const encSeed = this.currentAccount.seed const plnSeed = AESEncryptionService.decrypt(encSeed, payload.password) try { @@ -118,8 +117,7 @@ export class ModalMnemonicExportTs extends Vue { // display mnemonic this.hasMnemonicInfo = true return true - } - catch (e) { + } catch (e) { console.error('error mnemonic: ', e) } diff --git a/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue b/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue index 53d300f05..a5222ed07 100644 --- a/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue +++ b/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmation.vue @@ -7,7 +7,7 @@ :transfer="false" >
-
diff --git a/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmationTs.ts b/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmationTs.ts index 6fe8fbbd5..e0d3dd3be 100644 --- a/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmationTs.ts +++ b/src/views/modals/ModalTransactionConfirmation/ModalTransactionConfirmationTs.ts @@ -1,28 +1,25 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {Account, Transaction, SignedTransaction, PublicAccount, NetworkType} from 'symbol-sdk' - +import {Account, NetworkType, PublicAccount, SignedTransaction, Transaction} from 'symbol-sdk' // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel' +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel' import {TransactionService} from '@/services/TransactionService' import {BroadcastResult} from '@/core/transactions/BroadcastResult' - // child components // @ts-ignore import TransactionDetails from '@/components/TransactionDetails/TransactionDetails.vue' @@ -30,6 +27,7 @@ import TransactionDetails from '@/components/TransactionDetails/TransactionDetai import FormAccountUnlock from '@/views/forms/FormAccountUnlock/FormAccountUnlock.vue' // @ts-ignore import HardwareConfirmationButton from '@/components/HardwareConfirmationButton/HardwareConfirmationButton.vue' +import {Signer} from '@/store/Wallet' @Component({ components: { @@ -37,14 +35,15 @@ import HardwareConfirmationButton from '@/components/HardwareConfirmationButton/ FormAccountUnlock, HardwareConfirmationButton, }, - computed: {...mapGetters({ - generationHash: 'network/generationHash', - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - currentWallet: 'wallet/currentWallet', - stagedTransactions: 'wallet/stagedTransactions', - signedTransactions: 'wallet/signedTransactions', - })}, + computed: { + ...mapGetters({ + generationHash: 'network/generationHash', + networkType: 'network/networkType', + currentWallet: 'wallet/currentWallet', + stagedTransactions: 'wallet/stagedTransactions', + signedTransactions: 'wallet/signedTransactions', + }), + }, }) export class ModalTransactionConfirmationTs extends Vue { @@ -64,19 +63,12 @@ export class ModalTransactionConfirmationTs extends Vue { */ public networkType: NetworkType - /** - * Currently active account - * @see {Store.Account} - * @var {AccountsModel} - */ - public currentAccount: AccountsModel - /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /** * List of transactions on-stage @@ -105,7 +97,7 @@ export class ModalTransactionConfirmationTs extends Vue { */ public get isUsingHardwareWallet(): boolean { // XXX should use "stagedTransaction.signer" to identify wallet - return WalletType.TREZOR === this.currentWallet.values.get('type') + return WalletType.TREZOR === this.currentWallet.type } /** @@ -113,7 +105,7 @@ export class ModalTransactionConfirmationTs extends Vue { * @type {boolean} */ public get show(): boolean { - return this.visible + return this.visible // && !!this.stagedTransactions // && this.stagedTransactions.length > 0 } @@ -174,28 +166,25 @@ export class ModalTransactionConfirmationTs extends Vue { * that has been unlocked. Subsequently it will also announce * the signed transaction. * - * @param {Password} password - * @return {void} */ public async onAccountUnlocked({account}: {account: Account}): Promise { // - log about unlock success this.$store.dispatch('diagnostic/ADD_INFO', `Account ${account.address.plain()} unlocked successfully.`) - + // - get transaction stage config const options = this.$store.getters['wallet/stageOptions'] const service = new TransactionService(this.$store) - let signedTransactions: SignedTransaction[] + let signedTransactions: SignedTransaction[] = [] // - case 1 "is multisig": must create hash lock (aggregate bonded pre-requirement) if (options.isMultisig) { // - multisig account "issues" aggregate bonded - const currentSigner = this.$store.getters['wallet/currentSigner'] + const currentSigner: Signer = this.$store.getters['wallet/currentSigner'] const multisigAccount = PublicAccount.createFromPublicKey( - currentSigner.values.get('publicKey'), + currentSigner.publicKey, this.networkType, ) - // - use multisig public account and cosignatory to sign signedTransactions = service.signMultisigStagedTransactions(multisigAccount, account) } @@ -213,7 +202,7 @@ export class ModalTransactionConfirmationTs extends Vue { // - notify about successful transaction announce const debug = `Count of transactions signed: ${signedTransactions.length}` - this.$store.dispatch('notification/ADD_DEBUG', debug) + this.$store.dispatch('diagnostic/ADD_DEBUG', debug) this.$store.dispatch('notification/ADD_SUCCESS', 'success_transactions_signed') this.$emit('success', account.publicAccount) this.show = false diff --git a/src/views/modals/ModalTransactionCosignature/ModalTransactionCosignatureTs.ts b/src/views/modals/ModalTransactionCosignature/ModalTransactionCosignatureTs.ts index 6fe3b8e15..dca41b464 100644 --- a/src/views/modals/ModalTransactionCosignature/ModalTransactionCosignatureTs.ts +++ b/src/views/modals/ModalTransactionCosignature/ModalTransactionCosignatureTs.ts @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' -import {CosignatureSignedTransaction, Account, AggregateTransaction, AggregateTransactionCosignature} from 'symbol-sdk' +import {Component, Prop, Vue} from 'vue-property-decorator' +import { + Account, + AggregateTransaction, + AggregateTransactionCosignature, + CosignatureSignedTransaction, +} from 'symbol-sdk' import {mapGetters} from 'vuex' -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel' +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel' import {TransactionService} from '@/services/TransactionService' import {BroadcastResult} from '@/core/transactions/BroadcastResult' @@ -51,9 +56,9 @@ export class ModalTransactionCosignatureTs extends Vue { /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /// region computed properties /** @@ -79,11 +84,11 @@ export class ModalTransactionCosignatureTs extends Vue { */ public get isUsingHardwareWallet(): boolean { // XXX should use "stagedTransaction.signer" to identify wallet - return WalletType.TREZOR === this.currentWallet.values.get('type') + return WalletType.TREZOR === this.currentWallet.type } public get needsCosignature(): boolean { - const currentPubAccount = this.currentWallet.objects.publicAccount + const currentPubAccount = WalletModel.getObjects(this.currentWallet).publicAccount return !this.transaction.signedByAccount(currentPubAccount) } @@ -106,7 +111,7 @@ export class ModalTransactionCosignatureTs extends Vue { const service = new TransactionService(this.$store) // - log about transaction signature success - this.$store.dispatch('diagnostic/ADD_INFO', `Co-signed ${transactions.length} Transaction(s) with Hardware Wallet`) + this.$store.dispatch('diagnostic/ADD_INFO', 'Co-signed ' + transactions.length + ' Transaction(s) with Hardware Wallet') // - broadcast signed transactions const results: BroadcastResult[] = await service.announceCosignatureTransactions(transactions) @@ -134,7 +139,7 @@ export class ModalTransactionCosignatureTs extends Vue { */ public async onAccountUnlocked({account}: {account: Account}) { // - log about unlock success - this.$store.dispatch('diagnostic/ADD_INFO', `Account ${account.address.plain()} unlocked successfully.`) + this.$store.dispatch('diagnostic/ADD_INFO', 'Account ' + account.address.plain() + ' unlocked successfully.') // - sign cosignature transaction const service = new TransactionService(this.$store) diff --git a/src/views/modals/ModalTransactionDetails/ModalTransactionDetailsTs.ts b/src/views/modals/ModalTransactionDetails/ModalTransactionDetailsTs.ts index 51826466d..90c65869d 100644 --- a/src/views/modals/ModalTransactionDetails/ModalTransactionDetailsTs.ts +++ b/src/views/modals/ModalTransactionDetails/ModalTransactionDetailsTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,7 +35,7 @@ export class ModalTransactionDetailsTs extends Vue { }) transaction: Transaction /// region computed properties - + /** * Visibility state * @type {boolean} diff --git a/src/views/pages/accounts/LoginPageTs.ts b/src/views/pages/accounts/LoginPageTs.ts index 28dc5446c..89deac724 100644 --- a/src/views/pages/accounts/LoginPageTs.ts +++ b/src/views/pages/accounts/LoginPageTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,34 +16,30 @@ import {mapGetters} from 'vuex' import {Component, Vue} from 'vue-property-decorator' import {NetworkType, Password} from 'symbol-sdk' - // internal dependencies import {$eventBus} from '@/events' import {NotificationType} from '@/core/utils/NotificationType' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {WalletsRepository} from '@/repositories/WalletsRepository' -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel} from '@/core/database/entities/WalletsModel' -import {SettingsModel} from '@/core/database/entities/SettingsModel' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {WalletModel} from '@/core/database/entities/WalletModel' import {AccountService} from '@/services/AccountService' -import {SettingService} from '@/services/SettingService' - // child components // @ts-ignore -import {ValidationProvider, ValidationObserver} from 'vee-validate' +import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import LanguageSelector from '@/components/LanguageSelector/LanguageSelector.vue' - // configuration import appConfig from '@/../config/app.conf.json' +import {SettingService} from '@/services/SettingService' +import {SettingsModel} from '@/core/database/entities/SettingsModel' +import {WalletService} from '@/services/WalletService' @Component({ computed: { ...mapGetters({ - currentLanguage: 'app/currentLanguage', + language: 'app/currentLanguage', currentAccount: 'account/currentAccount', isAuthenticated: 'account/isAuthenticated', }), @@ -69,7 +65,7 @@ export default class LoginPageTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * List of languages @@ -80,15 +76,11 @@ export default class LoginPageTs extends Vue { /** * Accounts repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accountsRepository = new AccountsRepository() + public accountService = new AccountService() - /** - * Accounts repository - * @var {WalletsRepository} - */ - public walletsRepository = new WalletsRepository() + public walletService = new WalletService() /** * Validation rules @@ -100,7 +92,7 @@ export default class LoginPageTs extends Vue { * Network types * @var {NetworkNodeEntry[]} */ - public networkTypeList: {value: NetworkType, label: string}[] = [ + public networkTypeList: { value: NetworkType, label: string }[] = [ {value: NetworkType.MIJIN_TEST, label: 'MIJIN_TEST'}, {value: NetworkType.MAIN_NET, label: 'MAIN_NET'}, {value: NetworkType.TEST_NET, label: 'TEST_NET'}, @@ -125,10 +117,12 @@ export default class LoginPageTs extends Vue { this.$store.commit('app/SET_LANGUAGE', lang) } - get accountsClassifiedByNetworkType() { - const repository = new AccountsRepository() - return repository.getNamesByNetworkType() + get accountsClassifiedByNetworkType(): Map { + const accounts = this.accountService.getAccounts() + return new Map(accounts.map(i => [ i.networkType, i.accountName ])) + } + /// end-region computed properties getter/setter /** @@ -137,24 +131,24 @@ export default class LoginPageTs extends Vue { */ public mounted() { if (this.currentAccount) { - this.formItems.currentAccountName = this.currentAccount.values.get('accountName') + this.formItems.currentAccountName = this.currentAccount.accountName return } // no account pre-selected, select first if available - const accounts = this.accountsRepository.entries() - if (!accounts.size) { + const accounts = this.accountService.getAccounts() + if (!accounts.length) { return } // accounts available, iterate to first account - const firstAccount = this.accountsRepository.collect().shift() - this.formItems.currentAccountName = firstAccount.values.get('accountName') + const firstAccount = accounts[0] + this.formItems.currentAccountName = firstAccount.accountName } /** * Getter for network type label - * @param {NetworkType} networkType + * @param {NetworkType} networkType * @return {string} */ public getNetworkTypeLabel(networkType: NetworkType): string { @@ -171,23 +165,16 @@ export default class LoginPageTs extends Vue { * @return {string} */ public getPasswordHint(): string { - const identifier = this.formItems.currentAccountName - - // if account doesn't exist, authentication is not valid - if (!this.accountsRepository.find(identifier)) { - return '' - } - - // account exists, fetch data - const account: AccountsModel = this.accountsRepository.read(identifier) - return account.values.get('hint') + const accountName = this.formItems.currentAccountName + const account = this.accountService.getAccountByName(accountName) + return account && account.hint || '' } /** * Submit action, validates form and logs in user if valid * @return {void} */ - public submit() { + public async submit() { if (!this.formItems.currentAccountName.length) { return this.$store.dispatch('notification/ADD_ERROR', NotificationType.ACCOUNT_NAME_INPUT_ERROR) } @@ -205,58 +192,59 @@ export default class LoginPageTs extends Vue { * @return {void} */ private async processLogin() { - const identifier = this.formItems.currentAccountName - const accountService = new AccountService(this.$store) - const settingService = new SettingService(this.$store) + const currentAccountName = this.formItems.currentAccountName + const account = this.accountService.getAccountByName(currentAccountName) + const settingService = new SettingService() // if account doesn't exist, authentication is not valid - if (!this.accountsRepository.find(identifier)) { + if (!account) { this.$store.dispatch('diagnostic/ADD_ERROR', 'Invalid login attempt') return this.$router.push({name: 'accounts.login'}) } // account exists, fetch data - const account: AccountsModel = this.accountsRepository.read(identifier) - const settings: SettingsModel = settingService.getSettings(account) - const knownWallets: Map = this.accountsRepository.fetchRelations( - this.walletsRepository, - account, - 'wallets', - ) + const settings: SettingsModel = settingService.getAccountSettings(currentAccountName) + + const knownWallets: WalletModel[] = this.walletService.getKnownWallets(account.wallets) + if (knownWallets.length == 0) { + throw new Error('knownWallets is empty') + } // use service to generate password hash - const passwordHash = accountService.getPasswordHash(new Password(this.formItems.password)) + const passwordHash = AccountService.getPasswordHash(new Password(this.formItems.password)) // read account's password hash and compare - const accountPass = account.values.get('password') + const accountPass = account.password if (accountPass !== passwordHash) { return this.$store.dispatch('notification/ADD_ERROR', NotificationType.WRONG_PASSWORD_ERROR) } // if account setup was not finalized, redirect - if (!account.values.has('seed') || !account.values.get('seed').length) { + if (!account.seed) { this.$store.dispatch('account/SET_CURRENT_ACCOUNT', account) this.$store.dispatch('temporary/SET_PASSWORD', this.formItems.password) - this.$store.dispatch('diagnostic/ADD_WARNING', `Account has not setup mnemonic pass phrase, redirecting: ${account.getIdentifier()}`) + this.$store.dispatch('diagnostic/ADD_WARNING', 'Account has not setup mnemonic pass phrase, redirecting: ' + currentAccountName) return this.$router.push({name: 'accounts.createAccount.generateMnemonic'}) } // read default wallet from settings - const defaultWalletId = settings.values.get('default_wallet').length - ? settings.values.get('default_wallet') - : Array.from(knownWallets.values()).shift().getIdentifier() - const defaultWallet = Array.from(knownWallets.values()).filter( - w => w.getIdentifier() === defaultWalletId, - ).shift() + const defaultWalletId = settings.defaultWallet ? settings.defaultWallet : knownWallets[0].id + if (!defaultWalletId) { + throw new Error('defaultWalletId could not be resolved') + } + const defaultWallet = knownWallets.find(w => w.id == defaultWalletId) + if (!defaultWallet) { + throw new Error(`defaultWallet could not be resolved from id ${defaultWalletId}`) + } // LOGIN SUCCESS: update app state await this.$store.dispatch('account/SET_CURRENT_ACCOUNT', account) - await this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: defaultWallet}) - this.$store.dispatch('wallet/SET_KNOWN_WALLETS', account.values.get('wallets')) - this.$store.dispatch('diagnostic/ADD_DEBUG', `Account login successful with identifier: ${account.getIdentifier()}`) + await this.$store.dispatch('wallet/SET_CURRENT_WALLET', defaultWallet) + this.$store.dispatch('wallet/SET_KNOWN_WALLETS', account.wallets) + this.$store.dispatch('diagnostic/ADD_DEBUG', 'Account login successful with currentAccountName: ' + currentAccountName) - $eventBus.$emit('onLogin', identifier) + $eventBus.$emit('onLogin', currentAccountName) return this.$router.push({name: 'dashboard'}) } } diff --git a/src/views/pages/accounts/create-account/CreateAccountTs.ts b/src/views/pages/accounts/create-account/CreateAccountTs.ts index e25b63147..f6342a2e8 100644 --- a/src/views/pages/accounts/create-account/CreateAccountTs.ts +++ b/src/views/pages/accounts/create-account/CreateAccountTs.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' @Component export default class CreateAccountTs extends Vue { diff --git a/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts b/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts index cae3483a2..dc8f2da15 100644 --- a/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts +++ b/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts @@ -1,37 +1,37 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {NetworkType, Password} from 'symbol-sdk' import {MnemonicPassPhrase} from 'symbol-hd-wallets' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' import {WalletService} from '@/services/WalletService' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {WalletsRepository} from '@/repositories/WalletsRepository' import {NotificationType} from '@/core/utils/NotificationType' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {AccountService} from '@/services/AccountService' @Component({ - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - currentPassword: 'temporary/password', - currentMnemonic: 'temporary/mnemonic', - })}, + computed: { + ...mapGetters({ + networkType: 'network/networkType', + currentAccount: 'account/currentAccount', + currentPassword: 'temporary/password', + currentMnemonic: 'temporary/mnemonic', + }), + }, }) export default class FinalizeTs extends Vue { /** @@ -46,7 +46,7 @@ export default class FinalizeTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Temporary stored password @@ -66,29 +66,13 @@ export default class FinalizeTs extends Vue { * Wallet Service * @var {WalletService} */ - public walletService: WalletService - - /** - * Wallets Repository - * @var {WalletsRepository} - */ - public walletsRepository: WalletsRepository + public walletService: WalletService = new WalletService() /** * Accounts Repository - * @var {AccountsRepository} - */ - public accountsRepository: AccountsRepository - - /** - * Hook called when the page is mounted - * @return {void} + * @var {AccountService} */ - public mounted() { - this.walletService = new WalletService(this.$store) - this.walletsRepository = new WalletsRepository() - this.accountsRepository = new AccountsRepository() - } + public accountService: AccountService = new AccountService() /** * Finalize the account creation process by adding @@ -106,28 +90,23 @@ export default class FinalizeTs extends Vue { ) // add wallet to account - const wallets = this.currentAccount.values.get('wallets') - wallets.push(wallet.getIdentifier()) - this.currentAccount.values.set('wallets', wallets) + const wallets = [ ...this.currentAccount.wallets, wallet.id ] // use repository for storage - this.walletsRepository.create(wallet.values) - this.accountsRepository.update( - this.currentAccount.getIdentifier(), - this.currentAccount.values, - ) + this.walletService.saveWallet(wallet) + + this.accountService.updateWallets(this.currentAccount, wallets) // execute store actions this.$store.dispatch('account/ADD_WALLET', wallet) - this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: wallet}) + this.$store.dispatch('wallet/SET_CURRENT_WALLET', wallet) this.$store.dispatch('wallet/SET_KNOWN_WALLETS', wallets) this.$store.dispatch('temporary/RESET_STATE') this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) // flush and continue return this.$router.push({name: 'dashboard'}) - } - catch (error) { + } catch (error) { throw new Error(error) } } diff --git a/src/views/pages/accounts/create-account/generate-mnemonic/GenerateMnemonicTs.ts b/src/views/pages/accounts/create-account/generate-mnemonic/GenerateMnemonicTs.ts index 2d9881ca1..a17336520 100644 --- a/src/views/pages/accounts/create-account/generate-mnemonic/GenerateMnemonicTs.ts +++ b/src/views/pages/accounts/create-account/generate-mnemonic/GenerateMnemonicTs.ts @@ -13,23 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {Password} from 'symbol-sdk' import {MnemonicPassPhrase} from 'symbol-hd-wallets' import CryptoJS from 'crypto-js' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' +import {AccountModel} from '@/core/database/entities/AccountModel' import {AESEncryptionService} from '@/services/AESEncryptionService' import {NotificationType} from '@/core/utils/NotificationType' -import {AccountsRepository} from '@/repositories/AccountsRepository' +import {AccountService} from '@/services/AccountService' @Component({ - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - currentPassword: 'temporary/password', - })}, + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + currentPassword: 'temporary/password', + }), + }, }) export default class GenerateMnemonicTs extends Vue { /** @@ -37,7 +38,7 @@ export default class GenerateMnemonicTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Previous step's password @@ -48,9 +49,9 @@ export default class GenerateMnemonicTs extends Vue { /** * Accounts repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accounts: AccountsRepository + public accountService: AccountService /** * Whether component should track mouse move @@ -75,7 +76,7 @@ export default class GenerateMnemonicTs extends Vue { * @return {void} */ public mounted() { - this.accounts = new AccountsRepository() + this.accountService = new AccountService() } /** @@ -112,8 +113,7 @@ export default class GenerateMnemonicTs extends Vue { ) // update currentAccount instance and storage - this.currentAccount.values.set('seed', encSeed) - this.accounts.update(this.currentAccount.getIdentifier(), this.currentAccount.values) + this.accountService.updateSeed(this.currentAccount, encSeed) // update state await this.$store.dispatch('account/SET_CURRENT_ACCOUNT', this.currentAccount) @@ -122,8 +122,7 @@ export default class GenerateMnemonicTs extends Vue { // redirect return this.$router.push({name: 'accounts.createAccount.showMnemonic'}) - } - catch (error) { + } catch (error) { console.log('An error happened while generating Mnenomic:', error) this.$store.dispatch('notification/ADD_ERROR', NotificationType.MNEMONIC_GENERATION_ERROR) } diff --git a/src/views/pages/accounts/create-account/show-mnemonic/ShowMnemonicTs.ts b/src/views/pages/accounts/create-account/show-mnemonic/ShowMnemonicTs.ts index ce75d067e..12ae006a9 100644 --- a/src/views/pages/accounts/create-account/show-mnemonic/ShowMnemonicTs.ts +++ b/src/views/pages/accounts/create-account/show-mnemonic/ShowMnemonicTs.ts @@ -14,11 +14,10 @@ * limitations under the License. */ import {mapGetters} from 'vuex' -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' import {MnemonicPassPhrase} from 'symbol-hd-wallets' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' +import {AccountModel} from '@/core/database/entities/AccountModel' @Component({ computed: {...mapGetters({ @@ -32,7 +31,7 @@ export default class ShowMnemonicTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Temporary Mnemonic pass phrase diff --git a/src/views/pages/accounts/create-account/verify-mnemonic/VerifyMnemonicTs.ts b/src/views/pages/accounts/create-account/verify-mnemonic/VerifyMnemonicTs.ts index 8cf6e7d2b..1338dfd65 100644 --- a/src/views/pages/accounts/create-account/verify-mnemonic/VerifyMnemonicTs.ts +++ b/src/views/pages/accounts/create-account/verify-mnemonic/VerifyMnemonicTs.ts @@ -1,25 +1,23 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {MnemonicPassPhrase} from 'symbol-hd-wallets' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' - +import {AccountModel} from '@/core/database/entities/AccountModel' // child components // @ts-ignore import MnemonicVerification from '@/components/MnemonicVerification/MnemonicVerification.vue' @@ -28,10 +26,11 @@ import MnemonicVerification from '@/components/MnemonicVerification/MnemonicVeri components: { MnemonicVerification, }, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - currentMnemonic: 'temporary/mnemonic', - }), + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + currentMnemonic: 'temporary/mnemonic', + }), }, }) export default class VerifyMnemonicTs extends Vue { @@ -40,7 +39,7 @@ export default class VerifyMnemonicTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Temporary Mnemonic pass phrase @@ -52,5 +51,6 @@ export default class VerifyMnemonicTs extends Vue { get mnemonicWordsList(): string[] { return this.currentMnemonic.plain.split(' ') } + /// end-region computed properties getter/setter } diff --git a/src/views/pages/accounts/import-account/ImportAccountTs.ts b/src/views/pages/accounts/import-account/ImportAccountTs.ts index 6dc3b98ba..c10327f83 100644 --- a/src/views/pages/accounts/import-account/ImportAccountTs.ts +++ b/src/views/pages/accounts/import-account/ImportAccountTs.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' @Component export default class ImportAccountTs extends Vue { diff --git a/src/views/pages/accounts/import-account/import-mnemonic/ImportMnemonicTs.ts b/src/views/pages/accounts/import-account/import-mnemonic/ImportMnemonicTs.ts index f5054d656..ddcbf27e0 100644 --- a/src/views/pages/accounts/import-account/import-mnemonic/ImportMnemonicTs.ts +++ b/src/views/pages/accounts/import-account/import-mnemonic/ImportMnemonicTs.ts @@ -1,34 +1,34 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import { Vue, Component } from 'vue-property-decorator' -import { mapGetters } from 'vuex' -import { MnemonicPassPhrase } from 'symbol-hd-wallets' - +import {Component, Vue} from 'vue-property-decorator' +import {mapGetters} from 'vuex' +import {MnemonicPassPhrase} from 'symbol-hd-wallets' // internal dependencies -import { AccountsModel } from '@/core/database/entities/AccountsModel' -import { AccountsRepository } from '@/repositories/AccountsRepository' -import { NotificationType } from '@/core/utils/NotificationType' -import { Password } from 'symbol-sdk' -import { AESEncryptionService } from '@/services/AESEncryptionService' +import {AccountModel} from '@/core/database/entities/AccountModel' + +import {NotificationType} from '@/core/utils/NotificationType' +import {Password} from 'symbol-sdk' +import {AESEncryptionService} from '@/services/AESEncryptionService' // @ts-ignore import MnemonicInput from '@/components/MnemonicInput/MnemonicInput.vue' +import {AccountService} from '@/services/AccountService' @Component({ - components:{MnemonicInput}, + components: {MnemonicInput}, computed: { ...mapGetters({ currentAccount: 'account/currentAccount', @@ -42,7 +42,7 @@ export default class ImportMnemonicTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Previous step's password @@ -53,9 +53,9 @@ export default class ImportMnemonicTs extends Vue { /** * Account repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accounts: AccountsRepository + public accountService: AccountService /** * Form items @@ -66,15 +66,16 @@ export default class ImportMnemonicTs extends Vue { } /** * @description: Receive the Input words - * @type: Array + * @type: Array */ - public wordsArray: Array=[] + public wordsArray: Array = [] + /** * Hook called when the component is mounted * @return {void} */ public mounted() { - this.accounts = new AccountsRepository() + this.accountService = new AccountService() } /** @@ -83,23 +84,24 @@ export default class ImportMnemonicTs extends Vue { */ public deleteAccountAndBack() { // - delete the temporary account from storage - const identifier = this.currentAccount.getIdentifier() - this.accounts.delete(identifier) + this.accountService.deleteAccount(this.currentAccount.accountName) this.$store.dispatch('account/RESET_STATE') // - back to previous page - this.$router.push({ name: 'accounts.importAccount.info' }) + this.$router.push({name: 'accounts.importAccount.info'}) } + /** * @description: receive input words and control the ui * @return: void */ - public setSeed(wordsArray){ + public setSeed(wordsArray) { this.wordsArray = wordsArray - if(wordsArray.length > 0){ + if (wordsArray.length > 0) { this.formItems.seed = wordsArray.join(' ') } } + /** * Process to mnemonic pass phrase verification * @return {void} @@ -123,18 +125,15 @@ export default class ImportMnemonicTs extends Vue { this.currentPassword, ) - // update currentAccount instance and storage - this.currentAccount.values.set('seed', encSeed) - this.accounts.update(this.currentAccount.getIdentifier(), this.currentAccount.values) + this.accountService.updateSeed(this.currentAccount, encSeed) // update state this.$store.dispatch('notification/ADD_SUCCESS', this.$t('Generate_entropy_increase_success')) this.$store.dispatch('temporary/SET_MNEMONIC', mnemonic.plain) // redirect - return this.$router.push({ name: 'accounts.importAccount.walletSelection' }) - } - catch(e) { + return this.$router.push({name: 'accounts.importAccount.walletSelection'}) + } catch (e) { console.log('An error happened while importing Mnenomic:', e) return this.$store.dispatch('notification/ADD_ERROR', this.$t('invalid_mnemonic_input')) } diff --git a/src/views/pages/accounts/import-account/import-strategy/ImportStrategyTs.ts b/src/views/pages/accounts/import-account/import-strategy/ImportStrategyTs.ts index 2950abbde..10e1940c6 100644 --- a/src/views/pages/accounts/import-account/import-strategy/ImportStrategyTs.ts +++ b/src/views/pages/accounts/import-account/import-strategy/ImportStrategyTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,6 @@ */ import Vue from 'vue' import Component from 'vue-class-component' - // resources import {walletTypeImages} from '@/views/resources/Images' @@ -54,7 +53,7 @@ export default class ImportStrategyTs extends Vue { /** * Redirect user to clicked route - * @param link + * @param link */ public redirect(routeName: string) { if (!routeName || !routeName.length) { @@ -64,7 +63,7 @@ export default class ImportStrategyTs extends Vue { return this.$router.push({ name: routeName, params: { - nextPage:'accounts.importAccount.importMnemonic', + nextPage: 'accounts.importAccount.importMnemonic', }, }) } diff --git a/src/views/pages/accounts/import-account/wallet-selection/WalletSelectionTs.ts b/src/views/pages/accounts/import-account/wallet-selection/WalletSelectionTs.ts index ec5f545be..797552969 100644 --- a/src/views/pages/accounts/import-account/wallet-selection/WalletSelectionTs.ts +++ b/src/views/pages/accounts/import-account/wallet-selection/WalletSelectionTs.ts @@ -1,43 +1,42 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Vue, Component} from 'vue-property-decorator' +import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {Password, MosaicId, Address, SimpleWallet} from 'symbol-sdk' +import {AccountInfo, Address, MosaicId, Password, SimpleWallet} from 'symbol-sdk' import {MnemonicPassPhrase} from 'symbol-hd-wallets' - // internal dependencies -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel' -import {DerivationService, DerivationPathLevels} from '@/services/DerivationService' +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel' +import {DerivationPathLevels, DerivationService} from '@/services/DerivationService' import {WalletService} from '@/services/WalletService' -import {MosaicService} from '@/services/MosaicService' -import {WalletsRepository} from '@/repositories/WalletsRepository' -import {AccountsRepository} from '@/repositories/AccountsRepository' import {NotificationType} from '@/core/utils/NotificationType' import {Formatters} from '@/core/utils/Formatters' - // child components // @ts-ignore import MosaicAmountDisplay from '@/components/MosaicAmountDisplay/MosaicAmountDisplay.vue' +import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyModel' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {AccountService} from '@/services/AccountService' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' @Component({ computed: { ...mapGetters({ networkType: 'network/networkType', networkMosaic: 'mosaic/networkMosaic', + networkCurrency: 'mosaic/networkCurrency', currentAccount: 'account/currentAccount', currentPassword: 'temporary/password', currentMnemonic: 'temporary/mnemonic', @@ -64,7 +63,7 @@ export default class WalletSelectionTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Temporary stored password @@ -92,23 +91,11 @@ export default class WalletSelectionTs extends Vue { */ public walletService: WalletService - /** - * Mosaic Service - * @var {MosaicService} - */ - public mosaicService: MosaicService - - /** - * Wallets Repository - * @var {WalletsRepository} - */ - public walletsRepository: WalletsRepository - /** * Accounts Repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accountsRepository: AccountsRepository + public accountService: AccountService = new AccountService() /** * List of addresses @@ -128,16 +115,15 @@ export default class WalletSelectionTs extends Vue { */ public selectedAccounts: number[] = [] + public networkCurrency: NetworkCurrencyModel + /** * Hook called when the page is mounted * @return {void} */ async mounted() { - this.derivation = new DerivationService(this.$store) - this.walletService = new WalletService(this.$store) - this.mosaicService = new MosaicService(this.$store) - this.walletsRepository = new WalletsRepository() - this.accountsRepository = new AccountsRepository() + this.derivation = new DerivationService() + this.walletService = new WalletService() Vue.nextTick().then(() => { setTimeout(() => this.initAccounts(), 200) @@ -161,36 +147,31 @@ export default class WalletSelectionTs extends Vue { try { // create wallet models const wallets = this.createWalletsFromPathIndexes(this.selectedAccounts) - + // save newly created wallets wallets.forEach((wallet, index) => { // Store wallets using repository - this.walletsRepository.create(wallet.values) + this.walletService.saveWallet(wallet) // set current wallet - if (index === 0) this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: wallet}) + if (index === 0) this.$store.dispatch('wallet/SET_CURRENT_WALLET', wallet) // add wallets to account this.$store.dispatch('account/ADD_WALLET', wallet) }) // get wallets identifiers - const walletIdentifiers = wallets.map(wallet => wallet.getIdentifier()) + const walletIdentifiers = wallets.map(wallet => wallet.id) // set known wallets this.$store.dispatch('wallet/SET_KNOWN_WALLETS', walletIdentifiers) - // add wallets to account - this.currentAccount.values.set('wallets', walletIdentifiers) - // store account using repository - this.accountsRepository.update( - this.currentAccount.getIdentifier(), - this.currentAccount.values, - ) + this.accountService.updateWallets(this.currentAccount, walletIdentifiers) + // execute store actions this.$store.dispatch('temporary/RESET_STATE') this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) return this.$router.push({name: 'accounts.importAccount.finalize'}) - } catch(error) { + } catch (error) { return this.$store.dispatch( 'notification/ADD_ERROR', error, @@ -206,7 +187,7 @@ export default class WalletSelectionTs extends Vue { // - generate addresses this.addressesList = this.walletService.getAddressesFromMnemonic( new MnemonicPassPhrase(this.currentMnemonic), - this.currentAccount.values.get('networkType'), + this.currentAccount.networkType, 10, ) @@ -217,17 +198,35 @@ export default class WalletSelectionTs extends Vue { ) if (!accountsInfo) return // map balances - this.addressMosaicMap = this.mosaicService.mapBalanceByAddress( + this.addressMosaicMap = this.mapBalanceByAddress( accountsInfo, this.networkMosaic, ) } + public mapBalanceByAddress(accountsInfo: AccountInfo[], mosaic: MosaicId): Record { + return accountsInfo.map(({mosaics, address}) => { + // - check balance + const hasNetworkMosaic = mosaics.find(mosaicOwned => mosaicOwned.id.equals(mosaic)) + + // - account doesn't hold network mosaic + if (hasNetworkMosaic === undefined) { + return null + } + // - map balance to address + const balance = hasNetworkMosaic.amount.compact() + return { + address: address.plain(), + balance: balance * Math.pow(10, this.networkCurrency.divisibility), + } + }).reduce((acc, {address, balance}) => ({...acc, [address]: balance}), {}) + } + /** * Create a wallet instance from mnemonic and path - * @return {WalletsModel} + * @return {WalletModel} */ - private createWalletsFromPathIndexes(indexes: number[]): WalletsModel[] { + private createWalletsFromPathIndexes(indexes: number[]): WalletModel[] { const paths = indexes.map(index => { if (index == 0) return WalletService.DEFAULT_WALLET_PATH @@ -241,7 +240,7 @@ export default class WalletSelectionTs extends Vue { const accounts = this.walletService.generateAccountsFromPaths( new MnemonicPassPhrase(this.currentMnemonic), - this.currentAccount.values.get('networkType'), + this.currentAccount.networkType, paths, ) @@ -250,21 +249,24 @@ export default class WalletSelectionTs extends Vue { 'SeedWallet', this.currentPassword, account.privateKey, - this.currentAccount.values.get('networkType'), + this.currentAccount.networkType, )) - return simpleWallets.map((simpleWallet, i) => - new WalletsModel(new Map([ - [ 'accountName', this.currentAccount.values.get('accountName') ], - [ 'name', `Seed Wallet${indexes[i] + 1}` ], - [ 'type', WalletType.fromDescriptor('Seed') ], - [ 'address', simpleWallet.address.plain() ], - [ 'publicKey', accounts[i].publicKey ], - [ 'encPrivate', simpleWallet.encryptedPrivateKey.encryptedKey ], - [ 'encIv', simpleWallet.encryptedPrivateKey.iv ], - [ 'path', paths[i] ], - [ 'isMultisig', false ], - ]))) + return simpleWallets.map((simpleWallet, i) => { + return { + id: SimpleObjectStorage.generateIdentifier(), + accountName: this.currentAccount.accountName, + name: `Seed Wallet${indexes[i] + 1}`, + node: '', + type: WalletType.SEED, + address: simpleWallet.address.plain(), + publicKey: accounts[i].publicKey, + encPrivate: simpleWallet.encryptedPrivateKey.encryptedKey, + encIv: simpleWallet.encryptedPrivateKey.iv, + path: paths[i], + isMultisig: false, + } + }) } /** diff --git a/src/views/pages/assets/AssetDashboardWrap/AssetDashboardWrap.vue b/src/views/pages/assets/AssetDashboardWrap/AssetDashboardWrap.vue index a929afa7a..8341347eb 100644 --- a/src/views/pages/assets/AssetDashboardWrap/AssetDashboardWrap.vue +++ b/src/views/pages/assets/AssetDashboardWrap/AssetDashboardWrap.vue @@ -13,8 +13,7 @@ diff --git a/src/views/pages/wallets/WalletBackupPage/WalletBackupPageTs.ts b/src/views/pages/wallets/WalletBackupPage/WalletBackupPageTs.ts index 76d270c38..7cd4eab63 100644 --- a/src/views/pages/wallets/WalletBackupPage/WalletBackupPageTs.ts +++ b/src/views/pages/wallets/WalletBackupPage/WalletBackupPageTs.ts @@ -16,10 +16,8 @@ // external dependencies import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' - +import {WalletModel} from '@/core/database/entities/WalletModel' // child components // @ts-ignore import WalletBackupOptions from '@/components/WalletBackupOptions/WalletBackupOptions.vue' @@ -36,9 +34,9 @@ export class WalletBackupPageTs extends Vue { /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} + * @var {WalletModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel /// region computed properties getter/setter /// end-region computed properties getter/setter diff --git a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue index 72475f3f9..0e149c71e 100644 --- a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue +++ b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue @@ -11,7 +11,7 @@
- +
@@ -27,7 +27,7 @@
-
+
{{ $t('wallets_flags_default_wallet') }}
@@ -37,7 +37,7 @@
-
+
{{ $t('wallets_flags_default_wallet') }}
diff --git a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPageTs.ts b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPageTs.ts index 62f2301c4..d42a01c04 100644 --- a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPageTs.ts +++ b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPageTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,10 +16,6 @@ // external dependencies import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - -// internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' - // child components // @ts-ignore import WalletNameDisplay from '@/components/WalletNameDisplay/WalletNameDisplay.vue' @@ -39,6 +35,7 @@ import WalletActions from '@/components/WalletActions/WalletActions.vue' import WalletLinks from '@/components/WalletLinks/WalletLinks.vue' // @ts-ignore import WalletAliasDisplay from '@/components/WalletAliasDisplay/WalletAliasDisplay.vue' +import {WalletModel} from '@/core/database/entities/WalletModel' @Component({ components: { @@ -52,10 +49,12 @@ import WalletAliasDisplay from '@/components/WalletAliasDisplay/WalletAliasDispl WalletPublicKeyDisplay, WalletAliasDisplay, }, - computed: {...mapGetters({ - defaultWallet: 'app/defaultWallet', - currentWallet: 'wallet/currentWallet', - })}, + computed: { + ...mapGetters({ + defaultWallet: 'app/defaultWallet', + currentWallet: 'wallet/currentWallet', + }), + }, }) export class WalletDetailsPageTs extends Vue { /** @@ -68,24 +67,8 @@ export class WalletDetailsPageTs extends Vue { /** * Currently active wallet * @see {Store.Wallet} - * @var {WalletsModel} - */ - public currentWallet: WalletsModel - - /** - * Name form visibility - * @type {boolean} + * @var {WalletModel} */ - hasNameForm: boolean = false - /// region computed properties getter/setter - /// end-region computed properties getter/setter + public currentWallet: WalletModel - /** - * Whether the wallet item is a seed wallet - * @param item - * @return {boolean} - */ - public isSeedWallet(wallet: WalletsModel): boolean { - return wallet.values.get('seed') && wallet.values.get('seed').length - } } diff --git a/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPage.vue b/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPage.vue index 6b6fc37c5..d7d2aae34 100644 --- a/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPage.vue +++ b/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPage.vue @@ -10,6 +10,7 @@ diff --git a/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPageTs.ts b/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPageTs.ts index 9ca074586..a3b3a058c 100644 --- a/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPageTs.ts +++ b/src/views/pages/wallets/WalletHarvestingPage/WalletHarvestingPageTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,10 +16,8 @@ // external dependencies import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' - +import {WalletModel} from '@/core/database/entities/WalletModel' // child components // @ts-ignore import WalletActions from '@/components/WalletActions/WalletActions.vue' @@ -28,15 +26,15 @@ import WalletActions from '@/components/WalletActions/WalletActions.vue' components: { WalletActions, }, - computed: {...mapGetters({ - currentWallet: 'wallet/currentWallet', - })}, + computed: { + ...mapGetters({ + currentWallet: 'wallet/currentWallet', + }), + }, }) export class WalletHarvestingPageTs extends Vue { /** * Currently active wallet - * @see {Store.Wallet} - * @var {WalletsModel} */ - public currentWallet: WalletsModel + public currentWallet: WalletModel } diff --git a/src/views/pages/wallets/WalletsTs.ts b/src/views/pages/wallets/WalletsTs.ts index aa7cce304..9277b4c25 100644 --- a/src/views/pages/wallets/WalletsTs.ts +++ b/src/views/pages/wallets/WalletsTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,9 +18,6 @@ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' -import {WalletService} from '@/services/WalletService' - // child components // @ts-ignore import NavigationTabs from '@/components/NavigationTabs/NavigationTabs.vue' @@ -32,39 +29,15 @@ import WalletSelectorPanel from '@/components/WalletSelectorPanel/WalletSelector NavigationTabs, WalletSelectorPanel, }, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - currentWallet: 'wallet/currentWallet', - knownWallets: 'wallet/knownWallets', - })}, + computed: { + ...mapGetters({}), + }, }) export class WalletsTs extends Vue { - /** - * Currently active wallet - * @see {Store.Wallet} - * @var {WalletsModel} - */ - public currentWallet: WalletsModel - - /** - * Known wallets identifiers - * @var {string[]} - */ - public knownWallets: string[] - - /** - * Wallets repository - * @var {WalletService} - */ - public service: WalletService - /** * Argument passed to the navigation component * @var {string} */ public parentRouteName: string = 'wallets' - public created() { - this.service = new WalletService(this.$store) - } } From 7961204f0cee8c00a16bff1917e6bbd2e50cfe4b Mon Sep 17 00:00:00 2001 From: Fernando Boucquez Date: Tue, 7 Apr 2020 13:04:11 -0300 Subject: [PATCH 02/17] Fixes after merge --- __mocks__/Wallets.ts | 28 ++++---- __tests__/services/WalletService.spec.ts | 8 +-- package.json | 2 +- .../DurationInput/DurationInputTs.ts | 51 +++++++++------ .../MosaicBalanceList/MosaicBalanceListTs.ts | 7 +- src/components/TableDisplay/TableDisplayTs.ts | 32 ++++++---- .../TransactionDetailsHeaderTs.ts | 28 ++++---- src/core/validation/ValidationRuleset.ts | 64 ++++++++++--------- src/services/AccountService.ts | 7 +- .../AssetTableService/MosaicTableService.ts | 7 +- .../NamespaceTableService.ts | 4 +- src/services/MosaicService.ts | 7 +- src/services/WalletService.ts | 32 ++++------ .../FormAccountPasswordUpdateTs.ts | 56 +++++++--------- ...ormExtendNamespaceDurationTransactionTs.ts | 17 ++--- .../FormNamespaceRegistrationTransactionTs.ts | 24 +++++-- .../FormTransactionBase.ts | 2 +- .../FormTransferTransactionTs.ts | 17 +++-- .../ModalMnemonicExportTs.ts | 3 +- .../create-account/finalize/FinalizeTs.ts | 2 +- 20 files changed, 206 insertions(+), 192 deletions(-) diff --git a/__mocks__/Wallets.ts b/__mocks__/Wallets.ts index 12901042e..4ef9e8920 100644 --- a/__mocks__/Wallets.ts +++ b/__mocks__/Wallets.ts @@ -17,7 +17,7 @@ import {SimpleWallet, Account, NetworkType, Password} from 'symbol-sdk'; // internal dependencies -import {WalletsModel, WalletType} from '@/core/database/entities/WalletsModel'; +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel'; export const wallet1Params = { walletName: 'wallet_name', @@ -37,16 +37,16 @@ export const simpleWallet1 = SimpleWallet.createFromPrivateKey( wallet1Params.networkType, ) -export const WalletsModel1 = new WalletsModel( - new Map([ - ['accountName', 'account_name'], - ['name', wallet1Params.walletName], - ['type', WalletType.fromDescriptor('Pk')], - ['address', simpleWallet1.address.plain()], - ['publicKey', wallet1Account.publicKey], - ['encPrivate', simpleWallet1.encryptedPrivateKey.encryptedKey], - ['encIv', simpleWallet1.encryptedPrivateKey.iv], - ['path', ''], - ['isMultisig', false], - ]) -) +export const WalletsModel1: WalletModel = { + id: 'someId', + node: '', + accountName: 'account_name', + name: wallet1Params.walletName, + type: WalletType.PRIVATE_KEY, + address: simpleWallet1.address.plain(), + publicKey: wallet1Account.publicKey, + encPrivate: simpleWallet1.encryptedPrivateKey.encryptedKey, + encIv: simpleWallet1.encryptedPrivateKey.iv, + path: '', + isMultisig: false +} diff --git a/__tests__/services/WalletService.spec.ts b/__tests__/services/WalletService.spec.ts index 0b56dff40..20ab60f09 100644 --- a/__tests__/services/WalletService.spec.ts +++ b/__tests__/services/WalletService.spec.ts @@ -115,8 +115,8 @@ describe('services/WalletServices', () => { const service = new WalletService() // get initial encrypted private key values - const initialEncPrivate = WalletsModel1.values.get('encPrivate') - const initialEncIv = WalletsModel1.values.get('encIv') + const initialEncPrivate = WalletsModel1.encPrivate + const initialEncIv = WalletsModel1.encIv // update the model const updatedWallet = service.updateWalletPassword( @@ -124,8 +124,8 @@ describe('services/WalletServices', () => { ) // decrypt the new model's private key - const newEncPrivate = updatedWallet.values.get('encPrivate') - const newEncIv = updatedWallet.values.get('encIv') + const newEncPrivate = updatedWallet.encPrivate + const newEncIv = updatedWallet.encIv const privateKey = new EncryptedPrivateKey(newEncPrivate, newEncIv) .decrypt(new Password('password2')) diff --git a/package.json b/package.json index b791db043..b470afff3 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "rxjs": "^6.5.2", "symbol-hd-wallets": "^0.9.2", "symbol-qr-library": "^0.9.0", - "symbol-sdk": "0.17.4-alpha-202003201053\n", + "symbol-sdk": "0.17.4-alpha-202003201053", "trezor-connect": "^7.0.5", "typescript": "^3.7.2", "utf-8-validate": "^5.0.2", diff --git a/src/components/DurationInput/DurationInputTs.ts b/src/components/DurationInput/DurationInputTs.ts index 9eb7b5f3a..a40f650e8 100644 --- a/src/components/DurationInput/DurationInputTs.ts +++ b/src/components/DurationInput/DurationInputTs.ts @@ -1,23 +1,21 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Vue, Prop} from 'vue-property-decorator' - +import {Component, Prop, Vue} from 'vue-property-decorator' // internal dependencies import {ValidationRuleset} from '@/core/validation/ValidationRuleset' - // child components import {ValidationProvider} from 'vee-validate' // @ts-ignore @@ -25,28 +23,36 @@ import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' -import { TimeHelpers } from '@/core/utils/TimeHelpers' +import {TimeHelpers} from '@/core/utils/TimeHelpers' +import {mapGetters} from 'vuex' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' + @Component({ components: { ValidationProvider, ErrorTooltip, FormRow, }, + computed: { + ...mapGetters({ + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class DurationInputTs extends Vue { - @Prop({ default: '' }) value: string + @Prop({default: ''}) value: string /** * Asset type * @type {('mosaic' | 'namespace')} */ - @Prop({ default: 'mosaic' }) targetAsset: 'mosaic' | 'namespace' + @Prop({default: 'mosaic'}) targetAsset: 'mosaic' | 'namespace' /** * Field label * @type {string} */ - @Prop({ default: 'form_label_duration' }) label: string + @Prop({default: 'form_label_duration'}) label: string /** * Validation rules @@ -55,15 +61,15 @@ export class DurationInputTs extends Vue { public validationRules = ValidationRuleset /** - * the toggle for the display of realativeTime - * @type boolean + * Injected network configuration. */ - @Prop({ default: false }) showRelativeTime: boolean + private networkConfiguration: NetworkConfigurationModel + /** - * relativeTime example: 56d 21h 18m - * @var {string} + * the toggle for the display of realativeTime + * @type boolean */ - public relativeTime=TimeHelpers.durationToRelativeTime(parseInt(this.value)) + @Prop({default: false}) showRelativeTime: boolean /// region computed properties getter/setter public get chosenValue(): string { @@ -71,14 +77,21 @@ export class DurationInputTs extends Vue { } public set chosenValue(amount: string) { - // toSetTheRelative - this.relativeTime = TimeHelpers.durationToRelativeTime(parseInt(amount)) this.$emit('input', amount) } - + + /** + * @return relativeTime example: 56d 21h 18m + */ + public get relativeTime() { + return TimeHelpers.durationToRelativeTime(parseInt(this.value), + this.networkConfiguration.blockGenerationTargetTime) + } + public get validationRule(): string { return this.targetAsset === 'mosaic' ? this.validationRules.duration : this.validationRules.namespaceDuration } + /// end-region computed properties getter/setter } diff --git a/src/components/MosaicBalanceList/MosaicBalanceListTs.ts b/src/components/MosaicBalanceList/MosaicBalanceListTs.ts index b55addd93..50e184565 100644 --- a/src/components/MosaicBalanceList/MosaicBalanceListTs.ts +++ b/src/components/MosaicBalanceList/MosaicBalanceListTs.ts @@ -24,6 +24,7 @@ import {dashboardImages} from '@/views/resources/Images' import {MosaicService} from '@/services/MosaicService' import {MosaicConfigurationModel} from '@/core/database/entities/MosaicConfigurationModel' import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export interface BalanceEntry { id: MosaicId @@ -42,6 +43,7 @@ export interface BalanceEntry { balanceMosaics: 'mosaic/balanceMosaics', networkMosaic: 'mosaic/networkMosaic', currentHeight: 'network/currentHeight', + networkConfiguration: 'network/networkConfiguration', }), }, }) @@ -79,6 +81,8 @@ export class MosaicBalanceListTs extends Vue { public currentHeight: number + private networkConfiguration: NetworkConfigurationModel + /// region computed properties getter/setter /** @@ -105,7 +109,8 @@ export class MosaicBalanceListTs extends Vue { get allBalanceEntries(): BalanceEntry[] { return this.balanceEntries.filter((entry) => { // calculate expiration - const expiration = MosaicService.getExpiration(entry.mosaic, this.currentHeight) + const expiration = MosaicService.getExpiration(entry.mosaic, this.currentHeight, + this.networkConfiguration.blockGenerationTargetTime) // skip if mosaic is expired return expiration !== 'expired' }) diff --git a/src/components/TableDisplay/TableDisplayTs.ts b/src/components/TableDisplay/TableDisplayTs.ts index b6b49a09b..63ef49bf3 100644 --- a/src/components/TableDisplay/TableDisplayTs.ts +++ b/src/components/TableDisplay/TableDisplayTs.ts @@ -63,10 +63,10 @@ export class TableDisplayTs extends Vue { }) assetType: string /** - * Loading state of the data to be shown in the table - * @type {boolean} - */ - loading: boolean =false + * Loading state of the data to be shown in the table + * @type {boolean} + */ + loading: boolean = false /** * Current wallet owned mosaics @@ -167,9 +167,11 @@ export class TableDisplayTs extends Vue { */ protected getService(): AssetTableService { if ('mosaic' === this.assetType) { - return new MosaicTableService(this.currentHeight, this.ownedMosaics) + return new MosaicTableService(this.currentHeight, this.ownedMosaics, + this.networkConfiguration) } else if ('namespace' === this.assetType) { - return new NamespaceTableService(this.currentHeight, this.ownedNamespaces, this.networkConfiguration) + return new NamespaceTableService(this.currentHeight, this.ownedNamespaces, + this.networkConfiguration) } throw new Error(`Asset type '${this.assetType}' does not exist in TableDisplay.`) } @@ -215,14 +217,16 @@ export class TableDisplayTs extends Vue { this.currentPage * this.pageSize, ) } + /** * getter and setter for the showExpired button * */ - get showExpired(): boolean{ + get showExpired(): boolean { return this.filteredBy.fieldName === 'expiration' && this.filteredBy.filteringType === 'show' } - set showExpired(newVal: boolean){ + + set showExpired(newVal: boolean) { this.setFilteredBy('expiration') } @@ -255,7 +259,7 @@ export class TableDisplayTs extends Vue { * Refreshes the owned assets * @returns {void} */ - private async refresh(): void { + private async refresh(): Promise { this.loading = true if ('mosaic' === this.assetType) { await this.$store.dispatch('mosaic/LOAD_MOSAICS') @@ -335,7 +339,8 @@ export class TableDisplayTs extends Vue { protected showAliasForm(rowValues: Record): void { // populate asset form modal props if asset is a mosaic if (this.assetType === 'mosaic') { - this.modalFormsProps.namespaceId = rowValues.name !== 'N/A' ? new NamespaceId(rowValues.name) : null + this.modalFormsProps.namespaceId = rowValues.name !== 'N/A' ? new NamespaceId( + rowValues.name) : null this.modalFormsProps.aliasTarget = new MosaicId(rowValues.hexId) this.modalFormsProps.aliasAction = rowValues.name !== 'N/A' ? AliasAction.Unlink : AliasAction.Link } @@ -394,6 +399,7 @@ export class TableDisplayTs extends Vue { protected closeModal(modalIdentifier: string): void { Vue.set(this.modalFormsVisibility, modalIdentifier, false) } + /** * avoid multiple clicks * @protected @@ -401,14 +407,14 @@ export class TableDisplayTs extends Vue { * @return {void} */ public isRefreshing: boolean = false + protected async onRefresh() { if (!this.isRefreshing) { this.isRefreshing = true try { await this.refresh() - this.$store.dispatch('notification/ADD_SUCCESS', `${this.$t('refresh_success')}`) - } catch{ - this.$store.dispatch('notification/ADD_ERROR', `${this.$t('refresh_failed')}`) + } catch (e) { + console.log('Cannot refresh', e) } this.isRefreshing = false } diff --git a/src/components/TransactionDetailsHeader/TransactionDetailsHeaderTs.ts b/src/components/TransactionDetailsHeader/TransactionDetailsHeaderTs.ts index 13b80a080..cdfa95ee0 100644 --- a/src/components/TransactionDetailsHeader/TransactionDetailsHeaderTs.ts +++ b/src/components/TransactionDetailsHeader/TransactionDetailsHeaderTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,19 +15,15 @@ */ import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NetworkType, MosaicId} from 'symbol-sdk' - +import {MosaicId, NetworkType} from 'symbol-sdk' // internal dependencies import {TransactionViewType} from '@/services/TransactionService' import {Formatters} from '@/core/utils/Formatters' - // configuration -import networkConfig from '@/../config/network.conf.json' -const currentNetworkConfig = networkConfig.networks['testnet-publicTest'] - // child components // @ts-ignore import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ components: { @@ -38,6 +34,7 @@ import TransactionDetailRow from '@/components/TransactionDetails/TransactionDet networkType: 'network/networkType', networkMosaic: 'mosaic/networkMosaic', networkMosaicTicker: 'mosaic/networkMosaicTicker', + networkConfiguration: 'network/networkConfiguration', }), }, }) @@ -68,11 +65,7 @@ export class TransactionDetailsHeaderTs extends Vue { */ public networkMosaicTicker: string - /** - * Explorer base path - * @var {string} - */ - public explorerBaseUrl: string = networkConfig.explorerUrl + private networkConfiguration: NetworkConfigurationModel /** * Formatters @@ -88,7 +81,7 @@ export class TransactionDetailsHeaderTs extends Vue { if (!this.view) return 0 const absoluteFee = this.view.values.get('effectiveFee') || this.view.values.get('maxFee') if (!absoluteFee) return 0 - const networkMosaicDivisibility = currentNetworkConfig.properties.maxMosaicDivisibility + const networkMosaicDivisibility = this.networkConfiguration.maxMosaicDivisibility return absoluteFee / Math.pow(10, networkMosaicDivisibility) } @@ -97,7 +90,7 @@ export class TransactionDetailsHeaderTs extends Vue { * @see {Store.Mosaic} * @type {({ key: string, value: string | boolean, | Mosaic }[])} */ - get items(): {key: string, value: any, isMosaic?: boolean}[] { + get items(): { key: string, value: any, isMosaic?: boolean }[] { return [ {key: 'status', value: `${this.$t(this.view.info ? 'confirmed' : 'unconfirmed')}`}, { @@ -123,7 +116,8 @@ export class TransactionDetailsHeaderTs extends Vue { }, { key: 'deadline', - value: `${this.view.values.get('deadline').value.toLocalDate()} ${this.view.values.get('deadline').value.toLocalTime()}`, + value: `${this.view.values.get('deadline').value.toLocalDate()} ${this.view.values.get( + 'deadline').value.toLocalTime()}`, }, ] } diff --git a/src/core/validation/ValidationRuleset.ts b/src/core/validation/ValidationRuleset.ts index 81db183ee..3acb713f7 100644 --- a/src/core/validation/ValidationRuleset.ts +++ b/src/core/validation/ValidationRuleset.ts @@ -27,37 +27,39 @@ export const createValidationRuleset = ({ maxMosaicDivisibility, maxMosaicDuration, minNamespaceDuration, -}: NetworkConfigurationModel) => ({ - address: 'required|address|addressNetworkType:currentAccount', - accountPassword: 'required|accountPassword', - addressOrAlias: 'required|addressOrAlias|addressOrAliasNetworkType:currentAccount', - amount: `excluded:""|is_not:0|min_value:0|maxDecimals:${maxMosaicDivisibility}|max_value:${maxMosaicAtomicUnits}`, - confirmPassword: 'required|confirmPassword:@newPassword', - divisibility: 'required|min_value:0|max_value:6|integer', - duration: `required|min_value:0|max_value:${maxMosaicDuration}`, - generationHash: 'required|min:64|max:64', - mosaicId: 'required|mosaicId', - message: `max:${maxMessageSize}`, - namespaceDuration: `required|min_value:${minNamespaceDuration}|maxNamespaceDuration`, - namespaceName: { - required: true, - regex: '^[a-z0-9-_]{1,64}$', - }, - subNamespaceName: { - required: true, - regex: '^[a-z0-9-_.]{1,64}$', - }, - password: `required|min:${MIN_PASSWORD_LENGTH}|passwordRegex`, - previousPassword: 'required|confirmLock:cipher', - privateKey: 'min:64|max:64|privateKey', - recipientPublicKey: 'required|publicKey', - supply: `required|integer|min_value: 1|max_value:${maxMosaicAtomicUnits}`, - walletPassword: 'required|confirmWalletPassword:wallet', - url: 'required|url', - newAccountName: 'required|newAccountName', - accountWalletName: 'required|accountWalletName', - addressOrPublicKey: 'addressOrPublicKey', -}) +}: NetworkConfigurationModel) => { + return { + address: 'required|address|addressNetworkType:currentAccount', + accountPassword: 'required|accountPassword', + addressOrAlias: 'required|addressOrAlias|addressOrAliasNetworkType:currentAccount', + amount: `excluded:""|is_not:0|min_value:0|maxDecimals:${maxMosaicDivisibility}|max_value:${maxMosaicAtomicUnits}`, + confirmPassword: 'required|confirmPassword:@newPassword', + divisibility: 'required|min_value:0|max_value:6|integer', + duration: `required|min_value:0|max_value:${maxMosaicDuration}`, + generationHash: 'required|min:64|max:64', + mosaicId: 'required|mosaicId', + message: `max:${maxMessageSize}`, + namespaceDuration: `required|min_value:${minNamespaceDuration}|maxNamespaceDuration`, + namespaceName: { + required: true, + regex: '^[a-z0-9-_]{1,64}$', + }, + subNamespaceName: { + required: true, + regex: '^[a-z0-9-_.]{1,64}$', + }, + password: `required|min:${MIN_PASSWORD_LENGTH}|passwordRegex`, + previousPassword: 'required|confirmLock:cipher', + privateKey: 'min:64|max:64|privateKey', + recipientPublicKey: 'required|publicKey', + supply: `required|integer|min_value: 1|max_value:${maxMosaicAtomicUnits}`, + walletPassword: 'required|confirmWalletPassword:wallet', + url: 'required|url', + newAccountName: 'required|newAccountName', + accountWalletName: 'required|accountWalletName', + addressOrPublicKey: 'addressOrPublicKey', + } +} // TODO ValidationRuleset needs to be created when the network configuration is resolved, UI needs // to use the resolved ValidationResulset ATM rules are using the hardocded ones diff --git a/src/services/AccountService.ts b/src/services/AccountService.ts index 63365327c..0de60fa2b 100644 --- a/src/services/AccountService.ts +++ b/src/services/AccountService.ts @@ -28,7 +28,8 @@ export class AccountService { * The storage to keep user configuration around mosaics. For example, the balance hidden * feature. */ - private readonly accountsStorage = new SimpleObjectStorage>('accounts') + private readonly accountsStorage = new SimpleObjectStorage>( + 'accounts') public getAccounts(): AccountModel[] { @@ -59,8 +60,8 @@ export class AccountService { this.saveAccount({...account, ...{seed}}) } - public updatePassword(account: AccountModel, password: string, hint: string) { - this.saveAccount({...account, ...{password, hint}}) + public updatePassword(account: AccountModel, password: string, hint: string, seed: string) { + this.saveAccount({...account, ...{password, hint, seed}}) } public updateWallets(account: AccountModel, wallets: string[]) { diff --git a/src/services/AssetTableService/MosaicTableService.ts b/src/services/AssetTableService/MosaicTableService.ts index dec1df348..bd42da62c 100644 --- a/src/services/AssetTableService/MosaicTableService.ts +++ b/src/services/AssetTableService/MosaicTableService.ts @@ -17,10 +17,12 @@ import {AssetTableService, TableField} from './AssetTableService' import {MosaicModel} from '@/core/database/entities/MosaicModel' import {MosaicService} from '@/services/MosaicService' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export class MosaicTableService extends AssetTableService { - constructor(currentHeight: number, private readonly mosaics: MosaicModel[]) { + constructor(currentHeight: number, private readonly mosaics: MosaicModel[], + private readonly networkConfiguration: NetworkConfigurationModel) { super(currentHeight) } @@ -51,7 +53,8 @@ export class MosaicTableService extends AssetTableService { const mosaicsInfo = this.mosaics const currentHeight = this.currentHeight return mosaicsInfo.map((mosaicInfo) => { - const expiration = MosaicService.getExpiration(mosaicInfo, currentHeight) + const expiration = MosaicService.getExpiration(mosaicInfo, currentHeight, + this.networkConfiguration.blockGenerationTargetTime) // - map table fields return { 'hexId': mosaicInfo.mosaicIdHex, diff --git a/src/services/AssetTableService/NamespaceTableService.ts b/src/services/AssetTableService/NamespaceTableService.ts index fee5bb982..8fa7e7a60 100644 --- a/src/services/AssetTableService/NamespaceTableService.ts +++ b/src/services/AssetTableService/NamespaceTableService.ts @@ -55,8 +55,8 @@ export class NamespaceTableService extends AssetTableService { 'name': namespaceModel.name, 'expiration': expiration, 'expired': expired, - 'aliasType': this.getAliasType(namespaceInfo), - 'aliasIdentifier': this.getAliasIdentifier(namespaceInfo), + 'aliasType': this.getAliasType(namespaceModel), + 'aliasIdentifier': this.getAliasIdentifier(namespaceModel), } }) } diff --git a/src/services/MosaicService.ts b/src/services/MosaicService.ts index 964c26c10..5b86786e8 100644 --- a/src/services/MosaicService.ts +++ b/src/services/MosaicService.ts @@ -24,6 +24,7 @@ import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyMode import {ObservableHelpers} from '@/core/utils/ObservableHelpers' import {fromIterable} from 'rxjs/internal-compatibility' import {MosaicConfigurationModel} from '@/core/database/entities/MosaicConfigurationModel' +import {TimeHelpers} from '@/core/utils/TimeHelpers' // custom types export type ExpirationStatus = 'unlimited' | 'expired' | string | number @@ -102,8 +103,10 @@ export class MosaicService { * Utility method that returns the mosaic expiration status * @param mosaicInfo the mosaic info * @param currentHeight + * @param blockGenerationTargetTime */ - public static getExpiration(mosaicInfo: MosaicModel, currentHeight: number): ExpirationStatus { + public static getExpiration(mosaicInfo: MosaicModel, currentHeight: number, + blockGenerationTargetTime: number): ExpirationStatus { const duration = mosaicInfo.duration const startHeight = mosaicInfo.height @@ -115,7 +118,7 @@ export class MosaicService { const expiresIn = startHeight + duration - (currentHeight || 0) if (expiresIn <= 0) return 'expired' // number of blocks remaining - return TimeHelpers.durationToRelativeTime(expiresIn) + return TimeHelpers.durationToRelativeTime(expiresIn, blockGenerationTargetTime) } diff --git a/src/services/WalletService.ts b/src/services/WalletService.ts index 820b7edbd..dd9dab717 100755 --- a/src/services/WalletService.ts +++ b/src/services/WalletService.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Account, Address, NetworkType, Password, SimpleWallet} from 'symbol-sdk' +import {Account, Address, EncryptedPrivateKey, NetworkType, Password, SimpleWallet} from 'symbol-sdk' import {ExtendedKey, MnemonicPassPhrase, Wallet} from 'symbol-hd-wallets' // internal dependencies import {DerivationPathLevels, DerivationService} from './DerivationService' @@ -281,44 +281,34 @@ export class WalletService { } - /** * Returns a WalletsModel with an updated SimpleWallet * @param {string} walletIdentifier * @param {Password} oldPassword * @param {Password} newPassword */ - public updateWalletPassword( - wallet: WalletsModel, - oldPassword: Password, - newPassword: Password, - ): WalletsModel { + public updateWalletPassword(wallet: WalletModel, oldPassword: Password, newPassword: Password): WalletModel { // Password modification is not allowed for hardware wallets - if (wallet.values.get('type') !== WalletType.fromDescriptor('Seed') - && wallet.values.get('type') !== WalletType.fromDescriptor('Pk')) { + if (wallet.type !== WalletType.SEED + && wallet.type !== WalletType.PRIVATE_KEY) { return wallet } - // Get the private key - const encryptedPrivateKey = new EncryptedPrivateKey( - wallet.values.get('encPrivate'), - wallet.values.get('encIv'), - ) + const encryptedPrivateKey = new EncryptedPrivateKey(wallet.encPrivate, wallet.encIv) const privateKey = encryptedPrivateKey.decrypt(oldPassword) // Encrypt the private key with the new password const newSimpleWallet = SimpleWallet.createFromPrivateKey( - wallet.values.get('name'), + wallet.name, newPassword, privateKey, - wallet.objects.address.networkType, + WalletModel.getObjects(wallet).address.networkType, ) - // Update the wallet model - wallet.values.set('encPrivate', newSimpleWallet.encryptedPrivateKey.encryptedKey) - wallet.values.set('encIv', newSimpleWallet.encryptedPrivateKey.iv) - - return wallet + return { + ...wallet, encPrivate: newSimpleWallet.encryptedPrivateKey.encryptedKey, + encIv: newSimpleWallet.encryptedPrivateKey.iv, + } } } diff --git a/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts b/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts index f6028cfc0..b9bd1e86f 100644 --- a/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts +++ b/src/views/forms/FormAccountPasswordUpdate/FormAccountPasswordUpdateTs.ts @@ -31,6 +31,9 @@ import FormRow from '@/components/FormRow/FormRow.vue' import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' import {NotificationType} from '@/core/utils/NotificationType' import {AccountModel} from '@/core/database/entities/AccountModel' +import {AESEncryptionService} from '@/services/AESEncryptionService' +import {WalletService} from '@/services/WalletService' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ components: { @@ -44,6 +47,7 @@ import {AccountModel} from '@/core/database/entities/AccountModel' computed: { ...mapGetters({ currentAccount: 'account/currentAccount', + networkConfiguration: 'network/networkConfiguration', }), }, }) @@ -55,6 +59,7 @@ export class FormAccountPasswordUpdateTs extends Vue { */ public currentAccount: AccountModel + private networkConfiguration: NetworkConfigurationModel /** * Validation rules * @var {ValidationRuleset} @@ -118,47 +123,30 @@ export class FormAccountPasswordUpdateTs extends Vue { try { const accountService = new AccountService() const newPassword = new Password(this.formItems.password) + const oldSeed = this.currentAccount.seed + const plainSeed = AESEncryptionService.decrypt(oldSeed, oldPassword) + const newSeed = AESEncryptionService.encrypt(plainSeed, newPassword) // // - create new password hash - // const passwordHash = AccountService.getPasswordHash(new Password(this.formItems.password)) - // accountService.updatePassword(this.currentAccount, passwordHash, this.formItems.passwordHint) - // - // // - create new password hash - // const passwordHash = service.getPasswordHash(newPassword) - // accountModel.values.set('password', passwordHash) - // accountModel.values.set('hint', this.formItems.passwordHint) - // - // // - encrypt the seed with the new password - // const oldSeed = accountModel.values.get('seed') - // const plainSeed = AESEncryptionService.decrypt(oldSeed , oldPassword) - // const newSeed = AESEncryptionService.encrypt(plainSeed, newPassword) - // this.currentAccount.values.set('seed', newSeed) - // - // // - update in storage - // repository.update(accountModel.getIdentifier(), accountModel.values) - // - // // - update wallets passwords - // const walletsRepository = new WalletsRepository() - // const walletService = new WalletService() - // - // const walletIdentifiers = accountModel.values.get('wallets') - // const walletModels = walletIdentifiers.map(id => walletsRepository.read(id)) - // - // for (const model of walletModels) { - // const updatedModel = walletService.updateWalletPassword(model, oldPassword, newPassword) - // const updatedValues = new Map([ - // [ 'encPrivate', updatedModel.values.get('encPrivate') ], - // [ 'encIv', updatedModel.values.get('encIv') ], - // ]) - // walletsRepository.update(model.getIdentifier(), updatedValues) - // } + const passwordHash = AccountService.getPasswordHash(newPassword) + accountService.updatePassword(this.currentAccount, passwordHash, this.formItems.passwordHint, + newSeed) + + const walletService = new WalletService() + const walletIdentifiers = this.currentAccount.wallets + + const wallets = walletService.getKnownWallets(walletIdentifiers) + for (const model of wallets) { + const updatedModel = walletService.updateWalletPassword(model, oldPassword, newPassword) + walletService.saveWallet(updatedModel) + } + // - update state and finalize this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.SUCCESS_PASSWORD_CHANGED) await this.$store.dispatch('account/LOG_OUT') setTimeout(() => {this.$router.push({name: 'accounts.login'})}, 500) - } - catch (e) { + } catch (e) { this.$store.dispatch('notification/ADD_ERROR', 'An error happened, please try again.') console.error(e) } diff --git a/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts b/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts index 4f323f9a9..471ffdc5f 100644 --- a/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts +++ b/src/views/forms/FormExtendNamespaceDurationTransaction/FormExtendNamespaceDurationTransactionTs.ts @@ -16,7 +16,6 @@ // external dependencies import {Component, Prop} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies import {FormNamespaceRegistrationTransactionTs} from '../FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs' import {NamespaceId} from 'symbol-sdk' @@ -34,27 +33,20 @@ import {NamespaceModel} from '@/core/database/entities/NamespaceModel' components: {ErrorTooltip, ModalTransactionConfirmation}, computed: { ...mapGetters({ - currentHeight: 'network/currentHeight', namespaces: 'namespace/namespaces', }), }, }) -export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegistrationTransactionTs { +export class FormExtendNamespaceDurationTransactionTs + extends FormNamespaceRegistrationTransactionTs { @Prop({default: null, required: true}) namespaceId: NamespaceId - /** - * Network current height - * @private - * @type {number} - */ - private currentHeight: number - private namespaces: NamespaceModel[] /** * Validation rules * @var {ValidationRuleset} */ - protected validationRules = ValidationRuleset + public validationRules = ValidationRuleset /** * Current namespace info @@ -63,7 +55,8 @@ export class FormExtendNamespaceDurationTransactionTs extends FormNamespaceRegis * @type {NamespaceInfo} */ protected get currentNamespaceEndHeight(): number { - const currentNamespace = this.namespaces.find(model => model.namespaceIdHex === this.namespaceId.toHex()) + const currentNamespace = this.namespaces.find( + model => model.namespaceIdHex === this.namespaceId.toHex()) return currentNamespace && currentNamespace.endHeight || 0 } diff --git a/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts b/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts index 18d2e224d..38614964d 100644 --- a/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts +++ b/src/views/forms/FormNamespaceRegistrationTransaction/FormNamespaceRegistrationTransactionTs.ts @@ -42,6 +42,7 @@ import ModalTransactionConfirmation from '@/views/modals/ModalTransactionConfirm // configuration import {NamespaceModel} from '@/core/database/entities/NamespaceModel' import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' +import {NamespaceService} from '@/services/NamespaceService' @Component({ components: { @@ -59,6 +60,7 @@ import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigu computed: { ...mapGetters({ ownedNamespaces: 'namespace/ownedNamespaces', + currentHeight: 'network/currentHeight', networkConfiguration: 'network/networkConfiguration', }), }, @@ -89,6 +91,11 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase */ public typeSubNamespace = NamespaceRegistrationType.SubNamespace + /** + * Current network block height + */ + public currentHeight: number + /** * Form items * @var {Record} @@ -165,7 +172,8 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase // - return instantiated Transaction return [this.factory.build(view)] } catch (error) { - console.error('Error happened in FormNamespaceRegistrationTransaction.transactions(): ', error) + console.error('Error happened in FormNamespaceRegistrationTransaction.transactions(): ', + error) } } @@ -191,16 +199,20 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase public relativeTimetoParent = '' /** - * Namespaces names - * @type {[h: string]: string} - */ + * Namespaces names + * @type {[h: string]: string} + */ public namespacesNames: { [h: string]: string } + public getTimeByparentNamespaceName() { const selectedNamespace = this.fertileNamespaces.find((item) => - this.namespacesNames[item.id.toHex()] === this.formItems.parentNamespaceName, + this.namespacesNames[item.namespaceIdHex] === this.formItems.parentNamespaceName, ) - this.relativeTimetoParent = new NamespaceTableService(this.$store).getExpiration(selectedNamespace).expiration + + this.relativeTimetoParent = NamespaceService.getExpiration(this.networkConfiguration, + this.currentHeight, selectedNamespace.endHeight).expiration } + setParentNamespaceName(val) { this.formItems.parentNamespaceName = val this.getTimeByparentNamespaceName() diff --git a/src/views/forms/FormTransactionBase/FormTransactionBase.ts b/src/views/forms/FormTransactionBase/FormTransactionBase.ts index 691f5326b..f8d8d4af8 100644 --- a/src/views/forms/FormTransactionBase/FormTransactionBase.ts +++ b/src/views/forms/FormTransactionBase/FormTransactionBase.ts @@ -337,7 +337,7 @@ export class FormTransactionBase extends Vue { */ private resetFormValidation(): void { this.$nextTick(() => { - this.$refs.observer.reset() + this.$refs.observer && this.$refs.observer.reset() }) } diff --git a/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts b/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts index 1236b8b3f..9c3cc7b38 100644 --- a/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts +++ b/src/views/forms/FormTransferTransaction/FormTransferTransactionTs.ts @@ -48,6 +48,7 @@ import MaxFeeAndSubmit from '@/components/MaxFeeAndSubmit/MaxFeeAndSubmit.vue' import FormRow from '@/components/FormRow/FormRow.vue' import {MosaicService} from '@/services/MosaicService' import {MosaicModel} from '@/core/database/entities/MosaicModel' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export interface MosaicAttachment { mosaicHex: string @@ -75,6 +76,7 @@ export interface MosaicAttachment { ...mapGetters({ currentHeight: 'network/currentHeight', balanceMosaics: 'mosaic/balanceMosaics', + networkConfiguration: 'network/networkConfiguration', }), }, }) @@ -124,6 +126,8 @@ export class FormTransferTransactionTs extends FormTransactionBase { private balanceMosaics: MosaicModel[] + private networkConfiguration: NetworkConfigurationModel + /** * Reset the form with properties * @return {void} @@ -136,9 +140,9 @@ export class FormTransferTransactionTs extends FormTransactionBase { this.formItems.signerPublicKey = this.selectedSigner.publicKey this.formItems.selectedMosaicHex = this.networkMosaic.toHex() // default currentWallet Address to recipientRaw - if(this.$route.path.indexOf('invoice') > -1){ - this.formItems.recipientRaw = this.currentWallet.objects.address.plain() || '' - }else{ + if (this.$route.path.indexOf('invoice') > -1) { + this.formItems.recipientRaw = this.currentWallet.address || '' + } else { this.formItems.recipientRaw = !!this.recipient ? this.recipient.plain() : '' } this.formItems.recipient = !!this.recipient ? this.recipient : null @@ -190,7 +194,8 @@ export class FormTransferTransactionTs extends FormTransactionBase { // filter out expired mosaics return this.balanceMosaics.filter(mosaicInfo => { // calculate expiration - const expiration = MosaicService.getExpiration(mosaicInfo, this.currentHeight) + const expiration = MosaicService.getExpiration(mosaicInfo, this.currentHeight, + this.networkConfiguration.blockGenerationTargetTime) // skip if mosaic is expired return expiration !== 'expired' }) @@ -394,7 +399,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { if (this.formItems.recipientRaw && this.formItems.recipientRaw !== '') { const transactions = this.getTransactions() // avoid error - if(transactions){ + if (transactions) { const data: ITransactionEntry[] = [] transactions.map((item: TransferTransaction) => { data.push({ @@ -402,7 +407,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { attachments: this.mosaicsToAttachments(item.mosaics), }) }) - + this.$emit('onTransactionsChange', data) } } diff --git a/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts b/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts index 837789b3f..cc27a94d2 100644 --- a/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts +++ b/src/views/modals/ModalMnemonicExport/ModalMnemonicExportTs.ts @@ -149,10 +149,9 @@ export class ModalMnemonicExportTs extends Vue { // - create link () const a = document.createElement('a') const event = new MouseEvent('click') - const accountName = this.currentAccount.values.get('accountName') + const accountName = this.currentAccount.accountName a.download = `qr_account_mnemonic_${accountName}` a.href = url - // - start download a.dispatchEvent(event) } diff --git a/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts b/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts index 5e6a699e3..05576c084 100644 --- a/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts +++ b/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts @@ -92,7 +92,7 @@ export default class FinalizeTs extends Vue { // execute store actions await this.$store.dispatch('account/ADD_WALLET', wallet) await this.$store.dispatch('wallet/SET_CURRENT_WALLET', wallet) - await this.$store.dispatch('wallet/SET_KNOWN_WALLETS', wallets) + await this.$store.dispatch('wallet/SET_KNOWN_WALLETS', [wallet.id]) await this.$store.dispatch('temporary/RESET_STATE') await this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) From ff0a303a3d8dbf033537439fde701b911bf7efc6 Mon Sep 17 00:00:00 2001 From: Fernando Boucquez Date: Tue, 14 Apr 2020 23:38:25 -0300 Subject: [PATCH 03/17] Fixed language picker fixed mosaic and namespace links Moved transactions to own module Improved balance component Improved how blocks are loaded Improved Amount shown in transaction table fixed load account info dispatch Fixed account creation Small fixes --- .../AccountBalancesPanel.vue | 4 +- .../AccountBalancesPanelTs.ts | 22 +- .../AmountDisplay/AmountDisplayTs.ts | 26 +- .../LanguageSelector/LanguageSelectorTs.ts | 8 +- .../MosaicAmountDisplayTs.ts | 44 +- .../MosaicBalanceList/MosaicBalanceList.vue | 4 +- .../MosaicBalanceList/MosaicBalanceListTs.ts | 2 +- .../NamespaceSelector/NamespaceSelectorTs.ts | 2 +- .../TransactionDetailRow.vue | 7 +- .../TransactionDetailsHeaderTs.ts | 11 +- .../TransactionList/TransactionListTs.ts | 79 ++-- .../TransactionRow/TransactionRow.vue | 21 +- .../TransactionRow/TransactionRowTs.ts | 61 ++- .../TransactionTable/TransactionTable.vue | 22 +- .../TransactionListOptionsTs.ts | 54 +-- .../WalletSelectorFieldTs.ts | 7 +- .../WalletSelectorPanel.vue | 3 +- .../WalletSelectorPanelTs.ts | 11 +- .../transactions/ViewHashLockTransaction.ts | 9 +- .../transactions/ViewTransferTransaction.ts | 14 +- src/services/AccountService.ts | 15 +- .../AssetTableService/MosaicTableService.ts | 2 +- src/services/RESTService.ts | 70 ++- src/services/TransactionService.ts | 6 +- src/services/WalletService.ts | 7 +- src/store/Account.ts | 23 +- src/store/AppInfo.ts | 25 +- src/store/Network.ts | 89 +--- src/store/Transaction.ts | 304 +++++++++++++ src/store/Wallet.ts | 412 ++++-------------- src/store/index.ts | 4 + src/store/plugins/onPeerConnection.ts | 2 +- .../FormAliasTransaction.vue | 2 +- .../FormAliasTransactionTs.ts | 13 +- .../FormGeneralSettingsTs.ts | 6 +- .../FormNamespaceRegistrationTransactionTs.ts | 9 +- .../FormSubWalletCreationTs.ts | 20 +- .../FormWalletNameUpdateTs.ts | 15 +- src/views/pages/accounts/LoginPageTs.ts | 24 - .../create-account/finalize/FinalizeTs.ts | 49 +-- .../generate-mnemonic/GenerateMnemonicTs.ts | 10 +- .../verify-mnemonic/VerifyMnemonicTs.ts | 8 - .../import-mnemonic/ImportMnemonicTs.ts | 10 +- .../wallet-selection/WalletSelection.vue | 6 +- .../wallet-selection/WalletSelectionTs.ts | 12 +- .../CreateSubNamespaceTs.ts | 2 +- .../WalletDetailsPage/WalletDetailsPage.vue | 2 +- 47 files changed, 658 insertions(+), 900 deletions(-) create mode 100644 src/store/Transaction.ts diff --git a/src/components/AccountBalancesPanel/AccountBalancesPanel.vue b/src/components/AccountBalancesPanel/AccountBalancesPanel.vue index f15ec7f8c..e89c3e899 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanel.vue +++ b/src/components/AccountBalancesPanel/AccountBalancesPanel.vue @@ -24,9 +24,7 @@
{{ networkCurrency.ticker }}
diff --git a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts index 744b24ffa..afb93ddcb 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts +++ b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {MosaicId, Address} from 'symbol-sdk' +import {Address} from 'symbol-sdk' import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' - // internal dependencies import {WalletModel} from '@/core/database/entities/WalletModel' import {UIHelpers} from '@/core/utils/UIHelpers' @@ -40,7 +39,6 @@ import {NetworkCurrencyModel} from '@/core/database/entities/NetworkCurrencyMode balanceMosaics: 'mosaic/balanceMosaics', isCosignatoryMode: 'wallet/isCosignatoryMode', networkCurrency: 'mosaic/networkCurrency', - networkMosaicId: 'mosaic/networkMosaic', }), }, }) @@ -75,8 +73,6 @@ export class AccountBalancesPanelTs extends Vue { */ public networkCurrency: NetworkCurrencyModel - public networkMosaicId: MosaicId - /** * UI Helpers @@ -88,25 +84,9 @@ export class AccountBalancesPanelTs extends Vue { this.$store.dispatch('mosaic/LOAD_MOSAICS') } - /** - * Network mosaic divisibility - * @readonly - * @protected - * @type {number} - */ - protected get divisibility(): number { - return this.networkCurrency && this.networkCurrency.divisibility || 0 - } - public get absoluteBalance() { const networkMosaicData = this.balanceMosaics.filter(m => m.isCurrencyMosaic).find(i => i) return networkMosaicData && networkMosaicData.balance || 0 } - public get networkMosaicBalance(): number { - const balance = this.absoluteBalance - if (balance === 0 || !this.divisibility) return 0 - return balance / Math.pow(10, this.divisibility) - } - } diff --git a/src/components/AmountDisplay/AmountDisplayTs.ts b/src/components/AmountDisplay/AmountDisplayTs.ts index 8fc92fbf2..dd712d861 100644 --- a/src/components/AmountDisplay/AmountDisplayTs.ts +++ b/src/components/AmountDisplay/AmountDisplayTs.ts @@ -15,18 +15,15 @@ */ // external dependencies import {Component, Prop, Vue} from 'vue-property-decorator' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' import {mapGetters} from 'vuex' -import {MosaicId} from 'symbol-sdk' // configuration -import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' @Component({ computed: { ...mapGetters({ - networkMosaicTicker: 'mosaic/networkMosaicTicker', - networkMosaic: 'mosaic/networkMosaic', networkConfiguration: 'network/networkConfiguration', }), }, @@ -34,7 +31,7 @@ import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigu export class AmountDisplayTs extends Vue { @Prop({default: 0}) value: number - @Prop() decimals: number | undefined + @Prop({default: undefined}) decimals: number | undefined @Prop({default: false}) showTicker: false @@ -43,17 +40,6 @@ export class AmountDisplayTs extends Vue { @Prop({default: 'normal'}) size: 'normal' | 'smaller' | 'bigger' | 'biggest' public networkConfiguration: NetworkConfigurationModel - /** - * Currency mosaic's ticker - * @var {string} - */ - public networkMosaicTicker: string - - /** - * Currency mosaic's Id - * @var {string} - */ - public networkMosaic: MosaicId /// region computed properties getter/setter get integerPart(): string { @@ -63,8 +49,8 @@ export class AmountDisplayTs extends Vue { get fractionalPart(): string { const rest = this.value - Math.floor(this.value) if (rest === 0) return '' - - const decimals = this.networkConfiguration.maxMosaicDivisibility + const decimals = this.decimals === undefined ? + (this.networkConfiguration.maxMosaicDivisibility || 6) : this.decimals // remove leftmost-0 and rightmost-0 return Number(rest.toFixed(decimals)).toPrecision().toString().replace(/^0/, '') } @@ -75,9 +61,7 @@ export class AmountDisplayTs extends Vue { * @type {string} */ get displayedTicker(): string { - if (!this.showTicker) return '' - if (this.ticker === this.networkMosaic.id.toHex()) return this.networkMosaicTicker - return this.ticker || this.networkMosaicTicker + return this.showTicker && this.ticker || '' } /// end-region computed properties getter/setter diff --git a/src/components/LanguageSelector/LanguageSelectorTs.ts b/src/components/LanguageSelector/LanguageSelectorTs.ts index dfaca0dce..fdaebce36 100644 --- a/src/components/LanguageSelector/LanguageSelectorTs.ts +++ b/src/components/LanguageSelector/LanguageSelectorTs.ts @@ -1,12 +1,12 @@ /** * Copyright 2020 NEM Foundation (https://nem.io) - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' @Component({computed: {...mapGetters({ - language: 'app/currentLanguage', + currentLanguage: 'app/language', languageList: 'app/languages', })}}) export class LanguageSelectorTs extends Vue { diff --git a/src/components/MosaicAmountDisplay/MosaicAmountDisplayTs.ts b/src/components/MosaicAmountDisplay/MosaicAmountDisplayTs.ts index 1e27855ef..bd528687c 100644 --- a/src/components/MosaicAmountDisplay/MosaicAmountDisplayTs.ts +++ b/src/components/MosaicAmountDisplay/MosaicAmountDisplayTs.ts @@ -41,7 +41,6 @@ export class MosaicAmountDisplayTs extends Vue { @Prop({ default: null, - required: true, }) id: MosaicId @Prop({ @@ -52,13 +51,6 @@ export class MosaicAmountDisplayTs extends Vue { default: null, }) absoluteAmount: number - /** - * Whether to show absolute amount or not - */ - @Prop({ - default: false, - }) absolute: boolean - @Prop({ default: 'green', }) color: 'red' | 'green' @@ -71,10 +63,6 @@ export class MosaicAmountDisplayTs extends Vue { default: false, }) showTicker: false - @Prop({ - default: null, - }) ticker: string - private mosaics: MosaicModel[] private networkCurrency: NetworkCurrencyModel @@ -87,28 +75,34 @@ export class MosaicAmountDisplayTs extends Vue { * @return {number} */ protected get divisibility(): number { - if (!this.id && this.networkCurrency) { - return this.networkCurrency.divisibility + if (!this.id) { + return this.networkCurrency && this.networkCurrency.divisibility + || this.networkConfiguration.maxMosaicDivisibility } - if (this.id && this.networkCurrency && this.id.toHex() === this.networkCurrency.mosaicIdHex) { + if (this.networkCurrency && this.id.toHex() === this.networkCurrency.mosaicIdHex) { return this.networkCurrency.divisibility } - const mosaic = this.id && this.mosaics.find(m => m.mosaicIdHex === this.id.toHex()) - if (mosaic) return mosaic.divisibility - - return this.networkConfiguration.maxMosaicDivisibility + const mosaic = this.mosaics.find(m => m.mosaicIdHex === this.id.toHex()) + return mosaic ? mosaic.divisibility : this.networkConfiguration.maxMosaicDivisibility } public get amount(): number { - if (this.absolute && null === this.absoluteAmount) { - return this.relativeAmount * Math.pow(10, this.divisibility) - } else if (this.absolute) { - return this.absoluteAmount - } else if (!this.absolute && this.absoluteAmount && this.divisibility >= 0) { + if (this.absoluteAmount) { return this.absoluteAmount / Math.pow(10, this.divisibility) + } else {return this.relativeAmount || 0} + } + + public get ticker(): string { + if (!this.id) { + return this.networkCurrency && this.networkCurrency.ticker || '' + } + + if (this.networkCurrency && this.id.toHex() === this.networkCurrency.mosaicIdHex) { + return this.networkCurrency.ticker } - return this.relativeAmount + const mosaic = this.mosaics.find(m => m.mosaicIdHex === this.id.toHex()) + return mosaic ? mosaic.name : this.id.toHex() } /// end-region computed properties getter/setter diff --git a/src/components/MosaicBalanceList/MosaicBalanceList.vue b/src/components/MosaicBalanceList/MosaicBalanceList.vue index e728ad04f..1b740e443 100644 --- a/src/components/MosaicBalanceList/MosaicBalanceList.vue +++ b/src/components/MosaicBalanceList/MosaicBalanceList.vue @@ -22,7 +22,7 @@ {{ entry.name !== '' ? entry.name : entry.id.toHex() }} - +
@@ -75,7 +75,7 @@ diff --git a/src/components/MosaicBalanceList/MosaicBalanceListTs.ts b/src/components/MosaicBalanceList/MosaicBalanceListTs.ts index 50e184565..f3328a88c 100644 --- a/src/components/MosaicBalanceList/MosaicBalanceListTs.ts +++ b/src/components/MosaicBalanceList/MosaicBalanceListTs.ts @@ -95,7 +95,7 @@ export class MosaicBalanceListTs extends Vue { return { id: new MosaicId(mosaic.mosaicIdHex), name: mosaic.name || mosaic.mosaicIdHex, - amount: (mosaic.balance || 0) / Math.pow(10, mosaic.divisibility), + amount: (mosaic.balance || 0), mosaic: mosaic, } }) diff --git a/src/components/NamespaceSelector/NamespaceSelectorTs.ts b/src/components/NamespaceSelector/NamespaceSelectorTs.ts index 122aff486..8aa490ebe 100644 --- a/src/components/NamespaceSelector/NamespaceSelectorTs.ts +++ b/src/components/NamespaceSelector/NamespaceSelectorTs.ts @@ -32,7 +32,7 @@ import {NamespaceModel} from '@/core/database/entities/NamespaceModel' }, computed: { ...mapGetters({ - namespaces: 'namespace/namespaces', + namespaces: 'namespace/ownedNamespaces', }), }, }) diff --git a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue index bf83e7aae..d86b10252 100644 --- a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue +++ b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue @@ -18,7 +18,6 @@ :id="value.id" :absolute-amount="value.amount" :show-ticker="true" - :ticker="value.mosaicHex" /> @@ -30,10 +29,12 @@ diff --git a/src/components/TransactionListOptions/TransactionListOptionsTs.ts b/src/components/TransactionListOptions/TransactionListOptionsTs.ts index b9ab2846d..ac8656442 100644 --- a/src/components/TransactionListOptions/TransactionListOptionsTs.ts +++ b/src/components/TransactionListOptions/TransactionListOptionsTs.ts @@ -16,19 +16,14 @@ // external dependencies import {mapGetters} from 'vuex' import {Component, Prop, Vue, Watch} from 'vue-property-decorator' -import {Address, NetworkType} from 'symbol-sdk' -import {asyncScheduler, Subject} from 'rxjs' -import {throttleTime} from 'rxjs/operators' - +import {NetworkType} from 'symbol-sdk' // child components // @ts-ignore import SignerSelector from '@/components/SignerSelector/SignerSelector.vue' -import {RESTDispatcher} from '@/core/utils/RESTDispatcher' import {Signer} from '@/store/Wallet' import {WalletModel} from '@/core/database/entities/WalletModel' +import {TransactionGroup} from '@/store/Transaction' -// custom types -type group = 'confirmed' | 'unconfirmed' | 'partial' @Component({ components: {SignerSelector}, @@ -42,22 +37,7 @@ type group = 'confirmed' | 'unconfirmed' | 'partial' }, }) export class TransactionListOptionsTs extends Vue { - @Prop({default: 'confirmed'}) currentTab: group - - /** - * Minimum interval in ms between each refresh call - * @private - * @type {number} - */ - private REFRESH_CALLS_THROTTLING: number = 500 - - /** - * Observable of public keys to fetch for - * - * @private - * @type {Observable} - */ - private refreshStream$: Subject<{ publicKey: string, group: group }> = new Subject + @Prop({default: TransactionGroup.confirmed}) currentTab: TransactionGroup /** * Currently active wallet @@ -104,18 +84,7 @@ export class TransactionListOptionsTs extends Vue { this.formItems.currentSignerPubicKey = publicKey // clear previous account transactions - this.$store.dispatch('wallet/RESET_TRANSACTIONS') - - // dispatch actions using the rest dispatcher - const dispatcher = new RESTDispatcher(this.$store.dispatch) - dispatcher.add('wallet/SET_CURRENT_SIGNER', {publicKey}) - dispatcher.add('wallet/REST_FETCH_TRANSACTIONS', { - group: this.currentTab, - address: Address.createFromPublicKey(publicKey, this.networkType).plain(), - pageSize: 100, - }) - - dispatcher.throttle_dispatch() + this.$store.dispatch('wallet/SET_CURRENT_SIGNER', {publicKey}) } /** @@ -123,7 +92,7 @@ export class TransactionListOptionsTs extends Vue { * @protected */ protected refresh(): void { - this.refreshStream$.next({publicKey: this.currentSigner.publicKey, group: this.currentTab}) + this.$store.dispatch('transaction/LOAD_TRANSACTIONS', {group: this.currentTab}) } /** @@ -131,18 +100,7 @@ export class TransactionListOptionsTs extends Vue { * Starts a subscription to handle REST calls for refreshing transactions */ public created(): void { - this.refreshStream$ - .pipe( - throttleTime(this.REFRESH_CALLS_THROTTLING, asyncScheduler, {leading: true, trailing: true}), - ) - .subscribe(({publicKey, group}) => { - // dispatch REST call - this.$store.dispatch('wallet/REST_FETCH_TRANSACTIONS', { - group, - address: Address.createFromPublicKey(publicKey, this.networkType).plain(), - pageSize: 100, - }) - }) + this.refresh() } public mounted(): void { diff --git a/src/components/WalletSelectorField/WalletSelectorFieldTs.ts b/src/components/WalletSelectorField/WalletSelectorFieldTs.ts index 7b855c60b..d2492ba47 100644 --- a/src/components/WalletSelectorField/WalletSelectorFieldTs.ts +++ b/src/components/WalletSelectorField/WalletSelectorFieldTs.ts @@ -44,10 +44,9 @@ export class WalletSelectorFieldTs extends Vue { public currentWallet: WalletModel /** - * Known wallets identifiers - * @var {string[]} + * Known wallets */ - public knownWallets: string[] + public knownWallets: WalletModel[] /** * Wallets repository @@ -78,7 +77,7 @@ export class WalletSelectorFieldTs extends Vue { } public get currentWallets(): WalletModel[] { - return this.walletService.getKnownWallets(this.knownWallets) + return this.knownWallets } /// end-region computed properties getter/setter diff --git a/src/components/WalletSelectorPanel/WalletSelectorPanel.vue b/src/components/WalletSelectorPanel/WalletSelectorPanel.vue index ee772b6a9..899042848 100644 --- a/src/components/WalletSelectorPanel/WalletSelectorPanel.vue +++ b/src/components/WalletSelectorPanel/WalletSelectorPanel.vue @@ -28,8 +28,7 @@
diff --git a/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts b/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts index c178a4e7a..240725d56 100644 --- a/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts +++ b/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts @@ -89,7 +89,7 @@ export class WalletSelectorPanelTs extends Vue { * Known wallets identifiers * @var {string[]} */ - public knownWallets: string[] + public knownWallets: WalletModel[] /** * Networks currency mosaic * @var {MosaicId} @@ -138,8 +138,7 @@ export class WalletSelectorPanelTs extends Vue { /// region computed properties getter/setter public get balances(): Map { const networkMosaics = this.mosaics.filter(m => m.mosaicIdHex === this.networkMosaic.toHex()) - return Object.assign({}, ...networkMosaics.map( - s => ({[s.addressRawPlain]: s.balance / Math.pow(10, this.networkCurrency.divisibility)}))) + return Object.assign({}, ...networkMosaics.map(s => ({[s.addressRawPlain]: s.balance}))) // return this.addressesBalances } @@ -164,11 +163,7 @@ export class WalletSelectorPanelTs extends Vue { } public get currentWallets(): WalletModel[] { - if (!this.knownWallets || !this.knownWallets.length) { - return [] - } - // filter wallets to only known wallet names - return this.walletService.getKnownWallets(this.knownWallets) + return this.knownWallets } public get hasAddWalletModal(): boolean { diff --git a/src/core/transactions/ViewHashLockTransaction.ts b/src/core/transactions/ViewHashLockTransaction.ts index 3146bcddf..cc65a84e9 100644 --- a/src/core/transactions/ViewHashLockTransaction.ts +++ b/src/core/transactions/ViewHashLockTransaction.ts @@ -15,14 +15,7 @@ * limitations under the License. */ // external dependencies -import { - HashLockTransaction, - Mosaic, - MosaicId, - RawUInt64, - SignedTransaction, - UInt64, -} from 'symbol-sdk' +import {HashLockTransaction, Mosaic, MosaicId, RawUInt64, SignedTransaction, UInt64} from 'symbol-sdk' // internal dependencies import {TransactionView} from './TransactionView' import {MosaicModel} from '@/core/database/entities/MosaicModel' diff --git a/src/core/transactions/ViewTransferTransaction.ts b/src/core/transactions/ViewTransferTransaction.ts index 1fea5856b..23ffac353 100644 --- a/src/core/transactions/ViewTransferTransaction.ts +++ b/src/core/transactions/ViewTransferTransaction.ts @@ -14,21 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Address, - EmptyMessage, - Mosaic, - MosaicId, - NamespaceId, - PlainMessage, - RawUInt64, - TransferTransaction, - UInt64, -} from 'symbol-sdk' +import {Address, EmptyMessage, Mosaic, MosaicId, NamespaceId, PlainMessage, RawUInt64, TransferTransaction, UInt64} from 'symbol-sdk' // internal dependencies import {TransactionView} from './TransactionView' -import {MosaicModel} from '@/core/database/entities/MosaicModel' import {AttachedMosaic} from '@/services/MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' /// region custom types export type TransferFormFieldsType = { diff --git a/src/services/AccountService.ts b/src/services/AccountService.ts index 0de60fa2b..39f734a52 100644 --- a/src/services/AccountService.ts +++ b/src/services/AccountService.ts @@ -44,10 +44,11 @@ export class AccountService { return this.accountsStorage.get() || {} } - public saveAccount(account: AccountModel) { + public saveAccount(account: AccountModel): AccountModel { const accounts = this.getAccountsByAccountName() accounts[account.accountName] = account this.accountsStorage.set(accounts) + return account } public deleteAccount(accountName: string) { @@ -56,16 +57,16 @@ export class AccountService { this.accountsStorage.set(accounts) } - public updateSeed(account: AccountModel, seed: string) { - this.saveAccount({...account, ...{seed}}) + public updateSeed(account: AccountModel, seed: string): AccountModel { + return this.saveAccount(Object.assign(account, {seed})) } - public updatePassword(account: AccountModel, password: string, hint: string, seed: string) { - this.saveAccount({...account, ...{password, hint, seed}}) + public updatePassword(account: AccountModel, password: string, hint: string, seed: string): AccountModel { + return this.saveAccount(Object.assign(account, {password, hint, seed})) } - public updateWallets(account: AccountModel, wallets: string[]) { - this.saveAccount({...account, ...{wallets}}) + public updateWallets(account: AccountModel, wallets: string[]): AccountModel { + return this.saveAccount(Object.assign(account, {wallets})) } /** diff --git a/src/services/AssetTableService/MosaicTableService.ts b/src/services/AssetTableService/MosaicTableService.ts index bd42da62c..98b33bdd2 100644 --- a/src/services/AssetTableService/MosaicTableService.ts +++ b/src/services/AssetTableService/MosaicTableService.ts @@ -60,7 +60,7 @@ export class MosaicTableService extends AssetTableService { 'hexId': mosaicInfo.mosaicIdHex, 'name': mosaicInfo.name || 'N/A', 'supply': mosaicInfo.supply.toLocaleString(), - 'balance': mosaicInfo.balance || 0 / Math.pow(10, mosaicInfo.divisibility), + 'balance': (mosaicInfo.balance || 0) / Math.pow(10, mosaicInfo.divisibility), 'expiration': expiration, 'divisibility': mosaicInfo.divisibility, 'transferable': mosaicInfo.transferable, diff --git a/src/services/RESTService.ts b/src/services/RESTService.ts index 65aa231d3..00ec345e9 100644 --- a/src/services/RESTService.ts +++ b/src/services/RESTService.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Address, IListener, Listener, RepositoryFactory, RepositoryFactoryHttp, TransactionStatusError} from 'symbol-sdk' +import {Address, IListener, RepositoryFactory, TransactionStatusError} from 'symbol-sdk' import {Subscription} from 'rxjs' // internal dependencies import {AddressValidator} from '@/core/validation/validators' import {NotificationType} from '@/core/utils/NotificationType' -import {URLHelpers} from '@/core/utils/URLHelpers' +import {TransactionGroup} from '@/store/Transaction' /** @@ -50,71 +50,69 @@ export class RESTService { // error listener const status = listener.status(address).subscribe( - (error: TransactionStatusError) => context.dispatch('notification/ADD_ERROR', error.code, {root: true})) + (error: TransactionStatusError) => context.dispatch('notification/ADD_ERROR', error.code, + {root: true})) // unconfirmed listeners const unconfirmedAdded = listener.unconfirmedAdded(address).subscribe( transaction => { - context.dispatch('wallet/ADD_TRANSACTION', {group: 'unconfirmed', transaction}, {root: true}) - context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_UNCONFIRMED_TRANSACTION, {root: true}) + context.dispatch('transaction/ADD_TRANSACTION', + {group: TransactionGroup.unconfirmed, transaction}, {root: true}) + context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_UNCONFIRMED_TRANSACTION, + {root: true}) }, err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) const unconfirmedRemoved = listener.unconfirmedRemoved(address).subscribe( - transaction => context.dispatch('wallet/REMOVE_TRANSACTION', {group: 'unconfirmed', transaction}, {root: true}), + transactionHash => context.dispatch('transaction/REMOVE_TRANSACTION', + {group: TransactionGroup.unconfirmed, transactionHash}, {root: true}), err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) // partial listeners const cosignatureAdded = listener.cosignatureAdded(address).subscribe( cosignature => { context.dispatch('wallet/ADD_COSIGNATURE', cosignature, {root: true}) - context.dispatch('notification/ADD_SUCCESS', NotificationType.COSIGNATURE_ADDED, {root: true}) + context.dispatch('notification/ADD_SUCCESS', NotificationType.COSIGNATURE_ADDED, + {root: true}) }, err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) const partialAdded = listener.aggregateBondedAdded(address).subscribe( transaction => { - context.dispatch('wallet/ADD_TRANSACTION', {group: 'partial', transaction}, {root: true}) - context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_AGGREGATE_BONDED, {root: true}) + context.dispatch('transaction/ADD_TRANSACTION', + {group: TransactionGroup.partial, transaction}, {root: true}) + context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_AGGREGATE_BONDED, + {root: true}) }, err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) const partialRemoved = listener.aggregateBondedRemoved(address).subscribe( - transaction => context.dispatch('wallet/REMOVE_TRANSACTION', {group: 'partial', transaction}, {root: true}), + transactionHash => context.dispatch('transaction/REMOVE_TRANSACTION', + {group: TransactionGroup.partial, transactionHash}, {root: true}), err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) // confirmed listener const confirmed = listener.confirmed(address).subscribe( transaction => { - context.dispatch('wallet/ADD_TRANSACTION', {group: 'confirmed', transaction}, {root: true}) - context.dispatch('wallet/ON_NEW_TRANSACTION', transaction, {root: true}) - context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_CONFIRMED_TRANSACTION, {root: true}) + context.dispatch('transaction/ADD_TRANSACTION', + {group: TransactionGroup.confirmed, transaction}, {root: true}) + context.dispatch('transaction/ON_NEW_TRANSACTION', transaction, {root: true}) + context.dispatch('notification/ADD_SUCCESS', NotificationType.NEW_CONFIRMED_TRANSACTION, + {root: true}) }, err => context.dispatch('diagnostic/ADD_ERROR', err, {root: true})) - return {listener, subscriptions: [ - status, - unconfirmedAdded, - unconfirmedRemoved, - cosignatureAdded, - partialAdded, - partialRemoved, - confirmed, - ]} - } - - /** - * It creates the RepositoryFactory used to build the http repository/clients and listeners. - * @param url the url. - */ - public static createRepositoryFactory(url: string): RepositoryFactory { - // TODO Extend RepositoryFactoryHttp in tha TS SDK to allow ws:// kind of urls. - // TODO Why the WebSocket as webSocketInjected? Can we avoid this? - const repositoryFactory = new RepositoryFactoryHttp(url) - const wsUrl = URLHelpers.httpToWsUrl(url) - repositoryFactory.createListener = () => { - return new Listener(wsUrl, WebSocket) + return { + listener, subscriptions: [ + status, + unconfirmedAdded, + unconfirmedRemoved, + cosignatureAdded, + partialAdded, + partialRemoved, + confirmed, + ], } - return repositoryFactory } + } diff --git a/src/services/TransactionService.ts b/src/services/TransactionService.ts index 550c0018b..06df535e7 100644 --- a/src/services/TransactionService.ts +++ b/src/services/TransactionService.ts @@ -98,7 +98,7 @@ export class TransactionService extends AbstractService { public getView(transaction: Transaction): TransactionViewType { // - store shortcuts const currentWallet: WalletModel = this.$store.getters['wallet/currentWallet'] - const knownBlocks: { [h: number]: BlockInfo } = this.$store.getters['network/knownBlocks'] + const knownBlocks: BlockInfo[] = this.$store.getters['transaction/blocks'] // - interpret transaction type and initialize view let view: TransactionViewType @@ -164,9 +164,7 @@ export class TransactionService extends AbstractService { // - try to find block for fee information const height = transaction.transactionInfo ? transaction.transactionInfo.height : undefined - const block: BlockInfo = !height ? undefined : Object.keys(knownBlocks).filter( - k => knownBlocks[k].height.equals(height), - ).map(k => knownBlocks[k]).shift() + const block: BlockInfo = !height ? undefined : knownBlocks.find(k => k.height.equals(height)) const isAggregate = [ TransactionType.AGGREGATE_BONDED, diff --git a/src/services/WalletService.ts b/src/services/WalletService.ts index 068a5a60e..e1bbb174e 100755 --- a/src/services/WalletService.ts +++ b/src/services/WalletService.ts @@ -45,15 +45,16 @@ export class WalletService { return this.storage.get() || {} } - public saveWallet(wallet: WalletModel) { + public saveWallet(wallet: WalletModel): WalletModel { const wallets = this.getWalletsById() wallets[wallet.id] = wallet this.storage.set(wallets) + return wallet } - public updateName(wallet: WalletModel, name: string) { - this.saveWallet({...wallet, ...{name}}) + public updateName(wallet: WalletModel, name: string): WalletModel { + return this.saveWallet(Object.assign(wallet, {name})) } diff --git a/src/store/Account.ts b/src/store/Account.ts index 439460a63..eb433d667 100644 --- a/src/store/Account.ts +++ b/src/store/Account.ts @@ -19,6 +19,8 @@ import {$eventBus} from '../events' import {AwaitLock} from './AwaitLock' import {SettingService} from '@/services/SettingService' import {AccountModel} from '@/core/database/entities/AccountModel' +import {WalletModel} from '@/core/database/entities/WalletModel' +import {AccountService} from '@/services/AccountService' /// region globals const Lock = AwaitLock.create() @@ -99,22 +101,19 @@ export default { $eventBus.$emit('onAccountChange', currentAccount.accountName) }, - ADD_WALLET({dispatch, getters}, walletModel) { - const resolvedAccount = getters['currentAccount'] - if (!resolvedAccount || !resolvedAccount.values) { + ADD_WALLET({dispatch, getters}, walletModel: WalletModel) { + const currentAccount: AccountModel = getters['currentAccount'] + if (!currentAccount) { return } - dispatch('diagnostic/ADD_DEBUG', - 'Adding wallet to account: ' + resolvedAccount.getIdentifier() + ' with: ' + walletModel.address, + 'Adding wallet to account: ' + currentAccount.accountName + ' with: ' + walletModel.address, {root: true}) - - const wallets = resolvedAccount.values.get('wallets') - wallets.push(walletModel.getIdentifier()) - - // update account and return - resolvedAccount.values.set('wallets', wallets) - return dispatch('SET_CURRENT_ACCOUNT', resolvedAccount) + if (!currentAccount.wallets.includes(walletModel.id)) { + new AccountService().updateWallets(currentAccount, + [ ...currentAccount.wallets, walletModel.id ]) + } + return dispatch('SET_CURRENT_ACCOUNT', currentAccount) }, /// end-region scoped actions }, diff --git a/src/store/AppInfo.ts b/src/store/AppInfo.ts index d5a41829b..76f0a52d6 100644 --- a/src/store/AppInfo.ts +++ b/src/store/AppInfo.ts @@ -26,6 +26,7 @@ import {SettingService} from '@/services/SettingService' const Lock = AwaitLock.create() const settingService = new SettingService() +const ANON_ACCOUNT_NAME = '' interface AppInfoState { initialized: false @@ -38,7 +39,6 @@ interface AppInfoState { controlsDisabledMessage: string faucetUrl: string settings: SettingsModel - isFetchingTransactions: boolean } const appInfoState: AppInfoState = { @@ -51,8 +51,7 @@ const appInfoState: AppInfoState = { hasControlsDisabled: false, controlsDisabledMessage: '', faucetUrl: networkConfig.faucetUrl, - settings: settingService.createDefaultSettingsModel(''), - isFetchingTransactions: false, + settings: settingService.getAccountSettings(ANON_ACCOUNT_NAME), } @@ -74,20 +73,22 @@ export default { faucetUrl: (state: AppInfoState) => state.faucetUrl, defaultFee: (state: AppInfoState) => state.settings.defaultFee, defaultWallet: (state: AppInfoState) => state.settings.defaultWallet, - isFetchingTransactions: (state: AppInfoState) => state.isFetchingTransactions, }, mutations: { setInitialized: (state: AppInfoState, initialized) => { state.initialized = initialized }, timezone: (state: AppInfoState, timezone) => Vue.set(state, 'timezone', timezone), - settings: (state: AppInfoState, settings: SettingsModel) => Vue.set(state, 'settings', settings), + settings: (state: AppInfoState, settings: SettingsModel) => Vue.set(state, 'settings', + settings), toggleControlsDisabled: (state: AppInfoState, {disable, message}) => { Vue.set(state, 'hasControlsDisabled', disable) Vue.set(state, 'controlsDisabledMessage', message || '') }, - toggleLoadingOverlay: (state: AppInfoState, display: boolean) => Vue.set(state, 'hasLoadingOverlay', display), - setLoadingOverlayMessage: (state: AppInfoState, message: string) => Vue.set(state, 'loadingOverlayMessage', message), - setLoadingDisableCloseButton: (state: AppInfoState, bool: boolean) => Vue.set(state, 'loadingDisableCloseButton', bool), - setFetchingTransactions: (state: AppInfoState, bool: boolean) => Vue.set(state, 'isFetchingTransactions', bool), + toggleLoadingOverlay: (state: AppInfoState, display: boolean) => Vue.set(state, + 'hasLoadingOverlay', display), + setLoadingOverlayMessage: (state: AppInfoState, message: string) => Vue.set(state, + 'loadingOverlayMessage', message), + setLoadingDisableCloseButton: (state: AppInfoState, bool: boolean) => Vue.set(state, + 'loadingDisableCloseButton', bool), }, actions: { async initialize({commit, getters}) { @@ -125,7 +126,8 @@ export default { i18n.locale = settingsModel.language window.localStorage.setItem('locale', settingsModel.language) } - const accountName = rootGetters['account/currentAccount'].accountName + const currentAccount = rootGetters['account/currentAccount'] + const accountName = currentAccount && currentAccount.accountName || ANON_ACCOUNT_NAME commit('settings', settingService.changeAccountSettings(accountName, settingsModel)) }, @@ -142,9 +144,6 @@ export default { SET_DEFAULT_WALLET({dispatch}, defaultWallet: string) { dispatch('SET_SETTINGS', {defaultWallet}) }, - SET_FETCHING_TRANSACTIONS({commit}, bool: boolean) { - commit('setFetchingTransactions', bool) - }, /// end-region scoped actions }, } diff --git a/src/store/Network.ts b/src/store/Network.ts index 13bfcc5d8..41a89e0fc 100644 --- a/src/store/Network.ts +++ b/src/store/Network.ts @@ -14,11 +14,10 @@ * limitations under the License. */ import Vue from 'vue' -import {BlockInfo, IListener, Listener, NetworkType, RepositoryFactory, UInt64} from 'symbol-sdk' +import {BlockInfo, IListener, Listener, NetworkType, RepositoryFactory} from 'symbol-sdk' import {Subscription} from 'rxjs' // internal dependencies import {$eventBus} from '../events' -import {RESTService} from '@/services/RESTService' import {URLHelpers} from '@/core/utils/URLHelpers' import app from '@/main' import {AwaitLock} from './AwaitLock' @@ -34,29 +33,6 @@ import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigu const Lock = AwaitLock.create() -/// region internal helpers -/** - * Recursive function helper to determine block ranges - * needed to fetch data about all block \a heights - * @param {number[]} heights - * @return {BlockRangeType[]} - */ -const getBlockRanges = (heights: number[], ranges: BlockRangeType[] = []): BlockRangeType[] => { - const pageSize: number = 100 - const min: number = Math.min(...heights) - - // - register first range - ranges.push({start: min}) - - // - take remaining block heights and run again - heights = heights.filter(height => height > min + pageSize) - if (heights.length) { - return getBlockRanges(heights, ranges) - } - return ranges -} -/// end-region internal helpers - /// region custom types /** * Type SubscriptionType for Wallet Store @@ -86,7 +62,6 @@ interface NetworkState { isConnected: boolean knowNodes: NodeModel[] currentHeight: number - knownBlocks: Record subscriptions: Subscription[] } @@ -96,16 +71,15 @@ const networkState: NetworkState = { initialized: false, currentPeer: defaultPeer, currentPeerInfo: new NodeModel(defaultPeer.url, defaultPeer.url), - networkType: undefined, + networkType: networkConfig.defaultNetworkType, generationHash: undefined, networkModel: undefined, networkConfiguration: networkConfig.networkConfigurationDefaults, - repositoryFactory: RESTService.createRepositoryFactory(networkConfig.defaultNodeUrl), + repositoryFactory: NetworkService.createRepositoryFactory(networkConfig.defaultNodeUrl), listener: undefined, isConnected: false, knowNodes: [], currentHeight: 0, - knownBlocks: {}, subscriptions: [], } export default { @@ -125,7 +99,6 @@ export default { isConnected: (state: NetworkState) => state.isConnected, knowNodes: (state: NetworkState) => state.knowNodes, currentHeight: (state: NetworkState) => state.currentHeight, - knownBlocks: (state: NetworkState) => state.knownBlocks, }, mutations: { setInitialized: (state: NetworkState, @@ -172,11 +145,6 @@ export default { new NodeService().saveNodes(newNodes) Vue.set(state, 'knowNodes', newNodes) }, - addBlock: (state: NetworkState, block: BlockInfo) => { - const knownBlocks = state.knownBlocks - knownBlocks[block.height.compact()] = block - Vue.set(state, 'knownBlocks', knownBlocks) - }, subscriptions: (state: NetworkState, data) => Vue.set(state, 'subscriptions', data), addSubscriptions: (state: NetworkState, payload) => { const subscriptions = state.subscriptions @@ -305,9 +273,7 @@ export default { } }, - ADD_BLOCK({commit}, block: BlockInfo) { - commit('addBlock', block) - }, + /** * Websocket API */ @@ -317,7 +283,7 @@ export default { const listener = getters['listener'] as Listener const subscription = listener.newBlock().subscribe((block: BlockInfo) => { dispatch('SET_CURRENT_HEIGHT', block.height.compact()) - dispatch('ADD_BLOCK', block) + dispatch('transaction/ADD_BLOCK', block, {root: true}) dispatch('diagnostic/ADD_INFO', 'New block height: ' + block.height.compact(), {root: true}) }) // update state of listeners & subscriptions @@ -338,50 +304,5 @@ export default { commit('currentHeight', height) }, - async REST_FETCH_BLOCKS({commit, dispatch, getters, rootGetters}, blockHeights: number[]) { - - // - filter out known blocks - const knownBlocks: {[h: number]: BlockInfo} = getters['knownBlocks'] - const unknownHeights = blockHeights.filter(height => !knownBlocks || !knownBlocks[height]) - const knownHeights = blockHeights.filter(height => knownBlocks && knownBlocks[height]) - - // - initialize blocks list with known blocks - let blocks: BlockInfo[] = knownHeights.map(known => knownBlocks[known]) - if (!unknownHeights.length) { - return blocks - } - - // - use block ranges helper to minimize number of requests (recent blocks first) - const ranges: {start: number}[] = getBlockRanges(unknownHeights).reverse() - - try { - // - prepare REST gateway connection - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const blockHttp = repositoryFactory.createBlockRepository() - - // - fetch blocks information per-range (wait 3 seconds every 4th block) - ranges.slice(0, 3).map(({start}) => { - blockHttp.getBlocksByHeightWithLimit(UInt64.fromUint(start), 100).subscribe( - (infos: BlockInfo[]) => { - infos.map(b => commit('addBlock', b)) - blocks = blocks.concat(infos) - }) - }) - - const nextHeights = ranges.slice(3).map(r => r.start) - if (nextHeights.length) { - setTimeout(() => { - dispatch('diagnostic/ADD_DEBUG', - 'Store action network/REST_FETCH_BLOCKS delaying heights discovery for 2 seconds: ' + JSON.stringify( - nextHeights), {root: true}) - return dispatch('REST_FETCH_BLOCKS', nextHeights) - }, 2000) - } - } catch (e) { - dispatch('diagnostic/ADD_ERROR', - 'An error happened while trying to fetch blocks information: ' + e, {root: true}) - return false - } - }, }, } diff --git a/src/store/Transaction.ts b/src/store/Transaction.ts new file mode 100644 index 000000000..3dbeb5537 --- /dev/null +++ b/src/store/Transaction.ts @@ -0,0 +1,304 @@ +/* + * Copyright 2020 NEM Foundation (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +import {Address, AggregateTransaction, BlockInfo, QueryParams, RepositoryFactory, Transaction, TransactionType, UInt64} from 'symbol-sdk' +// internal dependencies +import {AwaitLock} from './AwaitLock' +import {Observable} from 'rxjs' +import * as _ from 'lodash' + +const Lock = AwaitLock.create() + +export enum TransactionGroup { + confirmed = 'confirmed', + unconfirmed = 'unconfirmed', + partial = 'partial' +} + + +/** + * Helper to format transaction group in name of state variable. + * + * @internal + * @param {string} group + * @return {string} One of 'confirmedTransactions', 'unconfirmedTransactions' or + * 'partialTransactions' + */ +const transactionGroupToStateVariable = (group: TransactionGroup): string => { + return group + 'Transactions' +} + +const transactionComparator = (t1, t2) => { + // - unconfirmed/partial sorted by index + return t1.transactionInfo.index - t2.transactionInfo.index +} +const confirmedTransactionComparator = (t1, t2) => { + const info1 = t1.transactionInfo + const info2 = t2.transactionInfo + // - confirmed sorted by height then index + const diffHeight = info1.height.compact() - info2.height.compact() + const diffIndex = info1.index - info2.index + return diffHeight !== 0 ? diffHeight : diffIndex +} + +function conditionalSort(array: T[] | undefined, comparator: (a: T, b: T) => number): T[] | undefined { + if (!array) { + return array + } + return array.sort(comparator) +} + + +interface TransactionState { + initialized: boolean + confirmedTransactions: Transaction[] | undefined + unconfirmedTransactions: Transaction[] | undefined + partialTransactions: Transaction[] | undefined + blocks: BlockInfo[] +} + +const transactionState: TransactionState = { + initialized: false, + confirmedTransactions: [], + unconfirmedTransactions: [], + partialTransactions: [], + blocks: [], +} +export default { + namespaced: true, + state: transactionState, + getters: { + getInitialized: (state: TransactionState) => state.initialized, + confirmedTransactions: (state: TransactionState) => state.confirmedTransactions, + unconfirmedTransactions: (state: TransactionState) => state.unconfirmedTransactions, + partialTransactions: (state: TransactionState) => state.partialTransactions, + blocks: (state: TransactionState) => state.blocks, + }, + mutations: { + setInitialized: (state: TransactionState, initialized: boolean) => { state.initialized = initialized }, + confirmedTransactions: (state: TransactionState, confirmedTransactions: Transaction[] | undefined) => { + state.confirmedTransactions = conditionalSort(confirmedTransactions, + confirmedTransactionComparator) + }, + unconfirmedTransactions: (state: TransactionState, unconfirmedTransactions: Transaction[] | undefined) => { + state.unconfirmedTransactions = conditionalSort(unconfirmedTransactions, + transactionComparator) + }, + partialTransactions: (state: TransactionState, partialTransactions: Transaction[] | undefined) => { + state.partialTransactions = conditionalSort(partialTransactions, transactionComparator) + }, + blocks: (state: TransactionState, blocks: BlockInfo[]) => { + state.blocks = _.uniqBy(blocks, b => b.height.compact()) + }, + + }, + actions: { + async initialize({commit, getters}) { + const callback = async () => { + // Placeholder for initialization if necessary. + commit('setInitialized', true) + } + // aquire async lock until initialized + await Lock.initialize(callback, {getters}) + }, + + async uninitialize({commit, getters, dispatch}) { + const callback = async () => { + await dispatch('RESET_TRANSACTIONS') + commit('setInitialized', false) + } + await Lock.uninitialize(callback, {getters}) + }, + + LOAD_TRANSACTIONS({commit, rootGetters, dispatch}, {group}: + { group: TransactionGroup | undefined } = {group: undefined}) { + const currentSignerAddress: Address = rootGetters['wallet/currentSignerAddress'] + if (!currentSignerAddress) { + return + } + const repositoryFactory: RepositoryFactory = rootGetters['network/repositoryFactory'] + const accountRepository = repositoryFactory.createAccountRepository() + const subscribeTransactions = (group: TransactionGroup, transactionCall: Observable) => { + const attributeName = transactionGroupToStateVariable(group) + commit(attributeName, undefined) + transactionCall.subscribe((transactions) => { + commit(attributeName, transactions) + const heights = _.uniqBy( + transactions.filter(t => t.transactionInfo && t.transactionInfo.height) + .map(t => t.transactionInfo.height), + h => h.compact()) + dispatch('LOAD_BLOCKS', heights) + }, err => { + console.log('HTTP Error retrieving transactions', err) + commit(attributeName, []) + }) + } + const queryParams = new QueryParams({pageSize: 100}) + if (!group || group === TransactionGroup.confirmed) { + subscribeTransactions(TransactionGroup.confirmed, + accountRepository.getAccountTransactions(currentSignerAddress, queryParams)) + } + if (!group || group === TransactionGroup.unconfirmed) { + subscribeTransactions(TransactionGroup.unconfirmed, + accountRepository.getAccountUnconfirmedTransactions(currentSignerAddress, queryParams)) + } + + if (!group || group === TransactionGroup.partial) { + subscribeTransactions(TransactionGroup.partial, + accountRepository.getAccountPartialTransactions(currentSignerAddress, queryParams)) + } + }, + + SIGNER_CHANGED({dispatch}) { + dispatch('LOAD_TRANSACTIONS') + }, + + RESET_TRANSACTIONS({commit}) { + Object.keys(TransactionGroup).forEach((group: TransactionGroup) => { + commit(transactionGroupToStateVariable(group), []) + }) + }, + + ADD_BLOCK({commit, getters}, block: BlockInfo) { + const blocks: BlockInfo[] = getters['blocks'] + commit('blocks', [ block, ...blocks ]) + }, + + async LOAD_BLOCKS({commit, getters, rootGetters}, heights: UInt64[]) { + if (!heights || !heights.length) { + return + } + + let blocks: BlockInfo[] = getters['blocks'] + const isUnknownBlock = h => !blocks.find(block => block.height.equals(h)) + const heightsToBeLoaded = heights.filter(isUnknownBlock).sort((a, b) => a.compare(b)) + if (!heightsToBeLoaded.length) { + return + } + + const asyncForEach = async (array, callback) => { + for (let index = 0; index < array.length; index ++) { + await callback(array[index], index, array) + } + } + + const repositoryFactory: RepositoryFactory = rootGetters['network/repositoryFactory'] + const blockRepository = repositoryFactory.createBlockRepository() + + await asyncForEach(heightsToBeLoaded, async (start: UInt64) => { + // Async one by one sequentially so we can reuse a previous loading if the height has been found. + if (isUnknownBlock(start)) { + const infos = await blockRepository.getBlocksByHeightWithLimit(start, 100).toPromise() + blocks = blocks.concat(infos) + commit('blocks', blocks) + } + }) + }, + + + ADD_TRANSACTION({commit, getters}, {group, transaction}: { group: TransactionGroup, transaction: Transaction }) { + if (!group) { + throw Error('Missing mandatory field \'group\' for action transaction/ADD_TRANSACTION.') + } + + if (!transaction) { + throw Error( + 'Missing mandatory field \'transaction\' for action transaction/ADD_TRANSACTION.') + } + // format transactionAttribute to store variable name + const transactionAttribute = transactionGroupToStateVariable(group) + + // register transaction + const transactions = getters[transactionAttribute] || [] + if (!transactions.find(t => t.transactionInfo.hash === transaction.transactionInfo.hash)) { + // update state + commit(transactionAttribute, [ transaction, ...transactions ]) + } + + }, + REMOVE_TRANSACTION({commit, getters}, {group, transactionHash}: + { group: TransactionGroup, transactionHash: string }) { + + if (!group) { + throw Error('Missing mandatory field \'group\' for action transaction/REMOVE_TRANSACTION.') + } + + if (!transactionHash) { + throw Error( + 'Missing mandatory field \'transactionHash\' for action transaction/REMOVE_TRANSACTION.') + } + // format transactionAttribute to store variable name + const transactionAttribute = transactionGroupToStateVariable(group) + + // register transaction + const transactions = getters[transactionAttribute] || [] + commit(transactionAttribute, + transactions.filter(t => t.transactionInfo.hash !== transactionHash)) + }, + + ON_NEW_TRANSACTION({dispatch}, transaction: Transaction): void { + if (!transaction) return + + // extract transaction types from the transaction + const transactionTypes: TransactionType[] = _.uniq(transaction instanceof AggregateTransaction + ? transaction.innerTransactions + .map(({type}) => type) + : [transaction.type]) + + // add actions to the dispatcher according to the transaction types + if ([ + TransactionType.NAMESPACE_REGISTRATION, + TransactionType.MOSAIC_ALIAS, + TransactionType.ADDRESS_ALIAS, + ].some(a => transactionTypes.some(b => b === a))) { + dispatch('namespace/LOAD_NAMESPACES', {}, {root: true}) + } + if ([ + TransactionType.MOSAIC_DEFINITION, + TransactionType.MOSAIC_SUPPLY_CHANGE, + ].some(a => transactionTypes.some(b => b === a))) { + dispatch('mosaic/LOAD_MOSAICS', {}, {root: true}) + } + + if (transactionTypes.includes(TransactionType.MULTISIG_ACCOUNT_MODIFICATION)) { + dispatch('wallet/LOAD_ACCOUNT_INFO', {}, {root: true}) + } + + }, + /// end-region scoped actions + }, + + + ADD_COSIGNATURE({commit, getters}, cosignatureMessage: { parentHash: string }) { + if (!cosignatureMessage || !cosignatureMessage.parentHash) { + throw Error('Missing mandatory field \'parentHash\' for action transaction/ADD_COSIGNATURE.') + } + const transactionAttribute = transactionGroupToStateVariable(TransactionGroup.partial) + const transactions = getters[transactionAttribute] || [] + + // return if no transactions + if (!transactions.length) return + + const index = transactions.findIndex( + t => t.transactionInfo.hash === cosignatureMessage.parentHash) + + // partial tx unknown, @TODO: handle this case (fetch partials) + if (index === -1) return + + transactions[index] = transactions[index].addCosignatures(cosignatureMessage) + commit('partialTransactions', transactions) + }, + +} diff --git a/src/store/Wallet.ts b/src/store/Wallet.ts index 6d160bf60..7c735c6f4 100644 --- a/src/store/Wallet.ts +++ b/src/store/Wallet.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import Vue from 'vue' -import {AccountInfo, Address, AggregateTransaction, CosignatureSignedTransaction, IListener, MultisigAccountInfo, NetworkType, QueryParams, RepositoryFactory, SignedTransaction, Transaction, TransactionType} from 'symbol-sdk' +import {AccountInfo, Address, CosignatureSignedTransaction, IListener, MultisigAccountInfo, NetworkType, RepositoryFactory, SignedTransaction, Transaction} from 'symbol-sdk' import {of, Subscription} from 'rxjs' // internal dependencies import {$eventBus} from '../events' @@ -22,36 +22,12 @@ import {RESTService} from '@/services/RESTService' import {AwaitLock} from './AwaitLock' import {BroadcastResult} from '@/core/transactions/BroadcastResult' import {WalletModel} from '@/core/database/entities/WalletModel' -import {RESTDispatcher} from '@/core/utils/RESTDispatcher' import {MultisigService} from '@/services/MultisigService' import * as _ from 'lodash' import {AccountModel} from '@/core/database/entities/AccountModel' import {WalletService} from '@/services/WalletService' import {catchError, map} from 'rxjs/operators' -/** - * Helper to format transaction group in name of state variable. - * - * @internal - * @param {string} group - * @return {string} One of 'confirmedTransactions', 'unconfirmedTransactions' or - * 'partialTransactions' - */ -const transactionGroupToStateVariable = ( - group: string, -): string => { - let transactionGroup = group.toLowerCase() - if (transactionGroup === 'unconfirmed' - || transactionGroup === 'confirmed' - || transactionGroup === 'partial') { - transactionGroup = transactionGroup + 'Transactions' - } else { - throw new Error('Unknown transaction group \'' + group + '\'.') - } - - return transactionGroup -} - /// region globals const Lock = AwaitLock.create() @@ -80,14 +56,11 @@ interface WalletState { currentSignerAddress: Address currentSignerMultisigInfo: MultisigAccountInfo // Known wallet database identifiers - knownWallets: string[] + knownWallets: WalletModel[] knownAddresses: Address[] accountsInfo: AccountInfo[] multisigAccountsInfo: MultisigAccountInfo[] - transactionHashes: string[] - confirmedTransactions: Transaction[] - unconfirmedTransactions: Transaction[] - partialTransactions: Transaction[] + stageOptions: { isAggregate: boolean, isMultisig: boolean } stagedTransactions: Transaction[] signedTransactions: SignedTransaction[] @@ -110,10 +83,6 @@ const walletState: WalletState = { knownAddresses: [], accountsInfo: [], multisigAccountsInfo: [], - transactionHashes: [], - confirmedTransactions: [], - unconfirmedTransactions: [], - partialTransactions: [], stageOptions: { isAggregate: false, isMultisig: false, @@ -147,66 +116,36 @@ export default { accountsInfo: (state: WalletState) => state.accountsInfo, multisigAccountsInfo: (state: WalletState) => state.multisigAccountsInfo, getSubscriptions: (state: WalletState) => state.subscriptions, - transactionHashes: (state: WalletState) => state.transactionHashes, - confirmedTransactions: (state: WalletState) => { - return state.confirmedTransactions.sort((t1, t2) => { - const info1 = t1.transactionInfo - const info2 = t2.transactionInfo - - // - confirmed sorted by height then index - const diffHeight = info1.height.compact() - info2.height.compact() - const diffIndex = info1.index - info2.index - return diffHeight !== 0 ? diffHeight : diffIndex - }) - }, - unconfirmedTransactions: (state: WalletState) => { - return state.unconfirmedTransactions.sort((t1, t2) => { - // - unconfirmed/partial sorted by index - return t1.transactionInfo.index - t2.transactionInfo.index - }) - }, - partialTransactions: (state: WalletState) => { - return state.partialTransactions.sort((t1, t2) => { - // - unconfirmed/partial sorted by index - return t1.transactionInfo.index - t2.transactionInfo.index - }) - }, stageOptions: (state: WalletState) => state.stageOptions, stagedTransactions: (state: WalletState) => state.stagedTransactions, signedTransactions: (state: WalletState) => state.signedTransactions, }, mutations: { setInitialized: (state: WalletState, initialized: boolean) => { state.initialized = initialized }, - currentWallet: (state: WalletState, walletModel: WalletModel) => Vue.set(state, 'currentWallet', - walletModel), - currentWalletAddress: (state: WalletState, walletAddress: Address) => Vue.set(state, - 'currentWalletAddress', walletAddress), - currentSigner: (state: WalletState, currentSigner: Signer) => Vue.set(state, 'currentSigner', - currentSigner), - signers: (state: WalletState, signers: Signer[]) => Vue.set(state, 'signers', signers), - currentSignerAddress: (state: WalletState, signerAddress) => Vue.set(state, - 'currentSignerAddress', signerAddress), - knownWallets: (state: WalletState, wallets) => Vue.set(state, 'knownWallets', wallets), - knownAddresses: (state: WalletState, knownAddresses: Address[]) => Vue.set(state, - 'knownAddresses', knownAddresses), - isCosignatoryMode: (state: WalletState, mode: boolean) => Vue.set(state, 'isCosignatoryMode', - mode), - accountsInfo: (state: WalletState, accountsInfo) => Vue.set(state, 'accountsInfo', - accountsInfo), - multisigAccountsInfo: (state: WalletState, multisigAccountsInfo) => Vue.set(state, - 'multisigAccountsInfo', multisigAccountsInfo), - currentWalletMultisigInfo: (state: WalletState, currentWalletMultisigInfo) => Vue.set(state, - 'currentWalletMultisigInfo', currentWalletMultisigInfo), - currentSignerMultisigInfo: (state: WalletState, currentSignerMultisigInfo) => Vue.set(state, - 'currentSignerMultisigInfo', currentSignerMultisigInfo), - transactionHashes: (state: WalletState, hashes) => Vue.set(state, 'transactionHashes', hashes), - confirmedTransactions: (state: WalletState, transactions) => Vue.set(state, - 'confirmedTransactions', transactions), - unconfirmedTransactions: (state: WalletState, transactions) => Vue.set(state, - 'unconfirmedTransactions', transactions), - partialTransactions: (state: WalletState, transactions) => Vue.set(state, 'partialTransactions', - transactions), - setSubscriptions: (state: WalletState, data) => Vue.set(state, 'subscriptions', data), + currentWallet: (state: WalletState, walletModel: WalletModel) => { state.currentWallet = walletModel }, + currentWalletAddress: (state: WalletState, walletAddress: Address) => + { state.currentWalletAddress = walletAddress }, + currentSigner: (state: WalletState, currentSigner: Signer) => + { state.currentSigner = currentSigner }, + signers: (state: WalletState, signers: Signer[]) => { state.signers = signers }, + currentSignerAddress: (state: WalletState, signerAddress) => + { state.currentSignerAddress = signerAddress }, + knownWallets: (state: WalletState, knownWallets: WalletModel[]) => + { state.knownWallets = knownWallets }, + knownAddresses: (state: WalletState, knownAddresses: Address[]) => + { state.knownAddresses = knownAddresses }, + isCosignatoryMode: (state: WalletState, isCosignatoryMode: boolean) => + { state.isCosignatoryMode = isCosignatoryMode }, + accountsInfo: (state: WalletState, accountsInfo) => { state.accountsInfo = accountsInfo }, + multisigAccountsInfo: (state: WalletState, multisigAccountsInfo) => + { state.multisigAccountsInfo = multisigAccountsInfo }, + currentWalletMultisigInfo: (state: WalletState, currentWalletMultisigInfo) => + { state.currentWalletMultisigInfo = currentWalletMultisigInfo }, + currentSignerMultisigInfo: (state: WalletState, currentSignerMultisigInfo) => + { state.currentSignerMultisigInfo = currentSignerMultisigInfo }, + + setSubscriptions: (state: WalletState, subscriptions: Record) => + { state.subscriptions = subscriptions }, addSubscriptions: (state: WalletState, payload: { address: string, subscriptions: SubscriptionType }) => { const {address, subscriptions} = payload @@ -279,32 +218,16 @@ export default { } await Lock.initialize(callback, {getters}) }, - async uninitialize({commit, dispatch, getters}, {address, which}) { + async uninitialize({commit, dispatch, getters}, {address}) { const callback = async () => { // close websocket connections await dispatch('UNSUBSCRIBE', address) - await dispatch('RESET_BALANCES', which) - await dispatch('RESET_MULTISIG') - await dispatch('RESET_TRANSACTIONS') + await dispatch('transaction/RESET_TRANSACTIONS', {}, {root: true}) commit('setInitialized', false) } await Lock.uninitialize(callback, {getters}) }, - /// region scoped actions - async REST_FETCH_WALLET_DETAILS({dispatch}, {address, options}) { - const dispatcher = new RESTDispatcher(dispatch) - - if (!options || !options.skipTransactions) { - dispatcher.add('REST_FETCH_TRANSACTIONS', { - group: 'confirmed', - pageSize: 100, - address: address, - }) - } - // - delays of 1000ms will be added every second request - dispatcher.throttle_dispatch() - }, /** * Possible `options` values include: * @type { @@ -331,7 +254,8 @@ export default { }, async RESET_CURRENT_WALLET({commit, dispatch}) { - dispatch('diagnostic/ADD_DEBUG', 'Store action wallet/RESET_CURRENT_WALLET dispatched', {root: true}) + dispatch('diagnostic/ADD_DEBUG', 'Store action wallet/RESET_CURRENT_WALLET dispatched', + {root: true}) commit('currentWallet', null) commit('currentWalletAddress', null) }, @@ -342,7 +266,6 @@ export default { const currentAccount: AccountModel = rootGetters['account/currentAccount'] const currentWallet: WalletModel = getters.currentWallet const previousSignerAddress: Address = getters.currentSignerAddress - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory const currentSignerAddress: Address = Address.createFromPublicKey(publicKey, networkType) @@ -352,146 +275,79 @@ export default { 'Store action wallet/SET_CURRENT_SIGNER dispatched with ' + currentSignerAddress.plain(), {root: true}) + dispatch('transaction/RESET_TRANSACTIONS', {}, {root: true}) + const currentWalletAddress = Address.createFromRawAddress(currentWallet.address) const walletService = new WalletService() const knownWallets = walletService.getKnownWallets(currentAccount.wallets) const knownAddresses = _.uniqBy([ currentSignerAddress, ...knownWallets.map(w => Address.createFromRawAddress(w.address)) ].filter(a => a), 'address') - const accountsInfo = await repositoryFactory.createAccountRepository() - .getAccountsInfo(knownAddresses).toPromise() - const multisigAccountsInfo: MultisigAccountInfo[] = - await repositoryFactory.createMultisigRepository() - .getMultisigAccountGraphInfo(currentWalletAddress).pipe(map(g => { - return MultisigService.getMultisigInfoFromMultisigGraphInfo(g) - }), catchError(() => { - return of([]) - })).toPromise() - const currentWalletMultisigInfo = multisigAccountsInfo.find( - m => m.account.address.equals(currentWalletAddress)) - const currentSignerMultisigInfo = multisigAccountsInfo.find( - m => m.account.address.equals(currentSignerAddress)) - const multisigService = new MultisigService() - const signers = multisigService.getSigners(networkType, knownWallets, currentWallet, - currentWalletMultisigInfo) commit('currentSignerAddress', currentSignerAddress) commit('currentWalletAddress', currentWalletAddress) commit('isCosignatoryMode', !currentSignerAddress.equals(currentWalletAddress)) - commit('currentSigner', signers.find(s => s.address.equals(currentSignerAddress))) - commit('signers', signers) - commit('knownWallets', currentAccount.wallets) - commit('knownAddresses', knownAddresses) - commit('accountsInfo', accountsInfo) - commit('multisigAccountsInfo', multisigAccountsInfo) - commit('currentWalletMultisigInfo', currentWalletMultisigInfo) - commit('currentSignerMultisigInfo', currentSignerMultisigInfo) - - // setting current signer should not fetch ALL data - await dispatch('REST_FETCH_WALLET_DETAILS', { - address: currentSignerAddress.plain(), options: { - skipTransactions: true, - }, - }) + commit('knownWallets', knownWallets) + commit('knownAddresses', knownAddresses) dispatch('namespace/SIGNER_CHANGED', {}, {root: true}) dispatch('mosaic/SIGNER_CHANGED', {}, {root: true}) - }, + dispatch('transaction/SIGNER_CHANGED', {}, {root: true}) + await dispatch('LOAD_ACCOUNT_INFO') - SET_KNOWN_WALLETS({commit}, wallets: string[]) { - commit('knownWallets', wallets) }, - RESET_TRANSACTIONS({commit}) { - commit('confirmedTransactions', []) - commit('unconfirmedTransactions', []) - commit('partialTransactions', []) - }, + async LOAD_ACCOUNT_INFO({commit, getters, rootGetters}) { + const networkType: NetworkType = rootGetters['network/networkType'] + const currentWallet: WalletModel = getters.currentWallet + const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory + const currentSignerAddress: Address = getters.currentSignerAddress + const currentWalletAddress: Address = getters.currentWalletAddress + const knownAddresses: Address[] = getters.knownAddresses + const knownWallets: WalletModel[] = getters.knownWallets - ADD_COSIGNATURE({commit, getters}, cosignatureMessage) { - if (!cosignatureMessage || !cosignatureMessage.parentHash) { - throw Error('Missing mandatory field \'parentHash\' for action wallet/ADD_COSIGNATURE.') - } + // remote calls: + const getAccontInfoPromise = repositoryFactory.createAccountRepository() + .getAccountsInfo(knownAddresses).toPromise() - const transactions = getters['partialTransactions'] + const getMultisignAccountInforPromise = repositoryFactory.createMultisigRepository() + .getMultisigAccountGraphInfo(currentWalletAddress).pipe(map(g => { + return MultisigService.getMultisigInfoFromMultisigGraphInfo(g) + }), catchError(() => { + return of([]) + })).toPromise() - // return if no transactions - if (!transactions.length) return + const accountsInfo = await getAccontInfoPromise + const multisigAccountsInfo: MultisigAccountInfo[] = await getMultisignAccountInforPromise - const index = transactions.findIndex( - t => t.transactionInfo.hash === cosignatureMessage.parentHash) + const currentWalletMultisigInfo = multisigAccountsInfo.find( + m => m.account.address.equals(currentWalletAddress)) + const currentSignerMultisigInfo = multisigAccountsInfo.find( + m => m.account.address.equals(currentSignerAddress)) + const multisigService = new MultisigService() + const signers = multisigService.getSigners(networkType, knownWallets, currentWallet, + currentWalletMultisigInfo) - // partial tx unknown, @TODO: handle this case (fetch partials) - if (index === -1) return + commit('currentSigner', signers.find(s => s.address.equals(currentSignerAddress))) + commit('signers', signers) + commit('accountsInfo', accountsInfo) + commit('multisigAccountsInfo', multisigAccountsInfo) + commit('currentWalletMultisigInfo', currentWalletMultisigInfo) + commit('currentSignerMultisigInfo', currentSignerMultisigInfo) - transactions[index] = transactions[index].addCosignatures(cosignatureMessage) - commit('partialTransactions', transactions) }, - ADD_TRANSACTION({commit, getters}, transactionMessage) { - if (!transactionMessage || !transactionMessage.group) { - throw Error('Missing mandatory field \'group\' for action wallet/ADD_TRANSACTION.') - } - // format transactionGroup to store variable name - const transactionGroup = transactionGroupToStateVariable(transactionMessage.group) - - // if transaction hash is known, do nothing - const hashes = getters['transactionHashes'] - const transaction = transactionMessage.transaction - const findIterator = hashes.find(hash => hash === transaction.transactionInfo.hash) - - // register transaction - const transactions = getters[transactionGroup] - const findTx = transactions.find( - t => t.transactionInfo.hash === transaction.transactionInfo.hash) - if (findTx === undefined) { - transactions.push(transaction) - } - - if (findIterator === undefined) { - hashes.push(transaction.transactionInfo.hash) - } - - // update state - commit(transactionGroup, transactions) - return commit('transactionHashes', hashes) + SET_KNOWN_WALLETS({commit}, wallets: string[]) { + commit('knownWallets', new WalletService().getKnownWallets(wallets)) }, - REMOVE_TRANSACTION({commit, getters}, transactionMessage) { - - if (!transactionMessage || !transactionMessage.group) { - throw Error('Missing mandatory field \'group\' for action wallet/removeTransaction.') - } - - // format transactionGroup to store variable name - const transactionGroup = transactionGroupToStateVariable(transactionMessage.group) - // read from store - const transactions = getters[transactionGroup] - - // prepare search - const transactionHash = transactionMessage.transaction - - // find transaction in storage - const findIterator = transactions.findIndex(tx => tx.transactionInfo.hash === transactionHash) - if (findIterator === undefined) { - return // not found, do nothing - } - - // commit empty array - if (transactions.length === 1) { - return commit(transactionGroup, []) - } - - // skip `idx` - const remaining = transactions.splice(0, findIterator).concat( - transactions.splice(findIterator + 1, transactions.length - findIterator - 1), - ) - - commit(transactionGroup, Array.from(remaining)) + RESET_SUBSCRIPTIONS({commit}) { + commit('setSubscriptions', []) }, + ADD_STAGED_TRANSACTION({commit}, stagedTransaction: Transaction) { commit('addStagedTransaction', stagedTransaction) }, @@ -525,13 +381,13 @@ export default { // Unsubscribe from all open websocket connections async UNSUBSCRIBE({dispatch, getters}, address) { const subscriptions = getters.getSubscriptions - const currentWallet = getters.currentWallet + const currentWallet: WalletModel = getters.currentWallet - if (!address) { + if (!address && currentWallet) { address = currentWallet.address } - const subsByAddress = subscriptions && subscriptions[address] ? subscriptions[address] : [] + const subsByAddress = subscriptions && subscriptions[address] || [] for (let i = 0, m = subsByAddress.length; i < m; i ++) { const subscription = subsByAddress[i] @@ -546,78 +402,6 @@ export default { // update state dispatch('RESET_SUBSCRIPTIONS', address) }, - /** - * REST API - */ - async REST_FETCH_TRANSACTIONS({dispatch, rootGetters}, - {group, address, id}) { - dispatch('app/SET_FETCHING_TRANSACTIONS', true, {root: true}) - - if (!group || ![ 'partial', 'unconfirmed', 'confirmed' ].includes(group)) { - group = 'confirmed' - } - - if (!address || address.length !== 40) { - return - } - - dispatch('diagnostic/ADD_DEBUG', - 'Store action wallet/REST_FETCH_TRANSACTIONS dispatched with : ' + JSON.stringify({ - address: address, - group, - }), {root: true}) - - try { - // prepare REST parameters - const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const queryParams = new QueryParams({pageSize: 100, id}) - const addressObject = Address.createFromRawAddress(address) - - // fetch transactions from REST gateway - const accountHttp = repositoryFactory.createAccountRepository() - let transactions: Transaction[] = [] - const blockHeights: number[] = [] - - if ('confirmed' === group) { - transactions = await accountHttp.getAccountTransactions(addressObject, queryParams) - .toPromise() - // - record block height to be fetched - transactions.map( - transaction => blockHeights.push(transaction.transactionInfo.height.compact())) - } else if ('unconfirmed' === group) { - transactions = await accountHttp.getAccountUnconfirmedTransactions(addressObject, - queryParams).toPromise() - } else if ('partial' === group) { - transactions = await accountHttp.getAccountPartialTransactions(addressObject, queryParams) - .toPromise() - } - - dispatch('diagnostic/ADD_DEBUG', - 'Store action wallet/REST_FETCH_TRANSACTIONS numTransactions: ' + transactions.length, - {root: true}) - - // update store - for (let i = 0, m = transactions.length; i < m; i ++) { - const transaction = transactions[i] - await dispatch('ADD_TRANSACTION', {address, group, transaction}) - } - - // fetch block information if necessary - if (blockHeights.length) { - // - non-blocking - dispatch('network/REST_FETCH_BLOCKS', blockHeights, {root: true}) - } - - return transactions - } catch (e) { - dispatch('diagnostic/ADD_ERROR', - 'An error happened while trying to fetch transactions: ' + e, {root: true}) - return false - } finally { - dispatch('app/SET_FETCHING_TRANSACTIONS', false, {root: true}) - } - }, - async REST_ANNOUNCE_PARTIAL( {commit, dispatch, rootGetters}, @@ -716,46 +500,8 @@ export default { return new BroadcastResult(cosignature, false, e.toString()) } }, - ON_NEW_TRANSACTION({dispatch, rootGetters}, transaction: Transaction): void { - if (!transaction) return - - // get current wallet address from store - const address: Address = rootGetters['wallet/currentWalletAddress'] - if (!address) return - const plainAddress = address.plain() - - // instantiate a dispatcher - const dispatcher = new RESTDispatcher(dispatch) - - - // extract transaction types from the transaction - const transactionTypes: TransactionType[] = _.uniq(transaction instanceof AggregateTransaction - ? transaction.innerTransactions - .map(({type}) => type) - : [transaction.type]) - - // add actions to the dispatcher according to the transaction types - if ([ - TransactionType.NAMESPACE_REGISTRATION, - TransactionType.MOSAIC_ALIAS, - TransactionType.ADDRESS_ALIAS, - ].some(a => transactionTypes.some(b => b === a))) { - dispatch('namespace/LOAD_NAMESPACES', {}, {root: true}) - } - if ([ - TransactionType.MOSAIC_DEFINITION, - TransactionType.MOSAIC_SUPPLY_CHANGE, - ].some(a => transactionTypes.some(b => b === a))) { - dispatch('mosaic/LOAD_MOSAICS', {}, {root: true}) - } - - if (transactionTypes.includes(TransactionType.MULTISIG_ACCOUNT_MODIFICATION)) { - dispatcher.add('REST_FETCH_MULTISIG', plainAddress) - } - // dispatch actions - dispatcher.throttle_dispatch() - }, - /// end-region scoped actions }, + + } diff --git a/src/store/index.ts b/src/store/index.ts index 4eeff191d..83999d25d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -25,6 +25,7 @@ import NotificationStore from '@/store/Notification' import TemporaryStore from '@/store/Temporary' import MosaicStore from '@/store/Mosaic' import NamespaceStore from '@/store/Namespace' +import TransactionStore from '@/store/Transaction' import StatisticsStore from '@/store/Statistics' import CommunityStore from '@/store/Community' import {onPeerConnection} from '@/store/plugins/onPeerConnection' @@ -53,6 +54,7 @@ const AppStore = new Vuex.Store({ temporary: TemporaryStore, mosaic: MosaicStore, namespace: NamespaceStore, + transaction: TransactionStore, statistics: StatisticsStore, community: CommunityStore, }, @@ -69,6 +71,7 @@ const AppStore = new Vuex.Store({ await dispatch('network/initialize') await dispatch('mosaic/initialize') await dispatch('namespace/initialize') + await dispatch('transaction/initialize') } // aquire async lock until initialized @@ -80,6 +83,7 @@ const AppStore = new Vuex.Store({ dispatch('app/uninitialize'), dispatch('network/uninitialize'), dispatch('mosaic/uninitialize'), + dispatch('transaction/uninitialize'), dispatch('account/uninitialize'), dispatch('wallet/uninitialize'), dispatch('namespace/uninitialize'), diff --git a/src/store/plugins/onPeerConnection.ts b/src/store/plugins/onPeerConnection.ts index 06f534df2..477f6e4a5 100644 --- a/src/store/plugins/onPeerConnection.ts +++ b/src/store/plugins/onPeerConnection.ts @@ -25,7 +25,7 @@ export const onPeerConnection = store => { if (!!currentWallet) { console.log('onPeerConnection dispatching wallet actions..') - // store.dispatch('wallet/REST_FETCH_TRANSACTIONS') + store.dispatch('transaction/LOAD_TRANSACTIONS') store.dispatch('mosaic/LOAD_MOSAICS') store.dispatch('namespace/LOAD_NAMESPACES') } diff --git a/src/views/forms/FormAliasTransaction/FormAliasTransaction.vue b/src/views/forms/FormAliasTransaction/FormAliasTransaction.vue index efae2fa27..dd815c137 100644 --- a/src/views/forms/FormAliasTransaction/FormAliasTransaction.vue +++ b/src/views/forms/FormAliasTransaction/FormAliasTransaction.vue @@ -63,7 +63,7 @@ diff --git a/src/components/PeerSelector/PeerSelectorTs.ts b/src/components/PeerSelector/PeerSelectorTs.ts index f2de611a7..4cc0b5121 100644 --- a/src/components/PeerSelector/PeerSelectorTs.ts +++ b/src/components/PeerSelector/PeerSelectorTs.ts @@ -121,6 +121,7 @@ export class PeerSelectorTs extends Vue { } else { return p.url.includes(this.formItems.filter) || (p.friendlyName && p.friendlyName.includes(this.formItems.filter)) + || this.currentPeerInfo.url === p.url } }) return _.sortBy(nodeModels, (a) => a.isDefault !== true, (a) => a.url) @@ -172,8 +173,10 @@ export class PeerSelectorTs extends Vue { this.$refs.observer.reset() // scroll to the bottom of the node list - const container = this.$el.querySelector('#node-list-container') - container.scrollTop = container.scrollHeight + // const container = this.$el.querySelector('#node-list-container') + // container.scrollTop = container.scrollHeight + // Maybe scroll to element instead of tunning the filter? + this.formItems.filter = nodeUrl }) } catch (e) { // hide loading overlay diff --git a/src/services/NodeService.ts b/src/services/NodeService.ts index b5aeab729..a2e9cbdeb 100644 --- a/src/services/NodeService.ts +++ b/src/services/NodeService.ts @@ -14,8 +14,8 @@ * */ -import {NodeInfo, RepositoryFactory, RoleType} from 'symbol-sdk' -import {combineLatest, Observable} from 'rxjs' +import {NodeInfo, NodeRepository, RepositoryFactory, RoleType} from 'symbol-sdk' +import {combineLatest, Observable, of} from 'rxjs' import {ObservableHelpers} from '@/core/utils/ObservableHelpers' import {map, tap} from 'rxjs/operators' import {NodeModel} from '@/core/database/entities/NodeModel' @@ -43,10 +43,10 @@ export class NodeService { return combineLatest([ nodeRepository.getNodeInfo().pipe(map(dto => this.createNodeModel(repositoryFactoryUrl, dto.friendlyName))) - .pipe(ObservableHelpers.defaultLast(this.createNodeModel(repositoryFactoryUrl))), - nodeRepository.getNodePeers().pipe(map(l => l.map(this.toNodeModel).filter(n => n && n.url))) - .pipe(ObservableHelpers.defaultLast( - storedNodes)), + .pipe(ObservableHelpers.defaultLast(this.createNodeModel(repositoryFactoryUrl))), + this.getNodePeers(nodeRepository) + .pipe(ObservableHelpers.defaultLast( + storedNodes)), ]).pipe(map(restData => { const currentNode = restData[0] @@ -57,6 +57,11 @@ export class NodeService { } + private getNodePeers(nodeRepository: NodeRepository): Observable { + // return nodeRepository.getNodePeers().pipe(map(l => l.map(this.toNodeModel).filter(n => n && n.url))) + return of([]); + } + private loadStaticNodes(): NodeModel[] { return networkConfig.nodes.map(n => { return this.createNodeModel(n.url, n.friendlyName, true) @@ -73,8 +78,8 @@ export class NodeService { private createNodeModel(url: string, - friendlyName: string | undefined = undefined, - isDefault: boolean | undefined = undefined): NodeModel { + friendlyName: string | undefined = undefined, + isDefault: boolean | undefined = undefined): NodeModel { return new NodeModel(url, friendlyName || '', isDefault || !!networkConfig.nodes.find(n => n.url === url)) } From 3b7542f202af7101bb9ea2cd75519cd88bd2497b Mon Sep 17 00:00:00 2001 From: Fernando Boucquez Date: Tue, 21 Apr 2020 16:21:26 -0300 Subject: [PATCH 16/17] fixed lint --- src/services/NodeService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/NodeService.ts b/src/services/NodeService.ts index a2e9cbdeb..e11ec9f05 100644 --- a/src/services/NodeService.ts +++ b/src/services/NodeService.ts @@ -43,10 +43,10 @@ export class NodeService { return combineLatest([ nodeRepository.getNodeInfo().pipe(map(dto => this.createNodeModel(repositoryFactoryUrl, dto.friendlyName))) - .pipe(ObservableHelpers.defaultLast(this.createNodeModel(repositoryFactoryUrl))), + .pipe(ObservableHelpers.defaultLast(this.createNodeModel(repositoryFactoryUrl))), this.getNodePeers(nodeRepository) - .pipe(ObservableHelpers.defaultLast( - storedNodes)), + .pipe(ObservableHelpers.defaultLast( + storedNodes)), ]).pipe(map(restData => { const currentNode = restData[0] @@ -59,7 +59,7 @@ export class NodeService { private getNodePeers(nodeRepository: NodeRepository): Observable { // return nodeRepository.getNodePeers().pipe(map(l => l.map(this.toNodeModel).filter(n => n && n.url))) - return of([]); + return of([]) } private loadStaticNodes(): NodeModel[] { @@ -78,8 +78,8 @@ export class NodeService { private createNodeModel(url: string, - friendlyName: string | undefined = undefined, - isDefault: boolean | undefined = undefined): NodeModel { + friendlyName: string | undefined = undefined, + isDefault: boolean | undefined = undefined): NodeModel { return new NodeModel(url, friendlyName || '', isDefault || !!networkConfig.nodes.find(n => n.url === url)) } From a8d787b801363bc8b758905c6245457960d0bd2d Mon Sep 17 00:00:00 2001 From: Gregory Saive Date: Wed, 22 Apr 2020 12:03:17 +0200 Subject: [PATCH 17/17] fix on-logout wallet details destroy issue --- package-lock.json | 9 --------- .../wallets/WalletDetailsPage/WalletDetailsPage.vue | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index d191decc8..4842a145a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24036,15 +24036,6 @@ } } }, - "ts-mockito": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.5.0.tgz", - "integrity": "sha512-b3qUeMfghRq5k5jw3xNJcnU9RKhqKnRn0k9v9QkN+YpuawrFuMIiGwzFZCpdi5MHy26o7YPnK8gag2awURl3nA==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, "ts-pnp": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz", diff --git a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue index 061e5ffa1..ef34fe7ae 100644 --- a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue +++ b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue @@ -1,5 +1,5 @@