diff --git a/.gitignore b/.gitignore index 4c333e43..d808db34 100644 --- a/.gitignore +++ b/.gitignore @@ -278,6 +278,8 @@ package-lock.json npm-debug.log testem.log **/typings +**/main.js +**/main.js.map # e2e **/e2e/*.js diff --git a/Breeze.UI/.angular-cli.json b/Breeze.UI/.angular-cli.json deleted file mode 100644 index 8736e1e8..00000000 --- a/Breeze.UI/.angular-cli.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "project": { - "name": "Breeze", - "ejected": true - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": [ - "assets" - ], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "app", - "styles": [ - "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", - "styles.css" - ], - "scripts": [], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "lint": [ - { - "project": "src/tsconfig.app.json" - }, - { - "project": "src/tsconfig.spec.json" - }, - { - "project": "e2e/tsconfig.e2e.json" - } - ], - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "css", - "component": { - } - } -} diff --git a/Breeze.UI/angular.json b/Breeze.UI/angular.json new file mode 100644 index 00000000..63368f5b --- /dev/null +++ b/Breeze.UI/angular.json @@ -0,0 +1,149 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "breeze": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "tsConfig": "src/tsconfig.app.json", + "polyfills": "src/polyfills.ts", + "assets": [ + "src/assets" + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "dev": { + "optimization": false, + "outputHashing": "all", + "sourceMap": true, + "extractCss": true, + "namedChunks": false, + "aot": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": false, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.dev.ts" + } + ] + }, + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "breeze:build" + }, + "configurations": { + "dev": { + "browserTarget": "breeze:build:dev" + }, + "production": { + "browserTarget": "breeze:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "breeze:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills-test.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "scripts": [], + "styles": [ + "src/styles.css" + ], + "assets": [ + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "breeze-e2e": { + "root": "e2e", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "breeze:serve" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "e2e/tsconfig.e2e.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "breeze", + "schematics": { + "@schematics/angular:component": { + "prefix": "app", + "styleext": "css" + }, + "@schematics/angular:directive": { + "prefix": "app" + } + } +} diff --git a/Breeze.UI/e2e/app.e2e-spec.ts b/Breeze.UI/e2e/app.e2e-spec.ts deleted file mode 100644 index 81f1a279..00000000 --- a/Breeze.UI/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BreezePage } from './app.po'; - -describe('breeze App', function() { - let page: BreezePage; - - beforeEach(() => { - page = new BreezePage(); - }); - - it('title should be Breeze', () => { - page.navigateTo(); - expect(page.getTitle()).toEqual('Breeze'); - }); -}); diff --git a/Breeze.UI/e2e/app.po.ts b/Breeze.UI/e2e/app.po.ts deleted file mode 100644 index 5772c133..00000000 --- a/Breeze.UI/e2e/app.po.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { browser, element, by } from 'protractor'; - -export class BreezePage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('app-root h1')).getText(); -} - - getTitle() { - let title: string; - return browser.getTitle(); - } -} diff --git a/Breeze.UI/e2e/tsconfig.e2e.json b/Breeze.UI/e2e/tsconfig.e2e.json deleted file mode 100644 index e22c1ff7..00000000 --- a/Breeze.UI/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "typeRoots": [ - "../node_modules/@types" - ], - "types":[ - "jasmine", - "node" - ] - } -} \ No newline at end of file diff --git a/Breeze.UI/electron-builder.json b/Breeze.UI/electron-builder.json index a0a170f2..96ab92ae 100644 --- a/Breeze.UI/electron-builder.json +++ b/Breeze.UI/electron-builder.json @@ -1,18 +1,31 @@ { "appId": "com.stratisplatform.breeze", - "productName": "Breeze Wallet", + "productName": "Breeze", "publish": null, "directories": { - "app": "dist", - "output": "app-builds", - "buildResources": "dist/assets/images" + "output": "app-builds" }, + "files": [ + "**/*", + "!**/*.ts", + "!*.code-workspace", + "!LICENSE.md", + "!package.json", + "!package-lock.json", + "!src/", + "!e2e/", + "!angular.json", + "!_config.yml", + "!karma.conf.js", + "!tsconfig.json", + "!tslint.json" + ], "win": { "icon": "dist/assets/images/icon", "target": [ "nsis" ], - "artifactName": "${productName}-v${version}-setup-${os}-${env.arch}.${ext}" + "artifactName": "${productName}-v${version}-setup-${os}-${arch}.${ext}" }, "linux": { "icon": "dist/assets/images/", @@ -20,11 +33,12 @@ "deb", "tar.gz" ], - "synopsis": "Breeze Wallet: Stratis' dual-currency full block light wallet with a strong focus on privacy.", + "synopsis": "Breeze: Bitcoin and Stratis wallet focused on privacy.", "category": "Utility", "artifactName": "${productName}-v${version}-${os}-${arch}.${ext}" }, "mac": { + "icon": "dist/assets/images/icon.icns", "target": [ "dmg" ], @@ -36,17 +50,17 @@ "perMachine": true, "allowToChangeInstallationDirectory": true, "deleteAppDataOnUninstall": true, - "guid": "ed20df62-ff5a-4b83-a5e3-a361357c6e51", + "guid": "ef77eb40-01cf-4516-9da6-f3c6ba6854aa", "createDesktopShortcut": true, "createStartMenuShortcut": true, - "license": "../Licenses/breeze-license.txt" + "license": "src/assets/images/license_en.txt" }, "msi": { "warningsAsErrors": false }, "extraResources": [ "daemon/**/*", - "src/assets/images/breeze-logo-tray.png", + "src/assets/images/icon-tray.png", "src/assets/images/license_en.txt" ] } diff --git a/Breeze.UI/main.ts b/Breeze.UI/main.ts index 5a594250..7fb97662 100644 --- a/Breeze.UI/main.ts +++ b/Breeze.UI/main.ts @@ -14,17 +14,13 @@ * -storeDir : Location of the registrationHistory.json file; this is passed to the BreezeD as -storeDir ************************************************************************************************************************************************/ -const electron = require('electron'); - -// Module to control application life. -const app = electron.app; -// Module to create native browser window. -const BrowserWindow = electron.BrowserWindow; -const nativeImage = require('electron').nativeImage; - -const path = require('path'); -const url = require('url'); -const os = require('os'); +import { app, BrowserWindow, ipcMain, Menu, nativeImage, Tray } from 'electron'; +import * as path from 'path'; +import * as url from 'url'; +import * as os from 'os'; +if (os.arch() == 'arm') { + app.disableHardwareAcceleration(); +} let serve; let testnet = false; @@ -89,11 +85,9 @@ if (args.some(val => val.indexOf("--tumblerProtocol=") == 0 || val.indexOf("-tum tumblerProtocol = args.filter(val => val.indexOf("--tumblerProtocol=") == 0 || val.indexOf("-tumblerProtocol=") == 0)[0].split("=")[1]; } -if (serve) { - require('electron-reload')(__dirname, { - electron: require('${__dirname}/../../node_modules/electron') - }); -} +ipcMain.on('get-testnet', (event, arg) => { + event.returnValue = testnet; +}); require('electron-context-menu')({ showInspectElement: serve @@ -111,15 +105,19 @@ function createWindow() { frame: true, minWidth: 1200, minHeight: 650, - title: 'Breeze Wallet' + title: 'Breeze' }); - // and load the index.html of the app. - mainWindow.loadURL(url.format({ - pathname: path.join(__dirname, '/index.html'), - protocol: 'file:', - slashes: true - })); + if (serve) { + require('electron-reload')(__dirname, { }); + mainWindow.loadURL('http://localhost:4200'); + } else { + mainWindow.loadURL(url.format({ + pathname: path.join(__dirname, 'dist/index.html'), + protocol: 'file:', + slashes: true + })); + } if (serve) { mainWindow.webContents.openDevTools(); @@ -145,8 +143,8 @@ app.on('ready', function () { if (serve) { console.log('Breeze UI was started in development mode. This requires the user to be running the Breeze Daemon himself.') } else if (startDaemons) { - startBitcoinApi(); - startStratisApi(); + startBitcoinDaemon(); + startStratisDaemon(); } createTray(); createWindow(); @@ -157,7 +155,7 @@ app.on('ready', function () { // Quit when all windows are closed. app.on('window-all-closed', function () { - //The user doesn't have the option to create another window/wallet from the Electron menu, so there's + //The user doesn't have the option to create another window/wallet from the Electron menu, so there's //no point in keeping it there, so we simply quit the app. quit(); }); @@ -170,48 +168,62 @@ app.on('activate', function () { } }); -function closeBitcoinApi() { - if (!serve) { - const http1 = require('http'); - const options1 = { - hostname: 'localhost', - port: (global).bitcoinApiPort, - path: '/api/node/shutdown', - method: 'POST' - }; - const req = http1.request(options1, (res) => {}); - req.write(''); - req.end(); - } +function closeBitcoinDaemon() { + let http = require('http'); + let body = JSON.stringify({}); + + let request = new http.ClientRequest({ + method: 'POST', + hostname: 'localhost', + port: (global).bitcoinApiPort, + path: '/api/node/shutdown', + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body) + } + }) + + request.write('true'); + request.on('error', function (e) { }); + request.on('timeout', function (e) { request.abort(); }); + request.on('uncaughtException', function (e) { request.abort(); }); + request.end(body); }; -function closeStratisApi() { - if (!serve) { - const http2 = require('http'); - const options2 = { - hostname: 'localhost', - port: (global).stratisApiPort, - path: '/api/node/shutdown', - method: 'POST' - }; - const req = http2.request(options2, (res) => {}); - req.write(''); - req.end(); - } +function closeStratisDaemon() { + let http = require('http'); + let body = JSON.stringify({}); + + let request = new http.ClientRequest({ + method: 'POST', + hostname: 'localhost', + port: (global).stratisApiPort, + path: '/api/node/shutdown', + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body) + } + }) + + request.write('true'); + request.on('error', function (e) { }); + request.on('timeout', function (e) { request.abort(); }); + request.on('uncaughtException', function (e) { request.abort(); }); + request.end(body); }; -function startBitcoinApi() { +function startBitcoinDaemon() { let bitcoinProcess; const spawnBitcoin = require('child_process').spawn; // Start Breeze Bitcoin Daemon - let apiPath; + let daemonPath; if (os.platform() === 'win32') { - apiPath = path.resolve(__dirname, '..\\..\\resources\\daemon\\Breeze.Daemon.exe'); + daemonPath = path.resolve(__dirname, '..\\..\\resources\\daemon\\Breeze.Daemon.exe'); } else if (os.platform() === 'linux') { - apiPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); + daemonPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); } else { - apiPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); + daemonPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); } let commandLineArguments = []; @@ -224,23 +236,23 @@ function startBitcoinApi() { commandLineArguments.push("-testnet"); if(regtest) commandLineArguments.push("-regtest"); - + if (noTor) - commandLineArguments.push("-noTor"); + commandLineArguments.push("-noTor"); if (tumblerProtocol != null) - commandLineArguments.push("-tumblerProtocol=" + tumblerProtocol); - + commandLineArguments.push("-tumblerProtocol=" + tumblerProtocol); + commandLineArguments.push("-tumblebit"); commandLineArguments.push("-registration"); if (dataDir != null) - commandLineArguments.push("-datadir=" + dataDir); - + commandLineArguments.push("-datadir=" + dataDir); + if (storeDir != null) commandLineArguments.push("-storedir=" + storeDir); - + console.log("Starting Bitcoin daemon with parameters: " + commandLineArguments); - bitcoinProcess = spawnBitcoin(apiPath, commandLineArguments, { + bitcoinProcess = spawnBitcoin(daemonPath, commandLineArguments, { detached: false }); @@ -249,38 +261,38 @@ function startBitcoinApi() { }); } -function startStratisApi() { +function startStratisDaemon() { let stratisProcess; const spawnStratis = require('child_process').spawn; // Start Breeze Stratis Daemon - let apiPath = path.resolve(__dirname, 'assets//daemon//Breeze.Daemon'); + let daemonPath = path.resolve(__dirname, 'assets//daemon//Breeze.Daemon'); if (os.platform() === 'win32') { - apiPath = path.resolve(__dirname, '..\\..\\resources\\daemon\\Breeze.Daemon.exe'); + daemonPath = path.resolve(__dirname, '..\\..\\resources\\daemon\\Breeze.Daemon.exe'); } else if (os.platform() === 'linux') { - apiPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); + daemonPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); } else { - apiPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); + daemonPath = path.resolve(__dirname, '..//..//resources//daemon//Breeze.Daemon'); } - + let commandLineArguments = []; commandLineArguments.push("-stratis"); commandLineArguments.push("-apiport=" + (global).stratisApiPort); if(stratisPort != null) commandLineArguments.push("-port=" + stratisPort); - + commandLineArguments.push("-light"); if(testnet) commandLineArguments.push("-testnet"); if(regtest) commandLineArguments.push("-regtest"); - + commandLineArguments.push("-registration"); if (dataDir != null) commandLineArguments.push("-datadir=" + dataDir); - + console.log("Starting Stratis daemon with parameters: " + commandLineArguments); - stratisProcess = spawnStratis(apiPath, commandLineArguments, { + stratisProcess = spawnStratis(daemonPath, commandLineArguments, { detached: false }); @@ -291,9 +303,6 @@ function startStratisApi() { function createTray() { // Put the app in system tray - const Menu = electron.Menu; - const Tray = electron.Tray; - let trayIcon; if (serve) { trayIcon = nativeImage.createFromPath('./src/assets/images/breeze-logo-tray.png'); @@ -338,25 +347,20 @@ function writeLog(msg) { }; function createMenu() { - const Menu = electron.Menu; - - // Create the Application's main menu const menuTemplate = [{ - label: 'Application', + label: app.getName(), submenu: [ - { label: 'About Application', selector: 'orderFrontStandardAboutPanel:' }, - { type: 'separator' }, - { label: 'Quit', accelerator: 'Command+Q', click: function() { quit(); }} + { label: "About " + app.getName(), selector: "orderFrontStandardAboutPanel:" }, + { label: "Quit", accelerator: "Command+Q", click: function() { app.quit(); }} ]}, { - label: 'Edit', + label: "Edit", submenu: [ - { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, - { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, - { type: 'separator' }, - { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, - { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, - { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, - { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' } + { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, + { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, + { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, + { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, + { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, + { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" } ]} ]; @@ -364,7 +368,7 @@ function createMenu() { }; const quit = () => { - closeBitcoinApi(); - closeStratisApi(); + closeBitcoinDaemon(); + closeStratisDaemon(); app.quit(); }; diff --git a/Breeze.UI/package.js b/Breeze.UI/package.js deleted file mode 100644 index 0ddbec04..00000000 --- a/Breeze.UI/package.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; - -var packager = require('electron-packager'); -const pkg = require('./package.json'); -const argv = require('minimist')(process.argv.slice(1)); - -const appName = argv.name || pkg.name; -const buildVersion = pkg.version || '1.0'; -const shouldUseAsar = argv.asar || false; -const shouldBuildAll = argv.all || false; -const arch = argv.arch || 'all'; -const platform = argv.platform || 'darwin'; -const path = argv.path || 'app-builds'; - -const DEFAULT_OPTS = { - dir: './dist', - name: "Breeze", - asar: shouldUseAsar, - buildVersion: buildVersion -}; - - -pack(platform, arch, path, function done(err, appPath) { - if (err) { - console.log(err); - } else { - console.log('Application packaged successfully!', appPath); - } - -}); - -function pack(plat, arch, path, cb) { - - // there is no darwin ia32 electron - if (plat === 'darwin' && arch === 'ia32') return; - - let icon = 'src/assets/images/icon'; - - if (icon) { - DEFAULT_OPTS.icon = icon + (() => { - let extension = '.png'; - if (plat === 'darwin') { - extension = '.icns'; - } else if (plat === 'win32') { - extension = '.ico'; - } - return extension; - })(); - } - - const opts = Object.assign({}, DEFAULT_OPTS, { - platform: plat, - arch, - prune: true, - overwrite: true, - all: shouldBuildAll, - out: path - }); - - console.log(opts) - packager(opts, cb); -} diff --git a/Breeze.UI/package.json b/Breeze.UI/package.json index 2f3e4eb2..530c431b 100644 --- a/Breeze.UI/package.json +++ b/Breeze.UI/package.json @@ -1,17 +1,19 @@ { - "name": "Breeze", - "description": "Graphical User Interface for Breeze Wallet.", - "version": "1.0.1", + "name": "breeze", + "description": "Breeze Wallet", + "version": "1.1.0", "author": { - "name": "Stratis Group Ltd", + "name": "Stratis Group Ltd.", "email": "support@stratisplatform.com" }, "license": "MIT", - "homepage": "https://github.com/BreezeHub/BreezeProject", + "homepage": "https://github.com/breezehub/BreezeProject", "keywords": [ "breeze", "ui", "stratis", + "privacy", + "bitcoin", "angular", "electron", "typescript", @@ -19,108 +21,86 @@ "bootstrap" ], "main": "main.js", + "private": true, "scripts": { + "postinstall": "npm run postinstall:electron && electron-builder install-app-deps", + "postinstall:web": "node postinstall-web", + "postinstall:electron": "node postinstall", "ng": "ng", - "lint": "ng lint", - "start": "npm-run-all --parallel \"webpack:watch\" \"electron:serve -- {@}\" --", - "webpack:watch": "webpack --watch", - "build:electron:main": "tsc main.ts --outDir dist && copyfiles package.json dist && cd dist && npm install --prod && cd ..", - "build": "webpack --display-error-details && npm run build:electron:main", - "build:prod": "cross-env NODE_ENV=production npm run build", + "ng:serve": "ng serve", + "ng:serve:web": "npm run postinstall:web && ng serve -o", + "start": "npm run postinstall:electron && npm-run-all -p ng:serve electron:serve", + "mainnet": "npm start", + "testnet": "npm run postinstall:electron && npm-run-all -p ng:serve electron:serve:testnet", + "build": "npm run postinstall:electron && npm run electron:serve-tsc && ng build", + "build:dev": "npm run build -- -c dev", + "build:prod": "npm run build -- -c production", "build:styling": "node-sass --output-style expanded --source-map true --precision 6 ./src/scss/bootstrap.scss ./src/styles.css", - "electron:serve": "npm run build:electron:main && electron ./dist --serve", - "electron:test": "electron ./dist", - "electron:dev": "npm run build && electron ./dist", - "electron:prod": "npm run build:prod && electron ./dist", - "package:linux": "npm run build:prod && node package.js --platform=linux --arch=x64", - "package:windows": "npm run build:prod && node package.js --platform=win32 --arch=ia32", - "package:winsetup": "npm run build:prod && npx electron-builder build --windows", - "package:mac": "npm run build:prod && node package.js --platform=darwin --arch=x64", - "test": "karma start ./karma.conf.js", - "pree2e": "webdriver-manager update --standalone false --gecko false --quiet && npm run build", - "e2e": "protractor ./protractor.conf.js" + "electron:serve-tsc": "tsc -p tsconfig-serve.json", + "electron:serve": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve", + "electron:serve:testnet": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve --testnet", + "electron:serve:sidechain": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve --sidechain", + "electron:serve:sidechain:testnet": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve --sidechain --testnet", + "electron:local": "npm run build:prod && electron .", + "package:linux": "npm run build:prod && electron-builder build --linux", + "package:linuxarm": "npm run build:prod && electron-builder build --linux --armv7l", + "package:windows": "npm run build:prod && electron-builder build --windows", + "package:windows86": "npm run build:prod && electron-builder build --windows --ia32", + "package:windows64": "npm run build:prod && electron-builder build --windows --x64", + "package:mac": "npm run build:prod && electron-builder build --mac", + "test": "npm run postinstall:web && ng test", + "e2e": "npm run postinstall:web && ng e2e" }, - "private": true, "dependencies": { - "@angular/animations": "5.1.0", - "@angular/common": "5.1.0", - "@angular/compiler": "5.1.0", - "@angular/core": "5.1.0", - "@angular/forms": "5.1.0", - "@angular/http": "5.1.0", - "@angular/platform-browser": "5.1.0", - "@angular/platform-browser-dynamic": "5.1.0", - "@angular/platform-server": "5.1.0", - "@angular/router": "5.1.0", - "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.6", - "bootstrap": "4.0.0-beta", - "core-js": "2.5.1", - "electron-context-menu": "0.9.1", - "enhanced-resolve": "3.4.1", - "ngx-clipboard": "9.0.0", - "ngx-bootstrap": "2.0.0-beta.10", - "rxjs": "5.5.5", - "zone.js": "0.8.18" + "electron-context-menu": "0.11.0", + "ngx-pagination": "3.2.1", + "ngx-qrcode2": "0.0.9" }, "devDependencies": { - "@angular/cli": "^1.7.4", - "@angular/compiler-cli": "5.1.0", - "@angular/language-service": "5.1.0", - "@types/core-js": "0.9.36", - "@types/jasmine": "2.6.0", - "@types/node": "7.0.7", - "autoprefixer": "7.2.1", - "circular-dependency-plugin": "4.3.0", - "codelyzer": "4.0.1", - "copy-webpack-plugin": "4.2.3", - "copyfiles": "1.2.0", - "cross-env": "5.1.1", - "css-loader": "0.28.7", - "cssnano": "3.10.0", - "electron": "1.7.13", - "electron-builder": "19.48.2", - "electron-packager": "10.1.0", - "electron-reload": "1.2.2", - "exports-loader": "0.6.4", - "file-loader": "1.1.5", - "html-loader": "0.5.1", - "html-webpack-plugin": "2.30.1", - "istanbul-instrumenter-loader": "3.0.0", - "jasmine-core": "2.8.0", + "@angular-devkit/build-angular": "0.13.6", + "@angular/animations": "7.2.10", + "@angular/cli": "7.3.6", + "@angular/common": "7.2.10", + "@angular/compiler": "7.2.10", + "@angular/compiler-cli": "7.2.10", + "@angular/core": "7.2.10", + "@angular/forms": "7.2.10", + "@angular/http": "7.2.10", + "@angular/language-service": "7.2.10", + "@angular/platform-browser": "7.2.10", + "@angular/platform-browser-dynamic": "7.2.10", + "@angular/router": "7.2.10", + "@ngx-translate/core": "11.0.1", + "@ngx-translate/http-loader": "4.0.0", + "@ng-bootstrap/ng-bootstrap": "4.1.0", + "@types/jasmine": "2.8.7", + "@types/jasminewd2": "2.0.6", + "@types/node": "10.12.23", + "bootstrap": "4.3.1", + "codelyzer": "4.5.0", + "core-js": "2.6.5", + "electron": "4.1.1", + "electron-builder": "20.39.0", + "electron-reload": "1.4.0", + "jasmine-core": "3.3.0", "jasmine-spec-reporter": "4.2.1", - "jquery": "3.2.1", - "json-loader": "0.5.7", - "karma": "1.7.1", + "karma": "3.1.4", "karma-chrome-launcher": "2.2.0", - "karma-cli": "1.0.1", - "karma-coverage-istanbul-reporter": "1.3.0", - "karma-jasmine": "1.1.1", - "karma-jasmine-html-reporter": "0.2.2", - "karma-sourcemap-loader": "0.3.7", - "less-loader": "4.0.5", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "node-sass": "4.7.2", - "npm-run-all": "4.1.2", - "npx": "9.7.1", - "postcss-custom-properties": "7.0.0", - "postcss-loader": "2.0.9", - "postcss-url": "7.3.0", - "protractor": "5.2.1", - "raw-loader": "0.5.1", - "sass-loader": "6.0.6", - "script-loader": "0.7.2", - "source-map-loader": "0.2.3", - "style-loader": "0.19.0", - "stylus-loader": "3.0.1", - "ts-node": "3.3.0", - "tslint": "5.8.0", - "typescript": "2.6.2", - "uglifyjs-webpack-plugin": "1.1.2", - "url-loader": "0.6.2", - "webdriver-manager": "12.0.6", - "webpack": "3.10.0", - "webpack-concat-plugin": "1.4.2", - "webpack-dev-server": "2.9.7" + "karma-coverage-istanbul-reporter": "2.0.5", + "karma-jasmine": "2.0.1", + "karma-jasmine-html-reporter": "1.4.0", + "ngx-bootstrap": "3.2.0", + "ngx-clipboard": "12.0.0", + "ngx-electron": "2.1.1", + "node-sass": "4.11.0", + "npm-run-all": "4.1.5", + "protractor": "5.4.2", + "rxjs": "6.4.0", + "ts-node": "8.0.3", + "tslint": "5.14.0", + "typescript": "3.2.4", + "wait-on": "3.2.0", + "webdriver-manager": "12.1.1", + "zone.js": "0.8.29" } } diff --git a/Breeze.UI/postinstall-web.js b/Breeze.UI/postinstall-web.js new file mode 100644 index 00000000..7dce7645 --- /dev/null +++ b/Breeze.UI/postinstall-web.js @@ -0,0 +1,16 @@ +// Allow angular using electron module (native node modules) +const fs = require('fs'); +const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js'; + +fs.readFile(f_angular, 'utf8', function (err, data) { + if (err) { + return console.log(err); + } + var result = data.replace(/target: "electron-renderer",/g, ''); + var result = result.replace(/target: "web",/g, ''); + var result = result.replace(/return \{/g, 'return {target: "web",'); + + fs.writeFile(f_angular, result, 'utf8', function (err) { + if (err) return console.log(err); + }); +}); \ No newline at end of file diff --git a/Breeze.UI/postinstall.js b/Breeze.UI/postinstall.js new file mode 100644 index 00000000..1fb12759 --- /dev/null +++ b/Breeze.UI/postinstall.js @@ -0,0 +1,16 @@ +// Allow angular using electron module (native node modules) +const fs = require('fs'); +const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js'; + +fs.readFile(f_angular, 'utf8', function (err, data) { + if (err) { + return console.log(err); + } + var result = data.replace(/target: "electron-renderer",/g, ''); + var result = result.replace(/target: "web",/g, ''); + var result = result.replace(/return \{/g, 'return {target: "electron-renderer",'); + + fs.writeFile(f_angular, result, 'utf8', function (err) { + if (err) return console.log(err); + }); +}); \ No newline at end of file diff --git a/Breeze.UI/protractor.conf.js b/Breeze.UI/protractor.conf.js deleted file mode 100644 index 16fdc5ba..00000000 --- a/Breeze.UI/protractor.conf.js +++ /dev/null @@ -1,36 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 25000, - getPageTimeout: 15000, - delayBrowserTimeInSeconds: 0, - specs: [ - './e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome', - chromeOptions: { - binary: './node_modules/electron/dist/electron.exe', - args: ['--test-type=webdriver', 'app=dist/main.js'] - } - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine2', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - beforeLaunch: function() { - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - }, - onPrepare() { - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; diff --git a/Breeze.UI/src/app/app-routing.module.ts b/Breeze.UI/src/app/app-routing.module.ts index 1bf5c684..56448d4a 100644 --- a/Breeze.UI/src/app/app-routing.module.ts +++ b/Breeze.UI/src/app/app-routing.module.ts @@ -1,18 +1,18 @@ -import { NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; - import { LoginComponent } from './login/login.component'; +import { AppComponent } from './app.component'; const routes: Routes = [ - { path: '', redirectTo: 'login', pathMatch: 'full'}, - { path: 'login', component: LoginComponent}, - { path: 'setup', loadChildren: 'app/setup/setup.module#SetupModule'}, - { path: 'wallet', loadChildren: 'app/wallet/wallet.module#WalletModule'} + { path: 'app', component: AppComponent}, + { path: 'login', component: LoginComponent }, + { path: '', redirectTo: 'app', pathMatch: 'full' }, + { path: '**', redirectTo: 'app', pathMatch: 'full' } ]; @NgModule({ - imports: [ RouterModule.forRoot(routes, {useHash: true}) ], + imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) -export class AppRoutingModule {} +export class AppRoutingModule { } diff --git a/Breeze.UI/src/app/app.component.ts b/Breeze.UI/src/app/app.component.ts index 352f43cc..0b1dece1 100644 --- a/Breeze.UI/src/app/app.component.ts +++ b/Breeze.UI/src/app/app.component.ts @@ -1,13 +1,13 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Title } from '@angular/platform-browser'; - -import { ApiService } from './shared/services/api.service'; - import { remote } from 'electron'; +import { Subscription } from 'rxjs'; +import { retryWhen, delay, tap } from 'rxjs/operators'; -import 'rxjs/add/operator/retryWhen'; -import 'rxjs/add/operator/delay'; +import { ApiService } from './shared/services/api.service'; +import { NodeStatus } from './shared/classes/node-status'; +import { GlobalService } from './shared/services/global.service'; @Component({ selector: 'app-root', @@ -16,38 +16,50 @@ import 'rxjs/add/operator/delay'; }) export class AppComponent implements OnInit { - private errorMessage: any; - private responseMessage: any; public loading = true; + public loadingFailed = false; + private readonly MaxRetryCount = 50; + private readonly TryDelayMilliseconds = 3000; + private subscription: Subscription; - constructor(private router: Router, private apiService: ApiService, private titleService: Title) {} + constructor(private router: Router, private apiService: ApiService, private titleService: Title, private globalService: GlobalService) {} ngOnInit() { this.setTitle(); - this.apiService - .getWalletFiles() - .retryWhen(errors => errors.delay(2000)) - .subscribe(() => this.checkStratisDaemon()); + this.tryStart(); } - private checkStratisDaemon() { - this.apiService - .getStratisWalletFiles() - .retryWhen(errors => errors.delay(2000)) - .subscribe(() => this.startApp()); - } + private tryStart() { + let retry = 0; + const stream$ = this.apiService.getNodeStatus(true).pipe( + retryWhen(errors => + errors.pipe(delay(this.TryDelayMilliseconds)).pipe( + tap(errorStatus => { + if (retry++ === this.MaxRetryCount) { + throw errorStatus; + } + console.log(`Retrying ${retry}...`); + }) + ) + ) + ); - private startApp() { - this.loading = false; - this.router.navigate(['/login']); + this.subscription = stream$.subscribe( + (data: NodeStatus) => { + this.loading = false; + this.router.navigate(['login']) + }, (error: any) => { + console.log('Failed to start wallet'); + this.loading = false; + this.loadingFailed = true; + } + ) } - private setTitle() { - const applicationName = 'Breeze Wallet'; - const applicationVersion = remote.app.getVersion(); - const newTitle = `${applicationName} ${applicationVersion}`; + let applicationName = "Breeze Wallet"; + let applicationVersion = this.globalService.getApplicationVersion(); + let newTitle = applicationName + " " + applicationVersion; this.titleService.setTitle(newTitle); } - } diff --git a/Breeze.UI/src/app/app.module.ts b/Breeze.UI/src/app/app.module.ts index 3c1efe00..c9ddbe83 100644 --- a/Breeze.UI/src/app/app.module.ts +++ b/Breeze.UI/src/app/app.module.ts @@ -1,78 +1,36 @@ import { NgModule } from '@angular/core'; -import { BrowserModule, Title } from '@angular/platform-browser'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ClipboardModule } from 'ngx-clipboard'; -import { RouterModule, RouteReuseStrategy } from '@angular/router'; +import { RouteReuseStrategy } from '@angular/router'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { SharedModule } from './shared/shared.module'; import { AppRoutingModule } from './app-routing.module'; +import { BrowserModule } from '@angular/platform-browser'; +import { SetupModule } from './setup/setup.module'; +import { WalletModule } from './wallet/wallet.module'; import { AppComponent } from './app.component'; import { LoginComponent } from './login/login.component'; -import { GenericModalComponent } from './shared/components/generic-modal/generic-modal.component'; -import { ConfirmDialogComponent } from './shared/components/confirm-dialog/confirm-dialog.component'; -import { ConnectionModalComponent } from './shared/components/connection-modal/connection-modal.component'; -import { ApiService } from './shared/services/api.service'; -import { GlobalService } from './shared/services/global.service'; -import { Log } from './shared/services/logger.service'; -import { TumblebitService } from './wallet/tumblebit/tumblebit.service'; -import { ModalService } from './shared/services/modal.service'; -import { LicenseService } from './shared/services/license.service'; - -import { SendComponent } from './wallet/send/send.component'; -import { SendConfirmationComponent } from './wallet/send/send-confirmation/send-confirmation.component'; -import { ReceiveComponent } from './wallet/receive/receive.component'; -import { TransactionDetailsComponent } from './wallet/transaction-details/transaction-details.component'; -import { PasswordConfirmationComponent } from './wallet/tumblebit/password-confirmation/password-confirmation.component'; -import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout-confirmation.component'; -import { LicenseAgreementComponent } from './wallet/license-agreement/license-agreement.component'; - -import { CustomReuseStrategy } from './reuse-strategy'; +import { CustomReuseStrategy } from './shared/reuse-strategy/reuse-strategy'; +import { ApiInterceptor } from './shared/http-interceptors/api-interceptor'; @NgModule({ imports: [ - AppRoutingModule, BrowserModule, - BrowserAnimationsModule, - ClipboardModule, - ReactiveFormsModule, - FormsModule, - HttpModule, - RouterModule, - NgbModule.forRoot(), - SharedModule.forRoot() + HttpClientModule, + SharedModule, + SetupModule, + WalletModule, + AppRoutingModule ], declarations: [ AppComponent, - GenericModalComponent, - ConnectionModalComponent, - ConfirmDialogComponent, - LoginComponent, - LogoutConfirmationComponent, - PasswordConfirmationComponent, - SendComponent, - SendConfirmationComponent, - ReceiveComponent, - TransactionDetailsComponent, - LicenseAgreementComponent + LoginComponent ], - entryComponents: [ - PasswordConfirmationComponent, - GenericModalComponent, - ConnectionModalComponent, - ConfirmDialogComponent, - SendComponent, - SendConfirmationComponent, - ReceiveComponent, - TransactionDetailsComponent, - LogoutConfirmationComponent + providers: [ + { provide: RouteReuseStrategy, useClass: CustomReuseStrategy }, + { provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} ], - providers: [ ApiService, GlobalService, ModalService, Title, TumblebitService, LicenseService, - { provide: RouteReuseStrategy, useClass: CustomReuseStrategy } ], bootstrap: [ AppComponent ] }) diff --git a/Breeze.UI/src/app/login/login.component.ts b/Breeze.UI/src/app/login/login.component.ts index 4695aad9..d6b6382f 100644 --- a/Breeze.UI/src/app/login/login.component.ts +++ b/Breeze.UI/src/app/login/login.component.ts @@ -55,42 +55,19 @@ export class LoginComponent implements OnInit { this.apiService.getWalletFiles() .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - const responseMessage = response.json(); - this.wallets = responseMessage.walletsFiles; - this.globalService.setWalletPath(responseMessage.walletsPath); - if (this.wallets.length > 0) { - this.hasWallet = true; - for (const wallet in this.wallets) { - if (this.wallets.hasOwnProperty(wallet)) { - this.wallets[wallet] = this.wallets[wallet].slice(0, -12); - } - } - this.updateWalletFileDisplay(this.wallets[0]); - } else { - this.hasWallet = false; - } - } - }, - error => { - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get wallet files. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal( - Error.toDialogOptionsWithFallbackMsg( - error, null, 'Failed to get wallet files. Reason: API returned a bad request but message was not specified.')); + this.wallets = response.walletsFiles; + this.globalService.setWalletPath(response.walletsPath); + if (this.wallets.length > 0) { + this.hasWallet = true; + for (let wallet in this.wallets) { + this.wallets[wallet] = this.wallets[wallet].slice(0, -12); } + } else { + this.hasWallet = false; } } - ); - } - - private updateWalletFileDisplay(walletName: string) { - this.openWalletForm.patchValue({ selectWallet: walletName }) + ) + ; } private onCreateClicked() { @@ -115,87 +92,44 @@ export class LoginComponent implements OnInit { } private loadWallets(walletLoad: WalletLoad) { + this.loadBitcoinWallet(walletLoad); + } + + private loadBitcoinWallet(walletLoad: WalletLoad) { this.apiService.loadBitcoinWallet(walletLoad) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - this.globalService.setWalletName(walletLoad.name); - } + this.loadStratisWallet(walletLoad); }, error => { this.isDecrypting = false; - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to load Bitcoin wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } - } - }, - () => this.loadStratisWallet(walletLoad) - ); + } + ) + ; } private loadStratisWallet(walletLoad: WalletLoad) { this.apiService.loadStratisWallet(walletLoad) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - // Navigate to the wallet section - this.router.navigate(['/wallet']); - } + this.router.navigate(['wallet/dashboard']); }, error => { this.isDecrypting = false; - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to load Stratis wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } - } } - ); + ) + ; } private getCurrentNetwork() { - const walletInfo = new WalletInfo(this.globalService.getWalletName()) - this.apiService.getGeneralInfoOnce(walletInfo) + this.apiService.getNodeStatus() .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - const responseMessage = response.json(); - this.globalService.setNetwork(responseMessage.network); - if (responseMessage.network === 'Main') { - this.globalService.setCoinName('Bitcoin'); - this.globalService.setCoinUnit('BTC'); - } else if (responseMessage.network === 'TestNet') { - this.globalService.setCoinName('TestBitcoin'); - this.globalService.setCoinUnit('TBTC'); - } else if (responseMessage.network === 'RegTest') { - this.globalService.setCoinName('TestBitcoin'); - this.globalService.setCoinUnit('TBTC'); - } - } - }, - error => { - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get general wallet information. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } - } + let responseMessage = response; + this.globalService.setCoinUnit(responseMessage.coinTicker); + this.globalService.setNetwork(responseMessage.network); } - ); + ) + ; } } diff --git a/Breeze.UI/src/app/setup/create/confirm-mnemonic/confirm-mnemonic.component.ts b/Breeze.UI/src/app/setup/create/confirm-mnemonic/confirm-mnemonic.component.ts index cb011c1f..fcb3790a 100644 --- a/Breeze.UI/src/app/setup/create/confirm-mnemonic/confirm-mnemonic.component.ts +++ b/Breeze.UI/src/app/setup/create/confirm-mnemonic/confirm-mnemonic.component.ts @@ -1,17 +1,12 @@ import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; +import { FormGroup, Validators, FormBuilder } from '@angular/forms'; import { Router, ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; -import { GlobalService } from '../../../shared/services/global.service'; import { ApiService } from '../../../shared/services/api.service'; import { ModalService } from '../../../shared/services/modal.service'; import { WalletCreation } from '../../../shared/classes/wallet-creation'; -import { Error } from '../../../shared/classes/error'; - -import { Subscription } from 'rxjs/Subscription'; -import { Subscribable } from 'rxjs/Observable'; - import { SecretWordIndexGenerator } from './secret-word-index-generator'; @Component({ @@ -23,61 +18,29 @@ export class ConfirmMnemonicComponent implements OnInit { public secretWordIndexGenerator = new SecretWordIndexGenerator(); - formErrors = { - 'word1': '', - 'word2': '', - 'word3': '' - }; - - validationMessages = { - 'word1': { - 'required': 'This secret word is required.', - 'minlength': 'A secret word must be at least one character long', - 'maxlength': 'A secret word can not be longer than 24 characters', - 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' - }, - 'word2': { - 'required': 'This secret word is required.', - 'minlength': 'A secret word must be at least one character long', - 'maxlength': 'A secret word can not be longer than 24 characters', - 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' - }, - 'word3': { - 'required': 'This secret word is required.', - 'minlength': 'A secret word must be at least one character long', - 'maxlength': 'A secret word can not be longer than 24 characters', - 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' - } - }; - private subscription: Subscription; + constructor(private apiService: ApiService, private genericModalService: ModalService, private route: ActivatedRoute, private router: Router, private fb: FormBuilder) { + this.buildMnemonicForm(); + } private newWallet: WalletCreation; + private subscription: Subscription; public mnemonicForm: FormGroup; - public matchError = ''; + public matchError: string = ""; public isCreating: boolean; - constructor( - private globalService: GlobalService, - private apiService: ApiService, - private genericModalService: ModalService, - private route: ActivatedRoute, - private router: Router, - private fb: FormBuilder) { - this.buildMnemonicForm(); - } - ngOnInit() { this.subscription = this.route.queryParams.subscribe(params => { this.newWallet = new WalletCreation( - params['name'], - params['mnemonic'], - params['password'] + params["name"], + params["mnemonic"], + params["password"], + params["passphrase"] ) }); } private buildMnemonicForm(): void { this.mnemonicForm = this.fb.group({ - 'word1': ['', + "word1": ["", Validators.compose([ Validators.required, Validators.minLength(1), @@ -85,7 +48,7 @@ export class ConfirmMnemonicComponent implements OnInit { Validators.pattern(/^[a-zA-Z]*$/) ]) ], - 'word2': ['', + "word2": ["", Validators.compose([ Validators.required, Validators.minLength(1), @@ -93,7 +56,7 @@ export class ConfirmMnemonicComponent implements OnInit { Validators.pattern(/^[a-zA-Z]*$/) ]) ], - 'word3': ['', + "word3": ["", Validators.compose([ Validators.required, Validators.minLength(1), @@ -113,42 +76,61 @@ export class ConfirmMnemonicComponent implements OnInit { if (!this.mnemonicForm) { return; } const form = this.mnemonicForm; for (const field in this.formErrors) { - if (!this.formErrors.hasOwnProperty(field)) { continue; } this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { - if (control.errors.hasOwnProperty(key)) { - this.formErrors[field] += messages[key] + ' '; - } + this.formErrors[field] += messages[key] + ' '; } } } - this.matchError = ''; + this.matchError = ""; } + formErrors = { + 'word1': '', + 'word2': '', + 'word3': '' + }; + + validationMessages = { + 'word1': { + 'required': 'This secret word is required.', + 'minlength': 'A secret word must be at least one character long', + 'maxlength': 'A secret word can not be longer than 24 characters', + 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' + }, + 'word2': { + 'required': 'This secret word is required.', + 'minlength': 'A secret word must be at least one character long', + 'maxlength': 'A secret word can not be longer than 24 characters', + 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' + }, + 'word3': { + 'required': 'This secret word is required.', + 'minlength': 'A secret word must be at least one character long', + 'maxlength': 'A secret word can not be longer than 24 characters', + 'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.' + } + }; + public onConfirmClicked() { + this.checkMnemonic(); if (this.checkMnemonic()) { this.isCreating = true; - this.createWallets(this.newWallet); + this.createWallet(this.newWallet); } } public onBackClicked() { - this.router.navigate( - ['/setup/create/show-mnemonic'], - { queryParams : { - name: this.newWallet.name, - mnemonic: this.newWallet.mnemonic, - password: this.newWallet.password - }}); + this.router.navigate(['/setup/create/show-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password, passphrase: this.newWallet.passphrase }}); } private checkMnemonic(): boolean { - const mnemonic = this.newWallet.mnemonic; - const mnemonicArray = mnemonic.split(' '); + let mnemonic = this.newWallet.mnemonic; + let mnemonicArray = mnemonic.split(" "); if (this.mnemonicForm.get('word1').value.trim() === mnemonicArray[this.secretWordIndexGenerator.index1] && this.mnemonicForm.get('word2').value.trim() === mnemonicArray[this.secretWordIndexGenerator.index2] && @@ -160,63 +142,29 @@ export class ConfirmMnemonicComponent implements OnInit { } } - private createWallets(wallet: WalletCreation) { - this.apiService - .createBitcoinWallet(wallet) + private createWallet(wallet: WalletCreation) { + this.apiService.createStratisWallet(wallet) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - // Bitcoin wallet created - } + this.createBitcoinWallet(wallet); }, error => { - console.log(error); this.isCreating = false; - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to create Bitcoin wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - this.router.navigate(['/setup/create']); - } - } - }, - () => this.createStratisWallet(wallet) + } ) ; } - private createStratisWallet(wallet: WalletCreation) { - this.apiService - .createStratisWallet(wallet) + private createBitcoinWallet(wallet: WalletCreation) { + this.apiService.createBitcoinWallet(wallet) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - this.genericModalService.openModal( - { - title: 'Wallet Created', - body: 'Your wallet has been created.
Keep your secret words and password safe!' - }); - this.router.navigate(['']); - } + this.genericModalService.openModal("Wallet Created", "Your wallet has been created.
Keep your secret words, password and passphrase safe!"); + this.router.navigate(['']); + this.createBitcoinWallet(wallet); }, error => { this.isCreating = false; - console.log(error); - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to create Stratis wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - this.router.navigate(['/setup/create']); - } - } } ) ; diff --git a/Breeze.UI/src/app/setup/create/create.component.html b/Breeze.UI/src/app/setup/create/create.component.html index 4074834c..67c8e5ff 100644 --- a/Breeze.UI/src/app/setup/create/create.component.html +++ b/Breeze.UI/src/app/setup/create/create.component.html @@ -25,13 +25,21 @@

Create a new wallet

{{ formErrors.walletPasswordConfirmation }}
-
- +
+ (optional) + +
{{ formErrors.walletPassphrase }}
+
+ +
+
diff --git a/Breeze.UI/src/app/setup/create/create.component.ts b/Breeze.UI/src/app/setup/create/create.component.ts index e4b887aa..98d6ddeb 100644 --- a/Breeze.UI/src/app/setup/create/create.component.ts +++ b/Breeze.UI/src/app/setup/create/create.component.ts @@ -9,8 +9,6 @@ import { ModalService } from '../../shared/services/modal.service'; import { PasswordValidationDirective } from '../../shared/directives/password-validation.directive'; import { WalletCreation } from '../../shared/classes/wallet-creation'; -import { Mnemonic } from '../../shared/classes/mnemonic'; -import { Error } from '../../shared/classes/error'; @Component({ // tslint:disable-next-line:component-selector @@ -27,6 +25,7 @@ export class CreateComponent implements OnInit { formErrors = { 'walletName': '', + 'walletPassphrase': '', 'walletPassword': '', 'walletPasswordConfirmation': '' }; @@ -50,9 +49,7 @@ export class CreateComponent implements OnInit { }; constructor( - private globalService: GlobalService, private apiService: ApiService, - private genericModalService: ModalService, private router: Router, private fb: FormBuilder) { this.buildCreateForm(); @@ -72,6 +69,7 @@ export class CreateComponent implements OnInit { Validators.pattern(/^[a-zA-Z0-9]*$/) ]) ], + "walletPassphrase" : [""], 'walletPassword': ['', Validators.required, // Validators.compose([ @@ -109,7 +107,7 @@ export class CreateComponent implements OnInit { } public onBackClicked() { - this.router.navigate(['/setup']); + this.router.navigate(["/setup"]); } public onContinueClicked() { @@ -118,37 +116,17 @@ export class CreateComponent implements OnInit { this.createWalletForm.get('walletName').value, this.mnemonic, this.createWalletForm.get('walletPassword').value, + this.createWalletForm.get("walletPassphrase").value, ); - this.router.navigate( - ['/setup/create/show-mnemonic'], - { - queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password } - }); - // this.createWallets(this.newWallet); + this.router.navigate(['/setup/create/show-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password, passphrase: this.newWallet.passphrase }}); } } private getNewMnemonic() { - this.apiService - .getNewMnemonic() + this.apiService.getNewMnemonic() .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - this.mnemonic = response.json(); - } - }, - error => { - console.log(error); - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get new mnemonic. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } - } + this.mnemonic = response; } ) ; diff --git a/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.html b/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.html index 6cfc866a..a6ee8c7f 100644 --- a/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.html +++ b/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.html @@ -10,7 +10,7 @@

Secret words

- +
diff --git a/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.ts b/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.ts index 1b532ca2..89b511c6 100644 --- a/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.ts +++ b/Breeze.UI/src/app/setup/create/show-mnemonic/show-mnemonic.component.ts @@ -1,19 +1,16 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; import { WalletCreation } from '../../../shared/classes/wallet-creation'; -import { Subscription } from 'rxjs/Subscription'; - @Component({ selector: 'app-show-mnemonic', templateUrl: './show-mnemonic.component.html', styleUrls: ['./show-mnemonic.component.css'] }) export class ShowMnemonicComponent implements OnInit, OnDestroy { - constructor(private route: ActivatedRoute, private router: Router) { } - private parameters: any; private mnemonic: string; private subscription: Subscription; private newWallet: WalletCreation; @@ -24,7 +21,8 @@ export class ShowMnemonicComponent implements OnInit, OnDestroy { this.newWallet = new WalletCreation( params["name"], params["mnemonic"], - params["password"] + params["password"], + params["passphrase"] ) }); @@ -37,7 +35,7 @@ export class ShowMnemonicComponent implements OnInit, OnDestroy { } public onContinueClicked() { - this.router.navigate(['/setup/create/confirm-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password }}); + this.router.navigate(['/setup/create/confirm-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password, passphrase: this.newWallet.passphrase }}); } public onCancelClicked() { diff --git a/Breeze.UI/src/app/setup/recover/recover.component.html b/Breeze.UI/src/app/setup/recover/recover.component.html index 3dee8331..d4a06240 100644 --- a/Breeze.UI/src/app/setup/recover/recover.component.html +++ b/Breeze.UI/src/app/setup/recover/recover.component.html @@ -32,6 +32,11 @@

Restore a wallet

{{ formErrors.walletPassword }}
+
+ (optional) + +
{{ formErrors.walletPassphrase }}
+
diff --git a/Breeze.UI/src/app/setup/recover/recover.component.ts b/Breeze.UI/src/app/setup/recover/recover.component.ts index 8dd837c4..6e7f094e 100644 --- a/Breeze.UI/src/app/setup/recover/recover.component.ts +++ b/Breeze.UI/src/app/setup/recover/recover.component.ts @@ -3,7 +3,6 @@ import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms' import { Router } from '@angular/router'; import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker'; -import { GlobalService } from '../../shared/services/global.service'; import { ApiService } from '../../shared/services/api.service'; import { ModalService } from '../../shared/services/modal.service'; @@ -21,7 +20,7 @@ export class RecoverComponent implements OnInit { 'walletMnemonic': '', 'walletDate': '', 'walletPassword': '', - + 'walletPassphrase': '' }; validationMessages = { @@ -51,16 +50,15 @@ export class RecoverComponent implements OnInit { private walletRecovery: WalletRecovery; constructor( - private globalService: GlobalService, private apiService: ApiService, - private genericModalService: ModalService, private router: Router, + private genericModalService: ModalService, private fb: FormBuilder) { this.buildRecoverForm(); } ngOnInit() { - this.bsConfig = Object.assign({}, {showWeekNumbers: false, containerClass: 'theme-blue'}); + this.bsConfig = Object.assign({}, {showWeekNumbers: false, containerClass: 'theme-dark-blue'}); } private buildRecoverForm(): void { @@ -74,6 +72,7 @@ export class RecoverComponent implements OnInit { ], 'walletMnemonic': ['', Validators.required], 'walletDate': ['', Validators.required], + 'walletPassphrase': [''], 'walletPassword': ['', Validators.required], 'selectNetwork': ['test', Validators.required] }); @@ -116,83 +115,36 @@ export class RecoverComponent implements OnInit { this.recoverWalletForm.get('walletName').value, this.recoverWalletForm.get('walletMnemonic').value, this.recoverWalletForm.get('walletPassword').value, + this.recoverWalletForm.get("walletPassphrase").value, recoveryDate ); this.recoverWallets(this.walletRecovery); } private recoverWallets(recoverWallet: WalletRecovery) { - let bitcoinErrorMessage = ''; - this.apiService - .recoverBitcoinWallet(recoverWallet) + this.apiService.recoverBitcoinWallet(recoverWallet) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - // Bitcoin Wallet Recovered - } - this.recoverStratisWallet(recoverWallet, bitcoinErrorMessage); + this.recoverStratisWallet(recoverWallet); }, error => { this.isRecovering = false; - console.log(error); - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to recover Bitcoin wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - bitcoinErrorMessage = error.json().errors[0].message; - } - } - this.recoverStratisWallet(recoverWallet, bitcoinErrorMessage); } ) ; } - private recoverStratisWallet(recoverWallet: WalletRecovery, bitcoinErrorMessage: string) { - let stratisErrorMessage = ''; - this.apiService - .recoverStratisWallet(recoverWallet) + private recoverStratisWallet(recoverWallet: WalletRecovery) { + this.apiService.recoverStratisWallet(recoverWallet) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - const body = - 'Your wallet has been recovered. \nYou will be redirected to the decryption page.'; - this.genericModalService.openModal({ title: 'Wallet Recovered', body: body}); - this.router.navigate(['']) - } - this.AlertIfNeeded(bitcoinErrorMessage, stratisErrorMessage); + let body = "Your wallet has been recovered. \nYou will be redirected to the decryption page."; + this.genericModalService.openModal("Wallet Recovered", body) + this.router.navigate(['']) }, error => { this.isRecovering = false; - console.log(error); - if (error.status === 0) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to recover Stratis wallet. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - stratisErrorMessage = error.json().errors[0].message; - } - } - this.AlertIfNeeded(bitcoinErrorMessage, stratisErrorMessage); } - ) - ; - } - - private AlertIfNeeded(bitcoinErrorMessage: string, stratisErrorMessage: string) { - if (bitcoinErrorMessage !== '' || stratisErrorMessage !== '') { - const errorMessage = - `Bitcoin wallet recovery:
- ${bitcoinErrorMessage} - '

- Stratis wallet recovery:
- ${stratisErrorMessage}`; - this.genericModalService.openModal({ body: errorMessage}); - } + ); } } diff --git a/Breeze.UI/src/app/setup/setup-routing.module.ts b/Breeze.UI/src/app/setup/setup-routing.module.ts index 2c24c99f..2c9e7eb2 100644 --- a/Breeze.UI/src/app/setup/setup-routing.module.ts +++ b/Breeze.UI/src/app/setup/setup-routing.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; - import { SetupComponent } from './setup.component'; import { CreateComponent } from './create/create.component'; import { ShowMnemonicComponent } from './create/show-mnemonic/show-mnemonic.component'; @@ -8,17 +7,15 @@ import { ConfirmMnemonicComponent } from './create/confirm-mnemonic/confirm-mnem import { RecoverComponent } from './recover/recover.component'; const routes: Routes = [ - { path: '', redirectTo: 'setup', pathMatch: 'full'}, { path: 'setup', component: SetupComponent }, - { path: 'create', component: CreateComponent }, - { path: 'create/show-mnemonic', component: ShowMnemonicComponent }, - { path: 'create/confirm-mnemonic', component: ConfirmMnemonicComponent }, - { path: 'recover', component: RecoverComponent } + { path: 'setup/create', component: CreateComponent }, + { path: 'setup/create/show-mnemonic', component: ShowMnemonicComponent }, + { path: 'setup/create/confirm-mnemonic', component: ConfirmMnemonicComponent }, + { path: 'setup/recover', component: RecoverComponent } ]; @NgModule({ - imports: [ RouterModule.forChild(routes) ], - exports: [ RouterModule ] + imports: [ RouterModule.forChild(routes) ] }) -export class SetupRoutingModule {} +export class SetupRoutingModule { } diff --git a/Breeze.UI/src/app/setup/setup.module.ts b/Breeze.UI/src/app/setup/setup.module.ts index 3cf2c87a..d85344fa 100644 --- a/Breeze.UI/src/app/setup/setup.module.ts +++ b/Breeze.UI/src/app/setup/setup.module.ts @@ -1,25 +1,20 @@ -import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; import { SetupComponent } from './setup.component'; import { CreateComponent } from './create/create.component'; -import { SharedModule } from '../shared/shared.module'; - import { SetupRoutingModule } from './setup-routing.module'; import { RecoverComponent } from './recover/recover.component'; import { ShowMnemonicComponent } from './create/show-mnemonic/show-mnemonic.component'; import { ConfirmMnemonicComponent } from './create/confirm-mnemonic/confirm-mnemonic.component'; +import { SharedModule } from '../shared/shared.module'; @NgModule({ imports: [ BsDatepickerModule.forRoot(), - CommonModule, - ReactiveFormsModule, - SetupRoutingModule, - SharedModule.forRoot() + SharedModule, + SetupRoutingModule ], declarations: [ CreateComponent, diff --git a/Breeze.UI/src/app/shared/classes/composite-disposable.ts b/Breeze.UI/src/app/shared/classes/composite-disposable.ts index a4edd90f..0a67e061 100644 --- a/Breeze.UI/src/app/shared/classes/composite-disposable.ts +++ b/Breeze.UI/src/app/shared/classes/composite-disposable.ts @@ -1,4 +1,4 @@ -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; export class CompositeDisposable { private _subscriptions: Set = new Set(); diff --git a/Breeze.UI/src/app/shared/classes/fee-estimation.ts b/Breeze.UI/src/app/shared/classes/fee-estimation.ts index 9e6e494e..cbc66585 100644 --- a/Breeze.UI/src/app/shared/classes/fee-estimation.ts +++ b/Breeze.UI/src/app/shared/classes/fee-estimation.ts @@ -1,18 +1,25 @@ +export class Recipient { + constructor(destinationAddress: string, amount: string) { + this.destinationAddress = destinationAddress; + this.amount = amount; + } + + destinationAddress: string; + amount: string; +} + export class FeeEstimation { constructor(walletName: string, accountName: string, destinationAddress: string, amount: string, feeType: string, allowUnconfirmed: boolean) { this.walletName = walletName; this.accountName = accountName; - this.destinationAddress = destinationAddress; - this.amount = amount; + this.recipients = [new Recipient(destinationAddress, amount)]; this.feeType = feeType; this.allowUnconfirmed = allowUnconfirmed; } - + walletName: string; accountName: string; - destinationAddress: string; - amount: string; + recipients: Recipient[]; feeType: string; allowUnconfirmed: boolean; } - \ No newline at end of file diff --git a/Breeze.UI/src/app/shared/classes/node-status.ts b/Breeze.UI/src/app/shared/classes/node-status.ts new file mode 100644 index 00000000..e34dc4fa --- /dev/null +++ b/Breeze.UI/src/app/shared/classes/node-status.ts @@ -0,0 +1,52 @@ +export class NodeStatus { + constructor(agent: string, version: string, network: string, coinTicker: string, processId: number, consensusHeight: number, blockStoreHeight: number, inboundPeers: [Peer], + outbountPeers: [Peer], enabledFeatures: [string], dataDirectoryPath: string, runningtime: string, difficulty: number, protocolVersion: number, testnet: boolean, relayFee: number, state: string) { + this.agent = agent; + this.version = version; + this.network = network; + this.coinTicker = coinTicker; + this.processId = processId; + this.consensusHeight = consensusHeight; + this.blockStoreHeight = blockStoreHeight; + this.inboundPeers = inboundPeers; + this.outbountPeers = outbountPeers; + this.enabledFeatures = enabledFeatures; + this.dataDirectoryPath = dataDirectoryPath; + this.runningTime = runningtime; + this.difficulty = difficulty; + this.protocolVersion = protocolVersion; + this.testnet = testnet; + this.relayFee = relayFee; + this.state = state; + } + + public agent: string; + public version: string; + public network: string; + public coinTicker: string; + public processId: number; + public consensusHeight: number; + public blockStoreHeight: number; + public inboundPeers: [Peer]; + public outbountPeers: [Peer]; + public enabledFeatures: [string]; + public dataDirectoryPath: string; + public runningTime: string; + public difficulty: number; + public protocolVersion: number; + public testnet: boolean; + public relayFee: number; + public state: string; +} + +class Peer { + constructor(version, remoteSocketEndpoint, tipHeight) { + this.version = version; + this.remoteSocketEndpoint = remoteSocketEndpoint; + this.tipHeight = tipHeight; + } + + public version: string; + public remoteSocketEndpoint: string; + public tipHeight: number; +} diff --git a/Breeze.UI/src/app/shared/classes/transaction-building.ts b/Breeze.UI/src/app/shared/classes/transaction-building.ts index 60514850..1f2e75a0 100644 --- a/Breeze.UI/src/app/shared/classes/transaction-building.ts +++ b/Breeze.UI/src/app/shared/classes/transaction-building.ts @@ -1,20 +1,33 @@ -export class TransactionBuilding { +export class Recipient { + constructor(destinationAddress: string, amount: string) { + this.destinationAddress = destinationAddress; + this.amount = amount; + } - constructor(walletName: string, accountName: string, password: string, destinationAddress: string, amount: string, feeType: string, allowUnconfirmed: boolean) { + destinationAddress: string; + amount: string; +} + +export class TransactionBuilding { + constructor(walletName: string, accountName: string, password: string, destinationAddress: string, amount: string, feeAmount: number, allowUnconfirmed: boolean, shuffleOutputs: boolean, opReturnData?: string, opReturnAmount?: number) { this.walletName = walletName; this.accountName = accountName; this.password = password; - this.destinationAddress = destinationAddress; - this.amount = amount; - this.feeType = feeType; + this.recipients = [new Recipient(destinationAddress, amount)]; + this.feeAmount = feeAmount; this.allowUnconfirmed = allowUnconfirmed; + this.shuffleOutputs = shuffleOutputs; + this.opReturnData = opReturnData; + this.opReturnAmount = opReturnAmount; } walletName: string; accountName: string; password: string; - destinationAddress: string; - amount: string; - feeType: string; + recipients: Recipient[]; + feeAmount: number; allowUnconfirmed: boolean; + shuffleOutputs: boolean; + opReturnData: string; + opReturnAmount: number; } diff --git a/Breeze.UI/src/app/shared/classes/transaction-info.ts b/Breeze.UI/src/app/shared/classes/transaction-info.ts index 82009791..dc881dfd 100644 --- a/Breeze.UI/src/app/shared/classes/transaction-info.ts +++ b/Breeze.UI/src/app/shared/classes/transaction-info.ts @@ -1,4 +1,4 @@ -import { Transaction } from 'interfaces/transaction' +import { Transaction } from '../../../interfaces/transaction'; export class TransactionInfo { diff --git a/Breeze.UI/src/app/shared/classes/wallet-creation.ts b/Breeze.UI/src/app/shared/classes/wallet-creation.ts index 02a65fa4..5b564d92 100644 --- a/Breeze.UI/src/app/shared/classes/wallet-creation.ts +++ b/Breeze.UI/src/app/shared/classes/wallet-creation.ts @@ -1,14 +1,15 @@ export class WalletCreation { - - constructor(name: string, mnemonic: string, password: string, folderPath: string = null ) { + constructor(name: string, mnemonic: string, password: string, passphrase: string, folderPath: string = null ) { this.name = name; this.mnemonic = mnemonic; this.password = password; + this.passphrase = passphrase; this.folderPath = folderPath; } name: string; mnemonic: string; password: string; + passphrase: string; folderPath?: string; } diff --git a/Breeze.UI/src/app/shared/classes/wallet-info.ts b/Breeze.UI/src/app/shared/classes/wallet-info.ts index b72e1e17..91549972 100644 --- a/Breeze.UI/src/app/shared/classes/wallet-info.ts +++ b/Breeze.UI/src/app/shared/classes/wallet-info.ts @@ -1,5 +1,4 @@ export class WalletInfo { - constructor(walletName: string) { this.walletName = walletName; } diff --git a/Breeze.UI/src/app/shared/classes/wallet-load.ts b/Breeze.UI/src/app/shared/classes/wallet-load.ts index 47809bc4..b3541aad 100644 --- a/Breeze.UI/src/app/shared/classes/wallet-load.ts +++ b/Breeze.UI/src/app/shared/classes/wallet-load.ts @@ -1,5 +1,4 @@ export class WalletLoad { - constructor(name: string, password: string, folderPath: string = null ) { this.name = name; this.password = password; diff --git a/Breeze.UI/src/app/shared/classes/wallet-recovery.ts b/Breeze.UI/src/app/shared/classes/wallet-recovery.ts index 81d5f98c..0afccc0d 100644 --- a/Breeze.UI/src/app/shared/classes/wallet-recovery.ts +++ b/Breeze.UI/src/app/shared/classes/wallet-recovery.ts @@ -1,15 +1,16 @@ export class WalletRecovery { - - constructor(walletName: string, mnemonic: string, password: string, creationDate: Date, folderPath: string = null) { + constructor(walletName: string, mnemonic: string, password: string, passphrase: string, creationDate: Date, folderPath: string = null) { this.name = walletName; this.mnemonic = mnemonic; this.password = password; + this.passphrase = passphrase; this.creationDate = creationDate; this.folderPath = folderPath; } mnemonic: string; password: string; + passphrase: string; name: string; creationDate: Date; folderPath?: string; diff --git a/Breeze.UI/src/app/shared/http-interceptors/api-interceptor.ts b/Breeze.UI/src/app/shared/http-interceptors/api-interceptor.ts new file mode 100644 index 00000000..23cd3b8a --- /dev/null +++ b/Breeze.UI/src/app/shared/http-interceptors/api-interceptor.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { HttpInterceptor, HttpRequest, HttpHandler } from "@angular/common/http"; + +@Injectable() +export class ApiInterceptor implements HttpInterceptor { + constructor() {} + + intercept(req: HttpRequest, next: HttpHandler) { + const finalReq = req.clone({ + headers: req.headers.set('Content-Type', 'application/json') + }); + + return next.handle(finalReq); + } +} diff --git a/Breeze.UI/src/app/shared/pipes/coin-notation.pipe.ts b/Breeze.UI/src/app/shared/pipes/coin-notation.pipe.ts index 03a339af..d8a93dde 100644 --- a/Breeze.UI/src/app/shared/pipes/coin-notation.pipe.ts +++ b/Breeze.UI/src/app/shared/pipes/coin-notation.pipe.ts @@ -1,69 +1,18 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { GlobalService } from '../services/global.service'; @Pipe({ name: 'coinNotation' }) export class CoinNotationPipe implements PipeTransform { - constructor (private globalService: GlobalService) { - this.setCoinUnit(); - } + constructor() { } - private coinUnit: string; - private coinNotation: number; private decimalLimit = 8; transform(value: number): number { let temp; if (typeof value === 'number') { - switch (this.getCoinUnit()) { - case "BTC": - temp = value / 100000000; - return temp.toFixed(this.decimalLimit); - case "mBTC": - temp = value / 100000; - return temp.toFixed(this.decimalLimit); - case "uBTC": - temp = value / 100; - return temp.toFixed(this.decimalLimit); - case "TBTC": - temp = value / 100000000; - return temp.toFixed(this.decimalLimit); - case "TmBTC": - temp = value / 100000; - return temp.toFixed(this.decimalLimit); - case "TuBTC": - temp = value / 100; - return temp.toFixed(this.decimalLimit); - case "STRAT": - temp = value / 100000000; - return temp.toFixed(this.decimalLimit); - case "mSTRAT": - temp = value / 100000; - return temp.toFixed(this.decimalLimit); - case "uSTRAT": - temp = value / 100; - return temp.toFixed(this.decimalLimit); - case "TSTRAT": - temp = value / 100000000; - return temp.toFixed(this.decimalLimit); - case "TmSTRAT": - temp = value / 100000; - return temp.toFixed(this.decimalLimit); - case "TuSTRAT": - temp = value / 100; - return temp.toFixed(this.decimalLimit); - } + temp = value / 100000000; + return temp.toFixed(this.decimalLimit); } } - - getCoinUnit() { - return this.coinUnit; - } - - setCoinUnit() { - this.coinUnit = this.globalService.getCoinUnit(); - }; } - - diff --git a/Breeze.UI/src/app/reuse-strategy.ts b/Breeze.UI/src/app/shared/reuse-strategy/reuse-strategy.ts similarity index 100% rename from Breeze.UI/src/app/reuse-strategy.ts rename to Breeze.UI/src/app/shared/reuse-strategy/reuse-strategy.ts diff --git a/Breeze.UI/src/app/shared/services/api.service.ts b/Breeze.UI/src/app/shared/services/api.service.ts index 19bb3458..185aab72 100644 --- a/Breeze.UI/src/app/shared/services/api.service.ts +++ b/Breeze.UI/src/app/shared/services/api.service.ts @@ -1,169 +1,183 @@ import { Injectable } from '@angular/core'; -import { Http, Headers, Response, RequestOptions, URLSearchParams } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/switchMap'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/interval'; -import 'rxjs/add/operator/startWith'; -import 'rxjs/observable/throw'; +import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http'; +import { Observable, interval, throwError } from 'rxjs'; +import { catchError, switchMap, startWith} from 'rxjs/operators'; + import { GlobalService } from './global.service'; + import { WalletCreation } from '../classes/wallet-creation'; import { WalletRecovery } from '../classes/wallet-recovery'; import { WalletLoad } from '../classes/wallet-load'; import { WalletInfo } from '../classes/wallet-info'; -import { Mnemonic } from '../classes/mnemonic'; import { FeeEstimation } from '../classes/fee-estimation'; import { TransactionBuilding } from '../classes/transaction-building'; import { TransactionSending } from '../classes/transaction-sending'; -import { Error } from '../../shared/classes/error'; -import { ServiceShared } from './shared'; +import { ModalService } from './modal.service'; +import { Router } from '@angular/router'; +import { NodeStatus } from '../classes/node-status'; -/** - * For information on the API specification have a look at our swagger files - * located at http://localhost:5000/swagger/ when running the daemon - */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class ApiService { - private _currentApiUrl; - private headers = new Headers({ 'Content-Type': 'application/json' }); - public readonly pollingInterval = 3000; - constructor(private http: Http, private globalService: GlobalService) { } - private getCurrentCoin() { - const currentCoin = this.globalService.getCoinName(); - if (ApiService.isBitcoin(currentCoin)) { - this._currentApiUrl = this.bitcoinApiUrl; - } else if (ApiService.isStratis(currentCoin)) { - this._currentApiUrl = this.stratisApiUrl; - } + constructor(private http: HttpClient, private globalService: GlobalService, private modalService: ModalService, private router: Router) { } + + private _currentApiUrl = this.getCurrentApiUrl(); + private bitcoinApiUrl = this.getBitcoinApiUrl(); + private stratisApiUrl = this.getStratisApiUrl(); + private pollingInterval = interval(5000); + private static isBitcoin(activeCoin: string): boolean { + return activeCoin === 'Bitcoin'; } - private static isBitcoin(coin: string): boolean { - return coin === 'Bitcoin' || coin === 'TestBitcoin'; + private static isStratis(activeCoin: string): boolean { + return activeCoin === 'Stratis'; } - private static isStratis(coin: string): boolean { - return coin === 'Stratis' || coin === 'TestStratis'; + private getCurrentNetwork() { + const activeCoin = this.globalService.getActiveCoin(); + if (ApiService.isBitcoin(activeCoin)) { + this._currentApiUrl = this.getBitcoinApiUrl(); + } else if (ApiService.isStratis(activeCoin)) { + this._currentApiUrl = this.getStratisApiUrl(); + } } - get bitcoinApiUrl() { - return `http://localhost:${this.globalService.bitcoinApiPort}/api`; + getBitcoinApiUrl() { + const bitcoinApiPort = this.globalService.getBitcoinApiPort(); + this.bitcoinApiUrl = `http://localhost:${bitcoinApiPort}/api`; + return this.bitcoinApiUrl; } - get stratisApiUrl() { - return `http://localhost:${this.globalService.stratisApiPort}/api`; + getStratisApiUrl() { + const stratisApiPort = this.globalService.getStratisApiPort(); + this.stratisApiUrl = `http://localhost:${stratisApiPort}/api`; + return this.stratisApiUrl; } - get currentApiUrl() { + getCurrentApiUrl() { if (!this._currentApiUrl) { - this._currentApiUrl = `http://localhost:${this.globalService.bitcoinApiPort}/api`; + this._currentApiUrl = `http://localhost:${this.globalService.getBitcoinApiPort()}/api`; } return this._currentApiUrl; } + + getNodeStatus(silent?: boolean): Observable { + this.getCurrentNetwork(); + return this.http.get(this._currentApiUrl + '/node/status').pipe( + catchError(err => this.handleHttpError(err, silent)) + ); + } + + getNodeStatusInterval(): Observable { + this.getCurrentNetwork(); + return this.pollingInterval.pipe( + startWith(0), + switchMap(() => this.http.get(this._currentApiUrl + '/node/status')), + catchError(err => this.handleHttpError(err)) + ) + } /** * Gets available wallets at the default path */ getWalletFiles(): Observable { - return this.http - .get(this.bitcoinApiUrl + '/wallet/files') - .map((response: Response) => response); + return this.http.get(this.bitcoinApiUrl + '/wallet/files').pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Gets available wallets at the default path */ getStratisWalletFiles(): Observable { - return this.http - .get(this.stratisApiUrl + '/wallet/files') - .map((response: Response) => response); + return this.http.get(this.stratisApiUrl + '/wallet/files').pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Get a new mnemonic */ getNewMnemonic(): Observable { - const params: URLSearchParams = new URLSearchParams(); - params.set('language', 'English'); - params.set('wordCount', '12'); - return this.http - .get(`${this.bitcoinApiUrl}/wallet/mnemonic`, new RequestOptions({ headers: this.headers, search: params })) - .map((response: Response) => response); + let params = new HttpParams() + .set('language', 'English') + .set('wordCount', '12'); + + return this.http.get(this._currentApiUrl + '/wallet/mnemonic', { params }).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Create a new Bitcoin wallet. */ createBitcoinWallet(data: WalletCreation): Observable { - return this.http - .post(`${this.bitcoinApiUrl}/wallet/create/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.bitcoinApiUrl + '/wallet/create/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Create a new Stratis wallet. */ createStratisWallet(data: WalletCreation): Observable { - return this.http - .post(`${this.stratisApiUrl}/wallet/create/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.stratisApiUrl + '/wallet/create/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Recover a Bitcoin wallet. */ recoverBitcoinWallet(data: WalletRecovery): Observable { - return this.http - .post(`${this.bitcoinApiUrl}/wallet/recover/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.bitcoinApiUrl + '/wallet/recover/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Recover a Stratis wallet. */ recoverStratisWallet(data: WalletRecovery): Observable { - return this.http - .post(`${this.stratisApiUrl}/wallet/recover/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.stratisApiUrl + '/wallet/recover/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Load a Bitcoin wallet */ loadBitcoinWallet(data: WalletLoad): Observable { - return this.http - .post(`${this.bitcoinApiUrl}/wallet/load/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.bitcoinApiUrl + '/wallet/load/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Load a Stratis wallet */ loadStratisWallet(data: WalletLoad): Observable { - return this.http - .post(`${this.stratisApiUrl}/wallet/load/`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + return this.http.post(this.stratisApiUrl + '/wallet/load/', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Get wallet status info from the API. */ getWalletStatus(): Observable { - this.getCurrentCoin(); - return this.http - .get(`${this.currentApiUrl}/wallet/status`) - .map((response: Response) => response); + this.getCurrentNetwork(); + return this.http.get(this._currentApiUrl + '/wallet/status').pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Get general wallet info from the API once. */ getGeneralInfoOnce(data: WalletInfo): Observable { - const params: URLSearchParams = new URLSearchParams(); - params.set('Name', data.walletName); - return this.http - .get(`${this.bitcoinApiUrl}/wallet/general-info`, new RequestOptions({ headers: this.headers, search: params })) - .map((response: Response) => response); + let params = new HttpParams().set('Name', data.walletName); + return this.http.get(this._currentApiUrl + '/wallet/general-info', { params }).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Get general wallet info from the API. */ getGeneralInfo(data: WalletInfo): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('Name', data.walletName); - return Observable - .interval(this.pollingInterval) - .startWith(0) - .switchMap(() => - this.http.get(`${this.currentApiUrl}/wallet/general-info`, new RequestOptions({ headers: this.headers, search: params })) - .retryWhen(e => ServiceShared.onRetryWhen(e))) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams().set('Name', data.walletName); + return this.pollingInterval.pipe( + startWith(0), + switchMap(() => this.http.get(this._currentApiUrl + '/wallet/general-info', { params })), + catchError(err => this.handleHttpError(err)) + ) } getGeneralInfoForCoin(data: WalletInfo, coin: string): Observable { let url; @@ -174,106 +188,119 @@ export class ApiService { } else { return Observable.throw(`No such coin '${coin}'`); } - const params: URLSearchParams = new URLSearchParams(); - params.set('Name', data.walletName); - return this.http.get(`${url}/wallet/general-info`, - new RequestOptions({ headers: this.headers, search: params })).map((response: Response) => response.json()); + + let params = new HttpParams().set('walletName', data.walletName); + + return this.pollingInterval.pipe( + startWith(0), + switchMap(() => this.http.get(this._currentApiUrl + '/wallet/general-info', { params })), + catchError(err => this.handleHttpError(err)) + ) } /** * Get wallet balance info from the API. */ getWalletBalance(data: WalletInfo): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('walletName', data.walletName); - return Observable - .interval(this.pollingInterval) - .startWith(0) - .switchMap(() => this.http.get(`${this.currentApiUrl}/wallet/balance`, new RequestOptions({ headers: this.headers, search: params })) - .retryWhen(e => ServiceShared.onRetryWhen(e))) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams() + .set('walletName', data.walletName) + .set('accountName', 'account 0'); + return this.pollingInterval.pipe( + startWith(0), + switchMap(() => this.http.get(this._currentApiUrl + '/wallet/balance', { params })), + catchError(err => this.handleHttpError(err)) + ) } /** * Get the maximum sendable amount for a given fee from the API */ getMaximumBalance(data): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('walletName', data.walletName); - params.set('accountName', 'account 0'); - params.set('feeType', data.feeType); - params.set('allowUnconfirmed', 'true'); - return this.http - .get(`${this.currentApiUrl}/wallet/maxbalance`, new RequestOptions({ headers: this.headers, search: params })) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams() + .set('walletName', data.walletName) + .set('accountName', "account 0") + .set('feeType', data.feeType) + .set('allowUnconfirmed', "true"); + return this.http.get(this._currentApiUrl + '/wallet/maxbalance', { params }).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Get a wallets transaction history info from the API. */ getWalletHistory(data: WalletInfo): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('walletName', data.walletName); - return Observable - .interval(this.pollingInterval) - .startWith(0) - .switchMap(() => this.http.get(`${this.currentApiUrl}/wallet/history`, new RequestOptions({ headers: this.headers, search: params }))) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams() + .set('walletName', data.walletName) + .set('accountName', "account 0"); + return this.pollingInterval.pipe( + startWith(0), + switchMap(() => this.http.get(this._currentApiUrl + '/wallet/history', { params: params })), + catchError(err => this.handleHttpError(err)) + ) } /** * Get unused receive addresses for a certain wallet from the API. */ getUnusedReceiveAddress(data: WalletInfo): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('walletName', data.walletName); - params.set('accountName', 'account 0'); // temporary - return this.http - .get(`${this.currentApiUrl}/wallet/unusedaddress`, new RequestOptions({ headers: this.headers, search: params })) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams() + .set('walletName', data.walletName) + .set('accountName', "account 0"); + return this.http.get(this._currentApiUrl + '/wallet/unusedaddress', { params }).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Estimate the fee of a transaction */ estimateFee(data: FeeEstimation): Observable { - this.getCurrentCoin(); - const params: URLSearchParams = new URLSearchParams(); - params.set('walletName', data.walletName); - params.set('accountName', data.accountName); - params.set('destinationAddress', data.destinationAddress); - params.set('amount', data.amount); - params.set('feeType', data.feeType); - params.set('allowUnconfirmed', 'true'); - return this.http - .get(`${this.currentApiUrl}/wallet/estimate-txfee`, new RequestOptions({ headers: this.headers, search: params })) - .map((response: Response) => response); + this.getCurrentNetwork(); + let params = new HttpParams() + .set('walletName', data.walletName) + .set('accountName', data.accountName) + .set('recipients[0].destinationAddress', data.recipients[0].destinationAddress) + .set('recipients[0].amount', data.recipients[0].amount) + .set('feeType', data.feeType) + .set('allowUnconfirmed', 'true'); + return this.http.get(this._currentApiUrl + '/wallet/estimate-txfee', { params }).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Build a transaction */ buildTransaction(data: TransactionBuilding): Observable { - this.getCurrentCoin(); - return this.http - .post(`${this.currentApiUrl}/wallet/build-transaction`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); + this.getCurrentNetwork(); + return this.http.post(this._currentApiUrl + '/wallet/build-transaction', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } /** * Send transaction */ sendTransaction(data: TransactionSending): Observable { - this.getCurrentCoin(); - return this.http - .post(`${this.currentApiUrl}/wallet/send-transaction`, JSON.stringify(data), { headers: this.headers }) - .map((response: Response) => response); - } - /** - * Send shutdown signal to the daemon - */ - shutdownNode(): Observable { - this.getCurrentCoin(); - return this.http - .post(`${this.currentApiUrl}/node/shutdown`, '') - .map((response: Response) => response); + this.getCurrentNetwork(); + return this.http.post(this._currentApiUrl + '/wallet/send-transaction', JSON.stringify(data)).pipe( + catchError(err => this.handleHttpError(err)) + ); } -} \ No newline at end of file + private handleHttpError(error: HttpErrorResponse, silent?: boolean) { + console.log(error); + if (error.status === 0) { + if(!silent) { + this.modalService.openModal(null, null); + this.router.navigate(['app']); + } + } else if (error.status >= 400) { + if (!error.error.errors[0].message) { + console.log(error); + } + else { + this.modalService.openModal(null, error.error.errors[0].message); + } + } + return throwError(error); + } +} diff --git a/Breeze.UI/src/app/shared/services/global.service.ts b/Breeze.UI/src/app/shared/services/global.service.ts index 408f5f49..85b6a80d 100644 --- a/Breeze.UI/src/app/shared/services/global.service.ts +++ b/Breeze.UI/src/app/shared/services/global.service.ts @@ -1,24 +1,69 @@ import { Injectable } from '@angular/core'; -import { remote } from 'electron'; +import { ElectronService } from 'ngx-electron'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class GlobalService { + private applicationVersion: string = "1.1.0"; private walletPath: string; + private activeCoin: string = "Bitcoin"; private currentWalletName: string; - private coinType: number; - private coinName: string; private coinUnit: string; private network: string; + private bitcoinApiPort: number; + private stratisApiPort: number; + private testnet: boolean = false; + + constructor(private electronService: ElectronService) { + this.setApplicationVersion(); + this.setTestnetEnabled(); + this.setBitcoinApiPort(); + this.setStratisApiPort(); + } + + getApplicationVersion() { + return this.applicationVersion; + } + + setApplicationVersion() { + if (this.electronService.isElectronApp) { + this.applicationVersion = this.electronService.remote.app.getVersion(); + } + } + + getActiveCoin() { + return this.activeCoin; + } + + setActiveCoin(activeCoin: string) { + this.activeCoin = activeCoin; + } - constructor() { + getTestnetEnabled() { + return this.testnet; } - get bitcoinApiPort() { - return remote.getGlobal('bitcoinApiPort'); + setTestnetEnabled() { + if (this.electronService.isElectronApp) { + this.testnet = this.electronService.ipcRenderer.sendSync('get-testnet'); + } } - get stratisApiPort() { - return remote.getGlobal('stratisApiPort'); + getBitcoinApiPort() { + return this.bitcoinApiPort; + } + + setBitcoinApiPort() { + this.bitcoinApiPort = this.electronService.remote.getGlobal('bitcoinApiPort'); + } + + getStratisApiPort() { + return this.stratisApiPort; + } + + setStratisApiPort() { + this.stratisApiPort = this.electronService.remote.getGlobal('stratisApiPort'); } getWalletPath() { @@ -45,22 +90,6 @@ export class GlobalService { this.currentWalletName = currentWalletName; } - getCoinType() { - return this.coinType; - } - - setCoinType(coinType: number) { - this.coinType = coinType; - } - - getCoinName() { - return this.coinName; - } - - setCoinName(coinName: string) { - this.coinName = coinName; - } - getCoinUnit() { return this.coinUnit; } diff --git a/Breeze.UI/src/app/shared/services/license.service.ts b/Breeze.UI/src/app/shared/services/license.service.ts index f239cc17..abe5b505 100644 --- a/Breeze.UI/src/app/shared/services/license.service.ts +++ b/Breeze.UI/src/app/shared/services/license.service.ts @@ -1,16 +1,18 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class LicenseService { - + private _licenseText: string; - + constructor(http: HttpClient) { - + this._licenseText = ` By installing this software the user agrees to all of the following: - + That this software is an experimental release and any use of it shall be at the users own discretion and risk. That the sole and exclusive remedy for any problem(s), malfunctions or defects in the product, software and / or service shall be to uninstall and/or to stop using it . In no event shall Stratis, its officers, shareholders, investors, employees, agents, directors, subsidiaries, affiliates, successors, assignees or suppliers be liable for diff --git a/Breeze.UI/src/app/shared/services/logger.service.ts b/Breeze.UI/src/app/shared/services/logger.service.ts index 0cef6a5b..7cea82cf 100644 --- a/Breeze.UI/src/app/shared/services/logger.service.ts +++ b/Breeze.UI/src/app/shared/services/logger.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@angular/core'; -import { environment } from '../../../environments'; +import { AppConfig } from '../../../environments/environment'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class Log { public static Logger: Log = new Log(); @@ -28,20 +30,20 @@ export class Log { constructor() { } log(...args: any[]) { - if (!environment.production) { + if (!AppConfig.production) { console.log(this.getLogPrefix(), 'background: #444; color: #fff', ...args); } } warn(...args: any[]) { - if (!environment.production) { + if (!AppConfig.production) { args.push(); console.warn(this.getLogPrefix(), 'background: #444; color: #fff', ...args); } } info(...args: any[]) { - if (!environment.production) { + if (!AppConfig.production) { args.push(); // tslint:disable-next-line:no-console console.info(this.getLogPrefix(), 'background: #444; color: #fff', ...args); @@ -49,14 +51,14 @@ export class Log { } debug(...args: any[]) { - if (!environment.production) { + if (!AppConfig.production) { // tslint:disable-next-line:no-console console.debug(this.getLogPrefix(), 'background: #444; color: #fff', ...args); } } error(...args: any[]) { - if (!environment.production) { + if (!AppConfig.production) { console.error(this.getLogPrefix(), 'background: #444; color: #fff', ...args); } } diff --git a/Breeze.UI/src/app/shared/services/modal.service.ts b/Breeze.UI/src/app/shared/services/modal.service.ts index 6265d336..395eb084 100644 --- a/Breeze.UI/src/app/shared/services/modal.service.ts +++ b/Breeze.UI/src/app/shared/services/modal.service.ts @@ -1,79 +1,21 @@ -import {Injectable} from '@angular/core'; - -import { NgbModal, NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; - +import {Injectable} from "@angular/core"; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { GenericModalComponent } from '../components/generic-modal/generic-modal.component'; -import { ConfirmDialogComponent } from '../components/confirm-dialog/confirm-dialog.component'; - -import { DialogOptions } from '../classes/dialog-options'; -import { ConfirmDialogOptions } from '../classes/confirm-dialog-options'; -import { Log } from '../services/logger.service'; - -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class ModalService { - private modalRef: NgbModalRef; - private confirmDialogRef: NgbModalRef; constructor(private modalService: NgbModal) {} - public openModal(options: DialogOptions) { - // check and dispose existing modal popup - if (!!this.modalRef) { - this.modalRef.dismiss(); - } - - if (!!options && !!options.modalOptions) { - this.modalRef = this.modalService.open(GenericModalComponent, options.modalOptions); - } else { - this.modalRef = this.modalService.open(GenericModalComponent); - } - - if (!options) { return; } - if (!!options.title) { - this.modalRef.componentInstance.title = options.title; - } - - if (!!options.body) { - this.modalRef.componentInstance.body = options.body; - } - - if (!!options.helpUrl) { - this.modalRef.componentInstance.helpUrl = options.helpUrl; - } - - if (options.showHomeLink) { - this.modalRef.componentInstance.showHomeLink = options.showHomeLink; - } - } - - public confirm(options: ConfirmDialogOptions, confirmCallback: Function) { - // check and dispose existing modal popup - if (!!this.confirmDialogRef) { - this.confirmDialogRef.dismiss(); - } - - this.confirmDialogRef = this.modalService.open(ConfirmDialogComponent); - this.confirmDialogRef.result.then((result) => { - if (!!confirmCallback && !!result) { - confirmCallback(); - } - }); - - if (!options) { return; } - if (!!options.title) { - this.confirmDialogRef.componentInstance.title = options.title; - } - - if (!!options.body) { - this.confirmDialogRef.componentInstance.body = options.body; - } - - if (!!options.noLabel) { - this.confirmDialogRef.componentInstance.noLabel = options.noLabel; + public openModal(title, body) { + const modalRef = this.modalService.open(GenericModalComponent, { backdrop: "static", keyboard: false }); + if (title) { + modalRef.componentInstance.title = title; } - if (!!options.yesLabel) { - this.confirmDialogRef.componentInstance.yesLabel = options.yesLabel; + if (body) { + modalRef.componentInstance.body = body; } } } diff --git a/Breeze.UI/src/app/shared/services/shared.ts b/Breeze.UI/src/app/shared/services/shared.ts index f24ac97d..8ccddf75 100644 --- a/Breeze.UI/src/app/shared/services/shared.ts +++ b/Breeze.UI/src/app/shared/services/shared.ts @@ -1,4 +1,5 @@ -import { Observable } from 'rxjs/Observable'; +import { Observable, of } from 'rxjs'; +import { delay } from 'rxjs/operators'; import { Error } from '../../shared/classes/error'; export class ServiceShared @@ -7,15 +8,15 @@ export class ServiceShared return errors.mergeMap((error: any) => { const firstError = Error.getFirstError(error); if (error.status === 0) { - return Observable.of(error.status).delay(5000) + return of(error.status).pipe(delay(5000)); } else if (error.status === 400 && !firstError.description) { console.log("Retrying; MVC error."); - return Observable.of(error.status).delay(5000); + return of(error.status).pipe(delay(5000)); } return Observable.throw(error); }) .take(5) .concat(Observable.throw(errors)); } -} \ No newline at end of file +} diff --git a/Breeze.UI/src/app/shared/shared.module.ts b/Breeze.UI/src/app/shared/shared.module.ts index 834abea4..bdfb359c 100644 --- a/Breeze.UI/src/app/shared/shared.module.ts +++ b/Breeze.UI/src/app/shared/shared.module.ts @@ -1,21 +1,24 @@ -import { NgModule, ModuleWithProviders } from '@angular/core'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CoinNotationPipe } from './pipes/coin-notation.pipe'; import { AutoFocusDirective } from './directives/auto-focus.directive'; import { PasswordValidationDirective } from './directives/password-validation.directive'; -import { HttpClientModule } from '@angular/common/http'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgxElectronModule } from 'ngx-electron'; +import { NgxQRCodeModule } from 'ngx-qrcode2'; +import { NgxPaginationModule } from 'ngx-pagination'; +import { ClipboardModule } from 'ngx-clipboard'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { GenericModalComponent } from './components/generic-modal/generic-modal.component'; @NgModule({ - imports: [CommonModule, HttpClientModule], - declarations: [CoinNotationPipe, AutoFocusDirective, PasswordValidationDirective], - exports: [CoinNotationPipe, AutoFocusDirective] + imports: [ CommonModule ], + declarations: [ CoinNotationPipe, AutoFocusDirective, PasswordValidationDirective, GenericModalComponent ], + exports: [ + CommonModule, ReactiveFormsModule, FormsModule, NgbModule, NgxElectronModule, NgxQRCodeModule, + NgxPaginationModule, ClipboardModule, GenericModalComponent, CoinNotationPipe, AutoFocusDirective, PasswordValidationDirective + ], + entryComponents: [ GenericModalComponent ] }) -export class SharedModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: SharedModule, - providers: [] - }; - } -} +export class SharedModule { } diff --git a/Breeze.UI/src/app/wallet/dashboard/dashboard.component.ts b/Breeze.UI/src/app/wallet/dashboard/dashboard.component.ts index c7b18b57..abb1f985 100644 --- a/Breeze.UI/src/app/wallet/dashboard/dashboard.component.ts +++ b/Breeze.UI/src/app/wallet/dashboard/dashboard.component.ts @@ -6,11 +6,11 @@ import { ModalService } from '../../shared/services/modal.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ReceiveComponent } from '../receive/receive.component'; import { SendComponent } from '../send/send.component'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; import { TransactionDetailsComponent } from '../transaction-details/transaction-details.component'; import { TransactionInfo } from '../../shared/classes/transaction-info'; import { WalletInfo } from '../../shared/classes/wallet-info'; -import { Transaction } from 'interfaces/transaction'; +import { Transaction } from '../../../interfaces/transaction'; @Component({ // tslint:disable-next-line:component-selector @@ -62,34 +62,23 @@ export class DashboardComponent implements OnInit, OnDestroy { this.walletBalanceSubscription = this.apiService.getWalletBalance(walletInfo) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - const balanceResponse = response.json(); - this.confirmedBalance = balanceResponse.balances[0].amountConfirmed; - this.unconfirmedBalance = balanceResponse.balances[0].amountUnconfirmed; - } + const balanceResponse = response; + this.confirmedBalance = balanceResponse.balances[0].amountConfirmed; + this.unconfirmedBalance = balanceResponse.balances[0].amountUnconfirmed; }, error => { - console.log(error); if (error.status === 0) { this.cancelSubscriptions(); - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get wallet balance. Reason: API is not responding or timing out.', null)); } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - if (error.json().errors[0].description) { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } else { - this.cancelSubscriptions(); - this.startSubscriptions(); - } + if (!error.error.errors[0].message) { + this.cancelSubscriptions(); + this.startSubscriptions(); } } } ) - ; - }; + ; + } // todo: add history in seperate service to make it reusable private getHistory() { @@ -98,34 +87,23 @@ export class DashboardComponent implements OnInit, OnDestroy { this.walletHistorySubscription = this.apiService.getWalletHistory(walletInfo) .subscribe( response => { - if (response.status >= 200 && response.status < 400) { - if (response.json().transactionsHistory.length > 0) { - historyResponse = response.json().transactionsHistory; - this.getTransactionInfo(historyResponse); - } + if (!!response.history && response.history[0].transactionsHistory.length > 0) { + historyResponse = response.history[0].transactionsHistory; + this.getTransactionInfo(historyResponse); } }, error => { - console.log(error); if (error.status === 0) { this.cancelSubscriptions(); - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get wallet history. Reason: API is not responding or timing out.', null)); } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - if (error.json().errors[0].description) { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } else { - this.cancelSubscriptions(); - this.startSubscriptions(); - } + if (!error.error.errors[0].message) { + this.cancelSubscriptions(); + this.startSubscriptions(); } } } ) - ; + ; }; private getTransactionInfo(transactions: Array) { diff --git a/Breeze.UI/src/app/wallet/history/history.component.ts b/Breeze.UI/src/app/wallet/history/history.component.ts index 2ff4166f..0bf40768 100644 --- a/Breeze.UI/src/app/wallet/history/history.component.ts +++ b/Breeze.UI/src/app/wallet/history/history.component.ts @@ -4,13 +4,13 @@ import { Error } from '../../shared/classes/error'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { formValidator } from '../../shared/helpers/form-validation-helper'; import { GlobalService } from '../../shared/services/global.service'; -import { Transaction } from 'interfaces/transaction' import { ModalService } from '../../shared/services/modal.service'; import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; import { TransactionDetailsComponent } from '../transaction-details/transaction-details.component'; import { TransactionInfo } from '../../shared/classes/transaction-info'; import { WalletInfo } from '../../shared/classes/wallet-info'; +import { Transaction } from '../../../interfaces/transaction'; @Component({ @@ -73,30 +73,21 @@ export class HistoryComponent implements OnInit, OnDestroy { .subscribe( response => { if (response.status >= 200 && response.status < 400) { - if (response.json().transactionsHistory.length > 0) { + if (response.transactionsHistory.length > 0) { if (!this.isSearching) { - historyResponse = response.json().transactionsHistory; + historyResponse = response.transactionsHistory; this.getTransactionInfo(historyResponse); } } } }, error => { - console.log(error); if (error.status === 0) { this.cancelSubscriptions(); - this.genericModalService.openModal( - Error.toDialogOptions('Failed to get wallet history. Reason: API is not responding or timing out.', null)); } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.log(error); - } else { - if (error.json().errors[0].description) { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } else { - this.cancelSubscriptions(); - this.startSubscriptions(); - } + if (!error.error.errors[0].message) { + this.cancelSubscriptions(); + this.startSubscriptions(); } } } diff --git a/Breeze.UI/src/app/wallet/menu/menu.component.html b/Breeze.UI/src/app/wallet/menu/menu.component.html index a835bcc9..e3c5281b 100644 --- a/Breeze.UI/src/app/wallet/menu/menu.component.html +++ b/Breeze.UI/src/app/wallet/menu/menu.component.html @@ -3,13 +3,13 @@ - + diff --git a/Breeze.UI/src/app/wallet/tumblebit/connection-progress/connection-progress.component.ts b/Breeze.UI/src/app/wallet/tumblebit/connection-progress/connection-progress.component.ts index 2ce9cfa1..93288775 100644 --- a/Breeze.UI/src/app/wallet/tumblebit/connection-progress/connection-progress.component.ts +++ b/Breeze.UI/src/app/wallet/tumblebit/connection-progress/connection-progress.component.ts @@ -1,9 +1,5 @@ import { Component, OnDestroy, Input } from '@angular/core'; - -import { Observable } from 'rxjs/Observable'; -import { Subscription } from 'rxjs/Subscription'; -import 'rxjs/observable/interval'; -import 'rxjs/add/operator/filter'; +import { Observable, Subscription, interval } from 'rxjs'; @Component({ selector: 'app-connection-progress', @@ -18,6 +14,7 @@ export class ConnectionProgressComponent implements OnDestroy { private _durationSeconds = 0; private _info = ""; private intervalSubscription: Subscription; + private pollingInterval = interval(3000); ngOnDestroy() { if (this.intervalSubscription) { @@ -30,7 +27,7 @@ export class ConnectionProgressComponent implements OnDestroy { } get progressWidth(): string { - return `${this.progress}%`; + return `${this.progress}%`; } get info(): string { @@ -55,11 +52,11 @@ export class ConnectionProgressComponent implements OnDestroy { private onRunChanged() { if (this.intervalSubscription) { this.intervalSubscription.unsubscribe(); - this.intervalSubscription = null; - } + this.intervalSubscription = null; + } if (this._run) { this._progressSeconds = this._durationSeconds; - this.intervalSubscription = Observable.interval(1000).subscribe(_ => this.onInterval()); + this.intervalSubscription = this.pollingInterval.subscribe(_ => this.onInterval()); } } @@ -75,18 +72,18 @@ export class ConnectionProgressComponent implements OnDestroy { const difference = this._durationSeconds - this._progressSeconds; const result = Math.ceil((difference / this._durationSeconds) * oneHundred); if (result <= oneHundred) { - this._progress = result; + this._progress = result; this.makeInfo(); if (result === oneHundred) { this.run = false; - } + } } } private makeInfo() { let secondsRemaining = this._durationSeconds - this._progressSeconds; secondsRemaining = this._durationSeconds - secondsRemaining; - + let date = new Date(null); date.setSeconds(secondsRemaining); const remaining = `${date.getMinutes()}m:${date.getSeconds()}s`; diff --git a/Breeze.UI/src/app/wallet/tumblebit/password-confirmation/password-confirmation.component.ts b/Breeze.UI/src/app/wallet/tumblebit/password-confirmation/password-confirmation.component.ts index f9463b07..d23fe22b 100644 --- a/Breeze.UI/src/app/wallet/tumblebit/password-confirmation/password-confirmation.component.ts +++ b/Breeze.UI/src/app/wallet/tumblebit/password-confirmation/password-confirmation.component.ts @@ -123,21 +123,8 @@ export class PasswordConfirmationComponent { } }, error => { - console.error(error); - if (error.status === 0) { - this.startingTumble = false; - this.genericModalService.openModal( - Error.toDialogOptions('Failed to start tumbling. Reason: API is not responding or timing out.', null)); - } else if (error.status >= 400) { - if (!error.json().errors[0]) { - console.error(error); - this.startingTumble = false; - } else { - this.startingTumble = false; - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } - } - }, + this.startingTumble = false; + } ) ; } diff --git a/Breeze.UI/src/app/wallet/tumblebit/tumblebit.component.ts b/Breeze.UI/src/app/wallet/tumblebit/tumblebit.component.ts index 9c811902..3939dfd2 100644 --- a/Breeze.UI/src/app/wallet/tumblebit/tumblebit.component.ts +++ b/Breeze.UI/src/app/wallet/tumblebit/tumblebit.component.ts @@ -1,12 +1,8 @@ import { Router, NavigationEnd, RouterEvent } from '@angular/router'; import { Component, OnInit, OnDestroy } from '@angular/core'; import { NgbModal, NgbActiveModal, NgbDropdown, NgbModalRef, NgbModalOptions } from '@ng-bootstrap/ng-bootstrap'; -import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; -import { Subscription } from 'rxjs/Subscription'; -import { ReplaySubject } from 'rxjs/ReplaySubject'; -import 'rxjs/add/operator/filter'; -import 'rxjs/add/operator/takeUntil'; -import { forkJoin } from 'rxjs/observable/forkJoin'; +import { FormGroup, Validators, FormBuilder } from '@angular/forms'; +import { Subscription, ReplaySubject, forkJoin, interval } from 'rxjs'; import { PasswordConfirmationComponent } from './password-confirmation/password-confirmation.component'; import { ConnectionModalComponent } from '../../shared/components/connection-modal/connection-modal.component'; @@ -14,15 +10,12 @@ import { ConnectionModalComponent } from '../../shared/components/connection-mod import { ApiService } from '../../shared/services/api.service'; import { GlobalService } from '../../shared/services/global.service'; import { WalletInfo } from '../../shared/classes/wallet-info'; -import { Error } from '../../shared/classes/error'; import { TumblebitService } from './tumblebit.service'; -import { TumblerConnectionRequest } from './classes/tumbler-connection-request'; -import { TumbleRequest } from './classes/tumble-request'; import { ConnectRequest } from './classes/connect-request'; import { CycleInfo } from './classes/cycle-info'; import { ModalService } from '../../shared/services/modal.service'; import { CompositeDisposable } from '../../shared/classes/composite-disposable'; -import { Observable } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; @Component({ // tslint:disable-next-line:component-selector @@ -121,10 +114,10 @@ export class TumblebitComponent implements OnDestroy { } const routerEvents = this.router.events; - const $1 = routerEvents.filter(x => this.started && TumblebitComponent.isNavigationEnd(x, this.loginPath)) + const $1 = routerEvents.pipe(filter(x => this.started && TumblebitComponent.isNavigationEnd(x, this.loginPath))) .subscribe(_ => this.stop()); - const $2 = routerEvents.filter(x => !this.started && TumblebitComponent.isNavigationEnd(x, this.routerPath)) + const $2 = routerEvents.pipe(filter(x => !this.started && TumblebitComponent.isNavigationEnd(x, this.routerPath))) .subscribe(_ => { this.destroyed$ = new ReplaySubject(); @@ -216,41 +209,29 @@ export class TumblebitComponent implements OnDestroy { private checkWalletStatus(): Subscription { const walletInfo = new WalletInfo(this.globalService.getWalletName()); - return Observable.interval(this.apiService.pollingInterval) - .subscribe(x => this.getWalletInfos(walletInfo)); + return interval(3000) + .subscribe(x => this.getWalletInfos(walletInfo)); } private getWalletInfos(walletInfo: WalletInfo): void { if (this.walletInfosSubscription) { this.walletInfosSubscription.unsubscribe(); } - this.walletInfosSubscription = forkJoin(this.apiService.getGeneralInfoForCoin(walletInfo, 'Bitcoin'), + this.walletInfosSubscription = forkJoin(this.apiService.getGeneralInfoForCoin(walletInfo, 'Bitcoin'), this.apiService.getGeneralInfoForCoin(walletInfo, 'Stratis')). - subscribe(x => this.onCoinsGeneralInfo(x[0], x[1]), + subscribe(x => this.onCoinsGeneralInfo(x[0], x[1]), e => this.onPollingError(e, 'Failed to get general wallet information. Reason: API is not responding or timing out.')); - + } private onCoinsGeneralInfo(bitcoinInfo, stratisInfo) { - this.isSynced = TumblebitComponent.isCoinSynced(bitcoinInfo) && + this.isSynced = TumblebitComponent.isCoinSynced(bitcoinInfo) && TumblebitComponent.isCoinSynced(stratisInfo); } private onPollingError(error: any, errorMessage: string) { console.log(error); - if (error.status === 0) { - this.genericModalService.openModal(Error.toDialogOptions(errorMessage, null)); - } else if (error.status >= 400) { - const firstError = Error.getFirstError(error); - if (!firstError) { - console.log(error); - } else { - if (firstError.description) { - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - } } - } - } private static isCoinSynced(coinInfo: any) { return coinInfo.lastBlockSyncedHeight === coinInfo.chainTip; @@ -261,34 +242,31 @@ export class TumblebitComponent implements OnDestroy { this.hasRegistrations = this.tumbling = false; return this.tumblebitService.getTumblingState() - .takeUntil(this.destroyed$) + .pipe(takeUntil(this.destroyed$)) .subscribe( response => { + if (response.registrations >= response.minRegistrations) { + this.hasRegistrations = true; + } else { + this.hasRegistrations = false; + } - if (response.status >= 200 && response.status < 400) { - if (response.json().registrations >= response.json().minRegistrations) { - this.hasRegistrations = true; - } else { - this.hasRegistrations = false; - } + if (!this.isConnected && this.isSynced) { + this.connectToTumbler(); + } - if (!this.isConnected && this.isSynced) { - this.connectToTumbler(); + if (response.state === 'OnlyMonitor') { + this.tumbling = false; + if (this.progressSubscription) { + this.progressSubscription.unsubscribe(); } - - if (response.json().state === 'OnlyMonitor') { - this.tumbling = false; - if (this.progressSubscription) { - this.progressSubscription.unsubscribe(); - } - } else if (response.json().state === 'Tumbling') { - this.tumbling = true; - if (!this.progressSubscription) { - this.getProgress(); - } - this.destinationWalletName = response.json().destinationWallet; - this.getDestinationWalletBalance(); + } else if (response.state === 'Tumbling') { + this.tumbling = true; + if (!this.progressSubscription) { + this.getProgress(); } + this.destinationWalletName = response.destinationWallet; + this.getDestinationWalletBalance(); } }, e => this.onPollingError(e, 'Failed to get tumbling state. Reason: API is not responding or timing out.') @@ -310,68 +288,60 @@ export class TumblebitComponent implements OnDestroy { const connectRequest = new ConnectRequest( this.globalService.getWalletName() ); - + this.connectionSubscription = this.tumblebitService .connectToTumbler(this.operation, connectRequest) .subscribe( // TODO abstract into shared utility method response => { this.connectionFatalError = response.status >= 400; - if (response.status >= 200 && response.status < 400) { - this.tumblerParameters = response.json(); - this.tumblerAddress = this.tumblerParameters.tumbler - this.estimate = this.tumblerParameters.estimate; - this.fee = this.tumblerParameters.fee; - this.denomination = this.tumblerParameters.denomination; - this.parametersAreStandard = this.tumblerParameters.parameters_are_standard.toLowerCase() === "true"; - - if (!!this.connectionModal) { - this.connectionModal.dismiss(); - } + this.tumblerParameters = response; + this.tumblerAddress = this.tumblerParameters.tumbler + this.estimate = this.tumblerParameters.estimate; + this.fee = this.tumblerParameters.fee; + this.denomination = this.tumblerParameters.denomination; + this.parametersAreStandard = this.tumblerParameters.parameters_are_standard.toLowerCase() === "true"; + + if (!!this.connectionModal) { + this.connectionModal.dismiss(); + } + + if (this.tumbling) { + this.markAsConnected(); + return; + } - if (this.tumbling) { + const ngbModalOptions: NgbModalOptions = { + backdrop : 'static', + keyboard : false + }; + this.connectionModal = this.modalService.open(ConnectionModalComponent, ngbModalOptions); + this.connectionModal.componentInstance.server = this.tumblerAddress; + this.connectionModal.componentInstance.denomination = this.denomination; + this.connectionModal.componentInstance.fees = this.fee; + this.connectionModal.componentInstance.estimatedTime = this.estimate; + this.connectionModal.componentInstance.coinUnit = this.coinUnit; + this.connectionModal.componentInstance.isStandardServer = this.parametersAreStandard; + this.connectionModal.result.then(result => { + this.stopConnectionRequest(); + if (result === 'skip') { + this.markAsServerChangeRequired(); + } else { this.markAsConnected(); - return; } - - const ngbModalOptions: NgbModalOptions = { - backdrop : 'static', - keyboard : false - }; - this.connectionModal = this.modalService.open(ConnectionModalComponent, ngbModalOptions); - this.connectionModal.componentInstance.server = this.tumblerAddress; - this.connectionModal.componentInstance.denomination = this.denomination; - this.connectionModal.componentInstance.fees = this.fee; - this.connectionModal.componentInstance.estimatedTime = this.estimate; - this.connectionModal.componentInstance.coinUnit = this.coinUnit; - this.connectionModal.componentInstance.isStandardServer = this.parametersAreStandard; - this.connectionModal.result.then(result => { - this.stopConnectionRequest(); - if (result === 'skip') { - this.markAsServerChangeRequired(); - } else { - this.markAsConnected(); - } - }); - } + }); }, error => { - this.stop(); - console.error(error); this.isConnected = false; this.connectionFatalError = error.status >= 400; if (error.status === 0 && !this.connectionFatalError) { - this.genericModalService.openModal( - Error.toDialogOptions('Failed to connect to tumbler. Reason: API is not responding or timing out.', null)); + } else if (error.status >= 400) { - if (!error.json().errors[0]) { + if (!error.errors[0]) { console.error(error); } else { - - this.genericModalService.openModal(Error.toDialogOptions(error, null)); - this.router.navigate(['/wallet']); this.started = false; this.destroyed$.next(true); @@ -389,7 +359,7 @@ export class TumblebitComponent implements OnDestroy { } if (!this.isConnected) { - this.genericModalService.openModal({ body: 'Can\'t start tumbling when you\'re not connected to a server. Please try again later.'}); + //this.genericModalService.openModal({ body: 'Can\'t start tumbling when you\'re not connected to a server. Please try again later.'}); } else { const modalRef = this.modalService.open(PasswordConfirmationComponent); modalRef.componentInstance.sourceWalletName = this.globalService.getWalletName(); @@ -402,24 +372,24 @@ export class TumblebitComponent implements OnDestroy { } private stopTumbling() { - this.genericModalService.confirm( - { - title: 'Are you sure you want to proceed?', - body: - 'By stopping all current cycles, any current funds that are mid-cycle may take up to 12 hours to reimburse depending on the phase.' - }, - () => { - this.tumblebitService.stopTumbling() - .subscribe( - response => { - if (response.status >= 200 && response.status < 400) { - this.tumbling = false; - this.progressSubscription.unsubscribe(); - } - }, - e => this.onPollingError(e, 'Failed to stop tumbler. Reason: API is not responding or timing out.') - ); - }); + // this.genericModalService.confirm( + // { + // title: 'Are you sure you want to proceed?', + // body: + // 'By stopping all current cycles, any current funds that are mid-cycle may take up to 12 hours to reimburse depending on the phase.' + // }, + // () => { + // this.tumblebitService.stopTumbling() + // .subscribe( + // response => { + // if (response.status >= 200 && response.status < 400) { + // this.tumbling = false; + // this.progressSubscription.unsubscribe(); + // } + // }, + // e => this.onPollingError(e, 'Failed to stop tumbler. Reason: API is not responding or timing out.') + // ); + // }); } private markAsServerChangeRequired() { @@ -440,7 +410,7 @@ export class TumblebitComponent implements OnDestroy { .subscribe( response => { if (response.status >= 200 && response.status < 400) { - const responseArray = response.json().cycleProgressInfoList; + const responseArray = response.cycleProgressInfoList; if (responseArray) { this.progressDataArray = []; const responseData = responseArray; @@ -466,11 +436,11 @@ export class TumblebitComponent implements OnDestroy { cycleAsciiArt, cycleStatus, cyclePhase, - cyclePhaseNumber, + cyclePhaseNumber, cycle.shouldStayConnected); - + this.progressDataArray.push(item); - + this.progressDataArray.sort(function(cycle1, cycle2) { return cycle1.cycleStart - cycle2.cycleStart; }); @@ -481,7 +451,7 @@ export class TumblebitComponent implements OnDestroy { this.shouldStayConnected = true; break; } - } + } } } }, @@ -510,22 +480,22 @@ export class TumblebitComponent implements OnDestroy { let result; switch (phase) { case 'Registration': - result = 'Registration'; + result = 'Registration'; break; case 'ClientChannelEstablishment': - result = 'Client Channel Establishment'; + result = 'Client Channel Establishment'; break; case 'TumblerChannelEstablishment': - result = 'Tumbler Channel Establishment'; + result = 'Tumbler Channel Establishment'; break; case 'PaymentPhase': - result = 'Payment Phase'; + result = 'Payment Phase'; break; case 'TumblerCashoutPhase': - result = 'Tumbler Cashout Phase'; + result = 'Tumbler Cashout Phase'; break; case 'ClientCashoutPhase': - result = 'Client Cashout Phase'; + result = 'Client Cashout Phase'; break; } return result && isSafetyPeriod ? `${result} Safety Period` : result; @@ -539,7 +509,7 @@ export class TumblebitComponent implements OnDestroy { response => { if (response.status >= 200 && response.status < 400) { var milli = new Date().getMilliseconds(); - const balanceResponse = response.json(); + const balanceResponse = response; this.confirmedBalance = balanceResponse.balances[0].amountConfirmed; this.unconfirmedBalance = balanceResponse.balances[0].amountUnconfirmed; this.totalBalance = this.confirmedBalance + this.unconfirmedBalance; @@ -559,7 +529,7 @@ export class TumblebitComponent implements OnDestroy { .subscribe( response => { if (response.status >= 200 && response.status < 400) { - const balanceResponse = response.json(); + const balanceResponse = response; this.destinationConfirmedBalance = balanceResponse.balances[0].amountConfirmed; this.destinationUnconfirmedBalance = balanceResponse.balances[0].amountUnconfirmed; this.destinationTotalBalance = this.destinationConfirmedBalance + this.destinationUnconfirmedBalance; @@ -575,7 +545,7 @@ export class TumblebitComponent implements OnDestroy { .subscribe( response => { if (response.status >= 200 && response.status < 400) { - const responseMessage = response.json(); + const responseMessage = response; this.wallets = responseMessage.walletsFiles; if (this.wallets.length > 0) { for (const wallet in this.wallets) { @@ -615,4 +585,4 @@ export class TumblebitComponent implements OnDestroy { } this.connectionInProgress = false; } -} \ No newline at end of file +} diff --git a/Breeze.UI/src/app/wallet/tumblebit/tumblebit.service.ts b/Breeze.UI/src/app/wallet/tumblebit/tumblebit.service.ts index c001168a..b5986062 100644 --- a/Breeze.UI/src/app/wallet/tumblebit/tumblebit.service.ts +++ b/Breeze.UI/src/app/wallet/tumblebit/tumblebit.service.ts @@ -1,107 +1,100 @@ import { Injectable } from '@angular/core'; import { Http, Headers, Response, RequestOptions, URLSearchParams } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/mergeMap'; -import 'rxjs/add/operator/take'; -import 'rxjs/add/operator/concat'; -import 'rxjs/add/observable/throw'; -import { TumblerConnectionRequest } from './classes/tumbler-connection-request'; +import { Observable, of, interval } from 'rxjs'; +import { delay, retryWhen, map, mergeMap, take, concat, startWith, switchMap } from 'rxjs/operators'; import { TumbleRequest } from './classes/tumble-request'; import { ConnectRequest } from './classes/connect-request'; import { GlobalService } from '../../shared/services/global.service'; -import { Error } from '../../shared/classes/error'; import { ServiceShared } from '../../shared/services/shared'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class TumblebitService { // The service to connect to & operate a TumbleBit Server via the // TumbleBit.Client.CLI tool private headers = new Headers({ 'Content-Type': 'application/json' }); - private pollingInterval = 3000; + private pollingInterval = interval(3000); constructor(private http: Http, private globalService: GlobalService) { }; get tumblerClientUrl() { - return `http://localhost:${this.globalService.bitcoinApiPort}/api/TumbleBit/`; + const bitcoinApiPort = this.globalService.getBitcoinApiPort(); + return `http://localhost:${bitcoinApiPort}/api/TumbleBit/`; } // Might make sense to populate tumblerParams here because services are singletons connectToTumbler(operation: 'connect' | 'changeserver', body: ConnectRequest): Observable { return this.http .post(`${this.tumblerClientUrl}${operation}`, JSON.stringify(body), {headers: this.headers}) - .retryWhen(e => { + .pipe(retryWhen(e => { return e - .mergeMap((error: any) => { + .pipe(mergeMap((error: any) => { if (error.status === 0) { - return Observable.of(error.status).delay(5000) + return of(error.status).pipe(delay(5000)) } return Observable.throw(error); - }) - .take(5) - .concat(Observable.throw(e)); - }) - .map((response: Response) => response); + }), + take(5), + concat(Observable.throw(e))) + }), + map((response: Response) => response)); }; changeTumblerServer(body: ConnectRequest): Observable { return this.http .post(`${this.tumblerClientUrl}changeserver`, JSON.stringify(body), {headers: this.headers}) - .retryWhen(e => { - return e - .mergeMap((error: any) => { + .pipe(retryWhen(e => { + return e.pipe( + mergeMap((error: any) => { if (error.status === 0) { - return Observable.of(error.status).delay(5000) + return of(error.status).pipe(delay(5000)) } return Observable.throw(error); - }) - .take(5) - .concat(Observable.throw(e)); - }) - .map((response: Response) => response); + }), + take(5), + concat(Observable.throw(e))); + }), + map((response: Response) => response)); }; getTumblingState(): Observable { - return Observable - .interval(1000) - .startWith(0) - .switchMap( + return this.pollingInterval.pipe( + startWith(0), + switchMap( () => this.http.get(`${this.tumblerClientUrl}tumbling-state`) - .retryWhen(e => ServiceShared.onRetryWhen(e))) - .map((response: Response) => response); + .pipe(retryWhen(e => ServiceShared.onRetryWhen(e)))), + map((response: Response) => response)); } - + startTumbling(body: TumbleRequest): Observable { return this.http .post(`${this.tumblerClientUrl}tumble`, JSON.stringify(body), {headers: this.headers}) - .map((response: Response) => response); + .pipe(map((response: Response) => response)); }; - + stopTumbling(): Observable { return this.http .get(`${this.tumblerClientUrl}onlymonitor`) - .map((response: Response) => response); + .pipe(map((response: Response) => response)); } - + getProgress(): Observable { - return Observable - .interval(this.pollingInterval) - .startWith(0) - .switchMap( + return this.pollingInterval.pipe( + startWith(0), + switchMap( () => this.http.get(`${this.tumblerClientUrl}progress`) - .retryWhen(e => ServiceShared.onRetryWhen(e))) - .map((response: Response) => response); + .pipe(retryWhen(e => ServiceShared.onRetryWhen(e)))), + (map((response: Response) => response))); } getWalletDestinationBalance(data: string): Observable { const params: URLSearchParams = new URLSearchParams(); params.set('walletName', data); - return Observable - .interval(this.pollingInterval) - .startWith(0) - .switchMap( + return this.pollingInterval.pipe( + startWith(0), + switchMap( () => this.http.get(`${this.tumblerClientUrl}destination-balance`, new RequestOptions({headers: this.headers, search: params})) - .retryWhen(e => ServiceShared.onRetryWhen(e))) - .map((response: Response) => response); + .pipe(retryWhen(e => ServiceShared.onRetryWhen(e)))), + map((response: Response) => response)); } - -} \ No newline at end of file +} diff --git a/Breeze.UI/src/app/wallet/wallet-routing.module.ts b/Breeze.UI/src/app/wallet/wallet-routing.module.ts index 0dd1f962..cc5df367 100644 --- a/Breeze.UI/src/app/wallet/wallet-routing.module.ts +++ b/Breeze.UI/src/app/wallet/wallet-routing.module.ts @@ -1,29 +1,23 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; - -import { WalletComponent } from './wallet.component'; -import { SendComponent } from './send/send.component'; -import { ReceiveComponent } from './receive/receive.component'; +import { WalletComponent } from './wallet.component'; import { HistoryComponent } from './history/history.component'; -import { TumblebitComponent } from './tumblebit/tumblebit.component'; import { DashboardComponent } from './dashboard/dashboard.component'; +import { TumblebitComponent } from './tumblebit/tumblebit.component'; const routes: Routes = [ - { path: '', component: WalletComponent, - children: [ - { path: '', redirectTo:'dashboard', pathMatch:'full' }, - { path: 'dashboard', component: DashboardComponent, data: { shouldReuse: false } }, - { path: 'privacy', component: TumblebitComponent, data: { shouldReuse: true } }, - { path: 'history', component: HistoryComponent, data: { shouldReuse: false } } - ] - }, - { path: 'stratis-wallet', component: WalletComponent, - children: [ - { path: '', redirectTo:'dashboard', pathMatch:'full' }, - { path: 'dashboard', component: DashboardComponent, data: { shouldReuse: false } }, - { path: 'history', component: HistoryComponent, data: { shouldReuse: false } } - ] -} + { path: 'wallet', component: WalletComponent, children: [ + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent, data: { shouldReuse: false } } , + { path: 'history', component: HistoryComponent, data: { shouldReuse: false } }, + { path: 'privacy', component: TumblebitComponent, data: { shouldReuse: true } }, + ]}, + { path: 'stratis-wallet', component: WalletComponent, children: [ + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent, data: { shouldReuse: false } } , + { path: 'history', component: HistoryComponent, data: { shouldReuse: false } }, + { path: 'privacy', component: TumblebitComponent, data: { shouldReuse: true } }, + ]} ]; @NgModule({ diff --git a/Breeze.UI/src/app/wallet/wallet.module.ts b/Breeze.UI/src/app/wallet/wallet.module.ts index 61ea8dad..b7db6c35 100644 --- a/Breeze.UI/src/app/wallet/wallet.module.ts +++ b/Breeze.UI/src/app/wallet/wallet.module.ts @@ -1,8 +1,4 @@ -import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ClipboardModule } from 'ngx-clipboard'; import { WalletComponent } from './wallet.component'; import { MenuComponent } from './menu/menu.component'; @@ -10,21 +6,21 @@ import { DashboardComponent } from './dashboard/dashboard.component'; import { TumblebitComponent } from './tumblebit/tumblebit.component'; import { HistoryComponent } from './history/history.component'; -import { SharedModule } from '../shared/shared.module'; import { WalletRoutingModule } from './wallet-routing.module'; import { SidebarComponent } from './sidebar/sidebar.component'; import { StatusBarComponent } from './status-bar/status-bar.component'; -import { TransactionDetailsComponent } from './transaction-details/transaction-details.component'; import { ConnectionProgressComponent } from './tumblebit/connection-progress/connection-progress.component'; +import { SharedModule } from '../shared/shared.module'; +import { LicenseAgreementComponent } from './license-agreement/license-agreement.component'; +import { LogoutConfirmationComponent } from './logout-confirmation/logout-confirmation.component'; +import { ReceiveComponent } from './receive/receive.component'; +import { SendComponent } from './send/send.component'; +import { SendConfirmationComponent } from './send/send-confirmation/send-confirmation.component'; +import { TransactionDetailsComponent } from './transaction-details/transaction-details.component'; @NgModule({ imports: [ - CommonModule, - ClipboardModule, - FormsModule, - SharedModule.forRoot(), - NgbModule, - ReactiveFormsModule, + SharedModule, WalletRoutingModule ], declarations: [ @@ -35,9 +31,22 @@ import { ConnectionProgressComponent } from './tumblebit/connection-progress/con HistoryComponent, SidebarComponent, StatusBarComponent, - ConnectionProgressComponent + ConnectionProgressComponent, + LicenseAgreementComponent, + LogoutConfirmationComponent, + ReceiveComponent, + SendComponent, + SendConfirmationComponent, + TransactionDetailsComponent, + TumblebitComponent ], - exports: [] + entryComponents: [ + SendComponent, + SendConfirmationComponent, + ReceiveComponent, + TransactionDetailsComponent, + LogoutConfirmationComponent + ] }) export class WalletModule { } diff --git a/Breeze.UI/src/app/icons/information.png b/Breeze.UI/src/assets/images/information.png similarity index 100% rename from Breeze.UI/src/app/icons/information.png rename to Breeze.UI/src/assets/images/information.png diff --git a/Breeze.UI/src/environments/environment.dev.ts b/Breeze.UI/src/environments/environment.dev.ts new file mode 100644 index 00000000..7953ebf8 --- /dev/null +++ b/Breeze.UI/src/environments/environment.dev.ts @@ -0,0 +1,9 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `index.ts`, but if you do +// `ng build --env=prod` then `index.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const AppConfig = { + production: false, + environment: 'DEV' +}; diff --git a/Breeze.UI/src/environments/environment.prod.ts b/Breeze.UI/src/environments/environment.prod.ts new file mode 100644 index 00000000..047b3fce --- /dev/null +++ b/Breeze.UI/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const AppConfig = { + production: true, + environment: 'PROD' +}; diff --git a/Breeze.UI/src/environments/environment.ts b/Breeze.UI/src/environments/environment.ts new file mode 100644 index 00000000..ca0b6a9c --- /dev/null +++ b/Breeze.UI/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const AppConfig = { + production: false, + environment: 'LOCAL' +}; diff --git a/Breeze.UI/src/environments/index.prod.ts b/Breeze.UI/src/environments/index.prod.ts deleted file mode 100644 index 37a0dc02..00000000 --- a/Breeze.UI/src/environments/index.prod.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const environment = { - production: true, - silent: false -}; diff --git a/Breeze.UI/src/environments/index.ts b/Breeze.UI/src/environments/index.ts deleted file mode 100644 index 9d71813a..00000000 --- a/Breeze.UI/src/environments/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// The file contents for the current environment will overwrite these during build. -// The build system defaults to the dev environment which uses `environment.ts`, but if you do -// `ng build --env=prod` then `environment.prod.ts` will be used instead. -// The list of which env maps to which file can be found in `angular-cli.json`. - -export const environment = { - production: false, - silent: false -}; diff --git a/Breeze.UI/src/index.html b/Breeze.UI/src/index.html index 62270eb2..d584978b 100644 --- a/Breeze.UI/src/index.html +++ b/Breeze.UI/src/index.html @@ -1,6 +1,7 @@ + Breeze Wallet diff --git a/Breeze.UI/karma.conf.js b/Breeze.UI/src/karma.conf.js similarity index 58% rename from Breeze.UI/karma.conf.js rename to Breeze.UI/src/karma.conf.js index 84b4cd5a..886ca1b0 100644 --- a/Breeze.UI/karma.conf.js +++ b/Breeze.UI/src/karma.conf.js @@ -4,41 +4,28 @@ module.exports = function (config) { config.set({ basePath: '', - frameworks: ['jasmine', '@angular/cli'], + frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), - require('@angular/cli/plugins/karma') + require('@angular-devkit/build-angular/plugins/karma') ], client:{ clearContext: false // leave Jasmine Spec Runner output visible in browser }, - files: [ - { pattern: './src/test.ts', watched: false } - ], - preprocessors: { - './src/test.ts': ['@angular/cli'] - }, - mime: { - 'text/x-typescript': ['ts','tsx'] - }, coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true }, - angularCli: { - environment: 'dev' - }, - reporters: config.angularCli && config.angularCli.codeCoverage - ? ['progress', 'coverage-istanbul'] - : ['progress', 'kjhtml'], + reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], - singleRun: false + singleRun: true }); }; diff --git a/Breeze.UI/src/main.ts b/Breeze.UI/src/main.ts index 1a6eec68..6ad7b100 100644 --- a/Breeze.UI/src/main.ts +++ b/Breeze.UI/src/main.ts @@ -2,9 +2,9 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -import { environment } from 'environments'; +import { AppConfig } from './environments/environment'; -if (environment.production) { +if (AppConfig.production) { enableProdMode(); } diff --git a/Breeze.UI/src/polyfills-test.ts b/Breeze.UI/src/polyfills-test.ts new file mode 100644 index 00000000..a386ad53 --- /dev/null +++ b/Breeze.UI/src/polyfills-test.ts @@ -0,0 +1,2 @@ +import 'core-js/es7/reflect'; +import 'zone.js/dist/zone'; diff --git a/Breeze.UI/src/polyfills.ts b/Breeze.UI/src/polyfills.ts index 53bdaf1b..ee8b84da 100644 --- a/Breeze.UI/src/polyfills.ts +++ b/Breeze.UI/src/polyfills.ts @@ -11,7 +11,7 @@ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** @@ -31,38 +31,50 @@ // import 'core-js/es6/array'; // import 'core-js/es6/regexp'; // import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; // import 'core-js/es6/set'; +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. -/** IE10 and IE11 requires the following to support `@angular/animation`. */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - - -/** Evergreen browsers require these. **/ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; - -/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; /*************************************************************************************************** - * Zone JS is required by Angular itself. + * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. - /*************************************************************************************************** * APPLICATION IMPORTS */ - -/** - * Date, currency, decimal and percent pipes. - * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 - */ -// import 'intl'; // Run `npm install --save intl`. diff --git a/Breeze.UI/src/test.ts b/Breeze.UI/src/test.ts index cd612eeb..16317897 100644 --- a/Breeze.UI/src/test.ts +++ b/Breeze.UI/src/test.ts @@ -1,24 +1,14 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files -import 'zone.js/dist/long-stack-trace-zone'; -import 'zone.js/dist/proxy.js'; -import 'zone.js/dist/sync-test'; -import 'zone.js/dist/jasmine-patch'; -import 'zone.js/dist/async-test'; -import 'zone.js/dist/fake-async-test'; +import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. -declare const __karma__: any; declare const require: any; -// Prevent Karma from running prematurely. -__karma__.loaded = function () {}; - // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, @@ -28,5 +18,3 @@ getTestBed().initTestEnvironment( const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); -// Finally, start Karma to run the tests. -__karma__.start(); diff --git a/Breeze.UI/src/tsconfig.app.json b/Breeze.UI/src/tsconfig.app.json index 5e2507db..fbae189a 100644 --- a/Breeze.UI/src/tsconfig.app.json +++ b/Breeze.UI/src/tsconfig.app.json @@ -7,7 +7,6 @@ "types": [] }, "exclude": [ - "test.ts", "**/*.spec.ts" ] } diff --git a/Breeze.UI/src/tsconfig.spec.json b/Breeze.UI/src/tsconfig.spec.json index 510e3f1f..eb2aca62 100644 --- a/Breeze.UI/src/tsconfig.spec.json +++ b/Breeze.UI/src/tsconfig.spec.json @@ -3,18 +3,22 @@ "compilerOptions": { "outDir": "../out-tsc/spec", "module": "commonjs", - "target": "es5", - "baseUrl": "", "types": [ "jasmine", "node" ] }, "files": [ - "test.ts" + "test.ts", + "polyfills-test.ts" ], "include": [ "**/*.spec.ts", "**/*.d.ts" + ], + "exclude": [ + "dist", + "release", + "node_modules" ] } diff --git a/Breeze.UI/src/typings.d.ts b/Breeze.UI/src/typings.d.ts new file mode 100644 index 00000000..78708ff3 --- /dev/null +++ b/Breeze.UI/src/typings.d.ts @@ -0,0 +1,11 @@ +/* SystemJS module definition */ +declare var nodeModule: NodeModule; +interface NodeModule { + id: string; +} + +declare var window: Window; +interface Window { + process: any; + require: any; +} diff --git a/Breeze.UI/tsconfig-serve.json b/Breeze.UI/tsconfig-serve.json new file mode 100644 index 00000000..56d7a0a7 --- /dev/null +++ b/Breeze.UI/tsconfig-serve.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "es2016", + "es2015", + "dom" + ] + }, + "include": [ + "main.ts" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] +} diff --git a/Breeze.UI/tsconfig.json b/Breeze.UI/tsconfig.json index ac943221..dc9baa07 100644 --- a/Breeze.UI/tsconfig.json +++ b/Breeze.UI/tsconfig.json @@ -2,34 +2,26 @@ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", - "baseUrl": "src", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowJs": true, "target": "es5", - "paths": { - "environments": [ - "./environments" - ], - "interfaces": [ - "./interfaces/*" - ], - }, - "types": [ - "node", - "jasmine" - ], "typeRoots": [ "node_modules/@types" ], "lib": [ + "es2017", "es2016", + "es2015", "dom" ] }, + "include": [ + "main.ts", + "src/**/*" + ], "exclude": [ "node_modules" ] diff --git a/Breeze.UI/tslint.json b/Breeze.UI/tslint.json index 9113f136..ae580808 100644 --- a/Breeze.UI/tslint.json +++ b/Breeze.UI/tslint.json @@ -3,6 +3,7 @@ "node_modules/codelyzer" ], "rules": { + "arrow-return-shorthand": true, "callable-types": true, "class-name": true, "comment-format": [ @@ -10,9 +11,15 @@ "check-space" ], "curly": true, + "deprecation": { + "severity": "warn" + }, "eofline": true, "forin": true, - "import-blacklist": [true, "rxjs"], + "import-blacklist": [ + true, + "rxjs/Rx" + ], "import-spacing": true, "indent": [ true, @@ -27,8 +34,14 @@ "member-access": false, "member-ordering": [ true, - "static-before-instance", - "variables-before-functions" + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } ], "no-arg": true, "no-bitwise": true, @@ -42,16 +55,22 @@ ], "no-construct": true, "no-debugger": true, - "no-duplicate-variable": true, + "no-duplicate-super": true, "no-empty": false, "no-empty-interface": true, "no-eval": true, - "no-inferrable-types": [true, "ignore-params"], + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, "no-shadowed-variable": true, "no-string-literal": false, "no-string-throw": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, "no-unused-expression": true, "no-use-before-declare": true, "no-var-keyword": true, @@ -70,6 +89,7 @@ ], "radix": true, "semicolon": [ + true, "always" ], "triple-equals": [ @@ -86,7 +106,6 @@ "variable-declaration": "nospace" } ], - "typeof-compare": true, "unified-signatures": true, "variable-name": false, "whitespace": [ @@ -97,9 +116,13 @@ "check-separator", "check-type" ], - - "directive-selector": [true, "attribute", "app", "camelCase"], - "component-selector": [true, "element", "app", "kebab-case"], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "no-output-on-prefix": true, "use-input-property-decorator": true, "use-output-property-decorator": true, "use-host-property-decorator": true, @@ -108,9 +131,6 @@ "use-life-cycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, - "directive-class-suffix": true, - "no-access-missing-member": true, - "templates-use-public": true, - "invoke-injectable": true + "directive-class-suffix": true } } diff --git a/Breeze.UI/webpack.config.js b/Breeze.UI/webpack.config.js deleted file mode 100644 index 6fd371f9..00000000 --- a/Breeze.UI/webpack.config.js +++ /dev/null @@ -1,549 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const ProgressPlugin = require('webpack/lib/ProgressPlugin'); -const CircularDependencyPlugin = require('circular-dependency-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const autoprefixer = require('autoprefixer'); -const postcssUrl = require('postcss-url'); -const customProperties = require('postcss-custom-properties'); - -const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, DefinePlugin, NamedModulesPlugin } = require('webpack'); -const { BaseHrefWebpackPlugin, NamedLazyChunksWebpackPlugin, InsertConcatAssetsWebpackPlugin } = require('@angular/cli/plugins/webpack'); -const { CommonsChunkPlugin } = require('webpack').optimize; -const { AngularCompilerPlugin } = require('@ngtools/webpack'); -const ConcatPlugin = require('webpack-concat-plugin'); - -const nodeModules = path.join(process.cwd(), 'node_modules'); -const realNodeModules = fs.realpathSync(nodeModules); -const genDirNodeModules = path.join(process.cwd(), 'src', '$$_gendir', 'node_modules'); -const entryPoints = ["inline", "polyfills", "sw-register", "styles", "vendor", "main"]; -const minimizeCss = false; -const baseHref = ""; -const deployUrl = ""; - -const postcssPlugins = function () { - const importantCommentRe = /@preserve|@license|[@#]\s*source(?:Mapping)?URL|^!/i; - const minimizeOptions = { - autoprefixer: false, - safe: true, - mergeLonghand: false, - discardComments: { remove: (comment) => !importantCommentRe.test(comment) } - }; - return [ - postcssUrl({ - url: (obj) => { - if (!obj.url.startsWith('/') || obj.url.startsWith('//')) { - return obj.url; - } - if (deployUrl.match(/:\/\//)) { - // If deployUrl contains a scheme, ignore baseHref use deployUrl as is. - return `${deployUrl.replace(/\/$/, '')}${obj.url}`; - } - else if (baseHref.match(/:\/\//)) { - // If baseHref contains a scheme, include it as is. - return baseHref.replace(/\/$/, '') + - `/${deployUrl}/${obj.url}`.replace(/\/\/+/g, '/'); - } - else { - // Join together base-href, deploy-url and the original URL. - // Also dedupe multiple slashes into single ones. - return `/${baseHref}/${deployUrl}/${obj.url}`.replace(/\/\/+/g, '/'); - } - } - }), - autoprefixer(), - customProperties({ preserve: true }) - ].concat(minimizeCss ? [cssnano(minimizeOptions)] : []); -}; - -const isProd = (process.env.NODE_ENV === 'production'); - -//add all external css to be added in our index.html--> like as if it's .angular-cli.json -const styles = [ - "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", - "./src/styles.css" -]; - -//we add all our external scripts we want to load externally, like inserting in our index.html --> like as if it's .angular-cli.json -const scripts = [ -]; - -//create file path for each , so we use for our excludes and includes where needed -let style_paths = styles.map(style_src => path.join(process.cwd(), style_src)); - -function getPlugins() { - var plugins = []; - - // Always expose NODE_ENV to webpack, you can now use `process.env.NODE_ENV` - // inside your code for any environment checks; UglifyJS will automatically - // drop any unreachable code. - plugins.push(new DefinePlugin({ - "process.env.NODE_ENV": "\"production\"" - })); - - plugins.push(new NoEmitOnErrorsPlugin()); - - if (scripts.length > 0) { - plugins.push(new ConcatPlugin({ - "uglify": false, - "sourceMap": true, - "name": "scripts", - "fileName": "[name].bundle.js", - "filesToConcat": scripts - })); - plugins.push(new InsertConcatAssetsWebpackPlugin([ - "scripts" - ])); - } - - plugins.push(new CopyWebpackPlugin([ - { - "context": "src", - "to": "", - "from": { - "glob": "assets/**/*", - "dot": true - } - }, - { - "context": "src", - "to": "", - "from": { - "glob": "favicon.ico", - "dot": true - } - } - ], { - "ignore": [ - ".gitkeep" - ], - "debug": "warning" - })); - - plugins.push(new ProgressPlugin()); - - plugins.push(new CircularDependencyPlugin({ - "exclude": /(\\|\/)node_modules(\\|\/)/, - "failOnError": false - })); - - plugins.push(new NamedLazyChunksWebpackPlugin()); - - plugins.push(new HtmlWebpackPlugin({ - "template": "./src/index.html", - "filename": "./index.html", - "hash": false, - "inject": true, - "compile": true, - "favicon": false, - "minify": false, - "cache": true, - "showErrors": true, - "chunks": "all", - "excludeChunks": [], - "title": "Webpack App", - "xhtml": true, - "chunksSortMode": function sort(left, right) { - let leftIndex = entryPoints.indexOf(left.names[0]); - let rightindex = entryPoints.indexOf(right.names[0]); - if (leftIndex > rightindex) { - return 1; - } - else if (leftIndex < rightindex) { - return -1; - } - else { - return 0; - } - } - })); - - plugins.push(new BaseHrefWebpackPlugin({})); - - plugins.push(new CommonsChunkPlugin({ - "name": [ - "inline" - ], - "minChunks": null - })); - - plugins.push(new CommonsChunkPlugin({ - "name": [ - "vendor" - ], - "minChunks": (module) => { - return module.resource - && (module.resource.startsWith(nodeModules) - || module.resource.startsWith(genDirNodeModules) - || module.resource.startsWith(realNodeModules)); - }, - "chunks": [ - "main" - ] - })); - - plugins.push(new SourceMapDevToolPlugin({ - "filename": "[file].map[query]", - "moduleFilenameTemplate": "[resource-path]", - "fallbackModuleFilenameTemplate": "[resource-path]?[hash]", - "sourceRoot": "webpack:///" - })); - - plugins.push(new CommonsChunkPlugin({ - "name": [ - "main" - ], - "minChunks": 2, - "async": "common" - })); - - plugins.push(new NamedModulesPlugin({})); - - if (isProd) { - plugins.push(new AngularCompilerPlugin({ - "mainPath": "main.ts", - "platform": 0, - "sourceMap": true, - "tsConfigPath": "src/tsconfig.app.json", - "skipCodeGeneration": true, - "compilerOptions": {}, - "hostReplacementPaths": { - "environments/index.ts": "environments/index.prod.ts" - }, - "exclude": [] - })); - - plugins.push(new UglifyJsPlugin({ - "sourceMap": false - })); - - } else { - plugins.push(new AngularCompilerPlugin({ - "mainPath": "main.ts", - "platform": 0, - "sourceMap": true, - "tsConfigPath": "src/tsconfig.app.json", - "skipCodeGeneration": true, - "compilerOptions": {}, - "hostReplacementPaths": { - "environments/index.ts": "environments/index.ts" - }, - "exclude": [] - })); - } - - return plugins; -} - -module.exports = { - "devtool": "source-map", - "externals": { - "electron": "require('electron')", - "buffer": "require('buffer')", - "child_process": "require('child_process')", - "crypto": "require('crypto')", - "events": "require('events')", - "fs": "require('fs')", - "http": "require('http')", - "https": "require('https')", - "assert": "require('assert')", - "dns": "require('dns')", - "net": "require('net')", - "os": "require('os')", - "path": "require('path')", - "querystring": "require('querystring')", - "readline": "require('readline')", - "repl": "require('repl')", - "stream": "require('stream')", - "string_decoder": "require('string_decoder')", - "url": "require('url')", - "util": "require('util')", - "zlib": "require('zlib')" - }, - "resolve": { - "extensions": [ - ".ts", - ".js", - ".scss", - ".css", - ".json" - ], - "aliasFields": [], - "alias": { // WORKAROUND See. angular-cli/issues/5433 - "environments": isProd ? path.resolve(__dirname, 'src/environments/index.prod.ts') : path.resolve(__dirname, 'src/environments/index.ts') - }, - "modules": [ - "./node_modules" - ], - "mainFields": [ - "browser", - "module", - "main" - ] - }, - "resolveLoader": { - "modules": [ - "./node_modules" - ] - }, - "entry": { - "main": [ - "./src/main.ts" - ], - "polyfills": [ - "./src/polyfills.ts" - ], - "styles": styles - }, - "output": { - "path": path.join(process.cwd(), "dist"), - "filename": "[name].bundle.js", - "chunkFilename": "[id].chunk.js", - "crossOriginLoading": false - }, - "module": { - "rules": [ - { - "test": /\.html$/, - "use": ["html-loader"] - }, - { - test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2|json|xml|ico|cur|ani)$/, - "use": ["file-loader?name=[path][name].[ext]"] - }, - { - "exclude": style_paths, - "test": /\.css$/, - "use": [ - "exports-loader?module.exports.toString()", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - } - ] - }, - { - "exclude": style_paths, - "test": /\.scss$|\.sass$/, - "use": [ - "exports-loader?module.exports.toString()", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "sass-loader", - "options": { - "sourceMap": false, - "precision": 8, - "includePaths": [] - } - } - ] - }, - { - "exclude": style_paths, - "test": /\.less$/, - "use": [ - "exports-loader?module.exports.toString()", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "less-loader", - "options": { - "sourceMap": false - } - } - ] - }, - { - "exclude": style_paths, - "test": /\.styl$/, - "use": [ - "exports-loader?module.exports.toString()", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "stylus-loader", - "options": { - "sourceMap": false, - "paths": [] - } - } - ] - }, - { - "include": style_paths, - "test": /\.css$/, - "use": [ - "style-loader", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - } - ] - }, - { - "include": style_paths, - "test": /\.scss$|\.sass$/, - "use": [ - "style-loader", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "sass-loader", - "options": { - "sourceMap": false, - "precision": 8, - "includePaths": [] - } - } - ] - }, - { - "include":style_paths, - "test": /\.less$/, - "use": [ - "style-loader", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "less-loader", - "options": { - "sourceMap": false - } - } - ] - }, - { - "include": style_paths, - "test": /\.styl$/, - "use": [ - "style-loader", - { - "loader": "css-loader", - "options": { - "sourceMap": false, - "importLoaders": 1 - } - }, - { - "loader": "postcss-loader", - "options": { - "ident": "postcss", - "plugins": postcssPlugins - } - }, - { - "loader": "stylus-loader", - "options": { - "sourceMap": false, - "paths": [] - } - } - ] - }, - { - "test": /\.ts$/, - "use": "@ngtools/webpack" - } - ] - }, - "plugins": getPlugins(), - "node": { - fs: "empty", - global: true, - crypto: "empty", - tls: "empty", - net: "empty", - process: true, - module: false, - clearImmediate: false, - setImmediate: false, - __dirname: false, - __filename: false - }, - "devServer": { - "historyApiFallback": true - } -};