├── .gitignore ├── LICENSE ├── README.md ├── frontend ├── .angular-cli.json ├── .editorconfig ├── .eslintrc ├── .gitignore ├── dev-setup.sh ├── dev-start.sh ├── dev.Dockerfile ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── setServerStatus.sh ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routes.ts │ │ ├── components │ │ │ ├── loader │ │ │ │ ├── loader.component.html │ │ │ │ ├── loader.component.md │ │ │ │ ├── loader.component.scss │ │ │ │ ├── loader.component.spec.ts │ │ │ │ └── loader.component.ts │ │ │ ├── send-coins-form │ │ │ │ ├── send-coins-form.component.html │ │ │ │ ├── send-coins-form.component.md │ │ │ │ ├── send-coins-form.component.scss │ │ │ │ ├── send-coins-form.component.spec.ts │ │ │ │ └── send-coins-form.component.ts │ │ │ ├── server-message-display │ │ │ │ ├── server-message-display.component.html │ │ │ │ ├── server-message-display.component.scss │ │ │ │ ├── server-message-display.component.spec.ts │ │ │ │ ├── server-message-display.component.ts │ │ │ │ └── server-message-model.ts │ │ │ ├── status │ │ │ │ ├── status.component.html │ │ │ │ ├── status.component.scss │ │ │ │ ├── status.component.spec.ts │ │ │ │ └── status.component.ts │ │ │ ├── test-socket-interface │ │ │ │ ├── test-socket-interface.component.html │ │ │ │ ├── test-socket-interface.component.scss │ │ │ │ ├── test-socket-interface.component.spec.ts │ │ │ │ └── test-socket-interface.component.ts │ │ │ └── tile │ │ │ │ ├── tile.component.html │ │ │ │ ├── tile.component.md │ │ │ │ ├── tile.component.scss │ │ │ │ ├── tile.component.spec.ts │ │ │ │ └── tile.component.ts │ │ ├── mock-classes.ts │ │ ├── pages │ │ │ ├── about │ │ │ │ ├── about.component.html │ │ │ │ ├── about.component.scss │ │ │ │ ├── about.component.spec.ts │ │ │ │ └── about.component.ts │ │ │ ├── demo │ │ │ │ ├── demo.component.html │ │ │ │ ├── demo.component.scss │ │ │ │ ├── demo.component.spec.ts │ │ │ │ └── demo.component.ts │ │ │ ├── home │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.scss │ │ │ │ ├── home.component.spec.ts │ │ │ │ └── home.component.ts │ │ │ ├── send │ │ │ │ ├── send.component.html │ │ │ │ ├── send.component.scss │ │ │ │ ├── send.component.spec.ts │ │ │ │ └── send.component.ts │ │ │ └── status │ │ │ │ ├── status.component.html │ │ │ │ ├── status.component.scss │ │ │ │ ├── status.component.spec.ts │ │ │ │ └── status.component.ts │ │ ├── sections │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ ├── footer.component.spec.ts │ │ │ │ └── footer.component.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ ├── header.component.spec.ts │ │ │ │ └── header.component.ts │ │ │ ├── hero-banner │ │ │ │ ├── hero-banner.component.html │ │ │ │ ├── hero-banner.component.scss │ │ │ │ ├── hero-banner.component.spec.ts │ │ │ │ └── hero-banner.component.ts │ │ │ ├── how-it-works │ │ │ │ ├── how-it-works.component.html │ │ │ │ ├── how-it-works.component.scss │ │ │ │ ├── how-it-works.component.spec.ts │ │ │ │ └── how-it-works.component.ts │ │ │ ├── legal │ │ │ │ ├── legal.component.html │ │ │ │ ├── legal.component.scss │ │ │ │ ├── legal.component.spec.ts │ │ │ │ └── legal.component.ts │ │ │ ├── partners │ │ │ │ ├── partners.component.html │ │ │ │ ├── partners.component.scss │ │ │ │ ├── partners.component.spec.ts │ │ │ │ └── partners.component.ts │ │ │ └── selling-points │ │ │ │ ├── selling-points.component.html │ │ │ │ ├── selling-points.component.scss │ │ │ │ ├── selling-points.component.spec.ts │ │ │ │ └── selling-points.component.ts │ │ ├── services │ │ │ ├── changelly-api │ │ │ │ ├── changelly-api.spec.ts │ │ │ │ └── changelly-api.ts │ │ │ ├── config.ts │ │ │ ├── generic-functions │ │ │ │ ├── generic-functions.spec.ts │ │ │ │ └── generic-functions.ts │ │ │ ├── generic-node-api │ │ │ │ ├── generic-node-api.spec.ts │ │ │ │ └── generic-node-api.ts │ │ │ ├── generic-socket │ │ │ │ ├── generic-socket.spec.ts │ │ │ │ └── generic-socket.ts │ │ │ ├── order │ │ │ │ ├── order.spec.ts │ │ │ │ └── order.ts │ │ │ ├── send-page-data │ │ │ │ ├── send-page-data.spec.ts │ │ │ │ └── send-page-data.ts │ │ │ └── title-change │ │ │ │ ├── title-change.spec.ts │ │ │ │ └── title-change.ts │ │ └── styles │ │ │ ├── base.scss │ │ │ ├── buttons.scss │ │ │ ├── colors.scss │ │ │ ├── fonts.scss │ │ │ ├── forms.scss │ │ │ └── layout.scss │ ├── assets │ │ ├── .gitkeep │ │ ├── background.jpg │ │ ├── circle-image.png │ │ ├── diagram.png │ │ ├── lock-blue-20.png │ │ ├── lock-blue.png │ │ ├── lock-fuschia.png │ │ ├── lock-ice.png │ │ ├── logo-extended.png │ │ ├── logo-mark.png │ │ ├── navcoin-bg.jpg │ │ ├── polymorph-background.jpg │ │ ├── polymorph-logo.svg │ │ └── polymorph-symbol.svg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json ├── package.json └── server ├── Dockerfile ├── docker-compose-dev.yml ├── docker-compose-prod.yml ├── package-lock.json ├── package.json ├── src ├── api-options.json ├── lib │ ├── changelly │ │ └── changelly.ctrl.js │ ├── db │ │ ├── blackList.model.js │ │ ├── failedLogins.model.js │ │ ├── login.ctrl.js │ │ ├── serverMessage.md │ │ ├── serverMessage.model.js │ │ ├── serverMode.ctrl.js │ │ ├── serverMode.model.js │ │ ├── transaction.ctrl.js │ │ └── transaction.model.js │ ├── error-handler.js │ ├── logger.js │ ├── options-validator.js │ ├── order │ │ ├── eta.ctrl.js │ │ ├── order.ctrl.js │ │ └── status.ctrl.js │ ├── processHandler.js │ ├── rpc │ │ ├── client.js │ │ ├── get-info.js │ │ └── get-new-address.js │ ├── settingsValidator.js │ └── socket │ │ └── socketCtrl.js ├── routes │ └── api.js ├── sample-server-config.json ├── server-settings-template.js ├── server-settings.js └── server.js ├── start-dev.sh ├── test ├── db.login.ctrl.spec.js ├── db.serverMode.ctrl.spec.js ├── db.transaction.ctrl.spec.js ├── eta.ctrl.spec.js ├── lib.error-handler.spec.js ├── processHandler.spec.js ├── rpc.get-info.spec.js └── rpc.get-new-address.spec.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | #docker mongo 4 | /server/data 5 | /server/node_modules 6 | 7 | 8 | # compiled output 9 | /dist 10 | /tmp 11 | /out-tsc 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | testem.log 39 | /typings 40 | 41 | # e2e 42 | /e2e/*.js 43 | /e2e/*.map 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | 49 | #secrets 50 | /server/src/secrets.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 NavCoin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polymorph 2 | 3 | A site for anonymizing crypto currencies via Changelly's crypto exchange system and NavTech's anonymous NavCoin transactions. 4 | 5 | ## Requirements 6 | 7 | This project uses nodejs v8.9.0 and Mongodb. 8 | 9 | 10 | # Development Setup 11 | The development flow requires both a backend and frontend docker service to be running. 12 | 13 | Please follow the steps below to get the docker version running on your system. 14 | 15 | Note: You need to have docker installed. 16 | 17 | ## Shortcuts 18 | `npm run start:server` 19 | `npm run start:client` 20 | 21 | **First run or when rebuild requires** 22 | `npm run docker:server:build` 23 | `npm run docker:build:build` 24 | 25 | ## Backend 26 | The backend is a node / express app + mongo system. 27 | 28 | Build and run the docker container. 29 | 30 | `docker-compose -f docker-compose-prod.yml -f docker-compose-dev.yml up` 31 | 32 | You can also force build the docker with. 33 | 34 | `docker-compose -f docker-compose-prod.yml -f docker-compose-dev.yml up --build` 35 | 36 | 37 | ## Frontend 38 | The frontend of NavMorph is an angular app. The dev enviroment is provided by docker. 39 | 40 | Docker is not a requirement, but it is the fastest way to get going. 41 | 42 | The docker environment for this build is a cmd line interface. This allows you to install packages, use the ng cli generator and run the app as you would with any other angular dev setup. 43 | Build the docker image with the following command 44 | 45 | `docker build -f dev.Dockerfile -t navmorph-frontend .` 46 | 47 | Run the Docker container you just built 48 | 49 | Windows: `docker run -it -p 4200:4200 -v ${pwd}:/app navmorph-frontend` 50 | Mac: `docker run -it -p 4200:4200 -v "$(pwd)":/app navmorph-frontend` 51 | 52 | ### First run 53 | As this is a dev env you need to run the dev-setup.sh 54 | 55 | From docker container bash run `dev-setup.sh` 56 | 57 | ### Server the 58 | 59 | For you convince we have a run and serve script. 60 | 61 | `dev-start.sh` 62 | 63 | 64 | ## Code scaffolding 65 | 66 | We use angular cli for creating new components. 67 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. 68 | 69 | ## Run the project 70 | 71 | Run `ng build-run` for a dev server. Navigate to `http://localhost:3000/`. 72 | To rebuild the angular without starting a server, run `ng build`. 73 | To skip rebuilding the angular, run `npm run express` 74 | 75 | ## Running unit tests 76 | 77 | For angular tests, run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 78 | For testing express run `npm run express-test` 79 | For code coverage reports, run `npm run test-coverage` or `npm run express-coverage` for the front end and backend. 80 | 81 | ## Running end-to-end tests 82 | 83 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 84 | Before running the tests make sure you are serving the app via `ng serve`. 85 | 86 | -------------------------------------------------------------------------------- /frontend/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "nav-test" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "../node_modules/materialize-css/dist/css/materialize.css", 23 | "styles.scss" 24 | ], 25 | "scripts": [ 26 | "../node_modules/jquery/dist/jquery.js", 27 | "../node_modules/hammerjs/hammer.js", 28 | "../node_modules/materialize-css/dist/js/materialize.js" 29 | ], 30 | "environmentSource": "environments/environment.ts", 31 | "environments": { 32 | "dev": "environments/environment.ts", 33 | "prod": "environments/environment.prod.ts" 34 | } 35 | } 36 | ], 37 | "e2e": { 38 | "protractor": { 39 | "config": "./protractor.conf.js" 40 | } 41 | }, 42 | "lint": [ 43 | { 44 | "project": "src/tsconfig.app.json" 45 | }, 46 | { 47 | "project": "src/tsconfig.spec.json" 48 | }, 49 | { 50 | "project": "e2e/tsconfig.e2e.json" 51 | } 52 | ], 53 | "test": { 54 | "karma": { 55 | "config": "./karma.conf.js" 56 | } 57 | }, 58 | "defaults": { 59 | "styleExt": "scss", 60 | "component": {} 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : [ 4 | "airbnb" 5 | ], 6 | "env" : { 7 | "browser" : true 8 | }, 9 | "globals": { 10 | "Action": false, 11 | "DEVELOPMENT": false, 12 | "PRODUCTION": false, 13 | "__DEBUG__": false, 14 | "sinon": false, 15 | "expect": false, 16 | "assert": false, 17 | "it": false, 18 | "describe": false, 19 | "before": false, 20 | "after": false, 21 | "beforeEach": false, 22 | "afterEach": false, 23 | "Exception": false, 24 | }, 25 | "rules": { 26 | "semi" : [2, "never"], 27 | "max-len": [2, 140, 2], 28 | "arrow-body-style": [0, "as-needed"], 29 | "no-use-before-define": 0, 30 | "new-cap": 0, 31 | "no-console": 0, 32 | "prefer-template": 0, 33 | "no-restricted-syntax": 0, 34 | "strict": 0, 35 | "no-underscore-dangle": ["error", {"allow": ["__set__"]}] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | #docker mongo 4 | /server/data 5 | /server/node_modules 6 | /server/server-settings\.json 7 | 8 | 9 | # compiled output 10 | /dist 11 | /tmp 12 | /out-tsc 13 | 14 | # dependencies 15 | /node_modules 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | testem.log 40 | /typings 41 | 42 | # e2e 43 | /e2e/*.js 44 | /e2e/*.map 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | 50 | # Config 51 | 52 | /server/config.js 53 | 54 | server-settings\.json 55 | -------------------------------------------------------------------------------- /frontend/dev-setup.sh: -------------------------------------------------------------------------------- 1 | npm install 2 | npm rebuild node-sass 3 | -------------------------------------------------------------------------------- /frontend/dev-start.sh: -------------------------------------------------------------------------------- 1 | npm start -- --host 0.0.0.0 2 | -------------------------------------------------------------------------------- /frontend/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9.4-alpine 2 | 3 | RUN mkdir -p /app 4 | WORKDIR /app 5 | 6 | RUN apk update && apk upgrade && \ 7 | apk add --no-cache bash git openssh 8 | 9 | RUN npm install -g @angular/cli 10 | 11 | EXPOSE 4200/tcp 12 | 13 | CMD ["/bin/bash"] 14 | -------------------------------------------------------------------------------- /frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { NavTestPage } from './app.po'; 2 | 3 | describe('nav-test App', () => { 4 | let page: NavTestPage; 5 | 6 | beforeEach(() => { 7 | page = new NavTestPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class NavTestPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma'), 14 | require('rewire'), 15 | ], 16 | client:{ 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | files: [ 20 | { pattern: './src/test.ts', watched: false } 21 | ], 22 | preprocessors: { 23 | './src/test.ts': ['@angular/cli'] 24 | }, 25 | mime: { 26 | 'text/x-typescript': ['ts','tsx'] 27 | }, 28 | coverageIstanbulReporter: { 29 | reports: [ 'html', 'lcovonly' ], 30 | fixWebpackSourcePaths: true 31 | }, 32 | angularCli: { 33 | environment: 'dev' 34 | }, 35 | reporters: config.angularCli && config.angularCli.codeCoverage 36 | ? ['progress', 'coverage-istanbul'] 37 | : ['progress', 'kjhtml'], 38 | port: 9876, 39 | colors: true, 40 | logLevel: config.LOG_INFO, 41 | autoWatch: true, 42 | browsers: ['Chrome'], 43 | singleRun: false 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymorph", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --prod", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^4.1.2", 16 | "@angular/common": "^4.0.0", 17 | "@angular/compiler": "^4.0.0", 18 | "@angular/core": "^4.0.0", 19 | "@angular/forms": "^4.0.0", 20 | "@angular/http": "^4.0.0", 21 | "@angular/platform-browser": "^4.0.0", 22 | "@angular/platform-browser-dynamic": "^4.0.0", 23 | "@angular/router": "^4.0.0", 24 | "angular2-materialize": "^15.0.2", 25 | "angular2-qrcode": "^2.0.1", 26 | "basic-auth": "^2.0.0", 27 | "bignumber.js": "^4.0.2", 28 | "bitcoin-core": "^1.2.0", 29 | "body-parser": "^1.17.1", 30 | "core-js": "^2.4.1", 31 | "express": "^4.15.2", 32 | "generate-key": "0.0.6", 33 | "hammerjs": "^2.0.8", 34 | "jayson": "^2.0.3", 35 | "jquery": "^2.2.4", 36 | "lodash": "^4.17.4", 37 | "materialize-css": "^0.98.2", 38 | "mongoose": "^4.9.9", 39 | "ngx-clipboard": "^8.0.3", 40 | "nodemailer": "^2.7.2", 41 | "pem": "^1.12.3", 42 | "promise": "^8.0.0", 43 | "rxjs": "^5.1.0", 44 | "socket.io": "^2.0.3", 45 | "socket.io-client": "^2.0.3", 46 | "validator": "^9.0.0", 47 | "zone.js": "^0.8.4" 48 | }, 49 | "devDependencies": { 50 | "@angular/cli": "^1.4.0", 51 | "@angular/compiler-cli": "^4.0.0", 52 | "@types/bignumber.js": "^4.0.3", 53 | "@types/jasmine": "2.5.38", 54 | "@types/node": "~6.0.60", 55 | "babel-eslint": "^7.2.3", 56 | "codelyzer": "~2.0.0", 57 | "eslint": "^3.19.0", 58 | "eslint-config-airbnb": "^14.1.0", 59 | "eslint-plugin-import": "^2.2.0", 60 | "eslint-plugin-jsx-a11y": "^4.0.0", 61 | "eslint-plugin-react": "^6.9.0", 62 | "expect": "^1.20.2", 63 | "istanbul": "^0.4.5", 64 | "jasmine-core": "~2.5.2", 65 | "jasmine-spec-reporter": "~3.2.0", 66 | "karma": "~1.4.1", 67 | "karma-chrome-launcher": "~2.1.1", 68 | "karma-cli": "~1.0.1", 69 | "karma-coverage-istanbul-reporter": "^0.2.0", 70 | "karma-jasmine": "~1.1.0", 71 | "karma-jasmine-html-reporter": "^0.2.2", 72 | "mocha": "^3.3.0", 73 | "protractor": "~5.1.0", 74 | "rewire": "^2.5.2", 75 | "sinon": "^2.2.0", 76 | "ts-node": "~2.0.0", 77 | "tslint": "~4.5.0", 78 | "typescript": "~2.2.0", 79 | "webpack": "^3.10.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | beforeLaunch: function() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | }, 27 | onPrepare() { 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/setServerStatus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # sysinfo_page - A script to update the server mode 4 | 5 | set_status () { 6 | echo "Updating server status to $1" 7 | 8 | echo "Calling: mongo polymorph --eval \"db.servermodes.update({}, {'server_mode': '$1'})\"" 9 | echo "--------" 10 | mongo polymorph --eval "db.servermodes.update({}, {'server_mode': '$1'},{upsert:true})" 11 | echo "--------" 12 | echo "Server status updated" 13 | echo "Fetching server mode" 14 | echo "--------" 15 | mongo polymorph --eval "db.servermodes.find({})" 16 | echo "--------" 17 | exit 18 | } 19 | 20 | if [ $# -lt 1 ]; then 21 | echo "You're missing a server status argument." 22 | echo "Usage: setServerStatus " 23 | elif [ $# -gt 1 ]; then 24 | echo "You have used too many arguments." 25 | echo "Usage: setServerStatus " 26 | else 27 | echo $1 28 | set_status $1 29 | fi 30 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/app/app.component.scss -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { HeaderSection} from "./sections/header/header.component"; 6 | import { FooterSection} from "./sections/footer/footer.component"; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | 9 | 10 | describe('AppComponent', () => { 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [RouterTestingModule, MaterializeModule], 14 | declarations: [ 15 | AppComponent, HeaderSection, FooterSection 16 | ], 17 | }).compileComponents(); 18 | })); 19 | 20 | it('should create the app', async(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app).toBeTruthy(); 24 | })); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | import { Router, NavigationStart } from '@angular/router' 3 | 4 | import { TitleChangeService } from './services/title-change/title-change' 5 | import { SendPageDataService } from './services/send-page-data/send-page-data' 6 | import { ServerMessageDisplayComponent } from './components/server-message-display/server-message-display.component' 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.scss'], 12 | providers: [TitleChangeService] 13 | }) 14 | export class AppComponent { 15 | 16 | previousUrl: string 17 | 18 | constructor ( 19 | private _router: Router, 20 | private titleChangeService: TitleChangeService, 21 | private sendDataServ: SendPageDataService, 22 | ) { 23 | _router.events.subscribe((event) => { 24 | if(event instanceof NavigationStart) { 25 | scroll(0,0) 26 | titleChangeService.updateTitle(event) 27 | this.sendDataServ.previousPageUrl = _router.url 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser' 2 | import { NgModule } from '@angular/core' 3 | import { FormsModule } from '@angular/forms' 4 | import { HttpModule, JsonpModule } from '@angular/http' 5 | import { RouterModule } from '@angular/router' 6 | import { MaterializeModule } from 'angular2-materialize' 7 | import { QRCodeModule } from 'angular2-qrcode' 8 | import { ClipboardModule } from 'ngx-clipboard' 9 | 10 | import { AppComponent } from './app.component' 11 | import { routes } from './app.routes' 12 | 13 | import { DemoPage } from './pages/demo/demo.component' 14 | import { AboutPage } from './pages/about/about.component' 15 | import { HomePage } from './pages/home/home.component' 16 | import { SendPage } from './pages/send/send.component' 17 | import { StatusPage } from './pages/status/status.component' 18 | 19 | import { HowItWorksSection } from './sections/how-it-works/how-it-works.component' 20 | import { LegalSection } from './sections/legal/legal.component' 21 | import { PartnersSection } from './sections/partners/partners.component' 22 | import { HeaderSection } from './sections/header/header.component' 23 | import { FooterSection } from './sections/footer/footer.component' 24 | import { SellingPointsSection } from './sections/selling-points/selling-points.component' 25 | import { HeroBannerSection } from './sections/hero-banner/hero-banner.component' 26 | 27 | import { LoaderComponent } from './components/loader/loader.component' 28 | import { TileComponent } from './components/tile/tile.component' 29 | import { StatusComponent } from './components/status/status.component' 30 | import { SendCoinsFormComponent } from './components/send-coins-form/send-coins-form.component' 31 | import { TestSocketInterfaceComponent } from './components/test-socket-interface/test-socket-interface.component' 32 | import { ServerMessageDisplayComponent } from './components/server-message-display/server-message-display.component' 33 | 34 | import { GenericNodeApiService } from './services/generic-node-api/generic-node-api' 35 | import { ChangellyApiService } from './services/changelly-api/changelly-api' 36 | import { SendPageDataService } from './services/send-page-data/send-page-data' 37 | import { GenericSocketService } from './services/generic-socket/generic-socket' 38 | import { GenericFunctionsService } from './services/generic-functions/generic-functions'; 39 | 40 | @NgModule({ 41 | declarations: [ 42 | AppComponent, 43 | HomePage, 44 | SendPage, 45 | DemoPage, 46 | AboutPage, 47 | StatusPage, 48 | HeaderSection, 49 | FooterSection, 50 | SellingPointsSection, 51 | HowItWorksSection, 52 | LegalSection, 53 | PartnersSection, 54 | TileComponent, 55 | LoaderComponent, 56 | SendCoinsFormComponent, 57 | HeroBannerSection, 58 | StatusComponent, 59 | TestSocketInterfaceComponent, 60 | ServerMessageDisplayComponent, 61 | ], 62 | imports: [ 63 | FormsModule, 64 | QRCodeModule, 65 | MaterializeModule, 66 | BrowserModule, 67 | HttpModule, 68 | JsonpModule, 69 | ClipboardModule, 70 | RouterModule.forRoot(routes) 71 | ], 72 | providers: [ 73 | GenericNodeApiService, 74 | ChangellyApiService, 75 | SendPageDataService, 76 | GenericSocketService, 77 | GenericFunctionsService, 78 | ], 79 | bootstrap: [AppComponent] 80 | }) 81 | export class AppModule { } 82 | -------------------------------------------------------------------------------- /frontend/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router' 2 | 3 | import { DemoPage } from './pages/demo/demo.component' 4 | import { AboutPage } from './pages/about/about.component' 5 | import { HomePage } from './pages/home/home.component' 6 | import { SendPage } from './pages/send/send.component' 7 | import { StatusPage } from './pages/status/status.component' 8 | 9 | export const routes: Routes = [ 10 | { path: 'demo', component: DemoPage}, 11 | { path: '', component: HomePage}, 12 | { path: 'send', component: SendPage}, 13 | { path: 'about', component: AboutPage}, 14 | { path: 'status/:id/:password', component: StatusPage}, 15 | { path: '**', redirectTo: ''}, 16 | ] 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/loader/loader.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /frontend/src/app/components/loader/loader.component.md: -------------------------------------------------------------------------------- 1 | # Loader Component 2 | 3 | The Loader component is used across the site and the style can be configured by passing in a variable via the html. 4 | 5 | Layout: It absolutely positions to the middle of the parent component, but the parent **must** be relatively positioned. 6 | 7 | ## Options 8 | 9 | | Option | Value | Type | Required | 10 | |:-----------|:-----------|:-----------|:-----------| 11 | | theme | | `String(Theme)` | false | 12 | 13 | ## Themes 14 | 15 | | Theme | Description | Default| 16 | |:-----------|:-----------|:-----------| 17 | | (default) | Transparent black background | true | 18 | | loader-light | Transparent white background | false | 19 | 20 | #### Example Usage 21 | ```html 22 | 23 | ``` 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/loader/loader.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/base.scss'; 2 | 3 | #loader-cover { 4 | position: absolute; 5 | width: 110%; 6 | height: 115%; 7 | z-index: 3; 8 | margin-left: -5%; 9 | margin-top: -5%; 10 | 11 | background: -moz-radial-gradient(center, ellipse cover, rgba(0,0,0,0.5) 56%, rgba(0,0,0,0) 100%); 12 | background: -webkit-radial-gradient(center, ellipse cover, rgba(0,0,0,0.5) 56%,rgba(0,0,0,0) 100%); 13 | background: radial-gradient(ellipse at center, rgba(0,0,0,0.5) 56%,rgba(0,0,0,0) 100%); 14 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='rgba(0,0,0,0.5)', endColorstr='rgba(0,0,0,0)',GradientType=1 ); 15 | 16 | animation: fade 1.5s linear; 17 | @keyframes fade { 18 | 0%, 50% { opacity: 0 } 19 | 100% { opacity: 1 } 20 | } 21 | &.light-theme { 22 | background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 56%, rgba(0,0,0,0) 100%); 23 | background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 56%,rgba(0,0,0,0) 100%); 24 | background: radial-gradient(ellipse at center, rgba(255,255,255,0.5) 56%,rgba(0,0,0,0) 100%); 25 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='rgba(255,255,255,0.5)', endColorstr='rgba(0,0,0,0)',GradientType=1 ); 26 | } 27 | } 28 | 29 | // Source: https://projects.lukehaas.me/css-loaders/ 30 | .loader, 31 | .loader:after { 32 | border-radius: 50%; 33 | width: 10em; 34 | height: 10em; 35 | } 36 | .loader { 37 | margin: 60px auto; 38 | font-size: 10px; 39 | position: relative; 40 | border-top: 1.1em solid rgba(14, 75, 190, 0.4); 41 | border-right: 1.1em solid rgba(14, 75, 190, 0.4); 42 | border-bottom: 1.1em solid rgba(14, 75, 190, 0.4); 43 | border-left: 1.1em solid #c926b5; 44 | -webkit-transform: translateZ(0); 45 | -ms-transform: translateZ(0); 46 | transform: translateZ(0); 47 | -webkit-animation: load8 1.1s infinite linear; 48 | animation: load8 1.1s infinite linear; 49 | } 50 | @-webkit-keyframes load8 { 51 | 0% { 52 | -webkit-transform: rotate(0deg); 53 | transform: rotate(0deg); 54 | } 55 | 100% { 56 | -webkit-transform: rotate(360deg); 57 | transform: rotate(360deg); 58 | } 59 | } 60 | @keyframes load8 { 61 | 0% { 62 | -webkit-transform: rotate(0deg); 63 | transform: rotate(0deg); 64 | } 65 | 100% { 66 | -webkit-transform: rotate(360deg); 67 | transform: rotate(360deg); 68 | } 69 | } -------------------------------------------------------------------------------- /frontend/src/app/components/loader/loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoaderComponent } from './loader.component'; 4 | 5 | describe('LoaderComponent', () => { 6 | let component: LoaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/components/loader/loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'loader-component', 5 | templateUrl: './loader.component.html', 6 | styleUrls: ['./loader.component.scss'] 7 | }) 8 | export class LoaderComponent { 9 | @Input() theme: string; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/components/send-coins-form/send-coins-form.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |
    7 |
  • Unable to load available currencies from Changelly
  • 8 |
  • Recevied incorrectly formatted data from Changelly
  • 9 |
  • Failed to create a Polymorph order
  • 10 |
  • Maintenance Mode is currently active, creation of new orders is disabled
  • 11 |
  • Your estimate has expired, click send to fetch a new one
  • 12 |
  • Error displaying available currencies
  • 13 |
14 | 15 |
16 |
17 | 21 | 22 |
23 | 24 |
25 | 26 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 39 | 40 |
41 | 42 |
43 | 46 | 47 |
48 |
49 |
50 | 51 |
52 |
53 | 54 | 57 | 60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /frontend/src/app/components/send-coins-form/send-coins-form.component.md: -------------------------------------------------------------------------------- 1 | # Send Coins Form Component 2 | 3 | The Send Coins Form component is used across the site and the style can be configured by passing in a variable via the html. 4 | 5 | Layout is controlled by the parent. 6 | 7 | ## Options 8 | 9 | | Option | Value | Type | Required | 10 | |:-----------|:-----------|:-----------|:-----------| 11 | | theme | | `String(Theme)` | false | 12 | | loaderTheme | | `String(Theme)` | false | 13 | 14 | ## Themes 15 | 16 | | Theme | Description | Default| 17 | |:-----------|:-----------|:-----------| 18 | | form-dark | Black text with an opaque grey background | true | 19 | | form-light | Grey text with transparent grey background | false | 20 | 21 | ## Loader Themes 22 | See the loader components documention for details on the availible theming options 23 | 24 | #### Example Usage 25 | ```html 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /frontend/src/app/components/send-coins-form/send-coins-form.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | #container { 4 | position: relative; 5 | } 6 | 7 | .send-buttons { 8 | text-align: right; 9 | } 10 | 11 | .error-message { 12 | color: $red; 13 | font-size: 24px; 14 | text-align: center; 15 | z-index: 10; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/send-coins-form/send-coins-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, inject, fakeAsync, tick } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { HttpModule } from '@angular/http'; 4 | import { MaterializeModule } from 'angular2-materialize'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { SendCoinsFormComponent } from './send-coins-form.component'; 8 | import { Observable } from 'rxjs/Observable'; 9 | import 'rxjs/add/observable/of'; 10 | 11 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 12 | import { ChangellyApiService } from './../../services/changelly-api/changelly-api'; 13 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 14 | import { OrderService } from '../../services/order/order'; 15 | 16 | import { MockChangellyService, fakeData } from '../../mock-classes'; 17 | 18 | describe('SendCoinsFormComponent', () => { 19 | let component: SendCoinsFormComponent; 20 | let fixture: ComponentFixture; 21 | 22 | beforeEach(async(() => { 23 | TestBed.configureTestingModule({ 24 | declarations: [ SendCoinsFormComponent ], 25 | imports: [ 26 | FormsModule, 27 | HttpModule, 28 | MaterializeModule, 29 | RouterTestingModule, 30 | ], 31 | providers: [ 32 | SendCoinsFormComponent, 33 | GenericNodeApiService, 34 | SendPageDataService, 35 | OrderService, 36 | { provide: ChangellyApiService, useClass: MockChangellyService }, 37 | ] 38 | }) 39 | 40 | .overrideComponent(SendCoinsFormComponent, { 41 | set: { 42 | providers: [ 43 | { provide: ChangellyApiService, useClass: MockChangellyService }, 44 | ] 45 | }}) 46 | 47 | .compileComponents(); 48 | })); 49 | 50 | beforeEach(() => { 51 | fixture = TestBed.createComponent(SendCoinsFormComponent); 52 | component = fixture.componentInstance; 53 | fixture.detectChanges(); 54 | }); 55 | 56 | it('should be created', () => { 57 | expect(component).toBeTruthy(); 58 | }); 59 | 60 | it('should get the currency data', async(inject([SendCoinsFormComponent], ( sendCoinsSection: SendCoinsFormComponent) => { 61 | sendCoinsSection.getCurrencies() 62 | expect(sendCoinsSection.currencies).toBe(fakeData) 63 | }))) 64 | 65 | it('should invalidate estimate data', (inject([SendCoinsFormComponent], (sendCoinsSection: SendCoinsFormComponent) => { 66 | sendCoinsSection.estimateValid = true 67 | sendCoinsSection.invalidateEstimate() 68 | expect(sendCoinsSection.estimateValid).toBe(false) 69 | }))) 70 | 71 | it('should clear form data', ( inject([SendCoinsFormComponent], (sendCoinsSection: SendCoinsFormComponent) => { 72 | // TODO: Figure out how to test private function call to dataServ 73 | 74 | sendCoinsSection.currencies[0] = 'nav' 75 | sendCoinsSection.estimateValid = true 76 | sendCoinsSection.originCoin = 'doge' 77 | sendCoinsSection.destCoin = 'doge' 78 | 79 | sendCoinsSection.clearFormData() 80 | 81 | expect(sendCoinsSection.estimateValid).toBe(false) 82 | expect(sendCoinsSection.originCoin).toBe('nav') 83 | expect(sendCoinsSection.destCoin).toBe('nav') 84 | }))) 85 | 86 | it('toggle form state after a certain time', async(inject([SendCoinsFormComponent], ( sendCoinsSection: SendCoinsFormComponent) => { 87 | jasmine.clock().install() 88 | sendCoinsSection.isDisabled = true 89 | sendCoinsSection.toggleFormState() 90 | expect(sendCoinsSection.isDisabled).toBe(true) 91 | jasmine.clock().tick(101) 92 | expect(sendCoinsSection.isDisabled).toBe(false) 93 | jasmine.clock().uninstall() 94 | }))) 95 | }); 96 | -------------------------------------------------------------------------------- /frontend/src/app/components/server-message-display/server-message-display.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{serverMessage}} 3 |
-------------------------------------------------------------------------------- /frontend/src/app/components/server-message-display/server-message-display.component.scss: -------------------------------------------------------------------------------- 1 | #server-message {} 2 | 3 | #message-container { 4 | font-size: 16px; 5 | padding: 5px 0; 6 | text-align: center; 7 | color: black; 8 | } 9 | 10 | .info { 11 | background-color: grey; 12 | } 13 | 14 | .warn { 15 | background-color: orange; 16 | font-weight: bold; 17 | } 18 | 19 | .error { 20 | background-color: red; 21 | font-weight: bold; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/app/components/server-message-display/server-message-display.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ServerMessageDisplayComponent } from './server-message-display.component'; 4 | 5 | describe('ServerMessageDisplayComponent', () => { 6 | let component: ServerMessageDisplayComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ServerMessageDisplayComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ServerMessageDisplayComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/components/server-message-display/server-message-display.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core' 2 | import { GenericSocketService } from '../../services/generic-socket/generic-socket' 3 | import { ServerMessageModel } from './server-message-model' 4 | import * as io from 'socket.io-client' 5 | 6 | @Component({ 7 | selector: 'server-message-display', 8 | templateUrl: './server-message-display.component.html', 9 | styleUrls: ['./server-message-display.component.scss'] 10 | }) 11 | export class ServerMessageDisplayComponent implements OnInit { 12 | 13 | private socketUrl = 'http://localhost:8080' 14 | connection 15 | displayMessage: boolean = false 16 | serverMessage: string 17 | modeInfo: boolean 18 | modeWarn: boolean 19 | modeError: boolean 20 | 21 | constructor(private genericSocket: GenericSocketService) { } 22 | 23 | ngOnInit() { 24 | this.connectToSocket() 25 | } 26 | 27 | ngOnDestroy() { 28 | this.connection.unsubscribe() 29 | } 30 | 31 | connectToSocket():void { 32 | this.connection = this.genericSocket.getMessages(this.socketUrl, 'SERVER_MESSAGES') 33 | .subscribe((serverMessage:ServerMessageModel) => { 34 | if(Object.keys(serverMessage).length > 0) { 35 | this.displayMessage = serverMessage.showMessage 36 | this.serverMessage = serverMessage.serverMessage 37 | this.setMode(serverMessage.serverMessageType) 38 | } 39 | }) 40 | } 41 | 42 | setMode(mode: String) { 43 | this.modeError = false 44 | this.modeWarn = false 45 | this.modeInfo = false 46 | if (mode === 'INFO') { 47 | this.modeInfo = true 48 | } else if(mode === 'WARN') { 49 | this.modeWarn = true 50 | } else if(mode === 'ERROR') { 51 | this.modeError = true 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/app/components/server-message-display/server-message-model.ts: -------------------------------------------------------------------------------- 1 | export class ServerMessageModel { 2 | showMessage: boolean 3 | serverMessage: string 4 | serverMessageType: string 5 | } -------------------------------------------------------------------------------- /frontend/src/app/components/status/status.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Status:

5 |
6 | Fill out the form and press 'Send' to get started. 7 |
8 |
9 |
10 |
Error with form submission:
11 |
You're attempting to send to an invalid destination address, please double check you've entered the correct address.
12 |
You're attempting to send to an invalid transfer amount, please double check you've entered a valid number.
13 |
You're attempting to send too few coins. Changelly requires at least {{formData.minTransferAmount | number:'1.0-8' }} {{formData.originCoin | uppercase}}
14 |
You're attempting to send too many coins at once, our current limit is {{MAX_NAV_PER_TRADE | number:'1.0-8' }} NAV per trade. You're attempting to send {{formData.estConvToNav | number:'1.0-8' }} NAV.
15 |
You're attempting a NAV to NAV anonymous transfer, please use NavTech wallet's built in anon transaction feature for this.
16 | 17 |
There was an error fetching an ETA.
18 | 19 |
20 |
21 |
22 | You are sending: 23 | {{transferAmount | number:'1.0-8'}} {{originCoin | uppercase}} to {{destAddr}} in {{destCoin | uppercase}} 24 |
25 |
26 | Est. Received: 27 | ~{{estConvFromNav | number:'1.0-8'}} {{destCoin | uppercase}} 28 |
29 |
30 | Est. Fees: 31 | ~{{estimatedFees | number:'1.0-8'}} {{originCoin | uppercase}} 32 |
33 |
34 | Est. Arrival: {{etaMin}} mins ~ {{etaMax}} mins 35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /frontend/src/app/components/status/status.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | #status-container{ 4 | position: relative; 5 | margin-top: 14px; 6 | border-bottom: 1px solid #9e9e9e; 7 | background-color: #e6ecf9; 8 | border-radius: 10px 10px 0 0; 9 | padding: 10px; 10 | @media (max-width: 600px){ 11 | padding: 10px 20px !important; 12 | } 13 | } 14 | 15 | h4 { 16 | margin-top: 5px; 17 | } 18 | 19 | @media (min-width: 600px) and (max-width: 992px ) { 20 | 21 | .status-data { 22 | display: block; 23 | } 24 | } 25 | 26 | .status-title { 27 | font-weight: bold; 28 | } 29 | 30 | .error-text { 31 | color: $red; 32 | font-size: 16px; 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/app/components/status/status.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpModule } from '@angular/http'; 3 | 4 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 5 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 6 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 7 | 8 | import { StatusComponent } from './status.component'; 9 | 10 | describe('StatusComponent', () => { 11 | let component: StatusComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ StatusComponent ], 17 | providers: [ 18 | SendPageDataService, 19 | ChangellyApiService, 20 | GenericNodeApiService, 21 | ], 22 | imports:[HttpModule] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(StatusComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should be created', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /frontend/src/app/components/status/status.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | 4 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 5 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 6 | import { GenericFunctionsService } from '../../services/generic-functions/generic-functions'; 7 | 8 | import { changellyConstData } from "../../services/config"; 9 | import BigNumber from 'bignumber.js' 10 | 11 | 12 | @Component({ 13 | selector: 'status-component', 14 | templateUrl: './status.component.html', 15 | styleUrls: ['./status.component.scss'] 16 | }) 17 | export class StatusComponent implements OnInit { 18 | 19 | transferAmount: string 20 | originCoin: string 21 | destCoin: string 22 | destAddr: string 23 | formDataStatus: string = 'UNSET' 24 | statusUntouched: boolean = true 25 | 26 | estConvToNav: string 27 | estConvFromNav: string 28 | etaMin: string 29 | etaMax: string 30 | 31 | estimatedFees: string 32 | formData = {} 33 | MAX_NAV_PER_TRADE = changellyConstData.MAX_NAV_PER_TRADE 34 | 35 | formDataSubscrip: Subscription 36 | 37 | 38 | constructor( 39 | private dataServ: SendPageDataService, 40 | private genFuncs: GenericFunctionsService, 41 | ) { 42 | this.getDataStatusStream() 43 | this.getFormDataStream() 44 | } 45 | 46 | ngOnInit() { 47 | } 48 | 49 | getFormDataStream() { 50 | this.dataServ.getDataStream().subscribe((data) => { 51 | this.formData = data 52 | if(Object.keys(data).length > 0) 53 | this.updateComponent(this.formData) 54 | }) 55 | } 56 | 57 | getDataStatusStream(): void { 58 | this.dataServ.getDataStatusStream().subscribe((dataStatus: string) => { 59 | this.formDataStatus = dataStatus 60 | }) 61 | } 62 | 63 | updateComponent(formData):void { 64 | this.transferAmount = formData.transferAmount 65 | this.originCoin = formData.originCoin 66 | this.destCoin = formData.destCoin 67 | this.destAddr = formData.destAddr 68 | this.estConvToNav = formData.estConvToNav 69 | this.estConvFromNav = formData.estConvFromNav 70 | this.estimatedFees = formData.estimatedFees 71 | this.etaMin = formData.estTime[0] 72 | this.etaMax = formData.estTime[1] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /frontend/src/app/components/test-socket-interface/test-socket-interface.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

{{message.text}}

5 |
-------------------------------------------------------------------------------- /frontend/src/app/components/test-socket-interface/test-socket-interface.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/app/components/test-socket-interface/test-socket-interface.component.scss -------------------------------------------------------------------------------- /frontend/src/app/components/test-socket-interface/test-socket-interface.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TestSocketInterfaceComponent } from './test-socket-interface.component'; 4 | 5 | describe('TestSocketInterfaceComponent', () => { 6 | let component: TestSocketInterfaceComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TestSocketInterfaceComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TestSocketInterfaceComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/components/test-socket-interface/test-socket-interface.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy} from '@angular/core' 2 | import { Observable } from 'rxjs/Observable' 3 | 4 | import * as io from 'socket.io-client' 5 | 6 | import { GenericSocketService } from '../../services/generic-socket/generic-socket' 7 | 8 | @Component({ 9 | selector: 'test-socket-interface', 10 | templateUrl: './test-socket-interface.component.html', 11 | styleUrls: ['./test-socket-interface.component.scss'] 12 | }) 13 | export class TestSocketInterfaceComponent implements OnInit, OnDestroy { 14 | 15 | private socketUrl = 'http://localhost:8080' 16 | 17 | private socket 18 | 19 | messages: any = [{text: 'sample message'}] 20 | connection 21 | message 22 | 23 | constructor(private genericSocket: GenericSocketService ) {} 24 | 25 | ngOnInit() { 26 | this.connection = this.genericSocket.getMessages(this.socketUrl, 'MESSAGE').subscribe((message) => { 27 | console.log(message) 28 | if(this.messages.length > 5) { 29 | this.messages = [] 30 | } 31 | this.messages.push(message) 32 | }) 33 | } 34 | 35 | ngOnDestroy() { 36 | this.connection.unsubscribe() 37 | } 38 | 39 | sendMessage(messageType, messageContent) { 40 | this.genericSocket.sendMessage(messageType, messageContent) 41 | console.log('MESSAGE SENT', messageContent) 42 | this.message = '' 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/app/components/tile/tile.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

{{title}}

6 |
7 |

{{text}}

8 |
9 |
10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /frontend/src/app/components/tile/tile.component.md: -------------------------------------------------------------------------------- 1 | # Tile Component 2 | 3 | The tile component is used across the site and can be configured in various ways. If the tile is linked it should be wrapped in an A tag in its parent component. Layout is controlled by the parent also. 4 | 5 | ## Options 6 | 7 | | Option | Value | Type | Required | 8 | |:-----------|:-----------| 9 | | title | `` | `String` | false | 10 | | text | `` | `String` | false | 11 | | buttonText | `` | `String` | false | 12 | | icon | `` | `String` | false | 13 | | theme | `` | `String(Theme)` | true | 14 | 15 | ## Themes 16 | 17 | | Theme | Description | 18 | |:-----------|:-----------| 19 | | tile-blue | Blue with transparent background | 20 | | tile-blue-white | Blue with white background | 21 | | tile-fuschia | Fuschia with transparent background | 22 | | tile-fuschia-white | Fuschia with white background | 23 | -------------------------------------------------------------------------------- /frontend/src/app/components/tile/tile.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/base.scss'; 2 | 3 | .tile { 4 | &.tile-blue-white, &.tile-fuschia-white { 5 | background-color: $white; 6 | } 7 | &.tile-fuschia, &.tile-blue { 8 | padding-bottom: 20px; 9 | .tile-text, .tile-title { 10 | color: $white; 11 | } 12 | } 13 | &.tile-blue-white, &.tile-fuschia-white { 14 | .tile-text { 15 | color: $black; 16 | } 17 | } 18 | &.tile-blue-white { 19 | .tile-title { 20 | color: $blue; 21 | } 22 | } 23 | &.tile-fuschia-white { 24 | .tile-title { 25 | color: $fuschia; 26 | } 27 | } 28 | &.tile-fuschia-white, &.tile-fuschia { 29 | .tile-button button { 30 | background-color: $fuschia; 31 | } 32 | } 33 | &.tile-blue-white, &.tile-blue { 34 | .tile-button button { 35 | background-color: $blue; 36 | } 37 | } 38 | } 39 | 40 | .tile-image { 41 | padding: 20px 20px 0 20px; 42 | img { 43 | max-width: 100%; 44 | } 45 | } 46 | 47 | .tile-text { 48 | padding: 0 20px; 49 | max-width: 600px; 50 | margin: 0 auto; 51 | &.no-button { 52 | padding-bottom: 20px; 53 | } 54 | } 55 | 56 | .tile-button { 57 | .btn { 58 | border-radius: 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/app/components/tile/tile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TileComponent } from './tile.component'; 4 | 5 | describe('TileComponent', () => { 6 | let component: TileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TileComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TileComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/components/tile/tile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'tile-component', 5 | templateUrl: './tile.component.html', 6 | styleUrls: ['./tile.component.scss'] 7 | }) 8 | export class TileComponent implements OnInit { 9 | 10 | @Input() title: string; 11 | @Input() text: string; 12 | @Input() buttonText: string; 13 | @Input() icon: string; 14 | @Input() theme: string; 15 | 16 | constructor() { } 17 | 18 | ngOnInit() { 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/app/mock-classes.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export const fakeData = ['NAV', 'BTC', 'ETH', 'LTC'] 4 | const fiveCoins = 5 5 | 6 | export class MockChangellyService { 7 | getCurrencies = function(){ 8 | return Observable.of(fakeData) 9 | } 10 | 11 | getExchangeAmount = function(){ 12 | return Observable.of(fiveCoins) 13 | } 14 | getMinAmount = function(){ 15 | return Observable.of(fiveCoins) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/pages/about/about.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |

NavTech

11 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

12 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |

Changelly

26 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

27 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /frontend/src/app/pages/about/about.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | -------------------------------------------------------------------------------- /frontend/src/app/pages/about/about.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AboutPage } from './about.component'; 4 | import { LegalSection } from '../../sections/legal/legal.component'; 5 | import { SellingPointsSection } from '../../sections/selling-points/selling-points.component'; 6 | import { HowItWorksSection } from '../../sections/how-it-works/how-it-works.component'; 7 | 8 | import { TileComponent } from '../../components/tile/tile.component'; 9 | 10 | 11 | describe('AboutPage', () => { 12 | let component: AboutPage; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [ 18 | AboutPage, 19 | HowItWorksSection, 20 | SellingPointsSection, 21 | LegalSection, 22 | TileComponent, 23 | ] 24 | }) 25 | .compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(AboutPage); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should be created', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /frontend/src/app/pages/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'about-page', 5 | templateUrl: './about.component.html', 6 | styleUrls: ['./about.component.scss'] 7 | }) 8 | export class AboutPage implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/pages/demo/demo.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | -------------------------------------------------------------------------------- /frontend/src/app/pages/demo/demo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { MaterializeModule } from 'angular2-materialize'; 4 | import { HttpModule } from '@angular/http'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { LegalSection } from '../../sections/legal/legal.component'; 8 | import { HeaderSection } from '../../sections/header/header.component'; 9 | import { FooterSection } from '../../sections/footer/footer.component'; 10 | import { SellingPointsSection } from '../../sections/selling-points/selling-points.component'; 11 | import { HeroBannerSection } from '../../sections/hero-banner/hero-banner.component'; 12 | import { SendCoinsFormComponent } from '../../components/send-coins-form/send-coins-form.component'; 13 | 14 | import { TileComponent } from '../../components/tile/tile.component'; 15 | 16 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 17 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 18 | import { OrderService } from '../../services/order/order'; 19 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 20 | import { MockChangellyService } from '../../mock-classes'; 21 | 22 | 23 | import { DemoPage } from './demo.component'; 24 | 25 | describe('DemoPage', () => { 26 | let component: DemoPage; 27 | let fixture: ComponentFixture; 28 | 29 | beforeEach(async(() => { 30 | TestBed.configureTestingModule({ 31 | imports: [ 32 | FormsModule, 33 | MaterializeModule, 34 | HttpModule, 35 | RouterTestingModule 36 | ], 37 | declarations: [ 38 | LegalSection, 39 | HeaderSection, 40 | FooterSection, 41 | SellingPointsSection, 42 | HeroBannerSection, 43 | SendCoinsFormComponent, 44 | TileComponent, 45 | DemoPage, 46 | ], 47 | providers: [ 48 | GenericNodeApiService, 49 | SendPageDataService, 50 | ChangellyApiService, 51 | OrderService, 52 | ] 53 | }) 54 | 55 | .overrideComponent(SendCoinsFormComponent, { 56 | set: { 57 | providers: [ 58 | { provide: ChangellyApiService, useClass: MockChangellyService }, 59 | ] 60 | }}) 61 | 62 | .compileComponents(); 63 | })); 64 | 65 | beforeEach(() => { 66 | fixture = TestBed.createComponent(DemoPage); 67 | component = fixture.componentInstance; 68 | fixture.detectChanges(); 69 | }); 70 | 71 | it('should be created', () => { 72 | expect(component).toBeTruthy(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /frontend/src/app/pages/demo/demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'demo-page', 5 | templateUrl: './demo.component.html', 6 | styleUrls: ['./demo.component.scss'] 7 | }) 8 | export class DemoPage implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/app/pages/home/home.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/app/pages/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | import { HttpModule } from '@angular/http'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { HomePage } from './home.component'; 8 | 9 | import { HowItWorksSection } from '../../sections/how-it-works/how-it-works.component'; 10 | import { LegalSection } from '../../sections/legal/legal.component'; 11 | import { PartnersSection } from '../../sections/partners/partners.component'; 12 | import { HeaderSection } from '../../sections/header/header.component'; 13 | import { FooterSection } from '../../sections/footer/footer.component'; 14 | import { SellingPointsSection } from '../../sections/selling-points/selling-points.component'; 15 | import { SendCoinsFormComponent } from '../../components/send-coins-form/send-coins-form.component'; 16 | import { HeroBannerSection } from '../../sections/hero-banner/hero-banner.component'; 17 | 18 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 19 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 20 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 21 | import { OrderService } from '../../services/order/order'; 22 | 23 | import { MockChangellyService } from '../../mock-classes'; 24 | 25 | import { TileComponent } from '../../components/tile/tile.component'; 26 | 27 | 28 | describe('HomePage', () => { 29 | let component: HomePage; 30 | let fixture: ComponentFixture; 31 | 32 | beforeEach(async(() => { 33 | TestBed.configureTestingModule({ 34 | imports: [ 35 | MaterializeModule, 36 | HttpModule, 37 | FormsModule, 38 | RouterTestingModule 39 | ], 40 | declarations: [ 41 | HomePage, 42 | LegalSection, 43 | HeaderSection, 44 | FooterSection, 45 | SellingPointsSection, 46 | HowItWorksSection, 47 | HeroBannerSection, 48 | PartnersSection, 49 | TileComponent, 50 | SendCoinsFormComponent, 51 | ], 52 | providers: [ 53 | GenericNodeApiService, 54 | SendPageDataService, 55 | ChangellyApiService, 56 | OrderService 57 | ] 58 | }) 59 | 60 | .overrideComponent(SendCoinsFormComponent, { 61 | set: { 62 | providers: [ 63 | { provide: ChangellyApiService, useClass: MockChangellyService }, 64 | ] 65 | }}) 66 | 67 | .compileComponents(); 68 | })); 69 | 70 | beforeEach(() => { 71 | fixture = TestBed.createComponent(HomePage); 72 | component = fixture.componentInstance; 73 | fixture.detectChanges(); 74 | }); 75 | 76 | it('should be created', () => { 77 | expect(component).toBeTruthy(); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /frontend/src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home-page', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomePage implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/pages/send/send.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Send Coins

3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /frontend/src/app/pages/send/send.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | -------------------------------------------------------------------------------- /frontend/src/app/pages/send/send.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | import { HttpModule } from '@angular/http'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { SendPage } from './send.component'; 8 | 9 | import { SendCoinsFormComponent } from '../../components/send-coins-form/send-coins-form.component'; 10 | import { StatusComponent } from '../../components/status/status.component'; 11 | 12 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 13 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 14 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 15 | import { OrderService } from '../../services/order/order'; 16 | 17 | import { MockChangellyService } from '../../mock-classes'; 18 | 19 | 20 | 21 | describe('SendPage', () => { 22 | let component: SendPage; 23 | let fixture: ComponentFixture; 24 | 25 | beforeEach(async(() => { 26 | TestBed.configureTestingModule({ 27 | imports:[ 28 | MaterializeModule, 29 | HttpModule, 30 | FormsModule, 31 | RouterTestingModule 32 | ], 33 | declarations: [ 34 | SendPage, 35 | SendCoinsFormComponent, 36 | StatusComponent, 37 | ], 38 | providers: [ 39 | GenericNodeApiService, 40 | SendPageDataService, 41 | ChangellyApiService, 42 | OrderService 43 | ] 44 | }) 45 | 46 | .overrideComponent(SendCoinsFormComponent, { 47 | set: { 48 | providers: [ 49 | { provide: ChangellyApiService, useClass: MockChangellyService }, 50 | ] 51 | }}) 52 | 53 | .compileComponents(); 54 | })); 55 | 56 | beforeEach(() => { 57 | fixture = TestBed.createComponent(SendPage); 58 | component = fixture.componentInstance; 59 | fixture.detectChanges(); 60 | }); 61 | 62 | it('should be created', () => { 63 | expect(component).toBeTruthy(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /frontend/src/app/pages/send/send.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'send-page', 5 | templateUrl: './send.component.html', 6 | styleUrls: ['./send.component.scss'] 7 | }) 8 | export class SendPage implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/pages/status/status.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Page Loading
3 |
4 |
5 |

IP Address Blocked

6 |
We've detected suspicious behaviour from your IP Address, so we have temporarily blocked any requests from this address.
7 |
8 | 9 |
10 |

Order Not Found

11 |
Either the Order ID, Password, or both, are incorrect. Please ensure you have the correct Order ID and Password
12 |
13 | 14 |
15 |

Order Status

16 |
17 |
18 |
19 | Please transfer EXACTLY {{orderAmount}} {{sourceCurrency | uppercase}} in ONE transaction to the following address.
20 | Transferring an incorrect amount may result in lost currency! 21 |
22 | 28 | 29 |
30 | 31 | 32 |
33 |
Status: {{interpretStatus(orderStatus)}}
34 |
Changelly Order Number: {{changellyOrderNumber}}
35 |
Amount Due: {{orderAmount}} {{sourceCurrency | uppercase}}
36 |
(If sent now) Should arrive in: {{waitTimeLow.toLocaleString()}} ~ {{waitTimeHigh.toLocaleString()}}
37 |
Est. Fee: {{estFee}}
38 |
Currency: {{sourceCurrency | uppercase}}
39 |
40 |
Source Currency: {{sourceCurrency | uppercase}}
41 |
Dest Currency: {{destCurrency | uppercase}}
42 |
43 | 47 | 48 |
49 | 52 | 53 |
54 | Click here to send orderID and Password to an email address 56 |
If you want to be able to view the status of your order, you will need to navigate to the page again, so bookmark this page or note down the id and password in the url.
57 |
58 |
59 |
60 | 61 |
62 | Attempting to abandon order: {{orderId}}
63 | {{abandonStatus}} 64 |
65 | 66 |
67 |
68 | -------------------------------------------------------------------------------- /frontend/src/app/pages/status/status.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | .content { 4 | min-height: 300px; 5 | } 6 | 7 | .copy-address{ 8 | cursor: pointer; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/app/pages/status/status.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | import { HttpModule } from '@angular/http'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { StatusPage } from './send.component'; 8 | 9 | import { SendCoinsFormComponent } from '../../components/send-coins-form/send-coins-form.component'; 10 | import { StatusComponent } from '../../components/status/status.component'; 11 | 12 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 13 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 14 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 15 | import { OrderService } from '../../services/order/order'; 16 | 17 | import { MockChangellyService } from '../../mock-classes'; 18 | 19 | 20 | 21 | describe('StatusPage', () => { 22 | let component: StatusPage; 23 | let fixture: ComponentFixture; 24 | 25 | beforeEach(async(() => { 26 | TestBed.configureTestingModule({ 27 | imports:[ 28 | MaterializeModule, 29 | HttpModule, 30 | FormsModule, 31 | RouterTestingModule 32 | ], 33 | declarations: [ 34 | StatusPage, 35 | SendCoinsFormComponent, 36 | StatusComponent, 37 | ], 38 | providers: [ 39 | GenericNodeApiService, 40 | SendPageDataService, 41 | ChangellyApiService, 42 | OrderService 43 | ] 44 | }) 45 | 46 | .overrideComponent(SendCoinsFormComponent, { 47 | set: { 48 | providers: [ 49 | { provide: ChangellyApiService, useClass: MockChangellyService }, 50 | ] 51 | }}) 52 | 53 | .compileComponents(); 54 | })); 55 | 56 | beforeEach(() => { 57 | fixture = TestBed.createComponent(StatusPage); 58 | component = fixture.componentInstance; 59 | fixture.detectChanges(); 60 | }); 61 | 62 | it('should be created', () => { 63 | expect(component).toBeTruthy(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /frontend/src/app/pages/status/status.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { OrderService } from '../../services/order/order' 4 | import { QRCodeModule } from 'angular2-qrcode'; 5 | import { GenericFunctionsService } from '../../services/generic-functions/generic-functions' 6 | 7 | 8 | @Component({ 9 | selector: 'status-page', 10 | templateUrl: './status.component.html', 11 | styleUrls: ['./status.component.scss'], 12 | 13 | providers: [ 14 | OrderService, 15 | GenericFunctionsService, 16 | ], 17 | }) 18 | export class StatusPage implements OnInit { 19 | 20 | isLoading: boolean = true 21 | orderId: string 22 | orderPass: string 23 | orderData: object 24 | 25 | orderSuccess: boolean 26 | orderFail: boolean 27 | ipBlocked: boolean 28 | beginAbandonOrder: boolean 29 | 30 | orderAmount: string 31 | changellyAddress: string 32 | changellyOrderNumber: string 33 | orderStatus: string 34 | estFee: string 35 | sourceCurrency: string 36 | destCurrency: string 37 | abandonStatus: string 38 | isCopied: boolean 39 | 40 | waitTimeLow: string 41 | waitTimeHigh: string 42 | 43 | constructor( 44 | private OrderService: OrderService, 45 | private GenericFuncs: GenericFunctionsService, 46 | private router: Router, 47 | ) {} 48 | 49 | ngOnInit() { 50 | this.parseUrl(this.router.url) 51 | this.getOrderData() 52 | } 53 | 54 | parseUrl (url: string){ 55 | const split = url.split('/') 56 | this.orderId = split[2] 57 | this.orderPass = split[3] 58 | } 59 | 60 | getOrderData() { 61 | this.OrderService.getOrder(this.orderId, this.orderPass) 62 | .subscribe(data => { 63 | if (data[0]) { 64 | if (data[0] === 'BLOCKED') { 65 | this.ipBlocked = true 66 | } else { 67 | this.orderData = data[0] 68 | this.orderSuccess = true 69 | this.fillData(data) 70 | } 71 | } else { 72 | this.orderFail = true 73 | } 74 | this.isLoading = false 75 | }) 76 | } 77 | 78 | fillData(data) { 79 | const mainData = data[0] 80 | const minMax = data[1] 81 | this.orderAmount = mainData.order_amount 82 | this.changellyAddress = mainData.changelly_address_one 83 | this.orderStatus = mainData.order_status 84 | this.changellyOrderNumber = mainData.changelly_id 85 | this.estFee = "10 NAV" 86 | this.sourceCurrency = mainData.input_currency 87 | this.destCurrency = mainData.output_currency 88 | this.waitTimeLow = '' + minMax[0] + ' mins' 89 | this.waitTimeHigh = '' + minMax[1] + ' mins' 90 | } 91 | 92 | abandonOrder() { 93 | this.beginAbandonOrder = true 94 | this.orderSuccess = false 95 | this.abandonStatus = 'Pending' 96 | 97 | this.OrderService.abandonOrder(this.orderId, this.orderPass) 98 | .subscribe(data => { 99 | console.log(data) 100 | if (data.status === 'SUCCESS') { 101 | this.abandonStatus = 'Order sucessfully abandoned. Redirecting to Home Page in 3 seconds' 102 | setTimeout(()=>{ this.router.navigateByUrl('/') } , 3000) 103 | } else { 104 | this.abandonStatus = 'Failed to Abandon Order' 105 | } 106 | }) 107 | } 108 | 109 | interpretStatus(status: string): string { 110 | switch (status) { 111 | case 'COMPLETED': 112 | return 'Completed' 113 | case 'ABANDONED': 114 | return 'Abandoned' 115 | case 'EXPIRED': 116 | return 'Expired' 117 | case 'CREATED': 118 | return 'Created' 119 | case 'CONFIRMING': 120 | return 'Received' 121 | case 'EXCHANGING': 122 | case 'SENDING': 123 | return 'Exchanging' 124 | case 'FINISHED': 125 | return 'Sent' 126 | case 'FAILED': 127 | case 'REFUNDED': 128 | default: 129 | return 'Error' 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /frontend/src/app/sections/footer/footer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | Polymorph logo 7 | 8 |
9 |
10 |

Links

11 | 17 |
18 |
19 |

Legal

20 |

21 | Deserunt culpa in laborum ad mollit fugiat duis cillum anim ullamco 22 | dolore nisi. Laborum aliqua nostrud quis nostrud consectetur occaecat 23 | proident veniam nisi nulla consectetur. 24 |

25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /frontend/src/app/sections/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | 4 | footer { 5 | text-align: left; 6 | 7 | @media (max-width: 600px) { 8 | text-align: center; 9 | } 10 | } 11 | 12 | a:hover { 13 | font-weight: bold; 14 | } 15 | 16 | li { 17 | line-height: 2; 18 | } 19 | 20 | img { 21 | max-height: 250px; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/app/sections/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FooterSection } from './footer.component'; 4 | 5 | describe('FooterSection', () => { 6 | let component: FooterSection; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FooterSection ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FooterSection); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/sections/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'footer-section', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.scss'] 7 | }) 8 | export class FooterSection implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/sections/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 26 | 27 | 33 |
34 | -------------------------------------------------------------------------------- /frontend/src/app/sections/header/header.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | i { 4 | color:$blue; 5 | } 6 | 7 | nav { 8 | background-color: $white; 9 | .tabs.tabs-transparent .tab a, 10 | .tabs.tabs-transparent .tab.disabled a, 11 | .tabs.tabs-transparent .tab.disabled a:hover { 12 | color: $blue; 13 | } 14 | } 15 | 16 | * /deep/ .tabs.tabs-transparent .indicator { 17 | background-color: $blue; 18 | } 19 | 20 | #header-logo { 21 | height: 50px; 22 | margin: 7px 5px; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/app/sections/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | 4 | import { HeaderSection } from './header.component'; 5 | 6 | import {RouterTestingModule} from "@angular/router/testing"; 7 | 8 | describe('HeaderSection', () => { 9 | let component: HeaderSection; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | MaterializeModule, 16 | RouterTestingModule 17 | ], 18 | declarations: [ 19 | HeaderSection, 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(HeaderSection); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should be created', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /frontend/src/app/sections/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit , EventEmitter, ViewChild } from '@angular/core'; 2 | import { Router, NavigationEnd } from '@angular/router'; 3 | import { Location } from "@angular/common"; 4 | 5 | @Component({ 6 | selector: 'header-section', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.scss'], 9 | providers: [Location] 10 | }) 11 | export class HeaderSection implements OnInit { 12 | 13 | @ViewChild('HOME') home; 14 | @ViewChild('SEND') send; 15 | @ViewChild('ABOUT') about; 16 | 17 | constructor ( 18 | private router: Router, 19 | private location: Location) { 20 | 21 | } 22 | 23 | ngOnInit() { 24 | this.router.events.subscribe ( event => { 25 | if( event instanceof NavigationEnd ){ 26 | this.clickTab(this.getTabFromUrl(window.location.pathname)) 27 | } 28 | }); 29 | } 30 | 31 | getTabFromUrl(url){ 32 | let tab = url.split('/')[1].toUpperCase() 33 | return tab 34 | } 35 | 36 | clickTab(tab){ 37 | switch (tab) { 38 | case '': 39 | setTimeout(() => this.home.nativeElement.click(), 100) 40 | break 41 | case 'SEND': 42 | setTimeout(() => this.send.nativeElement.click(), 100) 43 | break 44 | case 'ABOUT': 45 | setTimeout(() => this.about.nativeElement.click(), 100) 46 | break 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/app/sections/hero-banner/hero-banner.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla.

9 |
10 | 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /frontend/src/app/sections/hero-banner/hero-banner.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | #send-coin-sect { 4 | height: auto; /* Overrides the 500px height that the parallax class applies */ 5 | } 6 | 7 | #paragraph-container { 8 | color: $white; 9 | } 10 | 11 | .parallax-container { 12 | overflow: visible !important; 13 | } 14 | 15 | .parallax { 16 | overflow: hidden !important; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/sections/hero-banner/hero-banner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; 2 | import { MaterializeModule } from 'angular2-materialize'; 3 | import { HttpModule, Http, BaseRequestOptions, XHRBackend } from '@angular/http'; 4 | import { MockBackend } from '@angular/http/testing'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { RouterTestingModule } from '@angular/router/testing'; 7 | 8 | import { SendCoinsFormComponent } from '../../components/send-coins-form/send-coins-form.component'; 9 | 10 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 11 | import { SendPageDataService } from '../../services/send-page-data/send-page-data'; 12 | import { ChangellyApiService } from '../../services/changelly-api/changelly-api'; 13 | import { OrderService } from '../../services/order/order'; 14 | 15 | import { MockChangellyService } from '../../mock-classes'; 16 | 17 | import { HeroBannerSection } from './hero-banner.component'; 18 | 19 | describe('heroBannerSection', () => { 20 | let component: HeroBannerSection; 21 | let fixture: ComponentFixture; 22 | 23 | beforeEach(async(() => { 24 | TestBed.configureTestingModule({ 25 | declarations: [ 26 | HeroBannerSection, 27 | SendCoinsFormComponent, 28 | ], 29 | imports: [ 30 | MaterializeModule, 31 | HttpModule, 32 | FormsModule, 33 | RouterTestingModule, 34 | ], 35 | providers: [ 36 | GenericNodeApiService, 37 | SendPageDataService, 38 | ChangellyApiService, 39 | OrderService, 40 | ] 41 | }) 42 | 43 | .overrideComponent(SendCoinsFormComponent, { 44 | set: { 45 | providers: [ 46 | { provide: ChangellyApiService, useClass: MockChangellyService }, 47 | ] 48 | }}) 49 | 50 | .compileComponents(); 51 | })); 52 | 53 | beforeEach(() => { 54 | fixture = TestBed.createComponent(HeroBannerSection); 55 | component = fixture.componentInstance; 56 | fixture.detectChanges(); 57 | }); 58 | 59 | it('should be created', () => { 60 | expect(component).toBeTruthy(); 61 | }); 62 | 63 | }); 64 | -------------------------------------------------------------------------------- /frontend/src/app/sections/hero-banner/hero-banner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'hero-banner-section', 6 | templateUrl: './hero-banner.component.html', 7 | styleUrls: ['./hero-banner.component.scss'], 8 | providers: [ ], 9 | }) 10 | export class HeroBannerSection implements OnInit { 11 | 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/sections/how-it-works/how-it-works.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |

Send any coins privately

9 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

10 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

11 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

12 | Small 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /frontend/src/app/sections/how-it-works/how-it-works.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | #info-text { 4 | text-align: justify; 5 | padding-bottom: 15px; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/sections/how-it-works/how-it-works.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HowItWorksSection } from './how-it-works.component'; 4 | 5 | describe('HowItWorksSection', () => { 6 | let component: HowItWorksSection; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HowItWorksSection ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HowItWorksSection); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/sections/how-it-works/how-it-works.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'how-it-works-section', 5 | templateUrl: './how-it-works.component.html', 6 | styleUrls: ['./how-it-works.component.scss'] 7 | }) 8 | export class HowItWorksSection implements OnInit { 9 | 10 | @Input('showButton') 11 | showButton: boolean = true; 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/sections/legal/legal.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Legal Information

4 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra est interdum, tempus urna id, consequat velit. Pellentesque finibus massa eget mi sollicitudin, non rhoncus dui euismod. Mauris rutrum orci nulla, scelerisque bibendum elit faucibus a.

5 |
6 |
7 | -------------------------------------------------------------------------------- /frontend/src/app/sections/legal/legal.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/app/sections/legal/legal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LegalSection } from './legal.component'; 4 | 5 | describe('LegalSection', () => { 6 | let component: LegalSection; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LegalSection ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LegalSection); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/sections/legal/legal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'legal-section', 5 | templateUrl: './legal.component.html', 6 | styleUrls: ['./legal.component.scss'] 7 | }) 8 | export class LegalSection implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/sections/partners/partners.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 13 | 14 |
15 |
16 | 17 | 24 | 25 |
26 |
27 | 28 | 35 | 36 |
37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /frontend/src/app/sections/partners/partners.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | 3 | 4 | a { 5 | color: $black; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/sections/partners/partners.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PartnersSection } from './partners.component'; 4 | 5 | import { TileComponent } from '../../components/tile/tile.component'; 6 | 7 | 8 | describe('PartnersSection', () => { 9 | let component: PartnersSection; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ PartnersSection, TileComponent] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(PartnersSection); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should be created', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /frontend/src/app/sections/partners/partners.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'partners-section', 5 | templateUrl: './partners.component.html', 6 | styleUrls: ['./partners.component.scss'] 7 | }) 8 | export class PartnersSection implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/sections/selling-points/selling-points.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 11 |
12 |
13 | 19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /frontend/src/app/sections/selling-points/selling-points.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/base.scss"; 2 | -------------------------------------------------------------------------------- /frontend/src/app/sections/selling-points/selling-points.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SellingPointsSection } from './selling-points.component'; 4 | import { TileComponent } from '../../components/tile/tile.component'; 5 | 6 | 7 | describe('SellingPointsSection', () => { 8 | let component: SellingPointsSection; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ SellingPointsSection, TileComponent ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(SellingPointsSection); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should be created', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/src/app/sections/selling-points/selling-points.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'selling-points-section', 5 | templateUrl: './selling-points.component.html', 6 | styleUrls: ['./selling-points.component.scss'] 7 | }) 8 | export class SellingPointsSection implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/services/changelly-api/changelly-api.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { HttpModule } from '@angular/http'; 3 | 4 | import { ChangellyApiService } from './changelly-api'; 5 | 6 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 7 | 8 | 9 | describe('ChangellyApiService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | HttpModule, 14 | ], 15 | providers: [ 16 | ChangellyApiService, 17 | GenericNodeApiService, 18 | ] 19 | }); 20 | }); 21 | 22 | it('should be created', inject([ChangellyApiService], (service: ChangellyApiService) => { 23 | expect(service).toBeTruthy(); 24 | })); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/services/changelly-api/changelly-api.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { changellyNodeApiEndPoints } from "../config"; 4 | 5 | import {GenericNodeApiService} from '../generic-node-api/generic-node-api'; 6 | 7 | @Injectable() 8 | export class ChangellyApiService { 9 | 10 | constructor( private genServ:GenericNodeApiService) { } 11 | 12 | getCurrencies() { 13 | return this.getApiRequest(changellyNodeApiEndPoints.getCurrencies, undefined) 14 | } 15 | 16 | getEta(originCoin, destCoin) { 17 | return this.getApiRequest(changellyNodeApiEndPoints.getEta, [originCoin, destCoin]) 18 | } 19 | 20 | getMinAmount(originCoin, destCoin) { 21 | return this.getApiRequest(changellyNodeApiEndPoints.getMinAmount, [originCoin, destCoin]) 22 | } 23 | 24 | getExchangeAmount(originCoin, destCoin, amount) { 25 | return this.getApiRequest(changellyNodeApiEndPoints.getExchangeAmount, [originCoin, destCoin, amount]) 26 | } 27 | 28 | getApiRequest(endpoint, params){ 29 | let paramString = '/' 30 | if(params){ //if we have undefined this wont affect the request 31 | params.forEach((param, i) => { 32 | if( i > 0) { 33 | paramString += '/' + param 34 | } else { 35 | paramString += param 36 | } 37 | }); 38 | } 39 | return this.genServ.getRequest('changelly/' + endpoint + paramString) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/app/services/config.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | 3 | export const changellyNodeApiEndPoints = { 4 | getCurrencies: 'getCurrencies', 5 | getMinAmount: 'getMinAmount', 6 | getExchangeAmount: 'getExchangeAmount', 7 | getGenerateAddress: 'generateAddress', 8 | getTransaction: 'getTransactions', 9 | getExchangeStatus: 'getStatus', 10 | getEta: 'getEta', 11 | } 12 | 13 | export const orderNodeApiEndPoints = { 14 | createOrder: 'createOrder', 15 | getOrder: 'getOrder', 16 | getOrderStatus: 'getOrderStatus', 17 | abandonOrder: 'abandonOrder', 18 | getEta: 'getEta', 19 | } 20 | 21 | export const nodeApiBaseUrl = 'http://localhost:8080/api/' 22 | 23 | export const changellyConstData = { 24 | 'CHANGELLY_FEE': 0.005, 25 | 'NAVTECH_FEE': 0.005, 26 | 27 | 'MAX_NAV_PER_TRADE': 10000, 28 | } 29 | 30 | export interface dataBundleTemplate { 31 | transferAmount?: string, 32 | originCoin?: string, 33 | destCoin?: string, 34 | destAddr?: string, 35 | estConvToNav?: any, 36 | estConvFromNav?: string, 37 | estTime?: any, 38 | estimatedFees?: string, 39 | changellyFeeOne?: any, 40 | minTransferAmount?:string 41 | errors?: Array 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-functions/generic-functions.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { GenericFunctionsService } from './generic-functions'; 4 | 5 | describe('GenericFunctionsService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [GenericFunctionsService, Title] 9 | }); 10 | 11 | }); 12 | 13 | it('should be created', inject([GenericFunctionsService], (genericFunctionsService) => { 14 | expect(genericFunctionsService).toBeTruthy(); 15 | })); 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-functions/generic-functions.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class GenericFunctionsService { 5 | 6 | constructor() { } 7 | 8 | calculateOrderEst(minutes) { 9 | let estimate = new Date() 10 | estimate.setMinutes(estimate.getMinutes() + minutes) 11 | return estimate 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-node-api/generic-node-api.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { HttpModule } from '@angular/http'; 3 | 4 | import { GenericNodeApiService } from './generic-node-api'; 5 | 6 | describe('GenericNodeApiService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | HttpModule 11 | ], 12 | providers: [ 13 | GenericNodeApiService, 14 | 15 | ] 16 | }); 17 | }); 18 | 19 | it('should be created', inject([GenericNodeApiService], (service: GenericNodeApiService) => { 20 | expect(service).toBeTruthy(); 21 | })); 22 | 23 | // it('should extract data from a response') 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-node-api/generic-node-api.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Http, Response } from '@angular/http' 3 | 4 | import { Observable } from 'rxjs/Rx' 5 | import 'rxjs/add/operator/catch' 6 | import 'rxjs/add/operator/map' 7 | 8 | import { nodeApiBaseUrl } from "../config" 9 | 10 | 11 | @Injectable() 12 | export class GenericNodeApiService { 13 | 14 | baseApiUrl: string = nodeApiBaseUrl 15 | 16 | constructor(private http: Http) { } 17 | 18 | getRequest(apiRouteUrl): Observable { 19 | return this.http.get(this.baseApiUrl + apiRouteUrl) 20 | .map(this.extractData) 21 | .catch(this.handleError) 22 | } 23 | 24 | private extractData(res: Response) { 25 | try{ 26 | let body = res.json() 27 | return body.result || body 28 | } catch (error) { 29 | console.error('Error extracting api data: ' + error ) 30 | return ['Error'] 31 | } 32 | } 33 | 34 | private handleError (error: Response | any) { 35 | let errMsg: string 36 | if (error instanceof Response) { 37 | const body = error.json() || '' 38 | const err = body.error || JSON.stringify(body) 39 | errMsg = `${error.status} - ${error.statusText || ''} ${err}` 40 | } else { 41 | errMsg = error.message ? error.message : error.toString() 42 | } 43 | return Observable.throw(errMsg) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-socket/generic-socket.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { HttpModule } from '@angular/http'; 3 | 4 | import { GenericSocketService } from './generic-socket'; 5 | 6 | describe('GenericSocketService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | HttpModule 11 | ], 12 | providers: [ 13 | GenericSocketService, 14 | 15 | ] 16 | }); 17 | }); 18 | 19 | it('should be created', inject([GenericSocketService], (service: GenericSocketService) => { 20 | expect(service).toBeTruthy(); 21 | })); 22 | 23 | // it('should extract data from a response') 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/services/generic-socket/generic-socket.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Http, Response } from '@angular/http' 3 | 4 | import { Observable } from 'rxjs/Observable' 5 | 6 | import * as io from 'socket.io-client' 7 | 8 | 9 | @Injectable() 10 | export class GenericSocketService { 11 | 12 | private socket 13 | 14 | constructor() { } 15 | 16 | sendMessage(messageType:string , messageContent:string) { 17 | if(!this.socket) { 18 | console.log('Err: Socket not setup yet') 19 | return 20 | } 21 | this.socket.emit(messageType, messageContent) 22 | console.log("MESSAGE SENT", messageContent) 23 | } 24 | 25 | getMessages(socketUrl, mode) { 26 | let observable = new Observable(observer => { 27 | this.socket = io(socketUrl) 28 | console.log('socketUrl', socketUrl) 29 | if (mode === 'MESSAGE' || mode === 'ALL') { 30 | this.socket.on('MESSAGE', (data) => { 31 | observer.next(data) 32 | }) 33 | } 34 | if (mode === 'SERVER_MODE' || mode === 'ALL') { 35 | this.socket.on('SERVER_MODE', (data) => { 36 | observer.next(data) 37 | }) 38 | } 39 | 40 | if (mode === 'SERVER_MESSAGES' || mode === 'ALL') { 41 | this.socket.on('SERVER_MESSAGE', (data) => { 42 | observer.next(data) 43 | }) 44 | } 45 | return () => { 46 | this.socket.disconnect() 47 | } 48 | }) 49 | return observable 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/app/services/order/order.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { HttpModule } from '@angular/http'; 3 | 4 | import { OrderService } from './order'; 5 | 6 | import { GenericNodeApiService } from './../../services/generic-node-api/generic-node-api'; 7 | 8 | 9 | describe('OrderService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | HttpModule, 14 | ], 15 | providers: [ 16 | OrderService, 17 | GenericNodeApiService, 18 | ] 19 | }); 20 | }); 21 | 22 | it('should be created', inject([OrderService], (service: OrderService) => { 23 | expect(service).toBeTruthy(); 24 | })); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/services/order/order.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import {GenericNodeApiService} from '../generic-node-api/generic-node-api'; 4 | 5 | import { orderNodeApiEndPoints } from "../config"; 6 | 7 | @Injectable() 8 | export class OrderService { 9 | 10 | constructor( private genServ:GenericNodeApiService) { } 11 | 12 | createOrder(originCoin, destCoin, destAddr, transferAmount) { 13 | return this.getApiRequest( orderNodeApiEndPoints.createOrder, [originCoin, destCoin, destAddr, transferAmount]) 14 | } 15 | 16 | getOrder(orderId, password) { 17 | return this.getApiRequest( orderNodeApiEndPoints.getOrder, [orderId, password]) 18 | } 19 | 20 | updateOrderStatus(orderId, password, newStatus) { 21 | return this.getApiRequest( orderNodeApiEndPoints.getOrderStatus, [orderId, password, newStatus]) 22 | } 23 | 24 | abandonOrder(orderId, password) { 25 | return this.getApiRequest( orderNodeApiEndPoints.abandonOrder, [orderId, password]) 26 | } 27 | 28 | getApiRequest(endpoint, params){ 29 | let paramString = '/' 30 | if(params){ //if we have undefined this wont affect the request 31 | params.forEach((param, i) => { 32 | if( i > 0) { 33 | paramString += '/' + param 34 | } else { 35 | paramString += param 36 | } 37 | }); 38 | } 39 | return this.genServ.getRequest('order/' + endpoint + paramString) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/app/services/title-change/title-change.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { TitleChangeService } from './title-change'; 4 | import { Title } from '@angular/platform-browser'; 5 | 6 | 7 | describe('TitleChangeService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [TitleChangeService, Title] 11 | }); 12 | 13 | }); 14 | 15 | it('should be created', inject([TitleChangeService], (titleChangeService) => { 16 | expect(titleChangeService).toBeTruthy(); 17 | })); 18 | 19 | }) 20 | -------------------------------------------------------------------------------- /frontend/src/app/services/title-change/title-change.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { Title } from '@angular/platform-browser'; 4 | 5 | 6 | @Injectable() 7 | export class TitleChangeService { 8 | 9 | constructor(private titleService: Title) { } 10 | 11 | updateTitle(event) { 12 | let newTitle = this.getNewTitle(event.url); 13 | this.titleService.setTitle(newTitle); 14 | } 15 | 16 | getNewTitle(url: string){ 17 | let newTitle = 'Polymorph - ' 18 | if(url === '/'){ 19 | return newTitle + 'Home'; 20 | } 21 | 22 | newTitle = newTitle + url.slice(1,2).toUpperCase() + url.slice(2) 23 | newTitle = newTitle.split('/')[0] // This removes any trailing url params e.g. url is status/id/pass 24 | return newTitle; 25 | } 26 | 27 | setTitle( string ){ 28 | this.titleService.setTitle(string); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/app/styles/base.scss: -------------------------------------------------------------------------------- 1 | @import './colors.scss'; 2 | @import './fonts.scss'; 3 | @import './layout.scss'; 4 | @import './buttons.scss'; 5 | @import './forms.scss'; 6 | -------------------------------------------------------------------------------- /frontend/src/app/styles/buttons.scss: -------------------------------------------------------------------------------- 1 | button { 2 | width: 60%; 3 | max-width: 175px; 4 | } 5 | 6 | .btn-small { 7 | width: 25%; 8 | max-width: 125px; 9 | min-width: 70px; 10 | padding: 0 0.5rem; 11 | } 12 | 13 | .btn-wide { 14 | width: 100%; 15 | max-width: 100%; 16 | padding: 0; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $white: #FFF; 2 | $grey-light: #F5F5F5; 3 | $grey: #c7c5c5; 4 | $grey-dark: #828282; 5 | $grey-form: #9e9e9e; 6 | $black: #333; 7 | $fuschia: #c926b5; 8 | $purple: #7d59b5; 9 | $blue: #0e4bbe; 10 | $ice: #43b5eb; 11 | $red: red; 12 | 13 | /*font colours*/ 14 | 15 | .txt-black { 16 | color: $black; 17 | } 18 | 19 | .txt-white { 20 | color: $white; 21 | } 22 | 23 | .txt-purple { 24 | color: $purple; 25 | } 26 | 27 | .txt-blue { 28 | color: $blue; 29 | } 30 | 31 | .txt-grey-light { 32 | color: $grey-light; 33 | } 34 | 35 | .txt-grey { 36 | color: $grey; 37 | } 38 | 39 | .txt-grey-dark { 40 | color: $grey-dark; 41 | } 42 | 43 | /* Background Colors*/ 44 | 45 | .bg-black { 46 | background-color: $black; 47 | } 48 | 49 | .bg-white { 50 | background-color: $white; 51 | } 52 | 53 | .bg-fuschia { 54 | background-color: $fuschia; 55 | color: $white; 56 | } 57 | 58 | .bg-fuschia-dark { 59 | background-color: darken($fuschia, 20%); 60 | color: $white; 61 | } 62 | 63 | .bg-blue-light { 64 | background-color: lighten($blue, 20%); 65 | color: $white; 66 | } 67 | 68 | .bg-blue { 69 | background-color: $blue; 70 | color: $white; 71 | } 72 | 73 | .bg-blue-dark { 74 | background-color: darken($blue, 20%); 75 | color: $white; 76 | } 77 | 78 | .bg-purp { 79 | background-color: $purple; 80 | } 81 | 82 | .bg-ice { 83 | background-color: $ice; 84 | } 85 | 86 | .bg-grey-dark { 87 | background-color: $grey-dark; 88 | } 89 | 90 | .bg-grey { 91 | background-color: $grey; 92 | } 93 | 94 | .bg-grey-light { 95 | background-color: $grey-light; 96 | } 97 | -------------------------------------------------------------------------------- /frontend/src/app/styles/fonts.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 50px; 3 | } 4 | 5 | h2 { 6 | font-size: 40px; 7 | } 8 | 9 | h3 { 10 | font-size: 30px; 11 | } 12 | 13 | h4 { 14 | font-size: 20px; 15 | } 16 | 17 | h5 { 18 | font-size: 16px; 19 | } 20 | 21 | h6 { 22 | font-size: 14px; 23 | } 24 | 25 | .text-center { 26 | text-align: center; 27 | } 28 | 29 | .text-center-m { 30 | @media(max-width: 991px){ 31 | text-align: center; 32 | } 33 | } 34 | 35 | .text-right-l { 36 | @media(min-width: 992px){ 37 | text-align: right; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/app/styles/forms.scss: -------------------------------------------------------------------------------- 1 | /*** FORM GENERIC ****/ 2 | 3 | .input-field { 4 | textarea { 5 | padding: 10px; 6 | } 7 | input { 8 | padding: 0 10px; 9 | } 10 | .input-with-select { 11 | border-top-right-radius: 0; 12 | } 13 | input, textarea { 14 | border-top-left-radius: 5px; 15 | border-top-right-radius: 5px; 16 | width: calc(100% - 20px); 17 | } 18 | label { 19 | left: 10px; 20 | } 21 | label:not(.label-icon).active { 22 | font-size: 0.8rem; 23 | -webkit-transform: translateY(-175%); 24 | transform: translateY(-175%); 25 | } 26 | input[type=text]:focus:not([readonly]), textarea:focus:not([readonly]) { 27 | box-shadow: none; 28 | } 29 | } 30 | 31 | .input-field /deep/ .select-wrapper + label { 32 | top: -20px; 33 | } 34 | 35 | .input-field /deep/ .select-wrapper input.select-dropdown { 36 | border-top-right-radius: 5px; 37 | padding: 0 10px; 38 | width: calc(100% - 20px); 39 | } 40 | 41 | .input-field /deep/ .select-wrapper span.caret { 42 | right: 10px; 43 | } 44 | 45 | input[type=text]:disabled:not([readonly]), textarea:disabled:not([readonly]), 46 | .input-field /deep/ .select-wrapper input.select-dropdown:disabled { 47 | border-bottom: 1px solid #9e9e9e; 48 | } 49 | /**** FORM LIGHT *******/ 50 | 51 | .form-light { 52 | .input-field { 53 | input, textarea { 54 | background-color: rgba(255,255,255,0.2); 55 | color: $grey-form; 56 | } 57 | 58 | label:not(.label-icon).active { 59 | color: $ice; 60 | } 61 | input[type=text]:focus:not([readonly]), textarea:focus:not([readonly]){ 62 | border-bottom: 1px solid $ice; 63 | } 64 | } 65 | 66 | .input-field /deep/ .select-wrapper { 67 | color: $grey-form; 68 | } 69 | 70 | .input-field /deep/ .select-wrapper + label { 71 | color: $ice; 72 | } 73 | 74 | .input-field /deep/ .select-wrapper input.select-dropdown { 75 | background-color: rgba(255,255,255,0.2); 76 | } 77 | 78 | .input-field /deep/ .select-wrapper span.caret { 79 | color: $grey-form; 80 | } 81 | 82 | .input-field /deep/ .select-wrapper .dropdown-content li > span { 83 | color: $blue; 84 | } 85 | } 86 | 87 | /**** FORM DARK *******/ 88 | 89 | .form-dark { 90 | .input-field { 91 | input, textarea { 92 | background-color: rgba(14,75,190,0.1); 93 | color: $black; 94 | } 95 | label { 96 | color: $black; 97 | } 98 | 99 | input[type=text]:disabled + label { 100 | color: rgba(0, 0, 0, 0.26); 101 | } 102 | 103 | label:not(.label-icon).active { 104 | color: $blue; 105 | } 106 | input[type=text]:focus:not([readonly]), textarea:focus:not([readonly]) { 107 | border-bottom: 1px solid $black; 108 | } 109 | } 110 | 111 | .input-field /deep/ .select-wrapper { 112 | color: $black; 113 | } 114 | 115 | .input-field /deep/ .select-wrapper + label { 116 | color: $blue; 117 | } 118 | 119 | .input-field /deep/ .select-wrapper input.select-dropdown { 120 | background-color: rgba(14,75,190,0.1); 121 | } 122 | 123 | .input-field /deep/ .select-wrapper span.caret { 124 | color: $black; 125 | } 126 | 127 | .input-field /deep/ .select-wrapper .dropdown-content li > span { 128 | color: $black; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /frontend/src/app/styles/layout.scss: -------------------------------------------------------------------------------- 1 | .section { 2 | padding: 4rem 1rem; 3 | } 4 | 5 | .flex-row { 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: center; 10 | &.no-flex-s { 11 | @media(max-width: 600px){ 12 | display: inline-block; 13 | } 14 | } 15 | &.no-flex-m { 16 | @media(max-width: 991px){ 17 | display: inline-block; 18 | } 19 | } 20 | } 21 | 22 | .show-m { 23 | display: none; 24 | @media(max-width: 991px){ 25 | display: inline-block; 26 | } 27 | } 28 | 29 | .hide-m { 30 | display: inline-block; 31 | @media(max-width: 991px){ 32 | display: none; 33 | } 34 | } 35 | 36 | .margin-bottom-m { 37 | @media(max-width: 991px){ 38 | margin-bottom: 20px; 39 | } 40 | } 41 | 42 | .margin-bottom-s { 43 | @media(max-width: 600px){ 44 | margin-bottom: 20px; 45 | } 46 | } 47 | 48 | .flex-grow { 49 | flex: 1; 50 | } 51 | 52 | .container { 53 | img { 54 | max-width: 100%; 55 | } 56 | } 57 | 58 | .no-margin { 59 | margin: 0!important; 60 | } 61 | 62 | .no-vert-spacing { 63 | margin-top: 0!important; 64 | margin-bottom: 0!important; 65 | padding-top: 0!important; 66 | padding-bottom: 0!important; 67 | } 68 | 69 | .no-hoz-spacing { 70 | margin-left: 0!important; 71 | margin-right: 0!important; 72 | padding-left: 0!important; 73 | padding-right: 0!important; 74 | } 75 | -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/background.jpg -------------------------------------------------------------------------------- /frontend/src/assets/circle-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/circle-image.png -------------------------------------------------------------------------------- /frontend/src/assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/diagram.png -------------------------------------------------------------------------------- /frontend/src/assets/lock-blue-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/lock-blue-20.png -------------------------------------------------------------------------------- /frontend/src/assets/lock-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/lock-blue.png -------------------------------------------------------------------------------- /frontend/src/assets/lock-fuschia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/lock-fuschia.png -------------------------------------------------------------------------------- /frontend/src/assets/lock-ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/lock-ice.png -------------------------------------------------------------------------------- /frontend/src/assets/logo-extended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/logo-extended.png -------------------------------------------------------------------------------- /frontend/src/assets/logo-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/logo-mark.png -------------------------------------------------------------------------------- /frontend/src/assets/navcoin-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/navcoin-bg.jpg -------------------------------------------------------------------------------- /frontend/src/assets/polymorph-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/assets/polymorph-background.jpg -------------------------------------------------------------------------------- /frontend/src/assets/polymorph-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 14 | 17 | 21 | 22 | 27 | 32 | 33 | 34 | 37 | 42 | 47 | 48 | 49 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/assets/polymorph-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Encrypt-S/NavMorph/cd351e42a6a8266e4c7e7a4996fc304aee9510db/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Polymorph - Home 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Loading... 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | 47 | 48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "../src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [ 16 | true, 17 | "rxjs" 18 | ], 19 | "import-spacing": true, 20 | "indent": [ 21 | true, 22 | "spaces" 23 | ], 24 | "interface-over-type-literal": true, 25 | "label-position": true, 26 | "max-line-length": [ 27 | true, 28 | 140 29 | ], 30 | "member-access": false, 31 | "member-ordering": [ 32 | true, 33 | "static-before-instance", 34 | "variables-before-functions" 35 | ], 36 | "no-arg": true, 37 | "no-bitwise": true, 38 | "no-console": [ 39 | true, 40 | "debug", 41 | "info", 42 | "time", 43 | "timeEnd", 44 | "trace" 45 | ], 46 | "no-construct": true, 47 | "no-debugger": true, 48 | "no-empty": false, 49 | "no-empty-interface": true, 50 | "no-eval": true, 51 | "no-inferrable-types": [ 52 | true, 53 | "ignore-params" 54 | ], 55 | "no-shadowed-variable": true, 56 | "no-string-literal": false, 57 | "no-string-throw": true, 58 | "no-switch-case-fall-through": true, 59 | "no-trailing-whitespace": true, 60 | "no-unused-expression": true, 61 | "no-use-before-declare": true, 62 | "no-var-keyword": true, 63 | "object-literal-sort-keys": false, 64 | "one-line": [ 65 | true, 66 | "check-open-brace", 67 | "check-catch", 68 | "check-else", 69 | "check-whitespace" 70 | ], 71 | "prefer-const": true, 72 | "quotemark": [ 73 | true, 74 | "single" 75 | ], 76 | "radix": true, 77 | "semicolon": [ 78 | "always" 79 | ], 80 | "triple-equals": [ 81 | true, 82 | "allow-null-check" 83 | ], 84 | "typedef-whitespace": [ 85 | true, 86 | { 87 | "call-signature": "nospace", 88 | "index-signature": "nospace", 89 | "parameter": "nospace", 90 | "property-declaration": "nospace", 91 | "variable-declaration": "nospace" 92 | } 93 | ], 94 | "typeof-compare": true, 95 | "unified-signatures": true, 96 | "variable-name": false, 97 | "whitespace": [ 98 | true, 99 | "check-branch", 100 | "check-decl", 101 | "check-operator", 102 | "check-separator", 103 | "check-type" 104 | ], 105 | "directive-selector": [ 106 | true, 107 | "attribute", 108 | "app", 109 | "camelCase" 110 | ], 111 | "component-selector": [ 112 | true, 113 | "element", 114 | "app", 115 | "kebab-case" 116 | ], 117 | "use-input-property-decorator": true, 118 | "use-output-property-decorator": true, 119 | "use-host-property-decorator": true, 120 | "no-input-rename": true, 121 | "no-output-rename": true, 122 | "use-life-cycle-interface": true, 123 | "use-pipe-transform-interface": true, 124 | "component-class-suffix": true, 125 | "directive-class-suffix": true, 126 | "no-access-missing-member": true, 127 | "templates-use-public": true, 128 | "invoke-injectable": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymorph", 3 | "version": "0.1.0", 4 | "description": "A site for anonymizing crypto currencies via Changelly's crypto exchange system and NavTech's anonymous NavCoin transactions.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "echo 'You must use \nnpm run start:server \nand/or \nnpm run start:client'", 8 | "start:server": "docker-compose -f ./server/docker-compose-prod.yml -f ./server/docker-compose-dev.yml up", 9 | "docker:server:build": "docker-compgitose -f ./server/docker-compose-prod.yml -f ./server/docker-compose-dev.yml up --build", 10 | "start:client": "docker run -it -p 4200:4200 -v \"$(pwd)\":/app navmorph-frontend", 11 | "docker:client:build": "docker build -f ./frontend/dev.Dockerfile -t navmorph-frontend ." 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/NAVCoin/polymorph.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/NAVCoin/polymorph/issues" 21 | }, 22 | "homepage": "https://github.com/NAVCoin/polymorph#readme" 23 | } 24 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9.4-alpine 2 | 3 | RUN apk update && apk upgrade && \ 4 | apk add --no-cache bash git openssh openssl 5 | 6 | RUN apk add dos2unix --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/community/ --allow-untrusted 7 | 8 | RUN mkdir -p /usr/src/app 9 | WORKDIR /usr/src/app 10 | RUN npm install nodemon -g 11 | 12 | COPY ./package.json /usr/src/app/ 13 | RUN npm install 14 | 15 | COPY ./ /usr/src/app 16 | 17 | RUN dos2unix /usr/src/app/start-dev.sh 18 | 19 | EXPOSE 8080 20 | 21 | CMD [ "npm", "start" ] 22 | -------------------------------------------------------------------------------- /server/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | polymorph_server: 5 | command: /bin/bash ./start-dev.sh 6 | volumes: 7 | - ./:/usr/src/app 8 | ports: 9 | - 5555:5555 10 | -------------------------------------------------------------------------------- /server/docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | polymorph_server: 5 | build: . 6 | command: node src/server.js 7 | ports: 8 | - 8080:8080 9 | links: 10 | - mongo 11 | 12 | mongo: 13 | container_name: mongo 14 | image: mongo 15 | volumes: 16 | - ./data:/data 17 | ports: 18 | - "27017:27017" 19 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymorph-server", 3 | "version": "0.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "cors": { 8 | "version": "2.8.4", 9 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", 10 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", 11 | "requires": { 12 | "object-assign": "4.1.1", 13 | "vary": "1.1.2" 14 | } 15 | }, 16 | "object-assign": { 17 | "version": "4.1.1", 18 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 19 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 20 | }, 21 | "vary": { 22 | "version": "1.1.2", 23 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 24 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymorph-server", 3 | "version": "0.0.2", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "concurrently \"npm run watch:sass\" \"npm run build\" \"ng serve\"", 8 | "build": "ng build", 9 | "build-run": "ng build && node server.js", 10 | "test": "ng test", 11 | "test-coverage": "ng test --code-coverage --reporters=coverage-istanbul", 12 | "lint": "ng lint", 13 | "e2e": "ng e2e", 14 | "mocha": "./node_modules/.bin/mocha", 15 | "express": "node server.js", 16 | "express-coverage": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha", 17 | "express-test": "npm run mocha" 18 | }, 19 | "private": true, 20 | "dependencies": { 21 | "basic-auth": "^2.0.0", 22 | "bignumber.js": "^4.0.2", 23 | "bitcoin-core": "^1.2.0", 24 | "body-parser": "^1.17.1", 25 | "core-js": "^2.4.1", 26 | "cors": "^2.8.4", 27 | "express": "^4.15.2", 28 | "generate-key": "0.0.6", 29 | "hammerjs": "^2.0.8", 30 | "jayson": "^2.0.3", 31 | "jquery": "^2.2.4", 32 | "lodash": "^4.17.4", 33 | "mongoose": "^4.9.9", 34 | "nodemailer": "^2.7.2", 35 | "pem": "^1.12.3", 36 | "promise": "^8.0.0", 37 | "socket.io": "^2.0.3", 38 | "socket.io-client": "^2.0.3", 39 | "validator": "^9.0.0" 40 | }, 41 | "devDependencies": { 42 | "@types/node": "~6.0.60", 43 | "babel-eslint": "^7.2.3", 44 | "eslint": "^3.19.0", 45 | "eslint-config-airbnb": "^14.1.0", 46 | "eslint-plugin-import": "^2.2.0", 47 | "eslint-plugin-jsx-a11y": "^4.0.0", 48 | "eslint-plugin-react": "^6.9.0", 49 | "expect": "^1.20.2", 50 | "istanbul": "^0.4.5", 51 | "jasmine-core": "~2.5.2", 52 | "jasmine-spec-reporter": "~3.2.0", 53 | "karma": "~1.4.1", 54 | "karma-chrome-launcher": "~2.1.1", 55 | "karma-cli": "~1.0.1", 56 | "karma-coverage-istanbul-reporter": "^0.2.0", 57 | "karma-jasmine": "~1.1.0", 58 | "karma-jasmine-html-reporter": "^0.2.2", 59 | "mocha": "^3.3.0", 60 | "rewire": "^2.5.2", 61 | "sinon": "^2.2.0", 62 | "ts-node": "~2.0.0", 63 | "webpack": "^3.10.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server/src/api-options.json: -------------------------------------------------------------------------------- 1 | { 2 | "getOrderStatusOptions": { 3 | "orderId": [{ 4 | "validator": "isString" 5 | }], 6 | "orderPassword": [{ 7 | "validator": "isString" 8 | }], 9 | "ipAddress": [{ 10 | "validator": "isString", 11 | "shouldBeAlphanumeric": false 12 | }] 13 | }, 14 | 15 | "updateOrderStatusOptions": { 16 | "orderId": [{ 17 | "validator": "isString" 18 | }], 19 | "orderPassword": [{ 20 | "validator": "isString" 21 | }], 22 | "status": [{ 23 | "validator": "isString" 24 | }] 25 | }, 26 | 27 | "orderOptions": { 28 | "from": [{ 29 | "validator": "isString" 30 | }], 31 | "to": [{ 32 | "validator": "isString" 33 | }], 34 | "address": [{ 35 | "validator": "isString" 36 | }], 37 | "amount": [{ 38 | "validator": "isNumber" 39 | }] 40 | }, 41 | 42 | "getMinAmountOptions": { 43 | "from": [{ 44 | "validator": "isString" 45 | }], 46 | "to": [{ 47 | "validator": "isString" 48 | }] 49 | }, 50 | 51 | "getExchangeAmountOptions": { 52 | "from": [{ 53 | "validator": "isString" 54 | }], 55 | "to": [{ 56 | "validator": "isString" 57 | }], 58 | "amount": [{ 59 | "validator": "isNumber" 60 | }] 61 | }, 62 | 63 | "generateAddressOptions": { 64 | "from": [{ 65 | "validator": "isString" 66 | }], 67 | "to": [{ 68 | "validator": "isString" 69 | }], 70 | "address": [{ 71 | "validator": "isString" 72 | }], 73 | "extraId": [{ 74 | "validator": "other" 75 | }] 76 | }, 77 | 78 | "getEtaOptions": { 79 | "status": [{ 80 | "validator": "isString" 81 | }], 82 | "timeSent": [{ 83 | "validator": "isDate" 84 | }], 85 | "from": [{ 86 | "validator": "isString" 87 | }], 88 | "to": [{ 89 | "validator": "isString" 90 | }] 91 | }, 92 | 93 | "generateEstimateOptions": { 94 | "from": [{ 95 | "validator": "isString" 96 | }], 97 | "to": [{ 98 | "validator": "isString" 99 | }] 100 | }, 101 | 102 | "processHandler": { 103 | "timerLength": "isNumber" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /server/src/lib/changelly/changelly.ctrl.js: -------------------------------------------------------------------------------- 1 | const ConfigData = require('../../server-settings') 2 | const ApiOptions = require('../../api-options.json') 3 | 4 | const crypto = require('crypto') 5 | const jayson = require('jayson') 6 | const Logger = require('../logger') 7 | 8 | const URL = ConfigData.changellyUrl 9 | const client = jayson.client.https(URL) 10 | const Validator = require('../options-validator') 11 | 12 | const ChangellyCtrl = { internal: {} } 13 | 14 | ChangellyCtrl.id = () => { 15 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 16 | const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8) // eslint-disable-line 17 | return v.toString(16) 18 | }) 19 | } 20 | 21 | ChangellyCtrl.sign = (message) => { 22 | return crypto 23 | .createHmac('sha512', ConfigData.changellySecretKey) 24 | .update(JSON.stringify(message)) 25 | .digest('hex') 26 | } 27 | 28 | ChangellyCtrl.request = (method, options, callback) => { 29 | const id = ChangellyCtrl.id() 30 | const message = jayson.utils.request(method, options, id) 31 | client.options.headers = { 32 | 'api-key': ConfigData.changellyKey, 33 | sign: ChangellyCtrl.sign(message), 34 | } 35 | client.request(method, options, id, (err, response) => { 36 | callback(err, response) 37 | }) 38 | } 39 | 40 | ChangellyCtrl.getCurrencies = (req, res) => { 41 | ChangellyCtrl.request(ConfigData.changellyApiEndPoints.getCurrencies, {}, (err, data) => { 42 | if (err) { 43 | Logger.writeLog('CHNGLLY_001', 'Failed to getCurrencies', { error: err, data }, true) 44 | res.send(err) 45 | } else if (data.result && data.result.indexOf('nav') === -1) { 46 | Logger.writeLog('CHNGLLY_006', 'Nav not listed in currencies', { error: err, data }, true) 47 | res.status(500).send(new Error('Nav not listed in currencies')) 48 | } else { 49 | res.send(data) 50 | } 51 | }) 52 | } 53 | 54 | ChangellyCtrl.getMinAmount = (req, res) => { 55 | 56 | ChangellyCtrl.validateParams(req.params, ApiOptions.getMinAmountOptions) 57 | .then(() => { 58 | return ChangellyCtrl.request(ConfigData.changellyApiEndPoints.getMinAmount, req.params, (err, data) => { 59 | if (err) { 60 | Logger.writeLog('CHNGLLY_002', 'Failed to getMinAmount', err, true) 61 | res.send(err) 62 | } else { 63 | res.send(data) 64 | } 65 | }) 66 | }) 67 | .catch((error) => {res.send(error)}) 68 | } 69 | 70 | ChangellyCtrl.getExchangeAmount = (req, res) => { 71 | 72 | ChangellyCtrl.validateParams(req.params, ApiOptions.getExchangeAmountOptions) 73 | .then(() => { 74 | return ChangellyCtrl.request(ConfigData.changellyApiEndPoints.getExchangeAmount, req.params, (err, data) => { 75 | if (err) { 76 | Logger.writeLog('CHNGLLY_003', 'Failed to getExchangeAmount', err, true) 77 | res.send(err) 78 | } else { 79 | res.send(data) 80 | } 81 | }) 82 | }) 83 | .catch((error) => { res.send(error)}) 84 | } 85 | 86 | ChangellyCtrl.generateAddress = (req, res) => { 87 | ChangellyCtrl.validateParams(req.params, ApiOptions.generateAddressOptions) 88 | .then(() => { 89 | return ChangellyCtrl.request(ConfigData.changellyApiEndPoints.generateAddress, req.params, (err, data) => { 90 | if (err) { 91 | Logger.writeLog('CHNGLLY_004', 'Failed to generateAddress (external)', err, true) 92 | res.send(err) 93 | } else { 94 | res.send(data) 95 | } 96 | }) 97 | }) 98 | .catch((error) => { res.send(error)}) 99 | } 100 | 101 | ChangellyCtrl.internal.generateAddress = (params) => { 102 | return new Promise((fulfill, reject) => { 103 | ChangellyCtrl.validateParams(params, ApiOptions.generateAddressOptions) 104 | .then(() => { 105 | ChangellyCtrl.request(ConfigData.changellyApiEndPoints.generateAddress, params, (err, data) => { 106 | if (data.err) { 107 | Logger.writeLog('CHNGLLY_005', 'Failed to generateAddress (internal)', err, false) 108 | reject(new Error(data.err)) 109 | return 110 | } 111 | fulfill(data) 112 | }) 113 | }) 114 | .catch((error) => { reject(error)}) 115 | }) 116 | } 117 | 118 | ChangellyCtrl.validateParams = (params, options) => { 119 | return new Promise((fulfill, reject) => { 120 | Validator.startValidation(params, options) 121 | .then(() => { 122 | fulfill() 123 | }) 124 | .catch((error) => { 125 | Logger.writeLog('CHNGLLY_006', 'Param Validation Error', error, false) 126 | reject(error) 127 | }) 128 | }) 129 | } 130 | 131 | module.exports = ChangellyCtrl 132 | -------------------------------------------------------------------------------- /server/src/lib/db/blackList.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require('mongoose') 4 | 5 | // Define schema 6 | 7 | const Schema = mongoose.Schema 8 | 9 | const BlackList = new Schema({ 10 | ip_address: { type: String, required: true }, 11 | timestamp: { type: Date, required: true }, 12 | }) 13 | 14 | // Compile model from schema 15 | let BlackListModel = mongoose.model('BlackList', BlackList) //eslint-disable-line 16 | 17 | module.exports = BlackListModel 18 | -------------------------------------------------------------------------------- /server/src/lib/db/failedLogins.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require('mongoose') 4 | 5 | // Define schema 6 | 7 | const Schema = mongoose.Schema 8 | 9 | const failedLogin = new Schema({ 10 | ip_address: { type: String, required: true }, 11 | polymorph_id: { type: String, required: true }, 12 | timestamp: { type: Date, required: true }, 13 | params: { type: String, required: true }, 14 | }) 15 | 16 | // Compile model from schema 17 | let failedLoginModel = mongoose.model('failedLogin', failedLogin) //eslint-disable-line 18 | 19 | module.exports = failedLoginModel 20 | -------------------------------------------------------------------------------- /server/src/lib/db/login.ctrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const lodash = require('lodash') 4 | let Logger = require('../logger') // eslint-disable-line prefer-const 5 | 6 | // Compile models from schema 7 | let BlackListModel = require('./blackList.model') // eslint-disable-line prefer-const 8 | let FailedLoginsModel = require('./failedLogins.model') // eslint-disable-line prefer-const 9 | 10 | const LoginCtrl = { 11 | runtime: {}, 12 | } 13 | 14 | LoginCtrl.insertAttempt = (options) => { 15 | const required = ['ipAddress', 'polymorphId', 'params'] 16 | return new Promise((fulfill, reject) => { 17 | if (lodash.intersection(Object.keys(options), required).length !== required.length) { 18 | Logger.writeLog('LGN_001', 'invalid options', { options, required }) 19 | reject('LGN_001') 20 | return 21 | } 22 | LoginCtrl.runtime = {} 23 | LoginCtrl.runtime.transaction = new FailedLoginsModel( 24 | { 25 | ip_address: options.ipAddress, 26 | polymorph_id: options.polymorphId, 27 | timestamp: new Date(), 28 | params: JSON.stringify(options.params), 29 | }) 30 | LoginCtrl.executeSave(fulfill, reject) 31 | }) 32 | } 33 | 34 | LoginCtrl.blackListIp = (options) => { 35 | const required = ['ipAddress'] 36 | return new Promise((fulfill, reject) => { 37 | if (lodash.intersection(Object.keys(options), required).length !== required.length) { 38 | Logger.writeLog('LGN_004', 'invalid options', { options, required }) 39 | reject('LGN_004') 40 | return 41 | } 42 | LoginCtrl.runtime = { } 43 | LoginCtrl.runtime.transaction = new BlackListModel( 44 | { 45 | ip_address: options.ipAddress, 46 | timestamp: new Date(), 47 | }) 48 | LoginCtrl.executeSave(fulfill, reject) 49 | }) 50 | } 51 | 52 | LoginCtrl.executeSave = (fulfill, reject) => { 53 | try { 54 | LoginCtrl.runtime.transaction.save() 55 | .then((result) => { 56 | fulfill(result) 57 | }).catch((result) => { 58 | Logger.writeLog('LGN_002', 'Save Rejected', { result }) 59 | reject(result) 60 | }) 61 | } catch (error) { 62 | Logger.writeLog('LGN_003', 'Save Exception', { error }) 63 | reject(error) 64 | } 65 | } 66 | 67 | LoginCtrl.checkIpBlocked = (options) => { 68 | const required = ['ipAddress'] 69 | return new Promise((fulfill, reject) => { 70 | if (lodash.intersection(Object.keys(options), required).length !== required.length) { 71 | Logger.writeLog('LGN_005', 'invalid options', { options, required }) 72 | reject('LGN_005') 73 | return 74 | } 75 | const query = BlackListModel 76 | query.find() 77 | .and([ 78 | { ip_address: options.ipAddress }, 79 | { timestamp: { $gte: new Date(new Date().getTime() - (10 * 60000)) } }, 80 | ]) 81 | .select('ip_address timestamp') 82 | .exec() 83 | .then((result) => { 84 | const minLength = 0 85 | LoginCtrl.checkResults(result, minLength, fulfill) 86 | }) 87 | .catch((error) => { reject(error) }) 88 | }) 89 | } 90 | 91 | LoginCtrl.checkIfSuspicious = (ipAddress) => { 92 | return new Promise((fulfill, reject) => { 93 | const query = FailedLoginsModel 94 | query.find() 95 | .and([ 96 | { ip_address: ipAddress }, 97 | { timestamp: { $gte: new Date(new Date().getTime() - (10 * 60000)) } }, 98 | ]) 99 | .select('ip_address timestamp') 100 | .exec() 101 | .then((result) => { 102 | const minLength = 10 103 | LoginCtrl.checkResults(result, minLength, fulfill) 104 | }) 105 | .catch((error) => { reject(error) }) 106 | }) 107 | } 108 | 109 | LoginCtrl.checkResults = (result, minLength, fulfill) => { 110 | if (result.length > minLength) { 111 | fulfill(true) 112 | return 113 | } 114 | fulfill(false) 115 | } 116 | 117 | module.exports = LoginCtrl 118 | -------------------------------------------------------------------------------- /server/src/lib/db/serverMessage.md: -------------------------------------------------------------------------------- 1 | # ServerMessage How To 2 | 3 | #### Format 4 | ``` 5 | { 6 | "_id" : ObjectId("59c1bba8149c8d719abbc4a1"), 7 | "server_message" : "DEFCON 1", 8 | "message_type" : "ERROR", 9 | "show_message" : true 10 | } 11 | ``` 12 | 13 | #### Valid Inputs 14 | 15 | | Field | Valid Values | Type | Required | 16 | |:-----------|:-----------|:-----------|:-----------| 17 | | server_message | `any string` | `String` | true | 18 | | message_type | `ERROR`, `WARN`, `INFO` | `String` | true | 19 | | show_message | `true`, `false` | `Boolean` | true | 20 | -------------------------------------------------------------------------------- /server/src/lib/db/serverMessage.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require('mongoose') 4 | 5 | // Define schema 6 | 7 | const Schema = mongoose.Schema 8 | 9 | const serverMessage = new Schema({ 10 | server_message: { type: String, required: true }, 11 | message_type: {type: String, required: true }, 12 | show_message: {type: Boolean, required: true }, 13 | }) 14 | 15 | // Compile model from schema 16 | let ServerMessageModel = mongoose.model('serverMessage', serverMessage) //eslint-disable-line 17 | 18 | module.exports = ServerMessageModel 19 | -------------------------------------------------------------------------------- /server/src/lib/db/serverMode.ctrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const lodash = require('lodash') 4 | 5 | // Compile model from schema 6 | let ServerModeModel = require('./serverMode.model') 7 | let ServerMessageModel = require('./serverMessage.model') 8 | 9 | const ServerModeCtrl = {} 10 | 11 | ServerModeCtrl.checkMode = () => { 12 | return new Promise((fulfill, reject) => { 13 | ServerModeModel.find({}) 14 | .select('server_mode') 15 | .exec() 16 | .then((modeStatus) => { fulfill(modeStatus) }) 17 | .catch((error) => { reject(error) }) 18 | }) 19 | } 20 | 21 | ServerModeCtrl.checkMessage = () => { 22 | return new Promise((fulfill, reject) => { 23 | ServerMessageModel.find({}) 24 | .exec() 25 | .then((serverMessage) => { fulfill(serverMessage) }) 26 | .catch((error) => { reject(error) }) 27 | }) 28 | } 29 | 30 | module.exports = ServerModeCtrl 31 | -------------------------------------------------------------------------------- /server/src/lib/db/serverMode.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require('mongoose') 4 | 5 | // Define schema 6 | 7 | const Schema = mongoose.Schema 8 | 9 | const serverMode = new Schema({ 10 | server_mode: { type: String, required: true } 11 | }) 12 | 13 | // Compile model from schema 14 | let ServerModeModel = mongoose.model('serverMode', serverMode) //eslint-disable-line 15 | 16 | module.exports = ServerModeModel 17 | -------------------------------------------------------------------------------- /server/src/lib/db/transaction.ctrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const lodash = require('lodash') 4 | let ErrorHandler = require('../error-handler') // eslint-disable-line prefer-const 5 | 6 | // Compile model from schema 7 | let TransactionModel = require('./transaction.model') 8 | 9 | const TransactionCtrl = { } 10 | 11 | TransactionCtrl.createTransaction = (req, res) => { 12 | return new Promise((fulfill, reject) => { 13 | const required = ['from', 'to', 'address', 'amount', 'polymorphId', 14 | 'polymorphPass', 'changellyAddressOne', 'changellyAddressTwo', 'navAddress'] 15 | if (!req || lodash.intersection(Object.keys(req.params), required).length !== required.length) { 16 | reject(new Error('PARAMS_ERROR', 'TRANS_CTRL_001', 'Failed to receive params')) 17 | return 18 | } 19 | 20 | TransactionCtrl.runtime = { req, res } 21 | 22 | TransactionCtrl.runtime.transaction = new TransactionModel({ 23 | changelly_id: req.params.changellyId || '123123123', 24 | polymorph_id: req.params.polymorphId, 25 | polymorph_pass: req.params.polymorphPass, 26 | changelly_address_one: req.params.changellyAddressOne, 27 | changelly_address_two: req.params.changellyAddressTwo, 28 | order_amount: req.params.amount, 29 | nav_address: req.params.navAddress, 30 | input_currency: req.params.from, 31 | output_currency: req.params.to, 32 | output_address: req.params.address, 33 | order_status: 'CREATED', 34 | delay: req.params.delay || 0, 35 | created: new Date(), 36 | sent: null, 37 | }) 38 | TransactionCtrl.runtime.transaction.save() 39 | .then(() => fulfill()) 40 | .catch(err => reject(err)) 41 | }) 42 | } 43 | 44 | TransactionCtrl.getOrder = (id, pass) => { 45 | return new Promise((fulfill, reject) => { 46 | const query = TransactionModel.find() 47 | if (!id || !pass) { 48 | reject(new Error('Id or Password missing. Id: ' + id + '. Pass: ' + pass)) 49 | return 50 | } 51 | query.and([{ polymorph_id: id }, { polymorph_pass: pass }]) 52 | query.select('-_id polymorph_id polymorph_pass changelly_address_one changelly_id ' + 53 | 'order_amount input_currency output_currency order_status') 54 | query.exec() 55 | .then((order) => { fulfill(order) }) 56 | .catch((error) => { reject(error) }) 57 | }) 58 | } 59 | 60 | TransactionCtrl.updateOrderStatus = (id, pass, newStatus) => { 61 | return new Promise((fulfill, reject) => { 62 | if (!id || !pass || !newStatus) { 63 | reject(new Error('Id, Password or Status missing. Id: ' + id + '. Pass: ' + pass + '. Status: ' + newStatus)) 64 | return 65 | } 66 | const query = { polymorph_id: id, polymorph_pass: pass } 67 | TransactionModel.findOneAndUpdate(query, { order_status: newStatus }) 68 | .then(() => { 69 | fulfill() 70 | }) 71 | .catch((error) => { reject(error) }) 72 | }) 73 | } 74 | 75 | TransactionCtrl.getTransaction = (req, res) => { 76 | TransactionCtrl.runtime = { res, req } 77 | const query = TransactionModel.find() 78 | if (req.params && req.params.id) { 79 | query.where('_id').equals(req.params.id) 80 | } 81 | query.exec(TransactionCtrl.gotTransaction) 82 | } 83 | 84 | TransactionCtrl.gotTransaction = (err, transactions) => { 85 | if (err) { 86 | ErrorHandler.handleError({ 87 | statusMessage: 'Failed to get transaction', 88 | err, 89 | code: 'TRANS_CTRL_003', 90 | sendEmail: false, 91 | res: TransactionCtrl.runtime.res 92 | }) 93 | return 94 | } 95 | TransactionCtrl.runtime.res.send(JSON.stringify({ 96 | status: 200, 97 | type: 'SUCCESS', 98 | data: transactions, 99 | })) 100 | } 101 | 102 | TransactionCtrl.checkIfIdExists = (polymorphId) => { 103 | return new Promise((fulfill, reject) => { 104 | const query = TransactionModel.find() 105 | try { 106 | if (polymorphId) { 107 | query.where('polymorph_id').equals(polymorphId) 108 | } else { 109 | reject(new Error('Incorrect Params - No Polymorph ID')) 110 | return 111 | } 112 | query.exec() 113 | .then((result) => { 114 | if (result.length !== 0) { 115 | fulfill(true) 116 | return 117 | } 118 | fulfill(false) 119 | }) 120 | .catch((error) => { reject(error) }) 121 | } catch (exception) { 122 | reject(exception) 123 | } 124 | }) 125 | } 126 | 127 | module.exports = TransactionCtrl 128 | -------------------------------------------------------------------------------- /server/src/lib/db/transaction.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require('mongoose') 4 | 5 | // Define schema 6 | 7 | const Schema = mongoose.Schema 8 | 9 | const Transaction = new Schema({ 10 | changelly_id: { type: String, required: false }, 11 | polymorph_id: { type: String, required: true }, 12 | polymorph_pass: { type: String, required: true }, 13 | changelly_address_one: { type: String, required: true }, 14 | changelly_address_two: { type: String, required: true }, 15 | order_amount: { type: String, required: true }, 16 | nav_address: { type: String, required: true }, 17 | input_currency: { type: String, required: true }, 18 | output_currency: { type: String, required: true }, 19 | order_status: { type: String, required: true }, 20 | delay: { type: Number, min: 0, max: 604800 }, // 1 week max delay 21 | created: { type: Date, required: true }, 22 | sent: { type: Date, required: false }, 23 | }) 24 | 25 | // Compile model from schema 26 | let TransactionModel = mongoose.model('Transaction', Transaction) //eslint-disable-line 27 | 28 | module.exports = TransactionModel 29 | -------------------------------------------------------------------------------- /server/src/lib/error-handler.js: -------------------------------------------------------------------------------- 1 | let Logger = require('./logger') // eslint-disable-line prefer-const 2 | let Validator = require('./options-validator') // eslint-disable-line prefer-const 3 | const ApiOptions = require('../api-options.json') 4 | 5 | const ErrorHandler = {} 6 | 7 | ErrorHandler.handleError = (params) => { 8 | Validator.startValidation(params, ApiOptions) 9 | .then(() => { 10 | params.res.send(JSON.stringify({ 11 | statusCode: 200, 12 | type: 'FAIL', 13 | statusMessage: params.statusMessage, 14 | err: params.err, 15 | })) 16 | Logger.writeLog(params.code, params.statusMessage, { error: params.err }, params.sendEmail) 17 | }) 18 | .catch((errorArr) => { 19 | Logger.writeLog('ERR_HDL_001', 'Incorrect Params - couldn\'t handle error', { error: errorArr, originalError: params }, true) 20 | }) 21 | } 22 | 23 | module.exports = ErrorHandler 24 | -------------------------------------------------------------------------------- /server/src/lib/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const nodemailer = require('nodemailer') 4 | const config = require('../server-settings') 5 | 6 | const settings = config.mailSettings 7 | 8 | const emailAuth = encodeURIComponent(settings.smtp.user) + ':' + encodeURIComponent(settings.smtp.pass) 9 | 10 | const Logger = {} 11 | 12 | // Logger.transporter = nodemailer.createTransport('smtps://' + emailAuth + '@' + settings.smtp.server) 13 | 14 | 15 | Logger.writeLog = (errorCode, errorMessage, data, email) => { 16 | if (email) { 17 | // Logger.sendEmail(errorCode, errorMessage, data) 18 | } 19 | const date = new Date() 20 | let logString = '\r\n' 21 | logString += 'Date: ' + date + '\r\n' 22 | logString += 'Error Code: ' + errorCode + '\r\n' 23 | logString += 'Error Message: ' + errorMessage + '\r\n' 24 | 25 | for (const key in data) { 26 | if (data.hasOwnProperty(key)) { 27 | let string = data[key] 28 | if (typeof data[key] === 'object') string = JSON.stringify(data[key]) 29 | logString += key + ': ' + string + '\r\n' 30 | } 31 | } 32 | logString += '\r\n-----------------------------------------------------------\r\n' 33 | console.log(logString) 34 | } 35 | 36 | Logger.sendEmail = (errorCode, errorMessage, data) => { 37 | const mailOptions = { 38 | from: '"Polymorph System" <' + settings.smtp.user + '>', 39 | to: settings.notificationEmail, 40 | subject: 'Polymorph System Message - ' + errorCode, 41 | text: errorCode + ' - ' + errorMessage, 42 | } 43 | if (data) { 44 | mailOptions.attachments = [ 45 | { 46 | filename: 'data.json', 47 | content: JSON.stringify(data), 48 | }, 49 | ] 50 | } 51 | 52 | Logger.transporter.sendMail(mailOptions, (error, info) => { 53 | if (error) { 54 | return console.log('nodemail error', error) 55 | } 56 | return console.log('nodemail success: ' + info.response) 57 | }) 58 | } 59 | 60 | module.exports = Logger 61 | -------------------------------------------------------------------------------- /server/src/lib/options-validator.js: -------------------------------------------------------------------------------- 1 | const Validator = require('validator') 2 | const lodash = require('lodash') 3 | 4 | const OptionsValidator = { } 5 | 6 | OptionsValidator.startValidation = (params, options) => { 7 | const errors = [] 8 | return new Promise((fulfill, reject) => { 9 | if (lodash.intersection(Object.keys(params), Object.keys(options)).length !== Object.keys(options).length) { 10 | reject(new Error('PARAMS_ERROR', 'Failed to receive params', params, options)) 11 | return 12 | } 13 | try { 14 | for (const key of Object.keys(params)) { 15 | OptionsValidator.validateParams(params[key], options[key], errors) 16 | } 17 | } catch (exception) { 18 | errors.push(exception) 19 | } 20 | 21 | if (errors.length > 0) { 22 | reject(errors) 23 | return 24 | } 25 | fulfill() 26 | }) 27 | } 28 | 29 | OptionsValidator.validateParams = (param, validators, errors) => { 30 | validators.forEach((validator) => { 31 | switch (validator.validator) { 32 | case 'isString': 33 | OptionsValidator.isString(param, validator, errors) 34 | break 35 | case 'isNumber': 36 | OptionsValidator.isNumber(param, validator, errors) 37 | break 38 | default: 39 | break 40 | } 41 | }) 42 | } 43 | 44 | OptionsValidator.isString = (param, validator, errors) => { 45 | if (!(typeof param === 'string')) { 46 | errors.push({ err: 'STR_NON_STRING', param, validator }) 47 | return 48 | } 49 | 50 | if (validator.shouldBeAlphanumeric && !Validator.isAlphanumeric(param)) { 51 | errors.push({ err: 'STR_NON_ALPHANUM', param, validator }) 52 | } 53 | 54 | if (validator.maxLength && validator.maxLength < param.length) { 55 | errors.push({ err: 'STR_MAX_LENGTH', param, validator }) 56 | } 57 | if (validator.minLength && validator.minLength > param.length) { 58 | errors.push({ err: 'STR_MIN_LENGTH', param, validator }) 59 | } 60 | } 61 | 62 | OptionsValidator.isNumber = (param, validator, errors) => { 63 | if (!Validator.isNumeric(param) && !Validator.isDecimal(param)) { 64 | errors.push({ err: 'NUM_NON_NUMBER', param, validator }) 65 | } 66 | } 67 | 68 | module.exports = OptionsValidator 69 | -------------------------------------------------------------------------------- /server/src/lib/order/eta.ctrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Config = require('../../server-settings') 4 | let ApiOptions = require('../../api-options.json') // eslint-disable-line prefer-const 5 | 6 | let validStatuses = Config.validOrderStatuses // eslint-disable-line prefer-const 7 | let timeConsts = Config.timeConsts // eslint-disable-line prefer-const 8 | let Validator = require('../options-validator') // eslint-disable-line prefer-const 9 | let ErrorHandler = require('../error-handler') // eslint-disable-line prefer-const 10 | 11 | const EtaCtrl = {} 12 | 13 | 14 | EtaCtrl.generateEstimate = (req, res) => { 15 | Validator.startValidation(req.params, ApiOptions.generateEstimateOptions) 16 | .then(() => { 17 | return EtaCtrl.getEta({ status: 'ESTIMATE', timeSent: new Date(), from: req.params.from, to: req.params.to }) 18 | }) 19 | .then((eta) => { 20 | res.send(eta) 21 | }) 22 | .catch((error) => { 23 | ErrorHandler.handleError({ 24 | statusMessage: 'Unable to get ETA', 25 | err: error, 26 | code: 'ETA_CTRL_001', 27 | sendEmail: true, 28 | res 29 | }) 30 | }) 31 | } 32 | 33 | EtaCtrl.getEta = (params) => { 34 | return new Promise((fufill, reject) => { 35 | Validator.startValidation(params, ApiOptions.getEtaOptions) 36 | .then(() => { 37 | if (!EtaCtrl.validStatus(params.status)) { 38 | reject(new Error('INVALID_ORDER_STATUS')) 39 | return 40 | } else if (params.status === 'FINISHED' && !(params.timeSent instanceof Date)) { 41 | reject(new Error('INVALID_SENT_TIME')) 42 | return 43 | } 44 | fufill(EtaCtrl.buildEta(params)) 45 | }) 46 | .catch((error) => { 47 | reject({ error }) 48 | }) 49 | }) 50 | } 51 | 52 | EtaCtrl.validStatus = (status) => { 53 | if (validStatuses.indexOf(status) === -1) { 54 | return false 55 | } 56 | return true 57 | } 58 | 59 | EtaCtrl.buildEta = (options) => { 60 | let etaMin = 0 // These are in minutes 61 | let etaMax = 0 62 | 63 | switch (options.status) { 64 | case 'COMPLETED': 65 | case 'ABANDONED': 66 | case 'FAILED': 67 | case 'REFUNDED': 68 | case 'EXPIRED': 69 | break 70 | case 'CREATED': 71 | case 'ESTIMATE': 72 | etaMin = timeConsts.navTech[0] + (timeConsts.changelly[0] * 2) 73 | etaMax = timeConsts.navTech[1] + (timeConsts.changelly[1] * 2) 74 | if (options.originCoin === 'NAV' || options.destCoin === 'NAV') { 75 | etaMin -= timeConsts.changelly[0] 76 | etaMax -= timeConsts.changelly[1] 77 | } 78 | break 79 | case 'CONFIRMING': 80 | etaMin = timeConsts.navTech[0] + timeConsts.changelly[0] 81 | etaMax = timeConsts.navTech[1] + timeConsts.changelly[1] 82 | if (options.destCoin === 'NAV') { 83 | etaMin -= timeConsts.changelly[0] 84 | etaMax -= timeConsts.changelly[1] 85 | } 86 | break 87 | case 'EXCHANGING': 88 | case 'SENDING': 89 | etaMin = timeConsts.navTech[0] + timeConsts.changelly[0] 90 | etaMax = timeConsts.navTech[1] + timeConsts.changelly[1] 91 | if (options.destCoin === 'NAV') { 92 | etaMin -= timeConsts.changelly[0] 93 | etaMax -= timeConsts.changelly[1] 94 | } 95 | break 96 | case 'FINISHED': 97 | etaMin = timeConsts.navTech[0] + timeConsts.changelly[0] 98 | etaMax = timeConsts.navTech[1] + timeConsts.changelly[1] 99 | const modifiedMinMax = EtaCtrl.factorTimeSinceSending(etaMin, etaMax, options.timeSent) 100 | etaMin = modifiedMinMax[0] 101 | etaMax = modifiedMinMax[1] 102 | if (options.destCoin === 'NAV') { 103 | etaMin -= timeConsts.changelly[0] 104 | etaMax -= timeConsts.changelly[1] 105 | } 106 | break 107 | } 108 | return [etaMin, etaMax] 109 | } 110 | 111 | EtaCtrl.factorTimeSinceSending = (min, max, timeSent) => { 112 | const minutesPassed = Math.round((new Date() - timeSent) / 1000 / 60, 0) 113 | return [min - minutesPassed, max - minutesPassed] 114 | } 115 | 116 | module.exports = EtaCtrl 117 | -------------------------------------------------------------------------------- /server/src/lib/processHandler.js: -------------------------------------------------------------------------------- 1 | const Config = require('../server-settings') 2 | let Logger = require('./logger') // eslint-disable-line prefer-const 3 | const RpcGetNewAddress = require('./rpc/get-new-address') 4 | 5 | const ProcessHandler = { 6 | tasksRunning: false, 7 | timerPaused: false, 8 | } 9 | 10 | ProcessHandler.setup = () => { 11 | return new Promise((fulfill, reject) => { 12 | ProcessHandler.testRpc() 13 | .then(() => { 14 | ProcessHandler.startTimer() 15 | fulfill() 16 | }) 17 | .catch((err) => { 18 | ProcessHandler.setTimerPaused(true) 19 | Logger.writeLog('PH_001', 'Failed to pass RPC pretimer check', err, true) 20 | reject() 21 | }) 22 | }) 23 | } 24 | 25 | ProcessHandler.testRpc = () => { // TODO: Complete this function 26 | return new Promise((fulfill, reject) => { 27 | fulfill() 28 | }) 29 | } 30 | 31 | ProcessHandler.startTimer = () => { 32 | global.setInterval(ProcessHandler.runTasks, Config.processHandler.timerLength) 33 | } 34 | 35 | ProcessHandler.runTasks = () => { // TODO: Complete this function 36 | ProcessHandler.preflightChecks() 37 | .then(() => { 38 | 39 | }) 40 | .catch((err) => { 41 | ProcessHandler.setTimerPaused(true) 42 | Logger.writeLog('PH_002', 'Failed to pass preflightChecks', err, true) 43 | }) 44 | } 45 | 46 | ProcessHandler.preflightChecks = () => { // TODO: Complete this function 47 | return new Promise((fulfill, reject) => { 48 | if (!ProcessHandler.timerPaused && !ProcessHandler.tasksRunning) { 49 | fulfill() 50 | } else { 51 | reject(new Error('Preflight checks failed')) 52 | } 53 | }) 54 | } 55 | 56 | ProcessHandler.setTimerPaused = (newStatus) => { 57 | ProcessHandler.timerPaused = newStatus 58 | } 59 | 60 | module.exports = ProcessHandler 61 | -------------------------------------------------------------------------------- /server/src/lib/rpc/client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Client = require('bitcoin-core') 4 | const configData = require('../../server-settings') 5 | 6 | 7 | let Rpc = { //eslint-disable-line 8 | navClient: new Client(configData.navClient), 9 | internal: {} 10 | } 11 | 12 | module.exports = Rpc 13 | -------------------------------------------------------------------------------- /server/src/lib/rpc/get-info.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Rpc = require('./client') 4 | let Logger = require('../logger') 5 | 6 | Rpc.getInfo = () => { 7 | return new Promise((fulfill, reject) => { 8 | Rpc.navClient.getInfo() 9 | .then(address => fulfill(address)) 10 | .catch((err) => { 11 | reject(err) 12 | }) 13 | }) 14 | } 15 | 16 | module.exports = Rpc 17 | -------------------------------------------------------------------------------- /server/src/lib/rpc/get-new-address.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Rpc = require('./client') 4 | const config = require('../../server-settings') 5 | 6 | Rpc.getNewAddress = () => { 7 | return new Promise((fulfill, reject) => { 8 | try { 9 | const address = Rpc.navClient.getNewAddress() 10 | fulfill(address) 11 | } catch (firstGetAddressError) { 12 | if (firstGetAddressError.code === -12) { 13 | Rpc.keypoolRefill() 14 | .then(() => { 15 | try { 16 | const address = Rpc.navClient.getNewAddress() 17 | fulfill(address) 18 | } catch (secondGetAddressError) { 19 | reject(secondGetAddressError) 20 | } 21 | }) 22 | .catch((keypoolError) => { 23 | reject(keypoolError) 24 | }) 25 | } else { 26 | reject(firstGetAddressError) 27 | } 28 | } 29 | }) 30 | } 31 | 32 | Rpc.keypoolRefill = () => { 33 | return new Promise((fulfill, reject) => { 34 | try { 35 | Rpc.navClient.walletPassphrase(config.walletKey, 5) 36 | Rpc.navClient.keypoolRefill() 37 | Rpc.navClient.walletLock() 38 | fulfill() 39 | } catch (error) { 40 | Rpc.navClient.walletLock() 41 | reject(error) 42 | } 43 | }) 44 | } 45 | 46 | module.exports = Rpc 47 | -------------------------------------------------------------------------------- /server/src/lib/settingsValidator.js: -------------------------------------------------------------------------------- 1 | const serverSettingsTemplate = require('../server-settings-template') 2 | 3 | const NUMBER_TWO_DECIMALS = /^(\d+)?([.]?\d{0,2})?$/ 4 | 5 | const SettingsValidator = { 6 | errors: [], 7 | } 8 | 9 | SettingsValidator.validateSettings = (serverSettings) => { 10 | return new Promise((resolve, reject) => { 11 | SettingsValidator.errors = [] 12 | validate(serverSettings, serverSettingsTemplate) 13 | if (SettingsValidator.errors.length < 1) { 14 | resolve() 15 | } else { 16 | reject('Error: invalid server config - ' + SettingsValidator.errors) 17 | } 18 | }) 19 | } 20 | 21 | function validate(value, validation, currentKey) { 22 | if (typeof value === 'object' && !(value instanceof Array)) { 23 | for (const key in validation) { 24 | if (validation.hasOwnProperty(key)) { 25 | validate(value[key], validation[key], key) 26 | } 27 | } 28 | } else { 29 | eachField(value, validation, currentKey) 30 | } 31 | } 32 | 33 | function eachField(value, validation, currentKey) { 34 | // console.log(value, validation, currentKey) 35 | if (validation === true && !value) { 36 | SettingsValidator.errors.push('VALUE_IS_REQUIRED for ' + currentKey, value) 37 | return 38 | } 39 | if (validation === false && (!value || value === undefined)) { 40 | return 41 | } 42 | 43 | if (!value || value === undefined) { 44 | SettingsValidator.errors.push('MISSING_VALUE for ' + currentKey + ', must provide a valid value') 45 | return 46 | } 47 | 48 | switch (validation) { 49 | case 'PORT': 50 | validatePort(value, validation, currentKey) 51 | break 52 | case 'NUMBER': 53 | validateNumber(value, validation, currentKey) 54 | break 55 | case 'STRING': 56 | validateString(value, validation, currentKey) 57 | break 58 | case 'ARRAY': 59 | validateArray(value, validation, currentKey) 60 | break 61 | case 'BOOLEAN': 62 | validateBoolean(value, validation, currentKey) 63 | break 64 | default: 65 | SettingsValidator.errors.push('NO_VALIDATION_SET for ' + currentKey) 66 | } 67 | } 68 | 69 | function validatePort(value, validation, key) { 70 | const integer = parseInt(value, 10) 71 | if (integer < 1 || integer > 65535) { 72 | SettingsValidator.errors.push('PORT_OUT_OF_RANGE for ' + key + ', must be between 1 and 65535 ') 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | function validateNumber(value, validation, key) { 79 | 80 | if (typeof value === 'number') { 81 | return true 82 | } 83 | SettingsValidator.errors.push('INCORRECT_TYPE for ' + key + ', must be a Number. ') 84 | return false 85 | } 86 | 87 | function validateString(value, validation, key) { 88 | if (typeof value !== 'string') { 89 | SettingsValidator.errors.push( 90 | 'INCORRECT_TYPE for ' + key + ', must be a String' 91 | ) 92 | return false 93 | } 94 | return true 95 | } 96 | 97 | function validateBoolean(value, validation, key) { 98 | if (typeof value !== 'boolean') { 99 | SettingsValidator.errors.push( 100 | 'INCORRECT_TYPE for ' + key + ', must be a Boolean' 101 | ) 102 | return false 103 | } 104 | return true 105 | } 106 | 107 | function validateArray(value, validation, key) { 108 | if (typeof value !== 'object') { 109 | SettingsValidator.errors.push( 110 | 'INCORRECT_TYPE for ' + key + ', must be an object (array)' 111 | ) 112 | return false 113 | } 114 | return true 115 | } 116 | 117 | module.exports = SettingsValidator 118 | -------------------------------------------------------------------------------- /server/src/lib/socket/socketCtrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const serverModeCtrl = require('../db/serverMode.ctrl') 4 | const Logger = require('../logger') 5 | 6 | 7 | const SocketCtrl = {} 8 | 9 | SocketCtrl.setupServerSocket = (socket) => { 10 | return new Promise((fufill, reject) => { 11 | try { 12 | socket.on('connection', function(socket){ 13 | console.log('a user connected') 14 | socket.on('disconnect', function(){ 15 | console.log('USER DISCONNECTED') 16 | }) 17 | socket.on('ADD_MESSAGE', (message) => { 18 | socket.emit('MESSAGE', { type: 'NEW_MESSAGE', text: message }) 19 | }) 20 | }) 21 | SocketCtrl.startDbWatch(socket) 22 | fufill() 23 | } catch (err) { 24 | reject(err) 25 | } 26 | }) 27 | } 28 | 29 | SocketCtrl.startDbWatch = (socket) => { 30 | let previousMode 31 | let previousMessage 32 | setInterval(() => { 33 | serverModeCtrl.checkMode() 34 | .then((currServerMode) => { 35 | if (currServerMode.length === 1 && previousMode !== currServerMode) { 36 | previousMode = currServerMode 37 | socket.emit('SERVER_MODE', currServerMode[0].server_mode) 38 | } 39 | }) 40 | .then(() => { 41 | return serverModeCtrl.checkMessage() 42 | }) 43 | .then((currServerMessageData) => { 44 | if (currServerMessageData.length === 1 && previousMessage !== currServerMessageData) { 45 | previousMessage = currServerMessageData 46 | socket.emit('SERVER_MESSAGE', { 47 | serverMessage: currServerMessageData[0].server_message, 48 | serverMessageType: currServerMessageData[0].message_type, 49 | showMessage: currServerMessageData[0].show_message, 50 | }) 51 | } 52 | }) 53 | .catch((err) => { 54 | Logger.writeLog('SKT_001', 'Something went wrong with the socket(s)', { error: err }, false) 55 | }) 56 | }, 1000) 57 | } 58 | 59 | module.exports = SocketCtrl 60 | -------------------------------------------------------------------------------- /server/src/routes/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const express = require('express') 4 | 5 | const router = express.Router() 6 | const TransactionCtrl = require('../lib/db/transaction.ctrl') 7 | const ChangellyCtrl = require('../lib/changelly/changelly.ctrl') 8 | const OrderCtrl = require('../lib/order/order.ctrl') 9 | const EtaCtrl = require('../lib/order/eta.ctrl') 10 | const OrderStatusCtrl = require('../lib/order/status.ctrl') 11 | 12 | router.get('/', (req, res) => res.send('api works')) 13 | router.get('/db/transaction/:id', TransactionCtrl.getTransaction) 14 | router.get('/db/transaction', TransactionCtrl.getTransaction) 15 | 16 | router.get('/changelly/getCurrencies', ChangellyCtrl.getCurrencies) 17 | router.get('/changelly/getMinAmount/:from/:to', ChangellyCtrl.getMinAmount) 18 | router.get('/changelly/getExchangeAmount/:from/:to/:amount', ChangellyCtrl.getExchangeAmount) 19 | 20 | router.get('/changelly/generateAddress/:from/:to/:address', ChangellyCtrl.generateAddress) 21 | 22 | router.get('/changelly/getEta/:from/:to/', EtaCtrl.generateEstimate) 23 | 24 | router.get('/order/createOrder/:from/:to/:address/:amount', OrderCtrl.createOrder) 25 | router.get('/order/getOrder/:orderId/:orderPassword', OrderStatusCtrl.getOrder) 26 | router.get('/order/updateOrderStatus/:orderId/:orderPassword/:status', OrderStatusCtrl.updateOrderStatus) 27 | router.get('/order/abandonOrder/:orderId/:orderPassword', OrderStatusCtrl.abandonOrder) 28 | 29 | router.all('/*', (req, res) => { 30 | res.status(404).json({ error: ' - API Endpoint ' + req.url + ' does not exist' }) 31 | }) 32 | 33 | module.exports = router 34 | -------------------------------------------------------------------------------- /server/src/sample-server-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "navClient": { 4 | "username": "user", 5 | "password": "password", 6 | "port": 44444, 7 | "host": "127.0.0.1" 8 | }, 9 | "changellyApiEndPoints": { 10 | "getCurrencies": "getCurrencies", 11 | "getMinExchange": "getMinAmount", 12 | "getEstimatedExchange": "getExchangeAmount", 13 | "getGenerateAddress": "generateAddress", 14 | "getTransaction": "getTransactions", 15 | "getExchangeStatus": "getStatus" 16 | }, 17 | "changellyUrl": "https://api.changelly.com", 18 | "changellyKey": "32 char long", 19 | "changellySecretKey": "64 char long", 20 | "walletKey": "", 21 | "mailSettings": { 22 | "smtp": { 23 | "user": " @ .com", 24 | "pass": " ", 25 | "server": "smtp.gmail.com" 26 | }, 27 | "clientId": " ", 28 | "clientSecret": " ", 29 | "refreshToken": " ", 30 | "notificationEmail": " @ .com", 31 | "authCode": " " 32 | }, 33 | "validOrderStatuses": [ 34 | "created", 35 | "abandoned", 36 | "completed" 37 | ] 38 | } -------------------------------------------------------------------------------- /server/src/server-settings-template.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "app": { 3 | "static": "STRING", 4 | "apiUri": "STRING", 5 | "catchAllUri": "STRING" 6 | }, 7 | 8 | "serverPort": "PORT", 9 | 10 | "sslCert": { 11 | "days": "NUMBER", 12 | "selfSigned": "BOOLEAN" 13 | }, 14 | 15 | "mongoDBUrl": "STRING", 16 | 17 | "version": "STRING", 18 | 19 | "navClient": { 20 | "username": "STRING", 21 | "password": "STRING", 22 | "port": "PORT", 23 | "host": "STRING" 24 | }, 25 | "changellyApiEndPoints": { 26 | "getCurrencies": "STRING", 27 | "getMinAmount": "STRING", 28 | "getExchangeAmount": "STRING", 29 | "generateAddress": "STRING", 30 | "getTransaction": "STRING", 31 | "getExchangeStatus": "STRING" 32 | }, 33 | "changellyUrl": "STRING", 34 | "changellyKey": "STRING", 35 | "changellySecretKey": "STRING", 36 | "mailSettings": { 37 | "smtp": { 38 | "user": "STRING", 39 | "pass": "STRING", 40 | "server": "STRING" 41 | }, 42 | "clientId": "STRING", 43 | "clientSecret": "STRING", 44 | "refreshToken": "STRING", 45 | "notificationEmail": "STRING", 46 | "authCode": "STRING" 47 | }, 48 | "validOrderStatuses": "ARRAY", 49 | 50 | "timeConsts": { 51 | "changelly": "ARRAY", 52 | "navTech": "ARRAY" 53 | }, 54 | "basicAuth": { 55 | "name": "STRING", 56 | "pass": "STRING" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/src/server-settings.js: -------------------------------------------------------------------------------- 1 | const secrets = require('./secrets') 2 | 3 | module.exports = { 4 | "app": { 5 | "static": "dist", 6 | "apiUri": "/api", 7 | "catchAllUri": "dist/index.html" 8 | }, 9 | 10 | "serverPort": "8080", 11 | 12 | "sslCert": { 13 | "days": 1, 14 | "selfSigned": true 15 | }, 16 | 17 | // Update to "mongodb://mongo/polymorph" for docker environment 18 | "mongoDBUrl": "mongodb://127.0.0.1/polymorph", 19 | 20 | "version": "0.0.2", 21 | "navClient": { 22 | "username": secrets.navClient.username, 23 | "password": secrets.navClient.password, 24 | "port": 44444, 25 | "host": "127.0.0.1", 26 | }, 27 | "changellyApiEndPoints": { 28 | "getCurrencies": "getCurrencies", 29 | "getMinAmount": "getMinAmount", 30 | "getExchangeAmount": "getExchangeAmount", 31 | "generateAddress": "generateAddress", 32 | "getTransaction": "getTransactions", 33 | "getExchangeStatus": "getStatus", 34 | }, 35 | "changellyUrl": "https://api.changelly.com", 36 | "changellyKey": secrets.changelly.changellyKey, 37 | "changellySecretKey": secrets.changelly.changellySecretKey, 38 | "mailSettings": { 39 | "smtp": { 40 | "user": secrets.mailSettings.user, 41 | "pass": secrets.mailSettings.pass, 42 | "server": "smtp.gmail.com", 43 | }, 44 | "clientId": secrets.clientId, 45 | "clientSecret": secrets.clientSecret, 46 | "refreshToken": secrets.refreshToken, 47 | "notificationEmail": secrets.notificationEmail, 48 | "authCode": secrets.authCode, 49 | }, 50 | "validOrderStatuses": ["ESTIMATE","COMPLETED","ABANDONED","EXPIRED","CREATED","CONFIRMING","EXCHANGING","FINISHED","FAILED"], 51 | 52 | "timeConsts": { 53 | "changelly": [5, 30], 54 | "navTech": [5, 10], 55 | }, 56 | "basicAuth": { 57 | "name": secrets.basicAuth.name, 58 | "pass": secrets.basicAuth.pass, 59 | }, 60 | 61 | "processHandler": { 62 | "timerLength": 120000, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/src/server.js: -------------------------------------------------------------------------------- 1 | // Get dependencies 2 | const express = require('express') 3 | const path = require('path') 4 | const https = require('https') 5 | const http = require('http') 6 | const bodyParser = require('body-parser') 7 | const cors = require('cors') 8 | const pem = require('pem') 9 | const mongoose = require('mongoose') 10 | const SocketCtrl = require('./lib/socket/socketCtrl') 11 | const auth = require('basic-auth') 12 | const ConfigData = require('./server-settings') 13 | const SettingsValidator = require('./lib/settingsValidator.js') 14 | const ProcessHandler = require('./lib/processHandler') 15 | 16 | // Get our API routes 17 | const api = require('./routes/api') 18 | const Logger = require('./lib/logger') 19 | 20 | // Get Config data 21 | const config = require('./server-settings') 22 | 23 | const app = express() 24 | /** 25 | * Validate Server Settings Config 26 | */ 27 | 28 | 29 | SettingsValidator.validateSettings(config) 30 | .then(() => { 31 | console.log('--------------------------------------------') 32 | console.log('Server Config Validated. Continuing start up') 33 | console.log('--------------------------------------------') 34 | 35 | app.startUpServer() 36 | }) 37 | .catch((err) => { 38 | console.log('--------------------------------------------') 39 | console.log('ERR: Server Config invalid. Stopping start up') 40 | console.log(err) 41 | console.log('--------------------------------------------') 42 | }) 43 | 44 | app.startUpServer = () => { 45 | // Parsers for POST data 46 | app.use(bodyParser.json()) 47 | app.use(cors()) 48 | app.use(bodyParser.urlencoded({ extended: false })) 49 | 50 | app.use((req, res, next) => { 51 | const user = auth(req) 52 | // if (user === undefined || user.name !== ConfigData.basicAuth.name || user.pass !== ConfigData.basicAuth.pass) { 53 | // res.send('unauthorised access attempt') 54 | // return 55 | // } 56 | next() 57 | }) 58 | 59 | 60 | // Point static path to dist 61 | app.use(express.static(path.join(__dirname, ConfigData.app.static))) 62 | 63 | // Set our api routes 64 | app.use(ConfigData.app.apiUri, api) 65 | 66 | // Catch all other routes and return the index file 67 | app.get('*', (req, res) => { 68 | res.sendFile(path.join(__dirname, ConfigData.app.catchAllUri)) 69 | }) 70 | 71 | /** 72 | * Get port from environment and store in Express. 73 | */ 74 | const port = process.env.PORT || ConfigData.serverPort 75 | app.set('port', port) 76 | 77 | /** 78 | * Create HTTPS server, set up sockets and listen on all network interfaces 79 | */ 80 | 81 | var server 82 | var io 83 | 84 | pem.createCertificate({ days: 1, selfSigned: true }, (error, keys) => { 85 | if (error) { 86 | console.log('pem error: ' + error) 87 | } 88 | const sslOptions = { 89 | key: keys.serviceKey, 90 | cert: keys.certificate, 91 | requestCert: false, 92 | rejectUnauthorized: false, 93 | } 94 | app.use(bodyParser.json()) 95 | app.use(bodyParser.urlencoded({ 96 | extended: true, 97 | })) 98 | server = http.createServer(app) 99 | io = require('socket.io')(server) 100 | 101 | SocketCtrl.setupServerSocket(io) 102 | .then(() => { 103 | Logger.writeLog('n/a', 'Server Mode Socket Running', null, false) 104 | }) 105 | .catch((err) => { 106 | Logger.writeLog('001', 'Failed to start up Server Mode Socket', err, true) 107 | }) 108 | 109 | server.listen(port, () => { 110 | Logger.writeLog('n/a', `API running on http://localhost:${port}`, null, false) 111 | 112 | /** 113 | * Connect to mongoose 114 | */ 115 | 116 | mongoose.Promise = global.Promise 117 | const mongoDB = ConfigData.mongoDBUrl 118 | mongoose.connect(mongoDB) 119 | const db = mongoose.connection 120 | db.on('error', console.error.bind(console, 'MongoDB connection error:')) 121 | 122 | Logger.writeLog('MongoDB Connect', `Conected to MongoDB on ${mongoDB}`, null, false) 123 | 124 | Logger.writeLog('n/a', 'Sending start up notification email.', null, false) 125 | Logger.writeLog('Server Start Up', 'Start Up Complete @' + new Date().toISOString() + 126 | ', Polymorph Version: ' + ConfigData.version, null, true) 127 | 128 | /** 129 | * Setup the process handler 130 | */ 131 | 132 | ProcessHandler.setup() 133 | .then(() => { 134 | console.log('set up') 135 | }) 136 | .catch((err) => { 137 | // Logger.writeLog('error msg', '' , errObj, true) 138 | }) 139 | }) 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /server/start-dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | npm install 3 | nodemon -L --inspect=5555 ./src/server.js 4 | 5 | #npm run start 6 | -------------------------------------------------------------------------------- /server/test/db.serverMode.ctrl.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('expect') 4 | const rewire = require('rewire') 5 | 6 | let ServerModeCtrl = rewire('../server/lib/db/serverMode.ctrl') 7 | let ServerModeModel = require('../server/lib/db/serverMode.model') 8 | let ServerMessageModel = require('../server/lib/db/serverMessage.model') 9 | 10 | 11 | describe('[ServerModeCtrl]', () => { 12 | describe('(checkMode)', () => { 13 | beforeEach(() => { // reset the rewired functions 14 | ServerModeCtrl = rewire('../server/lib/db/serverMode.ctrl') 15 | }) 16 | it('should check the server mode', (done) => { 17 | const serverMode = 'SERVER_ON' 18 | const mockModel = { 19 | find: () => mockModel, 20 | select: () => mockModel, 21 | exec: () => { 22 | return new Promise((ful) => { ful(serverMode) }) 23 | }, 24 | } 25 | ServerModeCtrl.__set__('ServerModeModel', mockModel) 26 | ServerModeCtrl.checkMode() 27 | .then((mode) => { 28 | expect(mode).toBe(serverMode) 29 | done() 30 | }) 31 | }) 32 | it('should catch rejected errors', (done) => { 33 | const mockErr = 'REJECT' 34 | const mockModel = { 35 | find: () => mockModel, 36 | select: () => mockModel, 37 | exec: () => { 38 | return new Promise((ful, rej) => rej(mockErr)) 39 | }, 40 | } 41 | ServerModeCtrl.__set__('ServerModeModel', mockModel) 42 | ServerModeCtrl.checkMode() 43 | .catch((err) => { 44 | expect(err).toBe(mockErr) 45 | done() 46 | }) 47 | }) 48 | }) 49 | describe('(checkMessage)', () => { 50 | beforeEach(() => { // reset the rewired functions 51 | ServerModeCtrl = rewire('../server/lib/db/serverMode.ctrl') 52 | }) 53 | it('should check the server mode', (done) => { 54 | const serverMode = 'SERVER_ON' 55 | const mockModel = { 56 | find: () => mockModel, 57 | exec: () => { 58 | return new Promise((ful) => { ful(serverMode) }) 59 | }, 60 | } 61 | ServerModeCtrl.__set__('ServerMessageModel', mockModel) 62 | ServerModeCtrl.checkMessage() 63 | .then((mode) => { 64 | expect(mode).toBe(serverMode) 65 | done() 66 | }) 67 | }) 68 | it('should catch rejected errors', (done) => { 69 | const mockErr = 'REJECT' 70 | const mockModel = { 71 | find: () => mockModel, 72 | select: () => mockModel, 73 | exec: () => { 74 | return new Promise((ful, rej) => rej(mockErr)) 75 | }, 76 | } 77 | ServerModeCtrl.__set__('ServerMessageModel', mockModel) 78 | ServerModeCtrl.checkMessage() 79 | .catch((err) => { 80 | expect(err).toBe(mockErr) 81 | done() 82 | }) 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /server/test/lib.error-handler.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('expect') 4 | const rewire = require('rewire') 5 | const sinon = require('sinon') 6 | 7 | let ErrorHandler = rewire('../server/lib/error-handler') 8 | let Validator = rewire('../server/lib/options-validator') // eslint-disable-line 9 | 10 | describe('[ErrorHandler]', () => { 11 | describe('(handleError)', () => { 12 | beforeEach(() => { // reset the rewired functions 13 | ErrorHandler = rewire('../server/lib/error-handler') 14 | Validator = rewire('../server/lib/options-validator') 15 | }) 16 | it('should catch rejected params', (done) => { 17 | const mockStatusMessage = 'MOCK_STATUS' 18 | const mockErr = 'ERROR' 19 | const mockCode = 'CODE' 20 | const mockSendEmail = true 21 | const mockRes = 'RES' 22 | let mockParams = {} 23 | 24 | const mockStartValidation = (params, options) => { 25 | expect(params.statusMessage).toBe(mockStatusMessage) 26 | expect(params.err).toBe(mockErr) 27 | expect(params.code).toBe(mockCode) 28 | expect(params.sendEmail).toBe(mockSendEmail) 29 | expect(params.res).toBe(mockRes) 30 | mockParams = params 31 | return Promise.reject(mockErr) 32 | } 33 | 34 | const mockLogger = { 35 | writeLog: (errCode, statusMessage, error, sendEmail) => { 36 | expect(statusMessage).toBe('Incorrect Params - couldn\'t handle error') 37 | expect(error.error).toBe(mockErr) 38 | expect(error.originalError).toBe(mockParams) 39 | expect(errCode).toBe('ERR_HDL_001') 40 | expect(sendEmail).toBe(mockSendEmail) 41 | 42 | done() 43 | } 44 | } 45 | 46 | ErrorHandler.__set__('Validator.startValidation', mockStartValidation) 47 | 48 | ErrorHandler.__set__('Logger', mockLogger) 49 | 50 | ErrorHandler.handleError({ 51 | statusMessage: mockStatusMessage, 52 | err: mockErr, 53 | code: mockCode, 54 | sendEmail: mockSendEmail, 55 | res: mockRes, 56 | }) 57 | }) 58 | 59 | it('should pass on params', (done) => { 60 | const mockStatusMessage = 'ANOTHER_MOCK_STATUS' 61 | const mockErr = 'ERROR' 62 | const mockCode = 'CODE' 63 | const mockSendEmail = true 64 | const mockRes = { 65 | send: sinon.spy() 66 | } 67 | 68 | const mockStartValidation = (params, options) => { 69 | expect(params.statusMessage).toBe(mockStatusMessage) 70 | expect(params.err).toBe(mockErr) 71 | expect(params.code).toBe(mockCode) 72 | expect(params.sendEmail).toBe(mockSendEmail) 73 | expect(params.res).toBe(mockRes) 74 | return Promise.resolve() 75 | } 76 | 77 | const mockLogger = { 78 | writeLog: (errCode, statusMessage, error, sendEmail) => { 79 | expect(statusMessage).toBe(mockStatusMessage) 80 | expect(error.error).toBe(mockErr) 81 | expect(errCode).toBe('CODE') 82 | expect(sendEmail).toBe(mockSendEmail) 83 | sinon.assert.calledOnce(mockRes.send) 84 | done() 85 | } 86 | } 87 | 88 | ErrorHandler.__set__('Validator.startValidation', mockStartValidation) 89 | 90 | ErrorHandler.__set__('Logger', mockLogger) 91 | 92 | ErrorHandler.handleError({ 93 | statusMessage: mockStatusMessage, 94 | err: mockErr, 95 | code: mockCode, 96 | sendEmail: mockSendEmail, 97 | res: mockRes, 98 | }) 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /server/test/processHandler.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('expect') 4 | const rewire = require('rewire') 5 | const sinon = require('sinon') 6 | 7 | let ProcessHandler = rewire('../server/lib/processHandler') 8 | let mockLogger = { writeLog: sinon.spy() } 9 | 10 | describe('[ProcessHandler]', () => { 11 | describe('(setup)', () => { 12 | beforeEach(() => { // reset the rewired functions 13 | ProcessHandler = rewire('../server/lib/processHandler') 14 | mockLogger = { writeLog: sinon.spy() } 15 | ProcessHandler.__set__('Logger', mockLogger) 16 | }) 17 | 18 | it('should testRpc then startTimer', (done) => { 19 | ProcessHandler.testRpc = () => { 20 | return Promise.resolve() 21 | } 22 | const mockTimer = () => {} 23 | ProcessHandler.startTimer = mockTimer 24 | 25 | const timerSpy = sinon.spy(ProcessHandler, 'startTimer') 26 | const testRpcSpy = sinon.spy(ProcessHandler, 'testRpc') 27 | 28 | 29 | ProcessHandler.setup() 30 | .then(() => { 31 | sinon.assert.called(timerSpy) 32 | sinon.assert.called(testRpcSpy) 33 | done() 34 | }) 35 | }) 36 | 37 | it('should catch errors from testRpc, then writeLog', (done) => { 38 | ProcessHandler.testRpc = () => { 39 | return Promise.reject() 40 | } 41 | ProcessHandler.setTimerPaused = () => {} 42 | const pauseSpy = sinon.spy(ProcessHandler, 'setTimerPaused') 43 | 44 | ProcessHandler.setup() 45 | .catch(() => { 46 | sinon.assert.called(pauseSpy.withArgs(true)) 47 | sinon.assert.calledOnce(mockLogger.writeLog) 48 | 49 | done() 50 | }) 51 | }) 52 | }) 53 | 54 | 55 | // describe('(testRpc)', () => {}) 56 | 57 | describe('(startTimer)', () => { 58 | beforeEach(() => { // reset the rewired functions 59 | ProcessHandler = rewire('../server/lib/processHandler') 60 | }) 61 | it('should run setInterval with specific args', (done) => { 62 | this.clock = sinon.useFakeTimers() 63 | const intervalSpy = sinon.spy(global, 'setInterval') 64 | ProcessHandler.runTasks = () => {} 65 | const handlerSpy = sinon.spy(ProcessHandler, 'runTasks') 66 | 67 | ProcessHandler.startTimer() 68 | this.clock.tick(120000 + 1) // Same as server settings timerLength + 1 69 | sinon.assert.called(intervalSpy) 70 | sinon.assert.called(handlerSpy) 71 | this.clock.restore() 72 | done() 73 | }) 74 | }) 75 | 76 | // describe('(runTasks)', () => {}) 77 | 78 | // describe('(preflightChecks)', () => {}) 79 | 80 | describe('(setTimerPaused)', () => { 81 | beforeEach(() => { // reset the rewired functions 82 | ProcessHandler = rewire('../server/lib/processHandler') 83 | }) 84 | it('should unpause/pause the timer', () => { 85 | ProcessHandler.timerPaused = true 86 | 87 | ProcessHandler.setTimerPaused(false) 88 | expect(ProcessHandler.timerPaused).toBe(false) 89 | ProcessHandler.setTimerPaused(true) 90 | expect(ProcessHandler.timerPaused).toBe(true) 91 | }) 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /server/test/rpc.get-info.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('expect') 4 | const rewire = require('rewire') 5 | 6 | let Rpc = rewire('../server/lib/rpc/get-info') 7 | let mockLogger = { writeLog: () => {} } 8 | 9 | describe('[Rpc]', () => { 10 | describe('(getInfo)', () => { 11 | beforeEach(() => { // reset the rewired functions 12 | Rpc = rewire('../server/lib/rpc/get-info') 13 | mockLogger = { writeLog: () => {} } 14 | Rpc.__set__('Logger', mockLogger) 15 | }) 16 | it('should reject errors', (done) => { 17 | Rpc.navClient = { 18 | getInfo: () => { return Promise.reject({ code: -17 }) }, 19 | } 20 | 21 | Rpc.getInfo() 22 | .catch((err) => { 23 | expect(err.code).toBe(-17) 24 | done() 25 | }) 26 | }) 27 | 28 | it('should run the command with params and succeed', (done) => { 29 | Rpc.navClient = { 30 | getInfo: () => { return Promise.resolve({ code: 200 }) }, 31 | } 32 | Rpc.getInfo() 33 | .then((data) => { 34 | expect(data.code).toBe(200) 35 | done() 36 | }) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /server/test/rpc.get-new-address.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('expect') 4 | const rewire = require('rewire') 5 | 6 | let Rpc = rewire('../server/lib/rpc/get-new-address') 7 | 8 | describe('[Rpc]', () => { 9 | describe('(getNewAddress)', () => { 10 | beforeEach(() => { // reset the rewired functions 11 | Rpc = rewire('../server/lib/rpc/get-new-address') 12 | }) 13 | 14 | describe('(Rpc.getNewAddress)', () => { 15 | it('should run and get an address', (done) => { 16 | Rpc.navClient = { 17 | getNewAddress: () => { return 'address' }, 18 | } 19 | Rpc.getNewAddress().then((data) => { 20 | expect(data).toBe('address') 21 | done() 22 | }) 23 | }) 24 | 25 | it('should refill the keypool if the pool is empty, and return an address', (done) => { 26 | let keysAvailible = false 27 | Rpc.navClient = { 28 | getNewAddress: () => { 29 | if (keysAvailible) { 30 | return 'address' 31 | } 32 | const e = new Error() 33 | e.code = -12 34 | throw e 35 | }, 36 | walletPassphrase: () => {}, 37 | walletLock: () => {}, 38 | keypoolRefill: () => { 39 | keysAvailible = true 40 | }, 41 | } 42 | Rpc.getNewAddress().then((data) => { 43 | expect(data).toBe('address') 44 | done() 45 | }) 46 | }) 47 | 48 | it('should run getNewAddress and reject any error thats doesn\'t have code = -12', (done) => { 49 | Rpc.navClient = { 50 | getNewAddress: () => { throw new Error() }, 51 | } 52 | 53 | Rpc.getNewAddress().catch((err) => { 54 | expect(err instanceof Error && err.code !== -12).toBe(true) 55 | done() 56 | }) 57 | }) 58 | 59 | it('should return an error if getNewAddress errors twice', (done) => { 60 | let keypoolRefillCalled = false 61 | 62 | Rpc.keypoolRefill = () => { 63 | return new Promise((fulfill) => { 64 | keypoolRefillCalled = true 65 | fulfill() 66 | }) 67 | } 68 | 69 | let errorCount = 0 70 | Rpc.navClient = { 71 | getNewAddress: () => { 72 | errorCount += 1 73 | const e = new Error() 74 | e.code = -12 75 | throw e 76 | }, 77 | } 78 | 79 | Rpc.getNewAddress().catch((err) => { 80 | expect(err instanceof Error && errorCount === 2 && keypoolRefillCalled).toBe(true) 81 | done() 82 | }) 83 | }) 84 | 85 | it('should reject an error if keypoolRefill errors', (done) => { 86 | const mockError = { 87 | err: 'ERR', 88 | code: -12, 89 | } 90 | 91 | Rpc.keypoolRefill = () => { 92 | return new Promise((ful, rej) => { rej(mockError) }) 93 | } 94 | 95 | Rpc.navClient = { 96 | getNewAddress: () => { throw mockError }, 97 | } 98 | 99 | Rpc.getNewAddress() 100 | .catch((err) => { 101 | expect(err).toBe(mockError) 102 | done() 103 | }) 104 | }) 105 | }) 106 | 107 | describe('(Rpc.keypoolRefill)', (done) => { 108 | it('should run and catch any error', () => { 109 | Rpc.navClient = { 110 | walletPassphrase: () => {}, 111 | walletLock: () => {}, 112 | keypoolRefill: () => { throw new Error() }, 113 | } 114 | 115 | Rpc.keypoolRefill().catch((err) => { expect(err instanceof Error).toBe(true) }) 116 | }) 117 | 118 | it('should lock the wallet after filling pool', (done) => { 119 | let walletIsLocked 120 | Rpc.navClient = { 121 | walletPassphrase: () => { walletIsLocked = false }, 122 | walletLock: () => { walletIsLocked = true }, 123 | keypoolRefill: () => {}, 124 | } 125 | 126 | Rpc.keypoolRefill().then(() => { 127 | expect(walletIsLocked).toBe(true) 128 | done() 129 | }) 130 | }) 131 | }) 132 | }) 133 | }) 134 | --------------------------------------------------------------------------------