diff --git a/src/app/inbox/inbox-compose/inbox-compose.component.html b/src/app/inbox/inbox-compose/inbox-compose.component.html index 4c174d7..86695f2 100644 --- a/src/app/inbox/inbox-compose/inbox-compose.component.html +++ b/src/app/inbox/inbox-compose/inbox-compose.component.html @@ -7,6 +7,22 @@

Compose New

+ +
+
+ Balance: +
+
+
+ + + +
@@ -20,8 +36,9 @@

Compose New

- - @@ -44,8 +61,7 @@

Compose New

diff --git a/src/app/inbox/inbox-compose/inbox-compose.component.spec.ts b/src/app/inbox/inbox-compose/inbox-compose.component.spec.ts index 4c98c7f..e4326a7 100644 --- a/src/app/inbox/inbox-compose/inbox-compose.component.spec.ts +++ b/src/app/inbox/inbox-compose/inbox-compose.component.spec.ts @@ -25,6 +25,8 @@ import { EnvelopeServiceStub } from 'src/app/services/mailchain/envelope/envelop import { PublicKeyServiceStub } from 'src/app/services/mailchain/public-key/public-key.service.stub'; import { SendServiceStub } from 'src/app/services/mailchain/messages/send.service.stub'; import { NameserviceServiceStub } from 'src/app/services/mailchain/nameservice/nameservice.service.stub'; +import { BalanceService } from 'src/app/services/mailchain/addresses/balance.service'; +import { BalanceServiceStub } from 'src/app/services/mailchain/addresses/balance.service.stub'; // Workaround: @@ -47,6 +49,8 @@ describe('InboxComposeComponent', () => { const currentAccount = '0x92d8f10248c6a3953cc3692a894655ad05d61efb'; const currentAccount2 = '0x0123456789012345678901234567890123456789'; + const balance = 0; + const fees = ''; const ensName = 'mailchain.eth'; const addresses = [currentAccount, currentAccount2]; @@ -65,6 +69,7 @@ describe('InboxComposeComponent', () => { { provide: PublicKeyService, useClass: PublicKeyServiceStub }, { provide: SendService, useClass: SendServiceStub }, { provide: NameserviceService, useClass: NameserviceServiceStub }, + { provide: BalanceService, useClass: BalanceServiceStub }, ], imports: [ @@ -145,6 +150,24 @@ describe('InboxComposeComponent', () => { expect(component.model.body).toBe('') }) + + describe('handling the balance', () => { + it('should initialize a balance in the "balance" field using an available balance', async () => { + await component.ngOnInit(); + fixture.detectChanges() + expect(component.balance).toBe(balance) + }) + }); + + describe('handling the fees', () => { + it('should initialize a Fees in the "fees" field', async () => { + await component.ngOnInit(); + fixture.detectChanges() + expect(component.fees).toBe(fees) + }) + }); + + describe('handling the envelope', () => { it('should initialize an envelope in the "envelope" field using an available envelop', async () => { envelopes = mailchainTestService.envelopeTypeMli() @@ -177,7 +200,7 @@ describe('InboxComposeComponent', () => { // TODO await component.ngOnInit(); // expect(component.mailForm).toBe('0x0123456789abcdef0123456789abcdef01234567') - }) + }) it('should initialize the model "replyTo" field with the sender address', async () => { await component.ngOnInit(); @@ -187,27 +210,27 @@ describe('InboxComposeComponent', () => { it('should initialize the model "to" field with the reply-to recipient address', async () => { await component.ngOnInit(); expect(component.model.to).toBe('0xABCDEABCDE012345678901234567890123456789') - }) - + }) + xit('should disable the model "to" field on page so the address cannot be changed', async () => { // TODO await component.ngOnInit(); // expect(component.mailForm).toBe('0xABCDEABCDE012345678901234567890123456789') - }) + }) it('should initialize the model "subject" field with the original message field + a prefix of "Re: "', async () => { await component.ngOnInit(); expect(component.model.subject).toBe('Re: Mailchain Test!') }) - + it('should not re-initialize the model "subject" field with an extra prefix of "Re: "', async () => { component.currentMessage.subject = "Re: Mailchain Test!" await component.ngOnInit(); expect(component.model.subject).toBe('Re: Mailchain Test!') }) - - + + @@ -233,7 +256,7 @@ describe('InboxComposeComponent', () => { component.currentMessage.headers["content-type"] = "text/html; charset=\"UTF-8\"" }) - + it('should initialize the model "body" field with the original message field and wrap the body in a `blockquote`', async () => { let response = "

