Commit 6e3e2d7f authored by Jeremy Bokobza's avatar Jeremy Bokobza

Merge remote-tracking branch 'upstream/master' into feature/list-files

parents 94932a80 ede7dece
......@@ -2,12 +2,7 @@
## Request/Response
RESPONSE: responsecode (`200` if success, `400`/`500` if error, see later)
```
{
"success": "true"
}
```
RESPONSE: response code (`200` for all successful requests, `4xx`/`5xx` if error, see later)
HEADERS
`Content-Type:application/json`
......@@ -16,15 +11,19 @@ HEADERS
### General errors
`400` series status codes for client issues & `500` series status codes for server issues.
API should standardize that all `400` series errors come with consumable JSON error representation.
BODY
The error response is an array of error objects.
Depending on the circumstance the API will either return an error at the first encounter or will continue until multiple errors are gathered.
```
{
"success": "false",
"message": "something bad happened", // ex.Message maybe?
"description": ex.ToString()
"errors": [
{
"status": 400,
"message": "No wallet file found at Wallets\\myFirstWallet.json",
"description": "System.ArgumentException: No wallet file found at..."
}
]
}
```
......@@ -36,11 +35,7 @@ This error message comes at all request if the wallet is not created yet, except
- `POST /wallet/send-transaction`
```
{
"success": "false",
"message": "wallet is not created",
"description": ""
}
404 (Not Found) - "wallet is not created"
```
### wallet is not decrypted
......@@ -53,11 +48,7 @@ This error message comes at all request if the wallet is not loaded yet, except
- `DELETE /wallet`
```
{
"success": "false",
"message": "wallet is not decrypted",
"description": ""
}
400 (Bad Request) - "wallet is not decrypted"
```
## Key Management
......@@ -99,7 +90,6 @@ POST /wallet/send-transaction - Attempts to send a transaction
### Responses
```
{
"success": "true",
"walletFilePath": "path to the wallet file",
"encryptedSeed": "6PYKWP34en1wELfcJDgXaFRPugjgkDdEk2p2Pzytm1158dxgNyLAUXwpKL",
"chainCode": "q/Fn7+RSIVM0p0Nj6rIuNkybF+0WKeSZPMQS2QCbDzY=",
......@@ -119,7 +109,6 @@ POST /wallet/send-transaction - Attempts to send a transaction
### Responses
```
{
"success": "true",
"extkey": "sadwqdpqoijedqcdoijsadoijsadisa",
"extpubkey": "dalkdsaklkjdlkjdsaljlkjdsalkjdsalk",
}
......@@ -128,7 +117,6 @@ POST /wallet/send-transaction - Attempts to send a transaction
### Responses
```
{
"success": "true",
"connectedNodeCount": "7",
"maxConnextedNodeCount": "8",
"headerChainHeight": "1048",
......@@ -151,7 +139,6 @@ POST /wallet/send-transaction - Attempts to send a transaction
### Responses
```
{
"success": "true",
"mnemonic": "foo bar buz",
}
```
......@@ -185,7 +172,6 @@ Works as expected.
### Responses
```
{
"success": "true",
"addresses": // 7 unused receive address (7 is the best number: https://www.psychologytoday.com/blog/fulfillment-any-age/201109/7-reasons-we-7-reasons)
[
"mzz63n3n89KVeHQXRqJEVsQX8MZj5zeqCw",
......@@ -203,7 +189,6 @@ Works as expected.
### Responses
```
{
"success": "true",
"history":
[
{
......@@ -226,7 +211,6 @@ Works as expected.
### Responses
```
{
"success": "true",
"synced": "true",
"confirmed": "0.144",
"unconfirmed": "-6.23"
......@@ -252,7 +236,6 @@ Unconfirmed balance is the difference of unconfirmed incoming and outgoing trans
#### Successful
```
{
"success": "true",
"spendsUnconfirmed": "false", // If spends unconfirmed you can ask the user if it's sure about spending unconfirmed transaction (if inputs are malleated or inputs never confirm then this transaction will never confirm either"
"fee": "0.0001",
"feePercentOfSent": "0.1" // Percentage of the total spent amount, there must be a safety limit implemented here
......@@ -303,11 +286,7 @@ Unconfirmed balance is the difference of unconfirmed incoming and outgoing trans
#### Errors
```
{
"success": "false",
"message": "wallet is not synced",
"description": ""
}
400 - "wallet is not synced"
```
## POST /wallet/send-transaction - Attempts to send a transaction
......
{
"create":
{
"mnemonic": "foo bar buz"
},
"load":
{
"success": "true"
"files": {
"walletsPath": "/home/dev0tion/Desktop/Wallets",
"walletsFiles": [
"myFirstWallet.json",
"mySecondWallet.json"
]
},
"status":
{
......
......@@ -8,7 +8,7 @@ const routes: Routes = [
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
imports: [ RouterModule.forRoot(routes, {useHash: true}) ],
exports: [ RouterModule ]
})
......
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { remote } from 'electron';
import { ApiService } from './shared/api/api.service';
@Component({
......@@ -13,33 +11,30 @@ import { ApiService } from './shared/api/api.service';
export class AppComponent implements OnInit {
constructor(private router: Router, private apiService: ApiService) {}
private errorMessage: string;
private response: any;
private isConfigured: boolean = true;
private errorMessage: any;
private responseMessage: any;
ngOnInit() {
this.checkWalletStatus();
}
private checkWalletStatus(){
this.apiService.getWalletStatus()
.subscribe(
response => this.response = response,
error => this.errorMessage = <any>error,
() => this.navigate()
);
this.router.navigate(['/login']);
//this.checkWalletStatus();
}
private navigate() {
if (this.response.success === "true") {
// remote.dialog.showMessageBox({message: remote.app.getPath('userData')})
this.router.navigate(['/login'])
} else {
this.router.navigate(['/setup'])
}
}
private hasWallet() {
return true;
}
// private checkWalletStatus(){
// this.apiService.getWalletStatus()
// .subscribe(
// response => {
// if (response.status >= 200 && response.status < 400) {
// this.responseMessage = response;
// this.router.navigate(['/login']);
// }
// },
// error => {
// this.errorMessage = <any>error;
// if (error.status === 400 || error.status === 404) {
// this.router.navigate(['/setup']);
// console.log(this.errorMessage);
// }
// }
// );
// }
}
\ No newline at end of file
<h1>Welcome back</h1>
<p>Please enter your password to decrypt your wallet</p>
<form (ngSubmit)="onSubmit()" #passwordForm="ngForm">
<h1>Welcome to Breeze</h1>
<div *ngIf="hasWallet">
<p>Choose the wallet you want to open:</p>
<div class="form-group">
<label for="walletLabel">Wallet to open:</label>
<select name="wallet" #walletName (change)="walletChanged(walletName.value)">
<option *ngFor="let wallet of wallets" [value]="wallet">{{wallet}}</option>
</select>
</div>
<p>Please enter your password to decrypt your wallet</p>
<form (ngSubmit)="onSubmit()" #passwordForm="ngForm">
<div class="form-group">
<label for="password">Your password: </label>
<input type="password" class="form-control" id="password" required name="password">
<input type="password" class="form-control" id="password" [(ngModel)]="password" required name="password">
</div>
<button type="submit" class="btn btn-success">Decrypt</button>
</form>
\ No newline at end of file
</form>
<p></p>
</div>
<div *ngIf="hasWallet;else no_wallet">
<p> If you like to create or restore a wallet please click the button below.</p>
</div>
<ng-template #no_wallet>
<p> Looks like you're new here. Please create or restore a wallet.</p>
</ng-template>
<button type="button" (click)="clickedCreate()" class="btn btn-success">Create or restore wallet</button>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from '../shared/api/api.service';
import { WalletLoad } from '../shared/wallet-load';
@Component({
selector: 'app-login',
......@@ -10,27 +11,79 @@ import { ApiService } from '../shared/api/api.service';
export class LoginComponent implements OnInit {
constructor(private apiService: ApiService, private router: Router) { }
private response: any;
private errorMessage: string;
private walletLoad: WalletLoad;
private hasWallet: boolean = false;
private currentWalletName: string;
private wallets: [any];
private walletPath: string;
private password: string;
private responseMessage: any;
private errorMessage: any;
ngOnInit() {
this.apiService.getWalletFiles()
.subscribe(
response => {
if (response.status >= 200 && response.status < 400) {
this.responseMessage=response;
this.wallets = response.json().walletsFiles;
this.walletPath = response.json().walletsPath;
if (this.wallets.length > 0) {
this.hasWallet = true;
this.currentWalletName = this.wallets[0].slice(0, -5);
} else {
this.hasWallet = false;
}
}
},
error => {
this.errorMessage = <any>error;
if (error.status >= 400) {
alert(this.errorMessage);
console.log(this.errorMessage);
}
}
);
}
private onSubmit() {
this.apiService.loadWallet("123")
this.walletLoad = new WalletLoad();
this.walletLoad.password = this.password;
this.walletLoad.name = this.currentWalletName;
this.walletLoad.folderPath = this.walletPath;
console.log(this.walletLoad);
this.apiService.loadWallet(this.walletLoad)
.subscribe(
response => this.response = response,
error => this.errorMessage = error,
() => this.loadWallet()
response => {
console.log(response);
if (response.status >= 200 && response.status < 400) {
this.responseMessage = response;
this.router.navigate(['/wallet']);
}
},
error => {
this.errorMessage = <any>error;
if (error.status === 403 && error.json().errors[0].message === "Wrong password, please try again.") {
alert("Wrong password, try again.");
} else if (error.status >= 400) {
alert(this.errorMessage);
console.log(this.errorMessage);
}
}
);
}
private loadWallet() {
if (this.response.success === "true") {
this.router.navigate(['/wallet/send']);
} else {
alert("Something went wrong.")
private walletChanged(walletName: string) {
let walletNameNoJson: string = walletName.slice(0, -5);
this.currentWalletName = walletNameNoJson;
}
private clickedCreate() {
this.router.navigate(['/setup']);
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateComponent } from './create.component';
describe('CreateComponent', () => {
let component: CreateComponent;
let fixture: ComponentFixture<CreateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CreateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
......@@ -15,7 +15,9 @@ export class CreateComponent {
constructor(private apiService: ApiService) {}
private newWallet: WalletCreation;
private responseMessage: string;
private errorMessage: string;
private createWallet(password: string, network: string, folderPath: string, name: string, ) {
this.newWallet = new WalletCreation();
......@@ -26,6 +28,18 @@ export class CreateComponent {
this.apiService
.createWallet(this.newWallet)
.subscribe((response: string) => this.responseMessage = response);
.subscribe(
response => {
if (response.status >= 200 && response.status < 400){
this.responseMessage = response.json();
}
},
error => {
if (error.status >= 400) {
this.errorMessage = error;
console.log(this.errorMessage);
}
}
);
}
}
......@@ -10,8 +10,11 @@ import { WalletRecovery } from '../../shared/wallet-recovery'
export class RecoverComponent implements OnInit {
constructor(private apiService: ApiService) { }
private walletRecovery: WalletRecovery;
private responseBody: string;
private responseMessage: string;
private errorMessage: string;
ngOnInit() {
}
......@@ -26,7 +29,18 @@ export class RecoverComponent implements OnInit {
this.apiService
.recoverWallet(this.walletRecovery)
.subscribe((response: string) => this.responseBody = response,
() => console.log("recoverWallet() completed"));
.subscribe(
response => {
if (response.status >= 200 && response.status < 400) {
this.responseMessage = response;
}
},
error => {
if (error.status >= 400) {
this.errorMessage = error;
console.log(this.errorMessage);
}
}
);
}
}
\ No newline at end of file
<h1>Welcome to Breeze. Looks like you're new here.</h1>
<h1>Welcome to Breeze.</h1>
<p>
If you haven't used Breeze before, please create a new wallet.
</p>
......
......@@ -6,6 +6,7 @@ import 'rxjs/add/operator/catch';
import { WalletCreation } from '../wallet-creation';
import { WalletRecovery } from '../wallet-recovery';
import { WalletLoad } from '../wallet-load';
import { Mnemonic } from '../mnemonic';
/**
......@@ -20,13 +21,22 @@ export class ApiService {
private webApiUrl = 'http://localhost:5000/api/v1';
private headers = new Headers({'Content-Type': 'application/json'});
/**
* Gets available wallets at the default path
*/
getWalletFiles(): Observable<any> {
return this.http
.get(this.mockApiUrl + '/wallet/files')
.map((response: Response) => response);
}
/**
* Create a new wallet.
*/
createWallet(data: WalletCreation): Observable<any> {
return this.http
.post(this.webApiUrl + '/wallet/create/', JSON.stringify(data), {headers: this.headers})
.map(response => response.json());
.map((response: Response) => response);
}
/**
......@@ -35,17 +45,16 @@ export class ApiService {
recoverWallet(data: WalletRecovery): Observable<any> {
return this.http
.post(this.webApiUrl + '/wallet/recover/', JSON.stringify(data), {headers: this.headers})
.map(response => response.json());
.map((response: Response) => response);
}
/**
* Load a wallet
*/
loadWallet(password: string): Observable<any> {
loadWallet(data: WalletLoad): Observable<any> {
return this.http
.get(this.webApiUrl + '/wallet/load/', {headers: this.headers, body: JSON.stringify(password)})
.map(response => response.json())
.catch(this.handleError);
.post(this.webApiUrl + '/wallet/load/', JSON.stringify(data), {headers: this.headers})
.map((response: Response) => response);
}
/**
......@@ -54,8 +63,7 @@ export class ApiService {
getWalletStatus(): Observable<any> {
return this.http
.get(this.mockApiUrl + '/wallet/status')
.map((response:Response) => response.json())
.catch(this.handleError);
.map((response: Response) => response);
}
/**
......@@ -63,9 +71,8 @@ export class ApiService {
*/
getWalletBalance(): Observable<any> {
return this.http
.get(this.webApiUrl + '/wallet/balance')
.map((response:Response) => response.json())
.catch(this.handleError);
.get(this.mockApiUrl + '/wallet/balance')
.map((response: Response) => response);
}
/**
......@@ -73,9 +80,8 @@ export class ApiService {
*/
getWalletHistory(): Observable<any> {
return this.http
.get(this.webApiUrl + '/wallet/history')
.map((response:Response) => response.json())
.catch(this.handleError);
.get(this.mockApiUrl + '/wallet/history')
.map((response: Response) => response);
}
/**
......@@ -83,25 +89,7 @@ export class ApiService {
*/
getUnusedReceiveAddresses(): Observable<any> {
return this.http
.get(this.webApiUrl + '/wallet/receive')
.map((response:Response) => response.json())
.catch(this.handleError);
}
/**
* Handle errors from the API.
* @param error
*/
private handleError (error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
.get(this.mockApiUrl + '/wallet/receive')
.map((response: Response) => response);
}
}
export class WalletLoad {
password: string;
folderPath: string;
name: string;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
describe('HistoryComponent', () => {
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HistoryComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
......@@ -21,8 +21,17 @@ export class HistoryComponent {
private getWalletHistory() {
this.apiService.getWalletHistory()
.subscribe(
response => this.transactions = response.history,
error => this.errorMessage = <any>error
response => {
if (response.status >= 200 && response.status < 400) {
this.transactions = response.json().history;
}
},
error => {
if (error.status >= 400) {
this.errorMessage = <any>error;
console.log(this.errorMessage);
}
}
);
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DashboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
......@@ -21,14 +21,19 @@ export class DashboardComponent {
private getWalletBalance() {
this.apiService.getWalletBalance()
.subscribe(
response => this.balanceResponse = response,
error => this.errorMessage = <any>error,
() => this.setBalance()
);
}
private setBalance() {
response => {
if (response.status >= 200 && response.status < 400) {
this.balanceResponse = response.json();
this.confirmedBalance = this.balanceResponse.confirmed;
this.unconfirmedBalance = this.balanceResponse.unconfirmed;
}
},
error => {
if (error.status >= 400) {
this.errorMessage = <any>error;
console.log(this.errorMessage);
}
}
);
}
}
......@@ -6,6 +6,7 @@
<li class="nav-item"><a class="nav-link" routerLink="send">Send</a></li>
<li class="nav-item"><a class="nav-link" routerLink="receive">Receive</a></li>
<li class="nav-item"><a class="nav-link" routerLink="history">History</a></li>
<li class="nav-item"><a class="nav-link" (click)="logOut()">Logout</a></li>
</ul>
</div>
</nav>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MenuComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-menu',
......@@ -6,5 +7,9 @@ import { Component } from '@angular/core';
styleUrls: ['./menu.component.css'],
})
export class MenuComponent {
constructor(private router: Router) {}
private logOut() {
this.router.navigate(['/login']);
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReceiveComponent } from './receive.component';
describe('ReceiveComponent', () => {
let component: ReceiveComponent;
let fixture: ComponentFixture<ReceiveComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ReceiveComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ReceiveComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
......@@ -21,8 +21,17 @@ export class ReceiveComponent {
private getUnusedReceiveAddresses() {
this.apiService.getUnusedReceiveAddresses()
.subscribe(
response => this.addresses = response.addresses,
error => this.errorMessage = <any>error
response => {
if (response.status >= 200 && response.status < 400) {
this.addresses = response.json().addresses;
}
},
error => {
if (error.status >= 400) {
this.errorMessage = <any>error;
console.log(this.errorMessage);
}
}
);
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SendComponent } from './send.component';
describe('SendComponent', () => {
let component: SendComponent;
let fixture: ComponentFixture<SendComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SendComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SendComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WalletComponent } from './wallet.component';
describe('WalletComponent', () => {
let component: WalletComponent;
let fixture: ComponentFixture<WalletComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WalletComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WalletComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
......@@ -15,7 +15,7 @@ let mainWindow = null;
function createWindow () {
setTimeout(() => {
// Create the browser window.
mainWindow = new BrowserWindow({width: 1000, height: 600, frame: true, minWidth: 1000, minHeight: 600, icon: "./assets/images/stratis-tray.png"})
mainWindow = new BrowserWindow({width: 1000, height: 600, frame: true, minWidth: 1000, minHeight: 600, icon: "./src/assets/images/stratis-tray.png"})
mainWindow.loadURL(url.format({
pathname: 'localhost:4200',
......
......@@ -28,6 +28,28 @@ namespace Breeze.Api
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add service and create Policy to allow Cross-Origin Requests
services.AddCors
(
options =>
{
options.AddPolicy
(
"CorsPolicy",
builder =>
{
var allowedDomains = new[]{"http://localhost","http://localhost:4200"};
builder
.WithOrigins(allowedDomains)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}
);
});
// Add framework services.
services.AddMvc()
// add serializers for NBitcoin objects
......@@ -73,6 +95,8 @@ namespace Breeze.Api
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("CorsPolicy");
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
......
......@@ -59,6 +59,7 @@
},
"runtimes": {
"win10-x64": {},
"win7-x64": {}
"win7-x64": {},
"ubuntu.16.04-x64": {}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment