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/__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__/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__/e2e/MosaicServiceIntegrationTest.spec.ts b/__tests__/e2e/MosaicServiceIntegrationTest.spec.ts new file mode 100644 index 000000000..cd153d882 --- /dev/null +++ b/__tests__/e2e/MosaicServiceIntegrationTest.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 {Address, RepositoryFactoryHttp} from 'symbol-sdk' +import {MosaicService} from '@/services/MosaicService' + + +const address1 = Address.createFromRawAddress('TDH3WI3AXBZJQMFI5XPTWRABTWVEDR54DMTHAB6I') +const address2 = Address.createFromRawAddress('TAI4NEL4SBQDSPY2XKMPIGOC53UALIXJ5WHRJVQZ') +const address3 = Address.createFromRawAddress('TAHNZXQBC57AA7KJTMGS3PJPZBXN7DV5JHJU42GL') +const address4 = Address.createFromRawAddress('TB5JQQKUT7MBEMFVIYYSZQ37JYG4UKMXCNJ6DUQ5') +const address5 = Address.createFromRawAddress('TAWBSSHIKZNX6E65FJG4BVIJR236Z64Z77CMOJBO') + +const mosaicService = new MosaicService() +const realUrl = 'http://api-01.us-west-1.symboldev.network:3000' +const realRepositoryFactory = new RepositoryFactoryHttp(realUrl) + +describe.skip('services/MosaicService', () => { + test('getMosaics all addresses', async () => { + const networkCurrencies = await mosaicService.getNetworkCurrencies(realRepositoryFactory).toPromise() + const addresses: Address[] = [ address1, address2, address3, address4, address5 ] + const accountInfos = await realRepositoryFactory.createAccountRepository().getAccountsInfo(addresses).toPromise() + const result = await mosaicService.getMosaics(realRepositoryFactory, networkCurrencies, accountInfos).toPromise() + console.log(JSON.stringify(result, null, 2)) + }) + + test('getMosaics account 1 addresses', async () => { + const networkCurrencies = await mosaicService.getNetworkCurrencies(realRepositoryFactory).toPromise() + const addresses: Address[] = [address1] + const accountInfos = await realRepositoryFactory.createAccountRepository().getAccountsInfo(addresses).toPromise() + const result = await mosaicService.getMosaics(realRepositoryFactory, networkCurrencies, accountInfos).toPromise() + console.log(JSON.stringify(result, null, 2)) + }) + + test('getMosaics account 3 addresses', async () => { + const networkCurrencies = await mosaicService.getNetworkCurrencies(realRepositoryFactory).toPromise() + const addresses: Address[] = [address3] + const accountInfos = await realRepositoryFactory.createAccountRepository().getAccountsInfo(addresses).toPromise() + const result = await mosaicService.getMosaics(realRepositoryFactory, networkCurrencies, accountInfos).toPromise() + console.log(JSON.stringify(result, null, 2)) + }) +}) diff --git a/__tests__/e2e/NetworkServiceIntegrationTest.spec.ts b/__tests__/e2e/NetworkServiceIntegrationTest.spec.ts new file mode 100644 index 000000000..b51b23724 --- /dev/null +++ b/__tests__/e2e/NetworkServiceIntegrationTest.spec.ts @@ -0,0 +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 {NetworkService} from '@/services/NetworkService' + +import networkConfig from '../../config/network.conf.json' + +const networkService = new NetworkService() +describe.skip('services/NetworkService', () => { + test('getNetworkModel when default', async () => { + const data = await networkService.getNetworkModel(undefined).toPromise() + expect(data.networkModel.url).toBe(networkConfig.defaultNodeUrl) + expect(data.fallback).toBe(false) + }) + + test('getNetworkModel when custom', async () => { + const candidate = 'http://api-01.eu-central-1.symboldev.network:3000' + const data = await networkService.getNetworkModel(candidate).toPromise() + expect(data.networkModel.url).toBe(candidate) + expect(data.fallback).toBe(false) + }) + + test('getNetworkModel when broken', async () => { + const candidate = 'http://localhost:3000' + + const data = await networkService.getNetworkModel(candidate).toPromise() + expect(data.networkModel.url).toBe(networkConfig.defaultNodeUrl) + expect(data.fallback).toBe(true) + }) +}) diff --git a/__tests__/e2e/NodeServiceIntegrationTest.spec.ts b/__tests__/e2e/NodeServiceIntegrationTest.spec.ts new file mode 100644 index 000000000..6b0fdd977 --- /dev/null +++ b/__tests__/e2e/NodeServiceIntegrationTest.spec.ts @@ -0,0 +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 {RepositoryFactoryHttp} from 'symbol-sdk' +import {NodeService} from '@/services/NodeService' +import {toArray} from 'rxjs/operators' + + +const nodeService = new NodeService() +const realUrl = 'http://api-01.us-west-1.symboldev.network:3000' +const realRepositoryFactory = new RepositoryFactoryHttp(realUrl) + +describe.skip('services/NodeService', () => { + test('getNodes', async () => { + const peers = await nodeService.getNodes(realRepositoryFactory, realUrl).pipe(toArray()).toPromise() + console.log(JSON.stringify(peers, null, 2)) + }) +}) 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 3fb6fb9bf..20ab60f09 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, Password} from 'symbol-sdk' +import {NetworkType, Account, Password, EncryptedPrivateKey} from 'symbol-sdk' import {WalletService} from '@/services/WalletService' import {MnemonicPassPhrase} from 'symbol-hd-wallets' import {wallet1Params, WalletsModel1} from '@MOCKS/Wallets' @@ -115,20 +115,23 @@ 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 encryptedKey = service.updateWalletPassword( + const updatedWallet = service.updateWalletPassword( WalletsModel1, wallet1Params.password, new Password('password2'), ) // decrypt the new model's private key - const privateKey = encryptedKey.decrypt(new Password('password2')) + const newEncPrivate = updatedWallet.encPrivate + const newEncIv = updatedWallet.encIv + const privateKey = new EncryptedPrivateKey(newEncPrivate, newEncIv) + .decrypt(new Password('password2')) // assert the encrypted private key changed - expect(encryptedKey.encryptedKey).not.toBe(initialEncPrivate) - expect(encryptedKey.iv).not.toBe(initialEncIv) + expect(newEncPrivate).not.toBe(initialEncPrivate) + expect(newEncIv).not.toBe(initialEncIv) // assert the plain private key did not change expect(privateKey).toBe(wallet1Params.privateKey) diff --git a/__tests__/store/Account.spec.ts b/__tests__/store/Account.spec.ts index 624483e5a..1f6bc4cfb 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' import flushPromises from 'flush-promises' describe('store/Account', () => { @@ -37,7 +37,7 @@ describe('store/Account', () => { test('dispatch "RESET_STATE"', async (done) => { // prepare const dispatch = jest.fn() - const rootGetters = {'wallet/currentWallet': getFakeModel('1234')} + const rootGetters = {'wallet/currentWallet': {}} // act AccountStore.actions.LOG_OUT({dispatch, rootGetters}) @@ -58,7 +58,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 aa34dcd91..4842a145a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2802,6 +2802,12 @@ "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==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -12440,9 +12446,9 @@ "dev": true }, "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==", "dev": true }, "galactus": { @@ -21978,9 +21984,9 @@ } }, "rxjs-compat": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.5.4.tgz", - "integrity": "sha512-rkn+lbOHUQOurdd74J/hjmDsG9nFx0z66fvnbs8M95nrtKvNqCKdk7iZqdY51CGmDemTQk+kUPy4s8HVOHtkfA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.5.5.tgz", + "integrity": "sha512-F42sssVbUyWH4vJswEo6m+Eh02xHv3q93n8S7nUJO58R7sbc3CvJIOts605zdaBhWa1xMB9aVSyqPqhQ5q3eXg==", "dev": true }, "safe-buffer": { @@ -23211,18 +23217,53 @@ "tweetnacl": "^1.0.3" }, "dependencies": { + "symbol-sdk": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.4.tgz", + "integrity": "sha512-mSZMWYbMUR4tqUqvYSi8n34eDFVW5sr4nQ4zitkG3HqtWIbarCAFoSqZoVa0gT9QyCKGgkBt23OJ2P7TF4aJfQ==", + "dev": true, + "requires": { + "bluebird": "^3.7.2", + "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", + "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.9", + "tweetnacl": "^1.0.3", + "utf8": "^3.0.0", + "ws": "^7.2.3" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "dev": true } } }, "symbol-openapi-typescript-node-client": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/symbol-openapi-typescript-node-client/-/symbol-openapi-typescript-node-client-0.8.5.tgz", - "integrity": "sha512-1o6qvkabi93xLRHr/C2A+uFhesaXF26EsFIRW6B51DLOGFOrDRPlobnKyahRkieJXEALTOj6MXimUGd/O6jdSw==", + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/symbol-openapi-typescript-node-client/-/symbol-openapi-typescript-node-client-0.8.9.tgz", + "integrity": "sha512-JElZ2vA63oqFGaT2wdEe7L2P17i3NaTwQhoQO4Pk1zMrQ9D+dGI1iknTuAXj2+xqxj36vLJX4Vi1UugvSkfJCA==", "dev": true, "requires": { "@types/bluebird": "*", @@ -23256,16 +23297,57 @@ "requires": { "glob": "^7.1.3" } + }, + "symbol-sdk": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.4.tgz", + "integrity": "sha512-mSZMWYbMUR4tqUqvYSi8n34eDFVW5sr4nQ4zitkG3HqtWIbarCAFoSqZoVa0gT9QyCKGgkBt23OJ2P7TF4aJfQ==", + "dev": true, + "requires": { + "bluebird": "^3.7.2", + "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", + "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.9", + "tweetnacl": "^1.0.3", + "utf8": "^3.0.0", + "ws": "^7.2.3" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "dev": true } } }, "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", + "resolved": "https://registry.npmjs.org/symbol-sdk/-/symbol-sdk-0.17.4.tgz", + "integrity": "sha512-mSZMWYbMUR4tqUqvYSi8n34eDFVW5sr4nQ4zitkG3HqtWIbarCAFoSqZoVa0gT9QyCKGgkBt23OJ2P7TF4aJfQ==", "dev": true, "requires": { - "bluebird": "^3.5.5", + "bluebird": "^3.7.2", "catbuffer-typescript": "0.0.11", "crypto-js": "^3.1.9-1", "diff": "^4.0.2", @@ -23276,15 +23358,16 @@ "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.9", "tweetnacl": "^1.0.3", - "utf8": "^2.1.2", - "ws": "^5.2.0" + "utf8": "^3.0.0", + "ws": "^7.2.3" }, "dependencies": { "tweetnacl": { @@ -23292,6 +23375,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "dev": true } } }, @@ -24485,9 +24574,9 @@ } }, "utf8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", - "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", "dev": true }, "utf8-byte-length": { diff --git a/package.json b/package.json index e3273c888..518b6d234 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@babel/core": "^7.7.2", "@types/crypto-js": "^3.1.43", "@types/jest": "^23.3.1", + "@types/lodash": "^4.14.149", "@types/node": "^9.6.50", "@types/request": "^2.47.1", "@types/vue-moment": "^4.0.0", @@ -93,7 +94,7 @@ "rimraf": "^3.0.2", "symbol-hd-wallets": "^0.9.2", "symbol-qr-library": "^0.9.0", - "symbol-sdk": "^0.17.3", + "symbol-sdk": "0.17.4", "ts-jest": "^25.3.0", "tslint": "^5.20.1", "typescript": "^3.7.2", diff --git a/src/app/AppTs.ts b/src/app/AppTs.ts index 413fe58fe..66526b2d2 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', }), }, @@ -43,13 +41,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 f85132e3a..ba15761ac 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanel.vue +++ b/src/components/AccountBalancesPanel/AccountBalancesPanel.vue @@ -12,22 +12,19 @@ >
- {{ currentSignerAddress }} + {{ currentSignerAddress.plain() }}
- -
-
{{ networkMosaicTicker }}
+
+
{{ networkCurrency.ticker }}
@@ -40,10 +37,10 @@
diff --git a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts index fa69d9af2..c237847c9 100644 --- a/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts +++ b/src/components/AccountBalancesPanel/AccountBalancesPanelTs.ts @@ -1,73 +1,58 @@ /** * 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 {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 {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({ + currentSignerAddress: 'wallet/currentSignerAddress', + balanceMosaics: 'mosaic/balanceMosaics', + isCosignatoryMode: 'wallet/isCosignatoryMode', + networkCurrency: 'mosaic/networkCurrency', + }), + }, }) export class AccountBalancesPanelTs extends Vue { - /** - * Currently active wallet - * @var {WalletsModel} - */ - public currentWallet: WalletsModel /** * 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 +64,8 @@ export class AccountBalancesPanelTs extends Vue { * Networks currency mosaic * @var {MosaicId} */ - public networkMosaic: MosaicId + public networkCurrency: NetworkCurrencyModel - /** - * Currency mosaic's ticker - * @var {string} - */ - public networkMosaicTicker: string /** * UI Helpers @@ -93,77 +73,13 @@ 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() - } - - /** - * Network mosaic divisibility - * @readonly - * @protected - * @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') + public async created() { + this.$store.dispatch('mosaic/LOAD_MOSAICS') } 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 { - const balance = this.absoluteBalance - 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/ActionDisplay.vue b/src/components/ActionDisplay/ActionDisplay.vue index e8c11947d..0a1ec6dc5 100644 --- a/src/components/ActionDisplay/ActionDisplay.vue +++ b/src/components/ActionDisplay/ActionDisplay.vue @@ -1,10 +1,14 @@ 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 @@ @@ -85,5 +98,11 @@ export default class PeerSelector extends PeerSelectorTs {} .ivu-poptip-body-content { overflow: hidden; } + .ivu-poptip-title { + overflow: hidden; + } + .ivu-poptip-inner { + width: 1000px; + } } diff --git a/src/components/PeerSelector/PeerSelectorTs.ts b/src/components/PeerSelector/PeerSelectorTs.ts index ece311ada..4cc0b5121 100644 --- a/src/components/PeerSelector/PeerSelectorTs.ts +++ b/src/components/PeerSelector/PeerSelectorTs.ts @@ -1,59 +1,47 @@ /** * 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' - -// 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' - } -} - // 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' +import {NetworkTypeHelper} from '@/core/utils/NetworkTypeHelper' +import * as _ from 'lodash' @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 +50,7 @@ export class PeerSelectorTs extends Vue { * @see {Store.Network} * @var {Object} */ - public currentPeer: Record + public currentPeerInfo: NodeModel /** * Whether the connection is up @@ -90,13 +78,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 +88,7 @@ export class PeerSelectorTs extends Vue { */ public formItems = { nodeUrl: '', + filter: '', setDefault: false, } @@ -119,29 +104,39 @@ 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[] { + const nodeModels = this.knowNodes.filter(p => { + if (!this.formItems.filter) { + return true + } 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) } get networkTypeText(): string { if (!this.isConnected) return this.$t('Invalid_node').toString() - return !!this.networkType ? getNetworkTypeText(this.networkType) : this.$t('Loading').toString() + return !!this.networkType ? NetworkTypeHelper.getNetworkTypeLabel(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,74 +147,40 @@ 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 node = URLHelpers.formatUrl(nodeUrl) + const nodeUrl = URLHelpers.getNodeUrl(this.formItems.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 } - // show loading overlay - this.$store.dispatch('app/SET_LOADING_OVERLAY', { - show: true, - message: `${this.$t('info_connecting_peer', {peerUrl: node.url})}`, - }) - // 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() // 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) { + } 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 +190,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 5591e0aac..0f1af66b9 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,10 @@ 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' +import {WalletModel} from '@/core/database/entities/WalletModel' @Component({ components: { @@ -51,11 +45,15 @@ import FormMosaicSupplyChangeTransaction from '@/views/forms/FormMosaicSupplyCha FormExtendNamespaceDurationTransaction, FormMosaicSupplyChangeTransaction, }, - computed: {...mapGetters({ - currentWalletAddress: 'wallet/currentWalletAddress', - ownedMosaics: 'wallet/currentWalletOwnedMosaics', - ownedNamespaces: 'wallet/currentWalletOwnedNamespaces', - })}, + computed: { + ...mapGetters({ + currentHeight: 'network/currentHeight', + currentWallet: 'wallet/currentWallet', + holdMosaics: 'mosaic/holdMosaics', + ownedNamespaces: 'namespace/ownedNamespaces', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class TableDisplayTs extends Vue { /** @@ -67,24 +65,30 @@ 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 * @protected - * @type {MosaicInfo[]} + * @type {MosaicModel[]} */ - protected ownedMosaics: MosaicInfo[] + private holdMosaics: MosaicModel[] /** * Current wallet owned namespaces * @protected - * @type {NamespaceInfo[]} + * @type {NamespaceModel[]} */ - protected ownedNamespaces: NamespaceInfo[] + private ownedNamespaces: NamespaceModel[] + + private currentWallet: WalletModel + + private currentHeight: number + + private networkConfiguration: NetworkConfigurationModel /** * Current table sorting state @@ -93,17 +97,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 @@ -119,10 +116,12 @@ export class TableDisplayTs extends Vue { public nodata = [...new Array(this.pageSize).keys()] + 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.holdMosaics.filter(({ownerRawPlain}) => ownerRawPlain === this.currentWallet.address) + .map(({mosaicIdHex}) => mosaicIdHex) } /** @@ -165,6 +164,7 @@ export class TableDisplayTs extends Vue { aliasAction: null, mosaicId: null, } + // Alias forms props /** @@ -173,12 +173,12 @@ 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.holdMosaics, + this.networkConfiguration) + } 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.`) } @@ -223,14 +223,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') } @@ -243,6 +245,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 /** @@ -264,14 +267,14 @@ export class TableDisplayTs extends Vue { */ private async refresh(): Promise { this.loading = true - if (this.assetType === 'namespace') { - this.$store.dispatch('wallet/REST_FETCH_OWNED_NAMESPACES', this.currentWalletAddress.plain()) + if ('mosaic' === this.assetType) { + await this.$store.dispatch('mosaic/LOAD_MOSAICS') + } else if ('namespace' === this.assetType) { + await 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) this.loading = false } + /** * Sets the default filtering state */ @@ -307,7 +310,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} } @@ -319,7 +322,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}) @@ -342,7 +345,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 } @@ -361,7 +365,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 } @@ -401,21 +405,22 @@ export class TableDisplayTs extends Vue { protected closeModal(modalIdentifier: string): void { Vue.set(this.modalFormsVisibility, modalIdentifier, false) } + /** * avoid multiple clicks * @protected - * @param {string} + * @param {string} * @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/TransactionDetails/AccountAddressRestriction/AccountAddressRestriction.vue b/src/components/TransactionDetails/AccountAddressRestriction/AccountAddressRestriction.vue index e93c86c82..b63669247 100644 --- a/src/components/TransactionDetails/AccountAddressRestriction/AccountAddressRestriction.vue +++ b/src/components/TransactionDetails/AccountAddressRestriction/AccountAddressRestriction.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/AccountLink/AccountLink.vue b/src/components/TransactionDetails/AccountLink/AccountLink.vue index 57f2f2183..d7b00d67d 100644 --- a/src/components/TransactionDetails/AccountLink/AccountLink.vue +++ b/src/components/TransactionDetails/AccountLink/AccountLink.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/AccountMetadata/AccountMetadata.vue b/src/components/TransactionDetails/AccountMetadata/AccountMetadata.vue index 62368c4fb..f635e634d 100644 --- a/src/components/TransactionDetails/AccountMetadata/AccountMetadata.vue +++ b/src/components/TransactionDetails/AccountMetadata/AccountMetadata.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/AccountMosaicRestriction/AccountMosaicRestriction.vue b/src/components/TransactionDetails/AccountMosaicRestriction/AccountMosaicRestriction.vue index d888092d8..6b79bc612 100644 --- a/src/components/TransactionDetails/AccountMosaicRestriction/AccountMosaicRestriction.vue +++ b/src/components/TransactionDetails/AccountMosaicRestriction/AccountMosaicRestriction.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/AccountOperationRestriction/AccountOperationRestriction.vue b/src/components/TransactionDetails/AccountOperationRestriction/AccountOperationRestriction.vue index 5e5799e32..fcc90bc52 100644 --- a/src/components/TransactionDetails/AccountOperationRestriction/AccountOperationRestriction.vue +++ b/src/components/TransactionDetails/AccountOperationRestriction/AccountOperationRestriction.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/Alias/Alias.vue b/src/components/TransactionDetails/Alias/Alias.vue index acc9054be..90911075b 100644 --- a/src/components/TransactionDetails/Alias/Alias.vue +++ b/src/components/TransactionDetails/Alias/Alias.vue @@ -1,25 +1,21 @@ diff --git a/src/components/TransactionDetails/HashLock/HashLock.vue b/src/components/TransactionDetails/HashLock/HashLock.vue index fa28fd334..509e630c7 100644 --- a/src/components/TransactionDetails/HashLock/HashLock.vue +++ b/src/components/TransactionDetails/HashLock/HashLock.vue @@ -1,11 +1,11 @@ @@ -19,6 +19,7 @@ import { AttachedMosaic } from '@/services/MosaicService' // child components import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' @Component({ components: { TransactionDetailRow } }) export default class HashLock extends Vue { @@ -28,7 +29,7 @@ export default class HashLock extends Vue { * Displayed items * @type {({ key: string, value: string | boolean, isMosaic: boolean }[])} */ - protected get items(): { key: string, value: any, isMosaic?: boolean }[] { + protected get items(): TransactionDetailItem[] { // get attached mosaic const attachedMosaic: AttachedMosaic = this.view.values.get('mosaic') diff --git a/src/components/TransactionDetails/MosaicAddressRestriction/MosaicAddressRestriction.vue b/src/components/TransactionDetails/MosaicAddressRestriction/MosaicAddressRestriction.vue index 6125a6c9f..e8db7d4d9 100644 --- a/src/components/TransactionDetails/MosaicAddressRestriction/MosaicAddressRestriction.vue +++ b/src/components/TransactionDetails/MosaicAddressRestriction/MosaicAddressRestriction.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/MosaicDefinition/MosaicDefinition.vue b/src/components/TransactionDetails/MosaicDefinition/MosaicDefinition.vue index fcf041c01..433b8243c 100644 --- a/src/components/TransactionDetails/MosaicDefinition/MosaicDefinition.vue +++ b/src/components/TransactionDetails/MosaicDefinition/MosaicDefinition.vue @@ -1,11 +1,11 @@ @@ -20,6 +20,7 @@ import { TransactionViewType } from '@/services/TransactionService' // child components import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' @Component({ components: { TransactionDetailRow } }) export default class MosaicDefinition extends Vue { @@ -29,7 +30,7 @@ export default class MosaicDefinition extends Vue { * Displayed items * @type {({ key: string, value: string | boolean }[])} */ - get items(): { key: string, value: string | boolean }[] { + get items(): TransactionDetailItem[] { const mosaicId: MosaicId = this.view.values.get('mosaicId') const divisibility: number = this.view.values.get('divisibility') const mosaicFlags: MosaicFlags = this.view.values.get('mosaicFlags') diff --git a/src/components/TransactionDetails/MosaicGlobalRestriction/MosaicGlobalRestriction.vue b/src/components/TransactionDetails/MosaicGlobalRestriction/MosaicGlobalRestriction.vue index 37df866d5..292e1ab97 100644 --- a/src/components/TransactionDetails/MosaicGlobalRestriction/MosaicGlobalRestriction.vue +++ b/src/components/TransactionDetails/MosaicGlobalRestriction/MosaicGlobalRestriction.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/MosaicMetadata/MosaicMetadata.vue b/src/components/TransactionDetails/MosaicMetadata/MosaicMetadata.vue index 2090d8b68..f15561634 100644 --- a/src/components/TransactionDetails/MosaicMetadata/MosaicMetadata.vue +++ b/src/components/TransactionDetails/MosaicMetadata/MosaicMetadata.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/MosaicSupplyChange/MosaicSupplyChange.vue b/src/components/TransactionDetails/MosaicSupplyChange/MosaicSupplyChange.vue index 72689d553..ae55972a9 100644 --- a/src/components/TransactionDetails/MosaicSupplyChange/MosaicSupplyChange.vue +++ b/src/components/TransactionDetails/MosaicSupplyChange/MosaicSupplyChange.vue @@ -1,11 +1,11 @@ @@ -20,6 +20,7 @@ import { TransactionViewType } from '@/services/TransactionService' // child components import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' @Component({ components: { TransactionDetailRow } }) export default class MosaicSupplyChange extends Vue { @@ -30,7 +31,7 @@ export default class MosaicSupplyChange extends Vue { * @see {Store.Mosaic} * @type {({ key: string, value: string | boolean }[])} */ - get items(): { key: string, value: string | boolean }[] { + get items(): TransactionDetailItem[] { const mosaicId: MosaicId = this.view.values.get('mosaicId') const action: MosaicSupplyChangeAction = this.view.values.get('action') const delta: UInt64 = this.view.values.get('delta') diff --git a/src/components/TransactionDetails/MultisigAccountModification/MultisigAccountModification.vue b/src/components/TransactionDetails/MultisigAccountModification/MultisigAccountModification.vue index b8d29d1ab..7b27c4363 100644 --- a/src/components/TransactionDetails/MultisigAccountModification/MultisigAccountModification.vue +++ b/src/components/TransactionDetails/MultisigAccountModification/MultisigAccountModification.vue @@ -1,11 +1,11 @@ @@ -21,6 +21,7 @@ import {TransactionViewType} from '@/services/TransactionService' // child components import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' @Component({ components: { TransactionDetailRow }, @@ -35,7 +36,7 @@ export default class MultisigAccountModification extends Vue { */ private networkType: NetworkType - protected get items(): { key: string, value: string | boolean }[] { + protected get items(): TransactionDetailItem[] { // get data from view values const minApprovalDelta: number = this.view.values.get('minApprovalDelta') const minRemovalDelta: number = this.view.values.get('minRemovalDelta') diff --git a/src/components/TransactionDetails/NamespaceMetadata/NamespaceMetadata.vue b/src/components/TransactionDetails/NamespaceMetadata/NamespaceMetadata.vue index c57485bf3..879b93619 100644 --- a/src/components/TransactionDetails/NamespaceMetadata/NamespaceMetadata.vue +++ b/src/components/TransactionDetails/NamespaceMetadata/NamespaceMetadata.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/NamespaceRegistration/NamespaceRegistration.vue b/src/components/TransactionDetails/NamespaceRegistration/NamespaceRegistration.vue index c2e82abf7..488e94f86 100644 --- a/src/components/TransactionDetails/NamespaceRegistration/NamespaceRegistration.vue +++ b/src/components/TransactionDetails/NamespaceRegistration/NamespaceRegistration.vue @@ -1,11 +1,11 @@ @@ -20,6 +20,7 @@ import { TransactionViewType } from '@/services/TransactionService' // child components import TransactionDetailRow from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' @Component({ components: { TransactionDetailRow } }) export default class NamespaceRegistration extends Vue { @@ -30,7 +31,7 @@ export default class NamespaceRegistration extends Vue { * @see {Store.Mosaic} * @type {({ key: string, value: string | boolean }[])} */ - get items(): { key: string, value: string | boolean }[] { + get items(): TransactionDetailItem[] { const rootNamespaceName: string = this.view.values.get('rootNamespaceName') const subNamespaceName: string = this.view.values.get('subNamespaceName') const registrationType: NamespaceRegistrationType = this.view.values.get( diff --git a/src/components/TransactionDetails/SecretLock/SecretLock.vue b/src/components/TransactionDetails/SecretLock/SecretLock.vue index 59719e7a7..4a69a3d75 100644 --- a/src/components/TransactionDetails/SecretLock/SecretLock.vue +++ b/src/components/TransactionDetails/SecretLock/SecretLock.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/SecretProof/SecretProof.vue b/src/components/TransactionDetails/SecretProof/SecretProof.vue index 571b4584c..c6c57b240 100644 --- a/src/components/TransactionDetails/SecretProof/SecretProof.vue +++ b/src/components/TransactionDetails/SecretProof/SecretProof.vue @@ -1,11 +1,11 @@ diff --git a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem.ts b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem.ts new file mode 100644 index 000000000..98af2beab --- /dev/null +++ b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem.ts @@ -0,0 +1,22 @@ +/* + * 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 interface TransactionDetailItem { + readonly key: string + readonly value: any + readonly isMosaic?: boolean + readonly isAddress?: boolean +} diff --git a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue index b3605a229..07325c3a1 100644 --- a/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue +++ b/src/components/TransactionDetails/TransactionDetailRow/TransactionDetailRow.vue @@ -10,19 +10,22 @@ {{ value }} + :href="(explorerBaseUrl + '/transaction/' + item.value)" + >{{ item.value }} - + + + + - {{ value }} + {{ item.value }} @@ -31,20 +34,23 @@ + + diff --git a/src/components/WalletActions/WalletActionsTs.ts b/src/components/WalletActions/WalletActionsTs.ts index 31698d7c6..3a7249465 100644 --- a/src/components/WalletActions/WalletActionsTs.ts +++ b/src/components/WalletActions/WalletActionsTs.ts @@ -13,17 +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 {Component, Prop, Vue} from 'vue-property-decorator' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' @Component export class WalletActionsTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /// region computed properties getter/setter /// end-region computed properties getter/setter diff --git a/src/components/WalletAddressDisplay/WalletAddressDisplay.vue b/src/components/WalletAddressDisplay/WalletAddressDisplay.vue index 7af3921f8..d8700bd6a 100644 --- a/src/components/WalletAddressDisplay/WalletAddressDisplay.vue +++ b/src/components/WalletAddressDisplay/WalletAddressDisplay.vue @@ -2,11 +2,11 @@
{{ $t('Wallet_address') }}
- {{ wallet.objects.address.pretty() }} + {{ getWalletAddressPretty() }}
diff --git a/src/components/WalletAddressDisplay/WalletAddressDisplayTs.ts b/src/components/WalletAddressDisplay/WalletAddressDisplayTs.ts index ce719186e..00b698af3 100644 --- a/src/components/WalletAddressDisplay/WalletAddressDisplayTs.ts +++ b/src/components/WalletAddressDisplay/WalletAddressDisplayTs.ts @@ -1,22 +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 {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 WalletAddressDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers @@ -32,6 +31,10 @@ export class WalletAddressDisplayTs extends Vue { */ public uiHelpers = UIHelpers + public getWalletAddressPretty(): string { + return this.wallet && WalletModel.getObjects(this.wallet).address.pretty() || '' + } + /// region computed properties getter/setter /// end-region computed properties getter/setter } diff --git a/src/components/WalletAliasDisplay/WalletAliasDisplayTs.ts b/src/components/WalletAliasDisplay/WalletAliasDisplayTs.ts index 90a87e82e..df1263a5f 100644 --- a/src/components/WalletAliasDisplay/WalletAliasDisplayTs.ts +++ b/src/components/WalletAliasDisplay/WalletAliasDisplayTs.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,48 +14,36 @@ * limitations under the License. */ // external dependencies -import {Component, Vue, Prop} from 'vue-property-decorator' +import {Component, Prop, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NamespaceInfo} from 'symbol-sdk' - // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' +import {WalletModel} from '@/core/database/entities/WalletModel' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' @Component({ computed: mapGetters({ - namespacesInfo: 'namespace/namespacesInfo', - namespacesNames: 'namespace/namespacesNames', + namespaces: 'namespace/namespaces', }), }) export class WalletAliasDisplayTs extends Vue { - @Prop({ default: null }) wallet: WalletsModel - - /** - * Namespaces info - * @protected - * @type {Record} - */ - protected namespacesInfo: Record + @Prop({default: null}) wallet: WalletModel /** - * Namespaces names - * @protected - * @type {Record} + * NamespaceModel */ - protected namespacesNames: Record + protected namespaces: NamespaceModel[] get walletAliases(): string[] { - if (!this.namespacesInfo || !this.wallet) return [] + if (!this.namespaces || !this.wallet) return [] - // get an array of namespaceInfo - const namespacesInfo = Object.values(this.namespacesInfo) // get the current wallet address - const address = this.wallet.values.get('address') + const address = this.wallet.address // return the current wallet aliases - return namespacesInfo - .filter(({alias}) => alias.address && alias.address.plain() === address) - .map(({id}) => this.namespacesNames[id.toHex()] || id.toHex()) + return this.namespaces + .filter( + ({aliasTargetAddressRawPlain}) => aliasTargetAddressRawPlain && aliasTargetAddressRawPlain === address) + .map(({name, namespaceIdHex}) => name || namespaceIdHex) } } diff --git a/src/components/WalletBackupOptions/WalletBackupOptionsTs.ts b/src/components/WalletBackupOptions/WalletBackupOptionsTs.ts index 4c3b13aa4..4d0b35dea 100644 --- a/src/components/WalletBackupOptions/WalletBackupOptionsTs.ts +++ b/src/components/WalletBackupOptions/WalletBackupOptionsTs.ts @@ -16,8 +16,7 @@ import {Component, Vue, Prop} from 'vue-property-decorator' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' - +import {WalletModel} from '@/core/database/entities/WalletModel' // child components // @ts-ignore import ModalFormAccountUnlock from '@/views/modals/ModalFormAccountUnlock/ModalFormAccountUnlock.vue' @@ -36,7 +35,7 @@ export class WalletBackupOptionsTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * Whether account is currently being unlocked diff --git a/src/components/WalletContactQR/WalletContactQRTs.ts b/src/components/WalletContactQR/WalletContactQRTs.ts index 48a76b45d..f4d574dd3 100644 --- a/src/components/WalletContactQR/WalletContactQRTs.ts +++ b/src/components/WalletContactQR/WalletContactQRTs.ts @@ -13,19 +13,17 @@ * 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 {ContactQR} from 'symbol-qr-library' import {PublicAccount} from 'symbol-sdk' -import {of, Observable} from 'rxjs' -import {pluck, concatMap} from 'rxjs/operators' - +import {Observable, of} from 'rxjs' +import {concatMap, pluck} from 'rxjs/operators' // internal dependencies -import {WalletsModel} from '@/core/database/entities/WalletsModel' - +import {WalletModel} from '@/core/database/entities/WalletModel' // resources // @ts-ignore import failureIcon from '@/views/resources/img/monitor/failure.png' -import { mapGetters } from 'vuex' +import {mapGetters} from 'vuex' @Component({ computed: {...mapGetters({ @@ -46,7 +44,7 @@ export class WalletContactQRTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * Current network's generation hash @@ -68,9 +66,9 @@ export class WalletContactQRTs extends Vue { } try { - const publicAccount: PublicAccount = this.wallet.objects.publicAccount + const publicAccount: PublicAccount = WalletModel.getObjects(this.wallet).publicAccount return new ContactQR( - this.wallet.values.get('name'), + this.wallet.name, // @ts-ignore // @TODO: SDK upgrade publicAccount, publicAccount.address.networkType, diff --git a/src/components/WalletDetailsDisplay/WalletDetailsDisplay.vue b/src/components/WalletDetailsDisplay/WalletDetailsDisplay.vue index 44c2231a0..ebd055461 100644 --- a/src/components/WalletDetailsDisplay/WalletDetailsDisplay.vue +++ b/src/components/WalletDetailsDisplay/WalletDetailsDisplay.vue @@ -3,22 +3,22 @@
{{ $t('Wallet_address') }} - {{ wallet.objects.address.pretty() }} + {{ WalletModel.getObjects(wallet).address.pretty() }}
{{ $t('Wallet_public_key') }} - {{ wallet.objects.publicAccount.publicKey }} + {{ wallet.publicKey }}
diff --git a/src/components/WalletDetailsDisplay/WalletDetailsDisplayTs.ts b/src/components/WalletDetailsDisplay/WalletDetailsDisplayTs.ts index e72362a1a..c9610f91d 100644 --- a/src/components/WalletDetailsDisplay/WalletDetailsDisplayTs.ts +++ b/src/components/WalletDetailsDisplay/WalletDetailsDisplayTs.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 WalletDetailsDisplayTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel /** * UI Helpers diff --git a/src/components/WalletLinks/WalletLinksTs.ts b/src/components/WalletLinks/WalletLinksTs.ts index 06dbf1351..1066e7282 100644 --- a/src/components/WalletLinks/WalletLinksTs.ts +++ b/src/components/WalletLinks/WalletLinksTs.ts @@ -13,11 +13,10 @@ * 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 { mapGetters } from 'vuex' +import {WalletModel} from '@/core/database/entities/WalletModel' +import {mapGetters} from 'vuex' @Component({ computed: {...mapGetters({ @@ -29,7 +28,7 @@ export class WalletLinksTs extends Vue { @Prop({ default: null, - }) wallet: WalletsModel + }) wallet: WalletModel public explorerBaseUrl: string public faucetBaseUrl: string @@ -38,7 +37,7 @@ export class WalletLinksTs extends Vue { /// end-region computed properties getter/setter public get explorerUrl() { - return `${this.explorerBaseUrl}/account/${this.wallet.values.get('address')}` + return this.explorerBaseUrl + '/account/' + this.wallet.address } public get faucetUrl() { diff --git a/src/components/WalletNameDisplay/WalletNameDisplay.vue b/src/components/WalletNameDisplay/WalletNameDisplay.vue index 16921c0e6..af9f7c722 100644 --- a/src/components/WalletNameDisplay/WalletNameDisplay.vue +++ b/src/components/WalletNameDisplay/WalletNameDisplay.vue @@ -4,7 +4,7 @@ {{ $t('Wallet_name') }}
- {{ wallet.values.get('name') }} + {{ wallet.name }}
-
@@ -30,8 +28,7 @@
diff --git a/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts b/src/components/WalletSelectorPanel/WalletSelectorPanelTs.ts index d48139edd..240725d56 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,17 +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', - currentWallets: 'wallet/currentWallets', - 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} @@ -69,33 +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[] - - /** - * current wallets identifiers - * @var {string[]} - */ - public currentWallets: string[] - - /** - * Currently active wallet's balances - * @var {Mosaic[]} - */ - public knownWalletsInfo: AccountInfo[] - + public knownWallets: WalletModel[] /** * Networks currency mosaic * @var {MosaicId} @@ -103,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 /** @@ -130,91 +126,46 @@ 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)) - if(netBalance){ - // 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}))) + // 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(): WalletModel[] { + return this.knownWallets + } + public get hasAddWalletModal(): boolean { return this.isAddingWallet } @@ -226,10 +177,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 /** @@ -238,19 +190,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..788c33c70 --- /dev/null +++ b/src/core/database/entities/NodeModel.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. + * + */ +/** + * 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, + public readonly isDefault: boolean, + ) { + + } +} 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/TransactionView.ts b/src/core/transactions/TransactionView.ts index ca419eafb..22cb7d857 100644 --- a/src/core/transactions/TransactionView.ts +++ b/src/core/transactions/TransactionView.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. @@ -16,6 +16,7 @@ */ import {Store} from 'vuex' import {Transaction, TransactionInfo} from 'symbol-sdk' +import {TransactionDetailItem} from '@/components/TransactionDetails/TransactionDetailRow/TransactionDetailItem' export abstract class TransactionView { @@ -66,7 +67,7 @@ export abstract class TransactionView { /** * Construct a transaction view around \a store - * @param {Store} store + * @param {Store} store */ public constructor(store: Store) { this.$store = store @@ -116,4 +117,13 @@ export abstract class TransactionView { return this } + + /** + * It returns a list that that it easy to render when displaying TransactionDetailRow components. + */ + public get items(): TransactionDetailItem[] { + return Array.from(this.values.entries()).map(([ key, value ]) => { + return {key, value} + }) + } } diff --git a/src/core/transactions/ViewHashLockTransaction.ts b/src/core/transactions/ViewHashLockTransaction.ts index 769a02b18..cc65a84e9 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,11 @@ * 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 +48,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 +89,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..23ffac353 100644 --- a/src/core/transactions/ViewTransferTransaction.ts +++ b/src/core/transactions/ViewTransferTransaction.ts @@ -1,24 +1,24 @@ /** - * + * * 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 {AttachedMosaic} from '@/services/MosaicService' +import {MosaicModel} from '@/core/database/entities/MosaicModel' /// region custom types export type TransferFormFieldsType = { @@ -30,6 +30,7 @@ export type TransferFormFieldsType = { message?: string maxFee: UInt64 } + /// end-region custom types export class ViewTransferTransaction extends TransactionView { @@ -58,11 +59,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 +104,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/NetworkTypeHelper.ts b/src/core/utils/NetworkTypeHelper.ts new file mode 100644 index 000000000..5a21857fe --- /dev/null +++ b/src/core/utils/NetworkTypeHelper.ts @@ -0,0 +1,47 @@ +/* + * 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. + * + */ +/// region custom types +import {NetworkType} from 'symbol-sdk' + +type NetworkNodeEntry = { value: NetworkType, label: string } + +export class NetworkTypeHelper { + + /** + * Network types with their names + */ + public static networkTypeList: NetworkNodeEntry[] = [ + {value: NetworkType.MIJIN_TEST, label: 'MIJIN_TEST'}, + {value: NetworkType.MAIN_NET, label: 'MAIN_NET'}, + {value: NetworkType.TEST_NET, label: 'TEST_NET'}, + {value: NetworkType.MIJIN, label: 'MIJIN'}, + ] + + /** + * Getter for network type label + * @param {NetworkType} networkType + * @return {string} + */ + public static getNetworkTypeLabel(networkType: NetworkType): string { + const findType = NetworkTypeHelper.networkTypeList.find(n => n.value === networkType) + if (findType === undefined) { + return '' + } + return findType.label + } + + +} 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..3acb713f7 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,48 +14,53 @@ * 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 = { - 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 +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 515f152f1..437b78e7d 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 edcb5517f..519b9a3ac 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..39f734a52 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,67 @@ * 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): AccountModel { + const accounts = this.getAccountsByAccountName() + accounts[account.accountName] = account + this.accountsStorage.set(accounts) + return account + } + + public deleteAccount(accountName: string) { + const accounts = this.getAccountsByAccountName() + delete accounts[accountName] + this.accountsStorage.set(accounts) + } + + public updateSeed(account: AccountModel, seed: string): AccountModel { + return this.saveAccount(Object.assign(account, {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[]): AccountModel { + return this.saveAccount(Object.assign(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 +83,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..98b33bdd2 100644 --- a/src/services/AssetTableService/MosaicTableService.ts +++ b/src/services/AssetTableService/MosaicTableService.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 {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' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export class MosaicTableService extends AssetTableService { -/** - * Creates an instance of MosaicTableService. - * @param {*} store - */ - constructor(store?: Store) { - super(store) + + constructor(currentHeight: number, private readonly mosaics: MosaicModel[], + private readonly networkConfiguration: NetworkConfigurationModel) { + super(currentHeight) } /** @@ -48,44 +45,27 @@ 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, + this.networkConfiguration.blockGenerationTargetTime) // - 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 ee3fad0c0..8fa7e7a60 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, - 'aliasType': this.getAliasType(namespaceInfo), - 'aliasIdentifier': this.getAliasIdentifier(namespaceInfo), + 'aliasType': this.getAliasType(namespaceModel), + 'aliasIdentifier': this.getAliasIdentifier(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 * @public - * @param {NamespaceInfo} mosaicInfo + * @param the namespace model. * @returns {string} */ - public 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 ff5222fe4..42aa25b58 100644 --- a/src/services/MosaicService.ts +++ b/src/services/MosaicService.ts @@ -1,31 +1,35 @@ -/** +/* * 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 { TimeHelpers } from '@/core/utils/TimeHelpers' +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, Address, 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' +import {TimeHelpers} from '@/core/utils/TimeHelpers' // custom types export type ExpirationStatus = 'unlimited' | 'expired' | string | number +// TODO. Can this interface be removed? export interface AttachedMosaic { id: MosaicId | NamespaceId mosaicHex: string @@ -35,412 +39,283 @@ export interface AttachedMosaic { amount: number } -export class MosaicService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'mosaic' - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store +interface MosaicBalance { + mosaicId: MosaicId + amount: UInt64 + address: Address +} + +/** + * 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 { /** - * Construct a service instance around \a store - * @param store + * Store that caches the mosaic information of the current accounts when returned from rest. */ - constructor(store?: Store) { - super() - this.$store = store - } + private readonly mosaicDataStorage = new SimpleObjectStorage('mosaicData') /** - * Read the collection of known mosaics from database. - * - * @param {Function} filterFn - * @return {MosaicsModel[]} + * The storage to keep user configuration around mosaics. For example, the balance hidden + * feature. */ - public getMosaics( - filterFn: ( - value: MosaicsModel, - index: number, - array: MosaicsModel[] - ) => boolean = () => true, - ): MosaicsModel[] { - const repository = new MosaicsRepository() - return repository.collect().filter(filterFn) - } + private readonly mosaicConfigurationsStorage = new SimpleObjectStorage>( + 'mosaicConfiguration') /** - * Refresh mosaic models data - * @param {Mosaic[] | MosaicInfo[]} mosaics Mosaics to create / refresh in the database - * @param {boolean} [forceUpdate=false] Option to bypass the cache + * Store that caches the information around the network currency. The network currency is + * currently calculated from the block 1 transactions. + * + * In the near future, rest will return the information without loading block 1. */ - public async refreshMosaicModels( - mosaics: Mosaic[] | MosaicInfo[], - forceUpdate = false, - ) { - // @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) { - await this.fetchMosaicsInfos(mosaicIds as MosaicId[]) - return - } + private readonly networkCurrencyStorage = new SimpleObjectStorage( + 'networkCurrencyStorage') - // 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()) }), - ) - - // 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 resolvedBalancesObservable = this.resolveBalances(repositoryFactory, accountsInfo) + const accountAddresses = accountsInfo.map(a => a.address) + const mosaicsFromAccountsObservable = repositoryFactory.createMosaicRepository() + .getMosaicsFromAccounts(accountAddresses) + + return combineLatest([ resolvedBalancesObservable, mosaicsFromAccountsObservable ]) + .pipe(flatMap(([ balances, owedMosaics ]) => { + const mosaicIds = _.uniqBy([ ...balances.map(m => m.mosaicId), ...owedMosaics.map(o => o.id) ], m => m.toHex()) + const nameObservables = repositoryFactory.createNamespaceRepository().getMosaicsNames(mosaicIds) + const mosaicInfoObservable = repositoryFactory.createMosaicRepository().getMosaics(mosaicIds) + return combineLatest([ nameObservables, mosaicInfoObservable ]).pipe(map(([ names, mosaicInfos ]) => { + return this.toMosaicDtos(balances, mosaicInfos, names, networkCurrencies, accountAddresses) + })) + })).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)} - */ - public getMosaicSync(mosaicId: MosaicId | NamespaceId): MosaicsModel | null { - if (!mosaicId) return // @TODO: find route cause, should not happen + private getName(mosaicNames: MosaicNames[], accountMosaicDto: MosaicId): string { + return _.first( + mosaicNames.filter(n => n.mosaicId.equals(accountMosaicDto)) + .filter(n => n.names.length).map(n => n.names[0].name)) + } - const repository = new MosaicsRepository() - // 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 + private toMosaicDtos(balances: MosaicBalance[], + mosaicDtos: MosaicInfo[], + mosaicNames: MosaicNames[], + networkCurrencies: NetworkCurrencyModel[], + accountAddresses: Address[]): MosaicModel[] { + + return _.flatten(accountAddresses.map((address) => { + return mosaicDtos.map(mosaicDto => { + const name = this.getName(mosaicNames, mosaicDto.id) + const isCurrencyMosaic = !!networkCurrencies.find(n => n.mosaicIdHex == mosaicDto.id.toHex()) + const balance = balances.find( + balance => balance.mosaicId.equals(mosaicDto.id) && balance.address.equals(address)) + return new MosaicModel(address.plain(), mosaicDto.owner.address.plain(), name, isCurrencyMosaic, + balance && balance.amount.compact() || 0, mosaicDto) + }) + })) + } - const namespaceInfo = model.objects.namespaceInfo - if (namespaceInfo.hasAlias() && namespaceInfo.alias.mosaicId) { - return this.getMosaicSync(namespaceInfo.alias.mosaicId) - } - } + private resolveBalances(repositoryFactory: RepositoryFactory, + accountsInfo: AccountInfo[]): Observable { + const mosaicIdOrAliases = _.flatten(accountsInfo.map(a => a.mosaics.map(m => m.id))) + const mosaicIdOrAliasesUnique = _.uniqBy(mosaicIdOrAliases, m => m.toHex()) + return this.resolveMosaicIds(repositoryFactory, mosaicIdOrAliasesUnique).pipe( + map(resolveMosaicIds => { + return _.flatten(accountsInfo.map(a => { + return a.mosaics.map(m => { + return { + address: a.address, + amount: m.amount, + mosaicId: resolveMosaicIds.find(pair => pair.from.equals(m.id)).to, + } + }) + })) + }), + ) + } - if (!repository.find(mosaicId.toHex())) { - // - mosaic is unknown, fetch from REST + add to storage - this.fetchMosaicInfo(mosaicId as MosaicId) - return null - } - return repository.read(mosaicId.toHex()) + private resolveMosaicIds(repositoryFactory: RepositoryFactory, + ids: (NamespaceId | MosaicId)[]): Observable<{ from: (NamespaceId | MosaicId), to: MosaicId }[]> { + const namespaceRepository = repositoryFactory.createNamespaceRepository() + return fromIterable(ids).pipe(flatMap(id => { + if (id instanceof MosaicId) { + return of({from: id, to: id as MosaicId}) + } else { + const linkedMosaicIdObservable = namespaceRepository.getLinkedMosaicId(id as NamespaceId) + return linkedMosaicIdObservable.pipe(map((to) => { + return {from: id, to: to} + })) + } + })).pipe(toArray()) } - /** - * 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) - }) - } catch (error) { - this.$store.dispatch( - 'diagnostic/ADD_DEBUG', - `MosaicService/fetchMosaicsInfos error: ${JSON.stringify(error)}`, - ) - } - } /** - * Read mosaic from REST using store action. + * 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. * - * @internal - * @param {MosaicId} mosaicId - * @return {MosaicsModel} + * @param repositoryFactory tge repository factory used to load the block 1 transactions + * @return the list of {@link NetworkCurrencyModel} found in block 1. */ - 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) - } else { - // - create a new entry in the repository - repository.create(mosaic.values) - } + 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)) + } - 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 ], - ])) - } + private loadMosaicData(): MosaicModel[] { + return this.mosaicDataStorage.get() } - /** - * 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) + private saveMosaicData(mosaics: MosaicModel[]) { + this.mosaicDataStorage.set(mosaics) + } + + public reset() { + this.mosaicDataStorage.remove() + this.networkCurrencyStorage.remove() } /** - * Format a mosaic amount to relative format - * @param {number} amount - * @param {MosaicId} mosaic - * @return {number} + * 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 getRelativeAmountSync( - amount: number, - mosaic: MosaicId, - ): number { - const info = this.getMosaicSync(mosaic) - return amount / Math.pow(10, info.values.get('divisibility') || 0) + 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) } + // } /** - * Get list of balances mapped by address - * @param {AccountInfo[]} accountsInfo - * @param {MosaicId} mosaic - * @return {Record} Object with address as key and balance as value + * 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 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), + 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}` } - }).reduce((acc, {address, balance}) => ({...acc, [address]: balance}), {}) + } } - 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 - - return ({ - id: new MosaicId(model.getIdentifier()), - mosaicHex: mosaic.id.toHex(), - amount: mosaic.amount.compact() / Math.pow(10, divisibility), - }) - }) - } /** - * Returns a view of a mosaic expiration info - * @private - * @param {MosaicsInfo} mosaic - * @returns {ExpirationStatus} + * + * Utility method that returns the mosaic expiration status + * @param mosaicInfo the mosaic info + * @param currentHeight + * @param blockGenerationTargetTime */ - public getExpiration(mosaicInfo: MosaicInfo): ExpirationStatus { - const duration = mosaicInfo.duration.compact() - const startHeight = mosaicInfo.height.compact() + public static getExpiration(mosaicInfo: MosaicModel, currentHeight: number, + blockGenerationTargetTime: number): ExpirationStatus { + const duration = mosaicInfo.duration + const startHeight = mosaicInfo.height // 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 + const expiresIn = startHeight + duration - (currentHeight || 0) if (expiresIn <= 0) return 'expired' - // number of blocks remaining - return TimeHelpers.durationToRelativeTime(expiresIn) + return TimeHelpers.durationToRelativeTime(expiresIn, blockGenerationTargetTime) } - /** - * 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? - */ - 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 - - // get model - const model = repository.read(hexId) - - // get next visibility state - const nextVisibilityState = hide === undefined ? !model.values.get('isHidden') : hide - - // update visibility state - model.values.set('isHidden', nextVisibilityState) - - // persist change - repository.update(hexId, model.values) + public getMosaicConfigurations(): Record { + return this.mosaicConfigurationsStorage.get() || {} + } + + public getMosaicConfiguration(mosaicId: MosaicId): MosaicConfigurationModel { + return this.getMosaicConfigurations()[mosaicId.toHex()] || new MosaicConfigurationModel() } + + 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 a90fbb32c..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['network/networkType'] - 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..9a0d7a742 --- /dev/null +++ b/src/services/NetworkService.ts @@ -0,0 +1,158 @@ +/* + * 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, concatMap, 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 candidateUrl is down, + * the next available url will be used. + * + * @param candidateUrl the new url. + */ + public getNetworkModel(candidateUrl: string | undefined): + Observable<{ fallback: boolean, networkModel: NetworkModel, repositoryFactory: RepositoryFactory }> { + const storedNetworkModel = this.loadNetworkModel() + const possibleUrls = this.resolveCandidates(candidateUrl, storedNetworkModel) + + const repositoryFactoryObservable = fromIterable(possibleUrls) + .pipe(concatMap(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 { + fallback: !!candidateUrl && candidateUrl !== url, + 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 }> { + + console.log(`Testing ${url}`) + 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 | undefined, + 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..e11ec9f05 --- /dev/null +++ b/src/services/NodeService.ts @@ -0,0 +1,98 @@ +/* + * 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, 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' +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, repositoryFactoryUrl: string): Observable { + const storedNodes = this.loadNodes().concat(this.loadStaticNodes()) + const nodeRepository = repositoryFactory.createNodeRepository() + + return combineLatest([ + nodeRepository.getNodeInfo().pipe(map(dto => this.createNodeModel(repositoryFactoryUrl, dto.friendlyName))) + .pipe(ObservableHelpers.defaultLast(this.createNodeModel(repositoryFactoryUrl))), + this.getNodePeers(nodeRepository) + .pipe(ObservableHelpers.defaultLast( + storedNodes)), + + ]).pipe(map(restData => { + const currentNode = restData[0] + const nodePeers = restData[1] + const nodeInfos = [currentNode].concat(nodePeers, storedNodes) + return _.uniqBy(nodeInfos, 'url') + }), tap(p => this.saveNodes(p))) + } + + + 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) + }) + } + + private toNodeModel(n: NodeInfo): NodeModel | undefined { + if (!n.host || n.roles == RoleType.PeerNode) { + return undefined + } + const resolvedUrl = URLHelpers.getNodeUrl(n.host) + return this.createNodeModel(resolvedUrl, n.friendlyName) + } + + + private createNodeModel(url: string, + friendlyName: string | undefined = undefined, + isDefault: boolean | undefined = undefined): NodeModel { + return new NodeModel(url, friendlyName || '', isDefault + || !!networkConfig.nodes.find(n => n.url === url)) + } + + 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..513077fd6 100644 --- a/src/services/RESTService.ts +++ b/src/services/RESTService.ts @@ -13,20 +13,12 @@ * 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, 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' /** @@ -58,95 +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('transaction/ADD_COSIGNATURE', cosignature, {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 } - /** - * 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..16d6673a9 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 {...this.createDefaultSettingsModel(accountName), ...storedData[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..06df535e7 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: BlockInfo[] = this.$store.getters['transaction/blocks'] // - 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,16 +156,15 @@ 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}'`) } // - 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, @@ -250,8 +184,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 +203,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 +211,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 +233,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 +266,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 +281,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 +309,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 +367,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 +428,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 +460,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 44c09b8a4..e1bbb174e 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, EncryptedPrivateKey} from 'symbol-sdk' -import { - ExtendedKey, - MnemonicPassPhrase, - NodeEd25519, - Wallet, -} from 'symbol-hd-wallets' - +import {Account, Address, EncryptedPrivateKey, 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' +import {WalletModel, WalletType} from '@/core/database/entities/WalletModel' +import {AccountModel} from '@/core/database/entities/AccountModel' +import {SimpleObjectStorage} from '@/core/database/backends/SimpleObjectStorage' -export class WalletService extends AbstractService { - /** - * Service name - * @var {string} - */ - public name: string = 'wallet' +export class WalletService { - /** - * Vuex Store - * @var {Vuex.Store} - */ - public $store: Store - - /** - * Wallets repository - * @var {WalletsRepository} - */ - public wallets: WalletsRepository + private readonly storage = new SimpleObjectStorage>('wallets') /** * Default wallet derivation path @@ -56,74 +32,40 @@ 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): WalletModel { + const wallets = this.getWalletsById() + wallets[wallet.id] = wallet + this.storage.set(wallets) + return wallet } + + public updateName(wallet: WalletModel, name: string): WalletModel { + return this.saveWallet(Object.assign(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 +74,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 +101,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 +118,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 +137,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 +153,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 +183,35 @@ export class WalletService extends AbstractService { networkType, ) - return new WalletsModel(new Map([ - [ 'accountName', currentAccount.getIdentifier() ], - [ 'name', 'Seed Account 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 Account 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 +227,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,53 +266,50 @@ 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, + } } + /** * Returns a WalletsModel with an updated SimpleWallet * @param {string} walletIdentifier * @param {Password} oldPassword * @param {Password} newPassword */ - public updateWalletPassword( - wallet: WalletsModel, - oldPassword: Password, - newPassword: Password, - ): EncryptedPrivateKey { + 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) { throw new Error('Hardware wallet password cannot be changed') } - // 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, ) - - // return new encrypted private key - return newSimpleWallet.encryptedPrivateKey + // Update the wallet model + return { + ...wallet, encPrivate: newSimpleWallet.encryptedPrivateKey.encryptedKey, + encIv: newSimpleWallet.encryptedPrivateKey.iv, + } } } diff --git a/src/store/Account.ts b/src/store/Account.ts index 55f641dce..eb433d667 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,47 @@ * 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' +import {WalletModel} from '@/core/database/entities/WalletModel' +import {AccountService} from '@/services/AccountService' /// 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 +62,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) @@ -64,42 +76,44 @@ export default { }, async LOG_OUT({dispatch, rootGetters}): Promise { const currentWallet = rootGetters['wallet/currentWallet'] - await dispatch('wallet/uninitialize', {address: currentWallet.values.get('address')}, {root: true}) + await dispatch('wallet/uninitialize', {address: currentWallet.address}, {root: true}) await dispatch('wallet/SET_KNOWN_WALLETS', [], {root: true}) await dispatch('wallet/RESET_CURRENT_WALLET', undefined, {root: true}) await 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'] - 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.values.get('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) + dispatch('diagnostic/ADD_DEBUG', + 'Adding wallet to account: ' + currentAccount.accountName + ' with: ' + walletModel.address, + {root: true}) + 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 7e0eca251..76f0a52d6 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,84 @@ * 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() +const ANON_ACCOUNT_NAME = '' + +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 +} + +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.getAccountSettings(ANON_ACCOUNT_NAME), +} + 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, }, 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), }, actions: { - async initialize({ commit, getters }) { + async initialize({commit, getters}) { const callback = async () => { // update store commit('setInitialized', true) @@ -88,7 +100,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 +110,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,26 +120,29 @@ export default { commit('setLoadingOverlayMessage', loadingOverlay.message) commit('setLoadingDisableCloseButton', loadingOverlay.disableCloseButton || false) }, - SET_EXPLORER_URL({commit}, url: string) { - commit('setExplorerUrl', url) - }, - SET_LANGUAGE({commit}, language: string) { - commit('setCurrentLanguage', language) + + SET_SETTINGS({commit, rootGetters}, settingsModel: SettingsModel) { + if (settingsModel.language) { + i18n.locale = settingsModel.language + window.localStorage.setItem('locale', settingsModel.language) + } + const currentAccount = rootGetters['account/currentAccount'] + const accountName = currentAccount && currentAccount.accountName || ANON_ACCOUNT_NAME + commit('settings', settingService.changeAccountSettings(accountName, settingsModel)) }, - SET_DEFAULT_FEE({commit}, maxFee: number) { - commit('setDefaultFee', maxFee) + + SET_EXPLORER_URL({dispatch}, explorerUrl: string) { + dispatch('SET_SETTINGS', {explorerUrl}) }, - SET_DEFAULT_WALLET({commit}, defaultWallet: string) { - commit('setDefaultWallet', defaultWallet) + + SET_LANGUAGE({dispatch}, language: string) { + dispatch('SET_SETTINGS', {language}) }, - 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_FEE({dispatch}, defaultFee: number) { + dispatch('SET_SETTINGS', {defaultFee}) }, - SET_FETCHING_TRANSACTIONS({commit}, bool: boolean) { - commit('setFetchingTransactions', bool) + SET_DEFAULT_WALLET({dispatch}, defaultWallet: string) { + dispatch('SET_SETTINGS', {defaultWallet}) }, /// end-region scoped actions }, 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..4d887e8aa 100644 --- a/src/store/Mosaic.ts +++ b/src/store/Mosaic.ts @@ -1,87 +1,55 @@ /** * 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[] + holdMosaics: 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: [], + holdMosaics: [], + ownedMosaics: [], + networkCurrency: null, networkMosaicName: '', networkMosaicTicker: '', - nemesisTransactions: [], - mosaicsInfoByHex: {}, - mosaicsNamesByHex: {}, - hiddenMosaics: [], + mosaicConfigurations: {}, } export default { @@ -89,208 +57,107 @@ 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, + holdMosaics: (state: MosaicState) => state.holdMosaics, + 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] + mosaics: (state: MosaicState, {mosaics, currentSignerAddress, networkCurrency}: + { mosaics: MosaicModel[], currentSignerAddress: Address, networkCurrency: NetworkCurrencyModel }) => { - // find the index of the mosaic to hide - const index = hiddenMosaics.indexOf(mosaicId.toHex()) + const ownedMosaics = mosaics.filter( + m => m.ownerRawPlain === currentSignerAddress.plain() && m.addressRawPlain === currentSignerAddress.plain()) - // the mosaic is already in the list, return - if (index > -1) return - - // update the state - Vue.set(state, 'hiddenMosaics', [ ...hiddenMosaics, mosaicId.toHex() ]) - }, - showMosaic: (state: MosaicState, mosaicId) => { - const hiddenMosaics = [...state.hiddenMosaics] + const holdMosaics = mosaics.filter(m => m.addressRawPlain === currentSignerAddress.plain()).sort((m1, m2)=>{ + const owner1 = m1.ownerRawPlain === currentSignerAddress.plain() + const owner2 = m2.ownerRawPlain === currentSignerAddress.plain() + return Number(owner1) - Number(owner2) + }) - // find the index of the mosaic to show - const index = hiddenMosaics.indexOf(mosaicId.toHex()) + const noMosaic = networkCurrency && !holdMosaics.find( + m => m.isCurrencyMosaic) - // the mosaic is not in the list, return - if (index === -1) return + const balanceMosaics = (noMosaic ? [ ...holdMosaics, { + mosaicIdHex: networkCurrency.mosaicIdHex, + divisibility: networkCurrency.divisibility, + name: networkCurrency.namespaceIdFullname, + isCurrencyMosaic: true, + balance: 0, + } as MosaicModel ] : [...holdMosaics]).filter(m => m.isCurrencyMosaic || m.balance > 0) - // remove the mosaic from the list - hiddenMosaics.splice(index, 1) - // update the state - Vue.set(state, 'hiddenMosaics', hiddenMosaics) + Vue.set(state, 'mosaics', mosaics) + Vue.set(state, 'balanceMosaics', balanceMosaics) + Vue.set(state, 'ownedMosaics', ownedMosaics) + Vue.set(state, 'holdMosaics', holdMosaics.filter(m => m.ownerRawPlain === currentSignerAddress.plain() || m.balance > 0)) }, + mosaicConfigurations: (state: MosaicState, + mosaicConfigurations: Record) => Vue.set( + state, 'mosaicConfigurations', mosaicConfigurations), + }, 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..ea512e0bc 100644 --- a/src/store/Namespace.ts +++ b/src/store/Namespace.ts @@ -1,105 +1,123 @@ /** * 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, NamespaceId, RepositoryFactory} 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' + +import * as _ from 'lodash' 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 }) => { + const uniqueNamespaces = _.uniqBy(namespaces, n => n.namespaceIdHex) + Vue.set(state, 'namespaces', uniqueNamespaces) + Vue.set(state, 'ownedNamespaces', + uniqueNamespaces.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 - }, - async REST_FETCH_NAMES({commit, rootGetters}, namespaceIds: NamespaceId[]): Promise<{hex: string, name: string}[]> { + async RESOLVE_NAME({commit, getters, rootGetters}, namespaceId: NamespaceId): Promise { + if (!namespaceId) { + return '' + } + + if (namespaceId.fullName) { + return namespaceId.fullName + } + const namespaces: NamespaceModel[] = getters['namespaces'] + const knownNamespace = namespaces.find(n => n.namespaceIdHex === namespaceId.toHex()) + if (knownNamespace) { + return knownNamespace.name + } const repositoryFactory = rootGetters['network/repositoryFactory'] as RepositoryFactory - const namespaceHttp = repositoryFactory.createNamespaceRepository() - const namespaceNames = await namespaceHttp.getNamespacesName(namespaceIds).toPromise() + const currentSignerAddress = rootGetters['wallet/currentSignerAddress'] as Address + const namespaceRepository = repositoryFactory.createNamespaceRepository() + + const namespaceInfo = await namespaceRepository.getNamespace(namespaceId).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, - } - }) + const namespaceName = await namespaceRepository.getNamespacesName([namespaceId]).toPromise() - // update store - mappedNames.forEach(mappedEntry => commit('addNamespaceName', mappedEntry)) - return mappedNames + // Note, fullName may not be full. How can we load it without needing to load each parent recursively?. + const model = new NamespaceModel(namespaceInfo, + NamespaceService.getFullNameFromNamespaceNames(namespaceName[0], namespaceName)) + namespaces.push(model) + commit('namespaces', {namespaces, currentSignerAddress}) + return model.name }, - ADD_NAMESPACE_INFOS({commit}, namespacesInfo: NamespaceInfo[]): void { - namespacesInfo.forEach(namespace => commit('addNamespaceInfo', namespace)) + + SIGNER_CHANGED({dispatch}) { + dispatch('LOAD_NAMESPACES') }, - /// end-region scoped actions + }, } diff --git a/src/store/Network.ts b/src/store/Network.ts index d8b8b08d6..a0c222484 100644 --- a/src/store/Network.ts +++ b/src/store/Network.ts @@ -14,247 +14,199 @@ * limitations under the License. */ import Vue from 'vue' -import {BlockInfo, IListener, 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 {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() -/// 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 * @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 + subscriptions: Subscription[] +} + +const defaultPeer = URLHelpers.formatUrl(networkConfig.defaultNodeUrl) + +const networkState: NetworkState = { + initialized: false, + currentPeer: defaultPeer, + currentPeerInfo: new NodeModel(defaultPeer.url, defaultPeer.url, true), + networkType: networkConfig.defaultNetworkType, + generationHash: undefined, + networkModel: undefined, + networkConfiguration: networkConfig.networkConfigurationDefaults, + repositoryFactory: NetworkService.createRepositoryFactory(networkConfig.defaultNodeUrl), + listener: undefined, + isConnected: false, + knowNodes: [], + currentHeight: 0, + 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, }, 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, '', false) ] + 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) => { - 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'))) - + await dispatch('CONNECT') // 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}, newCandidate: string | undefined) { + const networkService = new NetworkService() + const {networkModel, repositoryFactory, fallback} = await networkService.getNetworkModel(newCandidate) + .toPromise() + if (fallback) { + throw new Error('Connection Error.') + } + const getNodesPromise = new NodeService().getNodes(repositoryFactory, networkModel.url).toPromise() + const getBlockchainHeightPromise = repositoryFactory.createChainRepository() + .getBlockchainHeight().toPromise() + const nodes = await getNodesPromise + const currentHeight = (await getBlockchainHeightPromise).compact() + const listener = repositoryFactory.createListener() + await listener.open() + + const currentPeer = URLHelpers.getNodeUrl(networkModel.url) + 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 === networkModel.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,181 +216,88 @@ 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) { + console.log(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}) } }, + 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}) { - // - populate known peers - knownPeers.map(peer => commit('addPeer', peer.values.get('rest_url'))) + const nodeService = new NodeService() + nodeService.reset() + + const networkService = new NetworkService() + networkService.reset() + + dispatch('SET_CURRENT_PEER', networkService.getDefaultUrl()) - dispatch('SET_CURRENT_PEER', knownPeers.shift().values.get('rest_url')) - }, - RESET_SUBSCRIPTIONS({commit}) { - commit('setSubscriptions', []) - }, - 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('transaction/ADD_BLOCK', block, {root: true}) + 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 - 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 - } - }, - 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..2c2b83968 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,18 +44,20 @@ 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() - const diagnostic: StorageInfo = await nodeHttp.getStorageInfo().toPromise() + const storageInfoPromise = nodeHttp.getStorageInfo().toPromise() + const nodePeersPromise = nodeHttp.getNodePeers().toPromise() + + const diagnostic: StorageInfo = await storageInfoPromise + const nodes = await nodePeersPromise 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) // update store @@ -66,14 +67,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/Transaction.ts b/src/store/Transaction.ts new file mode 100644 index 000000000..6660fddc2 --- /dev/null +++ b/src/store/Transaction.ts @@ -0,0 +1,321 @@ +/* + * 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, CosignatureSignedTransaction, QueryParams, RepositoryFactory, Transaction, TransactionType, UInt64} from 'symbol-sdk' +// internal dependencies +import {AwaitLock} from './AwaitLock' +import {combineLatest, Observable} from 'rxjs' +import * as _ from 'lodash' +import {map} from 'rxjs/operators' + +const Lock = AwaitLock.create() + +export enum TransactionGroup { + confirmed = 'confirmed', + unconfirmed = 'unconfirmed', + partial = 'partial', + all = 'all' +} + + +/** + * 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 + isFetchingTransactions: boolean + confirmedTransactions: Transaction[] + unconfirmedTransactions: Transaction[] + partialTransactions: Transaction[] + blocks: BlockInfo[] +} + +const transactionState: TransactionState = { + initialized: false, + isFetchingTransactions: false, + confirmedTransactions: [], + unconfirmedTransactions: [], + partialTransactions: [], + blocks: [], +} +export default { + namespaced: true, + state: transactionState, + getters: { + getInitialized: (state: TransactionState) => state.initialized, + isFetchingTransactions: (state: TransactionState) => state.isFetchingTransactions, + 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 }, + isFetchingTransactions: (state: TransactionState, isFetchingTransactions: boolean) => + { state.isFetchingTransactions = isFetchingTransactions }, + 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 } = {group: TransactionGroup.all}) { + 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): + Observable => { + const attributeName = transactionGroupToStateVariable(group) + commit(attributeName, []) + return transactionCall.pipe(map((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) + return transactions + })) + } + + const subscriptions: Observable[] = [] + commit('isFetchingTransactions', true) + + const queryParams = new QueryParams({pageSize: 100}) + if (group === TransactionGroup.all || group === TransactionGroup.confirmed) { + subscriptions.push(subscribeTransactions(TransactionGroup.confirmed, + accountRepository.getAccountTransactions(currentSignerAddress, queryParams))) + } + if (group === TransactionGroup.all || group === TransactionGroup.unconfirmed) { + subscriptions.push(subscribeTransactions(TransactionGroup.unconfirmed, + accountRepository.getAccountUnconfirmedTransactions(currentSignerAddress, queryParams))) + } + + if (group === TransactionGroup.all || group === TransactionGroup.partial) { + subscriptions.push(subscribeTransactions(TransactionGroup.partial, + accountRepository.getAccountPartialTransactions(currentSignerAddress, queryParams))) + } + + combineLatest(subscriptions) + .subscribe({ + complete: () => commit('isFetchingTransactions', false), + }) + }, + + + SIGNER_CHANGED({dispatch}) { + dispatch('LOAD_TRANSACTIONS') + }, + + RESET_TRANSACTIONS({commit}) { + Object.keys(TransactionGroup).forEach((group: TransactionGroup) => { + if (group !== TransactionGroup.all) {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}, transaction: CosignatureSignedTransaction) { + if (!transaction || !transaction.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 === transaction.parentHash) + + // partial tx unknown, @TODO: handle this case (fetch partials) + if (index === -1) return + + transactions[index] = transactions[index].addCosignatures(transaction) + commit('partialTransactions', transactions) + }, + }, + +} diff --git a/src/store/Wallet.ts b/src/store/Wallet.ts index 380ec9420..6129daa2c 100644 --- a/src/store/Wallet.ts +++ b/src/store/Wallet.ts @@ -14,126 +14,20 @@ * 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, CosignatureSignedTransaction, IListener, MultisigAccountInfo, NetworkType, RepositoryFactory, SignedTransaction, Transaction} 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 {RESTDispatcher} from '@/core/utils/RESTDispatcher' -import {NamespaceService} from '@/services/NamespaceService' +import {WalletModel} from '@/core/database/entities/WalletModel' 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 -} - -/** - * 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() @@ -148,38 +42,28 @@ 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 - transactionHashes: string[] - confirmedTransactions: Transaction[] - unconfirmedTransactions: Transaction[] - partialTransactions: Transaction[] + knownWallets: WalletModel[] + knownAddresses: Address[] + accountsInfo: AccountInfo[] + multisigAccountsInfo: MultisigAccountInfo[] + stageOptions: { isAggregate: boolean, isMultisig: boolean } stagedTransactions: Transaction[] signedTransactions: SignedTransaction[] - transactionCache: Record // Subscriptions to webSocket channels subscriptions: Record } @@ -189,29 +73,22 @@ 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: {}, - transactionHashes: [], - confirmedTransactions: [], - unconfirmedTransactions: [], - partialTransactions: [], + knownAddresses: [], + accountsInfo: [], + multisigAccountsInfo: [], stageOptions: { isAggregate: false, isMultisig: false, }, stagedTransactions: [], signedTransactions: [], - transactionCache: {}, // Subscriptions to websocket channels. subscriptions: {}, } @@ -224,133 +101,53 @@ 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, - currentWallets:(state: WalletState)=>{ - const knownWallets = state.knownWallets - if(!knownWallets || !knownWallets.length) return [] - const currentWallets = new WalletService().getWallets( - (e) => knownWallets.includes(e.getIdentifier()), - ) - return currentWallets.map( - ({identifier, values}) => ({ - identifier, - address: values.get('address'), - name: values.get('name'), - type: values.get('type'), - isMultisig: values.get('isMultisig'), - path: values.get('path'), - }), - ) - }, - 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) => { - 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, - 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) => { 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 // skip when subscriptions is an empty array if (!subscriptions.subscriptions.length) return @@ -361,19 +158,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 @@ -384,7 +173,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 @@ -392,14 +181,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` @@ -416,284 +205,167 @@ 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}) { 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) - - // - 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) { - dispatch('GET_ALL_TRANSACTIONS',{ - group: 'all', - pageSize: 100, - 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 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) }, - 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 - } - commit('isCosignatoryMode', isCosignatory) - - // setting current signer should not fetch ALL data - const detailOpts = { - skipTransactions: true, - skipMultisig: true, - skipOwnedAssets: false, - } - - if (!options || !options.skipDetails) { - await dispatch('REST_FETCH_WALLET_DETAILS', {address: address.plain(), options: detailOpts}) + UPDATE_CURRENT_WALLET_NAME({commit, getters, rootGetters}, name: string) { + const currentWallet: WalletModel = getters.currentWallet + if (!currentWallet) { + return } - }, - SET_KNOWN_WALLETS({commit}, wallets: string[]) { - commit('setKnownWallets', wallets) - }, - RESET_BALANCES({dispatch}, which) { - if (!which) which = 'currentWalletMosaics' - dispatch('SET_BALANCES', {which, mosaics: []}) - }, - 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]) + const currentAccount: AccountModel = rootGetters['account/currentAccount'] + if (!currentAccount) { return } - - commit(which, mosaics) - }, - RESET_SUBSCRIPTIONS({commit}) { - commit('setSubscriptions', []) - }, - RESET_TRANSACTIONS({commit}) { - commit('confirmedTransactions', []) - commit('unconfirmedTransactions', []) - commit('partialTransactions', []) - }, - RESET_MULTISIG({commit}) { - commit('setKnownMultisigInfo', {}) + const walletService = new WalletService() + walletService.updateName(currentWallet, name) + const knownWallets = walletService.getKnownWallets(currentAccount.wallets) + commit('knownWallets', knownWallets) }, - ADD_COSIGNATURE({commit, getters}, cosignatureMessage) { - if (!cosignatureMessage || !cosignatureMessage.parentHash) { - throw Error('Missing mandatory field \'parentHash\' for action wallet/ADD_COSIGNATURE.') - } - const transactions = getters['partialTransactions'] + async SET_CURRENT_SIGNER({commit, dispatch, getters, rootGetters}, + {publicKey}: { publicKey: string }) { + if (!publicKey){ + throw new Error('Public Key must be provided when calling wallet/SET_CURRENT_SIGNER!') + } + const networkType: NetworkType = rootGetters['network/networkType'] + const currentAccount: AccountModel = rootGetters['account/currentAccount'] + const currentWallet: WalletModel = getters.currentWallet + const previousSignerAddress: Address = getters.currentSignerAddress - // return if no transactions - if (!transactions.length) return + const currentSignerAddress: Address = Address.createFromPublicKey(publicKey, networkType) - const index = transactions.findIndex(t => t.transactionInfo.hash === cosignatureMessage.parentHash) + if (previousSignerAddress && previousSignerAddress.equals(currentSignerAddress)) return - // partial tx unknown, @TODO: handle this case (fetch partials) - if (index === -1) return + dispatch('diagnostic/ADD_DEBUG', + 'Store action wallet/SET_CURRENT_SIGNER dispatched with ' + currentSignerAddress.plain(), + {root: true}) - transactions[index] = transactions[index].addCosignatures(cosignatureMessage) - commit('partialTransactions', transactions) - }, - async GET_ALL_TRANSACTIONS({dispatch},{address,pageSize,group}){ - if (!pageSize) { - pageSize = 100 - } + dispatch('transaction/RESET_TRANSACTIONS', {}, {root: true}) - dispatch('app/SET_FETCHING_TRANSACTIONS', true, { root: true }) - try { - if (group == 'all') { - await Promise.all([ - dispatch('REST_FETCH_TRANSACTIONS', { - group: 'confirmed', - pageSize: pageSize, - address: address, - }), - dispatch('REST_FETCH_TRANSACTIONS', { - group: 'unconfirmed', - pageSize: pageSize, - address: address, - }), - dispatch('REST_FETCH_TRANSACTIONS', { - group: 'partial', - pageSize: pageSize, - address: address, - }), - ]) - } else { - await dispatch('REST_FETCH_TRANSACTIONS', { - group: group, - pageSize: pageSize, - address: address, - }) - } - } finally { - dispatch('app/SET_FETCHING_TRANSACTIONS', false, { root: true }) - } - - }, - ADD_TRANSACTION({commit, getters}, transactionMessage) { - if (!transactionMessage || !transactionMessage.group) { - throw Error('Missing mandatory field \'group\' for action wallet/ADD_TRANSACTION.') - } + 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') - // 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) + commit('currentSignerAddress', currentSignerAddress) + commit('currentWalletAddress', currentWalletAddress) + commit('isCosignatoryMode', !currentSignerAddress.equals(currentWalletAddress)) - // register transaction - const transactions = getters[transactionGroup] - const findTx = transactions.find(t => t.transactionInfo.hash === transaction.transactionInfo.hash) - if (findTx === undefined) { - transactions.push(transaction) - } + commit('knownWallets', knownWallets) + commit('knownAddresses', knownAddresses) - if (findIterator === undefined) { - hashes.push(transaction.transactionInfo.hash) - } + dispatch('namespace/SIGNER_CHANGED', {}, {root: true}) + dispatch('mosaic/SIGNER_CHANGED', {}, {root: true}) + dispatch('transaction/SIGNER_CHANGED', {}, {root: true}) + await dispatch('LOAD_ACCOUNT_INFO') - // update state - // commit('addTransactionToCache', {hash: transaction.transactionInfo.hash, transaction}) - commit(transactionGroup, transactions) - return commit('transactionHashes', hashes) }, - REMOVE_TRANSACTION({commit, getters}, transactionMessage) { - if (!transactionMessage || !transactionMessage.group) { - throw Error('Missing mandatory field \'group\' for action wallet/removeTransaction.') - } + 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 - // format transactionGroup to store variable name - const transactionGroup = transactionGroupToStateVariable(transactionMessage.group) + // remote calls: + const getAccontInfoPromise = repositoryFactory.createAccountRepository() + .getAccountsInfo(knownAddresses).toPromise() - // read from store - const transactions = getters[transactionGroup] + const getMultisignAccountInforPromise = repositoryFactory.createMultisigRepository() + .getMultisigAccountGraphInfo(currentWalletAddress).pipe(map(g => { + return MultisigService.getMultisigInfoFromMultisigGraphInfo(g) + }), catchError(() => { + return of([]) + })).toPromise() - // prepare search - const transactionHash = transactionMessage.transaction + const accountsInfo = await getAccontInfoPromise + const multisigAccountsInfo: MultisigAccountInfo[] = await getMultisignAccountInforPromise - // find transaction in storage - const findIterator = transactions.findIndex(tx => tx.transactionInfo.hash === transactionHash) - if (findIterator === undefined) { - return // not found, do nothing - } + 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 empty array - if (transactions.length === 1) { - return commit(transactionGroup, []) - } + commit('currentSigner', signers.find(s => s.address.equals(currentSignerAddress))) + commit('signers', signers) + commit('accountsInfo', accountsInfo) + commit('multisigAccountsInfo', multisigAccountsInfo) + commit('currentWalletMultisigInfo', currentWalletMultisigInfo) + commit('currentSignerMultisigInfo', currentSignerMultisigInfo) - // skip `idx` - const remaining = transactions.splice(0, findIterator).concat( - transactions.splice(findIterator + 1, transactions.length - findIterator - 1), - ) + }, + + + SET_KNOWN_WALLETS({commit}, wallets: string[]) { + commit('knownWallets', new WalletService().getKnownWallets(wallets)) + }, - commit(transactionGroup, Array.from(remaining)) + RESET_SUBSCRIPTIONS({commit}) { + commit('setSubscriptions', []) }, + ADD_STAGED_TRANSACTION({commit}, stagedTransaction: Transaction) { commit('addStagedTransaction', stagedTransaction) }, @@ -704,12 +376,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 @@ -725,15 +397,15 @@ 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 + const currentWallet: WalletModel = getters.currentWallet - if (!address) { - address = currentWallet.values.get('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] @@ -748,315 +420,22 @@ export default { // update state dispatch('RESET_SUBSCRIPTIONS', address) }, - /** - * REST API - */ - async REST_FETCH_TRANSACTIONS({dispatch, rootGetters}, {group, address, id}) { - - - 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 - } - }, - 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() - - // 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 @@ -1089,8 +468,7 @@ export default { }, ) }) - } - catch(e) { + } catch (e) { return new BroadcastResult(signedPartial, false, e.toString()) } }, @@ -1098,10 +476,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 @@ -1112,22 +491,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 @@ -1137,55 +514,12 @@ 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()) } }, - 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) - - // always refresh wallet balances - dispatcher.add('REST_FETCH_INFO', plainAddress) - - // extract transaction types from the transaction - const transactionTypes: TransactionType[] = transaction instanceof AggregateTransaction - ? transaction.innerTransactions - .map(({type}) => type) - .filter((el, i, a) => i === a.indexOf(el)) - : [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))) { - dispatcher.add('REST_FETCH_OWNED_NAMESPACES', plainAddress) - } - if ([ - TransactionType.MOSAIC_DEFINITION, - TransactionType.MOSAIC_SUPPLY_CHANGE, - ].some(a => transactionTypes.some(b => b === a))) { - dispatcher.add('REST_FETCH_OWNED_MOSAICS', plainAddress) - } + }, - 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 ee0c446ca..83999d25d 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. @@ -25,19 +25,20 @@ 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' - // 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({ @@ -53,6 +54,7 @@ const AppStore = new Vuex.Store({ temporary: TemporaryStore, mosaic: MosaicStore, namespace: NamespaceStore, + transaction: TransactionStore, statistics: StatisticsStore, community: CommunityStore, }, @@ -60,26 +62,28 @@ 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') + await dispatch('transaction/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'), 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 b17352563..b82c8bfc2 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. @@ -18,14 +18,7 @@ export const onPeerConnection = store => { if (mutation.type === 'network/currentPeerInfo') { // - Done connection to new node const nodeUrl = store.getters['network/currentPeer'].url - const currentWallet = store.getters['wallet/currentWallet'] - store.dispatch('statistics/initialize', nodeUrl) - - if (!!currentWallet) { - console.log('onPeerConnection dispatching wallet actions..') - store.dispatch('wallet/REST_FETCH_INFO', currentWallet.objects.address.plain()) - } } }) } diff --git a/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts b/src/views/forms/FormAccountCreation/FormAccountCreationTs.ts index 13ab2df89..e211b895d 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. @@ -15,15 +15,12 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NetworkType, Password} from 'symbol-sdk' - +import {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 @@ -32,9 +29,9 @@ import ErrorTooltip from '@/components/ErrorTooltip/ErrorTooltip.vue' import FormWrapper from '@/components/FormWrapper/FormWrapper.vue' // @ts-ignore import FormRow from '@/components/FormRow/FormRow.vue' +import {NetworkTypeHelper} from '@/core/utils/NetworkTypeHelper' + -/// region custom types -type NetworkNodeEntry = {value: NetworkType, label: string} /// end-region custom types @Component({ @@ -45,10 +42,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 +55,7 @@ export class FormAccountCreationTs extends Vue { * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Currently active network type @@ -67,9 +66,9 @@ export class FormAccountCreationTs extends Vue { /** * Accounts repository - * @var {AccountsRepository} + * @var {AccountService} */ - public accountsRepository = new AccountsRepository() + public accountService = new AccountService() /** * Validation rules @@ -93,19 +92,14 @@ export class FormAccountCreationTs extends Vue { * Network types * @var {NetworkNodeEntry[]} */ - public networkTypeList: NetworkNodeEntry[] = [ - {value: NetworkType.MIJIN_TEST, label: 'MIJIN_TEST'}, - {value: NetworkType.MAIN_NET, label: 'MAIN_NET'}, - {value: NetworkType.TEST_NET, label: 'TEST_NET'}, - {value: NetworkType.MIJIN, label: 'MIJIN'}, - ] + public networkTypeList= NetworkTypeHelper.networkTypeList /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } @@ -114,6 +108,7 @@ export class FormAccountCreationTs extends Vue { get nextPage() { return this.$route.meta.nextPage } + /// end-region computed properties getter/setter /** @@ -135,29 +130,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 28503dc5e..b9bd1e86f 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,16 +16,9 @@ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' import {Password} from 'symbol-sdk' - // internal dependencies -import {AccountService} from '@/services/AccountService' -import {AccountsModel} from '@/core/database/entities/AccountsModel' -import {AccountsRepository} from '@/repositories/AccountsRepository' -import {NotificationType} from '@/core/utils/NotificationType' import {ValidationRuleset} from '@/core/validation/ValidationRuleset' -import {WalletService} from '@/services/WalletService' -import {WalletsRepository} from '@/repositories/WalletsRepository' - +import {AccountService} from '@/services/AccountService' // child components import {ValidationObserver, ValidationProvider} from 'vee-validate' // @ts-ignore @@ -36,7 +29,11 @@ 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 {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: { @@ -47,9 +44,12 @@ import {AESEncryptionService} from '@/services/AESEncryptionService' FormRow, ModalFormAccountUnlock, }, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - })}, + computed: { + ...mapGetters({ + currentAccount: 'account/currentAccount', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class FormAccountPasswordUpdateTs extends Vue { /** @@ -57,8 +57,9 @@ export class FormAccountPasswordUpdateTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel + private networkConfiguration: NetworkConfigurationModel /** * Validation rules * @var {ValidationRuleset} @@ -82,11 +83,11 @@ export class FormAccountPasswordUpdateTs extends Vue { } /** - * Type the ValidationObserver refs + * Type the ValidationObserver refs * @type {{ - * observer: InstanceType - * }} - */ + * observer: InstanceType + * }} + */ public $refs!: { observer: InstanceType } @@ -99,6 +100,7 @@ export class FormAccountPasswordUpdateTs extends Vue { public set hasAccountUnlockModal(f: boolean) { this.isUnlockingAccount = f } + /// end-region computed properties getter/setter /** @@ -113,54 +115,38 @@ export class FormAccountPasswordUpdateTs extends Vue { this.$refs.observer.reset() }) } + /** * When account is unlocked, the sub wallet can be created */ public async onAccountUnlocked(account: Account, oldPassword: Password) { try { - const service = new AccountService(this.$store) - const repository = new AccountsRepository() + const accountService = new AccountService() const newPassword = new Password(this.formItems.password) - const accountModel = this.currentAccount - - // - 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 oldSeed = this.currentAccount.seed + const plainSeed = AESEncryptionService.decrypt(oldSeed, oldPassword) const newSeed = AESEncryptionService.encrypt(plainSeed, newPassword) - accountModel.values.set('seed', newSeed) - // - update in storage - repository.update(this.currentAccount.getIdentifier(), accountModel.values) + // // - create new password hash + const passwordHash = AccountService.getPasswordHash(newPassword) + accountService.updatePassword(this.currentAccount, passwordHash, this.formItems.passwordHint, + newSeed) - // - update wallets passwords - const walletsRepository = new WalletsRepository() const walletService = new WalletService() + const walletIdentifiers = this.currentAccount.wallets - const walletIdentifiers = accountModel.values.get('wallets') - const walletModels = walletIdentifiers.map(id => walletsRepository.read(id)).filter( - (v, i, s) => s.indexOf(v) === i, - ) - - for (const model of walletModels) { - const encryptedKey = walletService.updateWalletPassword(model, oldPassword, newPassword) - - model.values.set('encPrivate', encryptedKey.encryptedKey) - model.values.set('encIv', encryptedKey.iv) - - walletsRepository.update(model.getIdentifier(), model.values) + 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/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/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/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..791883628 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,8 @@ 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' +import {mapGetters} from 'vuex' @Component({ components: { @@ -63,6 +60,11 @@ import MultisigCosignatoriesDisplay from '@/components/MultisigCosignatoriesDisp ApprovalAndRemovalInput, MultisigCosignatoriesDisplay, }, + computed: { + ...mapGetters({ + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class FormMultisigAccountModificationTransactionTs extends FormTransactionBase { /// region component properties @@ -84,9 +86,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 +97,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 + 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 +136,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 +161,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 +179,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 +218,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 +264,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 +298,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 +313,7 @@ export class FormMultisigAccountModificationTransactionTs extends FormTransactio const newCosignatoryNumber = this.currentMultisigInfo ? this.currentMultisigInfo.cosignatories.length + numberOfAddedCosigners : numberOfAddedCosigners - + return { minApproval: newMinApproval, minRemoval: newMinRemoval, @@ -347,10 +329,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 +351,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 b6091a3a6..92e1bb70d 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,10 +39,11 @@ 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 { NamespaceTableService } from '@/services/AssetTableService/NamespaceTableService' +import {NamespaceModel} from '@/core/database/entities/NamespaceModel' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' +import {NamespaceService} from '@/services/NamespaceService' + @Component({ components: { ValidationObserver, @@ -58,23 +57,27 @@ import { NamespaceTableService } from '@/services/AssetTableService/NamespaceTab ModalTransactionConfirmation, MaxFeeAndSubmit, }, - computed: {...mapGetters({ - ownedNamespaces: 'wallet/currentWalletOwnedNamespaces', - namespacesNames: 'namespace/namespacesNames', - })}, + computed: { + ...mapGetters({ + ownedNamespaces: 'namespace/ownedNamespaces', + currentHeight: 'network/currentHeight', + 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 @@ -88,6 +91,11 @@ export class FormNamespaceRegistrationTransactionTs extends FormTransactionBase */ public typeSubNamespace = NamespaceRegistrationType.SubNamespace + /** + * Current network block height + */ + public currentHeight: number + /** * Form items * @var {Record} @@ -101,21 +109,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) } /** @@ -124,7 +125,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 : '' @@ -154,27 +155,28 @@ 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) + console.error('Error happened in FormNamespaceRegistrationTransaction.transactions(): ', + error) } } - + /** * Setter for TRANSFER transactions that will be staged * @see {FormTransactionBase} @@ -184,29 +186,28 @@ 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() } public relativeTimetoParent = '' - /** - * 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, + const selectedNamespace = this.ownedNamespaces.find((item) => + item.name === 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/FormSubWalletCreation/FormSubWalletCreationTs.ts b/src/views/forms/FormSubWalletCreation/FormSubWalletCreationTs.ts index 6d6fd7acf..212e8c9fd 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,15 @@ */ 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 {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 +34,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,73 +49,47 @@ const {MAX_SEED_WALLETS_NUMBER} = appConfig.constants FormRow, ModalFormAccountUnlock, }, - computed: {...mapGetters({ - networkType: 'network/networkType', - currentAccount: 'account/currentAccount', - knownWallets: 'wallet/knownWallets', - currentWallets: 'wallet/currentWallets', - })}, + 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[] - - /** - * current wallets identifiers - * @var {string[]} */ - public currentWallets: string[] + public knownWallets: WalletModel[] /** * 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 - /** * Validation rules - * @var {ValidationRuleset} */ public validationRules = ValidationRuleset /** * Whether account is currently being unlocked - * @var {boolean} */ public isUnlockingAccount: boolean = false @@ -140,20 +110,18 @@ 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() } /// region computed properties getter/setter @@ -169,21 +137,10 @@ 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) + return this.knownWallets.map(w => w.path).filter(p => p) } + /// end-region computed properties getter/setter /** @@ -213,15 +170,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, @@ -235,9 +192,10 @@ export class FormSubWalletCreationTs extends Vue { if (!subWallet) return // Verify that the import is repeated - const hasAddressInfo = this.currentWallets.find(w => w['address'] === subWallet.values.get('address')) - if (hasAddressInfo !== undefined){ - this.$store.dispatch('notification/ADD_ERROR', `This private key already exists. The account name is ${hasAddressInfo['name']}`) + const hasAddressInfo = this.knownWallets.find(w => w.address === subWallet.address) + if (hasAddressInfo !== undefined) { + this.$store.dispatch('notification/ADD_ERROR', + `This private key already exists. The account name is ${hasAddressInfo.name}`) return null } @@ -245,28 +203,15 @@ export class FormSubWalletCreationTs extends Vue { this.currentPassword = null // - use repositories for storage - const walletsRepo = new WalletsRepository() - walletsRepo.create(subWallet.values) - - // - 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, - ) + this.walletService.saveWallet(subWallet) // - update app state await this.$store.dispatch('account/ADD_WALLET', subWallet) - await this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: subWallet}) - await this.$store.dispatch('wallet/SET_KNOWN_WALLETS', wallets) + await this.$store.dispatch('wallet/SET_CURRENT_WALLET', subWallet) + await this.$store.dispatch('wallet/SET_KNOWN_WALLETS', this.currentAccount.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) } @@ -274,15 +219,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 } @@ -290,15 +236,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, @@ -306,7 +253,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 62a2cd29e..2fc7191eb 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_) @@ -431,51 +358,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 7a898fc80..9c3cc7b38 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, AliasValidator} 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,9 @@ 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' +import {NetworkConfigurationModel} from '@/core/database/entities/NetworkConfigurationModel' export interface MosaicAttachment { mosaicHex: string @@ -81,22 +72,20 @@ export interface MosaicAttachment { MaxFeeAndSubmit, FormRow, }, - computed: {...mapGetters({currentSignerMosaics: 'wallet/currentSignerMosaics'})}, + computed: { + ...mapGetters({ + currentHeight: 'network/currentHeight', + balanceMosaics: 'mosaic/balanceMosaics', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) 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 +122,11 @@ export class FormTransferTransactionTs extends FormTransactionBase { protected mosaicInputsManager = MosaicInputsManager.initialize([]) - /** - * Current signer mosaics - * @protected - * @type {Mosaic[]} - */ - protected currentSignerMosaics: Mosaic[] + public currentHeight: number + + private balanceMosaics: MosaicModel[] + + private networkConfiguration: NetworkConfigurationModel /** * Reset the form with properties @@ -149,27 +137,31 @@ 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() // 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 - 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(() => { @@ -193,41 +185,20 @@ 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, + this.networkConfiguration.blockGenerationTargetTime) // 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 } /** @@ -304,6 +275,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { return null } } + /// end-region computed properties getter/setter /** @@ -362,26 +334,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 */ @@ -424,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({ @@ -432,7 +407,7 @@ export class FormTransferTransactionTs extends FormTransactionBase { attachments: this.mosaicsToAttachments(item.mosaics), }) }) - + this.$emit('onTransactionsChange', data) } } @@ -443,6 +418,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..2d0e5e982 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. @@ -15,15 +15,11 @@ */ import {Component, Vue} from 'vue-property-decorator' import {mapGetters} from 'vuex' -import {NetworkType, Password} from 'symbol-sdk' - +import {NetworkType} 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,25 +40,20 @@ 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', + }), + }, }) export class FormWalletNameUpdateTs extends Vue { /** * Currently active account * @see {Store.Wallet} - * @var {WalletsModel} - */ - public currentWallet: WalletsModel - - /** - * Known wallets identifiers - * @var {string[]} + * @var {WalletModel} */ - public knownWallets: string[] + public currentWallet: WalletModel /** * Currently active network type @@ -71,18 +62,6 @@ export class FormWalletNameUpdateTs extends Vue { */ public networkType: NetworkType - /** - * Wallets repository - * @var {WalletService} - */ - public wallets: WalletService - - /** - * Wallets repository - * @var {WalletsRepository} - */ - public walletsRepository: WalletsRepository - /** * Validation rules * @var {ValidationRuleset} @@ -95,12 +74,6 @@ export class FormWalletNameUpdateTs extends Vue { */ public isUnlockingAccount: boolean = false - /** - * Current unlocked password - * @var {Password} - */ - public currentPassword: Password - /** * Form fields * @var {Object} @@ -110,19 +83,14 @@ 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() - } /// region computed properties getter/setter public get hasAccountUnlockModal(): boolean { @@ -132,6 +100,7 @@ export class FormWalletNameUpdateTs extends Vue { public set hasAccountUnlockModal(f: boolean) { this.isUnlockingAccount = f } + /// end-region computed properties getter/setter /** @@ -150,24 +119,12 @@ export class FormWalletNameUpdateTs extends Vue { /** * When account is unlocked, the sub wallet can be created */ - public onAccountUnlocked() { - // - interpret form items - const values = this.formItems - + public async onAccountUnlocked() { try { - // - update model values - this.currentWallet.values.set('name', values.name) - - // - use repositories for storage - this.walletsRepository.update( - this.currentWallet.getIdentifier(), - this.currentWallet.values, - ) - + await this.$store.dispatch('wallet/UPDATE_CURRENT_WALLET_NAME', 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 d628b78b1..6d58c2084 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 d423af394..cc27a94d2 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) } @@ -151,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/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 e403aa844..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) } 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/LoginPage.vue b/src/views/pages/accounts/LoginPage.vue index 851ba2f31..d41bad590 100644 --- a/src/views/pages/accounts/LoginPage.vue +++ b/src/views/pages/accounts/LoginPage.vue @@ -37,19 +37,19 @@
-
- {{ getNetworkTypeLabel(networkType) }} +
+ {{ getNetworkTypeLabel(pair.networkType) }}
diff --git a/src/views/pages/accounts/LoginPageTs.ts b/src/views/pages/accounts/LoginPageTs.ts index b82056f7c..ee0922529 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,29 @@ 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' +import {NetworkTypeHelper} from '@/core/utils/NetworkTypeHelper' @Component({ computed: { ...mapGetters({ - currentLanguage: 'app/currentLanguage', currentAccount: 'account/currentAccount', isAuthenticated: 'account/isAuthenticated', }), @@ -58,37 +53,28 @@ import appConfig from '@/../config/app.conf.json' export default class LoginPageTs extends Vue { /** - * Currently active language - * @see {Store.AppInfo} - * @var {string} + * All known accounts */ - public currentLanguage: string + private accounts: AccountModel[] + /** + * Account indexed by network type + */ + private accountsClassifiedByNetworkType: { networkType: NetworkType, accounts: AccountModel[] }[] /** * Currently active account * @see {Store.Account} * @var {string} */ - public currentAccount: AccountsModel - - /** - * List of languages - * @see {Config.app} - * @var {any[]} - */ - public languageList: any[] = appConfig.languages + public currentAccount: AccountModel /** * 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 @@ -96,17 +82,6 @@ export default class LoginPageTs extends Vue { */ public validationRules = ValidationRuleset - /** - * Network types - * @var {NetworkNodeEntry[]} - */ - 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'}, - {value: NetworkType.MIJIN, label: 'MIJIN'}, - ] - /** * Form items */ @@ -116,53 +91,42 @@ export default class LoginPageTs extends Vue { hasHint: false, } - /// region computed properties getter/setter - get language() { - return this.currentLanguage - } - - set language(lang) { - this.$store.commit('app/SET_LANGUAGE', lang) - } - - get accountsClassifiedByNetworkType() { - const repository = new AccountsRepository() - return repository.getNamesByNetworkType() - } /// end-region computed properties getter/setter /** * Hook called when the page is mounted * @return {void} */ - public mounted() { - if (this.currentAccount) { - this.formItems.currentAccountName = this.currentAccount.values.get('accountName') - return + public created() { + this.accounts = this.accountService.getAccounts() + + const reducer = (accumulator: { networkType: NetworkType, accounts: AccountModel[] }[], + currentValue: AccountModel) => { + + const currentAccumulator = accumulator.find(a => a.networkType == currentValue.networkType) + if (currentAccumulator) { + currentAccumulator.accounts.push(currentValue) + return accumulator + } else { + return [ ...accumulator, {networkType: currentValue.networkType, accounts: [currentValue]}] + } } - // no account pre-selected, select first if available - const accounts = this.accountsRepository.entries() - if (!accounts.size) { + this.accountsClassifiedByNetworkType = this.accounts.reduce(reducer, []) + if (!this.accounts.length) { return } - // accounts available, iterate to first account - const firstAccount = this.accountsRepository.collect().shift() - this.formItems.currentAccountName = firstAccount.values.get('accountName') + this.formItems.currentAccountName = this.accounts[0].accountName } /** * Getter for network type label - * @param {NetworkType} networkType + * @param {NetworkType} networkType * @return {string} */ public getNetworkTypeLabel(networkType: NetworkType): string { - const findType = this.networkTypeList.find(n => n.value === networkType) - if (findType === undefined) { - return '' - } - return findType.label + return NetworkTypeHelper.getNetworkTypeLabel(networkType) } /** @@ -171,23 +135,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 +162,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())[0].getIdentifier() - const defaultWallet = Array.from(knownWallets.values()).filter( - w => w.getIdentifier() === defaultWalletId, - )[0] + 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 a5aeace03..27bba559a 100644 --- a/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts +++ b/src/views/pages/accounts/create-account/finalize/FinalizeTs.ts @@ -1,37 +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 {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' @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 +45,7 @@ export default class FinalizeTs extends Vue { * @see {Store.Account} * @var {AccountsModel} */ - public currentAccount: AccountsModel + public currentAccount: AccountModel /** * Temporary stored password @@ -66,29 +65,7 @@ export default class FinalizeTs extends Vue { * Wallet Service * @var {WalletService} */ - public walletService: WalletService - - /** - * Wallets Repository - * @var {WalletsRepository} - */ - public walletsRepository: WalletsRepository - - /** - * Accounts Repository - * @var {AccountsRepository} - */ - public accountsRepository: AccountsRepository - - /** - * Hook called when the page is mounted - * @return {void} - */ - public mounted() { - this.walletService = new WalletService(this.$store) - this.walletsRepository = new WalletsRepository() - this.accountsRepository = new AccountsRepository() - } + public walletService: WalletService = new WalletService() /** * Finalize the account creation process by adding @@ -96,34 +73,27 @@ export default class FinalizeTs extends Vue { * @return {void} */ public async submit() { - try { - // create account by mnemonic - const wallet = this.walletService.getDefaultWallet( - this.currentAccount, - this.currentMnemonic, - this.currentPassword, - this.networkType, - ) - // execute store actions - await this.$store.dispatch('account/ADD_WALLET', wallet) - await this.$store.dispatch('wallet/SET_CURRENT_WALLET', {model: wallet}) - await this.$store.dispatch('wallet/SET_KNOWN_WALLETS', [wallet.getIdentifier()]) - await this.$store.dispatch('temporary/RESET_STATE') - await this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) + // create account by mnemonic + const wallet = this.walletService.getDefaultWallet( + this.currentAccount, + this.currentMnemonic, + this.currentPassword, + this.networkType, + ) + // use repository for storage + this.walletService.saveWallet(wallet) + + // 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', [wallet.id]) + await this.$store.dispatch('temporary/RESET_STATE') + await this.$store.dispatch('notification/ADD_SUCCESS', NotificationType.OPERATION_SUCCESS) + - // update account in storage - this.walletsRepository.create(wallet.values) - this.accountsRepository.update( - this.currentAccount.getIdentifier(), - this.currentAccount.values, - ) + // flush and continue + return this.$router.push({name: 'dashboard'}) - // flush and continue - return this.$router.push({name: 'dashboard'}) - } - 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..ac2cc8ea3 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 = new AccountService() /** * Whether component should track mouse move @@ -70,14 +71,6 @@ export default class GenerateMnemonicTs extends Vue { */ private percent: number = 0 - /** - * Hook called when the component is mounted - * @return {void} - */ - public mounted() { - this.accounts = new AccountsRepository() - } - /** * Track and handle mouse move event * @param {Vue.Event} event @@ -112,8 +105,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 +114,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..88b27401b 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,22 @@ /** * 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' - // child components // @ts-ignore import MnemonicVerification from '@/components/MnemonicVerification/MnemonicVerification.vue' @@ -28,19 +25,13 @@ import MnemonicVerification from '@/components/MnemonicVerification/MnemonicVeri components: { MnemonicVerification, }, - computed: {...mapGetters({ - currentAccount: 'account/currentAccount', - currentMnemonic: 'temporary/mnemonic', - }), + computed: { + ...mapGetters({ + currentMnemonic: 'temporary/mnemonic', + }), }, }) export default class VerifyMnemonicTs extends Vue { - /** - * Currently active account - * @see {Store.Account} - * @var {string} - */ - public currentAccount: AccountsModel /** * Temporary Mnemonic pass phrase @@ -52,5 +43,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..0300b7b9e 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 = new AccountService() /** * Form items @@ -66,16 +66,9 @@ export default class ImportMnemonicTs extends Vue { } /** * @description: Receive the Input words - * @type: Array - */ - public wordsArray: Array=[] - /** - * Hook called when the component is mounted - * @return {void} + * @type: Array */ - public mounted() { - this.accounts = new AccountsRepository() - } + public wordsArray: Array = [] /** * Delete account and go back @@ -83,23 +76,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 +117,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/WalletSelection.vue b/src/views/pages/accounts/import-account/wallet-selection/WalletSelection.vue index 762f62764..9f6c9db11 100644 --- a/src/views/pages/accounts/import-account/wallet-selection/WalletSelection.vue +++ b/src/views/pages/accounts/import-account/wallet-selection/WalletSelection.vue @@ -22,8 +22,7 @@ {{ formatters.miniAddress(a) }} @@ -57,8 +56,7 @@ {{ formatters.miniAddress(addressesList[index]) }} 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 b3969b672..61155466b 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, RepositoryFactory, 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,28 +187,44 @@ 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, ) - + const repositoryFactory = this.$store.getters['network/repositoryFactory'] as RepositoryFactory // fetch accounts info - const accountsInfo = await this.$store.dispatch( - 'wallet/REST_FETCH_INFOS', - this.addressesList, - ) + const accountsInfo = await repositoryFactory.createAccountRepository() + .getAccountsInfo(this.addressesList).toPromise() 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, + } + }).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 +238,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 +247,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 Account ${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 Account${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 92c42fcef..ef34fe7ae 100644 --- a/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue +++ b/src/views/pages/wallets/WalletDetailsPage/WalletDetailsPage.vue @@ -1,5 +1,5 @@