From: <0x0123456789012345678901234567890123456789@testnet.ethereum>
Reply To: <0xABCDEABCDE012345678901234567890123456789@testnet.ethereum>
Date: 2019-06-07T14:53:36Z
To: <0x0123456789abcdef0123456789abcdef01234567@testnet.ethereum>
Subject: Mailchain Test!

A body
" @@ -386,7 +409,7 @@ describe('InboxComposeComponent', () => { component.evaluateReply() expect(component.isReply).toBeTrue() }) - + }) describe('setupRecipientAddressLookupSubscription', () => { @@ -667,3 +690,5 @@ describe('InboxComposeComponent', () => { }); }); + + diff --git a/src/app/inbox/inbox-compose/inbox-compose.component.ts b/src/app/inbox/inbox-compose/inbox-compose.component.ts index 09301fb..4ba2bd7 100644 --- a/src/app/inbox/inbox-compose/inbox-compose.component.ts +++ b/src/app/inbox/inbox-compose/inbox-compose.component.ts @@ -7,6 +7,8 @@ import { PublicKeyService } from 'src/app/services/mailchain/public-key/public-k import { AddressesService } from 'src/app/services/mailchain/addresses/addresses.service'; import { EnvelopeService } from 'src/app/services/mailchain/envelope/envelope.service'; import { NameserviceService } from 'src/app/services/mailchain/nameservice/nameservice.service'; +import { BalanceService } from 'src/app/services/mailchain/addresses/balance.service'; + import { Subject, of, Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, mergeMap } from "rxjs/operators"; @@ -15,6 +17,7 @@ import { ModalConnectivityErrorComponent } from 'src/app/modals/modal-connectivi import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import { CKEditorComponent } from '@ckeditor/ckeditor5-angular'; +import { getCurrencySymbol } from '@angular/common'; @Component({ selector: '[inbox-compose]', @@ -34,6 +37,9 @@ export class InboxComposeComponent implements OnInit { public model = new Mail() public fromAddresses: Array = [] public envelopes: Array = [] + public balance: number = 0 + public currency: string = "" + public fees: string = "" public sendMessagesDisabled: boolean = false; private subscription @@ -64,6 +70,7 @@ export class InboxComposeComponent implements OnInit { private addressesService: AddressesService, private envelopeService: EnvelopeService, private nameserviceService: NameserviceService, + private balanceService: BalanceService, private modalService: BsModalService, ) { this.initMail() @@ -78,6 +85,7 @@ export class InboxComposeComponent implements OnInit { this.model.replyTo = "" this.model.subject = "" this.model.body = "" + } /** @@ -120,6 +128,32 @@ export class InboxComposeComponent implements OnInit { this.envelopes = await this.envelopeService.getEnvelope(); } + + /** + * updates the balance whenver address dropdwon value is changed + */ + + public async updateBalanceForAddress(address) { + this.balance = await this.balanceService.getBalance(address, this.currentProtocol, this.currentNetwork); + + } + + private async setBalance() { + if (this.currentAccount != undefined) { + + this.balance = await this.balanceService.getBalance(this.currentAccount, this.currentProtocol, this.currentNetwork); + } + } + + /** + * Sets the currency + */ + private async setCurrency() { + this.currency = await this.mailchainService.getCurrencyForProtocol(this.currentProtocol) + } + + + /** * Returns the identicon for the an address * @param address the address @@ -197,7 +231,7 @@ export class InboxComposeComponent implements OnInit { mergeMap(searchVal => { return this.resolveAddress(searchVal) }) - ).subscribe((res) => { + ).subscribe((res) => { res.subscribe(val => { let address = val['body']['address'] if ( @@ -353,7 +387,9 @@ export class InboxComposeComponent implements OnInit { this.evaluateReply() this.handleReplyFields() this.setupRecipientAddressLookupSubscription() - } + await this.setBalance() + await this.setCurrency() + } /** * Handles content-type in the view @@ -463,13 +499,13 @@ export class InboxComposeComponent implements OnInit { */ private handleReplyFields() { if (this.currentMessage && this.currentMessage.headers) { - + if (this.inputContentType == "html") { this.handleReplyInHtml() } else if (this.inputContentType == "plaintext") { this.handleReplyInPlaintext() } - + if (this.currentMessage.headers["reply-to"] && this.currentMessage.headers["reply-to"].length) { this.model.to = this.mailchainService.parseAddressFromMailchain(this.currentProtocol, this.currentMessage.headers["reply-to"]) } else { @@ -477,7 +513,7 @@ export class InboxComposeComponent implements OnInit { } this.messageToField = this.currentRecipientValue = this.model.to - + this.model.replyTo = this.model.from = this.mailchainService.parseAddressFromMailchain(this.currentProtocol, this.currentMessage.headers.to) this.model.subject = this.addRePrefixToSubject(this.currentMessage["subject"]) } diff --git a/src/app/services/mailchain/addresses/balance.service.spec.ts b/src/app/services/mailchain/addresses/balance.service.spec.ts new file mode 100644 index 0000000..f70f40e --- /dev/null +++ b/src/app/services/mailchain/addresses/balance.service.spec.ts @@ -0,0 +1,72 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { BalanceService } from './balance.service'; +import { MailchainTestService } from 'src/app/test/test-helpers/mailchain-test.service'; +import { HttpHelpersService } from '../../helpers/http-helpers/http-helpers.service'; +import { ProtocolsServiceStub } from '../protocols/protocols.service.stub'; +import { ProtocolsService } from '../protocols/protocols.service'; +import { Observable, of } from 'rxjs'; + + +describe('BalanceService', () => { + + let balanceService: BalanceService; + let httpTestingController: HttpTestingController; + let mailchainTestService: MailchainTestService + let serverResponse + + const currentAccount = '0x92d8f10248c6a3953cc3692a894655ad05d61efb'; + const desiredUrl = `http://127.0.0.1:8080/api/addresses/` + currentAccount + `/balance?protocol=ethereum&network=mainnet` + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + HttpHelpersService, + { provide: ProtocolsService, useClass: ProtocolsServiceStub }, + BalanceService, + ], + imports: [HttpClientTestingModule] + }); + + balanceService = TestBed.inject(BalanceService); + mailchainTestService = TestBed.inject(MailchainTestService); + httpTestingController = TestBed.inject(HttpTestingController); + + //serverResponse = mailchainTestService.senderAddressesEthereumObserveResponse() + // expectedAddresses = mailchainTestService.senderAddresses() + }); + + afterEach(() => { + httpTestingController.verify(); + }); + + + describe('initUrl', () => { + it('should initialize the url', () => { + balanceService.initUrl() + expect(balanceService['url']).toEqual('http://127.0.0.1:8080/api') + }); + }) + + it('should be created', () => { + expect(balanceService).toBeTruthy(); + }); + + describe('getBalance', () => { + beforeEach(() => { + let obs: Observable = of(mailchainTestService.senderBalanceEthereumObserveResponse()) + spyOn(balanceService, 'getBalance').and.returnValue( + obs.toPromise() + ) + }); + it('should get balance for addresses', async () => { + let result + await balanceService.getBalance(currentAccount, 'ethereum', 'mainnet').then(res => { + result = res + }); + expect(result).toEqual(mailchainTestService.senderBalanceEthereumObserveResponse()) + expect(balanceService.getBalance).toHaveBeenCalled() + }); + }) +}); diff --git a/src/app/services/mailchain/addresses/balance.service.stub.ts b/src/app/services/mailchain/addresses/balance.service.stub.ts new file mode 100644 index 0000000..1d860c9 --- /dev/null +++ b/src/app/services/mailchain/addresses/balance.service.stub.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { MailchainTestService } from 'src/app/test/test-helpers/mailchain-test.service'; +import { LocalStorageServerService } from '../../helpers/local-storage-server/local-storage-server.service'; + +@Injectable({ + providedIn: 'root' +}) +export class BalanceServiceStub { + private url + + constructor( + private mailchainTestService: MailchainTestService, + private localStorageServerService: LocalStorageServerService + ) { + + this.initUrl() + } + + /** + * Initialize URL from local storage + */ + async initUrl() { + this.url = `${this.localStorageServerService.getCurrentServerDetails()}/api` + } + + + async getBalance(address, protocol, network) { + let addresses = [] + let res: any + let balance = 0; + + switch (protocol) { + case 'ethereum': + res = this.mailchainTestService.senderBalanceEthereumObserveResponse() + break; + case 'substrate': + res = this.mailchainTestService.senderBalanceSubstrateObserveResponse() + break; + case 'algorand': + res = this.mailchainTestService.senderBalanceAlgorandObserveResponse() + break; + default: + break; + } + + switch (protocol) { + case 'ethereum': + balance = ((res["body"]["balance"]) / (10 ** 18)) + break; + default: + balance = res["body"]["balance"] + break; + } + + return balance; + + } +} diff --git a/src/app/services/mailchain/addresses/balance.service.ts b/src/app/services/mailchain/addresses/balance.service.ts new file mode 100644 index 0000000..0786ba3 --- /dev/null +++ b/src/app/services/mailchain/addresses/balance.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { LocalStorageServerService } from '../../helpers/local-storage-server/local-storage-server.service'; +import { HttpHelpersService } from '../../helpers/http-helpers/http-helpers.service'; + +@Injectable({ + providedIn: 'root' +}) +export class BalanceService { + + private url: string + + constructor( + private http: HttpClient, + private httpHelpersService: HttpHelpersService, + private localStorageServerService: LocalStorageServerService, + ) { + this.initUrl() + } + + /** + * Initialize URL from local storage + */ + async initUrl() { + this.url = `${this.localStorageServerService.getCurrentServerDetails()}/api` + } + + /** + * Get and return the balance for the address + */ + async getBalance(address, protocol, network) { + let balance = 0; + + var httpOptions = this.httpHelpersService.getHttpOptions([ + ['protocol', protocol], + ['network', network] + ]) + + let res = await this.http.get( + this.url + `/addresses/` + address + `/balance`, + httpOptions + ).toPromise(); + + switch (protocol) { + case 'ethereum': + balance = ((res["body"]["balance"]) / (10 ** 18)) + break; + default: + balance = res["body"]["balance"] + break; + } + + return balance; + } + + + + + /** + * Returns the balance response with status codes + */ + public getBalanceResponse(address, protocol, network) { + var httpOptions = this.httpHelpersService.getHttpOptions([ + ['protocol', protocol], + ['network', network] + ]) + + return this.http.get( + this.url + `/addresses/` + address + `/balance`, + httpOptions + ) + } + +} diff --git a/src/app/services/mailchain/mailchain.service.ts b/src/app/services/mailchain/mailchain.service.ts index 8f5538c..4c0fbb1 100644 --- a/src/app/services/mailchain/mailchain.service.ts +++ b/src/app/services/mailchain/mailchain.service.ts @@ -77,10 +77,10 @@ export class MailchainService { * @param address * @returns addr: '<33456789@network.protocol>' */ - parseAddressFieldOnly(address: string) { + parseAddressFieldOnly(address: string) { var arr = address.match(/<.*[@].*>/); - return(arr[0]); - } + return (arr[0]); + } /** * Parses an address in Mailchain form and returns public address @@ -130,7 +130,7 @@ export class MailchainService { * Parses an address in Mailchain form and returns public address * @param address an address in format '<00000000000000000@network.chainname>' */ - parseAddressBase32FromMailchain(address: string) { + parseAddressBase32FromMailchain(address: string) { var regexMailAddr = new RegExp('<[A-Z2-7]+[@].+>$'); if (regexMailAddr.test(address)) { return address.substr(1, address.indexOf('@') - 1); @@ -298,7 +298,7 @@ export class MailchainService { let regex = new RegExp('^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{20,}$'); return regex.test(value) } - + /** * tests the value matches the Algorand Address Regex * @param value the address value to test, e.g. G0GTKMEEEEZH5TFUDYZMWWGXZLO3Z7765CR52ZXBBNCCMNPDYM3ZII7CSI @@ -328,5 +328,22 @@ export class MailchainService { } } + /** + * Returns the currency on basis of the selected protocol in Mailchain + * @param protocol the cureent protocol + */ + getCurrencyForProtocol(protocol: string) { + + switch (protocol) { + case 'ethereum': + return "ETH" + case 'substrate': + return "" + case 'algorand': + return "ALGO" + default: + return "" + } + } -} +} diff --git a/src/app/test/test-helpers/mailchain-test.service.ts b/src/app/test/test-helpers/mailchain-test.service.ts index b312cc5..b6fc479 100644 --- a/src/app/test/test-helpers/mailchain-test.service.ts +++ b/src/app/test/test-helpers/mailchain-test.service.ts @@ -140,6 +140,63 @@ export class MailchainTestService { } } + public senderBalanceEthereumObserveResponse() { + return { + "headers": { + "normalizedNames": {}, + "lazyUpdate": null + }, + "status": 200, + "statusText": "OK", + "url": "http://127.0.0.1:8080/api/addresses/0x92d8f10248c6a3953cc3692a894655ad05d61efb/balance?protocol=ethereum&network=mainnet", + "ok": true, + "type": 4, + "body": { + "balance": 2183846200000000000, + "unit": "wei" + } + + } + } + + public senderBalanceSubstrateObserveResponse() { + return { + "headers": { + "normalizedNames": {}, + "lazyUpdate": null + }, + "status": 200, + "statusText": "OK", + "url": "http://127.0.0.1:8080/api/addresses/0x92d8f10248c6a3953cc3692a894655ad05d61efb/balance?protocol=substrate&network=Edgeware-mainnet", + "ok": true, + "type": 4, + "body": { + "balance": 2183846200000000000, + "unit": "wei" + } + + } + } + + public senderBalanceAlgorandObserveResponse() { + return { + "headers": { + "normalizedNames": {}, + "lazyUpdate": null + }, + "status": 200, + "statusText": "OK", + "url": "http://127.0.0.1:8080/api/addresses/0x92d8f10248c6a3953cc3692a894655ad05d61efb/balance?protocol=algorand&network=mainnet", + "ok": true, + "type": 4, + "body": { + "balance": 218, + "unit": "ALGO" + } + + } + } + public senderAddressEthereumServerResponse(): any { return { "addresses": [ @@ -314,7 +371,7 @@ export class MailchainTestService { } public protocolsServerResponse(): any { - return {"protocols":[{"name":"algorand","networks":[{"name":"betanet","id":"","nameservice-domain-enabled":false,"nameservice-address-enabled":false},{"name":"mainnet","id":"","nameservice-domain-enabled":false,"nameservice-address-enabled":false},{"name":"testnet","id":"","nameservice-domain-enabled":false,"nameservice-address-enabled":false}]},{"name":"ethereum","networks":[{"name":"goerli","id":"","nameservice-domain-enabled":true,"nameservice-address-enabled":true},{"name":"kovan","id":"","nameservice-domain-enabled":true,"nameservice-address-enabled":true},{"name":"mainnet","id":"","nameservice-domain-enabled":true,"nameservice-address-enabled":true},{"name":"rinkeby","id":"","nameservice-domain-enabled":true,"nameservice-address-enabled":true},{"name":"ropsten","id":"","nameservice-domain-enabled":true,"nameservice-address-enabled":true}]},{"name":"substrate","networks":[{"name":"edgeware-beresheet","id":"7","nameservice-domain-enabled":false,"nameservice-address-enabled":false},{"name":"edgeware-local","id":"7","nameservice-domain-enabled":false,"nameservice-address-enabled":false},{"name":"edgeware-mainnet","id":"7","nameservice-domain-enabled":false,"nameservice-address-enabled":false}]}]} + return { "protocols": [{ "name": "algorand", "networks": [{ "name": "betanet", "id": "", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }, { "name": "mainnet", "id": "", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }, { "name": "testnet", "id": "", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }] }, { "name": "ethereum", "networks": [{ "name": "goerli", "id": "", "nameservice-domain-enabled": true, "nameservice-address-enabled": true }, { "name": "kovan", "id": "", "nameservice-domain-enabled": true, "nameservice-address-enabled": true }, { "name": "mainnet", "id": "", "nameservice-domain-enabled": true, "nameservice-address-enabled": true }, { "name": "rinkeby", "id": "", "nameservice-domain-enabled": true, "nameservice-address-enabled": true }, { "name": "ropsten", "id": "", "nameservice-domain-enabled": true, "nameservice-address-enabled": true }] }, { "name": "substrate", "networks": [{ "name": "edgeware-beresheet", "id": "7", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }, { "name": "edgeware-local", "id": "7", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }, { "name": "edgeware-mainnet", "id": "7", "nameservice-domain-enabled": false, "nameservice-address-enabled": false }] }] } }